Unity3D 设计模式学习之组合模式

前言


对应游戏中的UI部分,可以使用分层式管理,将2D组件按照功能关系按层次摆放,是比较容易了解、设计和修改的。

分层式管理一般也称树状结构,常用于软件实现和应用中的一种结构。

GOF对于组合模式Composite的定义是:
“将对象以树状结构组合,用以表现部分-全体的层次关系。组合模式让客户端在操作 各个对象或组合和组合对象是一致的”

树状结构来组合各个对象,所以实现上包含了根结点和叶结点的概念。而”根节点”中会包含叶节点的对象,所以当根节点被删除时,叶节点也会被一起删除,并且希望对于”根节点”和“叶节点”在操作方法上能够一致。

这表示,这两种节点都是继承自同一个操作界面,能够对根节点调用的操作,同样能在叶节点上使用。

游戏对象中的分层管理

在Unity中每个GameObject对象都有一个Transform组件,这个组件提供了几个和游戏对象分层操作有关的方法和变量。
变量:
childCount:代表子组件数量
parent:代表父组件中的Transform对象引用

方法:
DetachChildren:解除所有子组件与本身的关联
Find: 寻找子组件
GetChild: 使用Index的方式取回子组件
IsChildOf: 判断某个Transform对象是否为其子组件
SetParent:设置某个Transform对象为其父组件

再仔细分析,则可以将Unity3D的Transform类当成是一个通用类,因为它并不明显得可以察觉出其下又被再分成“目录节点”或是单纯的“单的终端节点”

其实应该说,Transform类完全符合组合模式的需求:“让客户端在操作各个对象或组件时是一致的”。
因此对于场景上所有的游戏对象GameObject,可以不管它们最终代表的什么,对于所有操作都能正确反应。

游戏用户界面

在《P级阵地》中,每一个主要游戏功能属于IGameSystem的子类,这些子类负责实现《P级阵地》中不同的游戏需求和功能,而它们每一个都会利用组合的方式,成为PBaseDefenseGame类的成员。

public abstract class IUserInterface{
    protected PBaseDefenseGame m_PBDGame = null;
    protected GameObject m_RootUI = null;
    private bool m_bActive = true;
    public IUserInterface(PBaseDefenseGame PBDGame){
        m_PBDGame = PBDGame;
    }
    public bool IsVisible(){
        return m_bActive;
    }
    public virtual void Show(){
        m_RootUI.SetActive(true);
        m_bActive = true;
    }
    public virtual void Hide(){
        m_RootUI.SetActive(false);
        m_bActive = false;
    }

    public virtual void Initalize(){}
    public virtual void Release(){}
    public virtual void Update(){}
}

游戏中使用的4个界面偶是IUseInterface的子类,并且使用组合的方式成为PBaseDefenseGame类的成员。
UML类图

扫描二维码关注公众号,回复: 3082236 查看本文章

类结构图如上= = 就如同游戏系统那样,对内可以通过PBaseDefenseGame类中的中介者模式来通知其他系统或界面,对外也可以通过PBaseDenfenseGame类的外观模式让客户端存取和更新用户界面相关的功能
兵营界面的实现

public class CampInfoUI:IUserInterface{
    //...界面组件
    private Text m_AliveCountTxt = null;
    private Text m_CampLvTxt = null;
    private Text m_WeaponLvTxt = null;
    private Image m_CampImage = null;

    public CampInfoUI(PBaseDefenseGame PBDGame):base(PBDGame){
        Initialize();
    }

    public override void Initialize(){
        m_RootUI = UITool.FindUIGameObject("CampInfoUI");
        //显示的信息
        //兵营名称
        m_CampNameTxt = UITool.GetUIComponent<Text>(m_RootUI,"CampNameText");
        //兵营图
        m_CampImage = UITool.GetUIComponent<Image>(m_RootUI,"CampIcon");
    }

    public void ShowInfo(Icamp Camp){
        Show();
        m_Camp = Camp;
        //名称
        m_CampNameTxt.text = m_Camp.GetName();
        //...以下都为显示UI信息
    }
}

这里的UI脚本并有继承MonoBehaviour 而是继承UI接口,通过构造函数来初始化UI脚本,
然后定义一个公有Show方法来做显示和更新

这里还自定义了UI有关的查找工具类,以便查找UI物体脚本。

public static class UITool{
    //场景上的2D画布对象
    private static GameObject m_CanvasObj = null;

    public static GameObject FindUIGameObject(string UIName){
        if(m_CanvasObj == null)
            m_CanvasObj = UnityTool.FindGameObject("Canvas");
        if(m_CanvasObj == null)
            return null;
        return UnityTool.FindChildGameObject(m_CanvasObj,UIName);
    }   

    public static T GetUIComponent<T>(GameObject Container,string UIName) where T : UnityEngine.Component {

        //找出子对象
        GameObject childGameObject = UnityTool.FindChildGameObject(Container,UIName);
        if(ChildGameObject == null) 
            return null;

        T tempObj = ChildGameObj.GetComponent<T>();
        if(tempObj == null){
            Debug.LogWarning("组件["+UIName+"]不是["+typeof(T)+"]");
        }
        return tempObj;
    }
}

以上是UI工具类
UI工具类用到了自定义的UnityTool工具类

public static class UnityTool{

    //找到场景上的对象
    public static GameObject FindGameObject(string GameObjectName){
        //查找场景上的对象
        GameObject pTmpGameObj = GameObject.Find(GameObjectName);

        if(pTmpGameObj == null) {
            Debug.LogWarning("场景中找不到GameObject["+GameObjectName+"]对象");
            return null;
        }
        return pTmpGameObj;
    }

    public static GameObject FindChildGameObject(GameObject Container,string gameObjectName){
        if(Container == null){
            Debug.LogError("无父类对象");
            return null;
        }
        Transform pGameObjectTF = null;
        //是不是Container本身
        if(Container.name == gameobjectName)
        pGameObjectTF = Container.transform;

        else{
            Transfrom [] allChildren = Container.transform.GetComponentsInChildren<Transform>();
            foreach(Transform child in allChildren){
                if(child.name== gameObjectName)

                    if(pGameObject == null)
                        pGameObjectTF = child;
                    else
                        Debug.LogWarning("Container["+Container.name+"]下找出重子组件名称["+gameObjectName+"]");
        }

        if(GameObjectTF == null){
            Debug.LogError("组件["+Container.name+"]找不到子组件["+gameObjectName+"]");
            return null;
        }
        return pGameObjectTF.gameObject;
    }
}

UnityTool工具类中,FindChildGameObject 方法是用来搜索某游戏对象下的子对象。
对重复命名的问题加以防呆(防止出错的处理)以便在开发时查找调试。

并且因为是这查找中使用,虽然需要遍历一遍,但是组件可以只获取一次,在后期游戏运行状态下并不会一直使用搜索界面组件的功能。

这也是大家常说的:不要频繁调用GetComponent方法获得组件,一般在Awake Start函数获取一遍。

m_CampNameTxt = UIToo.GetUIComponent<Text>(m_RootUI,"CampNameText");

接下来 CampInfoUI有一个公有ShowInfo方法,根据功能需求来控制显示UI信息

public void ShowInfo(ICamp camp){
    Show();
    m_Camp = camp;
    m_CampNameTxt.text = m_Camp.GetName();
    m_TrainConstText.text = string.Format("AP:{0}",m_Camp.GetTrainCost());
    //训练中信息
    ShowOnTranInfo();
    //Icon
    IAssetFactory Factory = PBDFactory.GetAssetFactory();
    m_CampImage.sprite = Factory.LoadSprite(m_Camp.GetIconSpriteName());

    if(m_Camp.GetLevel() <= 0) {
        EnableLevelInfo(false);
    }else{
        m_CampLevelInfo(true);
        m_CampLvTxt.text = string.Format("等级:"+m_Camp.GetLevel());
        m_WeaponLvTxt.text = string.Format("武器等级:"+m_Camp.GetWeaponLevel());
    }
}
总结

使用组合模式的优点:
界面与功能分离,更具移植性
工作切分更容易,当脚本移除,就可以让UI设计交由美术和企划组装
界面更改不影响项目:只要维持组件名称不变,界面的更改就不容易影响到游戏现有程序功能的运行。

缺点:
组件名称重复,如果没有将层级切分好,就容易出现该问题,在工具名称添加警告可以解决。
组件更名不易:组件名需要通过字符串来查找,界面组件一旦不能获取,则会出现null值,和不正确的场景,应对的方法同样是在UnityTool中添加查找失败的警告。

猜你喜欢

转载自blog.csdn.net/liaoshengg/article/details/82346252