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

Unity 3D推箱子游戏:修复箱子无法与障碍物碰撞的问题

Unity推箱子(Sokoban)游戏:修复箱子无法与障碍物碰撞的问题

问题根源

  1. 箱子勾选了Is Trigger,导致物理引擎跳过碰撞阻挡逻辑,且原BoxMovement脚本未在移动前检测目标位置的障碍物。
  2. 原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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 05:05:58