using System; namespace GAS.Runtime { /// /// 复合属性 /// /// 计算公式: /// 最终值 = (基础值 + 基础附加值) * (1 + 累加加成) * 累乘加成 * (1 - 最大值惩罚) /// /// public sealed class CompositeAttribute { public float Value { get; private set; } public float MinValue { get; private set; } public float MaxValue { get; private set; } public delegate float OnPreValueChange(CompositeAttribute compositeAttribute, float oldValue, float newValue); public delegate void OnPostValueChange(CompositeAttribute compositeAttribute, float oldValue, float newValue); private OnPreValueChange _onPreValueChange; private event OnPostValueChange _onPostValueChange; public AbilitySystemComponent Owner { get; private set; } public AttributeBase Base { get; } public AttributeBase BaseAdditiveBonus { get; } public AttributeBase AdditiveBonus { get; } public AttributeBase MultiplicativeBonus { get; } public AttributeBase MaxValuePenalty { get; } /// /// 计算公式: /// 最终值 = (基础值 + 基础附加值) * (1 + 累加加成) * 累乘加成 * (1 - 最大值惩罚) /// /// 基础值 /// 基础附加值:默认0,应使用加/减法来修改。 /// 一般用于增加固定数值的装备加成: 比如增加10点攻击力。 /// 参考英雄联盟中的额外攻击力,若不需要区分基础值和基础附加值,无视此参数。 /// /// 累加加成:默认0,应使用加/减法来修改,25%加成对应的修改参数应该为0.25。 /// 将所有的累加加成求和之后作为一个加成值(1 + a1 + a2 + ... + an), 它们的收益是会"稀释"的。 /// 一般用于增加百分比的装备加成: 比如增加25%伤害。 /// /// 累乘加成:默认1(需要你自己设置),应使用乘法(除法本质上可以, 但是不推荐, 除法看起来会比较绕, 而且除法性能上不如乘法)来修改,25%加成对应的修改参数应该为1.25。 /// 每个技能的加成值都以乘法来计算(m1 * m2 * ... * mn), 它们的收益不会被"稀释",因此累乘加成效果很强大,但同时也是游戏后期数值容易爆炸的根源! 具体使用累加还是累乘取决于设定。 /// 一般用于技能/天赋/被动加成: 比如增加5%总伤害(有些游戏会用"总增"这个词来描述此类效果)。 /// /// 最大值惩罚:默认0, 取值范围一般为[0~1]. 注意:25%惩罚对应的修改参数应该为0.25,而不是-0.25。 /// 仅在需要惩罚取最大值时使用,参考英雄联盟中的减少移动速度效果, 当被多个debuff减少移动速度时, 只适用减速幅度最大的效果。 /// 该属性应该是MaxValueOnly的,即只有最大值才会生效,不会叠加。 /// /// 最小值 /// 最大值 public CompositeAttribute( AttributeBase @base, AttributeBase baseAdditiveBonus = null, AttributeBase additiveBonus = null, AttributeBase multiplicativeBonus = null, AttributeBase maxValuePenalty = null, float minValue = float.MinValue, float maxValue = float.MaxValue) { Base = @base; BaseAdditiveBonus = baseAdditiveBonus; AdditiveBonus = additiveBonus; MultiplicativeBonus = multiplicativeBonus; MaxValuePenalty = maxValuePenalty; MinValue = minValue; MaxValue = maxValue; Value = CalculateValue(); RegisterAttributeChangedListen(); } public void Dispose() { _onPreValueChange = null; _onPostValueChange = null; UnRegisterAttributeChangedListen(); } public void SetOwner(AbilitySystemComponent owner) { Owner = owner; Base.SetOwner(owner); BaseAdditiveBonus?.SetOwner(owner); AdditiveBonus?.SetOwner(owner); MultiplicativeBonus?.SetOwner(owner); MaxValuePenalty?.SetOwner(owner); } public void Init(float baseValue) => Base.Init(baseValue); public void SetBaseValue(float baseValue) => Base.SetBaseValue(baseValue); public void SetMinValue(float minValue) { MinValue = minValue; Value = CalculateValue(); } public void SetMaxValue(float maxValue) { MaxValue = maxValue; Value = CalculateValue(); } public void SetMinMaxValue(float minValue, float maxValue) { MinValue = minValue; MaxValue = maxValue; Value = CalculateValue(); } /// /// 可以使用这个来做自定义的伤害钳制函数, 甚至当成自定义的数值计算函数 /// 参考英雄联盟中移动速度的计算, 当移动速度超过或低于一定数值会被修正: https://leagueoflegends.fandom.com/wiki/Movement_speed /// public void SetPreValueChangeCallback(OnPreValueChange action) { _onPreValueChange = action; } public void RegisterPostValueChange(OnPostValueChange action) { _onPostValueChange += action; } public void UnregisterPostValueChange(OnPostValueChange action) { _onPostValueChange -= action; } private void RegisterAttributeChangedListen() { Base.RegisterPostCurrentValueChange(OnAttributeChanged); BaseAdditiveBonus?.RegisterPostCurrentValueChange(OnAttributeChanged); AdditiveBonus?.RegisterPostCurrentValueChange(OnAttributeChanged); MultiplicativeBonus?.RegisterPostCurrentValueChange(OnAttributeChanged); MaxValuePenalty?.RegisterPostCurrentValueChange(OnAttributeChanged); } private void UnRegisterAttributeChangedListen() { Base.UnregisterPostCurrentValueChange(OnAttributeChanged); BaseAdditiveBonus?.UnregisterPostCurrentValueChange(OnAttributeChanged); AdditiveBonus?.UnregisterPostCurrentValueChange(OnAttributeChanged); MultiplicativeBonus?.UnregisterPostCurrentValueChange(OnAttributeChanged); MaxValuePenalty?.UnregisterPostCurrentValueChange(OnAttributeChanged); } private void OnAttributeChanged(AttributeBase attribute, float attrOldValue, float attrNewValue) { var oldValue = Value; var newValue = CalculateValue(); newValue = Math.Clamp(newValue, MinValue, MaxValue); if (_onPreValueChange != null) { newValue = _onPreValueChange(this, oldValue, newValue); } if (Math.Abs(oldValue - newValue) < 0.000001f) { return; } Value = newValue; _onPostValueChange?.Invoke(this, oldValue, newValue); } private float CalculateValue() { return CalculateTotalValue(Base, BaseAdditiveBonus, AdditiveBonus, MultiplicativeBonus, MaxValuePenalty); } public static float CalculateTotalValue( AttributeBase @base, AttributeBase baseAdditiveBonus = null, AttributeBase additiveBonus = null, AttributeBase multiplicativeBonus = null, AttributeBase maxValuePenalty = null) { var totalValue = @base.CurrentValue; if (baseAdditiveBonus != null) { totalValue += baseAdditiveBonus.CurrentValue; } // 如果值为0, 计算就没有意义了, 可以忽略 if (totalValue != 0) { if (additiveBonus != null) { totalValue *= 1 + additiveBonus.CurrentValue; } if (multiplicativeBonus != null) { totalValue *= multiplicativeBonus.CurrentValue; } if (maxValuePenalty != null) { totalValue *= 1 - maxValuePenalty.CurrentValue; } } return totalValue; } } }