在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
其中关键的点:
- 使用
self.correction做为每次计算的修正向量; - 计算出
self.correction修正向量与碰撞法线乘以深入距离message.normal * message.distance向量的标量project。标量是指向量A与投射到向量B上的长度与向量B长度的占比。 - 当
project标量等于0时,相当于向量A与向量B相互垂直,不会有影响影响; - 当
project标量大于1时,向量A的长度大于向量B的长度,那么相当于已游戏对象已经把墙体击穿,那么也就没有调整位置便宜的必要了。 - 当
proejct标量小于1时,向量A的长度小于向量B的长度,游戏对象刚好卡在墙体中,所以需要处理。 - 由于标量
project是A向量投射长度与向量B的长度占比,所以message.distance - message.distance * porject就表示抵消向量A之后向量长度。再乘以法线message.normal就表示游戏对象要便宜的距离。 - 通过
self.correction向量累加偏移向量,为同一个帧内的下一个碰撞消息做准备。 - 通过
go.set_position(go.get_position() + comp)执行游戏对象的位置偏移。
向量补充解释
- 通过
vmath.project(A, B)可以计算出向量A投射到向量B上的标量。 - 通过
vmath.length(B)可以计算向量B的长度。 - 标量是向量A投射到向量B上的长度
l与向量B长度的占比值。
请大家持续监督,关注我,我将实时向你汇报进度。更希望各位精神股东在评论区给我提出宝贵的建议!