Look for help!at Unity 2D Spider Boss Attack Logic Issues

Hello everyone,

I’m developing a Unity 2D game and have encountered an issue with the attack logic of a spider boss. I hope to get some help.

What I’m Trying to Achieve:

  • After the spider boss enters the field, it locates the player’s position using two detection boxes in front and behind.
  • Once the player is detected, the spider rushes towards the player to attack, with an attack pre-cast of about 1 second.
  • After the attack is completed, it enters an attack completion animation.
  • This attack process loops twice; after two cycles, the spider retreats to a safe distance and enters a cooldown state.
  • The spider repeats this operation continuously.

Current Problems:

  1. Inconsistent Attack Cycles:
  • The spider does not perfectly execute the two-attack cooldown method. It often retreats excessively.
  1. Spider Stops Detecting Player:
  • After several attacks, the spider stands still. Even when the player touches the detection area, the spider does not initiate an attack.
  1. Spider Starts Bouncing:
  • After several attacks, the spider starts bouncing up and down with the ground, and I’m not sure why.

Implemented Attack Logic and Transition Conditions:

  • Entrance Phase:
    • The spider plays an entrance animation. After the animation ends, it begins detecting the player.
    • Uses the IsEntranceCompleted() method to check if the entrance animation has finished.
  • Player Detection:
    • Uses two detection boxes (leftCheckBox and rightCheckBox) to detect if the player is within range.
    • If the player is detected, sets readyToAttack = true and triggers the attack pre-cast animation SpiderAttackCast.
  • Attack Phase:
    • During the SpiderAttackCast phase, the spider moves towards the player until it reaches a certain distance (castMoveDistance).
    • The pre-cast lasts about 1 second; after that, the FinishAttack() method is called.
  • Attack Completion:
    • When the attack animation’s last frame triggers, FinishAttack() is called.
    • The attack counter attackCounter increments.
    • If attackCounter >= maxAttacks (set to 2), the spider enters the retreat state and resets attackCounter to 0.
    • Otherwise, it triggers the SpiderBossAttackFinished animation.
  • Retreat and Cooldown:
    • The spider moves away from the player by a certain distance (retreatDistance) and enters a cooldown state.
    • The cooldown lasts for cooldownDuration seconds.
    • After the cooldown, the spider resets relevant states and repeats the attack cycle.
  • Moving to Player’s X-Axis:
    • The spider periodically tries to move to the player’s X-axis position to approach the player.

Transition Conditions and Trigger Logic:

  • readyToAttack:
    • Set to true when the player is detected, initiating the attack pre-cast and movement.
  • isLeaving:
    • Set to true when the attack count reaches the limit (after two attacks), entering the retreat state.
    • Reset to false after the cooldown ends.
  • isCoolingDown:
    • Set to true when entering the cooldown state, disabling attacks and player detection.
    • Reset to false after the cooldown ends.
  • attackCounter:
    • Records the number of attacks.
    • Incremented after each attack is completed.
    • Reset to 0 when reaching the maximum number of attacks.

What I’ve Tried:

  • Ensuring that state variables (like isLeaving, isCoolingDown) are correctly reset after attacks.
  • Changing the movement method to use physics-based movement (Rigidbody2D.MovePosition) instead of directly modifying transform.position.
  • Disabling gravity effect (rb.gravityScale = 0f) to prevent the spider from being affected by gravity.

Persistent Issues:

  • The spider often does not execute the attack cycles as intended and retreats too much.
  • After several attacks, the spider stands still and stops detecting the player.
  • The spider starts bouncing up and down after a few attacks.

Questions:

  • How can I modify the code or animation state machine so that the spider executes the attack cycles as intended?
  • Why does the spider stop detecting the player and not attack even when the player is within the detection area?
  • What could be causing the spider to bounce, and how can I fix this?

Additional Information:

  • Game Type: 2D game.

  • Code Snippet:

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

public class BossAttackScript : MonoBehaviour
{
private SpiderBossControlScript spiderBoss;
private bool isAttacking = false;
private bool readyToAttack = false;
private bool thinking = false;
private bool isLeaving = false; // Flag used to return from Finished to Move state
private bool isCoolingDown = false; // Flag used to disable detection and skills

[SerializeField] private float moveSpeed = 6.6f;  // Movement speed
[SerializeField] private float attackOffset = 1.3f;  // Movement offset, maximum distance to move during attack
[SerializeField] private float castMoveSpeed = 2f;   // Movement speed during Cast phase
[SerializeField] private float castMoveDistance = 1f;  // Maximum distance to move during Cast phase
[SerializeField] private int maxAttacks = 2;  // Limit on the number of attacks per spider attack cycle
[SerializeField] private float cooldownDuration = 2f;  // Duration of the cooldown state
[SerializeField] private float retreatDistance = 1.5f;  // Retreat distance
[SerializeField] private float idleDuration = 1.3f;  // Duration of idle state
[SerializeField] private float xMoveSpeed = 7f;  // Speed when moving to player's X-axis
[SerializeField] private float maxXMoveTime = 2.7f;  // Maximum time to move
[SerializeField] private float xSkillCooldown = 1.3f;  // Cooldown time for the skill

public LayerMask playerLayer;
public LayerMask groundLayer;  // Used to detect GroundLayer
public Transform leftCheckBox;
public Transform rightCheckBox;
private Rigidbody2D rb;
private float detectionRadius = 0.3f;

private int attackCounter = 0;  // Attack counter
private Vector3 castStartPosition;  // Used to record the starting position of movement
private Transform playerTransform;  // Used to store the player's position

private bool isUsingXSkill = false;  // Used to determine if the X-axis movement skill is being used

void Start()
{
    // Get the SpiderBossControlScript component
    spiderBoss = GetComponent<SpiderBossControlScript>();
    rb = GetComponent<Rigidbody2D>();
    rb.simulated = false;  // Disable Rigidbody until the entrance animation ends
    rb.gravityScale = 1f;  // Disable gravity effect (Note: Setting gravityScale to 1f enables gravity. To disable gravity, set it to 0f.)

    // Get the player's Transform
    GameObject player = GameObject.FindGameObjectWithTag("Player");
    if (player != null)
    {
        playerTransform = player.transform;
    }
    else
    {
        Debug.LogError("Cannot find the Player object!");
    }

    // Immediately start X-axis detection after the entrance animation ends
    StartCoroutine(CheckPlayerXSkill());
}

void Update()
{
    // If in cooldown state, disable detection and skills
    if (isCoolingDown)
    {
        return;  // Disable all skills during cooldown
    }

    // Confirm if the entrance animation has completed
    if (spiderBoss != null && spiderBoss.IsEntranceCompleted() && !thinking && !isAttacking && !readyToAttack && attackCounter < maxAttacks && !isLeaving)
    {
        rb.simulated = true;  // Enable Rigidbody

        // Detect if the player is within the detection range
        Collider2D leftHit = Physics2D.OverlapCircle(leftCheckBox.position, detectionRadius, playerLayer);
        Collider2D rightHit = Physics2D.OverlapCircle(rightCheckBox.position, detectionRadius, playerLayer);

        if (leftHit != null || rightHit != null)
        {
            readyToAttack = true;  // Enter attack state
            spiderBoss.animator.SetBool("ReadyToAttack", true);  // Ensure the Animator parameter is correctly set
            spiderBoss.animator.SetTrigger("SpiderAttackCast");  // Switch to the attack pre-cast animation

            // Record the player's Transform position
            playerTransform = leftHit != null ? leftHit.transform : rightHit.transform;
            castStartPosition = transform.position;  // Record the spider's starting position
            StartCoroutine(DelayedFinishAttack());  // Trigger FinishAttack method after 1 second
        }
    }

    // Move towards the player's direction during the SpiderAttackCast phase
    if (readyToAttack && playerTransform != null && Vector3.Distance(transform.position, castStartPosition) < castMoveDistance)
    {
        MoveTowardsPlayer();  // Move towards the player
    }
}

// Execute the function to move towards the player's direction until reaching a distance of 1f
private void MoveTowardsPlayer()
{
    // Calculate the position for the next frame
    Vector2 targetPosition = Vector2.MoveTowards(rb.position, playerTransform.position, castMoveSpeed * Time.deltaTime);
    rb.MovePosition(targetPosition);  // Use Rigidbody2D's MovePosition

    AdjustFacingDirection(playerTransform.position.x);  // Adjust facing direction
}

// Adjust the spider's facing direction (left/right)
private void AdjustFacingDirection(float targetX)
{
    // Adjust facing direction based on the player's position
    if (targetX < transform.position.x)
    {
        transform.localScale = new Vector3(0.5f, transform.localScale.y, transform.localScale.z);  // Face left
    }
    else
    {
        transform.localScale = new Vector3(-0.5f, transform.localScale.y, transform.localScale.z);  // Face right
    }
}

// Delay triggering the FinishAttack method
private IEnumerator DelayedFinishAttack()
{
    yield return new WaitForSeconds(1.0f);  // Delay 1 second
    FinishAttack();  // Execute the FinishAttack method
}

// Triggered on the last frame of the attack animation
public void FinishAttack()
{
    readyToAttack = false;  // Reset ReadyToAttack, indicating the attack has ended
    spiderBoss.animator.SetBool("ReadyToAttack", false);  // Update the ReadyToAttack parameter in Animator

    attackCounter++;  // Increase the attack count
    if (attackCounter >= maxAttacks)
    {
        isLeaving = true;  // Prepare to enter cooldown
        spiderBoss.animator.SetBool("isLeaving", true);  // Update Animator parameter
        spiderBoss.animator.SetTrigger("SpiderBossMove");  // Switch to the move animation
        attackCounter = 0;  // Reset attack count
    }
    else
    {
        spiderBoss.animator.SetTrigger("SpiderBossAttackFinished");  // Switch to the Finished animation
    }
}

// Triggered during the SpiderBossMove phase, move away from the player and enter cooldown state
public void StartRetreat()
{
    StartCoroutine(RetreatAndCooldown());
}

// Spider moves away from the player and enters cooldown state
private IEnumerator RetreatAndCooldown()
{
    // Disable detection and skill functions
    isCoolingDown = true;
    isUsingXSkill = false;  // Disable X-axis skill

    // Check if the spider can move away from the player (GroundLayer detection)
    bool canRetreat = CanRetreat();

    if (canRetreat)
    {
        // Position to move away from the player: move along the direction opposite from the player to the spider
        Vector2 retreatDirection = (rb.position - (Vector2)playerTransform.position).normalized;
        Vector2 retreatTarget = rb.position + retreatDirection * retreatDistance;

        // Execute movement
        float retreatSpeed = 2f;
        while (Vector2.Distance(rb.position, retreatTarget) > 0.1f)
        {
            Vector2 newPosition = Vector2.MoveTowards(rb.position, retreatTarget, retreatSpeed * Time.deltaTime);
            rb.MovePosition(newPosition);
            yield return null;  // Wait for the next frame
        }
    }

    // Enter cooldown state
    yield return new WaitForSeconds(cooldownDuration);  // Wait for the cooldown time
    isCoolingDown = false;  // Cooldown ends, re-enable detection
    isLeaving = false;  // Reset isLeaving state
    spiderBoss.animator.SetBool("isLeaving", false);  // Update Animator parameter
}

// Check if the spider can move away from the player, avoiding passing through the GroundLayer
private bool CanRetreat()
{
    // Raycast backwards to detect GroundLayer
    RaycastHit2D leftRay = Physics2D.Raycast(leftCheckBox.position, -transform.right, retreatDistance, groundLayer);
    RaycastHit2D rightRay = Physics2D.Raycast(rightCheckBox.position, -transform.right, retreatDistance, groundLayer);

    if (leftRay.collider != null || rightRay.collider != null)
    {
        // Detected ground obstacle
        return false;
    }
    return true;
}

// Triggered on the last frame of the SpiderBossAttackFinished animation, return to move state
public void ReturnToMove()
{
    isLeaving = true;  // Prepare to return to move state
    spiderBoss.animator.SetBool("isLeaving", true);  // Update Animator parameter
    StartRetreat();  // Start retreat action
}

// Detect the player's X-axis coordinate and move to their X-axis
private IEnumerator CheckPlayerXSkill()
{
    yield return new WaitUntil(() => spiderBoss.IsEntranceCompleted());  // Wait for the entrance animation to end

    while (true)
    {
        if (!isCoolingDown && !isUsingXSkill && !readyToAttack && !isLeaving)
        {
            // Start moving towards the player's X-axis direction
            StartCoroutine(MoveTowardsPlayerX());
        }

        yield return new WaitForSeconds(0.2f);  // Check every 0.2 seconds
    }
}

// Move towards the player's X-axis direction
private IEnumerator MoveTowardsPlayerX()
{
    isUsingXSkill = true;
    float elapsedTime = 0f;

    while (elapsedTime < maxXMoveTime && playerTransform != null)
    {
        // Only move on the X-axis
        Vector2 targetPosition = new Vector2(playerTransform.position.x, rb.position.y);
        AdjustFacingDirection(playerTransform.position.x);  // Adjust facing direction
        Vector2 newPosition = Vector2.MoveTowards(rb.position, targetPosition, xMoveSpeed * Time.deltaTime);
        rb.MovePosition(newPosition);

        elapsedTime += Time.deltaTime;

        // Play move animation
        spiderBoss.animator.SetTrigger("SpiderBossMove");

        // If the player is detected and enters attack state, terminate this skill
        if (readyToAttack)
        {
            isUsingXSkill = false;
            yield break;
        }

        yield return null;
    }

    // If timed out, enter cooldown
    isUsingXSkill = false;
    yield return new WaitForSeconds(xSkillCooldown);  // Skill cooldown
}

// Disable physical collision rebound
void OnCollisionEnter2D(Collision2D collision)
{
    if (collision.gameObject.layer == LayerMask.NameToLayer("Player"))
    {
        // Disable rebound effect but keep gravity
        rb.velocity = Vector2.zero;  // Stop moving to avoid rebound
    }
}

void OnDrawGizmos()
{
    // Draw detection area for visualization in the editor
    Gizmos.color = Color.red;
    if (leftCheckBox != null)
    {
        Gizmos.DrawWireSphere(leftCheckBox.position, detectionRadius);
    }
    if (rightCheckBox != null)
    {
        Gizmos.DrawWireSphere(rightCheckBox.position, detectionRadius);
    }
}

}

Thank you very much for your time and assistance!