unity实现代码的复用,封装组件代码---单例模式

347 阅读8分钟

一、封装组件代码

1.前言

当我们创建一个面板的时候,实现面板淡出淡入的效果,需要获取Canvas Group组件中的通道Alpha值,通过改变Alpha的值(0到1淡出,1-0淡入的效果)来改变。

问题:这时候会发现当我们创建多个面板的时候,都需要重复上面的步骤(需要添加Canvas Group组件,获取Alpha值),这会显得很繁琐和代码的重复性很大。

解决:这时候我们可以通过创建一个基类(也就是父类),在父类中实现组件的添加,可以充分把继承和封装思想写入到untiy实践项目中

2.代码实现:

新建一个基类basePanle,声明需要添加的组件类型,以及需要修改的属性值。

在这里需要注意的是:声明抽象方法的必须在抽象类中生成,并且父类中的所有方法都应该可以被子类所重写的,即方法都要标注为virtual,采用虚函数声明。当然了,相应的子类可以访问父类中的方法,那么父类中的方法的修饰符的访问权限当然需要提升了,在这里我们使用protected修饰符来定义。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public abstract class basePanle : MonoBehaviour //基类
{
    //首先每个继承basePanle的物体是不是都要添加一个透明度CanvasGroup组件
    private CanvasGroup canvasGroup;
    //速度:淡入淡出的速度
    float alphaSpeed = 10f;
    //当对象被唤醒的时候,是不是要添加canvasGroup组件
    //游戏对象被激活,自动帮我们调用Awake方法
    protected virtual void Awake() //可以被继承,所以权限要确保子类(派生类)能够被访问;virtual派生类进行重写要用虚函数
    {
        //获取当前的canvasGroup组件  this指代当前的对象
        canvasGroup = this.gameObject.GetComponent<CanvasGroup>();
        //如果组件不存在,则需要添加一个组件
        if(canvasGroup == null)
        {
            this.gameObject.AddComponent<CanvasGroup>();

        }
    }
    protected virtual void Start()
    {
        Init();
    }

    //抽象方法  提示我们必须要实现这个方法  讲ui的时候可能 需要初始化一些参数,把它写到一个方法里面
    public abstract void Init();

}

再次新建一个子类Panle,来继承基类(basePanle)

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Panel : basePanle
{
    public override void Init()
    {

    }
}

将子类代码挂载到untiy中的面板panle中,当我们运行项目的时候,会自动运行面板挂载的panle脚本文件,在Panle脚本文件中重写了Init()方法,同时父类中监测到游戏对象被激活,自动帮我们调用Awake方法完成组件的创建。这也就是相对于通过代码的编写完成每次的重复组件的添加操作。

image.png

二、untiy的单例模式

1.前言

在上面我们简单实现了使用代码创建了一个组件,并成功实现了面板内容的淡入淡出效果,但是我们思考,如果以后需要创建多种不同的组件,还是通过代码实现该怎么去实现,并且太多的类型该如何去处理和定义,这时候我们引入了Unity中C#单例模式使用,通过单例模式我们可以实现集中管理好,现阶段理解的就是上述的意思

一、单例模式优点 单例模式核心在于对于某个单例类,在系统中同时只存在唯一一个实例,并且该实例容易被外界所访问; 意味着在内存中,只存在一个实例,减少了内存开销;

二、单例模式特点 只存在唯一一个实例; 提供统一对外访问接口,使得全局可对该单例的唯一实例进行访问; 自行实例化(私有构造函数,不允许外界对其进行实例化)。

2.实现

在这里我们通过代码实现一个简单的面板,里面包含文字和按钮,简单实现淡出这个UI登录界面的这个小案例。

导入图片的时候,图片拖入图像源之前要先把图像的纹理类型改为下面类型

image.png

我们先创建一个面板LoginPanle,里面包含登录按钮和图片,创建好后,将LoginPanle作为预制体拖入到UI文件夹中,创建好LoginPanle脚本文件,将脚本文件挂载到面板中。

LoginPanle脚本文件

继承了父类basePanle。

    using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LoginPanle : basePanle
{
    public override void Init()
    {
        
    }
}

UIManager脚本文件

这里面编写的代码现阶段理解较为复杂,可以理解为就是一个集中管理器,里面的内部实现逻辑就是实现创建面板的步骤,当以后有需要去创建新的类型的时候,也是通过这样的模板逻辑来接进行创建,在这里我们是通过字典来存储

    using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UIManager
{
    private Transform canvasTrans;
    //只实例化一次 静态成员变量返回该实例对象
    public static UIManager Instance = new UIManager();

    //创建一个字典来存储 string代表面板,basePanle代表脚本类,因为所有的派生类都可以使用基类basePanle表示
    private Dictionary<string,basePanle> palneDic = new Dictionary<string,basePanle>();
    //将每个面板拆分成一个个预制件,需要的时候使用,不需要的时候不显示

    //因为一旦canvas显示了说明ui管理器开始运作了,可以在构造方法里面进行初始化canvas
    //创建一个构造方法
    private UIManager()
    {
        //使用克隆一个canvas  注意的是这里要使用GameObject.的方式,因为这里没有继承MonoBehaviour
        GameObject canvas = GameObject.Instantiate(Resources.Load<GameObject>("UI/Canvas"));
        //面板应该是canvas的孩子:面板.transform.parent = canvas.transform
        canvasTrans = canvas.transform;
        //当我场景切换的时候可能界面还是保存的,也就说不能直接销毁
        GameObject.DontDestroyOnLoad(canvas); //跳转场景的时候不销毁canvas
    }
    //显示的方法:设计思想:我们等一下每一个预制体都挂载到一个面板脚本上,
    //这个面板脚本的积累就是basePanle,等一下面板名我们直接使用类名就好了
    //注意:预制件的名字最好和脚本文件的名字一样
    public T showpanle<T>() where T : basePanle //限制面板必须是继承basePanle
    {
        //通过泛型的名字来控制是否显示 那这里就要获取泛型的名字
        string pathName = typeof(T).Name; //获取泛型对应的名字
        //创建一个面板,确定是否在字典中
        if (palneDic.ContainsKey(pathName))
        {
            //如果有说明已经显示了,就直接返回
            return palneDic[pathName] as T; //需要的是T类型,但是字典里面存储的是basePanle类型,这时候需要使用语法糖转化成T类型
        }
        //如果没有显示过 那么需要创建一个对象来作为canvas的孩子,同时往字典添加键值对,字典中存储的都是当前系那是的面板
        GameObject panel = GameObject.Instantiate(Resources.Load<GameObject>("UI/"+pathName));
        //将当前的面板加入字典,成为其中的一员
        panel.transform.parent = canvasTrans;
        //添加字典当中
        palneDic.Add(pathName, panel.GetComponent<T>());
        //调用淡入方法
        panel.GetComponent<T>().showMe(); //T代表的就是它挂载的脚本

        return panel.GetComponent<T>();
    }
}

UI管理器的好处:

了UI管理器我们想要谁显示直接传入面板名字即可,不需要去关联太多面板。

编写Main脚本文件,将脚本文件挂载到主相机中,

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Main : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        UIManager.Instance.showpanle<LoginPanle>();
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}

basePanle脚本文件

    using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

public abstract class basePanle : MonoBehaviour //基类
{
    //创建一个委托 untiy内置了一个委托
    private UnityAction callback;
    //首先每个继承basePanle的物体是不是都要添加一个透明度CanvasGroup组件
    private CanvasGroup canvasGroup;
    //速度:淡入淡出的速度
    float alphaSpeed = 10f;
    //创建一个布尔值来记录当前的状态
    bool isShow = false;
    //当对象被唤醒的时候,是不是要添加canvasGroup组件
    //游戏对象被激活,自动帮我们调用Awake方法---添加组件
    protected virtual void Awake() //可以被继承,所以权限要确保子类(派生类)能够被访问;virtual派生类惊进行重写要用虚函数
    {
        //如果组件不存在,则需要添加一个组件
        if (canvasGroup == null)
        {
            this.gameObject.AddComponent<CanvasGroup>();

        }
        //获取当前的canvasGroup组件  this指代当前的对象
        canvasGroup = this.gameObject.GetComponent<CanvasGroup>();
    }
    protected virtual void Start()
    {
        Init();
    }

    //抽象方法  提示我们必须要实现这个方法  讲ui的时候可能 需要初始化一些参数,把它写到一个方法里面
    public abstract void Init();

    //实现淡出的方法,修饰符公开.透明度从0开始变化,最后变成1

    public void showMe()
    {
        canvasGroup.alpha = 0;
        isShow = true;
    }
    //实现淡入的方法 返回挂载的脚本
    //callback 可以方便的调用另一个脚本的方法
    public void hideMe(UnityAction callback) //隐藏完我们一般销毁游戏对象 在调用的地方传入一个回调函数,在这里面进行执行
    {
        
        canvasGroup.alpha = 1;
        isShow = false;
        //移除游戏对象
        if (callback != null) callback.Invoke(); //调用传进来的委托函数
    }

    private void Update()
    {
        Debug.Log($"1111:{canvasGroup.alpha}");
        //判断当前的显示状态
        if (isShow && canvasGroup.alpha <1)
        {
            //显示:透明度0-》1  一定要加上Time.deltaTime,否则一下子会超过1
            canvasGroup.alpha += alphaSpeed * Time.deltaTime;
            if (canvasGroup.alpha >= 1)
            {
                canvasGroup.alpha = 1;
            }
        }else if (!isShow &&canvasGroup.alpha>0)
        {
            //淡出效果 透明度1-》0
            canvasGroup.alpha -= alphaSpeed * Time.deltaTime;
            if (canvasGroup.alpha <= 0)
            {
                canvasGroup.alpha = 0;
            }
        }
    }


}

需要注意的是:Canvas画布和LoginPanle都是作为预制体通过代码实现的。并且当我们调用

    UIManager.Instance.showpanle<LoginPanle>();

showpanle方法里面的参数的名称要和你的预制体的名称是一致的,否则会找不到,并且加载资源的时候文件路径也要一一对应。

最终效果如下:

单例.gif

这时候我们发现明明面板成为预制体之前位置是好的,但是我们通过代码插入的时候会发现位置发生变化了,原因是因为我们在设置parent的时候,应该使用setparent方法来插入到字典

    panel.transform.SetParent(canvasTrans, false); //false表示相对于父元素

image.png

针对于# Unity中的刚体和碰撞体组件可以参考下面这篇文章 blog.csdn.net/weixin_4314…