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

Unity玩家碰撞墙体后无限反弹问题求助及代码排查

Unity玩家碰撞墙体后反弹飞离问题解决

问题描述

Player与Wall碰撞时会被弹开,甚至在墙体间来回反弹最终飞到虚空。需求是:玩家按下按键后,若下一步会碰撞Wall,则该方向移动无效;若下一步无碰撞,则正常移动。

相关配置

  • 标签设置:Player和Walls分别设置标签Player、Wall
  • Collider配置:
    None physical Material
    Is Trigger - unchecked
    Radius - 0.5
    
  • Rigidbody配置:
    Mass - 1
    Drag - 0 
    AngularDrag = 0.05
    Use Gravity = unchecked
    Is Kinematic = unchecked
    

当前PlayerController代码

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

public class PlayerController : MonoBehaviour {

    public bool smoothTransition = false;
    public float transitionSpeed = 10f;
    public float transitionRotationSpeed = 500f;

    Vector3 targetGridPos;
    Vector3 prevTargetGridPos;
    Vector3 targetRotation;

    private void Start()
    {
        targetGridPos = Vector3Int.RoundToInt(transform.position);
        Collider collider = gameObject.AddComponent<BoxCollider>();
        collider.isTrigger = false;
    }

    private void FixedUpdate()
    {
        MovePlayer();
    }

    void MovePlayer()
    {
        if (true)
        {
            prevTargetGridPos = targetGridPos;

            Vector3 targetPosition = targetGridPos;

            if (targetRotation.y > 270f && targetRotation.y < 361f) targetRotation.y = 0f;
            if (targetRotation.y < 0f) targetRotation.y = 270f;

            if (!smoothTransition)
            {
                transform.position = targetPosition;
                transform.rotation = Quaternion.Euler(targetRotation);
            }
            else
            {
                transform.position = Vector3.MoveTowards(transform.position, targetPosition, Time.deltaTime * transitionSpeed);
                transform.rotation = Quaternion.RotateTowards(transform.rotation, Quaternion.Euler(targetRotation), Time.deltaTime * transitionRotationSpeed);
            }

            // Check for collisions with walls
            Collider[] hitColliders = Physics.OverlapSphere(transform.position, 0.4f);
            foreach (Collider hitCollider in hitColliders)
            {
                Debug.Log("Collided with " + hitCollider.gameObject.name);

                if (hitCollider.gameObject.CompareTag("Wall"))
                {
                    targetGridPos = prevTargetGridPos; // move the player back to their previous position
                    break;
                }
            }
            Vector3 nextStep = targetGridPos + 2 * transform.forward;
            Collider[] nextStepHitColliders = Physics.OverlapSphere(nextStep, 0.4f);
            foreach (Collider nextStepHitCollider in nextStepHitColliders)
            {
                if (nextStepHitCollider.gameObject.CompareTag("Wall"))
                {
                    return; // don't update targetGridPos if the next step will collide with a wall
                }
            }
            targetGridPos = nextStep;
        }

        else
        {
            targetGridPos = prevTargetGridPos;
        }
    }

    void OnCollisionEnter(Collision collision)
    {
        // Check if the collision is with a wall
        if (collision.gameObject.tag == "Wall")
        {
            // Move the player back to their previous position
            targetGridPos = prevTargetGridPos;
        }
    }


    public void RotateLeft() { if (AtRest) targetRotation -= Vector3.up * 90f; }
    public void RotateRight() { if (AtRest) targetRotation += Vector3.up * 90f; }
    public void MoveForward() { if (AtRest) targetGridPos += 2 * transform.forward; }
    public void MoveBackward() { if (AtRest) targetGridPos -= 2 * transform.forward; }
    public void MoveLeft() { if (AtRest) targetGridPos -= transform.right; }
    public void MoveRight() { if (AtRest) targetGridPos += transform.right; }


    bool AtRest
    {
        get
        {
            if ((Vector3.Distance(transform.position, targetGridPos) < 0.05f) &&
                (Vector3.Distance(transform.eulerAngles, targetRotation) < 0.05f))
                return true;
            else
                return false;
        }
    }
}

解决方案

问题核心是同时使用物理引擎驱动的Rigidbody和手动设置transform位置,两者冲突导致物理引擎产生异常推力,引发反弹飞离。结合需求(预判碰撞,禁止无效移动),按以下步骤修复:

1. 调整Rigidbody设置

将Rigidbody的Is Kinematic设为checked,让物理引擎不再自动处理玩家移动,完全由代码控制,避免物理冲突。

2. 重构碰撞预判逻辑

把碰撞检查移到修改targetGridPos之前,在触发移动操作时就预判下一步是否会撞墙,只有无碰撞时才允许移动。同时移除碰撞后的补救代码,因为我们要提前阻止碰撞发生。

3. 修改后的关键代码

替换原有移动方法

public void MoveForward() 
{ 
    if (AtRest) 
    {
        Vector3 nextPos = targetGridPos + 2 * transform.forward;
        if (!IsPositionCollidingWithWall(nextPos))
        {
            targetGridPos = nextPos;
        }
    }
}

public void MoveBackward() 
{ 
    if (AtRest) 
    {
        Vector3 nextPos = targetGridPos - 2 * transform.forward;
        if (!IsPositionCollidingWithWall(nextPos))
        {
            targetGridPos = nextPos;
        }
    }
}

public void MoveLeft() 
{ 
    if (AtRest) 
    {
        Vector3 nextPos = targetGridPos - transform.right;
        if (!IsPositionCollidingWithWall(nextPos))
        {
            targetGridPos = nextPos;
        }
    }
}

public void MoveRight() 
{ 
    if (AtRest) 
    {
        Vector3 nextPos = targetGridPos + transform.right;
        if (!IsPositionCollidingWithWall(nextPos))
        {
            targetGridPos = nextPos;
        }
    }
}

新增碰撞预判辅助方法

private bool IsPositionCollidingWithWall(Vector3 position)
{
    Collider[] hitColliders = Physics.OverlapSphere(position, 0.4f);
    foreach (Collider hit in hitColliders)
    {
        if (hit.CompareTag("Wall"))
        {
            return true;
        }
    }
    return false;
}

简化MovePlayer方法

删除冗余的碰撞修正代码,只保留平滑移动逻辑:

void MovePlayer()
{
    prevTargetGridPos = targetGridPos;

    Vector3 targetPosition = targetGridPos;

    // 处理旋转循环
    if (targetRotation.y > 270f && targetRotation.y < 361f) targetRotation.y = 0f;
    if (targetRotation.y < 0f) targetRotation.y = 270f;

    if (!smoothTransition)
    {
        transform.position = targetPosition;
        transform.rotation = Quaternion.Euler(targetRotation);
    }
    else
    {
        transform.position = Vector3.MoveTowards(transform.position, targetPosition, Time.deltaTime * transitionSpeed);
        transform.rotation = Quaternion.RotateTowards(transform.rotation, Quaternion.Euler(targetRotation), Time.deltaTime * transitionRotationSpeed);
    }
}

删除冗余代码

移除OnCollisionEnter方法,以及MovePlayer中原有的碰撞检查代码块。

修改后,玩家只会在确认下一步无碰撞时才移动,彻底避免碰撞反弹问题,完全符合需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 13:59:59