英雄属性

单位状态(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写的,数值计算速度极快。