AbilityUser component instead of IAction in AbilityData

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?

2 Likes

I used pretty much the exact same model in my personal version of the project.

So for the first question… what I did was create a dozen or so “Cast” triggers (“Cast1”, “Cast2”, etc). Then the AnimationStrategy just sets the right Cast animation for that ability. Each cast trigger leads to a different animation or sequence of animations, with “Cast” as the AnimationEvent.

For the second, those are fairly easy to setup, though I would probably include an Animation Event at the end of all of the casts “EndAnimation” and use that to clear the CurrentAbility. Consistently use this in all casting animations. Then each call to Cast can still activate.

That last one is doable, but can be tricky. Again, use a separate trigger for this like “BeginSpecial” or “EndSpecial”. You’ll need to make sure that each of these “effects” is cancelable (wouldn’t it be awkward to be stuck suspended in the air while the caster lies on the ground dead from injuries?). :slight_smile:

2 Likes

Yeah, it makes a lot more sense to have animations and triggers for each animation rather than trying to map them to hotbar slots or one-to-one with abilities. Many-to-many between abilities and animations seems much more sensible.

Those are good callouts for the other two parts. I’m still a ways off from implementing either of those, but you’ve given me plenty to go on for them!

Privacy & Terms