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

Unity玩家带碰撞体仍穿墙,网格移动遇墙反弹问题求助

解决Tilemap网格逐格移动的穿墙与碰撞反弹问题

核心问题分析

  • 带Composite Collider 2D时穿墙:大概率是Composite Collider 2D的"Used By Composite"勾选逻辑错误,或是Tilemap碰撞体设置与玩家瞬间位移的移动逻辑不匹配,导致碰撞检测未及时触发。
  • 移除Composite Collider 2D后反弹:直接修改Transform.position会触发物理引擎的碰撞响应,刚体被被动弹开,而非预期的"遇墙即停"。

具体修复步骤

1. 恢复并正确配置Composite Collider 2D

  • 给墙体Tilemap添加Tilemap Collider 2D,勾选Inspector面板中的Used By Composite选项。
  • 添加Composite Collider 2D,设置Geometry Type为Outlines(适配Tilemap规整碰撞形状),确保Is Trigger未勾选。
  • 玩家对象添加Circle Collider 2D(匹配白色圆形外观)和Rigidbody 2D,将Rigidbody 2D的Body Type设为Kinematic(完全由代码控制移动,避免物理引擎主动推动)。

2. 修改逐格移动逻辑:先检测再移动

放弃直接修改Transform.position,改为先检测目标格子是否可通行,再执行平滑移动。示例代码框架如下:

using UnityEngine;
using UnityEngine.Tilemaps;

public class PlayerMovement : MonoBehaviour
{
    public Tilemap obstacleTilemap; // 赋值墙体所在Tilemap
    public float moveTimePerGrid = 0.2f;
    private bool isMoving = false;
    private Vector3Int currentGridPos;

    void Start()
    {
        currentGridPos = obstacleTilemap.WorldToCell(transform.position);
    }

    void Update()
    {
        if (isMoving) return;

        Vector3Int targetGridPos = currentGridPos;
        if (Input.GetKeyDown(KeyCode.W)) targetGridPos += Vector3Int.up;
        else if (Input.GetKeyDown(KeyCode.S)) targetGridPos += Vector3Int.down;
        else if (Input.GetKeyDown(KeyCode.A)) targetGridPos += Vector3Int.left;
        else if (Input.GetKeyDown(KeyCode.D)) targetGridPos += Vector3Int.right;

        // 检测目标格子是否存在墙体Tile
        if (targetGridPos != currentGridPos && !obstacleTilemap.HasTile(targetGridPos))
        {
            StartCoroutine(MoveToTargetGrid(targetGridPos));
        }
    }

    IEnumerator MoveToTargetGrid(Vector3Int targetGridPos)
    {
        isMoving = true;
        // 将格子坐标转换为世界空间的中心位置
        Vector3 targetWorldPos = obstacleTilemap.CellToWorld(targetGridPos) + obstacleTilemap.cellSize / 2;
        float elapsedTime = 0f;
        Vector3 startPos = transform.position;

        while (elapsedTime < moveTimePerGrid)
        {
            transform.position = Vector3.Lerp(startPos, targetWorldPos, elapsedTime / moveTimePerGrid);
            elapsedTime += Time.deltaTime;
            yield return null;
        }

        transform.position = targetWorldPos;
        currentGridPos = targetGridPos;
        isMoving = false;
    }
}

3. 校验碰撞矩阵与Layer设置

  • 进入Edit > Project Settings > Physics 2D,确认玩家Layer与墙体Layer的碰撞矩阵处于互相碰撞状态。
  • 玩家和墙体的碰撞体均不要勾选Is Trigger(除非有特殊触发需求,此场景无需开启)。

4. 物理碰撞模式下的反弹修复(可选)

若坚持使用物理碰撞而非格子检测,需调整Rigidbody 2D参数并改用rb.MovePosition()控制移动:

  • 将玩家Rigidbody 2D的Linear Drag设为10以上,减少碰撞后的动量残留。
  • 移动前添加射线检测,示例代码片段:
public LayerMask obstacleLayer;
private Rigidbody2D rb;

void Start()
{
    rb = GetComponent<Rigidbody2D>();
}

void Update()
{
    if (isMoving) return;

    Vector2 direction = Vector2.zero;
    if (Input.GetKeyDown(KeyCode.W)) direction = Vector2.up;
    // 其余方向判断逻辑略

    float gridSize = obstacleTilemap.cellSize.x;
    // 检测前方是否有墙体
    RaycastHit2D hit = Physics2D.Raycast(transform.position, direction, gridSize, obstacleLayer);
    if (hit.collider == null)
    {
        StartCoroutine(MoveWithPhysics(direction * gridSize));
    }
}

IEnumerator MoveWithPhysics(Vector2 moveDistance)
{
    isMoving = true;
    Vector2 targetPos = rb.position + moveDistance;
    float elapsedTime = 0f;

    while (elapsedTime < moveTimePerGrid)
    {
        rb.MovePosition(Vector2.Lerp(rb.position, targetPos, elapsedTime / moveTimePerGrid));
        elapsedTime += Time.deltaTime;
        yield return null;
    }

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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 21:54:55