Unity3D 自制摄像机不穿墙/不穿物体(射线cast方法)

本文详细介绍Unity中防止摄像机穿墙的多种方法,包括Raycast、SphereCast和LayerMask的使用,有效解决游戏开发中常见的碰撞检测问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

墙和物体都要加上collider(碰撞体),一般注意以下几点:

  • 墙加mesh collider,在Convex上打勾
  • 物体加box collider
  • 加刚体(rigidbody)的物体不要选Is Kenimatic

基础 最简单的Raycast方法

void Update()
{

    RaycastHit hitInfo;
    Vector3 fwd = Camera.main.transform.TransformDirection(Vector3.forward);
    if (Physics.Raycast(Camera.main.transform.position, fwd, out hitInfo, 0.5f))
    {
        float dis = hitInfo.distance;
        Vector3 correction = Vector3.Normalize(Camera.main.transform.TransformDirection(Vector3.back)) * dis;
        Camera.main.transform.position += correction;
    }
}

 上面的代码,在撞墙时会有抖动的效果,如果要平滑后退,可以把

Vector3.Normalize(Camera.main.transform.TransformDirection(Vector3.back)) * dis;

改为:

Vector3.Normalize(Camera.main.transform.TransformDirection(Vector3.back)) * dis * Time.deltaTime;

但是,这样可能会出现“移动物体过快导致穿墙”的问题。

关于 TransformDirection 参考资料: Unity TransformPoint、InverseTransformPoint、TransformDirection 

Vector3 worldPosition = transform.TransformPoint ( Vector3 relativePosition ) 
        将相对 “当前游戏对象” 的坐标转化为基于世界坐标系的坐标
Vector3 relativePosition = transform.InverseTransformPoint ( Vector3 worldPosition ) 
        将世界坐标转化为相对 “当前游戏对象” 的基于世界坐标系的坐标
Vector3 worldDirection = Transform.TransformDirection ( Vector3 relativeDirection ) 
        将相对 “当前游戏对象” 的方向转化为基于世界坐标系的方向(不受父对象缩放的影响)

进阶1 四个方向Raycast

在上一种方法中,显然,只有一个方向(前方)能不穿墙,没有考虑如下情况:

  • 摄像机倒着走,从背后穿墙
  • 摄像机横着向左/右走,从左/右穿墙

这里用了一个比较蠢的方法,就是往前、后、左、右四个方向投射线,作四次检测。把这个检测过程封装成函数detect()。

Vector3.forward实际是一个固定值,unity 内置的vector3还有以下一些方向:

参考资料 Unity - Scripting API: Vector3

backShorthand for writing Vector3(0, 0, -1).
downShorthand for writing Vector3(0, -1, 0).
forwardShorthand for writing Vector3(0, 0, 1).
leftShorthand for writing Vector3(-1, 0, 0).
negativeInfinityShorthand for writing Vector3(float.NegativeInfinity, float.NegativeInfinity, float.NegativeInfinity).
oneShorthand for writing Vector3(1, 1, 1).
positiveInfinityShorthand for writing Vector3(float.PositiveInfinity, float.PositiveInfinity, float.PositiveInfinity).
rightShorthand for writing Vector3(1, 0, 0).
upShorthand for writing Vector3(0, 1, 0).
zeroShorthand for writing Vector3(0, 0, 0).
void detect(Vector3 dir, Vector3 inverse_dir)
{
    RaycastHit hitInfo;
    Vector3 _dir = Camera.main.transform.TransformDirection(dir);
    if (Physics.Raycast(Camera.main.transform.position, _dir, out hitInfo, 0.5f))
    {
        float dis = hitInfo.distance;
        Vector3 correction = Vector3.Normalize(Camera.main.transform.TransformDirection(inverse_dir)) * dis;
        Camera.main.transform.position += correction;
    }
}

// Update is called once per frame
void Update()
{
    detect(Vector3.forward, Vector3.back);
    detect(Vector3.back, Vector3.forward);
    detect(Vector3.left, Vector3.right);
    detect(Vector3.right, Vector3.left);
}

进阶2 SphereCast

这里,射线只有一个像素点,尽管已经覆盖四个方向,但是可能还是会有疏漏的情况。可以用Physics.SphereCast()方法。

可参考下列指南:http://www.ceeger.com/Script/Physics/Physics.SphereCast.html

void detect(Vector3 dir, Vector3 inverse_dir)
{
    RaycastHit hitInfo;
    Vector3 _dir = Camera.main.transform.TransformDirection(dir);
    if (Physics.SphereCast(Camera.main.transform.position, 0.5f, _dir, out hitInfo, 0.5f))
    {
        float dis = hitInfo.distance;
        Vector3 correction = Vector3.Normalize(Camera.main.transform.TransformDirection(inverse_dir)) * dis;
        Camera.main.transform.position += correction;
    }
}

// Update is called once per frame
void Update()
{
    detect(Vector3.forward, Vector3.back);
    detect(Vector3.back, Vector3.forward);
    detect(Vector3.left, Vector3.right);
    detect(Vector3.right, Vector3.left);
}

进阶3 层遮罩LayerMask

某些碰撞体(collider) 可能只被用作trigger,但是却受到了碰撞检测。

一个比较简单的方法:

把物体放置到 Ignore Raycast层

或者用LayerMask,以下为示例:

void detect(Vector3 dir, Vector3 inverse_dir)
{
    RaycastHit hitInfo;
    Vector3 _dir = Camera.main.transform.TransformDirection(dir);
    LayerMask _ignoreLayer = 1 << LayerMask.NameToLayer("mylayer");
    if (Physics.Raycast(Camera.main.transform.position, _dir, out hitInfo, 0.5f, _ignoreLayer))
    {
        float dis = hitInfo.distance;
        Vector3 correction = Vector3.Normalize(Camera.main.transform.TransformDirection(inverse_dir)) * dis;
        Camera.main.transform.position += correction;
    }
}

更详细资料可参考:U3D开发学习之路--RayCast中layerMask的使用

### Unity3D 中角色移动时防止穿过墙壁的解决方案 在 Unity3D 开发过程中,确保角色穿过墙壁的关键在于正确配置物理组件以及合理编写移动逻辑。 #### 使用 Rigidbody 组件控制移动 为了使角色能够响应物理引擎并正确处理碰撞,应当为角色添加 `Rigidbody` 组件,并将其更新方法放置于 `FixedUpdate()` 函数内执行。这能保证每次物理计算周期都能同步位置变化,从而有效减少意外穿越障碍物的可能性[^4]。 ```csharp void FixedUpdate() { float moveHorizontal = Input.GetAxis("Horizontal"); float moveVertical = Input.GetAxis("Vertical"); Vector3 movement = new Vector3(moveHorizontal, 0.0f, moveVertical); rb.AddForce(movement * speed); } ``` #### 配置合适的 Collider 和 Trigger 设置 对于想要阻止进入的空间区域(如墙体),应为其分配适当的碰撞器 (Collider),比如 BoxCollider 或 MeshCollider。同时注意调整这些碰撞器的位置与大小使之贴合实际几何形状,避免出现间隙导致误触发穿透现象[^1]。 另外,在某些情况下可能还需要考虑是否启用了触发器功能 (`isTrigger`) ,当设定了两个物体间的交互方式为触发型而非阻挡型,则需额外编程来管理此类事件的发生条件。 #### 考虑 CharacterController 的应用 针对特定类型的玩家控制器,Unity 提供了专门用于人物导航的 `CharacterController` 类。相较于常规刚体而言,它提供了更高级别的抽象接口来进行平滑且自然的人形代理运动仿真。然而需要注意的是,默认状态下该组件可能会与其他非静态实体产生必要的干涉效应,因此建议依据项目需求权衡选用。 如果确实遇到因 `CharacterController` 导致的问题,尝试移除或替换为此种组件可能是解决问题的有效途径之一。 #### 处理摄像机视角下的视觉遮挡问题 除了上述措施外,还需关注由摄像机视场角引起的潜在干扰因素——即所谓的“穿帮”。通过引入射线投射技术可以在一定程度上缓解这类状况带来的良影响。例如: - **动态调节相机距主体间距**:基于当前视野范围内是否存在阻碍物件实时修改二者间距离; - **渐隐/显现目标对象**:利用 alpha 渠道参数调控被摄取画面中部分元素可见程度以维持整体连贯性[^2]。 综上所述,综合运用以上策略可显著提升游戏中角色行动的真实感及其互动表现力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

iteapoy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值