05 Weapons

Difficulty: Intermediate

Location: Assets/Plugins/Animancer/Examples/06 State Machines/05 Weapons

Namespace: Animancer.Examples.StateMachines.Weapons

This example demonstrates how you can implement a character that can move around and attack with various different weapons. It is based on a character and animations downloaded from Mixamo which unfortunately cannot be legally redistributed in their raw source form, meaning that they cannot be directly included in Animancer. So instead of providing a ready-made scene, this example explains how to download the required assets and set them up from scratch in Unity using the More Brains example as a base.

Required Assets

Character

The first thing you need to decide is which character model you want to use:

  • You can download any of the Mixamo Characters.
    • We are using the character called Paladin J Nordstrom, but you can choose any you like.
    • Once you have downloaded the character you will need to configure its Import Settings as explained here, including exposing the Right Hand bone so that we can attach weapons to it.
  • The LowPolyMan model included in Animancer at Assets/Plugins/Animancer/Examples/Art/Low Poly Man would work.
  • Or you could even use a Sprite based character if you have all the animations listed below.

Animations

Once you have a character, you will need to get the following animations for them:

Animations Import Settings

Idle, Walk, and Run.

If you are using a Humanoid Rig, the Humanoid-Idle, Humanoid-Walk, and Humanoid-Run animations included in Animancer will work.

The Import Settings of these animations should have Loop Time and Loop Pose enabled.

You can disable Loop Pose if you want to use Root Motion for the character's main movements.

Several Attack animations for each weapon you want to implement.

We are using 3 weapons with the following Mixamo Animations:

  • Unarmed uses Punching and Boxing which contain one attack each.
  • Club uses One Hand Club Combo which contains 3 attacks that we separate in Unity.
  • Great Sword uses Great Sword Slash which contains 3 attacks that we separate in Unity.
We want to allow the animations to move the character with Root Motion but not rotate them, so we set the Root Transform Rotation and Root Transform Position (Y) to Bake Into Pose based on the Original values.

Optional Sheathe and Unsheathe animations for each weapon.

We are using the following Mixamo Animations:

  • None for Unarmed
  • Draw Sword 2 for the Club which we play backwards to sheathe it.
  • Draw A Great Sword 2 and Sheath A Great Sword 1 for the Great Sword.
Same as above.

Weapons

You will also need a model for each weapon except Unarmed. The Getting Animations page lists several places where you can get models or programs to create your own, but for this example we are just using primitive shapes (cubes, cylinders, and capsules). Most of the construction should be obvious at a glance except for the spiked parts which are created by skewing cubes:

Hierarchy Transform

By rotating the child cube 45 degrees on one axis then scaling the parent object on a different axis, the cube gets stretched diagonally so that it turns into a spike:

Attack Input

The AttackInput script would normally be part of the CreatureBrain, but we have implemented it separately so that we can reuse the classes from the More Brains example and have it work with both the KeyboardBrain and MouseBrain:

using Animancer;
using Animancer.Examples.StateMachines.Brains;
using Animancer.FSM;
using UnityEngine;

public sealed class AttackInput : MonoBehaviour
{
    [SerializeField] private CreatureState _Attack;
    [SerializeField] private float _AttackInputTimeOut = 0.5f;

    private StateMachine<CreatureState>.InputBuffer _InputBuffer;

    private void Awake()
    {
        _InputBuffer = new StateMachine<CreatureState>.InputBuffer(_Attack.Creature.StateMachine);
    }

    private void Update()
    {
        if (Input.GetButtonDown("Fire2"))// Right Click by default.
        {
            _InputBuffer.TrySetState(_Attack, _AttackInputTimeOut);
        }
        else
        {
            _InputBuffer.Update();
        }
    }
}

It starts with a reference to the Attack State which is assigned in the Inspector (once we create it below):

[SerializeField] private CreatureState _Attack;

We are also using a concept known as input buffering which simply means that if a button press fails to trigger the desired action, it will still be able to do so for a short time afterwards. Animancer's Finite State Machine system has an InputBuffer class which is very easy to use. So we make a "time out" field to determine how long the buffer will last and initialise it with the StateMachine it will be buffering:

[SerializeField] private float _AttackInputTimeOut = 0.5f;

private StateMachine<CreatureState>.InputBuffer _InputBuffer;

private void Awake()
{
    _InputBuffer = new StateMachine<CreatureState>.InputBuffer(_Attack.Creature.StateMachine);
}

Then when we detect the Attack button being pressed and would normally call Creature.StateMachine.TrySetState(_Attack) we instead call a similar method on the InputBuffer where we also specify the time out duration:

private void Update()
{
    if (Input.GetButtonDown("Fire2"))// Right Click by default.
    {
        _InputBuffer.TrySetState(_Attack, _AttackInputTimeOut);
    }

And on any Update where we did not just attempt a new action, we update the buffer to give it another chance to retry a previously buffered action and check if it has timed out:

    else
    {
        _InputBuffer.Update();
    }
}

You could give each attack a different time out period, but using the same 0.5 second window for every attack tends to give more consistent gameplay where the player can get a feel for when they will need to press the button.

Weapon

The Weapon script is simply a component which holds the animations relating to a weapon so that it can be attached to the weapon's model in the scene:

using Animancer;
using UnityEngine;

public sealed class Weapon : MonoBehaviour
{
    [SerializeField]
    private ClipState.Transition[] _AttackAnimations;
    public ClipState.Transition[] AttackAnimations { get { return _AttackAnimations; } }

    [SerializeField]
    private ClipState.Transition _EquipAnimation;
    public ClipState.Transition EquipAnimation { get { return _EquipAnimation; } }

    [SerializeField]
    private ClipState.Transition _UnequipAnimation;
    public ClipState.Transition UnequipAnimation { get { return _UnequipAnimation; } }
}

In a real game, this class might have other details like damage, damage type, weapon category, etc. It could also inherit from a base Item class for things like weight, cost, and description.

Equip State

The EquipState script manages the currently equipped Weapon. It is a is a CreatureState so that other actions cannot be performed while it is playing the EquipAnimation and UnequipAnimation of the Weapon when it is changed:

using Animancer;
using Animancer.Examples.StateMachines.Brains;
using Animancer.FSM;
using UnityEngine;

public sealed class EquipState : CreatureState
{
    [SerializeField] private Transform _WeaponHolder;
    [SerializeField] private Weapon _Weapon;

    private Weapon _EquippingWeapon;
    private Action _OnUnequipEnd;

    public Weapon Weapon
    {
        get { return _Weapon; }
        set
        {
            if (enabled)
                return;

            _EquippingWeapon = value;
            if (!Creature.StateMachine.TrySetState(this))
                _EquippingWeapon = _Weapon;
        }
    }

    private void Awake()
    {
        _EquippingWeapon = _Weapon;
        _OnUnequipEnd = OnUnequipEnd;
        AttachWeapon();
    }

    public override bool CanEnterState(CreatureState previousState)
    {
        return _Weapon != _EquippingWeapon;
    }

    private void OnEnable()
    {
        if (_Weapon.UnequipAnimation.Clip != null)
        {
            var state = Creature.Animancer.Play(_Weapon.UnequipAnimation);
            state.Events.OnEnd = _OnUnequipEnd;
        }
        else
        {
            OnUnequipEnd();
        }
    }

    private void OnUnequipEnd()
    {
        DetachWeapon();
        _Weapon = _EquippingWeapon;
        AttachWeapon();

        if (_Weapon.EquipAnimation.Clip != null)
        {
            var state = Creature.Animancer.Play(_Weapon.EquipAnimation);
            state.Events.OnEnd = Creature.ForceEnterIdleState;
        }
        else
        {
            Creature.StateMachine.ForceSetState(Creature.Idle);
        }
    }

    private void AttachWeapon()
    {
        if (_Weapon == null)
            return;

        if (_WeaponHolder != null)
        {
            var transform = _Weapon.transform;
            transform.parent = _WeaponHolder;
            transform.localPosition = Vector3.zero;
            transform.localRotation = Quaternion.identity;
            transform.localScale = Vector3.one;
        }

        _Weapon.gameObject.SetActive(true);
    }

    private void DetachWeapon()
    {
        if (_Weapon == null)
            return;

        _Weapon.transform.parent = transform;
        _Weapon.gameObject.SetActive(false);
    }

    private void FixedUpdate()
    {
        Creature.Rigidbody.velocity = Vector3.zero;
    }

    public override bool CanExitState(CreatureState nextState)
    {
        return false;
    }
}

The RightHand bone of a character is usually in their wrist, so instead of needing to manually offset the equipped weapon from that position we have simply created an empty Weapon Holder object as a child of the hand which we can attach weapons to:

[SerializeField] private Transform _WeaponHolder;

We want other scripts (the AttackState in this example) to be able to access the currently equipped Weapon and also try to change it (normally the CreatureBrain would handle that, but in this example we are just using UI Buttons) so we have private fields for the current weapon and the new one we want to equip with a public property that tries to put the Creature in this state when you set it:

[SerializeField] private Weapon _Weapon;

private Weapon _EquippingWeapon;

public Weapon Weapon
{
    get { return _Weapon; }
    set
    {
        if (enabled)
            return;

        _EquippingWeapon = value;
        if (!Creature.StateMachine.TrySetState(this))
            _EquippingWeapon = _Weapon;
    }
}

private void Awake()
{
    _EquippingWeapon = _Weapon;
    ...
}

We only want this state to be entered when you are actually changing to a different Weapon, so we override the CanEnterState method to enforce that restriction:

public override bool CanEnterState(CreatureState previousState)
{
    return _Weapon != _EquippingWeapon;
}

Note that if CanEnterState (or the CanExitState method of whatever state the Creature is currently in) returns false, the Weapon property simply undoes the change. This ensures that the character cannot change weapons in the middle of other actions that do not allow Interruptions (such as attacks).

When the creature does actually enter this state, we want to play the UnequipAnimation from the previous Weapon if it has one and then swap to the new weapon:

private void OnEnable()
{
    if (_Weapon.UnequipAnimation.Clip != null)
    {
        var state = Creature.Animancer.Play(_Weapon.UnequipAnimation);
        state.Events.OnEnd = _OnUnequipEnd;
    }

Or if there is no UnequipAnimation, we just swap to the new Weapon immediately:

    else
    {
        OnUnequipEnd();
    }
}

Directly assigning the OnUnequipEnd method to the OnEnd event would create some Garbage every time this state is entered, so instead we want to create a delegate from it on startup so that we can reuse the same object every time:

private Action _OnUnequipEnd;

private void Awake()
{
    ...
    _OnUnequipEnd = OnUnequipEnd;
    AttachWeapon();
}

Once the UnequipAnimation ends (or is skipped because it was missing), we detach the previous Weapon from the character's hand, assign and attach the new weapon, and then play the EquipAnimation of the new Weapon:

private void OnUnequipEnd()
{
    DetachWeapon();
    _Weapon = _EquippingWeapon;
    AttachWeapon();

    if (_Weapon.EquipAnimation.Clip != null)
    {
        var state = Creature.Animancer.Play(_Weapon.EquipAnimation);
        state.Events.OnEnd = Creature.ForceEnterIdleState;
    }
    else
    {
        Creature.StateMachine.ForceSetState(Creature.Idle);
    }
}

private void AttachWeapon()
{
    if (_Weapon == null)
        return;

    if (_WeaponHolder != null)
    {
        var transform = _Weapon.transform;
        transform.parent = _WeaponHolder;
        transform.localPosition = Vector3.zero;
        transform.localRotation = Quaternion.identity;
        transform.localScale = Vector3.one;
    }

    _Weapon.gameObject.SetActive(true);
}

private void DetachWeapon()
{
    if (_Weapon == null)
        return;

    _Weapon.transform.parent = transform;
    _Weapon.gameObject.SetActive(false);
}

And finally, we prevent anything from moving the character while in this state or Interrupting it:

private void FixedUpdate()
{
    Creature.Rigidbody.velocity = Vector3.zero;
}

public override bool CanExitState(CreatureState nextState)
{
    return false;
}

Attack State

The AttackState script simply plays the appropriate animations from the EquipState.Weapon:

using Animancer;
using Animancer.Examples.StateMachines.Brains;
using UnityEngine;

public sealed class AttackState : CreatureState
{
    [SerializeField] private EquipState _Equipment;

    private int _AttackIndex = int.MaxValue;

    private void OnEnable()
    {
        Creature.Animancer.Animator.applyRootMotion = true;

        if (ShouldRestartCombo())
        {
            _AttackIndex = 0;
        }
        else
        {
            _AttackIndex++;
        }

        var animation = _Equipment.Weapon.AttackAnimations[_AttackIndex];
        var state = Creature.Animancer.Play(animation);
        state.Events.OnEnd = Creature.ForceEnterIdleState;
    }

    private bool ShouldRestartCombo()
    {
        var attackAnimations = _Equipment.Weapon.AttackAnimations;

        if (_AttackIndex >= attackAnimations.Length - 1)
            return true;

        var state = attackAnimations[_AttackIndex].State;
        if (state == null ||
            state.Weight == 0)
            return true;

        return false;
    }

    private void FixedUpdate()
    {
        Creature.Rigidbody.velocity = Vector3.zero;
    }

    public override bool CanExitState(CreatureState nextState)
    {
        return false;
    }

    private void OnDisable()
    {
        Creature.Animancer.Animator.applyRootMotion = false;
    }
}

It starts with a Serialized reference to the EquipState to be assigned in the Inspector:

[SerializeField] private EquipState _Equipment;

Normally that reference might go in the Creature class, but since that script comes from the More Brains example we do not want to modify it.

It is quite common for attack animations in games to cause the character to step forwards so the first thing we do when entering this state is enable Root Motion (and disable it when exiting this state):

private void OnEnable()
{
    Creature.Animancer.Animator.applyRootMotion = true;
    ...
}

private void OnDisable()
{
    Creature.Animancer.Animator.applyRootMotion = false;
}

Since each Weapon can have multiple attack animations which they perform in sequence, the next thing we need to do is determine which animation to use. Each time this state is entered we either start from the first animation or move onto the next one in the sequence:

private int _AttackIndex = int.MaxValue;

private void OnEnable()
{
    ...

    if (ShouldRestartCombo())
    {
        _AttackIndex = 0;
    }
    else
    {
        _AttackIndex++;
    }

    ...
}

We want to restart from the first attack if we are already at the last animation or if the previous animation has not yet faded out. Otherwise we want to advance to the next attack:

private bool ShouldRestartCombo()
{
    var attackAnimations = _Equipment.Weapon.AttackAnimations;

    if (_AttackIndex >= attackAnimations.Length - 1)
        return true;

    var state = attackAnimations[_AttackIndex].State;
    if (state == null ||
        state.Weight == 0)
        return true;

    return false;
}

Once we know which attack we are up to, we can simply play its animation:

private void OnEnable()
{
    ...

    var animation = _Equipment.Weapon.AttackAnimations[_AttackIndex];
    var state = Creature.Animancer.Play(animation);
    state.Events.OnEnd = Creature.ForceEnterIdleState;
}

And just like with the EquipState, we do not want the character to move during this action or to allow other actions to Interrupt it:

private void FixedUpdate()
{
    Creature.Rigidbody.velocity = Vector3.zero;
}

public override bool CanExitState(CreatureState nextState)
{
    return false;
}

Root Motion Redirect

Since the character model (the object with the Animator component) is separate from the root of the character (the object with the Rigidbody component), we are using a very simple RootMotionRedirect script to apply the movements from the Animator to the Rigidbody as explained in the Root Motion example:

using UnityEngine;

public sealed class RootMotionRedirect : MonoBehaviour
{
    [SerializeField] private Rigidbody _Rigidbody;
    [SerializeField] private Animator _Animator;

    private void OnAnimatorMove()
    {
        if (_Animator.applyRootMotion)
        {
            _Rigidbody.MovePosition(_Rigidbody.position + _Animator.deltaPosition);
            _Rigidbody.MoveRotation(_Rigidbody.rotation * _Animator.deltaRotation);
        }
    }
}

Scene Setup

This example is based on the More Brains scene so the first thing you should do is create a copy of it (either using Edit/Duplicate (Ctrl + D) or by opening it and using File/Save As... to save a copy somewhere else).

Character

  • If you are using a different character model from the LowPolyMan included in Animancer, delete the existing model (a child of the Creature object) and drag your own into the same place in the hierarchy.
    • Make sure the new model has its position and rotation reset to zero.
    • Add an AnimancerComponent to it.
    • Select the Creature and assign the new model as its Animancer reference.
  • Also add a RootMotionRedirect component to the model and assign its references.

  • If you are not using the Humanoid-Idle, Humanoid-Walk, and Humanoid-Run animations, assign the ones you want to use in the IdleState on the Creature and the LocomotionState on the Brain.
Idle Locomotion

Weapons

Each of the Weapons is a model with a Weapon script attached (except for Unarmed which has no model). They all start inactive (and Unarmed gets equipped on startup because it is assigned as the default).

The Unarmed and Great Sword setups are fairly obvious:

Unarmed Great Sword

Unarmed has two attacks and no Equip Animation or Unequip Animation.

Great Sword has three attacks with both an Equip Animation and an Unequip Animation.

We only have a single animation called Draw Sword 2 to use for the Club's Equip Animation, but we can also use that same animation played backwards (Speed = -1) for the Unequip Animation.

Attacks

Each of the other new scripts we made in this example can be attached to the Brain object:

Buttons

And finally, we can not use some UI Buttons to set the EquipState.Weapon when the player clicks them:

Note that if the Equip State does not actually allow itself to be entered, the displayed text will still be changed. We could fix that by having the EquipState set the text rather than doing it in the UI Button, but that is not really relevant to the purpose of that script so we have kept it separate for this example.

Final Result

This all combines to create a character that can do everything from the More Brains example as well as equip different weapons and attack with them.