前言
很多技能系统都是基于事件的,不然代码耦合严重,扩展困难
本技能系统的事件类似于c#的事件模型EventHandler
事件定义
GameplayTag,使用tag来定义各种事件,易扩展,可读性高,性能也不错
partial class Events
{
[Tooltip("Effect创建时\n(args=null)")]
public readonly static GameplayTag Effect_OnCreated = new("Event.Effect.OnCreated");
[Tooltip("Effect销毁时\n(args=null)")]
public readonly static GameplayTag Effect_OnDestroy = new("Event.Effect.OnDestroy");
[Tooltip("Effect周期触发时\n(args=null)")]
public readonly static GameplayTag Effect_OnPeriod = new("Event.Effect.OnPeriod");
[Tooltip("Effect堆叠溢出时\n(args=null)")]
public readonly static GameplayTag Effect_OnStackOverflow = new("Event.Effect.OnStackOverflow");
[Tooltip("Effect堆叠层数变化时\n(args=EventArgs_EffectStackCountChanged)")]
public readonly static GameplayTag Effect_OnStackCountChanged = new("Event.Effect.OnStackCountChanged");
[Tooltip("Effect持续时间刷新时\n(args=null)")]
public readonly static GameplayTag Effect_OnDurationRefresh = new("Event.Effect.DurationRefresh");
}
事件发起者
IGameplayEventSender,用于抽象技能系统里一些对象,例如Effect、Ability、Projectile、AoE
事件参数
GameplayEventArgs,针对不同类型的事件,需要的参数也不同,通过子类来实现
事件调用模型
Map<GameplayTag, f(sender, args)>,sender为事件发起者,args为事件参数。在不同的事件点,调用Map的事件即可。
internal static void HandleGameplayEvent(ref this AoEObj cAoe, GameplayTag eventTag, EcsEntity eAoe, GameplayEventArgs args)
{
var script = cAoe.eventScript;
if (script != null)
{
script.Invoke(eventTag, eAoe, args);
}
if (cAoe.data.eventContainer != null)
{
DataDrivenUtility.HandleDataDrivenEvents(cAoe.data.eventContainer, cAoe, eventTag, args, cAoe.level);
}
EffectSystem.HandleGameplayEvent(cAoe.caster, eventTag, args);
}
事件注册
- 代码注册,用于自定义脚本,脚本类包含Map<GameplayTag, f(sender, args)>,代码填充即可
public interface IEventScript<TSender> : IScript
{
Dictionary<GameplayTag, GameplyEventHandler<TSender>> EventMap { get; }
void Invoke(GameplayTag eventTag, TSender sender, GameplayEventArgs args)
{
Log.Assert(EventMap != null, $"{this.GetType().Name} {eventTag}'s EventMap is null");
if (EventMap.TryGetValue(eventTag, out var func))
{
Log.Assert(func != null, $"{this.GetType().Name} {eventTag}'s func is null");
func.Invoke(sender, args);
}
}
bool ContainsEvent(GameplayTag eventTag)
{
Log.Assert(EventMap != null, $"{this.GetType().Name} {eventTag}'s EventMap is null");
return EventMap.ContainsKey(eventTag);
}
}
- 数据驱动注册,编辑器编辑EventContainer
- 编写数据驱动Action
/// <summary>
/// 施加Effect
/// </summary>
[MovedFrom(true, sourceClassName: "GAction_Effect")]
[Serializable]
public sealed class GAction_ApplyEffect : GAction
{
[HideInInspector]
[ReadOnly]
public int effectId;
#if UNITY_EDITOR
[Tooltip("施加的effectId")]
[SerializeField]
[JsonConverter(typeof(UObjectToGUIDConverter))]
[JsonProperty]
private Effect m_EffectId;
#endif
[Tooltip("施加的effect层数")]
public VarInt stackCount = new("1");
[JsonProperty(DefaultValueHandling = DefaultValueHandling.Include)]
public bool queued = true;
[SerializeReference]
public ITargetType_DataDriven targetType;
public override GActionObj CreateObj(int level, bool poolable)
{
var obj = poolable ? SharedPool.Rent<GActionObj_ApplyEffect>() : new();
obj.Poolable = poolable;
obj.effectId = effectId;
obj.stackCount = stackCount.GetValue(level);
obj.queued = queued;
if (targetType is TargetType _targetType)
obj.targetType = TargetTypeObj.CreateObj(_targetType) as ITargetTypeObj_DataDriven;
return obj;
}
#if UNITY_EDITOR
public override void OnValidate()
{
base.OnValidate();
effectId = AbilityUtility.StringToHash(m_EffectId);
}
#endif
}
public sealed class GActionObj_ApplyEffect : GActionObj
{
public int effectId;
public int stackCount;
public bool queued;
public ITargetTypeObj_DataDriven targetType;
private void Act(EcsEntity caster, List<EcsEntity> targets, int level)
{
for (int i = 0; i < targets.Count; i++)
{
var target = targets[i];
ref var ctx = ref target.Get<EffectCtx>();
if (queued)
GameplayUtility.ApplyEffectQueued(ref ctx, effectId, caster, target, level, stackCount);
else
GameplayUtility.ApplyEffect(ref ctx, effectId, caster, target, level, stackCount);
}
}
public override void InvokeAction(IGameplayEventSender sender, GameplayEventArgs args)
{
using var _ = ListPool<EcsEntity>.Rent(out var targets);
targetType.GetTargets(sender, args, targets);
if (targets.Count > 0)
Act(sender.Caster, targets, sender.Level);
}
protected override void IReferenceClear()
{
base.IReferenceClear();
TargetTypeObj.ReleaseObj(targetType);
}
}
- Unity Visual Scripting(实验性质,UVS目前性能较差,并且存在bug)
总结
通过上述简单的抽象,统一了脚本和数据驱动的事件分发,大大提高了可技能系统的可扩展性。