Enemy Movement Script not transitioning

CTA33

New member
Joined
Feb 19, 2025
Messages
1
Programming Experience
Beginner
my problem with this script i wrote, and ai edited/changed some, is that the enemy is supposed to switch from attacking to idle for a cooldown period and then back to attacking, while the player is in close proximity. but what happens is the cooldown fails, idle doesnt interchange with attacking, the enemy just hits and hits and hits in quick succession, and the animatiion is a little jittery. i have the proper animation events set. where is this script going wrong?? Thank you! spent many hours on this. I am a newbie. :)
C#:
using UnityEngine;

public class EnemyMovement : MonoBehaviour
{
// Public variables
public Rigidbody2D rb;
public float speed = 1f;
public float attackRange = 2f;
public float attackCoolDown = 2f;
public float playerDetectRange = 5f;
public Transform detectionPoint;
public LayerMask playerLayer;

// Private variables
private Transform player;
private int facingDirection = 1;
private Animator anim;
private EnemyState enemyState;
private float attackCoolDownTimer;
private bool isAttacking;

public enum EnemyState
{
    Idle,
    Chasing,
    Attacking,
    Patrolling
}

void Start()
{
    rb = GetComponent<Rigidbody2D>();
    anim = GetComponent<Animator>();
    ChangeState(EnemyState.Idle);
}

void Update()
{
    CheckForPlayer();
    UpdateCooldownTimer();

    switch (enemyState)
    {
        case EnemyState.Chasing:
            Chase();
            break;
        case EnemyState.Attacking:
            if (!isAttacking)
            {
                rb.linearVelocity = Vector2.zero; // Stop movement when attacking
                Attack();
            }
            break;
        case EnemyState.Idle:
            if (attackCoolDownTimer <= 0 && player != null && Vector2.Distance(transform.position, player.position) <= attackRange)
            {
                ChangeState(EnemyState.Attacking);
            }
            break;
    }
}

private void UpdateCooldownTimer()
{
    if (attackCoolDownTimer > 0)
    {
        attackCoolDownTimer -= Time.deltaTime;
        Debug.Log($"Cooldown timer: {attackCoolDownTimer}");
    }
}

void Chase()
{
    if (player == null) return;

    if (Vector2.Distance(player.position, transform.position) <= attackRange)
    {
        ChangeState(EnemyState.Attacking);
    }
    else
    {
        AdjustFacingDirection();
        Vector2 direction = (player.position - transform.position).normalized;
        rb.linearVelocity = direction * speed; // Use linearVelocity instead of velocity
    }
}

private void AdjustFacingDirection()
{
    if (player.position.x > transform.position.x && facingDirection < 0 ||
        player.position.x < transform.position.x && facingDirection > 0)
    {
        Flip();
    }
}

void CheckForPlayer()
{
    Collider2D[] hits = Physics2D.OverlapCircleAll(detectionPoint.position, playerDetectRange, playerLayer);
    Debug.Log("Checking for player...");

    if (hits.Length > 0)
    {
        player = hits[0].transform;
        Debug.Log("Player detected!");

        if (Vector2.Distance(transform.position, player.position) <= attackRange && attackCoolDownTimer <= 0)
        {
            attackCoolDownTimer = attackCoolDown; // Reset cooldown when attacking
            ChangeState(EnemyState.Attacking);
        }
        else if (enemyState != EnemyState.Chasing)
        {
            ChangeState(EnemyState.Chasing);
        }
    }
    else
    {
        Debug.Log("No player detected.");
        if (enemyState != EnemyState.Idle)
        {
            ChangeState(EnemyState.Idle);
        }
    }
}

void Flip()
{
    facingDirection *= -1;
    transform.localScale = new Vector3(transform.localScale.x * -1, transform.localScale.y, transform.localScale.z);
}

public void Attack()
{
    // Implement attack logic here
    Debug.Log("Attack!");
    isAttacking = true;
    Invoke("OnAttackAnimationEnd", 1f); // Adjust the duration as per your attack animation length
}

public void OnAttackAnimationEnd()
{
    Debug.Log("Attack animation ended.");
    isAttacking = false;
    attackCoolDownTimer = attackCoolDown; // Reset the cooldown timer after the attack
    ChangeState(EnemyState.Idle); // Switch to Idle state after attack
}

void ChangeState(EnemyState newState)
{
    Debug.Log($"Changing state from {enemyState} to {newState}");
   
    // Exit the current animation
    switch (enemyState)
    {
        case EnemyState.Idle:
            anim.SetBool("isIdle", false);
            break;
        case EnemyState.Chasing:
            anim.SetBool("isChasing", false);
            break;
        case EnemyState.Attacking:
            anim.SetBool("isAttacking", false);
            break;
    }

    // Update our current state
    enemyState = newState;

    // Enter the new animation
    switch (enemyState)
    {
        case EnemyState.Idle:
            anim.SetBool("isIdle", true);
            rb.linearVelocity = Vector2.zero; // Ensure the enemy stops moving
            break;
        case EnemyState.Chasing:
            anim.SetBool("isChasing", true);
            break;
        case EnemyState.Attacking:
            anim.SetBool("isAttacking", true);
            rb.linearVelocity = Vector2.zero; // Ensure the enemy stops moving
            break;
    }
}


}
 
Last edited by a moderator:
Moving to 3rd party products subforum since we don't have a specific Unity area.
 
Your code is kind of convoluted, but I think what is happening is is that at time T, you are going into attack mode. On the next Update() call, line 102 in CheckForPlayer() is true because you were attacking last frame, and therefore still within attack range in this frame. Execution moves on to line 107. This is false because the cooldown timer is greater than 0, and so the check moves on to line 112. Since the current state is Attack, the state is then forced to be Chasing. When execution makes its way out of CheckForPlayer() back up to Update(), the switch statement on line 42 will go to line 44 since you are in a Chasing state. Line 45 will call Chase() which causes the state go back Attacking once more because of being in attack range. Since all of those state changes effectively make your animator to keep starting an new animation run.

At some point your OnAttackAnimationEnd() is called. The attack flag is turned off, the cool down timer is again reset to its max value, and the state is change to Idle. Update() will be called eventually, and once again, line 107 will be false, and line 112 will be true putting you into the Chase state. And then again, Chase() will put you into Attack state.
 
Back
Top Bottom