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

2D网格RPG中无Collider/Rigidbody的NPC避停玩家技术问询

解决方案:无碰撞体情况下实现NPC避停玩家

针对你遇到的问题——玩家不能加Collider2D/Rigidbody2D,但需要NPC在路径被玩家阻挡时停下等待——我们可以通过网格坐标直接检测的方式实现,完全绕开物理碰撞系统。

核心思路

因为你的游戏是网格移动,每个格子对应1单位的坐标,所以我们可以直接通过取整玩家和NPC的位置,判断两者是否处于同一网格(或NPC的路径网格),以此替代物理碰撞检测。

修改后的完整代码

1. 更新NpcMovementController.cs

我们将NPC的多步移动拆分为逐格移动,每移动前检查下一格是否被玩家占据,若被阻挡则停下等待玩家离开:

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

public class NpcMovementController : TileMovementController
{
    [System.Serializable]
    public struct MoveStep
    {
        public Direction direction;
        public int steps;
        public MoveStep(Direction direction, int steps)
        {
            this.direction = direction;
            this.steps = steps;
        }
    }

    public List<MoveStep> path = new List<MoveStep>();
    public float moveSpeed = 2.0f;
    public PlayerMovementController player; // 在Inspector中拖入玩家对象

    private int currentPathIndex = 0;
    private int remainingStepsInCurrentMove = 0;
    private bool waiting = false;

    void Start()
    {
        newPosition = transform.position;
        if (path.Count > 0)
        {
            remainingStepsInCurrentMove = path[currentPathIndex].steps;
        }
    }

    private void FixedUpdate()
    {
        if (path.Count == 0 || waiting)
            return;

        // 到达当前格子后,准备下一步移动
        if (Vector3.Distance(transform.position, newPosition) < 0.05f)
        {
            transform.position = newPosition;

            // 处理当前移动步骤的剩余步数
            if (remainingStepsInCurrentMove > 0)
            {
                remainingStepsInCurrentMove--;
                PrepareNextMove();
            }
            else
            {
                // 切换到下一个路径节点
                currentPathIndex = (currentPathIndex + 1) % path.Count;
                remainingStepsInCurrentMove = path[currentPathIndex].steps;
                PrepareNextMove();
            }
        }
        else
        {
            // 移动到目标格子
            transform.position = Vector3.MoveTowards(transform.position, newPosition, Time.deltaTime * moveSpeed);
        }
    }

    // 准备下一步移动,检查是否被玩家阻挡
    private void PrepareNextMove()
    {
        Vector3 nextGridPos = move(path[currentPathIndex].direction, 1);
        if (IsGridOccupied(nextGridPos))
        {
            waiting = true;
            StartCoroutine(WaitForPlayerToLeave(nextGridPos));
            return;
        }
        newPosition = nextGridPos;
    }

    // 检查目标网格是否被玩家占据
    private bool IsGridOccupied(Vector3 gridPos)
    {
        Vector2Int targetGrid = new Vector2Int(Mathf.RoundToInt(gridPos.x), Mathf.RoundToInt(gridPos.y));
        Vector2Int playerGrid = new Vector2Int(Mathf.RoundToInt(player.transform.position.x), Mathf.RoundToInt(player.transform.position.y));
        return targetGrid == playerGrid;
    }

    // 等待玩家离开目标网格
    IEnumerator WaitForPlayerToLeave(Vector3 targetGridPos)
    {
        Vector2Int targetGrid = new Vector2Int(Mathf.RoundToInt(targetGridPos.x), Mathf.RoundToInt(targetGridPos.y));
        while (true)
        {
            Vector2Int playerGrid = new Vector2Int(Mathf.RoundToInt(player.transform.position.x), Mathf.RoundToInt(player.transform.position.y));
            if (playerGrid != targetGrid)
            {
                break;
            }
            yield return null; // 每帧检查一次
        }
        waiting = false;
        newPosition = targetGridPos; // 玩家离开后继续移动
    }

    // 重写move方法,确保每次只移动1格
    protected override Vector3 move(Direction dir, int steps)
    {
        Vector3 pos = transform.position;
        switch(dir)
        {
            case Direction.Left: pos += new Vector3(-steps, 0, 0); break;
            case Direction.Right: pos += new Vector3(steps, 0, 0); break;
            case Direction.Up: pos += new Vector3(0, steps, 0); break;
            case Direction.Down: pos += new Vector3(0, -steps, 0); break;
        }
        return pos;
    }
}

2. 无需修改PlayerMovementController.cs和TileMovementController.cs

你的玩家移动逻辑保持原样,不需要添加任何碰撞体或刚体组件。

使用步骤

  1. 在Unity编辑器中,将玩家对象拖到NPC的player字段中。
  2. 给NPC设置路径:添加4个MoveStep,分别是Down(4)、Left(2)、Up(4)、Right(2),实现循环移动。
  3. 运行游戏:当玩家站在NPC即将移动的格子上时,NPC会在玩家前方停下;玩家离开该格子后,NPC会继续按路径移动。

方案优势

  • 完全避开物理组件,不会影响玩家的移动脚本。
  • 逐格检测确保NPC精准停在玩家前方,符合需求。
  • 保留了原有的固定路径循环逻辑,改动最小。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 07:08:43