Read this manual in English

處理动画碰撞

对于动画碰撞对象的碰撞必须手动处理. 一个想当然的处理方法如下:

function on_message(self, message_id, message, sender)
  -- 处理碰撞
  if message_id == hash("contact_point_response") then
    local newpos = go.get_position() + message.normal * message.distance
    go.set_position(newpos)
  end
end

动画碰撞对象的确离开了碰撞穿透, 但是分离之后经常会过冲, 这在许多情况下会产生抖动. 为了便于理解, 想象游戏主角碰到了两个物体, AB:

Physics collision

碰撞发生的那一帧里, 物理引擎发出多个 "contact_point_response" 消息, 一个给 A 一个给 B. 如果按上面那样移动角色, 结果会是这样:

  • 根据 A 的穿透距离把角色向上移 (黑色箭头)
  • 根据 B 的穿透距离把角色向左上移 (黑色箭头)

顺序无所谓结果是一样的: 最终位移是 每个穿透向量的矢量和:

Physics separation naive

要想正确地将角色移出 AB, 需要处理碰撞点的穿透距离并检测上一个位移是否, 完全或部分, 分离了它们.

假设第一次碰撞从 A 开始, 然后针对 A 做位移:

Physics separation step 1

这样一来角色也部分离开了 B. 最后只剩下 B 黑色箭头那点穿透. 这段位移应该是 A 向量映射到 B 剩余的补偿:

Projection

l = vmath.project(A, B) * vmath.length(B)

补偿向量等于 B 向量减去 l 向量. 所以计算位移的时候, 对于每个碰撞点, 可以引入矫正向量按以下步骤进行矫正:

  1. 把当前矫正向量映射到碰撞穿透向量上.
  2. 计算穿透向量的补偿 (按照上述公式).
  3. 依照补偿向量移动对象.
  4. 把补偿向量累加到矫正向量中.

完整的代码实现如下:

function init(self)
  -- 校正向量
  self.correction = vmath.vector3()
end

function update(self, dt)
  -- 重置矫正向量
  self.correction = vmath.vector3()
end

function on_message(self, message_id, message, sender)
  -- 处理碰撞
  if message_id == hash("contact_point_response") then
    -- 获取位移计算所需数据. 
    -- 当前帧可能有多个碰撞点需要处理,
    -- 通过累积矫正向量,
    -- 达到正确计算位移的目的:
    if message.distance > 0 then
      -- 第一步, 把矫正向量投射到
      -- 穿透向量上.
      local proj = vmath.project(self.correction, message.normal * message.distance)
      if proj < 1 then
        -- 没有过冲的才需要补偿.
        local comp = (message.distance - message.distance * proj) * message.normal
        -- 应用补偿向量.
        go.set_position(go.get_position() + comp)
        -- 积累矫正向量.
        self.correction = self.correction + comp
      end
    end
  end
end