日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 综合教程 >内容正文

综合教程

关于游戏架构设计(二)

發(fā)布時(shí)間:2023/12/3 综合教程 24 生活家
生活随笔 收集整理的這篇文章主要介紹了 关于游戏架构设计(二) 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

架構(gòu)設(shè)計(jì)目錄二

    • UI架構(gòu)
      • 基類BaseWindow
      • Control控制類
      • State狀態(tài)接口
      • 窗口管理類
      • 狀態(tài)管理類
    • 角色系統(tǒng)
      • 角色系統(tǒng)設(shè)計(jì)的框架圖
      • 角色實(shí)體類父類 IEntity
      • 有限狀態(tài)機(jī)接口EntityFSM
      • 功能實(shí)現(xiàn)類Entity
      • FSM具體實(shí)現(xiàn)
      • IEntity的后續(xù)細(xì)分
      • Entity管理類
      • 管理類的擴(kuò)展
    • 技能系統(tǒng)
      • 父類IEffect
      • 子類中的部分實(shí)現(xiàn)
      • 管理類EffectManager

UI架構(gòu)

先看一下模塊架構(gòu)設(shè)計(jì)思路圖:

UI 的設(shè)計(jì)框架有很多的,這次編寫(xiě)的 UI 框架不能說(shuō)是最好的,它最大的優(yōu)點(diǎn)就是實(shí)現(xiàn)了資源和代碼的徹底分離,真正實(shí)現(xiàn)了程序和美術(shù)人員的分工合作。 UI 框架也是采用了 MVC 設(shè)計(jì)模式,但是在外面加了一層 State 狀態(tài)變換,因?yàn)槊總€(gè) UI 切換是在不同的狀態(tài)之間的變換,這樣整個(gè) UI 架構(gòu)設(shè)計(jì)就完成了。

游戲的每個(gè) UI 都是使用 Panel 面板制作完成的,每個(gè)面板就是一個(gè)窗體,用 MVC 中的 View 表示,而用代碼表示就是 Window 窗體類,MVC 中的 Control 也同樣對(duì)應(yīng)窗體的 Control 類。MVC 中的 Model 表示的是數(shù)據(jù)的傳輸,在當(dāng)前可以直接使用配置文件進(jìn)行讀取加載,或者通過(guò)網(wǎng)絡(luò)進(jìn)行傳輸。


基類BaseWindow

先來(lái)看 Window 窗體類的設(shè)計(jì),每個(gè) UI 對(duì)應(yīng)自己的 Window 窗體類。下面先進(jìn)行 Window 類模塊的代碼編寫(xiě),在寫(xiě)某個(gè)模塊時(shí),首先做的事情是搞清楚這個(gè)模塊包括哪些內(nèi)容,再考慮一下編寫(xiě)此類時(shí)擴(kuò)展是否方便。這些都是編寫(xiě)時(shí)需要注意的問(wèn)題,有的程序員拿過(guò)策劃需求就開(kāi)始編寫(xiě)代碼,這樣導(dǎo)致的后果是一旦策劃需求改變,代碼中就要重新加功能,搞的很麻煩,這對(duì)于程序員來(lái)說(shuō)是大忌。

以 Window 類編寫(xiě)為例,游戲中顯示窗體首先要?jiǎng)?chuàng)建窗體,還有窗體可以隱藏、銷毀。另外,創(chuàng)建窗體首先要知道窗體的資源名字,還有這個(gè)窗體是在哪個(gè)場(chǎng)景中創(chuàng)建的,是登錄場(chǎng)景還是游戲戰(zhàn)斗場(chǎng)景等。因?yàn)榇绑w類不繼承 Mono,為了方便使用窗體中的控件,所以還要做初始化窗體控件的功能以及做監(jiān)聽(tīng)處理。

這些功能對(duì)于游戲中的任何 UI 窗體都是適用的,換句話說(shuō),所有的 UI 這些功能都是必備的,也就是 UI 的共性。這讓人自然而然想到創(chuàng)建一個(gè)父類,如果不建父類,每個(gè) Window 類都要寫(xiě)一套邏輯,這會(huì)使得代碼很亂,而且如果是多個(gè)人合作的話,每人都來(lái)一套邏輯,后期代碼無(wú)法維護(hù)。所以必須要建一個(gè)父類,代碼如下:

 public abstract class BaseWindow
{protected Transform mRoot; //UI根結(jié)點(diǎn)protected EScenesType mScenesType; //場(chǎng)景類型protected string mResName;         //資源名protected bool mResident;          //是否常駐 protected bool mVisible = false;   //是否可見(jiàn)//類對(duì)象初始化public abstract void Init();//類對(duì)象釋放public abstract void Realse();//窗口控制初始化protected abstract void InitWidget();//窗口控件釋放protected abstract void RealseWidget();//游戲事件注冊(cè)protected abstract void OnAddListener();//游戲事件注消protected abstract void OnRemoveListener();//顯示初始化public abstract void OnEnable();//隱藏處理public abstract void OnDisable();//每幀更新public virtual void Update(float deltaTime) { }//取得所以場(chǎng)景類型public EScenesType GetScenseType(){return mScenesType;}//是否已打開(kāi)public bool IsVisible() { return mVisible;  }//是否常駐public bool IsResident() { return mResident; }//顯示public void Show(){if (mRoot == null){if (Create()){InitWidget();}}if (mRoot && mRoot.gameObject.activeSelf == false){mRoot.gameObject.SetActive(true);mVisible = true;OnEnable();OnAddListener();}}//隱藏public void Hide(){if (mRoot && mRoot.gameObject.activeSelf == true){OnRemoveListener();OnDisable();if (mResident){mRoot.gameObject.SetActive(false);}else{RealseWidget();Destroy();}}mVisible = false;}//預(yù)加載public void PreLoad(){if (mRoot == null){if (Create()){InitWidget();}}}//延時(shí)刪除public void DelayDestory(){if (mRoot){RealseWidget();Destroy();}}//創(chuàng)建窗體private bool Create(){if (mRoot){Debug.LogError("Window Create Error Exist!");return false;}if (mResName == null || mResName == ""){Debug.LogError("Window Create Error ResName is empty!");return false;}if (GameMethod.GetUiCamera.transform== null){return false;}GameObject obj = LoadUiResource.LoadRes(GameMethod.GetUiCamera.transform, mResName);if (obj == null){return false;}mRoot = obj.transform;mRoot.gameObject.SetActive(false);return true;}//銷毀窗體protected void Destroy(){if (mRoot){LoadUiResource.DestroyLoad(mRoot.gameObject);mRoot = null;}}//取得根節(jié)點(diǎn)public Transform GetRoot(){return mRoot;}}

該父類設(shè)計(jì)成了一個(gè)抽象類并提供了一些接口便于子類的實(shí)現(xiàn),這些接口所要實(shí)現(xiàn)的具體內(nèi)容是不同的,父類無(wú)法具體一一實(shí)現(xiàn),但是顯示、隱藏、破壞這些都是通用的函數(shù),可以在父類中實(shí)現(xiàn)。再看一下子類的實(shí)現(xiàn)方式,以 LoginWindow 類為例,制作一個(gè)簡(jiǎn)單的 UI 界面,如下圖所示:


我們就以這兩個(gè)按鈕的界面為例來(lái)編寫(xiě)代碼:

public class LoginWindow : BaseWindow
{//開(kāi)始Transform mBtnStart;enum LOGINUI{None = -1,Login,SelectServer,}public LoginWindow() {//場(chǎng)景類型mScenesType = EScenesType.EST_Login;//場(chǎng)景資源mResName = GameConstDefine.LoadGameLoginUI;//是否常駐內(nèi)存mResident = false;}繼承接口///類對(duì)象初始化監(jiān)聽(tīng)顯示和隱藏,為了解耦合public override void Init(){EventCenter.AddListener(EGameEvent.eGameEvent_LoginEnter, Show);EventCenter.AddListener(EGameEvent.eGameEvent_LoginExit, Hide);}//類對(duì)象釋放public override void Realse(){EventCenter.RemoveListener(EGameEvent.eGameEvent_LoginEnter, Show);EventCenter.RemoveListener(EGameEvent.eGameEvent_LoginExit, Hide);}//窗口控件初始化以及控件監(jiān)聽(tīng)protected override void InitWidget(){mBtnStart = mRoot.Find("BtnStart");mBtnStart.GetComponent<Button>().onClick.AddListener(OnClickAddButton);DestroyOtherUI();}//消息回調(diào)函數(shù)private void OnClickAddButton(){//在這里監(jiān)聽(tīng)按鈕的點(diǎn)擊事件LoginCtrl.Instance.StartGame();}//刪除Login外其他控件,例如public static void DestroyOtherUI(){Canvas canvas = GameMethod.GetCanvas;for (int i = 0; i < canvas.transform.childCount; i++){if (canvas.transform.GetChild(i) != null && canvas.transform.GetChild(i).gameObject != null){GameObject obj = canvas.transform.GetChild(i).gameObject;if (obj.name != "Login(Clone)"){GameObject.DestroyImmediate(obj);}                    }}}//窗口控件釋放protected override void RealseWidget(){}//游戲事件注冊(cè)protected override void OnAddListener(){}//游戲事件注消protected override void OnRemoveListener(){}//顯示public override void OnEnable(){}//隱藏public override void OnDisable(){}
}

構(gòu)造函數(shù)對(duì)資源文件和 UI 所在的場(chǎng)景類型初始化,以及該 UI 是否常住內(nèi)存。后面函數(shù)是繼承自父類,并對(duì)它們的具體實(shí)現(xiàn),在函數(shù)中有 LoginCtrl 類接口調(diào)用,這個(gè)跟 LoginWidow 窗體息息相關(guān),它是 MVC 中的 Control。下面再編寫(xiě) LoginCtrl 類。


Control控制類

控制類的主要作用是播放消息,然后在 Loginwindow 中觸發(fā)已設(shè)置監(jiān)聽(tīng)的函數(shù),如 Show、顯示窗體,控制等:

public class LoginCtrl : Singleton<LoginCtrl>
{public void Enter(){EventCenter.Broadcast(EGameEvent.eGameEvent_LoginEnter);   }public void Exit(){EventCenter.Broadcast(EGameEvent.eGameEvent_LoginExit);}//登陸public void Login(string account, string pass){}//登陸錯(cuò)誤反饋public void LoginError(int code){}//登陸失敗public void LoginFail(){}//開(kāi)始游戲public void StartGame(){SceneManager.LoadScene("Play");WindowManager.Instance.ChangeScenseToPlay(EScenesType.EST_Login);GameStateManager.Instance.ChangeGameStateTo(GameStateType.GST_Play);}}

將其設(shè)置成單例模式,在 Enter 函數(shù)中進(jìn)行消息廣播,另外在函數(shù) StartGame 中使用了 WindowManager 類接口和 GameStateManager 類接口。這兩個(gè)類是很關(guān)鍵的,后面會(huì)貼出。當(dāng)然如果只有 Loginwindow 和 LoginCtrl 還是無(wú)法執(zhí)行的,我們還缺少 State 狀態(tài)類,狀態(tài)類是負(fù)責(zé)窗體 UI 之間的切換,每個(gè) UI 窗體對(duì)應(yīng)著自己的狀態(tài),為了區(qū)分不同的 UI 窗體,利用這些狀態(tài)枚舉表示:

public enum GameStateType
{GST_Continue,GST_Login,GST_Role,GST_Loading,GST_Play,//***
}

State狀態(tài)接口

通常來(lái)說(shuō),我們會(huì)定義設(shè)置某個(gè)狀態(tài)、獲取狀態(tài)、進(jìn)入狀態(tài)、停止?fàn)顟B(tài)、更新?tīng)顟B(tài)這些方法等。我們?cè)谠O(shè)計(jì)每個(gè)模塊時(shí),都會(huì)先明確這個(gè)模塊具體要做哪些事情,其實(shí)在設(shè)計(jì)一個(gè)類的內(nèi)容時(shí),可以先多想想,這樣類的函數(shù)定義自然就有了。另外,提到的這些方法,每個(gè) UI 狀態(tài)都會(huì)包含,這樣我們就可以將其抽離出來(lái),定義成一個(gè)接口模塊供程序使用:

public interface IGameState
{GameStateType GetStateType();void SetStateTo(GameStateType gsType);void Enter();GameStateType Update(float fDeltaTime);void FixedUpdate(float fixedDeltaTime);void Exit();
}

抽象類實(shí)現(xiàn)了 oop 中的一個(gè)原則,把可變的與不可變的分離,所以 BaseWindow 采用了抽象的定義。UI 通用函數(shù)方法在父類中先實(shí)現(xiàn)出來(lái),而不通用的函數(shù)方法只提供接口。再說(shuō)接口類,好的接口類定義應(yīng)該是具有專一功能性的,而不是多功能的,否則會(huì)造成接口污染。我們的狀態(tài)類 IGameState 功能是單一的,所以 IGameState 采用了接口的定義。窗體中的狀態(tài)類繼承 IGameState 類:

class LoginState : IGameState
{private  GameStateType _stateTo;//構(gòu)造函數(shù)public LoginState(){}//獲取狀態(tài)public GameStateType GetStateType(){return GameStateType.GST_Login;}//設(shè)置狀態(tài)public void SetStateTo(GameStateType gs){_stateTo = gs;}//進(jìn)入狀態(tài)public void Enter(){SetStateTo(GameStateType.GST_Continue);LoginCtrl.Instance.Enter();        }//停止?fàn)顟B(tài)public void Exit(){LoginCtrl.Instance.Exit();}public void FixedUpdate(float fixedDeltaTime){}//更新?tīng)顟B(tài)public GameStateType Update(float fDeltaTime){return _stateTo;}}

在這個(gè) Login UI 窗體狀態(tài)類中實(shí)現(xiàn)了 IGameState 中定義的接口函數(shù)。

總的來(lái)說(shuō)一個(gè) UI 窗體主要是包含三個(gè)代碼模塊:Window、Ctl、State。Window 和 Ctrl 之間是通過(guò) Event 和接口連接起來(lái)的,而 State 和 Ctrl 是通過(guò)調(diào)用接口連接起來(lái)的。模塊之間的關(guān)系框架:


窗口管理類

現(xiàn)在需要一個(gè)窗體管理類來(lái)進(jìn)行統(tǒng)一調(diào)度,這讓人會(huì)想到工廠模式。那么 WindowMananger 管理類要管理這么多窗體,首先要做的事情是將它們進(jìn)行注冊(cè),也就是將其存儲(chǔ)到字典中:

 public enum EScenesType{EST_None,EST_Login,EST_Play,}public enum EWindowType{EWT_LoginWindow, //登錄EWT_RoleWindow, //用戶EWT_PlayWindow,//戰(zhàn)斗}public class WindowManager : Singleton<WindowManager>{public WindowManager(){mWidowDic = new Dictionary<EWindowType, BaseWindow>();mWidowDic[EWindowType.EWT_LoginWindow] = new LoginWindow();mWidowDic[EWindowType.EWT_RoleWindow] = new RoleWindow();mWidowDic[EWindowType.EWT_PlayWindow] = new PlayWindow();}}

定義了窗體所對(duì)應(yīng)自己的類型后,便于區(qū)分不同的窗體,構(gòu)造函數(shù)實(shí)現(xiàn)了窗體的注冊(cè),注冊(cè)好了窗體后,下面開(kāi)始窗體方法的實(shí)現(xiàn),比如游戲中不同的窗體之間進(jìn)行 UI 切換,從一個(gè) UI 切換到另一個(gè) UI。再比如切換窗體到游戲場(chǎng)景、切換窗體返回到登錄場(chǎng)景等等,這些方法對(duì)應(yīng)的實(shí)現(xiàn)函數(shù)如下:

//切換到游戲場(chǎng)景public void ChangeScenseToPlay(EScenesType front){foreach (BaseWindow pWindow in mWidowDic.Values){if (pWindow.GetScenseType() == EScenesType.EST_Play){pWindow.Init();if(pWindow.IsResident()){pWindow.PreLoad();}}else if ((pWindow.GetScenseType() == EScenesType.EST_Login) && (front == EScenesType.EST_Login)){pWindow.Hide();pWindow.Realse();if (pWindow.IsResident()){pWindow.DelayDestory();}}}}//切換到登錄場(chǎng)景public void ChangeScenseToLogin(EScenesType front){foreach (BaseWindow pWindow in mWidowDic.Values){if (front == EScenesType.EST_None && pWindow.GetScenseType() == EScenesType.EST_None){pWindow.Init();if (pWindow.IsResident()){pWindow.PreLoad();}}if (pWindow.GetScenseType() == EScenesType.EST_Login){pWindow.Init();if (pWindow.IsResident()){pWindow.PreLoad();}}else if ((pWindow.GetScenseType() == EScenesType.EST_Play) && (front == EScenesType.EST_Play)){pWindow.Hide();pWindow.Realse();if (pWindow.IsResident()){pWindow.DelayDestory();}}}}

上面的兩個(gè)方法,就是遍歷窗體,看看它是對(duì)窗體隱藏還是顯示,還是對(duì)其進(jìn)行預(yù)加載操作。這兩個(gè)方法會(huì)經(jīng)常使用。另外,還需要提供隱藏所有窗體和更新窗體的接口:

public void HideAllWindow(EScenesType front){foreach (var item in mWidowDic){if (front == item.Value.GetScenseType()){Debug.Log(item.Key);item.Value.Hide();}}}public void Update(float deltaTime){foreach (BaseWindow pWindow in mWidowDic.Values){if (pWindow.IsVisible()){pWindow.Update(deltaTime);}}}

狀態(tài)管理類

每個(gè) UI 窗體都對(duì)應(yīng)三者:Window、Ctrl、State,這樣每個(gè)窗體對(duì)應(yīng)的 State 跟 Window 也是同樣多的,有這么多狀態(tài),就需要一個(gè)管理類來(lái)進(jìn)行管理。將其都放到 StateManager 管理類中,這樣既管理了 State,也做了狀態(tài)切換處理:

        public GameStateManager(){gameStates = new Dictionary<GameStateType, IGameState>();IGameState gameState;gameState = new LoginState();gameStates.Add(gameState.GetStateType(), gameState);gameState = new RoleState();gameStates.Add(gameState.GetStateType(), gameState);gameState = new PlayState();gameStates.Add(gameState.GetStateType(), gameState);}

管理類主要是對(duì)于這些同類型的類模塊的操作,比如要獲取到當(dāng)前狀態(tài)以及切換狀態(tài),設(shè)置游戲剛開(kāi)始的默認(rèn)狀態(tài),還有游戲狀態(tài)的更新函數(shù) Update / FixedUpdate,關(guān)于管理類的方法,為了邏輯的編寫(xiě),肯定需要一個(gè)統(tǒng)一的接口調(diào)用。狀態(tài)管理類 GameStateManager 的其他方法:

// 獲取當(dāng)前狀態(tài)public IGameState GetCurState(){return currentState;}//改變狀態(tài)public void ChangeGameStateTo(GameStateType stateType){if (currentState != null && currentState.GetStateType() != GameStateType.GST_Loading && currentState.GetStateType() == stateType) return;if (gameStates.ContainsKey(stateType)){if (currentState != null){currentState.Exit();}currentState = gameStates[stateType];currentState.Enter();}}//進(jìn)入默認(rèn)狀態(tài)public void EnterDefaultState(){ChangeGameStateTo(GameStateType.GST_Login);}public void FixedUpdate(float fixedDeltaTime){if (currentState != null){currentState.FixedUpdate(fixedDeltaTime);}}//更新?tīng)顟B(tài)public void Update(float fDeltaTime){GameStateType nextStateType = GameStateType.GST_Continue;if (currentState != null){nextStateType = currentState.Update(fDeltaTime);}if (nextStateType > GameStateType.GST_Continue){ChangeGameStateTo(nextStateType);}}//獲取狀態(tài)public IGameState getState(GameStateType type){if (!gameStates.ContainsKey(type)){return null;}return gameStates[type];}

UI 架構(gòu)設(shè)計(jì)基本完整了。


角色系統(tǒng)

大部分游戲都有自己的角色系統(tǒng),角色系統(tǒng)設(shè)計(jì)要考慮的問(wèn)題比較多,下面開(kāi)始搭建一個(gè)角色系統(tǒng),首先要加載一個(gè)角色,這個(gè)角色要包括這些屬性:資源路徑、資源名字、角色擁有的裝備、血條、經(jīng)驗(yàn)值、攻擊距離、防御、BUFF、角色等級(jí)、重生時(shí)間等,另外,我們的角色還會(huì)有自己的動(dòng)作和技能等。

定義好角色屬性后,就要考慮實(shí)現(xiàn)它們的方法了,角色會(huì)設(shè)計(jì)不同動(dòng)作和技能狀態(tài),這些狀態(tài)之間會(huì)不停的切換,因?yàn)榻巧臓顟B(tài)是有限個(gè)的,動(dòng)作不可能是無(wú)限的,這自然會(huì)讓人想到 FSM 有限狀態(tài)機(jī)的使用。


角色系統(tǒng)設(shè)計(jì)的框架圖


角色實(shí)體類父類 IEntity

游戲中的每個(gè)角色都會(huì)有這些屬性和方法,當(dāng)然也包括怪物和 NPC。了解了角色的屬性和方法后,接下來(lái)就要設(shè)計(jì)代碼去實(shí)現(xiàn)它們,作為共同擁有的屬性,我們可以將其抽離出來(lái)作為父類編寫(xiě)。首先定義角色的共有屬性,因?yàn)榻巧珜傩蕴?#xff0c;這里只寫(xiě)一下重要的,下面是定義的角色實(shí)體類的父類 IEntity 中的片段:

//物理攻擊public float PhyAtk{set;get;}//魔法攻擊public float MagAtk{set;get;}//物理防御public float PhyDef{set;get;}//魔法防御public float MagDef{set;get;}//血量恢復(fù)public float HpRecover{set;get;}//魔法恢復(fù)public float MpRecover{set;get;}// 血量public float Hp{set;get;}//魔法public float Mp{private set;get;}//等級(jí)public int Level{private set;get;}

以上是列出了一部分的屬性定義,另外使用有限狀態(tài)機(jī)對(duì)動(dòng)作和技能進(jìn)行狀態(tài)的變換,有限狀態(tài)機(jī)的設(shè)計(jì)首先要知道狀態(tài)機(jī)的類型,通過(guò)類型去區(qū)分不同的狀態(tài),這個(gè)一般使用枚舉值表示:

    //狀態(tài)枚舉public enum FSMState
{FSM_STATE_FREE,FSM_STATE_RUN,FSM_STATE_SING,FSM_STATE_RELEASE,FSM_STATE_LEADING,FSM_STATE_LASTING,FSM_STATE_DEAD,FSM_STATE_ADMOVE,FSM_STATE_FORCEMOVE,FSM_STATE_RELIVE,FSM_STATE_IDLE,
}

有限狀態(tài)機(jī)接口EntityFSM

該枚舉值定義了動(dòng)作的一些狀態(tài),然后開(kāi)始狀態(tài)接口的封裝,有限狀態(tài)機(jī)跟我們前面的 State 比較類似:

    //有限狀態(tài)機(jī)接口public interface EntityFSM
{bool CanNotStateChange{set;get;}FSMState State { get; }void Enter(IEntity entity , float stateLast);bool StateChange(IEntity entity , EntityFSM state);void Execute(IEntity entity);void Exit(IEntity Ientity);
}

有限狀態(tài)機(jī)定義了執(zhí)行的接口和方法,進(jìn)入狀態(tài)、改變狀態(tài)、執(zhí)行、停止?fàn)顟B(tài)這些方法,該類只提供了接口,并沒(méi)有實(shí)現(xiàn),這樣它的實(shí)現(xiàn)就會(huì)放到其子類中。由于 FSM 有限狀態(tài)機(jī)的切換是通用的,我們還是將它們放到父類 IEntity 類中進(jìn)行:

    public void OnFSMStateChange(EntityFSM fsm){if (this.FSM != null && this.FSM.StateChange(this, fsm)){return;}if (this.FSM == fsm && this.FSM != null && (this.FSM.State == FSMState.FSM_STATE_DEAD)){return;}if (this.FSM != null){this.FSM.Exit(this);}this.FSM = fsm;if (this.FSM != null)this.RealEntity.FSMStateName = fsm.ToString();this.FSM.Enter(this, 0.0f);}

在該函數(shù)的執(zhí)行過(guò)程中,首先判斷狀態(tài)是否為空,如果不為空,將停止當(dāng)前狀態(tài),執(zhí)行下一個(gè)狀態(tài)。設(shè)計(jì)思想是通用的接口可以將其放到某個(gè)類中定義,比如實(shí)體類的移動(dòng)、待機(jī)、技能等接口,這里將其放在父類 IEntity 中,當(dāng)然這些函數(shù)具體可以在子類中實(shí)現(xiàn),而通用的可以在父類中實(shí)現(xiàn),就比如:

  //狀態(tài)機(jī)改變數(shù)據(jù)public void EntityFSMChangeDataOnSing(Vector3 mvPos, Vector3 mvDir, int skillID, IEntity targetID){EntityFSMPosition = mvPos;EntityFSMDirection = mvDir;EntitySkillID = skillID;entitySkillTarget = targetID;}       public void EntityFSMChangedata(Vector3 mvPos, Vector3 mvDir){EntityFSMPosition = mvPos;EntityFSMDirection = mvDir;}    public virtual void OnEntitySkill(){}//進(jìn)入Idle狀態(tài)public virtual void OnEnterIdle(){}//進(jìn)入Move狀態(tài)public virtual void OnEnterMove(){}

功能實(shí)現(xiàn)類Entity

基本上角色所有的共同屬性和方法都定義在 IEntity 類中,也就是核心功能設(shè)計(jì)。再說(shuō)一下 Entity 類,核心思想就是播放角色的動(dòng)作以及設(shè)置技能的播放點(diǎn),它是繼承 Mono 的,需要?jiǎng)討B(tài)的掛接到對(duì)象上。新動(dòng)畫(huà)狀態(tài)機(jī)或者老動(dòng)畫(huà)狀態(tài)播放都是在此類中實(shí)現(xiàn)的,另外,角色身上的通過(guò)文本讀取的基礎(chǔ)屬性也是在此類中定義,這樣可以方便策劃調(diào)試運(yùn)行代碼:

public class EntityAttrubte
{public float Hp;public float HpMax;public float Mp;public float MpMax;public float Speed;public float PhyAtk;public float MagAtk;public float PhyDef;public float MagDef;public float AtkSpeed;public float AtkDis;public float HpRecover;public float MpRecover;public float RebornTime;public void AttribbuteUpdate(IEntity entity){Hp = entity.Hp;HpMax = entity.HpMax;Mp = entity.Mp;MpMax = entity.MpMax;Speed = entity.EntityFSMMoveSpeed;PhyAtk = entity.PhyAtk;MagAtk = entity.MagAtk;PhyDef = entity.PhyDef;MagDef = entity.MagDef;AtkSpeed = entity.AtkSpeed;AtkDis = entity.AtkDis;HpRecover = entity.HpRecover;MpRecover = entity.MpRecover;RebornTime = entity.RebornTime;}
}

在此定義了基礎(chǔ)屬性,然后看一下 Entity 類中的核心設(shè)計(jì):

    //初始化掛載點(diǎn)public virtual void Awake(){objAttackPoint = transform.Find("hitpoint");objBuffPoint = transform.Find("buffpoint");objPoint = transform.Find("point");if (objAttackPoint == null || objBuffPoint == null || objPoint == null){}skeletonAnimation = GetComponentInChildren<SkeletonAnimation>();EntityAttribute = new EntityAttrubte();}// 設(shè)置移動(dòng)速度public void SetMoveAnimationSpd(float spd){if (GetComponent<Animation>() == null){return;}AnimationState aState = GetComponent<Animation>()["walk"];if (aState != null){aState.speed = spd;}}//播放動(dòng)作public void PlayerAnimation(string name){if (name == "" || name == "0"){return;}if (this.GetComponent<Animation>() == null){return;}GetComponent<Animation>().CrossFade(name);}//播放移動(dòng)動(dòng)作public void PlayeMoveAnimation(){if (skeletonAnimation != null){var track = skeletonAnimation.AnimationState.SetAnimation(0, "run", true);}}//播放待機(jī)動(dòng)作public void PlayeIdleAnimation(){if (skeletonAnimation != null){var track = skeletonAnimation.AnimationState.SetAnimation(0, "idle", true);}}//播放攻擊動(dòng)作internal void PlayAttackAnimation(){if (skeletonAnimation != null){var track = skeletonAnimation.AnimationState.SetAnimation(1, "shoot", false);}}

在 Awake 函數(shù)中實(shí)現(xiàn)掛載節(jié)點(diǎn)的初始化操作,這里使用的是老動(dòng)畫(huà)播放,原理跟新動(dòng)畫(huà)是類似的,如果是使用新動(dòng)畫(huà)只是將其改成 SetTrigger 觸發(fā)就可以了。


FSM具體實(shí)現(xiàn)

下面再說(shuō)一下有限狀態(tài)機(jī) FSM 的具體實(shí)現(xiàn),在前面已經(jīng)介紹了類 EntityFSM,它只是提供了接口,沒(méi)有具體實(shí)現(xiàn)。下面以角色待機(jī)動(dòng)作為例介紹FSM的使用:

    public class EntityIdleFSM : EntityFSM{public static readonly EntityFSM Instance = new EntityIdleFSM();public FSMState State{get{return FSMState.FSM_STATE_IDLE;}}public bool CanNotStateChange{set;get;}            public bool StateChange(IEntity entity , EntityFSM fsm){return CanNotStateChange;}public void Enter(IEntity entity , float last){entity.OnEnterIdle();}public void Execute(IEntity entity){}public void Exit(IEntity entity){}}

這樣角色系統(tǒng)的基礎(chǔ)類設(shè)計(jì)完成了


IEntity的后續(xù)細(xì)分

下面再回到父類 IEntity,已經(jīng)在其中實(shí)現(xiàn)了角色的通用方法以及角色使用的屬性,我們還需要繼續(xù)實(shí)現(xiàn)它的子類,在此將其進(jìn)行了細(xì)分:

在 IEntity 類下面分了兩級(jí),之所以分兩級(jí)是因?yàn)榻巧€有與網(wǎng)絡(luò)有關(guān)的方法和屬性,如果只是放到一個(gè)類 IEntity 中,這樣 IEntity 就顯得很臃腫,代碼量會(huì)很龐大,不利于維護(hù)。

IPlayer 類模塊也是 IEntity 的子類,之所以劃分出這個(gè)類,是因?yàn)橛螒蛐枰?lián)網(wǎng),開(kāi)房間,而此類就涉及到開(kāi)房間陣營(yíng)的設(shè)置,它不包含具體的數(shù)值,它還是一個(gè)抽象的玩家實(shí)體。因?yàn)橥婕叶季哂嘘嚑I(yíng)、房間這些屬性,所以也是公有的,抽離出來(lái)的。

首先將角色身上的一些函數(shù)方法都放在 IPlayer 類里面,它做的事情都是與角色相關(guān)的,會(huì)比較雜,比如角色身上的相機(jī)位置更新、角色的重生、創(chuàng)建角色陰影效果,以及在角色身上動(dòng)態(tài)的添加多個(gè)碰撞體,改變角色的移動(dòng)速度、鎖定目標(biāo)、創(chuàng)建血條等,這些方法都是圍繞角色身上展開(kāi)的:

// 設(shè)置鎖定對(duì)象public virtual void SetSyncLockTarget(IEntity entity){if (SyncLockTarget == entity){return;}this.DeltLockTargetXuetiao(entity);this.SyncLockTarget = entity;}// Entity移動(dòng)屬性變化public override void OnEntityMoveSpeedChange(int value){base.OnEntityMoveSpeedChange(value);HeroConfigInfo heroConfig;if (ConfigReader.HeroXmlInfoDict.TryGetValue(NpcGUIDType, out heroConfig)){float speed = value / heroConfig.HeroMoveSpeed * heroConfig.n32BaseMoveSpeedScaling / 1000;if (speed == 0){return;}this.RealEntity.SetMoveAnimationSpd(speed);}}

以上代碼可以根據(jù)需求去編寫(xiě),重點(diǎn)是掌握思想,具體實(shí)現(xiàn)可以根據(jù)需求去做。接下來(lái)再看 IPlayer 類的子 Player 類的設(shè)計(jì),前面兩個(gè)都是角色公有的屬性和方法,Player 這個(gè)類涉及玩家的具體操作,比如玩家的游戲物品獲取、技能相關(guān)的釋放和冷卻、玩家的經(jīng)驗(yàn)值或者血量、自身角色的移動(dòng)、動(dòng)作的播放以及動(dòng)作事件的回調(diào)等,就比如下面的示例方法:

    public Player(UInt64 sGUID, EntityCampType campType): base(sGUID, campType){UserGameItems = new Dictionary<int, int>();UserGameItemsCount = new Dictionary<int, int>();UserGameItemsCoolDown = new Dictionary<int, float>();for (int ct = 0; ct < 6; ct++){UserGameItems.Add(ct, 0);UserGameItemsCount.Add(ct, 0);UserGameItemsCoolDown.Add(ct, 0f);}}//準(zhǔn)備釋放技能//技能類型public void SendPreparePlaySkill(SkillType skType){int skillID = GetSkillIdBySkillType(skType);//沉默了if (Game.Skill.BuffManager.Instance.isSelfHaveBuffType(1017)){return;}if (skillID == 0){MsgInfoManager.Instance.ShowMsg((int)ERROR_TYPE.eT_AbsentSkillNULL);return;}}//初始化技能升級(jí)列表private void InitSkillDic(Dictionary<SkillType, int> skillDic){int id = (int)ObjTypeID;HeroConfigInfo heroInfo = ConfigReader.GetHeroInfo(id);skillDic.Add(SkillType.SKILL_TYPE1, heroInfo.HeroSkillType1);skillDic.Add(SkillType.SKILL_TYPE2, heroInfo.HeroSkillType2);skillDic.Add(SkillType.SKILL_TYPE3, heroInfo.HeroSkillType3);skillDic.Add(SkillType.SKILL_TYPE4, heroInfo.HeroSkillType4);skillDic.Add(SkillType.SKILL_TYPEABSORB1, 0);skillDic.Add(SkillType.SKILL_TYPEABSORB2, 0);}//根據(jù)技能類型,換算出滿足指定技能的準(zhǔn)確idprivate void SetSkillUpdate(SkillType skillType, int lv){int baseId = 0;if (!BaseSkillIdDic.TryGetValue(skillType, out baseId)) return;//技能id不存在SkillManagerConfig info = ConfigReader.GetSkillManagerCfg(baseId);if (baseId == 0 || info == null){return;//不存在技能信息 }for (int i = baseId + SKILL_UPDATE_TOTAL_LEVEL - 1; i >= 0; i--){SkillManagerConfig infoNew = ConfigReader.GetSkillManagerCfg(i);if (i == 0 || infoNew == null || infoNew.n32UpgradeLevel > lv)continue;SkillIdDic[skillType] = i;break;}}

Entity管理類

至此角色系統(tǒng)底層的封裝就結(jié)束了,接下來(lái)再封裝一個(gè)管理類————EntityManager 類,因?yàn)橛螒蛑袝?huì)生成很多的實(shí)體對(duì)象,這些實(shí)體對(duì)象包括玩家自身、其他玩家、怪物 NPC 等,所以需要一個(gè)對(duì)外提供接口的管理類,還要?jiǎng)?chuàng)建一個(gè)對(duì)外提供的創(chuàng)建角色的接口,可以用它創(chuàng)建我們的角色。在其中,首先需要?jiǎng)?chuàng)建存儲(chǔ)我們所定義實(shí)體對(duì)象的字典 Dictionary,用于保存游戲中的所有生成的實(shí)體對(duì)象:

    public static EntityManager Instance{private set;get;}public static Dictionary<UInt64, IEntity> AllEntitys = new Dictionary<UInt64, IEntity>();public enum CampTag{SelfCamp = 1,EnemyCamp = 0,}static int[] HOME_BASE_ID = { 21006, 21007, 21020, 21021 };private static List<IEntity> homeBaseList = new List<IEntity>();public EntityManager(){Instance = this;}

這里將 EntityManager 管理類作為單例模式使用,有了存儲(chǔ)實(shí)體對(duì)象的字典后,就可以對(duì)實(shí)體對(duì)象進(jìn)行諸如顯示和隱藏操作以及刪除所有實(shí)體的操作,其實(shí)就是對(duì)保存在 Dictionary 中的對(duì)象進(jìn)行操作:

    //顯示實(shí)體public void ShowEntity(UInt64 sGUID, Vector3 pos, Vector3 dir){if (!AllEntitys.ContainsKey(sGUID) || AllEntitys[sGUID].realObject == null){return;}AllEntitys[sGUID].realObject.transform.position = pos;AllEntitys[sGUID].realObject.transform.rotation = Quaternion.LookRotation(dir);AllEntitys[sGUID].realObject.SetActive(true);if (AllEntitys[sGUID].FSM != null && AllEntitys[sGUID].FSM.State != Game.FSM.FSMState.FSM_STATE_DEAD){AllEntitys[sGUID].ShowXueTiao();}else if (AllEntitys[sGUID].FSM != null && AllEntitys[sGUID].FSM.State == Game.FSM.FSMState.FSM_STATE_DEAD){AllEntitys[sGUID].HideXueTiao();}}//隱藏實(shí)體public void HideEntity(UInt64 sGUID){if (!AllEntitys.ContainsKey(sGUID)){return;}IEntity entity = null;if (EntityManager.AllEntitys.TryGetValue(sGUID, out entity) && entity.entityType == EntityType.Player){}AllEntitys[sGUID].HideXueTiao();AllEntitys[sGUID].realObject.SetActive(false);}//刪除實(shí)體public void DestoryAllEntity(){List<UInt64> keys = new List<UInt64>();foreach (IEntity entity in AllEntitys.Values){if (entity.entityType != EntityType.Building){keys.Add(entity.GameObjGUID);}}foreach (UInt64 gui in keys){HandleDelectEntity(gui);}}

另外,最重要的操作就是創(chuàng)建模型實(shí)體,創(chuàng)建的過(guò)程,還包括將其放到對(duì)象池的過(guò)程,避免頻繁的創(chuàng)建和刪除,產(chǎn)生內(nèi)存碎片,同時(shí)需要將其在場(chǎng)景中顯示出來(lái),并且添加組件代碼:

    public GameObject CreateEntityModel(IEntity entity, UInt64 sGUID, Vector3 dir, Vector3 pos){if (entity != null){int id = (int)entity.ObjTypeID;this.SetCommonProperty(entity, id);entity.ModelName = "Jinglingnan_6";if (entity.ModelName == null || entity.ModelName == ""){return null;}string path = GameDefine.GameConstDefine.LoadModelPath;//創(chuàng)建GameObject    string resPath = path + entity.ModelName;entity.realObject = GameObjectPool.Instance.GetGO(resPath);if (entity.realObject == null){Debug.LogError("entity realObject is null");}//填充Entity信息entity.resPath = resPath;entity.objTransform = entity.realObject.transform;entity.realObject.transform.localPosition = pos;entity.realObject.transform.localRotation = Quaternion.LookRotation(dir);if (entity.NPCCateChild != ENPCCateChild.eNPCChild_BUILD_Shop){entity.CreateXueTiao();}AddEntityComponent(entity);return entity.realObject;}return null;}

在函數(shù)中,entity 是父類中的對(duì)象,它并不是實(shí)際意義上的模型實(shí)體,它內(nèi)部定義了 GameObject,這樣就可以對(duì)其進(jìn)行賦值操作,使用的是對(duì)象池中的函數(shù)接口。

在函數(shù)中還調(diào)用了接口 AddEntityComponent,它用于動(dòng)態(tài)添加角色的腳本組件,因?yàn)橘Y源和代碼是徹底分離的:

    public static void AddEntityComponent(IEntity entity){//沒(méi)有,添加Entity組件if (entity.realObject.GetComponent<Entity>() == null){Entity syncEntity = entity.realObject.AddComponent<Entity>() as Entity;entity.RealEntity = syncEntity;}//直接取else{Entity syncEntity = entity.realObject.GetComponent<Entity>() as Entity;entity.RealEntity = syncEntity;}}

以上函數(shù)能夠動(dòng)態(tài)的添加 Entity 代碼組件,如果在游戲中,我們要在場(chǎng)景中增加某個(gè)對(duì)象呢,還要獲取某個(gè)對(duì)象呢,這些也是這個(gè)實(shí)體管理類來(lái)提供的:

    public void AddEntity(UInt64 sGUID, IEntity entity){if (AllEntitys.ContainsKey(sGUID)){Debug.LogError("Has the same Guid: " + sGUID);return;}AllEntitys.Add(sGUID, entity);}public virtual IEntity GetEntity(UInt64 id){IEntity entity;if (AllEntitys.TryGetValue(id, out entity)){return entity;}return null;}

不論增加還是獲取都是通過(guò)它的 ID 來(lái)進(jìn)行的,相比字符串查找,效率更高,這樣 EntityManager 類就完成了。如果我們還需要在 EntityManager 類的基礎(chǔ)上擴(kuò)展,就需要自己實(shí)現(xiàn)管理類去繼承 EntityManager。


管理類的擴(kuò)展

在此又實(shí)現(xiàn)了一個(gè) PlayerManager 用于生成 Player,該類繼承 EntityManager,它的核心思想就是創(chuàng)建 Player:

public class PlayerManager : EntityManager{public static new PlayerManager Instance {private set;get;}public Dictionary<UInt64, IPlayer> AccountDic = new Dictionary<UInt64, IPlayer>();public PlayerManager(){Instance = this;}public Player LocalPlayer {set;get;}		 public IPlayer LocalAccount{set;get;}public override Ientity HandleCreateEntity (UInt64 sGUID , EntityCampType campType){    Iplayer player;if (GameUserModel.Instance.IsLocalPlayer(sGUID)){player = new Player(sGUID, campType);                }else{player =  new Iplayer(sGUID, campType);}player.GameUserId = sGUID;return player;}public void AddAccount(UInt64 sGUID, IPlayer entity){if (AccountDic.ContainsKey (sGUID)) {		return;}AccountDic.Add (sGUID , entity);}public override void SetCommonProperty(IEntity entity, int id){base.SetCommonProperty(entity, id);IPlayer mpl = (IPlayer)entity;if (mpl.GameUserNick == "" || mpl.GameUserNick == null){}}protected override string GetModeName (int id){}public bool IsLocalSameType(IEntity entity){if(PlayerManager.Instance.LocalPlayer.EntityCamp != entity.EntityCamp)return false;return true;}public void CleanAccount(){for (int i = AccountDic.Count - 1; i >= 0; i--) {AccountDic.Remove (AccountDic.ElementAt(i).Key);}					 }public void RemoveAccountBySeat(uint seat){for (int i = AccountDic.Count - 1; i >= 0; i--) {if (AccountDic.ElementAt(i).Value.GameUserSeat != seat)continue;	AccountDic.Remove (AccountDic.ElementAt(i).Key);break;}					 }public void CleanPlayerWhenGameOver() {foreach (var item in AccountDic.Values) { item.CleanWhenGameOver();}}}

以上的例子,說(shuō)明了可以在此框架的基礎(chǔ)上繼續(xù)擴(kuò)展,當(dāng)然也可以修改。以上實(shí)現(xiàn)了對(duì)外接口的編寫(xiě),這樣整個(gè)角色系統(tǒng)就完成了。


技能系統(tǒng)

在游戲中,技能特效是伴隨著角色動(dòng)作播放的,角色動(dòng)作可以使用 FSM 播放,技能就是將動(dòng)作和特效合在一起播放,看一下技能模塊設(shè)計(jì)圖:


父類IEffect

IEffect 模塊,所有的技能肯定有共同的屬性,為了避免屬性被重復(fù)的定義,我們將其放到一個(gè)父類中,該父類就是 IEffect。首先游戲中會(huì)有很多技能,這么多技能如何區(qū)分,這就涉及到一個(gè)技能類型的定義,技能類型定義可以使用字符串,也可以使用枚舉。這里使用枚舉表示:

    public enum ESkillEffectType{eET_Passive,eET_Buff,eET_BeAttack,eET_FlyEffect,eET_Normal,eET_Area,eET_Link,}

技能還有一些共同的屬性和方法,先定義屬性,比如特效的運(yùn)行時(shí)間、資源路徑、生命周期、技能釋放者和受擊者、播放的音效等。這些我們可以自己根據(jù)需求去定義:

    //基本信息    public GameObject obj = null;           //特效物體public Transform mTransform = null;protected float currentTime = 0.0f;     //特效運(yùn)行時(shí)間public bool isDead = false;             //特效是否死亡public string resPath;                  //特效資源路徑        public string templateName;             //特效模板名稱public Int64 projectID = 0;             //特效id  分為服務(wù)器創(chuàng)建id 和本地生成id        public uint skillID;                    //特效對(duì)應(yīng)的技能idpublic float cehuaTime = 0.0f;          //特效運(yùn)動(dòng)持續(xù)時(shí)間或者是特效基于外部設(shè)置的時(shí)間    策劃配置      public float artTime = 0.0f;            //美術(shù)設(shè)置的特效時(shí)間                            美術(shù)配置public float lifeTime = 0;              //特效生命周期, 0為無(wú)限生命周期public UInt64 enOwnerKey;               //技能釋放者public UInt64 enTargetKey;              //技能受擊者        public AudioSource mAudioSource = null; //聲音//運(yùn)動(dòng)信息public Vector3 fPosition;public Vector3 fixPosition;public Vector3 dir;public Vector3 distance;public ESkillEffectType mType;

共同屬性定義完了,下面定義它的共同方法。要使用特效,首先要?jiǎng)?chuàng)建特效:

    //特效創(chuàng)建接口public void Create(){//創(chuàng)建的時(shí)候檢查特效projectId,服務(wù)器沒(méi)有設(shè)置生成本地idCheckProjectId();//獲取特效模板名稱templateName = ResourceCommon.getResourceName(resPath);//使用特效緩存機(jī)制           obj = GameObjectPool.Instance.GetGO(resPath);if (null == obj){Debugger.LogError("load effect object failed in IEffect::Create" + resPath);return;}//創(chuàng)建完成,修改特效名稱,便于調(diào)試obj.name = templateName + "_" + projectID.ToString();OnLoadComplete();//獲取美術(shù)特效腳本信息                effectScript = obj.GetComponent<EffectScript>();if (effectScript == null){Debugger.LogError("cant not find the effect script in " + resPath);return;}artTime = effectScript.lifeTime;//美術(shù)配置時(shí)間為0,使用策劃時(shí)間if (effectScript.lifeTime == 0)lifeTime = cehuaTime;//否則使用美術(shù)時(shí)間elselifeTime = artTime;//特效等級(jí)不同,重新設(shè)置EffectLodLevel effectLevel = effectScript.lodLevel;EffectLodLevel curLevel = EffectManager.Instance.mLodLevel;if (effectLevel != curLevel){//調(diào)整特效顯示等級(jí)AdjustEffectLodLevel(curLevel);}}

該函數(shù)的實(shí)現(xiàn)流程是先加載特效,也是從對(duì)象池生成,并且將其重新命名,然后調(diào)用函數(shù) OnLoadComplete() 去設(shè)置特效的發(fā)射點(diǎn),因?yàn)樘匦Оl(fā)射點(diǎn)要根據(jù)不同的技能去設(shè)置,所以在 IEffect 類中只是定義了一個(gè)它的虛函數(shù):

    public virtual void OnLoadComplete(){}

愛(ài)他的具體的功能要在特效子類中去實(shí)現(xiàn),當(dāng)然 IEffect 類并不是只有這一個(gè)函數(shù)實(shí)現(xiàn),其他的功能函數(shù)可以根據(jù)需求自己去定義了。


子類中的部分實(shí)現(xiàn)

特效父類 IEffect 已定義完成,接下來(lái)就要編寫(xiě)具體的子類了,根據(jù)需求可以擴(kuò)展下去。我們先拿出 BeAttackEffect 被動(dòng)技能舉例說(shuō)明,具體實(shí)現(xiàn)一下 OnLoadComplete:

    public override void OnLoadComplete(GameObject obj){//判斷enTargetIEntity enTarget;EntityManager.AllEntitys.TryGetValue(enTargetKey, out enTarget);if (enTarget != null && obj != null){//擊中點(diǎn)Transform hitpoit = enTarget.RealEntity.transform.FindChild("hitpoint");if (hitpoit != null){//設(shè)置父類和位置GetTransform().parent = hitpoit;GetTransform().localPosition = new Vector3(0.0f, 0.0f, 0.0f);}}if (skillID == 0){return;}}

該函數(shù)是先在表里查找,如果找到了,則去查找對(duì)象的虛擬點(diǎn),然后將該虛擬點(diǎn)設(shè)置給特效。


管理類EffectManager

這么多的技能,我們同樣也需要一個(gè)管理器 EffectManager 類,用于對(duì)外提供創(chuàng)建特效接口:

        //創(chuàng)建基于時(shí)間的特效public BeAttackEffect CreateTimeBasedEffect(string res, float time, IEntity entity){if (res == "0")return null;BeAttackEffect effect = new BeAttackEffect();//加載特效信息effect.skillID = 0;             //技能id=0     effect.cehuaTime = time;effect.enTargetKey = entity.GameObjGUID;effect.resPath = res;           //創(chuàng)建effect.Create();AddEffect(effect.projectID, effect);return effect;}

該函數(shù)對(duì)應(yīng)的是 BeAttackEffect,將它初始化后,調(diào)用 Create 創(chuàng)建加載特效,為了便于管理我們調(diào)用了函數(shù) AddEffect,目的是將特效加到表中:

    //添加特效到EffectMap表public void AddEffect(Int64 id, IEffect effect){if (!m_EffectMap.ContainsKey(id)){m_EffectMap.Add(id, effect);}else{Debug.LogError("the id: " + id.ToString() + "effect: " + effect.resPath + "has already exsited in EffectManager::AddEffect");}}

總結(jié)

以上是生活随笔為你收集整理的关于游戏架构设计(二)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。