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
你的玩家移动逻辑保持原样,不需要添加任何碰撞体或刚体组件。
使用步骤
- 在Unity编辑器中,将玩家对象拖到NPC的
player字段中。 - 给NPC设置路径:添加4个
MoveStep,分别是Down(4)、Left(2)、Up(4)、Right(2),实现循环移动。 - 运行游戏:当玩家站在NPC即将移动的格子上时,NPC会在玩家前方停下;玩家离开该格子后,NPC会继续按路径移动。
方案优势
- 完全避开物理组件,不会影响玩家的移动脚本。
- 逐格检测确保NPC精准停在玩家前方,符合需求。
- 保留了原有的固定路径循环逻辑,改动最小。
内容的提问来源于stack exchange,提问作者user1499479
相关产品推荐
相关产品推荐

