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

Unity中解决可推动刚体卡在不可推动刚体的问题

推箱子场景玩家、箱子与障碍物卡死问题解决方案

问题根源

  1. 直接修改transform.position移动玩家,绕过了Unity物理引擎的碰撞响应逻辑,导致碰撞时无法正确触发物理反馈
  2. IsWalkable方法仅判断目标点是否存在碰撞,未区分可推动箱子和静态障碍物,当箱子被障碍物挡住时,玩家仍会尝试移动,三者互相挤压卡死
  3. 原代码中Move协程定义在Update方法内部,属于语法错误,会导致运行异常

解决方案步骤

1. 分层管理碰撞对象

  • 在Unity编辑器新建Pushable层,把所有可移动箱子设置到该层
  • 原collisionsLayer仅保留静态障碍物(比如碰撞瓦片地图),在PlayerController面板新增pushableLayer变量并赋值为Pushable层

2. 修正代码逻辑与结构

将Move协程移到Update方法外,改用刚体移动替代直接修改transform,同时新增推箱子的判断逻辑:

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

public class PlayerController : MonoBehaviour
{
    public float moveSpeed;
    public LayerMask collisionsLayer;
    public LayerMask pushableLayer; // 新增:可推动物体层

    private bool isMoving;
    private Vector2 input;
    private Animator animator;
    private Rigidbody2D rb; // 新增:引用玩家刚体

    private void Awake()
    {
        animator = GetComponent<Animator>();
        rb = GetComponent<Rigidbody2D>(); // 获取玩家刚体组件
    }

    private void Update()
    {
        if (!isMoving)
        {
            input.x = Input.GetAxisRaw("Horizontal");
            input.y = Input.GetAxisRaw("Vertical");

            // 禁止斜向移动
            if (input.x != 0) input.y = 0;

            if (input != Vector2.zero)
            {
                // 更新动画参数
                animator.SetFloat("moveX", input.x);
                animator.SetFloat("moveY", input.y);

                Vector3 playerTarget = transform.position + new Vector3(input.x, input.y, 0);
                // 检查目标点是否有可推动箱子
                Collider2D hitBox = Physics2D.OverlapCircle(playerTarget, 0.1f, pushableLayer);

                if (hitBox != null)
                {
                    // 计算箱子的目标位置
                    Vector3 boxTarget = hitBox.transform.position + new Vector3(input.x, input.y, 0);
                    // 检查箱子目标位置是否无障碍物
                    if (IsWalkable(boxTarget))
                    {
                        // 同时移动玩家和箱子
                        StartCoroutine(MoveWithPush(playerTarget, hitBox.transform, boxTarget));
                    }
                    // 箱子被挡住时,不允许玩家移动
                }
                else if (IsWalkable(playerTarget))
                {
                    // 无箱子,正常移动玩家
                    StartCoroutine(Move(playerTarget));
                }
            }
        }

        // 更新移动状态动画
        animator.SetBool("isMoving", isMoving);
    }

    // 普通移动协程
    IEnumerator Move(Vector3 targetPos)
    {
        isMoving = true;

        while ((targetPos - transform.position).sqrMagnitude > Mathf.Epsilon)
        {
            // 使用刚体移动,让物理引擎处理碰撞
            rb.MovePosition(Vector3.MoveTowards(transform.position, targetPos, moveSpeed * Time.deltaTime));
            yield return null;
        }

        rb.MovePosition(targetPos);
        isMoving = false;
    }

    // 带推箱子的移动协程
    IEnumerator MoveWithPush(Vector3 playerTarget, Transform boxTrans, Vector3 boxTarget)
    {
        isMoving = true;

        while ((playerTarget - transform.position).sqrMagnitude > Mathf.Epsilon)
        {
            rb.MovePosition(Vector3.MoveTowards(transform.position, playerTarget, moveSpeed * Time.deltaTime));
            boxTrans.position = Vector3.MoveTowards(boxTrans.position, boxTarget, moveSpeed * Time.deltaTime);
            yield return null;
        }

        rb.MovePosition(playerTarget);
        boxTrans.position = boxTarget;
        isMoving = false;
    }

    // 检查目标位置是否无静态障碍物
    private bool IsWalkable(Vector3 targetPos)
    {
        return Physics2D.OverlapCircle(targetPos, 0.1f, collisionsLayer) == null;
    }
}

3. 调整刚体配置

  • 玩家刚体:保持Dynamic类型,碰撞检测模式改为Continuous,避免快速移动穿透
  • 箱子刚体:设置为Dynamic类型,碰撞检测模式改为Continuous,勾选Freeze Rotation Z防止旋转
  • 静态障碍物(碰撞瓦片地图):保持Static类型不变

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 22:55:18