前言
技能系统的数值可以说是五花八门,每种技能的参数可能千奇百怪。
很多技能系统采用excel来配置技能数据。表头会变得很长,并且可能是好几张表,制作一个技能,需要几张表来回跳,个人不太喜欢这种方式。
本技能系统的数值表,采用类似dota2的数据驱动配置方式。
SpecialTable
数据源,键值对,key为string,value为Var。可以扩展csv格式导入导出,以支持批量数值编辑
Var
数值类,泛型实现,有VarSingle、VarInt等等类型,string,是一个类型数据的集合,可以直接填写数据,也可以通过$key来索引SpecialTable中的数据。
配置文件加载时,调用接口解析数据(默认使用反射,无需编写代码),字符串解析使用了Span API,无任何临时字符串,Var保存List
Var_T
public abstract class Var<T> : Var
{
[NonSerialized]
[JsonIgnore]
public List<T> values;
public T this[int index] => GetValue(index);
[JsonIgnore]
public int Count => values != null ? values.Count : 0;
public Var() { }
public Var(string txt) => this.txt = txt;
public sealed override object GetRawValue(int index) => GetValue(index);
public T GetValue(int index = 0)
{
if (values == null || values.Count == 0) return default;
if (index >= values.Count) return values[^1];
return values[index];
}
public sealed override void Parse(IReadOnlyList<VarPair> specials)
{
if (raw != null) return;
if (string.IsNullOrEmpty(txt)) return;
if (txt.StartsWith(Ref))
ParseRef(specials);
else
ParseValues();
}
private void ParseRef(IReadOnlyList<VarPair> specials)
{
if (specials == null || specials.Count == 0)
{
AbilityLog.ERROR($"ParseValues Error. {nameof(txt)}: {txt} {nameof(specials)} is null or empty");
return;
}
var valueSpan = txt.AsSpan();
for (int i = 0; i < specials.Count; i++)
{
var special = specials[i];
if (special.name.AsSpan().SequenceEqual(valueSpan[1..])) // 切掉 $ 字符
{
values = (List<T>)special.value.raw;
break;
}
}
Log.Assert(values != null, $"{nameof(ParseRef)} failed. '{txt}'");
}
protected abstract void ParseValues();
}
VarInt
[Serializable]
public sealed class VarInt : Var<int>
{
public VarInt() { }
public VarInt(string txt) : base(txt) { }
protected override void ParseValues()
{
var valueSpan = txt.AsSpan().Trim();
if (valueSpan.IsEmpty) return;
var ranges = txt.SplitFast(' ');
raw = values = new List<int>();
foreach (var range in ranges)
{
if (range.Start.Value == range.End.Value) continue;
var item = valueSpan[range].Trim();
if (item.IsEmpty) continue;
if (int.TryParse(item, out var val))
values.Add(val);
else
ParseError();
}
}
}
IVarMagnitudeCalculation
数值计算类接口,可以自定义扩展数值计算规则,并且可以在editor进行编辑,提供AttributeBasedSingle(基于属性计算)等数值计算类子类
AttributeBasedFloat
[Serializable]
[MovedFrom(true, sourceClassName: "AttributeBasedFloat")]
public sealed class AttributeBasedSingle : IVarMagnitudeCalculation
{
public enum EAttributeSource : byte
{
Caster = 0,
Target = 1,
}
public enum EAttributeCalculateType : byte
{
CurrValue = 0,
BaseValue = 1,
}
[FormerlySerializedAs("modifierId")]
public int attribute;
public EAttributeSource source;
public EAttributeCalculateType calculateType;
// coefficient * (attribute + preMulAddValue) + postMulAddValue
public VarSingle coefficient = new("1");
public VarSingle preMulAddValue;
public VarSingle postMulAddValue;
public Single CalculateMagnitude(EffectObj effect, int level)
{
var set = source == EAttributeSource.Target ?
effect.TargetAttributeSet :
effect.CasterAttributeSet;
Log.Assert(set != null, $"{effect.Config.id} {source}'s AttributeSet is null");
if (set != null)
{
var att = set[attribute];
Single attributeValue = (Single)0f;
if (calculateType == EAttributeCalculateType.CurrValue)
attributeValue = att.CurrValue;
else if (calculateType == EAttributeCalculateType.BaseValue)
attributeValue = att.BaseValue;
return coefficient.GetValue(level) * (attributeValue + preMulAddValue.GetValue(level)) + postMulAddValue.GetValue(level);
}
return (Single)0f;
}
public void Parse(IReadOnlyList<VarPair> specials)
{
coefficient.Parse(specials);
preMulAddValue.Parse(specials);
postMulAddValue.Parse(specials);
}
}
总结
通过上述,可实现配置统一,每个技能一个SpecialTable,数据想怎么定义怎么定义,而不用增加任何代码去解析,运行时性能也不错