Animator Override Controller Integration

Hi,
Recap: the player has an equipped sword. The animator controller has three sword attack animations. When you press the left mouse button, the player goes into the attacking state. Attack 1 animation gets played. If you keep pressing the left mouse button the player starts the combo attack. Sword attack 2 and sword attack 3 animations are played consecutively.
Goal: Integrate RPG Core Combat - Simple Weapons
If you have done the RPG Core Combat course, you know that each weapon has its own animator override controller. However, in the RPG Core Combat course each weapon has only 1 animation (unarmed: one attack anim., sword: one attack anim., bow: one attack anim., fireball: one attack anim.) Therefore, the default animator controller (unarmed) and the weapons (sword, bow, fireball) animator override ā€œoverlapā€. In other words, the default animator controller and the animator override controllers have equal amount of animation states.
Question: In 3rd Person Combat sword is the default animator. What will happen when unarmed and bow animator override only need to have one attack animation (attack 1) but the sword animator has three attack animations (attack 1, attack 2, attack 3)? There is a mismatch in animation states between the animators (default and override). They donā€™t have the same number of animation state anymore. If the sword is the default animator controller, then are we allowed to leave attack 2 and attack 3 empty in the unarmed and bow animator override? And what will happen to the combo code if attack 2 and attack 3 are left empty in the unarmed and bow animator override?
That was a long question and I hope I was clear.
Thanks a lot.

If you donā€™t specify an animation in the override, it doesnā€™t override that animation. But thatā€™s ok. If your unarmed attack only has one attack, you wonā€™t have a combo. Your combo will only have a single attack and no attacks following that. If you only have 2 attacks, you only have a combo with 2 attacks

Thanks a lot, I appreciate it.

I would like to ask you another related question.

In RPG Core Combat, the animator override data for a particular weapon resides in a scriptable object. In Unity 3rd Person the Attack class looks like it acts kind of a scriptable object. If you created a weapon scriptable object, just like in RPG Core Combat, would this kind of interfere with the Attack class? Can both exist (or should exist) at the same time? Thanks a lot.

I did this this past weekend. I have the weapon scriptable object like the RPG course, and the attack combo data is on this same scriptable object. This is needed to ā€˜overrideā€™ the combo for each attack. As above, the sword attack has a 3-attack combo, but unarmed only has 1 attack. So, I have to have a separate combo attack configuration for unarmed than I have for the sword. In the Attack state I get the attack data from the weapon instead of the state machine.

Ok, thanks a lot for the advice :+1:

@bixarrio Hello,
If we transfer the weapon damage data (damage property) from the attack class into a weapon scriptable object, then each weapon scriptable object will need to contain an int array of three weapon damage (given that we have a combo with three different damage amounts set in the attack array in the player state machine). Is this correct?

Well, no. I transferred the whole Attack array into the weapon scriptable object so the damage is already there.

Thanks a lot for the reply. Is this what you did?

PlayerStateMachine.cs

// CUT
[field: SerializeField] public Attack[   ] Attacks {get; private set; }
// ADD SCRIPTABLE OBJECT REFERENCE
Weapon weapon;

Weapon.cs (Scriptable Object)
// PASTE
[field: SerializeField] public Attack[  ] Attacks {get; private set; }

PlayerAttackingState.cs
private Attack attack;
// UPDATE CONSTRUCTOR
public PlayerAttackingState(PlayerStateMachine stateMachine, int attackIndex) : base(stateMachine)
{
attack = stateMachine.**weapon**.Attacks[AttackIndex];
}

Yes, pretty much line-for-line

Ok, great!

Hi, I am almost done integrating attack.cs into a weapon scriptable object. However I encountered a problem and I canā€™t find the solution to it. I would appreciate if you could give a feedback. Here is the code structure:

public class Weapon : Scriptable Object
{
[field: SerializeField] public Attack[  ] Attacks { get; private set; }
}

public PlayerStateMachine : StateMachine
{
public Weapon currentWeapon;
}
WeaponDamage.cs
{
private int damage;

private void OnTriggerEnter(Collider other)
{
    if (other.TryGetComponent<Health>(out Health health))
    {
    Debug.Log("Damage dealt: " + damage)
    health.DealDamage(damage)
    }
}

public void SetDamage(int damage)
{
this.damage = damage;
Debug.Log("Damage set: " + damage);
}
}
public PlayerAttackingState : PlayerBaseState
{
private Attack attack;

public PlayerAttackingState{PlayerStateMachine stateMachine, int attackIndext) : base(stateMachine)
{
attack = stateMachine.currentWeapon.Attacks[attackIndex];
}

public override void Enter()
{
stateMachine.WeaponDamage.SetDamage(attack.Damage);
Debug.Log("Attacking state damage: " + attack.Damage);
stateMachine.Animator.CrossFadeInFixedTime(attack.AnimationName, attack.TransitionDuration);
}
}

I have two scriptable objects: a sword and a staff.

When currentWeapon in PlayerStateMachine is initialized with the sword everything (combo animation and combo damage) works as expected.

However when currentWeapon is initialized with the staff I get a problem. The combo animations works as expected but an issue happens when setting the damage.

When I press the attack key and start a combo, without colliding with an enemy, I get:
Debug.Log("Attacking state damage: " + attack.Damage); LOGS 10-20-30 (Expected. The Attack inside the weapon scriptable object works)
Debug.Log("Damage set: " + damage); LOGS 10-20-30 (Expected)

However when I press the attack key, start a combo and collide with an enemy, I get this:
Debug.Log("Attacking state damage: " + attack.Damage); LOGS 10 (and 20, 30) - OK
Debug.Log("Damage set: " + damage); LOGS 10 (and 20, 30) - OK
Debug.Log("Damage dealt: " + damage); LOGS 0! (and 0, 0) - NOT OK
Although the attacking state sets the damage, somehow ontriggerenter does not register it and keeps the damage at 0. And therefore no damage is dealt. Why is this happening when currentWeapon is set to staff?

Thanks a lot!

Where is your WeaponDamage component? It has to be on the weapon prefab, because each weapon has its own colliders and the script must be on the same object as the colliders. So, you wonā€™t have a ā€˜serialisedā€™ reference to WeaponDamage on the state machine anymore. You could find the WeaponDamage component when instantiating the weapon and set the reference.
I suspect the reference is the problem. You have a WeaponDamage component on the weapons, but you never update the reference so you get the correct messages when setting the damage, but the WeaponDamage that responds to the collision is the one on the staff that has never been set

Thanks a lot for the reply, I really appreciate it. It helped a lot. I noticed that the Weapon Damage reference on the PlayerStateMachine is set to Sword Hitbox and when that reference does not change based on the Current Weapon, no damage is dealt when current weapon is something other than sword. When I manually change the Weapon Damage reference on runtime and match it according to the current weapon, everything works. All thatā€™s left is doing this via scripting.

Where is your WeaponDamage component?
It is on the weapon hitbox (a game object underneath the left or right hand) which is basically a collider with the same shape of the weapon that gets enabled and disabled in WeaponHandler.cs on the Player via two animation events.
I donā€™t know why the weapon hitbox collider and the instantiated weapon prefab are two different objects (as opposed to the hitbox being a component of the weapon prefab). Thatā€™s the way the course does it. Maybe because it makes the structure more modular?

Thanks again for your help. It really helped a lot.

Yeah, my setup is a little more complex. I have the WeaponDamage on the weapon prefab. When I equip a weapon, the old one is removed from the hand and the new one instantiated in its place. Then, my reference to WeaponDamage is updated to the new weaponā€™s instance.

How would you set Weapon Damage for a two-handed weapon (double swords for example)?

For a single-handed weapon you need one reference to a Weapon Damage. The EquipWeapon method can set that reference based on the currentWeapon.

For a two-handed weapon you would need two references to a Weapon Damage. You could create a WeaponDamage[ ] weaponDamage = new WeaponDamage[2];

However the EquipWeapon only sets one reference of Weapon Damage and the second reference ends up null and you get the null reference error.

How would you solve this issue? Thanks.

Yeah, thatā€™s where my complex setup comes in. I had to do it immediately because my ā€˜unarmedā€™ is a two handed ā€˜weaponā€™ā€¦

The setup is in a long write-up that I discussed with @The_Farmz in a DM about combining the RPG course and the 3rd Person Traversal and Combat course.

My messages (thereā€™s a little context missing, but it should be ok)

The Setup

I am not actually combining the two courses, but I am using bits from each. My classes and stuff are named differently, but Iā€™ll try to map it to the courses;

I donā€™t have all the weapons in the hands. When you pick up/equip a weapon, I remove whatever weapon is there and instantiate a new one in its place. I have a somewhat convoluted setup for this

Code Listing

Hand Enum

// Enum to identify each hand
public enum Hand { LeftHand, RightHand }

WeaponData

// WeaponData to define each hand and its 'socket'
[Serializable]
public class WeaponData
{
    [field: SerializeField] public Hand Hand { get; private set; }
    [field: SerializeField] public WeaponSocket WeaponSocket { get; private set; }
}

WeaponHandler

// This is the WeaponHandler.cs
public class WeaponHandler : MonoBehaviour
{
    [SerializeField] WeaponData[] weaponData;

    public void EnableWeapon(Hand hand)
    {
        if (!TryGetWeaponSocket(hand, out var socket)) return;
        socket.ToggleWeapon(true);
    }

    public void DisableWeapon(Hand hand)
    {
        if (!TryGetWeaponSocket(hand, out var socket)) return;
        socket.ToggleWeapon(false);
    }

    public bool TryGetWeaponSocket(Hand hand, out WeaponSocket weaponSocket)
    {
        weaponSocket = null;
        WeaponData data = weaponData.FirstOrDefault(wd => wd.Hand == hand);
        if (data == null) return false; // this should really be an error 'cos no hand is set up
        weaponSocket = data.WeaponSocket;
        return true;
    }
}

WeaponSocket

// This is the WeaponSocket.cs that goes on each hand
public class WeaponSocket : MonoBehaviour
{
    public void ToggleWeapon(bool active)
    {
        foreach(WeaponDamage weapon in GetComponentInChildren<WeaponDamage>(true))
            weapon.gameObject.SetActive(active);
    }
}

So, I have all these scripts (Actually, I have more but it gets messy and Iā€™m still trying to figure out stuff).

A WeaponSocket goes on each of the hands in the model. This is what I use as the parent of the weapon when I instantiate it. In the WeaponHandler - which is on the same game object as the animator - I set up the two hands in the list that appears in the inspector (my hands are called Socketā€¦ Bad naming. I named it ā€˜Handsā€™ here)
image

I have a reference to WeaponHandler in my state machine and I can get a reference to the correct hand via WeaponHandler.TryGetWeaponSocket(). The hand a weapon wants to be in is stored in the weapon config scriptable object.
image

In the animations, the animation events now expects a ā€˜handā€™ parameter which you can easily set because we have the enum
image

When this gets invoked, the handler will go to the relevant socket which, in turn, will enable/disable the weapon there, and everything is still working as normal. If you switch it out with another weapon, the same thing will happen but for each weapon, the socket will enable/disable the WeaponDamage component in its child, which is for the currently equipped weapon.

Weapon Damage

The WeaponDamage script is pretty much the one from the 3rd person course but I have a bunch of custom stuff in there because my health doesnā€™t work like either course. The one from the 3rd person course will work here.

Oh, I had to make a change that you may need. Because each weapon will have its own damage and knockback, etc. my SetAttack() method actually takes the whole Attack object instead of just the damage and knockback and, I may not have mentioned this earlier, the attack data is on the weapon scriptable object (see below). My Attack does have a lot of extra stuff like ā€˜stamina requirementā€™ so that I can check if I have enough stamina to perform the attack and subtract it from my stamina if I do, ā€˜hit positionā€™ (high, middle-left, middle-right) to trigger the correct ā€˜got hitā€™ animation, etc. but you should be good with what we already had in the course, and adding damage and knockback to it. Then you can either get it from there before passing it in, or just pass the full object.

Each weapon can have its own combo as well (with its own separate animations) so I moved the attack data from the state machine to the weapon SO. I also have the AnimatorOverrideController on the weapon so that I can override the attack animations with the correct animations for this specific weapon. It does mean I have to create an override controller for each weapon, but it allows me to use the correct animations that that weapon will require.

Iā€™m still prototyping this whole thing so some of this stuff may change in the future. Iā€™ve only got the movement, saving system and portals done so far and Iā€™m currently setting up the weapons, so this is what I have so far. One issue I know is coming that I have no real solution for yet is having a weapon in one hand and a shield in the other. Those will be two different animator controller overrides overlaid on top of each other, but I still have no idea how to solve that.

Iā€™m not using the attribute system from the RPG course. I have a different stats system which is why my health is different. The stats system is from a rather complex (both technically and because itā€™s so boring and quite broken - took me 2 days to get it to build proper) Udemy course that matched what I wanted to do in my game. If youā€™re interested you can see it here: Unity Stat System

Getting these 3 courses to work together is a mammoth task and I go through several iterations before I settle with something I can work with. All the above is still just one such iteration.

But this doesnā€™t cover the handedness of the weapons. For that, my Weapon scriptable object is an abstract class that only holds the AnimatorOverrideController and the AttackData[] array. I also have a ā€˜convenienceā€™ method on there to get the correct attack entry and an abstract method to ā€˜spawnā€™ the weapon. Itā€™s this abstract method that does the magic

public abstract Weapon : ScriptableObject
{
    #region Properties and Fields

    [SerializeField] protected AnimatorOverrideController _animationOverride;
    [SerializeField] protected AttackData[] _attackData;

    #endregion

    #region Public Methods

    public bool TryGetAttackData(int i, out AttackData attackData)
    {
        attackData = default;
        if (i >= _attackData.Length) return false;
        attackData = _attackData[i];
        return true;
    }

    public abstract void SpawnWeapon(WeaponHandler weaponHandler, Animator animator, CharacterController character);
}

I now use this abstract weapon to create two new weapon types; a one handed weapon, and a two handed weapon

[CreateAssetMenu(menuName = "Weapons/One Handed Weapon")]
public class OneHandedWeapon : Weapon
{
    [SerializeField] protected WeaponDamage _weaponPrefab;
    [SerializeField] protected Socket _socket;

    public override void SpawnWeapon(WeaponHandler weaponHandler, Animator animator, CharacterController character)
    {
        if (weaponHandler.TryGetWeaponSocket(_socket, out var weaponSocket))
        {
            var weapon = Instantiate(_weaponPrefab, weaponSocket.transform);
            weapon.SetOwnCollider(character);
        }
        animator.runtimeAnimatorController = _animationOverride;
    }
}

[CreateAssetMenu(menuName = "Weapons/Two Handed Weapon")]
public class TwoHandedWeapon : Weapon
{
    [SerializeField] protected WeaponSocketData[] _weaponSocketData;

    public override void SpawnWeapon(WeaponHandler weaponHandler, Animator animator, CharacterController character)
    {
        for (var i = 0; i < 2; i++)
        {
            if (weaponHandler.TryGetWeaponSocket(_weaponSocketData[i].Socket, out var weaponSocket))
            {
                var weapon = Instantiate(_weaponSocketData[i].WeaponPrefab, weaponSocket.transform);
                weapon.SetOwnCollider(character);
            }
        }
        animator.runtimeAnimatorController = _animationOverride;
    }

    [System.Serializable]
    protected struct WeaponSocketData
    {
        public WeaponDamage WeaponPrefab;
        public Socket Socket;
    }
}

Now I can just call SpawnWeapon on whichever weapon is equiped and it will do the rest.

Itā€™s quite convoluted and difficult to explain, sorry

1 Like

Thanks a lot! I will definitely examine the code in depth.

Privacy & Terms