0
\$\begingroup\$

I am working on a 2D mobile platformer where there will be different characters with unique skill sets. I am working on a character that should be able to perform chained attacks.

I have a button in my UI for attacking. I have added the listener for the ChainableAttack in the script below. The manner in which the normal attacks work is that the character starts attacking and sets the hit counter to 0. In the FixedUpdate function, there are some control statements that will accordingly trigger the EnemyAttacked function up until the character has been hit (This is so that the attack is continuous until it hits the enemy while the attack is ongoing. Otherwise, earlier it would only hit the enemy if it happened to be in the attackRange when the attack button is clicked).

The normal attack function is working perfectly, but I am struggling to make the follow-up attack work. The character attacks the enemy, but when I hit attack again while the attacking flag is true, it doesn't hit the enemy.

The chainHitCounter is returning 0 and the chainAttack flag is returning true. However, the enemy is not responding to the attack.

The fact that the debug statements in the FixedUpdate are being called but the enemy attacked function isn't, is really confusing me. I can't figure it out, any help would be appreciated. Thank you for your time!

This is the code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerAttackBehavior : MonoBehaviour
{
    public Transform attackPoint;
    public float attackRange = 0.5f;

    public Transform kickPoint;
    public float kickRange = 0.5f;
    public int attackDamage = 40;
    [SerializeField] private Animator playerAnimator;
    [SerializeField] LayerMask attackLayer;
    [SerializeField] float disabledAttackDuration = 1f;
    private PlayerMethods pm;
    private Collider2D[] hitEnemies;
    public bool kicking; //To handle movement in warrior behavior. But it might be being handled by canAttack. Acc very specific behavior.
    private int hitCounter;
    public bool canAttack = true; // Add a flag to control the player's ability to attack
    public bool attacking;
    
    public float chainAttackRange = 7f;

    public bool chainAttack;
    private int chainCounter;
    private int chainHitCounter;
    public bool canChainAttack;
    private Rigidbody2D rb;

    void Start(){
        pm = GetComponent<PlayerMethods> ();
        chainCounter = 0;
        rb = GetComponent<Rigidbody2D>();
        hitCounter = 0;
        attacking = false;
        chainAttack = false;
    }

    void Update(){
        //While attacking is true and int = 0, call enemy attacked function and when an enemy is hit increase the counter to stop the EnemyAttacked function. 
            //As such the attack box continuously searches for the enemy while the player is attacking until the enemy is hit. Set counter to zero,
            //at the beginning of the attack function to reset hitcounter when attacked.
        if(attacking && hitCounter == 0){
            //Checks for the enemies in attack range until there is a hit.
            EnemyAttacked(hitEnemies);
        } 
        
        if(chainAttack && chainHitCounter == 0){
            //The debug logs are being called but EnemyAttacked isn't being called.
            Debug.Log("Chain Attack: " + chainAttack);
            Debug.Log("Chain Hit Counter: " + chainHitCounter);
            EnemyAttacked(hitEnemies);
        }
    }

    public void Attack(bool kick)
    {
        if(pm.beenHit) return;
        if(canAttack){
            canAttack = false; //change so if chain Attack is true
            attacking = true;
            Invoke("SetChainAttack", disabledAttackDuration);
            hitCounter = 0;
            
            if(pm != null) {
                pm.SetCanTakeDamage(false);
                StartCoroutine(pm.DelayedCanTakeDamage(0.65f)); // Re-enable the player's ability to take damage after a delay
            }

            if(kick) {
                playerAnimator.SetTrigger("Kick");
                hitEnemies = Physics2D.OverlapCircleAll(kickPoint.position, kickRange,attackLayer);
                kicking = true;
                Invoke("Unkick", 0.7f);

            } else {
                playerAnimator.SetTrigger("Attack");
                hitEnemies = Physics2D.OverlapCircleAll(attackPoint.position, attackRange,attackLayer);
            }

            //EnemyAttacked(hitEnemies);
        }
    }

    public void ChainableAttack(bool kick)
    {
        //pm.animator.GetCurrentAnimatorStateInfo(0).IsName("ChainAttack")
        if(pm.beenHit) return;
        if(canAttack){
            // pm.animator.GetCurrentAnimatorStateInfo(0).IsName("ChainAttack"
            if(pm != null) {
                pm.SetCanTakeDamage(false);
                StartCoroutine(pm.DelayedCanTakeDamage(0.65f)); // Re-enable the player's ability to take damage after a delay
            }
            //if the attack button is clicked while the attacking is true it calls the chain attack. Or else it does the normal attack
            if(attacking){
                Debug.Log("Chain attack?");
                //Did this incase it was interfering... I don't think it was.
                StopCoroutine(pm.DelayedCanTakeDamage(0.65f));
                StartCoroutine(pm.DelayedCanTakeDamage(0.65f));
                //I put this in because I wanted the chain attack to be called when the player is done with the normal attack is done 
                //Because I thought that both were occuring simultaneously. The animation for the normal attack has exitTime = 1;
                Invoke("SetChainAttack", disabledAttackDuration/2);
                //Stops the chainAttack in the Update function once the chain attack is no longer running.
                Invoke("StopChainAttack", disabledAttackDuration*2);
            } else {
                //flag for normal attack used in the FixedUpdate and to trigger chained attack.
                attacking = true;
                //flag for normal attack.
                hitCounter = 0;
                Invoke("StopAttack", disabledAttackDuration);
            }

            if(kick) {
                playerAnimator.SetTrigger("Kick");
                hitEnemies = Physics2D.OverlapCircleAll(kickPoint.position, kickRange,attackLayer);
                kicking = true;
                Invoke("Unkick", 0.7f);

            } else {
                if(chainAttack) {
                    playerAnimator.SetTrigger("ChainAttack");
                    hitEnemies = Physics2D.OverlapCircleAll(attackPoint.position, chainAttackRange,attackLayer);
                }
                else {
                    playerAnimator.SetTrigger("Attack");
                    hitEnemies = Physics2D.OverlapCircleAll(attackPoint.position, attackRange,attackLayer);
                }
            }
        }
    }

    public void SetAttack(){
        canAttack = true;
        attacking = false;
    }
    public void StopAttack(){
        canAttack = false;
        if(chainAttack) return;
        attacking = false;
        Invoke("SetAttack", disabledAttackDuration);
    
    }
    public void SetChainAttack(){
        attacking = false;
        canAttack = false;
        chainHitCounter = 0;
        chainAttack = true;
    }

    public void StopChainAttack(){
        SetAttack();
        chainAttack = false;
    }
    void EnemyAttacked(Collider2D[] enemiesHit){
        foreach (Collider2D enemy in enemiesHit)
            {   
                Debug.Log("We hit a " + enemy.name);

                //Earlier I was using the animation state ... but there is no difference
                if(!attacking){
                    chainHitCounter++;
                    //This is not working... Earlier I had it 
                } else {
                    hitCounter++;
                    //This is working
                }
                // chainHitCounter++;
                // hitCounter++;
                if (enemy.CompareTag("Slime"))
                {
                    enemy.GetComponent<EnemyBehavior>().TakeDamage(attackDamage);
                } else if (enemy.name.StartsWith("Bat"))
                {
                    //Vector2 attackDirection = transform.position - enemy.transform.position;
                    // enemy.GetComponent<Rigidbody2D>().AddForce(attackDirection * 10f , ForceMode2D.Impulse);
                    enemy.GetComponent<EnemyBehavior>().TakeDamage(attackDamage);
                }
                else if (enemy.name.StartsWith("Dino"))
                {
                    enemy.GetComponent<EnemyBehavior>().TakeDamage(attackDamage);
                }
                else if (enemy.name.StartsWith("Bandit"))
                {
                    enemy.GetComponent<EnemyBehavior>().TakeDamage(attackDamage);
                } 
                else if (enemy.CompareTag("Bullet"))
                {
                    Bullet bullet = enemy.GetComponent<Bullet>();
                    bullet.BeenDeflected();
                }
                else if (enemy.CompareTag("Vase"))
                {
                    enemy.GetComponent<BreakInteraction>().BreakVase();
                } 
                else if (enemy.CompareTag("MafiaBoss"))
                {
                    //int randomNumber = Random.Range(1, 4);
                    //if(MafiaBossScript.ready && randomNumber!= 3){
                    if(!BoomerangManScript.vulnerable){
                    enemy.GetComponent<BoomerangManScript>().Block();
                    GetComponent<PlayerController>().EnemyCollision(5f, 0, 1.2f, false);
                    } else if(BoomerangManScript.vulnerable){
                    enemy.GetComponent<BoomerangManScript>().TakeDamage(attackDamage);
                    }
                }
                else if (enemy.CompareTag("Chicken")){
                    enemy.GetComponent<EnemyBehavior>().TakeDamage(attackDamage);
                    enemy.GetComponent<ChickenBehavior>().deflected = true;
                } else if(enemy.CompareTag("Scarecrow")){
                    FindObjectOfType<ScarecrowBehavior>().BeenHit();
                }
            }
    }
    void Unkick(){
        kicking = false;
    }
    void OnDrawGizmosSelected()
    {
        if (attackPoint == null) return;
        Gizmos.DrawWireSphere(attackPoint.position, attackRange);
    }

    // private IEnumerator DelayedCanTakeDamage(float delay, PlayerController pc)
    // {
    //     yield return new WaitForSeconds(delay);
    //     pc.SetCanTakeDamage(true); // Enable player's ability to take damage
    // }
    //Moved to player methods which holds all the general functions for all the different characters.
}
\$\endgroup\$
2
  • \$\begingroup\$ First thing to jump out at me is that whenever you have a giant chain of ifs checking for different types then doing largely the same few things, this is a job for polymorphism. Add an IDamageable interface that exposes a TakeDamage method. This interface can be implemented by the Health component on enemies, the Deflectable component on bullets, and the Breakable component on vases, each doing the right thing for their respective types. Then you don't need to edit every damage-dealer every time you add a new type of damage-taker: they only need to know the target is IDamageable. \$\endgroup\$
    – DMGregory
    Sep 25 at 9:50
  • \$\begingroup\$ Thank you for the suggestion! I'll begin working on it right away. \$\endgroup\$
    – shanksVR
    Sep 25 at 10:06

0

You must log in to answer this question.

Browse other questions tagged .