解决因碰撞被卡在墙里的问题

81 阅读3分钟

在Defold中,在同一帧内可能同时发生多次碰撞。比如,一个游戏对象撞向墙角,同时与墙面A与墙面B两个物理对象相撞,那么通过物理引擎监听器,会收到两个contact_point_response消息,分别来自于墙面A与墙面B。

最简处理方式

可以分别为两个contact_point_response消息进行单独处理,如下脚本所示:

function on_message(self, message_id, message)
    if message_id == hash("contact_point_response") then
        local new_pos = go.get_position() + message.normal * message.distance
        go.set_position(new_pos)
    end
end

在以上的代码中,如果一帧内此对象只有一次碰撞发生,那么这样处理没有任何问题。

我们可以梳理一下,当一帧内有多次碰撞发生时,会发生什么。

假设,当前对象碰撞之前位置为(100, 200, 0)

当与墙面A发生碰撞时,产生的法线为message.normal=(1,0,0),而与墙面的深入距离为message.distance=5px。那么由于与墙面A发生碰撞,游戏对象已卡在墙面里,所以需要通过脚本把游戏对象移出墙体。要移动向量就是message.normal * message.distance,由于法线表示推开碰撞对象的方向向量,所以与游戏对象相加go.get_position() + message.normal * message.distance 之后就表示处理了位置的偏移,把对象移出了墙体。

当与墙面A碰撞并修改偏移的过程中,又收到了与墙面B发生碰撞的消息,那么同时会执行上一段相同的逻辑,可能导致对象的位置移动发生抖动,看起来不平滑,甚至被卡在墙体中不能移动。

所以需要更完整的方面进行处理。

最佳处理方式

需要积累游戏对象在同一帧内的偏移向量,然后再进行移动修正。

function init()
    self.correction = vmath.vector3()
end

function update(self, dt)
    self.correction = vmath.vector3()
end

function on_message(self, message_id, message)
    if message_id == hash('contract_point_response') then
        local project = vmath.project(self.correction, message.normal * message.distance)
        if project < 1 then
            local comp = (message.distance - message.distance * project) * message.normal
            self.correction = self.correction + comp
            go.set_position(go.get_position() + comp)
        end
    end
end

其中关键的点:

  1. 使用self.correction做为每次计算的修正向量;
  2. 计算出self.correction修正向量与碰撞法线乘以深入距离message.normal * message.distance向量的标量project。标量是指向量A与投射到向量B上的长度与向量B长度的占比。
  3. project标量等于0时,相当于向量A与向量B相互垂直,不会有影响影响;
  4. project标量大于1时,向量A的长度大于向量B的长度,那么相当于已游戏对象已经把墙体击穿,那么也就没有调整位置便宜的必要了。
  5. proejct标量小于1时,向量A的长度小于向量B的长度,游戏对象刚好卡在墙体中,所以需要处理。
  6. 由于标量project是A向量投射长度与向量B的长度占比,所以message.distance - message.distance * porject就表示抵消向量A之后向量长度。再乘以法线message.normal就表示游戏对象要便宜的距离。
  7. 通过self.correction向量累加偏移向量,为同一个帧内的下一个碰撞消息做准备。
  8. 通过go.set_position(go.get_position() + comp)执行游戏对象的位置偏移。

向量补充解释

projection.png

  1. 通过vmath.project(A, B)可以计算出向量A投射到向量B上的标量。
  2. 通过vmath.length(B)可以计算向量B的长度。
  3. 标量是向量A投射到向量B上的长度l与向量B长度的占比值。

1f6bfa44ba984828a7eaa3dac156f864~tplv-73owjymdk6-jj-mark-v1_0_0_0_0_5o6Y6YeR5oqA5pyv56S-5Yy6IEAg56m66LCI6K-v5oiR_q75.webp

请大家持续监督,关注我,我将实时向你汇报进度。更希望各位精神股东在评论区给我提出宝贵的建议!