翼度科技»论坛 编程开发 .net 查看内容

ET介绍——数值组件设计

5

主题

5

帖子

15

积分

新手上路

Rank: 1

积分
15
类似魔兽世界,moba这种技能极其复杂,灵活性要求极高的技能系统,必须需要一套及其灵活的数值结构来搭配。数值结构设计好了,实现技能系统就会非常简单,否则就是一场灾难。比如魔兽世界,一个人物的数值属性非常之多,移动速度,力量,怒气,能量,集中值,魔法值,血量,最大血量,物理攻击,物理防御,法术攻击,法术防御,等等多达几十种之多。属性跟属性之间又相互影响,buff又会给属性增加绝对值,增加百分比,或者某种buff又会在算完所有的增加值之后再来给你翻个倍。
普通的做法:

一般就是写个数值类:
  1. class Numeric
  2. {
  3.     public int Hp;
  4.     public int MaxHp;
  5.     public int Speed;
  6.     // 能量
  7.     public int Energy;
  8.     public int MaxEnergy;
  9.     // 魔法
  10.     public int Mp;
  11.     public int MaxMp;
  12.     .....
  13. }
复制代码
 
仔细一想,我一个盗贼使用的是能量,为什么要有一个Mp的值?我一个法师使用的是魔法为什么要有能量的字段?纠结这个搞毛,当作没看见不就行了吗?实在不行,我来个继承?
  1. // 法师数值
  2. calss MageNumeric: Numeric
  3. {
  4.     // 魔法
  5.     public int Mp;
  6.     public int MaxMp;
  7. }
  8. // 盗贼数值
  9. calss RougeNumeric: Numeric
  10. {
  11.     // 能量
  12.     public int Energy;
  13.     public int MaxEnergy;
  14. }
复制代码
 
10个种族,每个种族7,8种英雄,光这些数值类继承关系,你就得懵逼了吧。面向对象是难以适应这种灵活的复杂的需求的。
再来看看Numeric类,每种数值可不能只设计一个字段,比如说,我有个buff会增加10点Speed,还有种buff增加50%的speed,那我至少还得加三个二级属性字段
  1. class Numeric
  2. {
  3.     // 速度最终值
  4.     public int Speed;
  5.     // 速度初始值
  6.     public int SpeedInit;
  7.     // 速度增加值
  8.     public int SpeedAdd;
  9.     // 速度增加百分比值
  10.     public int SpeedPct;
  11. }
复制代码
 
SpeedAdd跟SpeedPct改变后,进行一次计算,就可以算出最终的速度值。buff只需要去修改SpeedAdd跟SpeedPct就行了。
  1. Speed = (SpeedInit + SpeedAdd) * (100 + SpeedPct) / 100
复制代码
 
每种属性都可能有好几种间接影响值,可以想想这个类是多么庞大,初略估计得有100多个字段。麻烦的是计算公式基本一样,但是就是无法统一成一个函数,例如MaxHp,也有buff影响
  1. class Numeric
  2. {
  3.     public int Speed;
  4.     public int SpeedInit;
  5.     public int SpeedAdd;
  6.     public int SpeedPct;
  7.    
  8.     public int MaxHp;
  9.     public int MaxHpInit;
  10.     public int MaxHpAdd;
  11.     public int MaxHpPct;
  12. }
复制代码
 
也得写个Hp的计算公式
  1. MaxHp=(MaxHpInit + MaxHpAdd) * (100  + MaxHpPct) / 100
复制代码
 
几十种属性,就要写几十遍,并且每个二级属性改变都要正确调用对应的公式计算. 非常麻烦! 这样设计还有个很大的问题,buff配置表填对应的属性字段不是很好填,例如疾跑buff(增加速度50%),在buff表中怎么配置才能让程序简单的找到并操作SpeedPct字段呢?不好搞。
ET框架采用了Key Value形式保存数值属性
  1. using System.Collections.Generic;
  2. namespace Model
  3. {
  4.     public enum NumericType
  5.     {
  6.         Max = 10000,
  7.         Speed = 1000,
  8.         SpeedBase = Speed * 10 + 1,
  9.         SpeedAdd = Speed * 10 + 2,
  10.         SpeedPct = Speed * 10 + 3,
  11.         SpeedFinalAdd = Speed * 10 + 4,
  12.         SpeedFinalPct = Speed * 10 + 5,
  13.         Hp = 1001,
  14.         HpBase = Hp * 10 + 1,
  15.         MaxHp = 1002,
  16.         MaxHpBase = MaxHp * 10 + 1,
  17.         MaxHpAdd = MaxHp * 10 + 2,
  18.         MaxHpPct = MaxHp * 10 + 3,
  19.         MaxHpFinalAdd = MaxHp * 10 + 4,
  20.         MaxHpFinalPct = MaxHp * 10 + 5,
  21.     }
  22.     public class NumericComponent: Component
  23.     {
  24.         public readonly Dictionary<int, int> NumericDic = new Dictionary<int, int>();
  25.         public void Awake()
  26.         {
  27.             // 这里初始化base值
  28.         }
  29.         public float GetAsFloat(NumericType numericType)
  30.         {
  31.             return (float)GetByKey((int)numericType) / 10000;
  32.         }
  33.         public int GetAsInt(NumericType numericType)
  34.         {
  35.             return GetByKey((int)numericType);
  36.         }
  37.         public void Set(NumericType nt, float value)
  38.         {
  39.             this[nt] = (int) (value * 10000);
  40.         }
  41.         public void Set(NumericType nt, int value)
  42.         {
  43.             this[nt] = value;
  44.         }
  45.         public int this[NumericType numericType]
  46.         {
  47.             get
  48.             {
  49.                 return this.GetByKey((int) numericType);
  50.             }
  51.             set
  52.             {
  53.                 int v = this.GetByKey((int) numericType);
  54.                 if (v == value)
  55.                 {
  56.                     return;
  57.                 }
  58.                 NumericDic[(int)numericType] = value;
  59.                 Update(numericType);
  60.             }
  61.         }
  62.         private int GetByKey(int key)
  63.         {
  64.             int value = 0;
  65.             this.NumericDic.TryGetValue(key, out value);
  66.             return value;
  67.         }
  68.         public void Update(NumericType numericType)
  69.         {
  70.             if (numericType > NumericType.Max)
  71.             {
  72.                 return;
  73.             }
  74.             int final = (int) numericType / 10;
  75.             int bas = final * 10 + 1;
  76.             int add = final * 10 + 2;
  77.             int pct = final * 10 + 3;
  78.             int finalAdd = final * 10 + 4;
  79.             int finalPct = final * 10 + 5;
  80.             // 一个数值可能会多种情况影响,比如速度,加个buff可能增加速度绝对值100,也有些buff增加10%速度,所以一个值可以由5个值进行控制其最终结果
  81.             // final = (((base + add) * (100 + pct) / 100) + finalAdd) * (100 + finalPct) / 100;
  82.             this.NumericDic[final] = ((this.GetByKey(bas) + this.GetByKey(add)) * (100 + this.GetByKey(pct)) / 100 + this.GetByKey(finalAdd)) * (100 + this.GetByKey(finalPct)) / 100;
  83.             Game.EventSystem.Run(EventIdType.NumbericChange, this.Entity.Id, numericType, final);
  84.         }
  85.     }
  86. }
复制代码
 
1.数值都用key value来保存,key是数值的类型,由NumericType来定义,value都是整数,float型也可以转成整数,例如乘以1000;key value保存属性会变得非常灵活,例如法师没有能量属性,那么初始化法师对象不加能量的key value就好了。盗贼没有法力值,没有法术伤害等等,初始化就不用加这些。
2.魔兽世界中,一个数值由5个值来影响,可以统一使用一条公式:
  1. 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的变化:
  1.     /// 监视hp数值变化
  2.     [NumericWatcher(NumericType.Hp)]
  3.     public class NumericWatcher_Hp : INumericWatcher
  4.     {
  5.         public void Run(long id, int value)
  6.         {
  7.             if (value > 1000)
  8.             {
  9.                 //获得成就长寿大师成就
  10.             }
  11.         }
  12.     }
复制代码
 
同理,记录一次金币变化大于10000的异常日志等等都可以这样做。
有了这个数值组件,一个moba技能系统可以说已经完成了一半。
ET开源地址地址:egametang/ET: Unity3D Client And C# Server Framework (github.com)   qq群:474643097

来源:https://www.cnblogs.com/flamesky/archive/2023/05/19/17414908.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

举报 回复 使用道具