Laya:基于Prefab的简单UI框架。

UI框架的功能:
方便快捷的对UI界面进行管理:如快速打开与关闭一个界面,防止相同界面多开等等。

核心代码有2个脚本,一个UIMgr,一个UIBase。
UIMgr的功能是管理UI界面。
UIBase的功能是:所有界面都要继承自UIBase,这样界面就可以直接方便地调用父类的方法,更好地复用代码。管理特殊前缀开头的节点名称,只有使用了特定字符串开头的节点才会被添加到UI管理的数据结构里。

UIMgr里面用到了一个SceneMgr,可以在我的上一篇文章里找到有它的介绍,UIMgr的场景方法和SceneMgr是一样的,同样是一个单例。

import SceneMgr from "./SceneMgr";

/**
 * UI界面管理器
 */
export default class UIMgr extends Laya.Script
{
    private uiPrefabMap=new Map<string,Laya.Prefab>();  //UI预制体字典:Map<string,Laya.Prefab>
    public static Instance: UIMgr;

    constructor()
    {
        super();
        UIMgr.Instance=this;
    }

    /**
     * 获取UI预制体的完整路径
     * @param uiPrefabName UI预制体名称
     */
    public GetPrefabPath(uiPrefabName:string):string
    {
        return "prefab/"+uiPrefabName+".prefab";
    }

    //=====================================< UIPanel >类型UI======================================= 

    /**
     * 找到名字对应的UI物体
     */
    public FindTargetUI<T extends Laya.UIComponent>(targetName:string,parent:Laya.Node):T
    {
        if(!parent){
            return null;
        }
        return parent.getChildByName(targetName)as T;
    }

    /**
     * 在场景中找到UI并销毁
     */
    public DestroyUI(uiName:string)
    {
        let sc2d=SceneMgr.Instance.GetCurSc2D();
        if(sc2d){
            let ui=sc2d.getChildByName(uiName)as Laya.UIComponent;            
            if(ui){
                ui.destroy(true);
            }
        }
    }

    /**
     * 设置UI visible
     */
    public SetUIVisible(uiName:string,visible:boolean)
    {
        let sc2d=SceneMgr.Instance.GetCurSc2D();
        if(sc2d){
            let ui=sc2d.getChildByName(uiName)as Laya.UIComponent;
            if(ui){
                ui.visible=visible;
            }
        }
    }

    /**
     * 加载并打开UI面板(面板类型UI,不能打开多个)
     * @param uiCtlerScript  控制此界面的脚本类
     * @param callback 加载完回调(Laya.UIComponent)=>{}
     * @param parent 父级节点
     * @param isOnlyOne 是否只能存在一个
     */
    public OpenUI(uiCtlerScript:any,callback:Function=(param:any)=>{},parent?:Laya.Node,isOnlyOne=true)
    {
        let uiName=uiCtlerScript.name;
        if(!parent){
            parent=SceneMgr.Instance.GetCurSc2D();
        }
        if(!parent){
            return;
        }
        if(parent.getChildByName(uiName)){
            if(isOnlyOne==true){
                return;
            }
        }
        if(this.uiPrefabMap.has(uiName))
        {
            let uiPrefab=this.uiPrefabMap.get(uiName) as Laya.Prefab;
            this.OpenUICommonOp(uiPrefab,uiCtlerScript,parent,callback);
        }
        else
        {
            this.LoadUIPrefab(uiName,(uiPrefab:Laya.Prefab)=>
            {
                this.OpenUICommonOp(uiPrefab,uiCtlerScript,parent,callback);
            });
        }
    }

    /**
     * 从字典中移除UI预制体并清理单个资源(最好在切换场景时使用,清理掉下个场景不再使用的UI预制体)
     * @param uiCtlerScript 控制此界面的脚本类(ui索引,预制体的名称)
     */
    public ClearRes(uiCtlerScript:any)
    {
        let k=uiCtlerScript.name;
        if(!this.uiPrefabMap.has(k))
        return;
        this.uiPrefabMap.delete(k);
        Laya.LoaderManager.prototype.clearRes(this.GetPrefabPath(k));
        Laya.Resource.destroyUnusedResources();
    }

    /**
     * 加载UI预制体
     * @param uiName 预制体UI名称
     * @param callback 加载完回调
     */
    private LoadUIPrefab(uiName:string,callback:Function=(uiPrefab:any)=>{})
    {
        let uiPath=this.GetPrefabPath(uiName);
        Laya.loader.load(uiPath,Laya.Handler.create(this,(uiPrefab:Laya.Prefab)=>
        {    
            if(!uiPrefab){
                console.error("不存在目标预制体",uiName);
                return;
            }
            this.uiPrefabMap.set(uiName,uiPrefab);
            callback(uiPrefab);
        }));
    }

    /**
     * 加载面板的共有操作
     */
    private OpenUICommonOp(uiPrefab:Laya.Prefab,uiCtlerScript:any,parent:Laya.Node,callback:Function=(ui:any)=>{})
    {
        let uiName=uiCtlerScript.name;
        let ui=uiPrefab.create()as Laya.UIComponent;
        parent.addChild(ui);
        ui.name=uiName;
        this.AddSrcToNode(uiCtlerScript,ui);
        callback(ui);
    }
	
	private AddSrcToNode(src:any,targetNode:Laya.Node)
    {
        if(targetNode.getComponent(src))
        return;
        targetNode.addComponent(src);
    }
 
     /**
      * 打印字典
      */
    public LogUIMapInfo()
    {
        if(this.uiPrefabMap.size==0||!this.uiPrefabMap==null)
        {
            console.log("UI预制体资源Map为空");
            return;            
        }
        for (let [key, value] of this.uiPrefabMap) 
        {
            console.log("key:",key,"===","value:",value);
        }
    }
}

UIBase脚本:
当UI界面里的节点使用了prefixArray里的前缀命名时(如:imgTest,txtPlayerName等等),此节点会被添加到allUIDic里,在脚本里可以通过名称获取目标节点。prefixArray里可以自定义添加或删除你的节点前缀。

export default class UIBase extends Laya.Script {

    public allUIDic: Map<string, Laya.Node> = new Map<string, Laya.Node>();
    private prefixArray: string[] = ["img", "btn", "txt", "list", "box","hsld","ti"]; //需要加入UI字典的UI前缀

    constructor() {
        super();
    }

    onAwake() {
        this.SetAllUINodesDic();
    }

    /**
     * 销毁当前界面UI
     * @param cbOnClose 关闭时的回调
     */
    public CloseUI() {
        this.owner.destroy(true);
    }

    private GetNodeByMap<T extends Laya.Node>(nodeName:string,map:Map<string,Laya.Node>):T
    {
        if(!map.has(nodeName)){
            return null;
        }
        return map.get(nodeName) as T;
    }

    /**
     * 添加点击事件带有声音
     * @param btName 按钮名称
     * @param callback 回调
     * @param needPlayClickSound 是否播放点击音效
     * @param clickSoundPath 音效地址
     */
    public AddBtnEvent(btName: string, callback: Function,needPlayClickSound=true,clickSoundPath?:string) {
        let bt: Laya.Button = this.GetBtn(btName);
        if (!bt) return;
        bt.on(Laya.Event.CLICK,this,callback)
    }

    /**
     * 通过文本名称获取Tetx组件
     * @param txtName 文本组件名称
     * @returns Laya.Text
     */
    public GetTxt(txtName: string): Laya.Text {
        return this.GetNodeByMap<Laya.Text>(txtName, this.allUIDic);
    }

    /**
     * 通过文本名称获取Image组件
     * @param imgName Image组件名称
     * @returns Laya.Image
     */
    public GetImg(imgName: string): Laya.Image {
        return this.GetNodeByMap<Laya.Image>(imgName, this.allUIDic);
    }

    /**
     * 通过文本名称获取Image组件
     * @param btnName Image组件名称
     * @returns Laya.Image
     */
    public GetBtn(btnName: string): Laya.Button {
        return this.GetNodeByMap<Laya.Button>(btnName, this.allUIDic);
    }

    /**
     * 通过文本名称获取List组件
     * @param listName List组件名称
     * @returns Laya.List
     */
    public GetList(listName: string): Laya.List {
        return this.GetNodeByMap<Laya.List>(listName, this.allUIDic);
    }

    /**
     * 通过文本名称获取Box组件
     * @param boxName Box组件名称
     * @returns Laya.Box
     */
    public GetBox(boxName: string): Laya.Box {
        return this.GetNodeByMap<Laya.Box>(boxName, this.allUIDic);
    }

    /**
     * 通过文本名称获取HSlider组件
     */
    public GetHSlider(name: string): Laya.HSlider {
        return this.GetNodeByMap<Laya.HSlider>(name, this.allUIDic);
    }

    /**
     * 通过文本名称获取TextInput组件
     */
    public GetTextInput(name: string): Laya.TextInput {
        return this.GetNodeByMap<Laya.TextInput>(name, this.allUIDic);
    }

    /**
     * 通过泛型名称获取UI组件
     * @param uiName UI组件名称
     * @returns Laya.UIComponent
     */
    public GetUIByT<T extends Laya.UIComponent>(uiName: string): T {
        return this.GetNodeByMap<T>(uiName, this.allUIDic);
    }

    /**
     * 设置UIvisible
     */
    public SetVisible<T extends Laya.UIComponent>(uiName: string,visible:boolean)
    {
        this.GetUIByT<T>(uiName).visible=visible;
    }

    /**
     * 设置文本内容
     */
    public SetText(uiName: string,content:string)
    {
        this.GetTxt(uiName).text=content;
    }

    /**
     * 设置图片的skin
     */
    public SetImgSkin(imgName:string,skin:string)
    {
        this.GetImg(imgName).skin=skin;
    }

    /**
     * 设置按钮的skin
     */
    public SetBtnSkin(btnName:string,skin:string)
    {
        this.GetImg(btnName).skin=skin;
    }

    //--------------------------------------------添加UI到字典-------------------------------------------------

    /**
     * 检查目标ui是否需要加入字典
     */
    private CheckNeedAddToDic(uiName: string) {
        for (let i = 0; i < this.prefixArray.length; i++) {
            if (uiName.startsWith(this.prefixArray[i])) {
                return true;
            }
        }
        return false;
    }

    /**
     * 将所有的UI节点装入字典(每个ui节点不能重名)
     */
    public SetAllUINodesDic() {
        this.allUIDic = this.GetAllChildrenMap(this.owner);
    }

    //获取目标节点的所有子节点,将所有子节点放入数组并返回
    private GetChildNodesArray(target: Laya.Node): Laya.Node[] {
        let nodeArray: Laya.Node[] = [];
        for (let i = 0; i < target.numChildren; i++) {
            let node = target.getChildAt(i);
            if (node) {
                nodeArray.push(node);
            }
        }
        return nodeArray;
    }

    //递归获取目标节点的所有子孙节点,并将他们全部放入数组并返回
    private FindAndGetAllChildren(parentNode: Laya.Node, outNodesArray: Laya.Node[]): Laya.Node[] {
        if (parentNode.numChildren > 0) {
            let nodeArray = this.GetChildNodesArray(parentNode);
            nodeArray.forEach(node => {
                if (this.CheckNeedAddToDic(node.name) == true) {
                    outNodesArray.push(node);
                }
                if (this.GetChildNodesArray(node).length > 0) {
                    this.FindAndGetAllChildren(node, outNodesArray);
                }
                else {
                    return outNodesArray;
                }
            });
        }
        return null;
    }

    //构建一个数组来存放获取的所有节点并返回此数组
    private GetAllChildrenArray(parentNode: Laya.Node): Laya.Node[] {
        let allChildrenArray: Laya.Node[] = [];
        this.FindAndGetAllChildren(parentNode, allChildrenArray);
        return allChildrenArray;
    }

    //将所有节点封装到字典里,方便获取
    private GetAllChildrenMap(parentNode: Laya.Node): Map<string, Laya.Node> {
        let allChildrenArray = this.GetAllChildrenArray(parentNode);
        let map = new Map();
        for (let i = 0; i < allChildrenArray.length; i++) {
            if (!map.has(allChildrenArray[i].name)) {
                map.set(allChildrenArray[i].name, (allChildrenArray[i]));
            }
        }
        return map;
    }

    /**
      * 打印UI字典
      */
    public LogUIMap() {
        if (this.allUIDic.size== 0||!this.allUIDic){
            console.log("UI预制体资源Map为空");
            return;
        }
        console.log("UI节点个数:", this.allUIDic.size);
        for (let [key, value] of this.allUIDic) {
            console.log("key:", key, "===", "value:", value);
        }
    }
}

UI框架使用方法:

在创建UI界面前先要说一下场景的创建,我创建场景使用的是View而不是Scene,因为Scene不好做分辨率适配。
在这里插入图片描述
场景创建完成后要把View场景的设计分辨率和四个边距点设置为0。我使用的是1136x640的分辨率。
在这里插入图片描述
UI界面制作的流程:

比如现在要添加一个名称为TestUIPanel的界面。
我一般创建一个新UI界面是这样做的:
首先创建一个Image名称叫做:TestUIPanel
在这里插入图片描述
在这里插入图片描述
把Image的skin设置为空,四个边距点设置为0,因为这个Image会作为UI预制体的底板。
如果你的UI需要有黑色半透明背景,可以添加一个Box组件,然后设置它的颜色和透明度,但是4个边距点也要设置为0。
在这里插入图片描述
在这里插入图片描述
然后在下面添加了一个按钮,点击可以关闭界面。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
最后就是保存此UI界面为预制体既可。
在这里插入图片描述
Laya默认的UI预制体是在这个目录下的在这里插入图片描述
我加载时使用的也是默认目录。
双击打开UI预制体:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
可以点击设置页面调整UI预制体的默认场景大小。
目前UI界面就创建完成了。

接下来是编写界面的控制和打开的代码:
创建一个和界面名称一样的脚本,这里必须要保证界面名称和脚本名称一致,因为我是通过把脚本转为字符串去读取的UI预制体。
在这里插入图片描述
在这里插入图片描述
接着把创建好的脚本继承自UIBase,如果要使用onAwake方法必须调用一下基类的super().onAwake或是执行一下this.SetAllUINodesDic();方法。

接下来就是UI的逻辑代码:
在这里插入图片描述
打开此界面:
在这里插入图片描述
打开界面的方法有四个参数:
@param uiCtlerScript — 控制此界面的脚本类
@param callback — 加载完回调(Laya.UIComponent)=>{}
@param parent — 父级节点,不传则使用当前2d场景作为父节点
@param isOnlyOne — 是否只能存在一个,false时可以打开多个相同界面

运行运行:
在这里插入图片描述
点击关闭按钮:
在这里插入图片描述
以上就是此框架的大概内容,代码也比较简单,可以自己具体看一下。

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雨幕枫陵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值