So, I wasn’t happy with the way the delay effect was going to be used to sync up the animation and the effects, so I ended up implementing an AbilityUser component to drop on the player, modeled after the Fighter component.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using RPG.Core;
using UnityEngine;
namespace RPG.Abilities
{
public class AbilityUser : MonoBehaviour, IAction
{
private Ability currentAbility = null;
private AbilityData currentAbilityData = null;
public Ability CurrentAbility { get => currentAbility; private set => currentAbility = value; }
public AbilityData CurrentAbilityData { get => currentAbilityData; private set => currentAbilityData = value; }
public event Action<Ability, AbilityData> AbilityStarted;
public event Action<Ability, AbilityData> AbilityEffectsStarted;
public event Action<Ability, AbilityData> AbilityCanceled;
private Animator animator;
private void Awake()
{
this.animator = this.GetComponent<Animator>();
}
#region IAction Implementation
public void CancelAction()
{
this.AbilityCanceled?.Invoke(this.CurrentAbility, this.CurrentAbilityData);
this.CurrentAbility = null;
this.CurrentAbilityData = null;
this.TriggerStopAbilityAnimation();
}
#endregion
public void UseAbility(Ability ability, AbilityData data)
{
Debug.Log($"Using ability {ability.name} on {data.Targets.Count()} targets");
this.GetComponent<ActionScheduler>().StartAction(this);
this.CurrentAbility = ability;
this.CurrentAbilityData = data;
this.TriggerUseAbilityAnimation();
this.AbilityStarted?.Invoke(ability, data);
}
#region Animation Events
private void TriggerUseAbilityAnimation()
{
this.animator.ResetTrigger("stopUseAbility");
this.animator.SetTrigger("useAbility1");
}
private void TriggerStopAbilityAnimation()
{
this.animator.SetTrigger("stopUseAbility");
this.animator.ResetTrigger("useAbility1");
}
// Animation Event
private void Cast()
{
this.CurrentAbility.ExecuteEffects(this.CurrentAbilityData);
this.AbilityEffectsStarted?.Invoke(this.CurrentAbility, this.CurrentAbilityData);
}
#endregion
}
}
And then in Ability, instead of calling directly to effects in TargetAcquired, I’m calling into AbilityUser:
data.User.GetComponent<AbilityUser>().UseAbility(this, data);
I still implemented the delayed composite, because I can think of some other places where that’ll be useful, but can anyone think of a good reason not to run with this for the basic initial animation event/timing? (I imported a pack of Mixamo animations and manually added animation events at the right looking frame and named it Cast, hence the naked Cast() method.)
A couple of things I haven’t decided on are:
- whether to have multiple animation states for slots in the hotbar. (I’m thinking no because it could be a variable amount, and I might want to let the user open a big inventory of learned abilities and cast from there, so trying to dynamically swap animations might be better, but might also lead to madness)
- whether I’m interested in complex abilities with multiple animations, like maybe an initial cast animation followed by finger snaps or points or whatever to deal additional damage (definitely will wait to cross that bridge when I get to it). How cool would it be to have Shooter McGavin finger guns clawing HP off of targets?
- whether to have some separate immediate effects and other effects at the animation point, like maybe a slam effect that picks an enemy up as the spellcaster arms raise, with the enemy slammed to the ground when the spellcaster arms drop.
Any thoughts on these things?