1
\$\begingroup\$

I'v been trying to make a melee combo combat system in the Unity Animator, but I can't seem to get it right. I'm trying to go for a combo system that flows from Hit 1 to Hit 2 to Hit 3, ect, with clicks from my mouse, and if I don't click again it will go back to Idle. How would I go about making this combo attack system in the Animator with Unity and C#.

\$\endgroup\$
3
  • \$\begingroup\$ it all dependts how you want to implement that combo. \$\endgroup\$
    – virtouso
    Commented Oct 19, 2017 at 6:53
  • \$\begingroup\$ @virtouso how do you mean? \$\endgroup\$ Commented Oct 19, 2017 at 10:29
  • \$\begingroup\$ Check out one the simplest way to do it: youtube.com/watch?v=53Z7N-x09_k \$\endgroup\$ Commented Oct 23, 2019 at 19:44

5 Answers 5

2
\$\begingroup\$

If you're looking for a way to do this directly through Mecanim, it's also possible that way. I'm assuming you're using a trigger or bool parameter to start your transition from idle to attack, although the general principle works regardless. The nice thing about trigger parameters, of course, being that they are set to false automatically once they are used for a transition.

If you have the nodes 'Hit1', 'Hit2' and 'Hit3', and the idle node 'Idle', you can add the transitions: Idle->Hit1 Hit1->Hit2 Hit2->Hit3 all with the condition that the attack trigger parameter is true. Then, add transitions from each hit node back to idle, that are unconditional and occur at the end of the animation.

Therefore, if the player clicks again mid-swing, the state will transition to the next swing, but if the animation ends and they have not clicked, it will transition back to idle and end the combo.

More complex combo trees and patterns can be created in a similar way, with all of the attack nodes transitioning back to idle, but with greater nuance in the transitions between attack nodes.

The primary downside of this is that it becomes more difficult to assign different behavior or damage values to different combos. For that I recommend using AnimatorStateInfo.shortNameHash to detect what state the animator is in and whether that state is a special swing state that, say, deals extra knockback.

\$\endgroup\$
1
\$\begingroup\$

There are a ton of ways to build a Combo system in Unity with its Animator system (regardless of if you're using the Mecanim or the Generic Animations).

While there might be a "best way", I suggest you start by breaking down how you want your combo system to work, gameplay-wise. You need a clear view of what kind of restrictions, inputs, timing is required for your combo system even before you start typing the first character in your code.

For example, if you want the player to just hold the attack button and it automatically do Hit1>Hit2>Hit3>repeat until he/she release the button, you can set it up with a simple boolean (bool) that gets true if the Button is down (KeyDown or ButtonDown when pressed, and KeyUp or ButtonUp when released. Avoid making it bool = KeyDown or ButtonDown directly as it might return false negative for 1-2 frame at random time, especially if it involved online stuff.) By setting up a transition from all Hit1, Hit2 and Hit3 where it transition from that to the Idle animation (or Exit) if the boolean is false, it allows the player to do a combo that can stop at Hit2 or Hit3 or even just Hit1. It also allows a sort of "safeguard" against input issues as even if the player release the attack button, he/she can resume the combo by pressing it again before the current attack ends.

If you want the player to release and press the button again, you can do it by either using a timer through some coroutines or directly through a combo manager that gets call from some Event Triggers set directly in the animations. Both has their pros and cons. The Coroutines is stable and precise, but requires you to store, in some ways, all the timing related to the attacks unless all your attacks has the same timing. The Event Trigger set in the animations allows a precise and easy-to-setup way of managing the timing since you place the Event directly into the animation itself. The cons to that is when the game FPS slows down or raise which might affect the timing and feel like lagging input to the player.

Ever since Unity 4.x, the Event Trigger system in the animations don't skip anymore. (In Unity 3.x and prior version, a drop in FPS could make the engine miss an event.) So, there's no risk of having Trigger being skipped if the FPS drops within the player's client. With that said, it can actually drop the FPS itself since the Event "must" be called so it can hang up in-between frames a tiny bit if you're doing a ton of stuff within it. It would be good if you only use Event Trigger to "check" up on things that are analysed in a more mainstream script outside of it. (In other words, having another script that does all the checks/analyses and the Event Trigger only call up on the results from that other script.)

\$\endgroup\$
0
\$\begingroup\$

You can use coroutines to achieve this or check the timing.

For simplicity, I will activate animations as a trigger animation. this._animator.SetTrigger(this._animationHashCodesContainer[(int)this._nextComboAttack])

NOTE: I haven't tested this code, it may contain some errors or missing details, it's only to give an idea of implementation. Though, you are free to use it ofc.

public class Character : MonoBehaviour
{
    // You can use array of sting instead of enum (It's not the most scalable solution to use enum but it's faster (maybe it's a micro optimization in this case))
    public enum AttackComboTypes { Slash, Dash, FlyingAttack }
    private AttackComboTypes _nextComboAttack;

    private Dictionary<int, int> _animationHashCodesContainer;

    // or

    [SerializeField] private string[] _attackComboAnimationNames;
    private int _nextComboAttackIndex;

    private Dictionary<string, int> _animationNamesHashCodesContainer;

    private Animator _animator;

    protected virtual void Awake()
    {
        this._animator = this.GetComponent<Animator>();

        this._animationHashCodesContainer.Add((int)AttackComboTypes.Slash, Animator.StringToHash(AttackComboTypes.Slash.ToString()));
        this._animationHashCodesContainer.Add((int)AttackComboTypes.Dash, Animator.StringToHash(AttackComboTypes.Dash.ToString()));
        this._animationHashCodesContainer.Add((int)AttackComboTypes.FlyingAttack, Animator.StringToHash(AttackComboTypes.FlyingAttack.ToString()));

        // or

        for (int i = 0; i < this._attackComboAnimationNames.Length; i++)
        {
            // Don't actually know if this helps to gain more performance in case you need to get hash code from string anyway
            // It's probably useless to use dictionary in this case
            // So I will continue just passing the name of the animation to trigger
            this._animationNamesHashCodesContainer.Add(this._attackComboAnimationNames[i], Animator.StringToHash(this._attackComboAnimationNames[i])); 
        }

        this.StartCoroutine(this.CheckForCombo());
    }

    private void Update()
    {
        if (Input.GetMouseButtonDown(0))
        {
            this._attackFlag = true;

            if (some attack cooldown check)
            {
                this.Attack();
            }
        }
    }

    [SerializeField] private float _attackComboTimeDifference = 1f;
    private float _lastAttackTime;
    public void Attack()
    {
        // Some attacking code
        // ......
        // end

        if (Time.time - this._lastAttackTime > this._attackComboTimeDifference)
            this.ResetCombo();

        this.PlayNextCombo();

        this._lastAttackTime = Time.time;
    }

    // or

    private bool _attackFlag;
    private bool _comboFlag;
    public void Attack()
    {
        // This is just to ensure that you don't attack till check for combo is completed (it's some kind of cooldown)
        if (this._comboFlag)
        {
            // Some attacking code
            // ......
            // end

            this.PlayNextCombo();

            this._comboFlag = false;
        }
    }

    private IEnumerator CheckForCombo()
    {
        while (true)
        {
            // Simple, we wait for some time, if player pressed attack then we don't reset combo 
            // If he has been IDLE, we reset it
            yield return new WaitForSecondsRealtime(this._attackComboTimeDifference);

            if (!this._attackFlag)
                this.ResetCombo();

            this._attackFlag = false;


            this._comboFlag = true;
        }
    }

    private void PlayNextCombo()
    {
        this._animator.SetTrigger(this._attackComboAnimationNames[this._nextComboAttackIndex]);

        this._nextComboAttackIndex = (this._nextComboAttackIndex + 1) % this._attackComboAnimationNames.Length;
    }

    private void ResetCombo()
    {
        this._nextComboAttackIndex = 0;
    }
}
\$\endgroup\$
1
  • \$\begingroup\$ // or - shows 2 different ways to implement the same thing. One with manual time check looks more elegant and simple, the one with coroutine might cause some troubles but it's somewhat more flexible. \$\endgroup\$ Commented Oct 19, 2017 at 11:56
0
\$\begingroup\$

Here's how I managed the combo system of one of my Unity project:

My game has 3 attacks buttons which each has different combos. Each of those attacks has their own Bool parameter in the animator. In my case, the 3 are isUsingWeapon, isUsingSkill and isUsingAbility. (To put you in context, skills is related to the player's class and the ability is related to the player's race.)

Each attack are linked in a way that makes it so that the first attack of each attack buttons starts next to the previous attack. This allows me to set up as many possible combos as I want so that I'm not forced to have the same amount of attacks in a combo in every patterns.

The one thing I didn't script the same way as a typical fighting game is the fact that the player doesn't have to release any attack button if he/she wants to keep attacking. Kinda like how action-RPG works where you hold the mouse button and it keeps attacking and reset the combo when it's released. Did that with all 3 attacks buttons. This is also a LOT more simpler and efficient if you consider online with the possible issues of input lags over the network.

The way the combo works is that as you hold the button of an attack, whenever any animation state ends and reach its transition stage, it looks up what key is being held and and what direction and that's what determines which animation states it move toward at that point. This also allow the player to change the course of his action mid-animation. For example, the player hold the attack button as he think he can deal the 3rd attack of the combo, but at the last sec the opponent does something... the player release the attack button before the transition in the Animator is reached and that will allow him to do something else. If the animation state does reach the animation transition, then it's too late and that was a mistake on the player's part.

I call this kind of combat system: The trainer system. In a typical combat game, you usually take control of the fighter and timing of each buttons press is really important. In this system, you take control of the fighter's decision, but let the animation controls what's actually happening in the game. Instead of telling the character "attack, attack, attack, block, etc.", you tell him "Keep Attacking until I tell you to block". Way less complicated for the player which also allow younger players to have fun without having to learn how to time their button press in a way-to-precise way.

This idea came to me from the concept of how we script walking animations. Do we script "each steps individually" or do we just script the movement, add the walking animation over it and, if necessary, add the reverse IK's to allow the feets to always be on the ground? I basically use the same principle with the combos. Have X number of attack animations in rows and/or columbs (depending on what input controls the attacks) and look-up what's pressed each time an attack ends through the transition's conditions. You already looks at the input for movement and attacks so it's more of setting up the right conditions instead of going for the long "check animation state through script and script things in a way that counts input lags, frameskips, different controls sources, etc.

The only issue with this method is how much of a cluster the animator can become if you don't put the nodes in a clear way right as you create them. There are ways to make it visually simple, but it's ultimately the same thing, just visually different anyway.

\$\endgroup\$
0
\$\begingroup\$

You could make use of Combining Enumerator values with Bit-wise Operations, that way you can define a set of actions, and combine them on the same Enum value, and then check if a certain flag was activated. I use this for Animator controller operations, since some states may depend on multiple states to be on.

As for the combo checking, you could make use of co-routines to have a timing for combo checking, as already suggested. Example (I'll assume you've read the page I attached):

public enum PlayerActions
{
    Idle = 0,
    Attack = 1,
    Jump = 2,
    Defend = 4,
    SpecialAttack = 8,
    Die = 16
}

public class Player : MonoBehaviour
{
    public Coroutine comboCheck;
    public float comboWait;
    public KeyCode attackKey; 
    public KeyCode jumpKey; 
    private PlayerActions actions;
    private Animator animator;

    void Awake()
    {
        animator = GetComponent<Animator>();
    }

    void Update()
    {
        TrackActions();
        UpdateAnimator();
    }

    void UpdateAnimator()
    {
        animator.SetBool("Attack", (actions & PlayerActions.Attack) == PlayerActions.Attack);
        animator.SetBool("Jump", (actions & PlayerActions.Jump) == PlayerActions.Jump);
        animator.SetBool("Defend", (actions & PlayerActions.Defend) == PlayerActions.Defend);
        animator.SetBool("SpecialAttack", (actions & PlayerActions.SpecialAttack) == PlayerActions.SpecialAttack);
        animator.SetBool("Die", (actions & PlayerActions.Die) == PlayerActions.Die);
    }

    void TrackActions()
    {
        if(Input.GetKey(attackKey)) actions |= PlayerActions.Attack;
        else if((actions & PlayerActions.Attack) == PlayerActions.Attack && comboCheck == null) actions ^= PlayerActions.Attack;

        if(Input.GetKey(jumpKey)) actions |= PlayerActions.Jump;
        else if((actions & PlayerActions.Jump) == PlayerActions.Jump && comboCheck == null) actions ^= PlayerActions.Jump;

        if(comboCheck == null) comboCheck = StartCoroutine(ComboCheck());
    }

    IEnumerator ComboCheck()
    {
        WaitForSeconds wait = new WaitForSeconds(comboWait);
        yield return wait;

        // Your combo criteria:
        if((actions & PlayerActions.Jump) == PlayerActions.Jump && (actions & PlayerActions.Attack) == PlayerActions.Attack)
        {
            // Do some cool Jump + Attack wombo combo...
        }
    }
}

NOTE: The bit-wise operation enums are a suggestion to keep track of multiple states under just one variable, it is cheaper in memory, and easier to maintain than having n quantity of bools for n quantity of states. So, bools would also make the trick.

Hope it helps.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .