Unity 3D推箱子游戏:修复箱子无法与障碍物碰撞的问题
Unity推箱子(Sokoban)游戏:修复箱子无法与障碍物碰撞的问题
问题根源
- 箱子勾选了
Is Trigger,导致物理引擎跳过碰撞阻挡逻辑,且原BoxMovement脚本未在移动前检测目标位置的障碍物。 - 原
PlayerMovement逻辑中,玩家推箱子时未检查箱子前方是否有障碍物,直接允许移动,导致箱子被推入障碍物区域。
修复步骤
1. Unity编辑器设置调整
- 取消箱子对象的
Is Trigger勾选(保留Box Collider组件)。 - 给箱子添加
Rigidbody组件并勾选Kinematic(避免物理模拟干扰网格移动,同时保留Collider用于检测)。 - 确保所有障碍物对象都带有
Box Collider,静态障碍物可勾选Static选项优化性能。
2. 修改PlayerMovement脚本
核心优化:推箱子前先检查箱子移动方向是否有障碍物,只有箱子能移动时才允许玩家移动。
using System.Collections; using System.Collections.Generic; using UnityEngine; public class PlayerMovement : MonoBehaviour { [SerializeField] float moveSpeed = 0.25f; [SerializeField] float rayLength = 1.1f; // 适配1单位网格的检测长度 [SerializeField] LayerMask obstacleLayer; // 在Inspector中选择障碍物所在层 Vector3 targetPosition; bool moving; void Update() { if (moving) { transform.position += (targetPosition - transform.position).normalized * moveSpeed * Time.deltaTime; if (Vector3.Distance(transform.position, targetPosition) < 0.05f) { transform.position = targetPosition; moving = false; } return; } if (Input.GetKeyDown(KeyCode.W)) // 改用GetKeyDown避免持续触发 { Move(Vector3.forward); } else if (Input.GetKeyDown(KeyCode.S)) { Move(Vector3.back); } else if (Input.GetKeyDown(KeyCode.A)) { Move(Vector3.left); } else if (Input.GetKeyDown(KeyCode.D)) { Move(Vector3.right); } } void Move(Vector3 direction) { RaycastHit hit; if (Physics.Raycast(transform.position, direction, out hit, rayLength)) { if (hit.collider.CompareTag("Box")) { // 检测箱子前方是否有障碍物 if (!Physics.Raycast(hit.transform.position, direction, rayLength, obstacleLayer)) { // 触发箱子移动 hit.GetComponent<BoxMovement>().SetTargetPosition(hit.transform.position + direction); // 玩家移动 targetPosition = transform.position + direction; moving = true; } return; } // 碰到障碍物,禁止移动 return; } // 无阻碍,玩家移动 targetPosition = transform.position + direction; moving = true; } }
3. 修改BoxMovement脚本
移除独立的输入监听,改为由玩家脚本触发移动,逻辑更简洁可控:
using System.Collections; using System.Collections.Generic; using UnityEngine; public class BoxMovement : MonoBehaviour { [SerializeField] float moveSpeed = 0.25f; Vector3 targetPosition; bool moving; void Update() { if (moving) { transform.position += (targetPosition - transform.position).normalized * moveSpeed * Time.deltaTime; if (Vector3.Distance(transform.position, targetPosition) < 0.05f) { transform.position = targetPosition; moving = false; } } } // 供PlayerMovement调用的移动触发方法 public void SetTargetPosition(Vector3 newTarget) { targetPosition = newTarget; moving = true; } }
额外优化建议
- 使用
LayerMask替代标签检测,射线检测时只针对障碍物层,提升性能和准确性。 - 将
Input.GetKey改为Input.GetKeyDown,避免按住按键时持续触发移动,符合推箱子的操作逻辑。 - 确保所有对象的位置都对齐到网格(比如
(1,0,1)、(2,0,1)等),避免移动时出现偏移。
内容的提问来源于stack exchange,提问作者Sebastian Barragan
相关产品推荐
相关产品推荐

