目錄json
遊戲類型:塔防+RPG的3D遊戲架構
遊戲要素:3D 塔防 英雄 建築樹 搭配函數
主體玩法:遊戲裏將會有一波波怪物進攻基地。玩家能夠建造塔來防護敵人,同時也能夠控制單獨的個體英雄角色來攻擊敵人。工具
遊戲模式:this
控制方式:在遊戲中使用Tab按鍵,切換這兩種操做模式:編碼
勝利條件:消滅全部敵人 或者 堅持到時間結束插件
失敗條件:基地生命值爲0 或者 英雄死亡架構設計
通常來講,整個Unity遊戲項目總體結構,我比較偏向分爲以下5部分:設計
場景對象: 不會產生互動的可視物體對象,例如地型/建築/燈光。調試
遊戲對象: 參與互動的遊戲對象,例如英雄/怪物/塔。
遊戲邏輯: 負責控制遊戲的邏輯,其邏輯對象通常是單例的。
非遊戲性對象: 負責加強遊戲效果,但不是直接的遊戲邏輯,例如UI/HUD/特效/聲音。
工具: 負責輔助編碼,例如日誌工具,調試工具。
在《ATD》遊戲項目裏,我是這樣設置遊戲對象目錄的:
注:「個體」在《ATD》裏的術語表示遊戲對象單位。
經過分析《ATD》策劃案,確立了兩種基本遊戲機制:
和策劃商量後,策劃製做了下面一張含全部Buff屬性的Excel表:
因爲策劃還沒想好Buff名字,直接套用裝備或者技能名字來命名Buff。
首先,使用了一個數據類型BuffData,用於徹底映射Buff在表格的全部屬性:
public class BuffData { public int ID; public string Name; public int HpChange; //血量變化 public double HpChange_p; //血量百分比變化 public int AttackChange; //攻擊力變化 public double AttackChange_p; //攻擊力百分比變化 public double AttSpeedChange_p; //攻擊速度百分比變化 public double SpeedChange_p; //速度百分比變化 public int HpReturnChange; //血量恢復數值 public double HpReturnChange_p; //血量百分比恢復數值 public int AddReviveCount; //增長復活次數 public bool isDecelerate; //減速 public bool isVertigo; //眩暈 public bool isParalysis; //麻痹 public bool isSleep; //睡眠 public bool isBound; //束縛 public bool isBurn; //點燃 public bool isCharm; //魅惑 public bool isIncreaseAttSpeed; //攻速提升 public bool isPoisoning; //中毒 public bool isImmuneControl; //免疫控制 public bool isRevenge; //復仇 public bool isTaunt; //嘲諷 public bool isIncreaseHpReturn; //回血速度提升 public bool isIncreaseAttack; //攻擊力提升 }
而後咱們就能夠用一個List
//全局單例類 public class BuffDataBase : MonoBehaviour { //讀取excel插件生成的json文件 public TextAsset BuffDataJson; //存儲BuffData的列表 private List<BuffData> buffDatas; //全局單例實現 //... //根據ID獲取相應的BuffData對象 public BuffData GetBuffData(int ID){ //... } }
爲了表示遊戲對象動態獲得/失去一個Buff而從BuffDataBase找到對應並拷貝一份BuffData對象/釋放掉一份BuffData對象顯然是不明智的。(BuffData所佔空間大,開銷大)
正確的作法應該是使用索引/引用的方式,例如某個遊戲對象持有3號索引,則表示它當前受一個3號Buff影響。
爲了引入Buff的時間有效性,則進一步封裝索引,因而編寫了下面一個Buff類:
public class Buff { public int ID; //BuffData的ID(索引) public double time; //持續時間 public int repeatCount; //重複次數 public bool isTrigger; //是否觸發類型 }
由於每一個Buff的時間有效性都有所不一樣:有些Buff是一次性觸發Buff;也有一些是持續性Buff,持續N秒;還有一些是被動buff,永久生效。
因此我這裏就總結了個規則,Buff主要分爲兩種類型:
而後Buff的有效時間取決於2個屬性:
當一個Buff對象,持續時間 <= 0 而且 觸發次數爲0,則應視爲失效。特殊地,觸發次數爲-1時,表示無限時間。
這樣Buff/BuffData/BuffDataBase基本構造就這樣了:
整個遊戲同種類Buff只用存儲一份BuffData;可是能夠有不少個對象持有索引/引用,指向這個BuffData
遊戲對象持有Buff對象,經過BuffDataBase訪問BuffData的數據,而後利用這些數據對遊戲對象屬性形成影響
看到這裏,可能會有人想到前面有個問題:對於任意一種Buff,它每每有不少屬性是false或者0,使用這種徹底映射會不會很影響空間佔用或者效率。
首先,空間佔用絕對不用擔憂,由於前面BuffDataBase機制保證同種Buff只有惟一BuffData副本,其全部BuffData總共佔用量不過幾kb而已。
其次,至於效率,例如說某個Buff對某個遊戲對象形成影響,由於是徹底映射,因此須要對該遊戲對象每一個屬性都要進行更新,其實這也並非太糟糕。並且只要遊戲對象有比較好的Buff計算方式,可讓一個Buff對象的整個有效週期只對對象形成兩次影響計算(生效影響,失效影響),避免每幀出現影響多餘的計算,這樣就很不錯了。
能夠說技能是我比較頭疼的部分。
看到那千奇百怪的Skill需求時,而後才總結出大概這幾個分類:
最後我決定使用繼承接口的方式來實現Skill:
技能接口類:
public interface ISkill { // 技能初始化接口 void InitSkill(Individual user); // 使用技能接口 void ReleaseSkill(Individual user); /// 技能每幀更新 void UpdateSkill(Individual user); /// 技能是否冷卻 bool IsColdTimeEnd(); // 技能冷卻百分比 float GetColdTimePercent(); }
須要注意的一點是,技能並非主動釋放時調用一個自定義的技能函數便可完事:
例如持續性的範圍技能,須要每幀調用散發Buff的函數。
因此一個ISkill對象 該有這3種重要的接口方法:初始化/主動釋放/每幀更新
下面是其中一個派生類的具體實現:
因爲進度未完,目前只有兩個派生類:Buff技能類和召喚技能類。
Buff技能類暫時包含了ActiveBuff技能類和PassiveBuff技能類的功能。
// 示例:Buff技能類 public class BuffSkill : ISkill { public int buffID; //目的Buff public bool isAura = true; //光環 public bool releasable = true; //是否主動釋放 public float range = 0.01f; //範圍 private float coldTime = 5.0f; //冷卻時間 private float timer = 5.0f; //冷卻計時 public BuffSkill(int buffID,bool releasable = true,bool isAura = true, float range = 0.01f) { this.buffID = buffID; this.isAura = isAura; this.range = range; this.releasable = releasable; } public void InitSkill(Individual master) { if (!releasable && !isAura) { var individual = master.GetComponent<Individual>(); master.GetComponent<MessageSystem>().SendMessage(2, individual.ID,buffID); } } public void ReleaseSkill(Individual master) { if (releasable && IsColdTimeEnd()) { timer = 0.0f; Factory.TraversalIndividualsInCircle( (individual) => { master.GetComponent<MessageSystem>().SendMessage(2, individual.ID, buffID); } , master.transform.position, range); } } public void UpdateSkill(Individual master) { //增長計時 timer =Mathf.Min(timer+Time.deltaTime, coldTime+0.1f); if (!releasable && isAura) { Factory.TraversalIndividualsInCircle( (individual) => { master.GetComponent<MessageSystem>().SendMessage(2, individual.ID, buffID); } , master.transform.position, range); } } public float GetColdTimePercent() { if (!releasable) return 1.0f; return timer / coldTime; } public bool IsColdTimeEnd() { return timer > coldTime; } }
派生類的構造函數很重要,這樣即便硬編碼了4個技能派生類,經過不一樣的數據參數傳入,也能產生更多不一樣的技能對象。
最後還應該再寫一個SkillDataBase全局單例類,它負責讀取策劃寫的技能配置文件,來初始化出來一些Skill對象,以供遊戲對象使用。
不過項目代碼還沒寫完,所以目前是直接在SkillDataBase的初始化函數直接硬編碼3個技能。
//TODO //目前硬編碼給玩家賦予3個技能 HeroSkills.Add(new BuffSkill(6, true, true, 5.0f)); //主動技能:嘲諷Buff HeroSkills.Add(new BuffSkill(0, false, false)); //被動技能:回血buff HeroSkills.Add(new BuffSkill(14, true, false)); //主動技能:攻速戒指buff
之後的話,SkillDataBase的初始化函數應該是讀取某種配置文件,而後生成若干個對應的技能對象分配給遊戲對象使用:
《ATD》只是社團部門內提出的一個遊戲項目,而我負責這個項目的程序架構設計,然而中途開發由於很多事,咱們不得不放棄了這個項目。所以纔想寫點東西總結一下開發這個項目時的經驗。
以後還會有新博文來更新這個系列,大概涉及《ATD》的遊戲對象模型,全局遊戲邏輯,UI/HUD/特效/聲音管理,工具等,也同時會分享一些trick。