TH1/Book/5.6数值组件设计.md
2025-07-17 18:26:28 +08:00

215 lines
7.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

类似魔兽世界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个种族每个种族78种英雄光这些数值类继承关系你就得懵逼了吧。面向对象是难以适应这种灵活的复杂的需求的。
再来看看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型也可以转成整数例如乘以1000key value保存属性会变得非常灵活例如法师没有能量属性那么初始化法师对象不加能量的key value就好了。盗贼没有法力值没有法术伤害等等初始化就不用加这些。
2.魔兽世界中一个数值由5个值来影响可以统一使用一条公式
```
final = (((base + add) * (100 + pct) / 100) + finalAdd) * (100 + finalPct) / 100;
```
比如说速度值speed有个初始值speedbase有个buff1增加10点绝对速度那么buff1创建的时候会给speedadd加10buff1删除的时候给speedadd减10buff2增加20%的速度那么buff2创建的时候给speedpct加20buff2删除的时候给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**