類似魔獸世界,moba這種技能極其複雜,靈活性要求極高的技能系統,必須需要一套及其靈活的數值結構來搭配。數值結構設計好了,實現技能系統就會非常簡單,否則就是一場災難。比如魔獸世界,一個人物的數值屬性非常之多,移動速度,力量,怒氣,能量,集中值,魔法值,血量,最大血量,物理攻擊,物理防禦,法術攻擊,法 ...
類似魔獸世界,moba這種技能極其複雜,靈活性要求極高的技能系統,必須需要一套及其靈活的數值結構來搭配。數值結構設計好了,實現技能系統就會非常簡單,否則就是一場災難。比如魔獸世界,一個人物的數值屬性非常之多,移動速度,力量,怒氣,能量,集中值,魔法值,血量,最大血量,物理攻擊,物理防禦,法術攻擊,法術防禦,等等多達幾十種之多。屬性跟屬性之間又相互影響,buff又會給屬性增加絕對值,增加百分比,或者某種buff又會在算完所有的增加值之後再來給你翻個倍。
普通的做法:
一般就是寫個數值類:
class Numeric { public int Hp; public int MaxHp; public int Speed; // 能量 public int Energy; public int MaxEnergy; // 魔法 public int Mp; public int MaxMp; ..... }
仔細一想,我一個盜賊使用的是能量,為什麼要有一個Mp的值?我一個法師使用的是魔法為什麼要有能量的欄位?糾結這個搞毛,當作沒看見不就行了嗎?實在不行,我來個繼承?
// 法師數值 calss MageNumeric: Numeric { // 魔法 public int Mp; public int MaxMp; } // 盜賊數值 calss RougeNumeric: Numeric { // 能量 public int Energy; public int MaxEnergy; }
10個種族,每個種族7,8種英雄,光這些數值類繼承關係,你就得懵逼了吧。面向對象是難以適應這種靈活的複雜的需求的。
再來看看Numeric類,每種數值可不能只設計一個欄位,比如說,我有個buff會增加10點Speed,還有種buff增加50%的speed,那我至少還得加三個二級屬性欄位
class Numeric { // 速度最終值 public int Speed; // 速度初始值 public int SpeedInit; // 速度增加值 public int SpeedAdd; // 速度增加百分比值 public int SpeedPct; }
SpeedAdd跟SpeedPct改變後,進行一次計算,就可以算出最終的速度值。buff只需要去修改SpeedAdd跟SpeedPct就行了。
Speed = (SpeedInit + SpeedAdd) * (100 + SpeedPct) / 100
每種屬性都可能有好幾種間接影響值,可以想想這個類是多麼龐大,初略估計得有100多個欄位。麻煩的是計算公式基本一樣,但是就是無法統一成一個函數,例如MaxHp,也有buff影響
class Numeric { public int Speed; public int SpeedInit; public int SpeedAdd; public int SpeedPct; public int MaxHp; public int MaxHpInit; public int MaxHpAdd; public int MaxHpPct; }
也得寫個Hp的計算公式
MaxHp=(MaxHpInit + MaxHpAdd) * (100 + MaxHpPct) / 100
幾十種屬性,就要寫幾十遍,並且每個二級屬性改變都要正確調用對應的公式計算. 非常麻煩! 這樣設計還有個很大的問題,buff配置表填對應的屬性欄位不是很好填,例如疾跑buff(增加速度50%),在buff表中怎麼配置才能讓程式簡單的找到並操作SpeedPct欄位呢?不好搞。
ET框架採用了Key Value形式保存數值屬性
using System.Collections.Generic; namespace Model { public enum NumericType { Max = 10000, Speed = 1000, SpeedBase = Speed * 10 + 1, SpeedAdd = Speed * 10 + 2, SpeedPct = Speed * 10 + 3, SpeedFinalAdd = Speed * 10 + 4, SpeedFinalPct = Speed * 10 + 5, Hp = 1001, HpBase = Hp * 10 + 1, MaxHp = 1002, MaxHpBase = MaxHp * 10 + 1, MaxHpAdd = MaxHp * 10 + 2, MaxHpPct = MaxHp * 10 + 3, MaxHpFinalAdd = MaxHp * 10 + 4, MaxHpFinalPct = MaxHp * 10 + 5, } public class NumericComponent: Component { public readonly Dictionary<int, int> NumericDic = new Dictionary<int, int>(); public void Awake() { // 這裡初始化base值 } public float GetAsFloat(NumericType numericType) { return (float)GetByKey((int)numericType) / 10000; } public int GetAsInt(NumericType numericType) { return GetByKey((int)numericType); } public void Set(NumericType nt, float value) { this[nt] = (int) (value * 10000); } public void Set(NumericType nt, int value) { this[nt] = value; } public int this[NumericType numericType] { get { return this.GetByKey((int) numericType); } set { int v = this.GetByKey((int) numericType); if (v == value) { return; } NumericDic[(int)numericType] = value; Update(numericType); } } private int GetByKey(int key) { int value = 0; this.NumericDic.TryGetValue(key, out value); return value; } public void Update(NumericType numericType) { if (numericType > NumericType.Max) { return; } int final = (int) numericType / 10; int bas = final * 10 + 1; int add = final * 10 + 2; int pct = final * 10 + 3; int finalAdd = final * 10 + 4; int finalPct = final * 10 + 5; // 一個數值可能會多種情況影響,比如速度,加個buff可能增加速度絕對值100,也有些buff增加10%速度,所以一個值可以由5個值進行控制其最終結果 // final = (((base + add) * (100 + pct) / 100) + finalAdd) * (100 + finalPct) / 100; this.NumericDic[final] = ((this.GetByKey(bas) + this.GetByKey(add)) * (100 + this.GetByKey(pct)) / 100 + this.GetByKey(finalAdd)) * (100 + this.GetByKey(finalPct)) / 100; Game.EventSystem.Run(EventIdType.NumbericChange, this.Entity.Id, numericType, final); } } }
1.數值都用key value來保存,key是數值的類型,由NumericType來定義,value都是整數,float型也可以轉成整數,例如乘以1000;key value保存屬性會變得非常靈活,例如法師沒有能量屬性,那麼初始化法師對象不加能量的key value就好了。盜賊沒有法力值,沒有法術傷害等等,初始化就不用加這些。
2.魔獸世界中,一個數值由5個值來影響,可以統一使用一條公式:
final = (((base + add) * (100 + pct) / 100) + finalAdd) * (100 + finalPct) / 100;
比如說速度值speed,有個初始值speedbase,有個buff1增加10點絕對速度,那麼buff1創建的時候會給speedadd加10,buff1刪除的時候給speedadd減10,buff2增加20%的速度,那麼buff2創建的時候給speedpct加20,buff2刪除的時候給speedpct減20.甚至可能有buff3,會在最終值上再加100%,那麼buff3將影響speedfinalpct。這5個值發生改變,統一使用Update函數就可以重新計算對應的屬性了。buff配置中對應數值欄位相當簡單,buff配置中填上相應的NumericType,程式很輕鬆就能操作對應的數值。
3.屬性的改變可以統一拋出事件給其它模塊訂閱,寫一個屬性變化監視器變得非常簡單。例如成就模塊需要開發一個成就生命值超過1000,會獲得長壽大師的成就。那麼開發成就模塊的人將訂閱HP的變化:
/// 監視hp數值變化 [NumericWatcher(NumericType.Hp)] public class NumericWatcher_Hp : INumericWatcher { public void Run(long id, int value) { if (value > 1000) { //獲得成就長壽大師成就 } } }
同理,記錄一次金幣變化大於10000的異常日誌等等都可以這樣做。
有了這個數值組件,一個moba技能系統可以說已經完成了一半。
ET開源地址地址:egametang/ET: Unity3D Client And C# Server Framework (github.com) qq群:474643097