You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Unity 2D玩家移动脚本碰撞检测失效及物体推动功能异常求助

解决Unity 2D玩家碰撞失效与推箱子机制问题

嘿,我看了你在首个Unity 2D项目里遇到的问题——玩家撞不上墙、推不了箱子,还有OnDrawGizmos没反应,这几个坑我刚学Unity的时候也踩过,下面逐个给你拆解修复方案:


1. 碰撞失效的核心原因:绕过了物理系统

你现在是直接修改transform.position来移动玩家,这种方式会完全跳过Unity的2D物理引擎,自然不会触发碰撞检测。要让碰撞生效,必须通过Rigidbody2D来处理移动逻辑。

快速修复步骤:

  • 调整玩家Rigidbody2D设置:把玩家的Rigidbody2D的Body Type改成Dynamic,同时勾选Constraints下的Freeze Rotation Z(防止玩家移动时旋转)。
  • 替换移动方式:放弃直接改Transform,改用Rigidbody2D.MovePosition方法,让物理引擎接管移动过程。

2. 推箱子机制的实现思路

要实现类似Sokoban的推箱子,核心是在玩家移动前检测路径上的可推动物体,如果存在且该物体的移动方向没有障碍物,就同步推动它。

关键准备与逻辑:

  • 给可推动的箱子添加Rigidbody2D(设为Dynamic,同样冻结Z轴旋转)和Box Collider 2D,并给箱子打个Pushable标签,方便后续检测。
  • Physics2D.OverlapBox检测玩家移动目标点是否有箱子,再检测箱子的目标点是否被墙体或其他箱子阻挡。

3. OnDrawGizmos不生效的修复

OnDrawGizmos只在Scene视图中显示,而且需要确保Scene窗口顶部的Gizmos按钮是开启状态。另外你的moveToPosition只有在有输入时才会赋值,无输入时是默认的Vector3.zero,所以Gizmos可能画在原点了。可以初始化moveToPosition为玩家初始位置,或者在方法里加个非空判断。


修改后的完整代码

using UnityEngine;
using System.Collections;

public class PlayerMovement : MonoBehaviour
{
    public Tilemap wall;
    public float moveSpeed = 2f;
    private Rigidbody2D rb;
    private bool isWalking;
    private Vector3 moveToPosition;
    // 推箱子检测的范围(根据你的玩家大小调整)
    private Vector2 pushCheckSize = new Vector2(0.8f, 0.8f);

    private void Start()
    {
        rb = GetComponent<Rigidbody2D>();
        // 初始化moveToPosition为玩家当前位置,避免Gizmos初始画在原点
        moveToPosition = transform.position;
    }

    private void Update()
    {
        if (!isWalking)
        {
            // 获取输入并转为单位向量,防止斜移时速度过快
            Vector2 input = new Vector2(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical")).normalized;
            if (input != Vector2.zero)
            {
                moveToPosition = transform.position + new Vector3(input.x, input.y, 0);
                // 先检测目标位置是否是墙体
                Vector3Int wallTilePos = wall.WorldToCell(moveToPosition - new Vector3(0, 0.5f, 0));
                if (wall.GetTile(wallTilePos) == null)
                {
                    // 检测目标位置是否有可推动的箱子
                    Collider2D pushableObj = Physics2D.OverlapBox(moveToPosition, pushCheckSize, 0, LayerMask.GetMask("Pushable"));
                    if (pushableObj == null)
                    {
                        // 没有箱子,直接移动玩家
                        StartCoroutine(MovePlayer(moveToPosition));
                    }
                    else
                    {
                        // 有箱子,检测箱子的移动目标是否有障碍物
                        Vector3 boxTargetPos = pushableObj.transform.position + new Vector3(input.x, input.y, 0);
                        Vector3Int boxWallTile = wall.WorldToCell(boxTargetPos - new Vector3(0, 0.5f, 0));
                        // 箱子目标位置不是墙体,且没有其他可推动物体
                        if (wall.GetTile(boxWallTile) == null && !Physics2D.OverlapBox(boxTargetPos, pushCheckSize, 0, LayerMask.GetMask("Pushable")))
                        {
                            // 同时移动玩家和箱子
                            StartCoroutine(MovePlayerAndBox(moveToPosition, boxTargetPos, pushableObj.GetComponent<Rigidbody2D>()));
                        }
                    }
                }
            }
        }
    }

    // 玩家单独移动的协程
    private IEnumerator MovePlayer(Vector3 targetPos)
    {
        isWalking = true;
        while (Vector3.Distance(rb.position, targetPos) > Mathf.Epsilon)
        {
            // 使用Rigidbody2D移动,触发物理碰撞
            rb.MovePosition(Vector3.MoveTowards(rb.position, targetPos, moveSpeed * Time.deltaTime));
            yield return null;
        }
        rb.position = targetPos;
        isWalking = false;
    }

    // 玩家推动箱子的协程
    private IEnumerator MovePlayerAndBox(Vector3 playerTarget, Vector3 boxTarget, Rigidbody2D boxRb)
    {
        isWalking = true;
        while (Vector3.Distance(rb.position, playerTarget) > Mathf.Epsilon)
        {
            rb.MovePosition(Vector3.MoveTowards(rb.position, playerTarget, moveSpeed * Time.deltaTime));
            boxRb.MovePosition(Vector3.MoveTowards(boxRb.position, boxTarget, moveSpeed * Time.deltaTime));
            yield return null;
        }
        rb.position = playerTarget;
        boxRb.position = boxTarget;
        isWalking = false;
    }

    private void OnDrawGizmos()
    {
        Gizmos.color = Color.red;
        // 绘制玩家移动目标点的检测球
        Gizmos.DrawSphere(moveToPosition - new Vector3(0, 0.5f, 0), 0.2f);
        // 额外绘制推箱子的检测范围,方便调试
        Gizmos.color = Color.blue;
        Gizmos.DrawWireCube(moveToPosition, pushCheckSize);
    }
}

额外注意事项

  • 墙体的Rigidbody2D设置:把墙体的Rigidbody2D设为Static,物理引擎会优化碰撞检测,避免不必要的计算。
  • Layer分层:给玩家、墙体、箱子分别设置独立的Layer(比如Player、Wall、Pushable),这样在碰撞检测时可以用LayerMask过滤,避免误检测。
  • Tilemap碰撞验证:在Scene视图中查看墙体的Tilemap Collider 2D是否正确生成了碰撞体轮廓,确保瓦片的碰撞范围符合预期。

内容的提问来源于stack exchange,提问作者Raouf gaouer

火山引擎 最新活动