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