游戲鏈接https://pan.baidu.com/s/1eSd4ZHg
這是我開(kāi)發(fā)的一款策略戰(zhàn)棋游戲[遠(yuǎn)古帝國(guó)]
他是我以前上學(xué)時(shí)經(jīng)常玩的一款游戲 雖然現(xiàn)在市面上有很多重置版但是我還是喜歡玩原版 因?yàn)槔锩娴膽?zhàn)斗數(shù)據(jù)非常平衡 你能從中獲得更多的樂(lè)趣
所以我盡量還原經(jīng)典 仔細(xì)分析數(shù)據(jù) 力求完美(上下左右是wsad或方向鍵控制 確認(rèn)鍵是空格或回車(chē))
首先是移動(dòng)范圍的渲染
?
這里的人物移動(dòng)范圍可以在ACGame.Sprite中的MoveRange里配置
?
然后是地圖 這張大地圖都是由15*15的小地圖拼接起來(lái)的
?
每張小地圖上都掛了ACGame.Terrain腳本
CustomDepth設(shè)置的是移動(dòng)到該地形需消耗的移動(dòng)點(diǎn)
Def則是角色在該地形上的防御加成
?
當(dāng)玩家點(diǎn)擊了目的地 角色便會(huì)沿著路線移動(dòng)過(guò)去
?
?
當(dāng)角色移動(dòng)完畢會(huì)跳出一個(gè)提示框顯示是否結(jié)束當(dāng)前移動(dòng)
如果角色移動(dòng)的目的地中有敵人則會(huì)出現(xiàn)攻擊選項(xiàng)
?
每個(gè)角色的攻擊范圍不同?可以在腳本中配置
?
如果角色附近有多個(gè)敵人 玩家可以按上下左右鍵選擇要攻擊的敵人
?
然后就可以開(kāi)始攻擊
?
玩家攻擊完成后敵人也會(huì)反擊
?
角色的攻擊力是一個(gè)范圍 在腳本中可以配置 每次會(huì)從中隨機(jī)取一個(gè)值
?
攻擊的計(jì)算公式是(攻擊方的攻擊力-敵人防御-地形防御加成)*攻擊方的血量百分比
這樣可以保證攻擊時(shí)血量較多的一方比較有利
玩家也可以利用山地 房屋等有防御加成的地形攻擊敵人
也可以先使用滿血的單位攻擊 然后再使用殘血單位補(bǔ)刀如果玩家或者敵人血量為0 則會(huì)爆出一塊墓碑
?
接下來(lái)是購(gòu)買(mǎi)界面
?
點(diǎn)擊下方左右箭頭可以在各個(gè)兵種之間切換 這里實(shí)現(xiàn)了無(wú)縫滾動(dòng)
然后是我的代碼
代碼一共分為兩大類(lèi) 分別叫abstract和business
abstract中存放的都是接口 抽象類(lèi) 和工具類(lèi)
它們不依賴(lài)于business?所以不僅可以運(yùn)用于當(dāng)前項(xiàng)目
也可以在其他項(xiàng)目中使用
business存放具體的業(yè)務(wù)邏輯 這里的代碼依賴(lài)關(guān)系比較復(fù)雜
這樣的分類(lèi)也是我研究了好久 我曾經(jīng)試過(guò)將所有的類(lèi)都面向接口 耦合也降到最低
但是這樣做沒(méi)有意義
因?yàn)榫退忝嫦蛄私涌?但最終還是要把各處代碼組合在一起 耦合還是存在
并且修改的話還是有可能修改好幾處地方
后來(lái)我取消了接口 又發(fā)現(xiàn)代碼變得更加難以維護(hù)
最后我做了分類(lèi) 因?yàn)榧热晃覜](méi)辦法把所有的類(lèi)全部獨(dú)立 也不能把所有的類(lèi)依賴(lài)在一起
那我就索性把獨(dú)立性較強(qiáng)的類(lèi)和處理業(yè)務(wù)邏輯的類(lèi)分開(kāi)
這樣就誕生了abstract和business而在實(shí)際編程中經(jīng)常會(huì)出現(xiàn)business的代碼寫(xiě)到一半發(fā)現(xiàn)之前寫(xiě)過(guò)
然后把重復(fù)的邏輯整理好放到abstract中
而放入abstract中的代碼基本不會(huì)再去放回business
?
在abstarct中有一個(gè)分類(lèi)是專(zhuān)門(mén)存放設(shè)計(jì)模式的
雖然里面就兩個(gè)類(lèi) 但代碼中遠(yuǎn)不止這兩種設(shè)計(jì)模式
放在這里僅僅是因?yàn)樗鼈冋麄€(gè)類(lèi)里都在做設(shè)計(jì)模式做的事
而且其他模式則和其他邏輯耦合在一起
?
首先是DynamicFactroy
他本質(zhì)是一個(gè)工廠模式 但是沒(méi)有一本書(shū)上會(huì)有這種工廠模式 這是我自創(chuàng)的
這里就做兩件事 先找字典中有沒(méi)有你要的對(duì)象 沒(méi)有就用反射創(chuàng)建一個(gè)
?
第二個(gè)是單例模式
我知道學(xué)過(guò)c++的人都會(huì)七八種單例模式的寫(xiě)法
但是這里我就按照unity的思路寫(xiě)一個(gè)就行了
?
還有一個(gè)非常棘手的問(wèn)題就是對(duì)象的初始化參數(shù)
按照我們以前的思路 要么把類(lèi)中需要初始化的數(shù)據(jù)在聲明時(shí)就賦值
要么寫(xiě)一個(gè)構(gòu)造器 但是在unity中
如果你的類(lèi)繼承了MonoBehaviour你就沒(méi)辦法使用有參構(gòu)造器了
因?yàn)閡nity要序列化你的腳本顯示在inspector上 它走的是無(wú)參構(gòu)造器 就算你寫(xiě)了有參構(gòu)造器他也不會(huì)走
而且會(huì)報(bào)警
我看到很多老外的代碼里直接寫(xiě)了個(gè)setter 我覺(jué)得這樣做違背了初始化這個(gè)詞的兩個(gè)初衷
首先破壞了封裝 你的內(nèi)部變量可以在對(duì)象創(chuàng)建后隨意修改
其次有空數(shù)據(jù)的風(fēng)險(xiǎn) 如果你的代碼沒(méi)有調(diào)用相關(guān)setter
編譯不會(huì)察覺(jué) 那你的變量就為null了
這里我的解決方法是寫(xiě)一個(gè)ResourceManager 你可以看到它使用了我的單例模式
?
然后在腳本執(zhí)行順序中把他放到第一個(gè)
?
這樣我就可以在代碼中的任何地方隨意調(diào)用初始化好的參數(shù)
?
但是你可以看到ResourceManager中有各種各樣的控件
光標(biāo) 相機(jī)等
我的項(xiàng)目有這些控件 而你的項(xiàng)目不大可能也有這些
所以ResourceManager應(yīng)該歸到business中
那如果ResourceManager是business了 那么abstract中繼承了MonoBehaviour的類(lèi)也想要初始化參數(shù)怎么辦
這里還有一個(gè)辦法
就是在這些類(lèi)中寫(xiě)一個(gè)抽象函數(shù)作為構(gòu)造函數(shù)這里拿一個(gè)放大特效腳本做例子它的效果是激活后 物體會(huì)從一個(gè)點(diǎn)慢慢的放大成原來(lái)的樣子
接下來(lái)說(shuō)說(shuō)狀態(tài)模式?里面存放的全是游戲業(yè)務(wù)邏輯
如果你按下一個(gè)鍵后需要一大堆if else 那么你就需要狀態(tài)模式
我發(fā)現(xiàn)HeadFirstParttern一書(shū)中的狀態(tài)模式有些不足
第一 他的狀態(tài)會(huì)頻繁的創(chuàng)建
第二 他的狀態(tài)切換必須經(jīng)過(guò)Update
而我的狀態(tài)對(duì)象的獲取是通過(guò)我的動(dòng)態(tài)工廠獲得的 同一個(gè)狀態(tài)只有一個(gè)對(duì)象
然后我的狀態(tài)切換并不是通過(guò)update切換的
而是通過(guò)GoNextState<T>()
這樣我可以在任何事件中切換狀態(tài)
我的狀態(tài)模式基類(lèi)
?
狀態(tài)子類(lèi) 可以看到當(dāng)子類(lèi)中觸發(fā)了點(diǎn)擊事件后會(huì)進(jìn)入MoveState
?
進(jìn)入MoveState后角色移動(dòng)完畢又進(jìn)入BehaviourState
?
在BehaviourState中玩家如果選擇移動(dòng)結(jié)束則回到InitialState 如果選擇攻擊 則進(jìn)入SelectEmyState
?
在這個(gè)項(xiàng)目里一共有八個(gè)狀態(tài)
?
他們之間的切換過(guò)程可以在inspector中可以看到
?
這里出現(xiàn)了一個(gè)GameManager 起始它的作用就是刷新游戲狀態(tài)
這樣我的游戲中只有一個(gè)update在運(yùn)行
?
接下來(lái)是最困難的地方 就是地形的遍歷
作為一個(gè)策略戰(zhàn)棋游戲 最必不可少的就是地圖遍歷
這里我使用了四叉樹(shù)
而這里的四叉樹(shù)和一些算法書(shū)上不一樣 一般數(shù)據(jù)結(jié)構(gòu)的節(jié)點(diǎn)都會(huì)先建立一個(gè)四叉樹(shù)節(jié)點(diǎn)類(lèi)
然后在類(lèi)中存一個(gè)泛型數(shù)據(jù) 但我覺(jué)得這樣用的時(shí)候不方便
所以我的寫(xiě)法是每一個(gè)節(jié)點(diǎn)都包含了它上下左右四個(gè)同類(lèi)型的節(jié)點(diǎn)
?
設(shè)計(jì)好了四叉樹(shù) 那么在遍歷前先要把他們連接起來(lái)
這里我寫(xiě)個(gè)接口IQuadNodeSetter用于設(shè)置四叉樹(shù)的四個(gè)節(jié)點(diǎn)
?
然后是定義位置接口IPositional?因?yàn)槲蚁胪ㄟ^(guò)物體的位置關(guān)系來(lái)連接
?
連接之前我會(huì)對(duì)所有的地形根據(jù)位置進(jìn)行排序 所以還要用到IComparable<IPositional>
所以一個(gè)節(jié)點(diǎn)連接的所有接口準(zhǔn)備齊了 把它們合在一起就是ILinkable接口
?
任何類(lèi)只要實(shí)現(xiàn)了ILinkable接口就可以在PLinkQuadNode中被連接成四叉樹(shù)
?
在unity的界面中是這樣的
?
點(diǎn)擊連接按鈕它就會(huì)連接子節(jié)點(diǎn)中所有實(shí)現(xiàn)ILinkable的節(jié)點(diǎn)
?
你可以看到我并沒(méi)有開(kāi)始運(yùn)行游戲 而Terrain中已經(jīng)有了值
這樣我就可以在游戲運(yùn)行之前就連接好四叉樹(shù) 不會(huì)在游戲中消耗性能
所以PLinkQuadNode中的P表示的是Preprocess
凡是這種腳本都放在Preprocess文件分類(lèi)中有了連接好的四叉樹(shù)
那么就可以遍歷了
這里我寫(xiě)了三種遍歷器 而且每種遍歷器都使用了策略模式和享元模式
?
第一個(gè)構(gòu)造器中使用的IIterateBehaviour接口用來(lái)定義遍歷時(shí)的行為
?
先實(shí)現(xiàn)接口
?
再把他們組合起來(lái)
?
最后呈現(xiàn)出來(lái)的效果
?
如果改變遍歷時(shí)的行為
?
就會(huì)這樣渲染
?
這里我還可以使用相同的遍歷行為 但使用不同的遍歷方法 這樣就從策略模式變成橋接模式了
?
?
?
?
這里我測(cè)試了遍歷的邏輯
?
?
我建立了一個(gè)測(cè)試文件夾 里面存放test代碼
?
每次遍歷到一個(gè)節(jié)點(diǎn) 就把它z軸提高
這樣我可以清除的看到是否有重復(fù)遍歷
?
最后是享元模式 每個(gè)遍歷器都可以選擇創(chuàng)建新的變量 也可以公用其他遍歷器實(shí)例的變量