Home > Software design >  Enemy cannot be damaged if not using static variable for health
Enemy cannot be damaged if not using static variable for health

Time:06-12

So I have a problem where my enemy cannot be damaged if I'm not using a static variable for their health. But when using static variable if one of the enemy dies, the others die as well. The enemy doing damage to the player works ok, so I don't know what causing the problem. Debug log says I'm hitting the enemy just fine, but the enemy health is not decreasing.

here's my code:

Player

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.SceneManagement;

public class PlayerController : MonoBehaviour
{
    //Input System
    private PlayerControlInput playerInput;
    private InputAction movement;
    public KnightController knight;
    public EnemyArcher archer;

    //Movement and Physics
    private Rigidbody rb;
    [SerializeField]
    private float moveForce = 1f;
    [SerializeField]
    private float maxSpeed = 5f;
    private Vector3 forceDirection = Vector3.zero;
    public Vector3 destination;

    //Animation and Camera
    [SerializeField]
    private Camera playerCam;
    private Animator animator;

    //Audio
    public GameObject playerWeapon;
    public AudioClip lightAttackSound;
    public AudioClip heavyAttackSound;
    public AudioClip spellSound;
    public AudioClip walkingSound;

    //Player stats
    public int playerHealth;
    public int lightAttackDamage = 5;
    public bool isLightAttacking = false;
    public int heavyAttackDamage = 7;
    public bool isHeavyAttacking = false;
    public GameObject spell;
    public Transform casterPostion;
    public int spellDamage = 20;
    public bool isSpellCasting = false;
    public bool isAttacking = false;
    public bool isPlayerBlocking = false;
    public bool canCastSpell;
    public bool canAttack;
    public bool canBlock;
    private float lightAttackCooldown = 1f;
    private float heavyAttackCooldown = 2f;
    private float parryCooldown = 2f;
    private float spellCooldown = 5f;
    public int totalEnemySlain;
    public bool isDead;

    private void Start()
    {
        knight.GetComponent<KnightController>();
        archer.GetComponent<EnemyArcher>();
        knight.enabled = true;
        archer.enabled = true;
        isDead = false;
        playerHealth = 250;
        totalEnemySlain = 0;
    }
    private void Awake()
    {
        rb = GetComponent<Rigidbody>();
        playerInput = new PlayerControlInput(); 
        animator = GetComponent<Animator>();
    }

    private void OnEnable()
    {
        movement = playerInput.Player.Movement;
        movement.Enable();

        playerInput.Player.Light_Attack.started  = LightAttack;
        playerInput.Player.Light_Attack.Enable();
        playerInput.Player.Heavy_Attack.started  = HeavyAttack;
        playerInput.Player.Heavy_Attack.Enable();
        playerInput.Player.Spell_Casting.performed  = spellCast;
        playerInput.Player.Spell_Casting.Enable();
        playerInput.Player.Block.started  = Blocking;
        playerInput.Player.Block.Enable();
    }

    private void OnDisable()
    {
        movement.Disable();
        playerInput.Player.Spell_Casting.Disable();
        playerInput.Player.Light_Attack.started -= LightAttack;
        playerInput.Player.Light_Attack.Disable();
        playerInput.Player.Heavy_Attack.started  = HeavyAttack;
        playerInput.Player.Heavy_Attack.Disable();
        playerInput.Player.Block.started -= Blocking;
        playerInput.Player.Block.Disable();
    }

    #region Action Control
    public void delayMove()
    {
        movement.Enable();
    }

    #region Blocking
    public void Blocking(InputAction.CallbackContext obj)
    {
        isBlocking();
    }

    public void isBlocking()
    {
        canBlock = false;
        isPlayerBlocking = true;
        movement.Disable();
        animator.SetTrigger("Blocking");
        Invoke("delayMove", 1f);
        StartCoroutine(resetParryCoolDown());
    }
    
    IEnumerator resetParryCoolDown()
    {
        StartCoroutine(resetIsParrying());
        yield return new WaitForSeconds(parryCooldown);
        canBlock = true;
    }

    IEnumerator resetIsParrying()
    {
        yield return new WaitForSeconds(heavyAttackCooldown);
        isPlayerBlocking = false;
    }
    #endregion

    #region Heavy Attack
    public void HeavyAttack(InputAction.CallbackContext obj)
    {
        if (canAttack)
        {
            heavyAttack();
        }
    }

    public void heavyAttack()
    {
        isAttacking = true;
        isHeavyAttacking = true;
        canAttack = false;
        movement.Disable();
        animator.SetTrigger("HeavyAttack");
        AudioSource heavyAudio = GetComponent<AudioSource>();
        heavyAudio.PlayOneShot(heavyAttackSound);
        Invoke("delayMove", heavyAttackCooldown);
        StartCoroutine(resetHeavyAttackCoolDown());
    }

    IEnumerator resetHeavyAttackCoolDown()
    {
        StartCoroutine(resetIsHeavyAttacking());
        yield return new WaitForSeconds(heavyAttackCooldown);
        canAttack = true;
    }

    IEnumerator resetIsHeavyAttacking()
    {
        yield return new WaitForSeconds(heavyAttackCooldown);
        isAttacking = false;
        isHeavyAttacking = false;
    }
    #endregion

    #region Light Attack
    public void LightAttack(InputAction.CallbackContext obj)
    {
        if (canAttack)
        {
            lightAttack();
        }
    }

    public void lightAttack() 
    {
        isAttacking = true;
        isLightAttacking = true;
        canAttack = false;
        movement.Disable();
        animator.SetTrigger("LightAttack");
        AudioSource lightAudio = GetComponent<AudioSource>();
        lightAudio.PlayOneShot(lightAttackSound);
        Invoke("delayMove", lightAttackCooldown);
        StartCoroutine(resetLightAttackCoolDown());
    }
    IEnumerator resetLightAttackCoolDown()
    {
        StartCoroutine(resetIsLightAttacking());
        yield return new WaitForSeconds(lightAttackCooldown);
        canAttack = true;
    }

    IEnumerator resetIsLightAttacking()
    {
        yield return new WaitForSeconds(lightAttackCooldown);
        isAttacking = false;
        isLightAttacking = false;
    }
    #endregion

    #region Spell
    public void spellCast(InputAction.CallbackContext obj)
    {
        if (canCastSpell)
        {
            spellIsCasted();
        }
    }

    public void spellIsCasted()
    {
        fireSpell();
        isSpellCasting = true;
        canCastSpell = false;
        movement.Disable();
        animator.SetTrigger("Casting");
        AudioSource spellAudio = GetComponent<AudioSource>();
        spellAudio.PlayOneShot(spellSound);
        Invoke("delayMove", 1f);
        StartCoroutine(resetSpellCoolDown());
    }


    IEnumerator resetSpellCoolDown()
    {
        StartCoroutine(resetisSpellCasting());
        yield return new WaitForSeconds(spellCooldown);
        canCastSpell = true;
    }

    IEnumerator resetisSpellCasting()
    {
        yield return new WaitForSeconds(1);
        isSpellCasting = false;
    }
    public void fireSpell()
    {
        Ray ray = new Ray(casterPostion.position, casterPostion.forward);
        RaycastHit hit;

        if(Physics.Raycast(ray, out hit))
        {
            destination = hit.point;
        }
        else
        {
            destination = ray.GetPoint(10);
        }
        instantiateFire(casterPostion);
    }

    public void instantiateFire(Transform castingPoint)
    {

        GameObject fireball = Instantiate(spell, castingPoint.position, Quaternion.identity);
        fireball.GetComponent<Rigidbody>().velocity = (destination - castingPoint.position).normalized * 30;
    }
    #endregion

    public void hurt(int damage)
    {
        playerHealth -= damage;
    }

    public void dead()
    {
        if (playerHealth <= 0)
        {
            isDead = true;
            animator.SetTrigger("Dead");
            Debug.Log("Player is Dead");
            knight.enabled = false;
            archer.enabled = false;
            this.enabled = false;
            Invoke("deathScene", 2f);
        }
    }
    
    public void deathScene()
    {
        SceneManager.LoadScene("Death Scene");
    }

    private void OnCollisionEnter(Collision collision)
    {
        if(collision.gameObject.name == "Arrow" || collision.gameObject.tag == "Arrow")
        {
            playerHealth -= archer.attackDamage;
            Debug.Log("Player struck by arrow!");
        }
        if (collision.gameObject.name == "Arrow" || collision.gameObject.tag == "Arrow" && isPlayerBlocking)
        {
            Debug.Log("Arrow parried!");
        }
    }
    #endregion

    #region Camera
    private void lookingAt()
    {
        Vector3 direction = rb.velocity;
        direction.y = 0;
        if (movement.ReadValue<Vector2>().sqrMagnitude > 0.1f && direction.sqrMagnitude > 0.1f)
            this.rb.rotation = Quaternion.LookRotation(direction, Vector3.up);
        else
            rb.angularVelocity = Vector3.zero;
    }

    private Vector3 GetCameraRight(Camera playerCam)
    {
        Vector3 right = playerCam.transform.right;
        right.y = 0;
        return right.normalized;
    }

    private Vector3 GetCameraForward(Camera playerCam)
    {
        Vector3 forward = playerCam.transform.forward;
        forward.y = 0;
        return forward.normalized;
    }
    #endregion

    void FixedUpdate()
    {
        forceDirection  = movement.ReadValue<Vector2>().x * GetCameraRight(playerCam) * moveForce;
        forceDirection  = movement.ReadValue<Vector2>().y * GetCameraForward(playerCam) * moveForce;
        rb.AddForce(forceDirection, ForceMode.Impulse);
        forceDirection = Vector3.zero;

        Vector3 horizontalVelocity = rb.velocity;
        horizontalVelocity.y = 0;
        if (horizontalVelocity.sqrMagnitude > maxSpeed * maxSpeed)
            rb.velocity = horizontalVelocity.normalized * maxSpeed   Vector3.up * rb.velocity.y;

        lookingAt();
        dead();
    }
}

Player sword collision detection

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

public class CollisionDetection : MonoBehaviour
{
    public PlayerController player;
    public KnightController knight;
    public EnemyArcher archer;
    public AudioClip hit;
    public AudioClip parried;

    void OnTriggerEnter(Collider other)
    {
        if (other.tag == "EnemyKnight" || other.tag == "EnemyArcher")
        {
            AudioSource isHitting = GetComponent<AudioSource>();
            isHitting.PlayOneShot(hit);

            if(other.tag == "EnemyKnight")
            {
                if (player.isLightAttacking) 
                {
                    knight.takeDamage(5);
                    Debug.Log("Light attack on "   other.name   " For "   player.lightAttackDamage);
                }
                if (player.isHeavyAttacking)
                {
                    knight.takeDamage(7);
                    Debug.Log("Heavy attack on "   other.name   " For "   player.heavyAttackDamage);
                }
            }
            if(other.tag == "EnemyArcher"){
                if(player.isLightAttacking)
                {
                    archer.takeDamage(5);
                    Debug.Log("Light attack on "   other.name   " For "   player.lightAttackDamage);
                }
                if (player.isHeavyAttacking)
                {
                    archer.takeDamage(7);
                    Debug.Log("Heavy attack on "   other.name   " For "   player.heavyAttackDamage);
                }
            }
        }

        if(other.tag == "Blade")
        {
            if (player.isPlayerBlocking) 
            {
                AudioSource parry = GetComponent<AudioSource>();
                parry.PlayOneShot(parried);
                Debug.Log("Parried");
            }
        }
    }
}

Enemy Knight

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

public class KnightController : MonoBehaviour
{
    //Enemy Stats
    public int health = 20;
    public int attackDamage = 5;
    public float attackCooldown = 1f;
    public float agroRadius = 25f;
    public bool canAttack;
    public bool isAttacking = false;
    public bool isDead;

    //Enemy AI and Animation
    public PlayerController playerController;
    Transform target;
    NavMeshAgent agentKnight;
    Animator animator;


    void Start()
    {
        this.enabled = true;
        isDead = false;
        health = 20;
        target = PlayerManager.instance.player.transform;
        agentKnight = GetComponent<NavMeshAgent>();
        animator = GetComponent<Animator>();
    }

    void Update()
    {
        float distance = Vector3.Distance(target.position, transform.position);

        if (distance <= agroRadius)
        {
            isAgroedToPlayer();

            if (distance <= agentKnight.stoppingDistance   0.2f)
            {
                attackPlayer();
            }
        }
        die();
    }

    #region Attack
    public void attackPlayer()
    {
        isAttacking = true;
        canAttack = false;
        agentKnight.SetDestination(transform.position);
        animator.SetBool("isAgro", false);
        animator.SetTrigger("Attacking");
        facePlayerWhenAttacking();
        StartCoroutine(resetAttackCooldown());
    }

    IEnumerator resetAttackCooldown()
    {
        StartCoroutine(resetIsAttackingPlayer());
        yield return new WaitForSeconds(attackCooldown);
        canAttack = true;
    }

    IEnumerator resetIsAttackingPlayer()
    {
        yield return new WaitForSeconds(attackCooldown);
        isAttacking = false;
    }
    #endregion

    #region Agro
    public void isAgroedToPlayer()
    {
        animator.SetBool("isAgro", true);
        agentKnight.SetDestination(target.position);
    }
    #endregion

    public void takeDamage(int damage)
    {
        health -= damage;
    }

    public void die()
    {
        if (health <= 0)
        {
            isDead = true;
            animator.SetTrigger("Dead");
            animator.SetBool("isAgro", false);
            this.GetComponent<Rigidbody>().detectCollisions = false;
            playerController.totalEnemySlain  ;
            this.enabled = false;
            Debug.Log("Knight Killed!");
        }
    }

    private void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.name == "Fireball" || collision.gameObject.tag == "Spell")
        {
            health -= playerController.GetComponent<PlayerController>().spellDamage;
            Debug.Log("Knight is struck by fireball for "   playerController.GetComponent<PlayerController>().spellDamage);
        }
    }

    public void facePlayerWhenAttacking()
    {
        Vector3 direction = (target.position - transform.position).normalized;
        Quaternion lookRotation = Quaternion.LookRotation(new Vector3(direction.x, 0, direction.z));
        transform.rotation = Quaternion.Slerp(transform.rotation, lookRotation, Time.deltaTime * 5);
    }
}

Enemy knight sword collision detection

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

public class KnightCollisionDetector : MonoBehaviour
{
    public KnightController knight;
    public PlayerController player;
    public AudioClip playerHit;

    private void Start()
    {
        player = GameObject.FindGameObjectWithTag("Player").GetComponent<PlayerController>();
    }
    private void OnTriggerEnter(Collider other)
    {
        if (other.tag == "Player" || other.name == "Player" && knight.GetComponent<KnightController>().isAttacking)
        {
            if (player.isPlayerBlocking)
            {
                Debug.Log("Parried");
            }
            AudioSource hit = GetComponent<AudioSource>();
            hit.PlayOneShot(playerHit);
            player.hurt(knight.attackDamage);
            Debug.Log("Knight hit player for "   5);
        }
    }
}

Enemy Archer

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

public class EnemyArcher : MonoBehaviour
{
    //Enemy Stats
    public int health = 20;
    public int attackDamage = 10;
    public bool isAttacking = false;
    public bool canAttack;
    public float attackCooldown = 5f;
    public float agroRadius = 50f;
    public float bowRange = 30f;
    public bool isDead = false;

    //AI
    public PlayerController playerController;
    public GameObject arrow;
    public Transform arrowPoint;
    Transform target;
    NavMeshAgent agentArcher;
    Animator animator;

    void Start()
    {
        this.enabled = true;
        isDead = false;
        health = 10;
        target = PlayerManager.instance.player.transform;
        agentArcher = GetComponent<NavMeshAgent>();
        animator = GetComponent<Animator>();
    }

    void Update()
    {
        float distance = Vector3.Distance(target.position, transform.position);

        if (distance <= agroRadius)
        {
            isAgroedToPlayer();

            if(distance <= agroRadius && distance >= bowRange)
            {
                animator.SetBool("inRange", false);
                animator.SetTrigger("Attacking");
                agentArcher.SetDestination(target.position);
            }

            if (distance <= bowRange)
            {
                attackPlayer();
            }
        }
        die();
    }

    #region Attack
    public void attackPlayer()
    {
        isAttacking = true;
        canAttack = false;
        agentArcher.SetDestination(transform.position);
        animator.SetBool("isAgro", false);
        animator.SetBool("inRange", true);
        animator.SetTrigger("Attacking");
        facePlayerWhenAttacking();
        StartCoroutine(resetAttackCooldown());
    }


    IEnumerator resetAttackCooldown()
    {
        StartCoroutine(resetIsAttackingPlayer());
        yield return new WaitForSeconds(attackCooldown);
        canAttack = true;
    }


    IEnumerator resetIsAttackingPlayer()
    {
        yield return new WaitForSeconds(attackCooldown);
        isAttacking = false;
    }

    public void shootArrow()
    {
        GameObject arrowProjectile =  Instantiate(arrow, arrowPoint.position, transform.rotation);
        arrowProjectile.GetComponent<Rigidbody>().AddForce(transform.forward * 25f, ForceMode.Impulse);
    }
    #endregion

    #region Agro
    public void isAgroedToPlayer()
    {
        animator.SetBool("isAgro", true);
        agentArcher.SetDestination(target.position);
    }
    #endregion

    public void takeDamage(int damage)
    {
        health -= damage;
    }

    public void die()
    {
        if(health <= 0)
        {
            isDead = true;
            animator.SetTrigger("Dead");
            animator.SetBool("isAgro", false);
            this.GetComponent<Rigidbody>().detectCollisions = false;
            playerController.totalEnemySlain  ;
            this.enabled = false;
            Debug.Log("Archer Killed!");
        }
    }

    private void OnCollisionEnter(Collision collision)
    {
        if (collision.gameObject.name == "Fireball" || collision.gameObject.tag == "Spell")
        {
            health -= playerController.GetComponent<PlayerController>().spellDamage;
            Debug.Log("Archer is struck by fireball "   playerController.GetComponent<PlayerController>().spellDamage);
        }
    }

    public void facePlayerWhenAttacking()
    {
        Vector3 direction = (target.position - transform.position).normalized;
        Quaternion lookRotation = Quaternion.LookRotation(new Vector3(direction.x, 0, direction.z));
        transform.rotation = Quaternion.Slerp(transform.rotation, lookRotation, Time.deltaTime * 5);
    }
}

Random spawn enemy

using System.Collections.Generic;
using UnityEngine;

public class RandomEnemySpawn : MonoBehaviour
{
    public GameObject player;
    public GameObject archer;
    public GameObject knight;
    public bool playerIsDead;
    private int xPos1;
    private int zPos1;
    private int xPos2;
    private int zPos2;
    
    void Start()
    {
        playerIsDead = player.GetComponent<PlayerController>().isDead;
        StartCoroutine(spawnEnemy1());
        StartCoroutine(spawnEnemy2());
    }

    IEnumerator spawnEnemy1()
    {
        while (!playerIsDead)
        {
            xPos1 = 97;
            zPos1 = 67;
            Instantiate(archer, new Vector3(xPos1, 0 , zPos1), Quaternion.identity);
            Instantiate(knight, new Vector3(xPos1, 0 , zPos1), Quaternion.identity);
            yield return new WaitForSeconds(20);
        }
    }

    IEnumerator spawnEnemy2()
    {
        while (!playerIsDead)
        {
            xPos1 = 50;
            zPos1 = 55;
            Instantiate(archer, new Vector3(xPos1, 0, zPos1), Quaternion.identity);
            Instantiate(knight, new Vector3(xPos1, 0, zPos1), Quaternion.identity);
            yield return new WaitForSeconds(20);
        }
    }
}

CodePudding user response:

You are not getting the actual knight you're hitting, you're just damaging whatever you've put into the variable in your code:

public KnightController knight;

If I had to guess, you've probably dropped a prefab into it from the Editor. Taking a snippet from your code:

        if(other.tag == "EnemyKnight")
        {
            if (player.isLightAttacking) 
            {
                knight.takeDamage(5);
                Debug.Log("Light attack on "   other.name   " For "   player.lightAttackDamage);
            }
            if (player.isHeavyAttacking)
            {
                knight.takeDamage(7);
                Debug.Log("Heavy attack on "   other.name   " For "   player.heavyAttackDamage);
            }
        }

you can see that "knight" has nothing to do with "other", which is the thing you've actually hit.

My best recommendation would be to refactor your code such that your .takeDamage() method is an interface, then you can get that script from the object that's being hit, assuming the collider is attached to the same root object that has the KnightController or EnemyArcher script:

var enemy = other.gameObject.GetComponent<ITakeDamage>();
if(enemy != null) 
{
    enemy.takeDamage(5);
}

You could just replace ITakeDamage (a proposed interface you would have to write) with KnightController or EnemyArcher, but when you're writing basically the same code over and over to do the same action with different classes, that's a code smell asking for an interface.

I'd also recommend refactoring your player to have a method like CurrentDamage, and then the player knows when they're doing a heavy attack or something else. This is separation of concerns - a sword collision script might need to know that it can deal damage but shouldn't have to know all the different ways that damage is calculated.

CodePudding user response:

So it seems that the script is not detecting the tag I use, so instead I used the GetComponent method like this:

void OnTriggerEnter(Collider other)
    {
        if (other.tag == "EnemyKnight" || other.tag == "EnemyArcher")
        {
            AudioSource isHitting = GetComponent<AudioSource>();
            isHitting.PlayOneShot(hit);

            if(other.tag == "EnemyKnight")
            {
                if (player.isLightAttacking) 
                {
                    other.GetComponent<KnightController>().takeDamage(lightDamage);
                    Debug.Log("Light attack on "   other.name   " For "   lightDamage);
                }
                if (player.isHeavyAttacking)
                {
                    other.GetComponent<KnightController>().takeDamage(heavyDamage);
                    Debug.Log("Heavy attack on "   other.name   " For "   heavyDamage);
                }
            }
            if(other.tag == "EnemyArcher"){
                if(player.isLightAttacking)
                {
                    other.GetComponent<EnemyArcher>().takeDamage(lightDamage);
                    Debug.Log("Light attack on "   other.name   " For "   lightDamage);
                }
                if (player.isHeavyAttacking)
                {
                    other.GetComponent<EnemyArcher>().takeDamage(heavyDamage);
                    Debug.Log("Heavy attack on "   other.name   " For "   heavyDamage);
                }
            }
        }
    }

And now it works

  • Related