You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity敌人AI优化求助:实现Don't Starve式减速延迟攻击流程

Unity敌人AI攻击逻辑问题排查与修正

问题描述

开发敌人AI时遇到以下问题:敌人可正常追击玩家,但进入attackRange后无法造成伤害,会保持进入时的方向盲目移动,且不会减速。

需求目标

  • 敌人进入attackRange后减速至0.1f;
  • 等待0.5秒后发起攻击(玩家仍在范围内则造成伤害,否则仅播放动画);
  • 攻击后再等待0.5秒,恢复原移动速度;
  • 玩家离开attackRange时,敌人需完成完整流程后再加速,而非立刻提速。

原问题代码

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

public class enemy : MonoBehaviour
{
    public Transform player;
    public float chaseRange = 5f;
    public float attackRange = 1f;
    public float patrolSpeed = 0.5f;
    public float chaseSpeed = 0.75f;
    public float minIdleTime = 1f;
    public float maxIdleTime = 3f;
    public float minMoveTime = 1f;
    public float maxMoveTime = 3f;
    public eData enemyData;
    public float attackPrepareTime = 1f;
    public float attackCooldownTime = 2f;
    public int attackDamage = 9;

    private bool isChasing = false;
    private bool isPatrolling = true;
    private bool isIdle = false;
    private bool isAttacking = false;
    private bool isPreparingAttack = false;
    private bool isCooldown = false;
    private bool isReturning = false;
    private float idleTimer = 0f;
    private float idleTime = 0f;
    private float moveTimer = 0f;
    private float moveTime = 0f;
    private float attackPrepareTimer = 0f;
    private float attackCooldownTimer = 0f;
    private Vector2 patrolDirection;
    private Vector2 moveDirection;
    private Rigidbody2D rb;
    private Health enemyHealth;

    private void Start()
    {
        rb = GetComponent<Rigidbody2D>();
        enemyHealth = GetComponent<Health>();

        // Set enemy health from eData
        if (enemyHealth != null && enemyData != null)
        {
            enemyHealth.SetHealth(enemyData.hp, enemyData.hp);
        }

        idleTime = GetRandomIdleTime();
        moveTime = GetRandomMoveTime();
        patrolDirection = GetRandomDirection();
        moveDirection = GetRandomDirection();
    }

    private void Update()
    {
        float distanceToPlayer = Vector2.Distance(transform.position, player.position);

        if (distanceToPlayer <= chaseRange && distanceToPlayer > attackRange)
        {
            isChasing = true;
            isPatrolling = false;
            ChasePlayer();
        }
        else if (isChasing && distanceToPlayer > chaseRange)
        {
            isChasing = false;
            isPatrolling = true;
            StopChasing();
        }

        if (isReturning)
        {
            ReturnToPlayer();
        }
        else if (isPatrolling)
        {
            Patrol();
        }

        if (isPreparingAttack)
        {
            attackPrepareTimer += Time.deltaTime;

            if (attackPrepareTimer >= attackPrepareTime)
            {
                isPreparingAttack = false;
                isAttacking = true;
                attackPrepareTimer = 0f;
                rb.velocity = Vector2.zero;
                Invoke("PerformAttack", 0.5f);
            }
        }

        if (isCooldown)
        {
            attackCooldownTimer += Time.deltaTime;

            if (attackCooldownTimer >= attackCooldownTime)
            {
                isCooldown = false;
                attackCooldownTimer = 0f;
                if (!isReturning)
                {
                    rb.velocity = patrolDirection * patrolSpeed;
                }
            }
        }

        if (isAttacking)
        {
            rb.velocity = Vector2.zero;
        }
    }

    private void ChasePlayer()
    {
        Vector2 direction = (player.position - transform.position).normalized;
        rb.velocity = direction * chaseSpeed;

        if (!isAttacking && !isPreparingAttack)
        {
            if (Vector2.Distance(transform.position, player.position) <= attackRange)
            {
                rb.velocity = Vector2.zero;
                isPreparingAttack = true;
            }
        }
    }

    private void StopChasing()
    {
        rb.velocity = Vector2.zero;
        isAttacking = false;
        isPreparingAttack = false;
        isCooldown = false;
        isReturning = true;
    }

    private void ReturnToPlayer()
    {
        Vector2 direction = (player.position - transform.position).normalized;
        rb.velocity = direction * chaseSpeed;

        if (Vector2.Distance(transform.position, player.position) <= attackRange)
        {
            rb.velocity = Vector2.zero;
            isReturning = false; 
            isPreparingAttack = true;
        }
    }

    private void Patrol()
    {
        if (isIdle)
        {
            idleTimer += Time.deltaTime;

            if (idleTimer >= idleTime)
            {
                isIdle = false;
                moveTimer = 0f;
                moveTime = GetRandomMoveTime();
                moveDirection = GetRandomDirection();
            }
        }
        else
        {
            moveTimer += Time.deltaTime;

            if (moveTimer >= moveTime)
            {
                isIdle = true;
                idleTimer = 0f;
                idleTime = GetRandomIdleTime();
                rb.velocity = Vector2.zero;
            }
            else
            {
                rb.velocity = moveDirection * patrolSpeed;
            }
        }
    }

    private float GetRandomIdleTime()
    {
        return Random.Range(minIdleTime, maxIdleTime);
    }

    private float GetRandomMoveTime()
    {
        return Random.Range(minMoveTime, maxMoveTime);
    }

    private Vector2 GetRandomDirection()
    {
        float randomX = Random.Range(-1f, 1f);
        float randomY = Random.Range(-1f, 1f);
        return new Vector2(randomX, randomY).normalized;
    }

    private void PerformAttack()
    {
        if (isAttacking)
        {
            if (Vector2.Distance(transform.position, player.position) <= attackRange)
            {
                AttackPlayer();
            }

            isAttacking = false;
            isCooldown = true;
            Invoke("FinishAttack", 0.5f);
        }
    }

    private void FinishAttack()
    {
        isCooldown = false;
        if (!isReturning)
        {
            rb.velocity = chaseSpeed * (player.position - transform.position).normalized;
        }
    }

    private void AttackPlayer()
    {
        Health playerHealth = player.GetComponent<Health>();
        if (playerHealth != null)
        {
            playerHealth.Damage(attackDamage);
        }
    }
}

问题排查与修正方案

核心问题分析

  1. 状态优先级倒置:原代码中攻击相关状态的判断在追击/巡逻逻辑之后,导致追击逻辑会覆盖攻击阶段的速度设置,无法实现减速或停止。
  2. 减速逻辑缺失:需求要求进入攻击范围后减速至0.1f,原代码直接将速度设为0,不符合需求。
  3. 流程中断问题:玩家离开攻击范围时,原代码没有保留当前攻击流程,会提前退出攻击状态,不符合“完成完整流程后再加速”的要求。
  4. Invoke状态同步问题:原代码中PerformAttack的Invoke调用可能因状态变化导致逻辑失效,且速度恢复逻辑不完整。

修正后代码

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

public class Enemy : MonoBehaviour
{
    public Transform player;
    public float chaseRange = 5f;
    public float attackRange = 1f;
    public float patrolSpeed = 0.5f;
    public float chaseSpeed = 0.75f;
    public float attackSlowSpeed = 0.1f; // 攻击准备阶段减速速度
    public float minIdleTime = 1f;
    public float maxIdleTime = 3f;
    public float minMoveTime = 1f;
    public float maxMoveTime = 3f;
    public eData enemyData;
    public float attackPrepareDelay = 0.5f; // 攻击前等待时间(需求中的0.5秒)
    public float attackAfterDelay = 0.5f;   // 攻击后等待时间(需求中的0.5秒)
    public int attackDamage = 9;

    private bool isChasing = false;
    private bool isPatrolling = true;
    private bool isIdle = false;
    private bool isAttackProcessing = false; // 标记是否正在执行攻击流程
    private float idleTimer = 0f;
    private float idleTime = 0f;
    private float moveTimer = 0f;
    private float moveTime = 0f;
    private Vector2 moveDirection;
    private Rigidbody2D rb;
    private Health enemyHealth;

    private void Start()
    {
        rb = GetComponent<Rigidbody2D>();
        enemyHealth = GetComponent<Health>();

        if (enemyHealth != null && enemyData != null)
        {
            enemyHealth.SetHealth(enemyData.hp, enemyData.hp);
        }

        idleTime = GetRandomIdleTime();
        moveTime = GetRandomMoveTime();
        moveDirection = GetRandomDirection();
    }

    private void Update()
    {
        float distanceToPlayer = Vector2.Distance(transform.position, player.position);

        // 优先处理攻击流程,确保攻击过程不被打断
        if (isAttackProcessing)
            return;

        // 追击逻辑
        if (distanceToPlayer <= chaseRange)
        {
            isPatrolling = false;
            isChasing = true;

            // 进入攻击范围则启动攻击流程
            if (distanceToPlayer <= attackRange)
            {
                StartCoroutine(AttackCoroutine());
                return;
            }

            ChasePlayer();
        }
        else
        {
            // 离开追击范围,回到巡逻状态
            if (isChasing)
            {
                isChasing = false;
                isPatrolling = true;
                rb.velocity = Vector2.zero;
            }

            if (isPatrolling)
            {
                Patrol();
            }
        }
    }

    // 攻击流程协程,完整实现需求的攻击步骤
    private IEnumerator AttackCoroutine()
    {
        isAttackProcessing = true;
        Vector2 lastChaseDir = (player.position - transform.position).normalized;

        // 步骤1:减速至attackSlowSpeed
        rb.velocity = lastChaseDir * attackSlowSpeed;
        yield return new WaitForSeconds(attackPrepareDelay);

        // 步骤2:发起攻击(播放动画逻辑可在此处添加)
        float distance = Vector2.Distance(transform.position, player.position);
        if (distance <= attackRange)
        {
            AttackPlayer();
        }

        // 步骤3:攻击后等待,保持减速状态
        rb.velocity = lastChaseDir * attackSlowSpeed;
        yield return new WaitForSeconds(attackAfterDelay);

        // 步骤4:恢复原速度,结束攻击流程
        isAttackProcessing = false;
        // 如果玩家仍在追击范围内,继续追击;否则回到巡逻
        float finalDistance = Vector2.Distance(transform.position, player.position);
        if (finalDistance <= chaseRange && finalDistance > attackRange)
        {
            ChasePlayer();
        }
        else if (finalDistance > chaseRange)
        {
            isChasing = false;
            isPatrolling = true;
        }
    }

    private void ChasePlayer()
    {
        Vector2 direction = (player.position - transform.position).normalized;
        rb.velocity = direction * chaseSpeed;
    }

    private void Patrol()
    {
        if (isIdle)
        {
            idleTimer += Time.deltaTime;
            if (idleTimer >= idleTime)
            {
                isIdle = false;
                moveTimer = 0f;
                moveTime = GetRandomMoveTime();
                moveDirection = GetRandomDirection();
            }
        }
        else
        {
            moveTimer += Time.deltaTime;
            if (moveTimer >= moveTime)
            {
                isIdle = true;
                idleTimer = 0f;
                idleTime = GetRandomIdleTime();
                rb.velocity = Vector2.zero;
            }
            else
            {
                rb.velocity = moveDirection * patrolSpeed;
            }
        }
    }

    private float GetRandomIdleTime() => Random.Range(minIdleTime, maxIdleTime);
    private float GetRandomMoveTime() => Random.Range(minMoveTime, maxMoveTime);

    private Vector2 GetRandomDirection()
    {
        float randomX = Random.Range(-1f, 1f);
        float randomY = Random.Range(-1f, 1f);
        return new Vector2(randomX, randomY).normalized;
    }

    private void AttackPlayer()
    {
        Health playerHealth = player.GetComponent<Health>();
        if (playerHealth != null)
        {
            playerHealth.Damage(attackDamage);
        }
    }
}

修正说明

  1. 状态优先级调整:将攻击流程判断放在Update最前端,确保攻击过程中不会被追击/巡逻逻辑打断,实现“完成完整流程后再加速”的需求。
  2. 添加减速逻辑:新增attackSlowSpeed变量,在攻击准备和攻击后阶段将速度设置为该值,符合需求中的减速要求。
  3. 使用协程管理攻击流程:用AttackCoroutine替代原有的Invoke和状态标记,更清晰地控制攻击的各个阶段(减速-等待-攻击-等待-恢复速度),避免状态同步问题。
  4. 简化状态变量:移除冗余的isPreparingAttack、isAttacking、isCooldown等状态,统一用isAttackProcessing标记是否处于攻击流程,减少逻辑冲突。
  5. 优化离开范围处理:攻击流程结束后再判断玩家位置,决定继续追击或回到巡逻,确保流程完整执行。

内容的提问来源于stack exchange,提问作者Henzio

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 06:22:06