iT邦幫忙

2022 iThome 鐵人賽

DAY 18
0

今天我想重構的,是下圖中那個更新麵包獲取數的UI程式
https://ithelp.ithome.com.tw/upload/images/20221002/20151894OK2fKQKU7x.png

重構前的程式碼

GetFoodAndFleeGameEndCondition.cs

private void Awake() 
{
    ...
    m_StageTooltipGUI.SetFoodCount(m_CurrentPlayerHaveFoods, m_NumberOfNeedFoods); 
    ...
}

private void GetFoodEvent(List<Unit> stayUnit, BreadUnitObstacle bread) 
{ 
    foreach(Unit unit in stayUnit) 
    { 
        if(unit.PlayerNumber == (int)UnitType.Player) 
        { 
            m_StageTooltipGUI.SetFoodCount(m_CurrentPlayerHaveFoods, m_NumberOfNeedFoods); 
        }
        ...
    } 
}

雖然這看起來還好,但我希望他今天依賴的是像是CellGrid這種主要作為全系統溝通介面的程式,而不是直接使用StageTooltipGUI去做呼叫和更改,才不會之後的引用越來越亂。除此之外,要是今天這種想法改成訂閱制的話,要是今天的條件判斷不需要麵包數量的話,系統就不用多跑一個根本不存在的事件。

重構後的程式碼

FoodCountEventArgs.cs

using System; 

namespace LinXuan.Common.GameEvent 
{ 
    public class FoodCountEventArgs : EventArgs //此FoodCountEventArgs是用來作為傳遞食物數量的數值的暫時存放點
    { 
        public int FoodCollectedCount{ get; private set; } 
        public int FoodOfNeedCount { get; private set; } 

        public FoodCountEventArgs(int foodCollectedCount, int foodOfNeedCount) 
        { 
            FoodCollectedCount = foodCollectedCount; 
            FoodOfNeedCount = foodOfNeedCount; 
        } 
    } 
}

IGameEventObserver.cs

namespace LinXuan.Common.GameEvent
{
    public interface IGameEventObserver
    {
        void SetSubject(IGameEventSubject subject);
        void ObserverUpdate();
    }
}

FoodCollectedObserverGUI.cs

using LinXuan.Common.GameEvent; 
using TbsFramework.Grid; 

namespace LinXuan.TBSF.GameEvent 
{ 
    //用來更新m_CellGrid的UI更新
    public class FoodCollectedObserverGUI : IGameEventObserver 
    { 
        private BreadUnitCollectedSubject m_BreadUnitCollectedSubject = null; 
        private CellGrid m_CellGrid = null; 

        public FoodCollectedObserverGUI(CellGrid cellGrid) 
        { 
            m_CellGrid = cellGrid; 
        } 

        public void ObserverUpdate() 
        { 
            m_CellGrid.GUIUpdate(m_BreadUnitCollectedSubject.FoodCountEventArgs); 
        } 

        public void SetSubject(IGameEventSubject subject) 
        { 
            m_BreadUnitCollectedSubject = subject as BreadUnitCollectedSubject; 
        } 
    } 
}

IGameEventSubject.cs

using System.Collections.Generic;

namespace LinXuan.Common.GameEvent
{
    public abstract class IGameEventSubject
    {
        private List<IGameEventObserver> m_Observers = new List<IGameEventObserver>();
        private System.Object m_Parameter = null;


        public void Attach(IGameEventObserver observer)
        {
            m_Observers.Add(observer);
        }

        public void Detach(IGameEventObserver observer)
        {
            m_Observers.Remove(observer);
        }

        public void Notify()
        {
            foreach (IGameEventObserver observer in m_Observers)
                observer.ObserverUpdate();
        }

        public virtual void SetParameter(object parameter)
        {
            m_Parameter = parameter;
        }
    }
}

BreadUnitCollectedSubject.cs

namespace LinXuan.Common.GameEvent 
{ 
    //用來訂閱與獲取麵包數量的資料
    public class BreadUnitCollectedSubject : IGameEventSubject 
    { 
        public FoodCountEventArgs FoodCountEventArgs { get; private set; } 
        public BreadUnitCollectedSubject() 
        { 
        } 
        public override void SetParameter(System.Object foodCountEventArgs) 
        { 
            base.SetParameter(foodCountEventArgs); 
            FoodCountEventArgs = foodCountEventArgs as FoodCountEventArgs; 
            Notify(); 
        } 
    } 
}

GameEventSystem.cs

using LinXuan.TBSF; 
using LinXuan.TBSF.Enums; 
using System.Collections.Generic; 
using TbsFramework.Grid; 
using UnityEngine; 

namespace LinXuan.Common.GameEvent 
{ 
    //創建事件的地方,雖然有其他事件不過這次只實作出麵包數量更新的部分
    public class GameEventSystem 
    { 
        private Dictionary<GameEventType, IGameEventSubject> m_GameEvents = new Dictionary<GameEventType, IGameEventSubject>(); 

        public GameEventSystem(CellGrid cellGrid)
        { 
            Initialize(); 
        } 

        private void Initialize() 
        { 
        } 

        public void Release() 
        { 
            m_GameEvents.Clear(); 
        } 

        public void RegisterObserver(GameEventType gameEventType, IGameEventObserver observer) 
        { 
            IGameEventSubject subject = GetGameEventSubject(gameEventType); 
            if(subject != null) 
            { 
                subject.Attach(observer); 
                observer.SetSubject(subject); 
            } 
        } 

        private IGameEventSubject GetGameEventSubject(GameEventType gameEventType) 
        { 
            if (m_GameEvents.ContainsKey(gameEventType)) 
                return m_GameEvents[gameEventType]; 
            IGameEventSubject subject = null; 
            switch (gameEventType) 
            { 
                case GameEventType.EnemyUnitKilled: 
                    subject = new EnemyKilledSubject(); 
                    break; 
                case GameEventType.PlayerUnitKilleds: 
                    subject = new PlayerKilledSubject(); 
                    break; 
                case GameEventType.GetFoodUnit: 
                    subject = new FoodUnitCollectedSubject(); 
                    break; 
                case GameEventType.GetBreadUnitObstacle: 
                    subject = new BreadUnitCollectedSubject(); 
                    break; 
                default: 
                    Debug.LogError("Can't create["+gameEventType+"] subject class."); 
                    return null; 
            } 
            m_GameEvents.Add(gameEventType, subject); 
            return subject; 
        } 

        public void NotifySubject(GameEventType gameEventType, System.Object parameter) 
        { 
            if (m_GameEvents.ContainsKey(gameEventType) == false) 
                return; 
            m_GameEvents[gameEventType].SetParameter(parameter); 
        } 
    } 
}

CellGrid.cs

private GameEventSystem m_GameEventSystem;

public event EventHandler<FoodCountEventArgs> FoodCollectedUpdate;

...

private void Awake()
{
    UIInputObserver.CellGrid = this;
    m_GameEventSystem = new GameEventSystem(this);
}

public void RegisterGameEvent(GameEventType gameEventType, IGameEventObserver observer)
{
    m_GameEventSystem.RegisterObserver(gameEventType, observer);
}

public void NotifyGameEvent(GameEventType gameEventType, System.Object parameter)
{
    m_GameEventSystem.NotifySubject(gameEventType, parameter);
}

public void FoodCollectedGUIUpdate(FoodCountEventArgs foodCountEventArgs)
{
    FoodCollectedUpdate.Invoke(this, foodCountEventArgs);
}

CALGUIController.cs

private void Awake() 
{ 
    CellGrid.FoodCollectedUpdate += FoodCollectedUpdate; 
}

...

private void FoodCollectedUpdate(object sender, FoodCountEventArgs e) 
{ 
    m_StageTooltipGUI.SetFoodCount(e.FoodCollectedCount, e.FoodOfNeedCount); 
}

GetFoodAndFleeGameEndCondition.cs

private void Awake() 
{ 
    UIInputObserver.CellGrid.RegisterGameEvent(LinXuan.TBSF.Enums.GameEventType.GetBreadUnitObstacle, new FoodCollectedObserverGUI(UIInputObserver.CellGrid)); 
}

private void GetFoodEvent(List<Unit> stayUnit, BreadUnitObstacle bread) 
{ 
    foreach(Unit unit in stayUnit) 
    { 
        if(unit.PlayerNumber == (int)UnitType.Player) 
        { 
            ...
            UIInputObserver.CurrentPlayerHaveFoods = m_CurrentPlayerHaveFoods; 
            UIInputObserver.CellGrid.NotifyGameEvent(LinXuan.TBSF.Enums.GameEventType.GetBreadUnitObstacle, new FoodCountEventArgs(m_CurrentPlayerHaveFoods, m_NumberOfNeedFoods)); 
            ...
        }
        ...
    }
}

經過這樣的更改後,GetFoodAndFleeGameEndCondition的更新獲取麵包數量的方式就由StageTooltipGUI改成CellGrid,於此同時要是今天沒有需要去做麵包數量的判斷,那系統就擁有不會去訂閱此事件的能力了。

補充:

問:為什麼CellGrid是用UIInputObserver獲取?
答:作者的CellGrid本身不是Singleton模式,所以目前才暫時用UIInputObserver的方式獲取,下面是裡面的程式碼

UIInputObserver.cs

using System; 
using System.Collections.Generic; 
using TbsFramework.Grid; 
using TbsFramework.Units; 
using UnityEngine; 
namespace Assets.Scripts.GUI.GUITool 
{ 
    public static class UIInputObserver 
    { 
        public static bool IsPlayerCanInput { get; set; } 
        public static bool IsUnitTurnChange { get; set; } 
        public static bool HaveCharacterStayOnFleeCell { get; set; } 
        public static List<Unit> StayFleeCellCharacter { get; set; } 
        public static List<Unit> AlreadyFleeCharacter { get; set; } 
        public static int FleeCharacterCount { get; set; } 
        public static Action FleeAction { get; set; } 
        public static bool HaveBuffAdd { get; set; } 
        public static Action<Transform, bool> FleeInfoAction { get; set; } 
        public static int NumberOfNeedFood { get; set; }  
        public static int CurrentPlayerHaveFoods { get; set; } 
        public static Camera MainCamera { get; set; } 
        public static CellGrid CellGrid { get; set; } 
    } 
}

老實說GetFoodAndFleeGameEndCondition其實寫的也挺亂的,鐵人弄完後真的應該要好好整理一下Orz

參考資料

設計模式與遊戲開發的完美結合(暢銷回饋版)
Turn Based Strategy Framework
流離之歌


上一篇
Day 17:Observer模式(一)
下一篇
Day 19:Bridge模式(一)
系列文
如何在Unity裡寫出具有一定擴充性的遊戲30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言