Unity 3D基于旋转的网格玩家移动边界限制与对齐优化问题
Unity网格系统玩家移动优化:边界限制与无硬编码对齐
核心问题解决思路
1. 网格边界限制实现
不再直接操作世界坐标移动,而是基于当前网格坐标计算目标位置:
- 将玩家旋转后的前方向量转换为网格坐标系的偏移(利用方向向量的X/Z分量取整,得到网格轴上的±1/0偏移)
- 计算目标网格坐标后,判断是否在
xRange(网格宽度)和yRange(网格高度)的合法范围内(默认网格坐标从0开始) - 仅对合法目标位置执行移动逻辑
2. 避免硬编码旋转角度
通过旋转后的方向向量提取网格偏移,替代角度判断:
- 玩家旋转步长为90度,旋转后的
forward向量在网格X/Z轴上的分量必然是±1或0 - 直接对方向向量的X/Z分量取整,即可得到网格坐标的偏移量,无需硬编码判断0/90/180/270等角度值
修改后的完整Movement脚本
public class Movement : MonoBehaviour { bool Rotating = false; bool Moving = false; public Rigidbody rb; private Quaternion endRotation; [SerializeField] private AnimationCurve curve; private Quaternion startRotation; private Vector3 startPosition; public GameObject Grid; private Vector3 endPosition; public int xRange; public int yRange; public float posX; // 当前网格X坐标(0到xRange-1) public float posY; // 当前网格Y坐标(0到yRange-1) public float desiredDuration = 1f; private float elapsedTime; float percentageComplete = 0f; private void Awake() { rb = GetComponent<Rigidbody>(); var grid = Grid.GetComponent<GridCreation>(); xRange = grid.width; yRange = grid.height; // 初始化网格坐标(从初始世界坐标转换) posX = Mathf.Round(rb.position.x / 10); posY = Mathf.Round(rb.position.z / 10); } public void TurnRight(InputAction.CallbackContext context) { if (context.started && !Rotating && !Moving) { percentageComplete = 0; startRotation = rb.rotation; endRotation = startRotation * Quaternion.Euler(0,90,0); Rotating = true; elapsedTime = 0; } } public void TurnLeft(InputAction.CallbackContext context) { if (context.started && !Rotating && !Moving) { percentageComplete = 0; startRotation = rb.rotation; endRotation = startRotation * Quaternion.Euler(0,-90,0); Rotating = true; elapsedTime = 0; } } // 基于网格坐标的移动逻辑 public void Forward(InputAction.CallbackContext context) { if (context.started && !Rotating && !Moving) { // 计算旋转后的前方向量(仅保留X/Z轴分量) Vector3 forwardDir = rb.rotation * Vector3.forward; forwardDir.y = 0; forwardDir.Normalize(); // 转换为网格坐标偏移(因旋转步长90度,分量必为±1或0) int gridOffsetX = Mathf.RoundToInt(forwardDir.x); int gridOffsetY = Mathf.RoundToInt(forwardDir.z); // 计算目标网格坐标 float targetPosX = posX + gridOffsetX; float targetPosY = posY + gridOffsetY; // 边界检查:确保目标在网格范围内 if (targetPosX >= 0 && targetPosX < xRange && targetPosY >= 0 && targetPosY < yRange) { percentageComplete = 0; startPosition = rb.position; // 将网格坐标转换为世界坐标(每格10单位,Y固定为1) endPosition = new Vector3(targetPosX * 10, 1, targetPosY * 10); Moving = true; elapsedTime = 0; // 提前更新网格坐标(避免移动中计算误差) posX = targetPosX; posY = targetPosY; } } } private void Update() { if (Rotating) { if (percentageComplete < 1) { elapsedTime += Time.deltaTime; percentageComplete = elapsedTime / desiredDuration; rb.rotation = Quaternion.Lerp(startRotation, endRotation, curve.Evaluate(percentageComplete)); } if (percentageComplete > 1) { Rotating = false; } } if (Moving) { if (percentageComplete < 1) { elapsedTime += Time.deltaTime; percentageComplete = elapsedTime / desiredDuration; rb.position = Vector3.Lerp(startPosition, endPosition, curve.Evaluate(percentageComplete)); } if (percentageComplete > 1) { Moving = false; } } } }
关键优化点说明
- 初始网格坐标初始化:在Awake中直接从世界坐标转换,避免移动后才计算的误差
- 方向向量转网格偏移:利用旋转后的forward向量的归一化分量,自动适配所有90度旋转方向
- 提前边界检查:在移动前就判断目标网格坐标是否合法,无效输入直接忽略
- 网格坐标提前更新:移动开始时就更新posX/posY,避免移动结束后从世界坐标取整的精度问题
内容的提问来源于stack exchange,提问作者Chris W.
相关产品推荐
相关产品推荐

