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

Unity 2D敌人巡逻功能异常:Patrol方法未调用问题求助

Unity 2D敌人巡逻功能未触发问题排查

问题概述

开发Unity 2D敌人系统时,巡逻功能异常,核心表现为Patrol()方法未被调用。首次使用继承机制,推测问题出在CurrentState的设置逻辑上。设计逻辑如下:

  • 基类EnemyBase实现Patrol方法,并在自身Update中根据CurrentState调用对应状态方法
  • 子类LunaticCultistBase在Start中设置CurrentState = EnemyState.Patrol,但该状态未触发基类的巡逻逻辑

相关代码

基类 EnemyBase

using Assets.Code.Enemy.Enemies;
using System.Collections;
using System.Collections.Generic;
using Unity.VisualScripting;
using UnityEngine;

public class EnemyBase : MonoBehaviour
{
    [Header("Base values")]
    private int _health = 0;
    private int _damage = 0;
    private int _speed = 0;
    private EnemyState _currentState;

    [Header("Movement elements")]
    protected bool playerInRange = false;
    [SerializeField] protected CapsuleCollider2D SpotRange;
    [SerializeField] protected Transform PlayerTransform;
    [SerializeField] protected Transform EnemyFirstPosition, EnemySecondPosition;
    protected GameObject CurrentEnemy;

    virtual protected int Speed
    {
        get => _speed;
        set => _speed = value;
    }

    virtual protected EnemyState CurrentState
    {
        get => _currentState;
        set => _currentState = value;
    }

    virtual protected int Health
    {
        get => _health;
        set => _health = value;
    }

    virtual protected int Damage
    {
        get => _damage;
        set => _damage = value;
    }

    void Update()
    {
        switch (CurrentState)
        {
            case EnemyState.Patrol:
                StartCoroutine(Patrol());
                break;
            case EnemyState.Chase:
                MoveTowardsPlayer();
                break;
            case EnemyState.Attack:
                Attack();
                break;
        }
    }

    public virtual void TakeDamage(int damageTotake)
    {
        Health -= damageTotake;
    }

    protected virtual void MoveTowardsPlayer() { }

    protected virtual void Attack() { }

    protected virtual IEnumerator Patrol()
    {
        Transform nextTargetPoint = EnemyFirstPosition;

        while (true)
        {
            if (nextTargetPoint != null)
            {
                Debug.Log("move enemy");
                CurrentEnemy.transform.position = Vector2.MoveTowards(
                    CurrentEnemy.transform.position,
                    nextTargetPoint.transform.position,
                    Speed * Time.deltaTime
                );

                if ((CurrentEnemy.transform.position - nextTargetPoint.transform.position).magnitude < 0.1f)
                {
                    yield return new WaitForSeconds(1);
                    nextTargetPoint = (nextTargetPoint == EnemyFirstPosition) ? EnemySecondPosition : EnemyFirstPosition;
                }
            }
            else if (nextTargetPoint == null)
            {
                nextTargetPoint = EnemyFirstPosition.transform;
            }
        }
    }

    protected virtual void Rest() { }

    protected virtual void Die()
    {
        Destroy(this.gameObject);
    }

    protected virtual void OnTriggerEnter2D(Collider2D collision)
    {
        if (collision.CompareTag("Player"))
        {
            playerInRange = true;
        }
    }

    protected virtual void OnTriggerExit2D(Collider2D collision)
    {
        if (collision.CompareTag("Player"))
        {
            playerInRange = false;
        }
    }
}

子类 LunaticCultistBase

using Assets.Code.Enemy.Enemies;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class LunaticCultistBase : EnemyBase
{
    private Animator _enemyAnimator;
    private Rigidbody2D _enemyRigidbody;
    private SpriteRenderer _enemySpriteRenderer;

    void Start()
    {
        _enemyAnimator = GetComponent<Animator>();
        _enemyRigidbody = GetComponent<Rigidbody2D>();
        _enemySpriteRenderer = GetComponent<SpriteRenderer>();

        base.Health = 20;
        base.Damage = 10;
        base.Speed = 10;
        base.CurrentEnemy = this.gameObject;
        base.CurrentState = EnemyState.Patrol;
        CurrentEnemy = this.gameObject;
    }

    void Update()
    {
        if (base.Health <= 0)
            Die();

        if (playerInRange)
        {
            if (PlayerTransform.position.x < this.gameObject.transform.position.x)
            {
                _enemySpriteRenderer.flipX = true;
            }
            else
            {
                _enemySpriteRenderer.flipX = false;
            }
        }
    }

    public override void TakeDamage(int damageTotake)
    {
        _enemyRigidbody.bodyType = RigidbodyType2D.Static;
        base.TakeDamage(damageTotake);
        Invoke("SetAnimationAndBodyType", 0.1f);
    }

    private void SetAnimationAndBodyType()
    {
        _enemyAnimator.SetTrigger("Hit");
        _enemyRigidbody.bodyType = RigidbodyType2D.Dynamic;
    }

    protected override void Die()
    {
        _enemyAnimator.SetTrigger("Death");
        Invoke("InvokeDie", 1f);
    }

    private void InvokeDie()
    {
        base.Die();
    }   
}

EnemyState 枚举

public enum EnemyState
{
    Patrol,
    Chase,
    Attack
}

问题原因与解决方案

1. 核心问题:子类未调用基类Update方法

子类LunaticCultistBase重写了Update方法,但没有执行base.Update(),导致基类中根据CurrentState触发巡逻逻辑的代码完全没有运行。

修改子类Update方法:

void Update()
{
    base.Update(); // 必须调用基类Update,触发状态判断逻辑

    if (base.Health <= 0)
        Die();

    if (playerInRange)
    {
        if (PlayerTransform.position.x < this.gameObject.transform.position.x)
        {
            _enemySpriteRenderer.flipX = true;
        }
        else
        {
            _enemySpriteRenderer.flipX = false;
        }
    }
}

2. 次要问题:Patrol协程重复启动

基类Update每帧都会检查CurrentState,如果是Patrol就启动新的协程,会导致多个协程同时控制敌人移动,引发逻辑混乱。需要添加标记避免重复启动,并在状态切换时终止协程。

修改基类EnemyBase:

private EnemyState _currentState;
private bool isPatrolling = false; // 新增巡逻状态标记

void Update()
{
    switch (CurrentState)
    {
        case EnemyState.Patrol:
            if (!isPatrolling)
            {
                isPatrolling = true;
                StartCoroutine(Patrol());
            }
            break;
        case EnemyState.Chase:
            isPatrolling = false;
            MoveTowardsPlayer();
            break;
        case EnemyState.Attack:
            isPatrolling = false;
            Attack();
            break;
    }
}

protected virtual IEnumerator Patrol()
{
    Transform nextTargetPoint = EnemyFirstPosition;

    // 仅当处于Patrol状态时执行巡逻逻辑
    while (CurrentState == EnemyState.Patrol)
    {
        if (nextTargetPoint != null)
        {
            Debug.Log("move enemy");
            CurrentEnemy.transform.position = Vector2.MoveTowards(
                CurrentEnemy.transform.position,
                nextTargetPoint.transform.position,
                Speed * Time.deltaTime
            );

            if ((CurrentEnemy.transform.position - nextTargetPoint.transform.position).magnitude < 0.1f)
            {
                yield return new WaitForSeconds(1);
                nextTargetPoint = (nextTargetPoint == EnemyFirstPosition) ? EnemySecondPosition : EnemyFirstPosition;
            }
        }
        else
        {
            // EnemyFirstPosition已经是Transform,无需再取.transform
            nextTargetPoint = EnemyFirstPosition;
        }
        yield return null; // 每帧等待,避免阻塞主线程
    }
    isPatrolling = false; // 协程退出时重置标记
}

3. 细节修正

原Patrol方法中nextTargetPoint = EnemyFirstPosition.transform;存在冗余,因为EnemyFirstPosition本身就是Transform类型,直接赋值nextTargetPoint = EnemyFirstPosition;即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 01:04:52