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

## 普通的做法:
一般就是写个数值类：
```c#
class Numeric
{
    public int Hp;
    public int MaxHp;
    public int Speed;
    // 能量
    public int Energy;
    public int MaxEnergy;
    // 魔法
    public int Mp;
    public int MaxMp;
    .....
}
```
仔细一想，我一个盗贼使用的是能量，为什么要有一个Mp的值？我一个法师使用的是魔法为什么要有能量的字段？纠结这个搞毛，当作没看见不就行了吗？实在不行，我来个继承？
```C#
// 法师数值
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，那我至少还得加三个二级属性字段
```c#
class Numeric
{
    // 速度最终值
    public int Speed;
    // 速度初始值
    public int SpeedInit;
    // 速度增加值
    public int SpeedAdd;
    // 速度增加百分比值
    public int SpeedPct;
}
```
SpeedAdd跟SpeedPct改变后，进行一次计算，就可以算出最终的速度值。buff只需要去修改SpeedAdd跟SpeedPct就行了。
```c#
Speed = (SpeedInit + SpeedAdd) * (100 + SpeedPct) / 100
```
每种属性都可能有好几种间接影响值，可以想想这个类是多么庞大，初略估计得有100多个字段。麻烦的是计算公式基本一样，但是就是无法统一成一个函数，例如MaxHp，也有buff影响
```c#
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的计算公式
```c#
MaxHp=(MaxHpInit + MaxHpAdd) * (100  + MaxHpPct) / 100
```
几十种属性，就要写几十遍，并且每个二级属性改变都要正确调用对应的公式计算. 非常麻烦！
这样设计还有个很大的问题，buff配置表填对应的属性字段不是很好填，例如疾跑buff（增加速度50%），在buff表中怎么配置才能让程序简单的找到并操作SpeedPct字段呢？不好搞。

## ET框架采用了Key Value形式保存数值属性
```c#
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技能系统可以说已经完成了一半。

**代码地址：https://github.com/egametang/Egametang**


