lua写的简易2D物理引擎

这套物理引擎支持简单的包围盒碰撞,支持对刚体的各种力的施加(速度,加速度,冲量),刚体没有弹性,还有万有引力

1.首先来看下关键的设计(矩形对象的设计,就是刚体的设计)
--精灵绑定矩形后,精灵的坐标设置为矩形的中心点,所以精灵以中心点对齐
 --rect = {x,y,w,h,m,d,e,v,a,i,r,c} ,矩形格式,矩形改变,调用m:updateRect,数据对象(Actor、Effect)通知更新
 -- m 数据对象
 -- d 是否是动态矩形
 -- e 是否有效(是否受重力影响)
 -- v 速度 {offx,offy}
 -- a 加速度 {offx,offy} 加速度和力是对应的,可以认为是力(v:当前速度)
 -- i 冲量Impulse 瞬间加速度,加速度会衰减,{offx,offy,xm,ym},xm和ym是衰减速度
 -- r 重置矩形的速度{offx_,offy_}
 -- c 驱动是否计算矩形位置
 -- s 矩形上次的状态{false,false,false,false},用于本次计算(驱动计算使用)
 -- v_ 矩形当前的速度{offx_,offy_}(驱动使用)
 
rect = {x,y,w,h,m,d,e,v,a,i,r,c} 这些数据通常是我们访问的,s,v_ 属性通常是物理引擎维护,当然你也可以读这些数据用作特殊用途,但是这两个数据你一定不能更改
x,y 很显然是坐标,每次你根据这个坐标刷新sprite 的位置就行了
w,h 是刚体对象的宽高
m 是刚体引用的数据对象,每次刚体有变化时就调用 m:updateRect()
d 表示是静态刚体 还是动态刚体
v 是矩形的速度,你要操作矩形的时候,就为这个属性赋值(这个属性不常用)
a 加速度,例如现在你想在刚体上施加一个力 你可以这样 rect.a = {offx = 10,offy = 0},那么刚体就会水平加速了(是不是很方便)
i 冲量,就是加速度会衰减的一种
r 重置速度,这个非常有用,例如手柄要控制人物前后移动的时候,我们想强制重置刚体的速度为指定数值,就可以这样写 rect.r = {offx_ = 10},那么刚体会丢弃以前的速度,重置为这个速度
c 物理引擎是否计算位置,通常都需要计算
s 是一组状态{false,false,false,false} 表示左右上下是否接触到地面,你也可以访问这组状态来实现某些效果,但不能修改
v_ 这是引擎使用的,你不能修改,可以访问,如果有需要

2.说了这么多,现在来看怎么实现的
```lua
--检测加矩形x (这段就是引擎使用的函数,根据是否接触地面的情况去改变坐标)
local function add_x(rect,offx)
 local s = rect.s
 if (s and ( (s[LEFT_SIDE] and offx>0) or not s[LEFT_SIDE] ) and 
  ( (s[RIGHT_SIDE] and offx<0) or not s[RIGHT_SIDE] )) or not s then
  rect.x = rect.x + offx
 end
end
--检测加矩形y
local function add_y(rect,offy)
 local s = rect.s
 if (s and ( (s[UP_SIDE] and offy<0) or not s[UP_SIDE] ) and 
  ( (s[DOWN_SIDE] and offy>0) or not s[DOWN_SIDE] )) or not s then
  rect.y = rect.y + offy
 end
end
```
```lua
--计算施加矩形的速度
local function computeSpeed(rect)
 local v = rect.v
 if v then
  local v_ = rect.v_
  v_.offx_ = v_.offx_ + v.offx
  v_.offy_ = v_.offy_ + v.offy
  rect.v = nil --计算完成就毁灭速度
 end
end
--计算施加矩形的加速度
local function computeAddSpeed(rect)
 local a = rect.a
 if a then
  local v = rect.v_
  v.offx_ = v.offx_ + a.offx
  v.offy_ = v.offy_ + a.offy
 end
end
--计算施加矩形的冲量
local function computeImpulse(rect)
 local i = rect.i
 if i then
  local v = rect.v_
  v.offx_ = v.offx_ + i.offx
  v.offy_ = v.offy_ + i.offy
  i.offx = i.offx - i.xm
  i.offy = i.offy - i.ym
  if i.xm < 0 then -- 衰减符号和初始加速度符号要相同
   if i.offx >= 0 then
    i.offx = 0
   end
  else
   if i.offx <= 0 then
    i.offx = 0
   end
  end
  if i.ym < 0 then
   if i.offy >= 0 then 
    i.offy = 0
   end
  else
   if i.offy <= 0 then
    i.offy = 0
   end
  end
  if i.offx == 0 and i.offy == 0 then --冲量计算完成后销毁
   rect.i = nil
  end
 end
end
-- 计算重置速度
local function computeResetSpeed(rect)
 local r = rect.r
 if r then
  local v = rect.v_
  local offx_,offy_ = r.offx_,r.offy_
  v.offx_ = offx_ or v.offx_
  v.offy_ = offy_ or v.offy_
  rect.r = nil --计算完成后销毁
 end
end
-- 计算加速度和冲量叠加的速度
local function computeSpeed_(rect)
 local v_ = rect.v_
 add_x(rect,v_.offx_)
 add_y(rect,v_.offy_)
end
```

以上这些代码都比较好懂,无非就是计算速度
3.下面来看看 引擎的关键代码(其实就一个函数)
```lua
--计算矩形
function GameDrive:rectCompute()
 self:screenRect() --这句筛选矩形就是一种判断范围的优化方式,在这个范围内的矩形才参与计算
 function d(v_d)
  if not v_d.c then --不需要计算就返回
   return
  end
  local start_x,start_y = v_d.x,v_d.y --获得动态矩形的坐标
  --这下面几个函数就是计算它的新位置
  computeSpeed(v_d)
  computeAddSpeed(v_d)
  computeImpulse(v_d)
  computeResetSpeed(v_d)
  computeSpeed_(v_d)
  local exp_x,exp_y = v_d.x,v_d.y --期望x,y用于校准速度
  v_d.s = {} --清空本次计算状态
  --s 函数最为关键,是实现碰撞的关键
  function s(v_i,v_s)
   --以加向量的反方向贴边
   local s,b1,b2 = rectOnRect(v_d, v_s) --判断两个矩形的状态
   if s == ON_RECT then
    v_d.s = {} --抛弃状态(矩形位置再次改变)
    local end_x,end_y = v_d.x,v_d.y
    --[#+=2]反向移动,找到不相交的位置,这样就不用考虑和其他静态矩形相交的情况(动态矩形的一帧速度
     --不能超过静态矩形的最窄的一半)
    --计算反向向量和有解的两边,找到解短的那边(就是最终相切矩形的边)
    local a_x,a_y = start_x-end_x,start_y-end_y --反向向量
    local x1,y1,w1,h1 = v_d.x,v_d.y,v_d.w,v_d.h
    local x2,y2,w2,h2 = v_s.x,v_s.y,v_s.w,v_s.h
    --abs(x1+ax*s+w1-x2) == 0
    local xs1 = (x2-x1-w1)/a_x
    local xs2 = (x2+w2-x1)/a_x
    local ys1 = (y2+h2-y1)/a_y
    local ys2 = (y2-y1-h1)/a_y
    local xs,ys
    local xc,yc
    if xs1>0 then
     xs = xs1
     xc = RIGHT_SIDE
    elseif xs2>0 then
     xs = xs2
     xc = LEFT_SIDE
    else
     printLog("BUG","xs is invalid,xs1=%f,xs2=%f",xs1,xs2)
    end
    if ys1>0 then
     ys = ys1
     yc = DOWN_SIDE
    elseif ys2>0 then
     ys = ys2
     yc = UP_SIDE
    else
     printLog("BUG","ys is invalid,ys1=%f,ys2=%f",ys1,ys2)
    end
    local s --向量百分比
    local c --相切边
    if xs>ys then
     s = ys
     c = yc
    elseif xs<=ys then
     s = xs
     c = xc
    end
    v_d.s[c] = true
    local x_l,y_l = a_x*s,a_y*s
    --修正精度误差
    if x_l<0 then
     x_l=math.floor(x_l)
    else
     x_l=math.ceil(x_l)
    end
    if y_l<0 then
     y_l=math.floor(y_l)
    else
     y_l=math.ceil(y_l)
    end
    v_d.x=v_d.x+x_l
    v_d.y=v_d.y+y_l
   elseif s == CUT_RECT then
    v_d.s[b1] = true
   end
  end
  self.rect_list_s_c:checkElement(s)--为每个静态矩形调用函数
  local end_x,end_y = v_d.x,v_d.y
  local v_x,v_y = end_x-start_x,end_y-start_y --当前速度
  local e_x,e_y = exp_x-start_x,exp_y-start_y --期望速度
  --校准速度
  local s = v_d.s
  local v_ = v_d.v_
  v_.offx_ = ((s[LEFT_SIDE] or s[RIGHT_SIDE]) and v_x) or e_x
  v_.offy_ = ((s[UP_SIDE] or s[DOWN_SIDE]) and v_y) or e_y
  --___T:显示DEBUG 矩形框
  --app.mvc.view.GameView.getInstance():addDebugRect(v_d)
 end
 --___T:显示DEBUG 矩形框
 --app.mvc.view.GameView.getInstance():removeAllDebugRect()
 self.rect_list_d:checkElement(d) --为每个动态矩形调用函数
end
```

好了,以上就是简易物理引擎的实现,其实简易物理引擎在很多场合非常好用,例如闯关类的ARPG 或者 坦克大战什么的;而用其他较为真实的物理引擎反而别手蹩脚

4.下面来看使用实例
```lua
if self.jump_init_ and on_floor then --准备起跳了
  self.jump_init_ = false
  self.scheduler_ = app.scheduler.performWithDelayGlobal(handler(self,self.initJump),self.remain_time_)
  ... --读取青蛙的动作配置数据
  --起跳
  local dir = self.jump_dir_
  rect.i = {["offx"] = dir*offx,["offy"] = offy,["xm"] = dir*xm,["ym"] = ym} -- 只需为冲量赋值就行了,是不是很简单,然后青蛙落地了,计时器一到,又会继续起跳了,还可以改变它的方向 让它来回跳
 
 end
```


想要这套物理引擎的全部代码,下载链接: https://download.csdn.net/download/xiaowa_l/10334495


你可能感兴趣的:(lua,简易物理引擎,cocos2d-x)