Attack Input not working on first try

Hello,

so I have a kind of weird problem where my input for attacking doesn’t register correctly, but only the first time I ever attack. After that it works perfectly fine. Leads me to think I’ve made some kind of mistake in a Start() method but I seem unable to find it.

Relevant code:

Weapon script, sitting on the weapon GameObject

[RequireComponent(typeof(BoxCollider2D))]
public class Weapon2D : MonoBehaviour
{
    [SerializeField]
    private DamageParameters m_DamageParams = null;
    [SerializeField]
    private float m_HeavyAttackModifier = 1.5f;
    [SerializeField][Range(0.0f, float.MaxValue)]
    private float m_AttackTime = 1.0f;
    [SerializeField][Range(0.0f, float.MaxValue)]
    private float m_AttackTimeHeavy = 1.0f;
    [SerializeField][Range(0.0f, float.MaxValue)]
    private float m_Cooldown = 0.25f;

    public delegate void AttackEvent(GameObject weapon);
    public delegate void AttackHitEvent(GameObject weapon, GameObject hit, float damage);

    public event AttackEvent OnBeginAttack;
    public event AttackEvent OnAttack;
    public event AttackEvent OnEndAttack;
    public event AttackHitEvent OnTargetHit;

    private BoxCollider2D m_Collider;
    private float m_NextAttackTime;
    private bool m_IsHeavyAttack;
    private bool m_IsAttackPrepared;

    public bool IsHeavyAttack
    {
        get { return m_IsHeavyAttack; }
    }

    void Start()
    {
        m_Collider = GetComponent<BoxCollider2D>();
        if (!m_Collider.isTrigger)
            Debug.LogError($"Collider on weapon {transform.name} not set as trigger");

        m_Collider.enabled = false;
        m_NextAttackTime = 0.0f;
        m_IsHeavyAttack = false;
        m_IsAttackPrepared = false;
    }

    void OnTriggerEnter2D(Collider2D other)
    {
// Damage handling and knockback call happens here
    }

    private void ApplyKnockback(CharacterHealth2D target)
    {
// Knockback handling happens here
    }

    private void EndAttack()
    {
        m_IsAttackPrepared = false;
        m_Collider.enabled = false;
        OnEndAttack?.Invoke(gameObject);
    }

    private IEnumerator DoAttack(float time)
    {
        yield return new WaitForSeconds(time);
        EndAttack();
    }

    public void BeginAttack(bool heavy = false)
    {
        if (m_NextAttackTime > Time.time || m_IsAttackPrepared)
            return;

        m_IsHeavyAttack = heavy;
        m_IsAttackPrepared = true;
        OnBeginAttack?.Invoke(gameObject);
    }

    public void Attack()
    {
        if (!m_IsAttackPrepared)
            return;

        OnAttack?.Invoke(gameObject);
        m_Collider.enabled = true;

        StartCoroutine(DoAttack(m_IsHeavyAttack ? m_AttackTimeHeavy : m_AttackTime));

        m_NextAttackTime = Time.time + m_Cooldown;
    }

Animation controller for the weapon, also sitting on the weapon GameObject:

[RequireComponent(typeof(Animator), typeof(Weapon2D))]
public class WeaponAnimationController : MonoBehaviour
{
    private Weapon2D m_Weapon;
    private Animator m_Animator;

    void Awake()
    {
        m_Weapon = GetComponent<Weapon2D>();
        m_Animator = GetComponent<Animator>();
    }

    void Start()
    {
        m_Weapon.OnAttack += OnWeaponAttack;
        m_Weapon.OnBeginAttack += OnWeaponPrepare;
        m_Weapon.OnEndAttack += OnWeaponAttackEnd;
        m_Weapon.gameObject.SetActive(false);
    }

    private void OnWeaponPrepare(GameObject weapon)
    {
        weapon.SetActive(true);
        m_Animator.SetBool(AnimationStrings.BOOL_ATK_HEAVY, m_Weapon.IsHeavyAttack ? true : false);
    }

    private void OnWeaponAttack(GameObject weapon)
    {
        m_Animator.SetTrigger(AnimationStrings.TRIGGER_ATTACK);
    }

    private void OnWeaponAttackEnd(GameObject weapon)
    {
        weapon.SetActive(false);
    }

And the player weapon controller, sitting on the player GameObject

public class PlayerWeaponController : MonoBehaviour
{
    // TODO: Multiple Weapons

    [SerializeField]
    private Weapon2D m_Weapon = null;

    void Update()
    {
        if (Input.GetKeyDown(GlobalInput.VK_ATTACK))
        {
            m_Weapon.BeginAttack(false);
        }
        else if (Input.GetKeyDown(GlobalInput.VK_ATTACK_HEAVY))
        {
            m_Weapon.BeginAttack(true);
        }
        else if (Input.GetKeyUp(GlobalInput.VK_ATTACK) || Input.GetKeyUp(GlobalInput.VK_ATTACK_HEAVY))
        {
            m_Weapon.Attack();
        }
    }
}

The weapon GameObject is a child of the player GameObject. The classes GlobalInput and AnimationStrings are just classes that hold the constants for keys and strings respectively.

The attack handling itself is done by just using OnTriggerEnter and moving the weapon using an animator. Damage application works perfectly fine, the only real issue is that the first time I ever try to attack the player will in fact hold the sword (so the BeginAttack events do actually get called), but after releasing the respective button the Attack event just won’t fire at all. If I press any attack button again it works and from then on it always works as expected.

Would be nice if someone could help me out here. :slight_smile:

m_NextAttackTime is going to be 0 the first time you call BeginAttack. Line 70 will cause BeginAttack to return if m_NextAttackTime is 0. You want to flip your > to a < there.

Basically you got your cooldown backwards. As written you can only attack during the cooldown phase.

Why would m_NextAttackTime being 0 cause the method to return? If it is 0 it will always be less than Time.time, so it should never return in that case. Also, line 75 (the event call) does get called, so BeginAttack is working as intended.

However I found the issue:
When Attack() gets called for the first time, m_IsAttackPrepared is false, so that one does return. Since I am deactivating the weapon GameObject in the Animators Start() method the weapons Start() never gets called until the AttackPrepared event activates it again - which in turn resets m_IsAttackPrepared to false. Moving the code from Weapon2D.Start() into Weapon2D.Awake() did the trick.