英雄属性
单位状态(State)
单位状态(晕眩/冰冻/沉默/魅惑/嘲讽/石化/禁锢/隐身/无敌等),只需要一个bool标记,多种状态可以并存,因此我们用一个64位整数来存储,每个bit代表一种状态,最多可以有64种状态,足够游戏中战斗用了。
public enum UnitStateType {
Stunned = 0; // 晕眩
Sleeping = 1; // 沉睡
Fronzen = 2; // 冰冻
Chaos = 3; // 混乱
Silence = 4; // 沉默
Disarm = 5; // 缴械
Taunted = 6; // 嘲讽
Stone = 7; // 石化
Rooted = 8; // 禁锢
Invisible = 9; // 隐藏
Invincible = 10; // 无敌
// 更多状态定义在下面,最多大值63
}
public class UnitStates {
public UInt64 Data { get; private set; }
public void AddState(UnitStateType type) {
Data |= GetStateBits(type);
}
public void RemoveState(UnitStateType type) {
Data &= ~GetStateBits(type);
}
public bool HasState(UnitStateType type)
=> (Data & GetStateBits(type)) != 0;
public UInt64 GetStateBits(UnitStateType type)
=> (1 << (int)type);
}
单位属性(Attribute)
为了让战斗属性更加灵活,我们并不在一个类里定义所有属性,而是用表格的方式存储所有属性列表,方便以后增加新的属性。我们使用定点数来确保战斗计算结果的确定性,所以数值存储使用FixedNumber类。
public enum UnitAttributeType {
Health,
MaxHealth,
PhysicalDamage,
MagicalDamage,
PhysicalArmor,
MagicalArmor,
CriticalChance,
CriticalDamage,
MoveSpeed,
AttackSpeed,
AttackRange,
// 更多属性添加下面,数字递增
MaxCount
}
public class UnitAttributes
{
public List<FixedNumber> Data { get; private set; }
public UnitAttributes() {
Data = new List<FixedNumber>();
for (int i = 0; i < UnitAttributeType.MaxCount; i++) {
Data.Add(0);
}
}
public void SetAttribute(UnitAttributeType type, FixedNumber amount) {
Data[(int)type].Set(amount);
}
public void AddAttribute(UnitAttributeType type, FixedNumber amount) {
Data[(int)type].Add(amount);
}
}
战斗属性计算
属性的改变一般有两种:绝对值加量,百分比加量。我们的游戏中还有一种特殊的技能效果:属性拷贝,即一个英雄偷取另外一个英雄属性。由于属性计算过程比较复杂,被各种Buff/Debuff影响,之前我做的一个战斗系统中,偶尔会出现策划反应数值不大对劲,但又没有精确的证据证明数值有不错错误。 为了解决这个问题,新的战斗属性计算我采用这样的设计:所有属性的改变都单独存储,每帧都重新计算最终属性。
public class Unit {
private UnitAttributes CurrentAttr { get; private set } = new();
private UnitAttributes BaseAttr { get; private set } = new();
private UnitAttributes AddedAttr { get; private set } = new();
private UnitAttributes PercentAttr { get; private set } = new();
private UnitAttributes OverrideAttr { get; private set }
public void UpdateAttributes() {
if (OverrideAttr != null) {
BaseAttr.CopyFrom(OverrideAttr);
AddedAttr.CopyFrom(OverrideAttr);
PercentAttr.CopyFrom(OverrideAttr);
}
for (int i = 0; i < CurrentAttr.Data.Count; i++) {
CurrentAttr[i].Set((BaseAttr[i] + AddedAttr[i]) * PercentAttr[i]);
}
}
}
上面代码只是个示例,实际项目中计算过程比这个复杂很多。同时也不用担心性能问题,我们的顶点数是用纯C写的,数值计算速度极快。