Unity下,移动撞墙后抖动的解决方案

Unity下的移动方案:

1.Rigidbody.MovePosition
2.Rigidbody.AddForce
3.Transform.Translate;
Transform.position = vector3;

目前主要分这三大类的移动方式。

1和2是物理移动方式
3是实体对象坐标的移动方式

然后说说题目,为啥会抖动呐:

public class Test : MonoBehaviour
{
    public float m_nSpeed = 0;
    void Update()
	{
        transform.Translate(Vector3.forward * m_nSpeed * Time.deltaTime);
	}
}

通常,你会把移动写成这样。
当然没有错,因为我们相信API。官方API上就是这么写的API Demo:https://docs.unity.cn/cn/current/ScriptReference/Transform.Translate.html

但是实际上移动时候,我们为了做碰撞,会在场景墙壁上挂上Collider。 主角身上也会挂上Collider和Rigidbody。

主角和场景之间通过碰撞,可以有阻挡的表现。

然后就发现了!疯狂抽搐

来分析一下原因

先引用一下大佬的链接,Unity的生命周期:

Unity 脚本生命周期流程图

FixedUpdate会先走,之后才会走到Update。
那么想一想,上面的代码 Translate把物体的坐标改变了。
那么碰撞体跟着物体进行了移动。但是在当前这一次的生命周期循环里,Update之后没有物理判断了。

那就是说,在这一帧的画面,最终渲染出来的时候。主角的碰撞体是嵌入在墙壁里面的。

那么下一帧,走了FixedUpdate进行了物理判断,发现碰撞体是嵌入的。那么按照物理规则,物理引擎把人物给弹出来保证物理正确。

于是乎,反复的移动操作,实际上就是 碰撞体嵌入,挤出,嵌入,挤出。 我们就看到了人物撞墙一直在抖动。


那么来看看解决方案:

我们要解决的问题是
在人物移动后,可以让物理系统可以在渲染前,得出正确的结果
目前我们有2个点 Update和FixedUpdate需要处理。
先得了解FixedUpdate是物理帧,走定时器,并不是一次FixedUpdate和一次Update对应。相关的资料:
https://www.cnblogs.com/murongxiaopifu/p/7683140.html

其实就很容易理解了。我们尽量不要让移动的操作同时经过Update和FixedUpdate处理,混合处理容易出现时序问题,很难查问题,因为Update的时间间隔是不稳定的。就挑一个位置来处理移动。

    [SerializeField] float m_nSpeed = 0;
	void Update()
    {
        float nDis = m_nSpeed * Time.deltaTime;
        Vector3 v3Dis = transform.forward * nDis;
        //这边是重点
        Vector3 from = transform.position;
        from.y += 0.5f;
        Vector3 to = from + v3Dis;
        to.z += 0.5f;

        RaycastHit rh;
        Debug.DrawLine(from, to, Color.black);
        if (Physics.Linecast(from, to, out rh, LayerMask.GetMask("wall")))
        {
            v3Dis = rh.point - from;
            v3Dis.z -= 0.5f;
        }
        //重点结束

        transform.Translate(v3Dis, Space.Self);
	}

解释一下这段代码:
移动速度 和 Translate 没啥可以多说的了。

主要是重点这一段,计算出当前移动距离后,并不着急去Translate移动,先按照移动距离向前发射一根射线,如果射线碰撞到了墙

那么我就可以知道,这次移动如果移动满距离必定会嵌入墙体,那么我只需要移动可以移动的最大距离,就可以贴着墙,并且不嵌入墙体。

再来想一想下一帧,射线必然是碰撞的,算出的结果,必然是0.那么下一帧,就会移动0距离。

不嵌入墙体,物理系统不会把碰撞体给挤出来。于是就不抖动了。

PS:这段代码里面的 y轴 0.5f 其实可以忽略,看自己情况而定。因为我测试时候的模型中心点在脚底。
z轴 0.5f,是因为我的模型上挂的胶囊碰撞体,半径是0.5f。


上面是一个方案,然后基于在人物移动后,可以让物理系统可以在渲染前,得出正确的结果我们可以想到,上面的方案的主要逻辑是在Update里面处理的。那我忽略Update,全部在FixedUpdate里面处理,是否可行。
开头的两个函数:
1.Rigidbody.MovePosition
2.Rigidbody.AddForce
可以在FixedUpdate里面解决这个问题。

首先Rigidbody.MovePosition官方文档里面有说道:

如果在刚体上启用了刚体插值,则调用 Rigidbody.MovePosition 会导致在渲染的任意中间帧中的两个位置之间平滑过渡。若要在每个 FixedUpdate 中连续移动刚体,则应使用该方法。
官方文档链接:https://docs.unity.cn/cn/current/ScriptReference/Rigidbody.MovePosition.html

None 不使用插值。
Interpolate 插值将始终滞后一点,但比外推更流畅。
Extrapolate 外推将根据当前速度预测刚体的位置。
官方文档链接:
https://docs.unity.cn/cn/current/ScriptReference/RigidbodyInterpolation.html

官方还写了Demo,我就偷懒不写了。


上面这个方法,我自己是觉得心里空唠唠的,不是很靠谱。主要是因为我们的游戏逻辑很多是在Update里面运行,很多人在做移动的时候,可以算出最终的移动距离和坐标,需要经过复杂的计算。FixedUpdate下,性能 以及 Update的数据交互,可能都会很难以去处理。
如果只是单纯的移动,也许可以尝试,但是FixedUpdate的方案下,我更喜欢用Rigidbody.AddForce的方案去替换上面的MovePosition的方案,因为AddForce不需要使用Rigidbody的插值功能。

    [SerializeField] float m_nSpeed = 0;

	private void FixedUpdate()
	{
        transform.GetComponent<Rigidbody>().AddForce(Vector3.forward * m_nSpeed * Time.fixedDeltaTime);
	}

很简单的代码,FixedUpdate里用力去推刚体让他前进。
效果反而更好,用力会有加速度的效果。
看上去反而更加自然了。
并且在FixedUpdate里操作后,物理引擎会在C#代码结束后处理碰撞,在渲染前,实际位置已经是被挤出墙壁的位置。所以渲染时候完全不会抖动。

但是用力的方案,出现的加速度问题,可能是需要关注的一个点。
因为停下来的时候,可能会有惯性,和动画不匹配了,看上去就是人滑了一段距离。
那么可以用
Rigidbody.velocity 速度来处理。

AddForce后,会直接表现在velocity上面
我们F12查看这个API,可以发现

// 摘要:
// The velocity vector of the rigidbody. It represents the rate of change of Rigidbody
// position.
public Vector3 velocity { get; set; }

Rigidbody.velocity这个东西包含了Set
那么:

Rigidbody.velocity = Vector3.zero;

停止AddForce后,所有方向的速度全部归零,刚体就会立即停止.


最后, 如果你发现屏幕还在抖动,记得检查一下你的摄像机脚本。

大多第一人称,第三人称,都会把摄像机绑在人物节点下。
如果有摄像机相关的逻辑,记得把操作放到LateUpdate下。
原因其实和 FixedUpdate一样,是时序问题。
特别是LookAt。本篇就不多赘述了。


程序学无止尽。
欢迎大家沟通,有啥不明确的,或者不对的,也可以和我私聊
我的QQ 334524067 神一般的狄狄

  • 6
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值