Corona物理API
corona可以让你很容易的添加物理系统到你的游戏中,甚至如果你以前从来没有用过物理引擎。因为底层引擎是基于众所周知的Box2D构建的,我们采用了一个很激进的设计,使得传统需要的大多数代码都被省略了。
另外,我们无缝地把物理引擎添加到移动开发中:例如用我们的新的“gameUI”库,任何物理对象现在都可以被多点拖放,且只需要一行代码。
为了使用corona的物理引擎,你可从熟悉的Corona对象开始。Corona对待物理 body属性,就像对待它们图像对象的属性的扩展一样:任何标准的display object,包括image,向量绘图,或 animated sprites,都可以被“物理化”,且在模拟器中自动和其他对象交互。
我们的设计目标使允许用户主要考虑可见的游戏元素,而不是构造一个不可见的物理世界并将其附加到可见的 sprite的背后。
当然,corona将屏幕上单元间的距离翻译成米。所有位置值表示为像素,其内部被转换为米,默认比例是每米=30像素(这个比例用户可用 physics.setScale 来设置)。
为了和corona SDK其他部分保持一致,所有角度值被表示度,而不是弧度,且位置(0,0)保持在屏幕左上角,而y轴朝下。Shape定义应该按照顺时针顺序声明其point。
安装物理世界
这一行使“physics”名字空间下的物理引擎特性变得有效。
local physics = require "physics"
physics.start( [noSleep] )
下面函数开始,暂停,和停止物理模拟器。start函数不但是实例化而且恢复这个物理世界,其应该在在本节其他任何函数之前被调用。
注意 stop被作为销毁这个物理世界的一个请求,所以如果你只是想暂停物理引擎,你应该使用 physics.pause().
physics.start()
physics.pause()
physics.stop()
默认情况下,Box2D的body如果不发生碰撞,就会在几秒后“睡眠”。这降低了开销,但是有时你可能不希望如此。例如,如果允许body睡眠, "ShapeTumbler"的范例代码将没法工作的很好,因为休眠的body不会响应在重力方向上的改变。
你可以在一个给定的body上修改这个行为,通过 body.isSleepingAllowed = false, 但你也修改了所有body的全局行为。也可以象下面一样,修改start的布尔参数。
physics.start( true ) -- 阻止所有body休眠
physics.start( false ) -- 默认行为;body将休眠
physics.setGravity
设置全局的重力向量x,y分量,单位是 m/s2。 默认值是(0,9.8),模拟的是标准的地球重力,指向y轴向下。
physics.setGravity( 0, 10 )
physics.getGravity
返回全局重力向量的x,y分量,单位 m/s2。这也充分利用了lua可以返回多个值的好处。
gx, gy = physics.getGravity()
基于Tilt的重力
使用上述的setter,和现有的 Corona加速计API,实现一个基于 tilt的动态重力是非常容易的(注意加速计硬件返回的y分量必须反过来放入corona坐标系。)
local function onTilt( event )
physics.setGravity( 10 * event.xGravity, -10 * event.yGravity )
end
Runtime:addEventListener( "accelerometer", onTilt )
参看 “ShapeTumbler”范例工程(加速计在Corona模拟器中是没有的,所以要创建到设备上来看效果)
physics.setScale
设置内部 像素/米 的比例,被用来在屏幕上的坐标和模拟的物理坐标之间的转换。这只能进行一次,且在所有物理对象被实例化之前。
改变这个值并不会造成视觉的上改变,简单影响了物理模型的精度。这个Box2D引擎最好模拟中等大小对象,在0.1m和10m大小之间时,你游戏中的对象被映射到物理属性会工作的最好。
默认的scale值30,这意味着最佳的0.1m到10m的范围对应的可见 sprites,在3到300个像素大小,其应该覆盖了典型的iPhone内容。对更高分辨率的设备例如iPad,android,或iPhone 4,你可能希望提升这个值到60或更多。
physics.setScale( 60 )
你也可能象提高这个值,如果你模拟的对象有点小。粗略的讲,用像素值除以真实世界的物理大小。例如,一个篮球直径大概0.25米,所以一个20像素的篮球sprite建议一个物理scale因素约(20/0.25)=80。然而,影响结果也倚赖于你选择多少密度值来逼近真实世界。所以你应该最终设置scale因素为你感觉正确的值:如果对象看起来太呆滞,掉落太慢,但是你又希望它们又很大很重 -- 试着提高 scaling值或降低它们的密度。
注意到这些 像素/米 因素时相对于你的原始content尺寸的,如果用corona的content的scale特性来部署相同代码 到不同屏幕分辨率下(例如iPhone和android)。参看Corona API参考来获取更多关于多种屏幕上自动伸缩内容的信息。
此外,由于当这个值改变时,屏幕上的对象并没被修改大小,所以可见的模拟器将会出现奇怪的行为,如果当物理对象正在屏幕上时它被改变。换句话说,这个设置不是显式伸缩世界的正确方法。如要那样的话,你应该添加所有的游戏对象到一个普通的display group,其导致模拟器通过组的内部坐标来操作它,而不是全局stage坐标,然后缩放或平移该组。参看“EggBreaker”范例代码,这是这个技术的演示。
physics.setDrawMode
为物理引擎选择三种可能的渲染模式中的一种。这个模式随时可以改变 -- 参看“DebugDraw”范例工程,这是一个关于如何在飞行中切换它的例子。
physics.setDrawMode( "debug" ) -- 只显示碰撞引擎的外线
physics.setDrawMode( "hybrid" ) -- 在普通corona对象上覆盖碰撞外线
physics.setDrawMode( "normal" ) -- 默认corona渲染,没有碰撞外线
当这个特性在设备上运行时, 当调试未预期的物理引擎的行为时,它可能在corona模拟器中最有用。
当使用corona的display group和box2D来工作时,有一件事要注意,那就是Box2D希望所有的物理对象共享一个全局的坐标系统。这意味着一系列未分组的Corona对象将正常工作,一系列被加入到相同的display group也应该正常,因为它们共享那个group的内部坐标系 -- 这在“EggBreaker”范例代码中有演示,用一个移动的display group来创建一个“移动摄像头”的效果。
然而,未预期的结果可能会发生,如果物理对象被添加到不同的display group,尤其这些组向着不同的方向运动。 "hybrid"绘制模型使得这些问题很容易被诊断,因为它让你围出一个物理引擎的碰撞盒和在Corona渲染起的顶层的形状。
物理数据被采用颜色编码向量图像的方式显示出来,其反应不同的对象类型和属性:
• 橘色: 动态物理Body(默认body类型)
• 深蓝: 力学物理body
• 绿色: 静态物理body,例如地面或墙
• 灰色:休眠中的一个body,由于缺少运动
• 亮蓝: 接头
注意:当在物理 debug 显示模式下,渲染器应该把在嵌套的display group中的物理对象算在内,如果display group被旋转或伸缩的话,它将会显示误导的结果。 这将会在未来的版本里处理。
physics.setPositionIterations
设置引擎的位置计算的精度。
physics.setPositionIterations( 16 )
默认值是8,其意味着引擎将遍历 每个对象每帧8个位置。提高这个数字,将导致少许瞬间误差(重叠对象,等等)。但是将增加计算开销。默认值应该是一般情况最合适的。
physics.setVelocityIterations
设置引擎的速度计算的精度。
physics.setVelocityIterations( 6 )
默认值是3,这意味着那个引擎 每个对象每帧将遍历三个速度。 提高这个数字,将导致少许瞬间误差(重叠对象,等等)。但是将增加计算开销。默认值应该是一般情况最合适的。
已使用过Box2D的用户需要知道
Corona结合了Box2D物理引擎的最新版本(目前v2.1.2),暴雪娱乐的 Erin Catto编写的。
从其他平台上移植Box2D应用程序过来会相当容易。Corona API包括了大多数Box2D的核心特性,包括:
• 自动世界安装,伸缩,和与可见的Corona对象的同步
• 基本碰撞形状 (矩形,圆形,任意多边形)
• 复杂碰撞形状(多元素body)
• 主body特性和固定属性
• 通过Corona事件listener的碰撞事件。包括碰撞力
• 主要的碰撞事件阶段:开始和结束
• 附加的碰撞事件类型:碰撞前和碰撞后,其对应于Box2D的"preSolve" 和 "postSolve"碰撞特性
• 在模拟器中处理拖放对象
• 八种接头:: distance, pivot, piston, friction, weld, wheel, pulley 和 touch (其中一些被重新命名,和box2D原来的名称不同,参看下面的“Joints”节)
• Touch joints (基于Box2D的"mouse joints") 可被用于多点触摸,同时支持5-10个物理对象;参看 "DebugDraw"范例工程
• Joint motors和限制
• 应用力和推理,包括linear和angular
• 动态重力,包括加速计驱动的重力
• 传感器和子弹
• 碰撞分组,遮罩和分组
• “DebugDraw” 模式, 包括一个独有的"hybrid" 模式来显示一个调试试图,作为包围
• Joint 和 body 销毁器
下面这些特性还未支持Features not yet supported include:
• 齿轮接头
• 光线投射,区域查询,等
• 当corona显示对象被修改xScale/yScale特性,自动修改物理对象的大小(避免在此期间使用一个已经存在的物理对象上的xScale/yScale)
碰撞检测
碰撞事件
物理引擎碰撞事件采用标准的Corona event-listener模型暴露出来,有三种事件类型。对于一般的碰撞检测,你应该监听一个叫做“collision”的事件。这个“collision”包括“began”和“ended”阶段,其象征着初次接触和接触瞬间破碎。这些阶段村在于普通的2-body碰撞和body-感应器碰撞。如果你不实现一个“collision”listener,这个事件就不会发生。
除了“collision”事件,两个其他的事件类型也是可选有效的,当两个body(非传感器)发生碰撞。前一个,也可以是“collision”事件类型的阶段,但是它们已经被分到特定的事件类型,所以它们需要特别的对待。
"preCollision" :一个事件类型,其在对象开始相交前激发。基于你的游戏逻辑,你可能希望检测这个事件,并且有条件的改写这个碰撞。
例如,一个平台游戏,你可能希望构造“one-sided”平台,以便角色可以垂直跳跃,但只是一个方向。你可能通过比较角色和平台的位置来看:角色是否低于平台,然后用一个很短促的timer来使角色对象的“isSensor”body属性临时为true,以便它可以穿过平台。如果你没有实现一个“preCollision”listener,这个事件将不会激发。
"postCollision": 一个事件类型,其在对象开始相交后激发。 这是为一个报告碰撞力的事件。参看下面的“Collision forces”节了解关于这个主题的更多内容。如果你不实现一个“postCollision”listener。这事件将不会激发。
注意,那个“preCollision”有点吵杂,并且每次碰撞肯能会报告很多次。因此,你应该只在你需要 pre-collision警告时监听这些事件,我们也建议你为感兴趣的对象使用本地listener,而不是监听全局获得整个游戏世界中所有的“preCollision”事件。
碰撞发生在一对对象之间,用全局runtime listener它们互相能检测到对方,或者用一个局部的table listener检测一个对象。参看“CollisionDetection”范例代码,是如何使用全局和局部的碰撞listener。
全局碰撞listener
当检测到一个runtime event,每个碰撞事件包括 event.object1 和 event.object2,其包含了这两个参与的Corona display对象的table ID。
因为Corona display object的行为很象Lua table,所以你可以自由添加任意属性到这些table上,例如名称,分类代号,点坐标,或甚至保存函数在上面,并且可以在碰撞期接收这些数据。这类似于box2D里的userData属性,但是它基本上没有限制,你可以赋值许多你喜欢的自定义属性。
例如,你可能希望在一个容易访问的字符串格式里存储对象名称:
local crate1 = display.newImage( "crate.png", 100, 200 )
physics.addBody( crate1, { density = 1.0, friction = 0.3, bounce = 0.2 } )
crate1.myName = "first crate"
local crate2 = display.newImage( "crate.png", 100, 120 )
physics.addBody( crate2, { density = 1.0, friction = 0.3, bounce = 0.2 } )
crate2.myName = "second crate"
local function onCollision( event )
if ( event.phase == "began" ) then
print( "began: " .. event.object1.myName .. " & " .. event.object2.myName )
elseif ( event.phase == "ended" ) then
print( "ended: " .. event.object1.myName .. " & " .. event.object2.myName )
end
end
Runtime:addEventListener( "collision", onCollision )
本地碰撞listener
当用一个对象里的table listener检测到事件,每个碰撞事件都包括event.other,其包含了碰撞中另一个corona display object的table ID。另外,你可能希望存储每个对象的名称到一个字符串里,并且在碰撞事件里得到它:
local crate1 = display.newImage( "crate.png" )
physics.addBody( crate1, { density=3.0, friction=0.5, bounce=0.3 } )
crate1.myName = "first crate"
local crate2 = display.newImage( "crate.png" )
physics.addBody( crate2, { density=3.0, friction=0.5, bounce=0.3 } )
crate2.myName = "second crate"
local function onLocalCollision( self, event )
if ( event.phase == "began" ) then
print( self.myName .. ": collision began with " .. event.other.myName )
elseif ( event.phase == "ended" ) then
print( self.myName .. ": collision ended with " .. event.other.myName )
end
end
crate1.collision = onLocalCollision
crate1:addEventListener( "collision", crate1 )
crate2.collision = onLocalCollision
crate2:addEventListener( "collision", crate2 )
参看“CollisionDetection”范例代码。(注意用corona控制台来运行这个项目,好看到碰撞的输出。)
multi-element body 的碰撞
multi-element body的碰撞事件(参看 "Complex Body Construction")也返回涉及的特定的body部分。这允许更多的微调的游戏逻辑:例如你可以决定让与火箭头相撞受到的伤害超过撞到火箭尾翼。
Body element 是一个数字索引来标识的,第一个被加到这个body的元素给1,第二个给数字2,第三个给3,以此类推。
对于全局的碰撞事件,两个额外的整形值被返回:
event.element1
event.element2
这些数字属性和在object1和object2碰撞中的element的索引相等。例如,object1可能是一个火箭,包含3个body element:火箭头,身体和尾巴。如果撞到其尾巴,这个 event.element1 值可能为3;如果撞到身体,值为2。另外,这个数字只是简单确定顺序,按照你在其构造期声明body element的顺序。
当碰撞涉及 single-element body,上面的值都是1。
同样,本地碰撞事件(table listener)也有额外的字段:
event.selfElement
event.otherElement
如上所述,这些值将在 single-element body时为1,multi-element body时时一个数字,在1到body中element总数之间,也就是该element被添加到body中的次序数字。
碰撞力
当一个碰撞发生,你可以获得直接碰撞力,沿着两个对象之间的边沿方向有一个有效的摩擦力。例如,你可能有游戏对象,其在一个碰撞足够有力时被销毁。
这个碰撞的方向力会在“postCollision”事件中被报告,摩擦力可以用 event.friction。
local function onPostCollision( event )
if ( event.force > 1.0 ) then
print( "Collision force: " .. event.force .. " Friction: " .. event.friction )
end
end
Runtime:addEventListener( "postCollision", onPostCollision )
在上面这个例子力,非常小的力被忽略了。另外,用全局listener在这里,只是为了简化这个例子。
尽管来自物理引擎的 postCollision 事件流有点频繁(为了仿真,它注册了一系列越来越小的力施加到对象上,直到消失),你肯能想选择自己的门槛来忽略它们中的一些。参看“CollisionDetection”范例工程,关于如何做到这一点的例子(要在Corona控制台里运行这个工程,才能看到碰撞的输出。)
(技术提示:Box2D是真实反应碰撞冲击的,其随着时间流逝释放多个力。但由于这个时间间隔是相同的,按游戏逻辑的需要可以忽略这个区别。)
Collision categories, masking, and groups
默认情况下,所有的body和其他body发生碰撞,但也许你想稍微调整下这个行为。例如,在一个经典的街机游戏“小行星”中,没有小行星会撞到其他小行星,但是所有小行星,都会撞到玩家和敌机。有两种方法来实现之:
第一,你赋予 categoryBits 和 maskBits到你的对象上,通过一个“collision filter”定义(在body构造起赋予的可选的table)。一个对象只能和这样一些对象碰撞,就是它的categoryBits被其他对象的maskBits包含(二进制位掩码)。一般一个对象只有一个category bits被赋值,但可以有一个或更多mask bits,这取决于它被允许撞到其他什么东西。
(更新: 参看有用的 collision filter 工作表和讨论在这里 posted here.)
在下面的例子中,红色气球有一个maskbits值3(二进制位011),所以它将会和category位是2和1的任何对象碰撞,这种情况下包含其他红色气球(category位2)或墙对象(category位1)。同时,它将穿过任何蓝色的气球(category位4),因为4的二进制位没有包含在它的maskBits值里。
borderCollisionFilter = { categoryBits = 1, maskBits = 6 }
-- border collides with (4 & 2) only
borderBody = { friction=0.4, bounce=0.8, bodyType="static", filter=borderCollisionFilter }
local borderTop = display.newRect( 0, 0, 320, 1 )
physics.addBody( borderTop, borderBody )
local borderBottom = display.newRect( 0, 479, 320, 1 )
physics.addBody( borderBottom, borderBody )
local borderLeft = display.newRect( 0, 1, 1, 480 )
physics.addBody( borderLeft, borderBody )
local borderRight = display.newRect( 319, 1, 1, 480 )
physics.addBody( borderRight, borderBody )
local redCollisionFilter = { categoryBits = 2, maskBits = 3 }
-- red collides with (2 & 1) only
local blueCollisionFilter = { categoryBits = 4, maskBits = 5 }
-- blue collides with (4 & 1) only
local redBody = { density=0.2, friction=0, bounce=0.95, radius=43.0, filter=redCollisionFilter }
local blueBody = { density=0.2, friction=0, bounce=0.95, radius=43.0, filter=blueCollisionFilter }
redBalloon = display.newImage( "red_balloon.png" )
physics.addBody( redBalloon, redBody )
blueBalloon = display.newImage( "blue_balloon.png" )
physics.addBody( blueBalloon, blueBody )
注意 categoryBits 和 maskBits 二进制值,只是被写成了10进制方式。低位如1,2,4和8转换很方便,高位就不那么方便了--因为有16种可能的categoryBit,最高位的值是32768。因此,将来这个api的版本肯能会使用不同的赋值语法,例如一个二进制或十六进制值。
第二种方法是为每个对象赋予一个groupIndex,通过之前提到的相同的过滤定义。这个值可以是正或负的整数,并且有一个简单的方法来指定碰撞规则:有相同正groupIndex值的对象可以相互碰撞,有相同负groupIndex值的永远不能相互碰撞。
local greenCollisionFilter = { groupIndex = -2 }
如果groupIndex和collisionBits/maskBits都被赋予一个对象,那么 groupIndex将有更高的优先级。
参看"CollisionFilter"范例代码,关于 collision filter的例子。
碰撞事件传播规则
Corona种的碰撞事件现在有一个传播模型,类似于touch event。你可以 通过限制创建事件的个数,来更进一步优化你的游戏性能,
默认情况下,两个对象之间的碰撞将会为第一个对象激发一个本地事件,然后第二个对象的本地事件,然后才是runtime object的全局事件(假设所有事件都有可用的listener)。然而,你可能之感兴趣其中的部分信息。例如,在一个只有一个玩家角色和多个敌人的游戏中,你可能只感兴趣玩家被什么碰撞到了。
在alpha 3中,任何返回true的碰撞事件处理函数,都将停止进一步的碰撞事件的分发,即使有进一步的有很多listener想要收到这个事件。这允许你进一步限制创建的和传给lua端的事件的数量。虽然个别事件并不是开销很大,但是大量的就会影响整体性能,所以限制事件传播是很好的实践。
Physics Bodies
Body
Corona的物理世界,是基于刚性body的相互作用的。这些物理body可以被一对一的绑定在Corona display object上,corona将会自动的处理所有的位置更新和其他同步任务。
事实上,我们的目标是你能够把物理body作为可见的display object的字面部分。因此,body构造器 physics.addBody 并不返回一个新对象,而是“扩展”一个已经存在的display object,使它带有物理特性。
因此,标准对象的如x,y和rotation之类的属性,在一个物理body上仍然可以继续读写。但如果 bodyType是“dynamic”,物理引擎可能会“抵抗”你手动移动对象的尝试,因为目标对象也是同时处于重力和其他力的作用下的。
“DragPlatforms”范例代码显示了,在拖拽期间,修改body类型为“kinematic”的一个方法,其临时免除了它门的外部力量的影响。于此相反,“DebugDraw”范例显示了另一个方法,用 "touch joints"来拖拽对象,其不用免除外力影响。第二个方法,在基于物理系统的游戏中会更有用,但是你可以比较一下这两个例子,看看 "kinematic" 和 "dynamic"对象行为的差别。
一个有物理属性的display object可以被用一般的方法删除,用 object:removeSelf(). 它会自动被从可见的屏幕以及物理模拟器中删除。注意:你不能删除一个对象的物理属性--你只能删除整个对象。
简单body构造
physics.addBody
这允许你把任何Corona的display object加入到一个模拟的物理对象中,而仅用这一行代码,也包括了物理属性的赋值。
物理body有三个主要的物理属性:
• density ,乘以body的体积可以求出它的重量。这个参数基于一个标准值1.0(水密度),所以如果材料比水轻(例如木头)则密度就低于1.0,更重的材料(例如石头)密度就会大于1.0。然而,你应该根据你游戏的需要去正确设置密度,因为总的对象行为取决于你的重力以及 像素/米 伸缩设置(参看前面章节)。这个默认值为1.0。
• friction 应该为一个非负值;0意味着没有摩擦力,1.0意味着较强的摩擦力。默认值为0.3。
• bounce (弹跳)这是box2d的属性,其内部叫做“restitution”,确定当发生一个碰撞后一个对象可以获得多少速度。大于0.3的值是非常“有弹性”,如果对象的bounce值为1.0将永远弹下去(例如,如果掉落地板上,它会弹回到大致相同的高度)。比1.0还大的bounce值是有效的,但是将会导致奇怪的行为:对象将会在每次碰撞后获得速度,直到它们飞出太空。默认值是0.2,这是轻微的弹性。
注意
你可以发现一个bounce被设置为1.0的对象,将会越弹越高,这是因为Box2D中的数值逼近造成的。bounce值略低于1.0,再结合一些线性阻尼,可用在一段时间内更接近的模拟“相同高度”的行为,但是很难避免它跑丢或弹力衰减。
默认 (矩形) body
默认情况下,这个构造器假设物理对象是矩形的,以相关的image或向量对象的边缘自动作为碰撞边缘。这对于箱子,平台,大地body,以及其他简单的矩形sprite,是很好用的。所有table中的参数都是可选的,如果不提供都会有一个合理的默认值。
local crate = display.newImage( "crate.png", 100, 200 )
physics.addBody( crate, { density = 1.0, friction = 0.3, bounce = 0.2 } )
注意物理属性的table可以被额外声明,然后多次使用:
local crate1 = display.newImage( "crate.png", 100, 200 )
local crate2 = display.newImage( "crate.png", 180, 280 )
local crateMaterial = { density = 1.0, friction = 0.3, bounce = 0.2 }
physics.addBody( crate1, crateMaterial )
physics.addBody( crate2, crateMaterial )
还要注意这个默认的构造器假设你的物理对象也和你的display object采用相同的矩形边界。很多时候,这非常方便,但是另一些情况你却并不希望如此。例如,一个用 display.newLine() 绘制的向量线条,实际上是被作为一个矩形对象来渲染的,因为它创建了一个OpenGL文理的矩形区域(其大部分是透明的)。
如果你不想你的物理body匹配这个矩形边缘,你应该定义特定的形状数据,用圆半径属性或多边形坐标数组,下面会讨论这个方法。
不确定的时候,你可以用 physics.setDrawMode() 来获取一个“场景背后的”视图,它描述了物理引擎的实际动作,包括你物理body的碰撞边缘。
圆形 body
圆形body需要一个额外的半径参数。这对于球,石头和其他再碰撞时被当作圆形来对待的东西,都是很好用的。如果可见的sprite是圆,但是不规则(比方说一个鹅卵石或鸡蛋),你可能希望设置这个半径比物理图片大小略小一些。
注意,在Box2D碰撞几何中没有这样一个非圆的椭圆形,因为圆形body是作为一个特例存在的。为了得到一个椭圆body,你应该画出一些列指定的边缘点构成形状的外框线作为多边形(参看下面)。
local ball = display.newImage( "ball.png", 100, 200 )
physics.addBody( ball, { density = 1.0, friction = 0.3, bounce = 0.2, radius = 25 } )
多边形 body
通常多边形body 通过shape参数,就不会被分到上面两种特殊的情况,其被用来传入一个(x,y)坐标点组成的table,来勾勒碰撞边缘。这些坐标被指定为相对display object的:默认情况下,Corona在相关的image文件(或向量对象)的中心设置display object的origin。
例如,为了定义矩形形状为20像素高和40像素宽,在它的中心使用它的origin,可象下面这样定义形状:
squareShape = { -20,-10, 20,-10, 20,10, -20,10 }
这里允许每个碰撞形状的点个数的最大值(边缘)是8。一旦定义定义,一个形状定义就可以多次使用。
注意:这些多边形坐标必须按照顺时针方向定义,并且最后的形状必须凸出的。 违反这两个规则的坐标集看上去可以工作,但是它们的对象在碰撞中将会出现古怪的行为:例如它们可能卡住其他对象。(凹的物理对象必须被分解为附加在同一个body上的多个element;参看下面的 "Complex body construction"。)
这有两个有效的例子:
local triangle = display.newImage("triangle.png")
triangle.x = 200
triangle.y = 150
triangleShape = { 0,-35, 37,30, -37,30 }
physics.addBody( triangle, { density=1.6, friction=0.5, bounce=0.2, shape=triangleShape } )
------
local pentagon = display.newImage("pentagon.png")
pentagon.x = 200
pentagon.y = 50
pentagonShape = { 0,-37, 37,-10, 23,34, -23,34, -37,-10 }
physics.addBody( pentagon, { density=3.0, friction=0.8, bounce=0.3, shape=pentagonShape } )
多边形body的例子,要参看 “ShapeTumbler” 和 “SimplePool”中的 bumper object。
基于bodyType的构造器
这里有两种方法在添加一个物理body时指定bodyType。
physics.addBody( triangle, "static", { density=1.6, friction=0.5, bounce=0.2, shape=triangleShape } )
另一种,你可以构造这个body,然后设置它的type属性:
physics.addBody( triangle, { density=1.6, friction=0.5, bounce=0.2, shape=triangleShape } )
triangle.bodyType = "static"
可能bodyType是 “static”, “dynamic” 和 “kinematic”,默认的type是“dynamic”,如果值没有指定的话。
一般而言,你可以允许移动对象默认为“dynamic”,并为一些对象指定“static”(例如墙或地面)其不应该移动或循重力而掉落。
复杂body构造器
上面的例子假设一个body只有一个element。然而,也很可能构造一个有多个element组成的body。在这种情况下,每个body element被分别指定多边形形状为其自己的物理属性。
例如,模拟一个铅笔,你可能想要定义一个橡皮擦尾巴作为一个分离的body element,其比铅笔的其他部分带有更高的“弹性”值。
另外,因为在Box2D中碰撞多边形必须为凸的,任何凹形状的游戏对象必须被通过增加多个body element来构造。例如,为了构造一个汽车,你可以用一个body带两个或更多body element,因为一个汽车的形状包含凹角,例如挡风玻璃和引擎盖之间的角度。
复杂body的构造器和简单多边形构造器大部分差不多,除了包含超过一个body element列表:
physics.addBody( displayObject, [bodyType,] bodyElement1, [bodyElement2, ...] )
每个body element可能拥有它自己的物理属性,带有它们自己的碰撞边缘的形状定义。例如:
local car = display.newImage("big_red_car.png")
roofShape = { -20,-10, 20,-10, 20,10, -20,10 }
hoodShape = { 0,-35, 37,30, -37,30 }
trunkShape = { 0,-37, 37,-10, 23,34, -23,34, -37,-10 }
physics.addBody( car, "dynamic",
{ density=3.0, friction=0.5, bounce=0.2, shape=roofShape },
{ density=6.0, friction=0.6, bounce=0.4, shape=hoodShape },
{ density=4.0, friction=0.5, bounce=0.4, shape=trunkShape }
)
(这些形状定义只是例子,可能并不是很好的汽车形状:))
在这个简单的例子里,这个bodyType属性是可选的,且如不指定的话,则默认为“dynamic”。
传感器
任何body(或一个多element的body的element)可以被转变成一个“传感器”。传感器并不和其他body发生物理交互,但是当其他body经过他们时会产生碰撞事件,一张台球桌上的口袋可能就被实现为传感器。
因为最简单的Corona body构造器是基于可见的display object,实现一个可见的传感器区域的最简单的方法,是创建一个display object,然后使它变得不可见:
local rect = display.newRect( 50, 50, 100, 100 )
rect:setFillColor( 255, 255, 255, 100 )
rect.isVisible = false -- optional
physics.addBody( rect, { isSensor = true } )
可见对象可以被用于游戏调试,然后 一旦调试完成,这个isVisible属性可以被设置为false。在上面的例子里,初始的display object是一个用Corona绘制API画的半透明向量矩形。
销毁body
物理body可以被象其他任何display object一样的被销毁:
myBody:removeSelf()
-- 或 --
myBody.parent:remove( myBody )
上面第一个方法因为很简单所以被推荐,并且和用于销毁物理接头(其没有父节点,因为它们是不可见的且不是显示层次中的一部分)相同的语法。
当你销毁body或接头时,Corona将自动处理相关的Box2D对象,直到当前物理世界步骤的结束,然后安全的销毁它们。
注意
当然Box2D对象将被安全的保留,直到当前的世界步骤的结束,它们的lua引用将会被立刻删除。因此,小心不要偶尔多次删除同一个lua对象。这种情况会在被删除对象发生碰撞时出现,因为其在碰撞完全解决之前,可以有潜在的许多事件阶段。这个解决方案很简单:
local function onCollision( self, event )
if ( event.phase == "began" ) then
-- 在删除前检查body是否依然存在!
if ( crate1 ) then
crate1:removeSelf()
crate1 = nil
end
end
end
Body 属性
许多本地Box2D setter/getter方法被简化到display object上的简单的点属性。下面例子假设一个叫myBody的body,用上面的一个构造方法来创建的。
body.isAwake
一个布尔值描述当前的苏醒状态。默认情况下,所有的body在没有发生交互后的几秒钟后,一般都自动都“睡着了”,然后它们停止模拟器,直到某些事唤醒(例如:一个碰撞)它们。这个属性可以获取它们的当前状态或强制唤醒它们。
myBody.isAwake = true
local state = myBody.isAwake
body.isBodyActive
一个布尔值描述当前的活跃状态。交互body没有被销毁,但是它们已经从模拟器中删除,并且停止和其他body交互。
myBody.isBodyActive = true
local state = myBody.isBodyActive
body.isBullet
一个布尔值用来标识body是否应该被看作一个“子弹”。子弹会受到连续的碰撞检测,而不是在世界的事件步骤里定期碰撞检测。这消耗更多的计算资源,但是它可以阻止快速移动的对象,让其无法穿过固体障碍。默认值是false。
body.isSensor
一个 (write-only) 布尔属性,用来为这个body里所有的element设置一个内部的“isSensor”属性。一个传感器让其他对象穿过,而不是弹开它们,但是仍然激发一些碰撞事件(例如, "SimplePool" 范例中的桌球袋就是传感器)。因为这个body属性对所有body element都有效,它无条件的覆盖它自己所有element的“isSensor”设置。
myBody.isSensor = true
body.isSleepingAllowed
一个布尔值,关于这个body是否允许去睡眠。保持body醒着会产生很大的开销,不是一直需要的,因为和其他body碰撞将会自动的唤醒它们。然而,强制唤醒对于例如tile-gravity的情况是很有用的(尽管睡眠的body不在全局重力中响应变化)。其默认值是true。
myBody.isSleepingAllowed = true
local state = myBody.isSleepingAllowed
body.isFixedRotation
一个布尔值,body的旋转是否应该被锁定,甚至如果这个body在负载或在离心力作用下。默认值是false。
myBody.isFixedRotation = true
local state = myBody.isFixedRotation
body.angularVelocity
当前角速度(旋转)的数字值,每秒度数。
myBody.angularVelocity = 50
local v = myBody.angularVelocity
body.linearDamping
body直线运动受阻多少的数字值。默认值是0。
myBody.linearDamping = 5
local d = myBody.linearDamping
body.angularDamping
body旋转受阻多少的数字值。默认值是0。
myBody.angularDamping = 5
local d = myBody.angularDamping
body.bodyType
被模拟的物理body的类型的一个字符串值。可能值是 "static", "dynamic" 和 "kinematic"。
• 静态body不移动,互相不交互;静态对象的例子将包括地板,或一个弹球机的墙壁。
• 动态body受到重力影响,以及被其他body类型碰撞。
• kinematic对象被除了重力意外的力影响,所以你一般应该设置可拖动对象为“kinematic”,至少在在拖动事件期间。
默认的body type是"dynamic".
myBody.bodyType = "kinematic"
local currentType = myBody.bodyType
Body方法
下面的例子假设一个叫myBody的body已经上面的构造方法中的一个创建出来。
body:setLinearVelocity
一个函数,接收这个body线性速度的x,y分量,单位是 像素/秒。
myBody:setLinearVelocity( 2, 4 )
body:getLinearVelocity
一个函数,获得这个body线性速度的x,y分量,单位是 像素/秒。
vx, vy = myBody:getLinearVelocity()
body:applyForce
一个函数接收一个线性力量的x,y分量,在一个世界坐标下的x,y给定的点。如果目标点是body的重心,它将会一直在一条直线上推动body;如果这个目标相对于body重心有偏移,这个body将围绕这个重心自旋。
对于对称的对象,对象的重心和对象的中心是同一个位置:(object.x,object.y)。另外需要注意的是移动重的对象,需要很大的力量。
myBody:applyForce( 500, 2000, myBody.x, myBody.y )
body:applyTorque
一个函数,接收一个作用的旋转力的数值。这个body将围绕它的重心旋转。
myBody:applyTorque( 100 )
body:applyLinearImpulse
就像 applyForce一样,除了有一个冲力,产生单独的,短暂的颠簸。
Like applyForce, except that an impulse is a single, momentary jolt.
myBody:applyLinearImpulse( 600, 200, myBody.x, myBody.y )
body:applyAngularImpulse
就像applyTorque, 处理一个角冲力,产生单独的,短暂的颠簸。
myBody:applyAngularImpulse( 100 )
force vs. impulse?
关于应用一个“force”和“impulse”到一个body上的区别是什么呢?区别是 impulse意味着模拟器快速的“踢向”body,但是force(和 torque)是随着时间推移产生的。因此,为了得到一个真实的force的模拟,你应该在每一帧上重复应用force,也许几秒,或象你希望的那么久。你可以用一个“enterFrame”事件来完成这个目的。
body:resetMassData
如果body默认的重量数据被改写(TBD)的话,这个函数可以根据形状重新计算默认重量。
myBody:resetMassData()
Physics Joints
Joints(接头)
Joint被用来把多个刚性body集成为更复杂的游戏对象。例如,joint可以用来连接布娃娃的四肢,或汽车的轮子。
Box2D包括一些不同的joint类型,每个带有不同的参数和函数。一些joint类型可以被motor驱动,另一些则在他们的运动范围被施加不同的物理限制。
最简单的joint类型是“pivot”(枢纽),它在一个枢纽点上连接两个对象,参看“Bridge” and “Chains” 范例工程。
为了构造一个joint,首先构造要被连接的body,然后提交这些body给所需的joint构造器函数。
Pivot joint
一个Pivot Joint(在Box2D中也叫“旋转关节”)在一个重叠点上连接两个body。开始的参数是两个要被连接的body,随后是一个在世界全局坐标系力的锚点。
Pivot joint可受限于它的旋转范围。例如,如果构造一个“布娃娃”角色,头/颈部的连接,可以有一个角度运动的限制范围。
myJoint = physics.newJoint( "pivot", crateA, crateB, 200,300 )
Joint motor
myJoint.isMotorEnabled -- (boolean)
myJoint.motorSpeed
myJoint.motorTorque -- (get-only)
myJoint.maxMotorTorque -- (set-only)
默认,joint motor有一个相当微弱的最大扭矩,因此设置 motorSpeed可以看到一点可见的效果。所以,如果你视图移动任何重物(例如转动车轮来驱动一个汽车),你一般应该设置 maxMotorTorque为一个很高的值(例如 100000)。
旋转限制
Pivot joint可以被限制在它的旋转范围内。 例如,如果构造一个“布娃娃”角色,头/颈部的连接,可以有一个角度运动的限制范围。这些旋转限制,采用的单位是角度:
myJoint.isLimitEnabled = true -- (boolean)
myJoint:setRotationLimits( -45, 45 )
a1, a2 = myJoint:getRotationLimits()
其他属性:
myJoint.jointAngle -- (get-only; value in degrees)
myJoint.jointSpeed -- (get only; value in degrees per second)
Distance joint
一个distance joint在一个固定距离里连接两个body。开始的参数是要连接的两个body,随后是每个body上的一个(x,y)锚点。锚点被声明在世界坐标下。
myJoint = physics.newJoint( "distance", crateA, crateB, crateA.x,crateA.y, crateB.x,crateB.y )
其他属性:
myJoint.length
myJoint.frequency
myJoint.dampingRatio
length就是在锚点之间的距离,其不应该为0或非常小(如果你需要把两个body连起来你应该用 pivot joint)。
frequency 是质量弹簧阻尼频率(Hz)
dampingRatio 从0开始表示没有阻尼,到1为临界阻尼
Piston joint
一个piston joint(在Box2D中叫做“柱状连接”)连接两个沿着单一限轴向的运动,就像活塞或者汽车的减震器。开始的参数是要连接的两个body,随后是第一个body的一个锚点,和一个定义运动被允许的轴向的向量。
myJoint = physics.newJoint( "piston", crateA, crateB, crateA.x,crateA.y, axisDistanceX,axisDistanceY )
(注意:至少一个body应该为dynamic,带不固定旋转)
Joint motor
一个piston joint也应该被一个motor驱动。和pivot joint不同的是,这个运动是线性的,沿着指定轴,而不是旋转。
myJoint.isMotorEnabled -- (boolean)
myJoint.motorSpeed -- (linear speed, in units of pixels per second)
myJoint.motorForce -- (get-only)
myJoint.maxMotorForce -- (set-only)
运动限制
另外,线性运动的范围限制可以被指定:
myJoint.isLimitEnabled = true -- (boolean)
myJoint:setLimits( 100, 200 )
p1, p2 = myJoint:getLimits()
其他属性:
myJoint.jointTranslation -- (get-only; linear value in pixels)
myJoint.jointSpeed -- (get only; value in pixels per second)
Friction joint
一个friction joint是一种特别的 pivot joint,它会抵抗运动,因此也叫黏性接头:
myJoint = physics.newJoint( "friction", crateA, crateB, 200,300 )
它只开放两个属性:
myJoint.maxForce
myJoint.maxTorque
Weld joint
一个weld joint“焊接”两个body到一起,在一个世界坐标系的指定点上:
myJoint = physics.newJoint( "weld", crateA, crateB, 200,300 )
这个连接完全不移动或旋转,所以不开放属性。
然而,因为数学上的近似,这个joint可能在运动期间看起来有点“软”,所以如果你想要硬把多个形状绑在一起,你或许该指定它们作为一个复杂body的多个element,而不是用weld joint。
Wheel joint
一个wheel joint(在Box2D中被称为“行连接”)包含了一个piston和一个pivot的joint,就像安装在汽车减震器上的一个车轮。它的大部分属性从标准的piston joint上继承下来;不同在于在轴末端的body被允许自由旋转。
myJoint = physics.newJoint( "wheel", crateA, crateB, crateA.x,crateA.y, axisDistanceX,axisDistanceY )
Joint motor
就像一个piston joint,一个wheel joint的“减震器”部分可能被一个joint motor驱动。这个动作将是线性的,沿着指定的轴向,而不是旋转。
myJoint.isMotorEnabled -- (boolean)
myJoint.motorSpeed -- (linear speed, in units of pixels per second)
myJoint.motorForce -- (get-only)
-- myJoint.maxMotorForce -- (not currently supported in Box2D library; known bug)
运动限制
另外线性运动的范围限制,也可以被指定:
myJoint.isLimitEnabled = true -- (boolean)
myJoint:setLimits( 100, 200 )
p1, p2 = myJoint:getLimits()
其他属性:
myJoint.jointTranslation -- (get-only; linear value in pixels)
myJoint.jointSpeed -- (get only; value in pixels per second)
Pulley joint
一个pulley joint用一个假想长度不变的绳子来连接两个body:如果一个body被推下去,另一个就会上来。
这个构造器有点点复杂,因为它必须在每个body里指定一个joint的锚点,和绳子每一边挂接部位的固定锚点(世界坐标系下)。最后,有一个“比例”属性用于可选的模拟块和处理安排,如绳子的一边运动的比另一边块。默认,这个比例是1.0,其模拟一个简单的滑轮。myJoint = physics.newJoint( "pulley", crateA, crateB, anchorA_x,anchorA_y, anchorB_x,anchorB_y, crateA.x,crateA.y, crateB.x,crateB.y, 1.0 )
暴露了如下只读属性:
myJoint.length1 -- (get-only; value in pixels)
myJoint.length2 -- (get-only; value in pixels)
myJoint.ratio -- (get-only)
Touch joint
一个touch joint(在Box2D中叫做“鼠标连接”)连接一个单独对象到屏幕上一个当前触摸位置。这个连接是一个带有指定力量和行为的弹性关节,但是因为可能应用无限大的力到模拟器上,被快速触摸移动而拖动的对象可能会有“滞后”。然而,这个方法提供了比非物理拖动方法更加真实的行为,参看早前的“DragPlatforms”范例。
touch joint的例子,包括它们用到的多点触摸,请参看“DebugDraw”范例代码。myJoint = physics.newJoint( "touch", crate, targetX,targetY )
targetX和targetY的初始值,应该在世界坐标系中被给出,但在对象的上表示目标点。例如,通过对象中心点拖动上面的对象,你可如下操作:
touchJount = physics.newJoint( "touch", crate, crate.x, crate.y )
Alternatively, to drag an object by the point it was touched on:
touchJount = physics.newJoint( "touch", crate, event.x, event.y )
...“事件”被传入一个touch listener。这个行为在"DebugDraw"范例代码中有体现。
注意:因为touch joint可以跟随任何(x,y)值,它可能也会被用于除了跟踪一个touch之外其他的事情:例如,它可以被用于创建一个对象来跟随路径,如果这个路径被用一系列(x,y)值所指定的话。这应该可以让它用于触摸控制来战斗之类风格的游戏,之类的。
通常,一个touch joint在你的手指和对象间创建了一个临时的弹性关节,其使得对象尽量跟随手指。因为对象始终在模拟系统内,它的动作会被其他固体对象所阻止,且会和世界中其他body发生完整的交互。另外,对象也会真实的随重力旋转,当“一端被抬起”。(为了避免这个行为,你可以连接这个touch joint到对象的重心上,而不是你触摸到对象的那个点)
Joint target
用touch joint的触摸跟随行为可以通过传入当前触摸点(x,y)位置给joint来实现:
myJoint:setTarget( targetX, targetY )
更新的(targetX,targetY)值来自一个触控操作,所以任何值都可能被传入。例如,对象肯能会被迫使跟随另一个对象,或跟踪路径点。
当前目标值也可以被访问:
targetX, targetY = myJoint:getTarget()
最大力度
joint的整体速度或“滞后”取决于被施加的力:
myJoint.maxForce = 10000
force = myJoint.maxForce
默认情况下,这个属性被设置为body重量的1000倍,其允许较快速的拖动。
频率
弹性关节的重量弹簧阻尼频率(Hz):
myJoint.frequency = 50
frequency = myJoint.frequency
阻尼比
弹性关节的阻尼比,范围从0开始表无阻尼,到1表临界阻尼:
myJoint.dampingRatio = 0.2
damping = myJoint.dampingRatio
上面的属性的行为和它们在Distance joint中的同类属性是相似的(参考上面)。由于在这种情况下,默认值工作的还不错,但是你可能想实验下其他值。
用touch joint拖动对象
这有一个一般函数,用来创建一个物理对象拖放,也可参看 "DebugDraw" 范例代码:
local physics = require("physics")
physics.start()
system.activate( "multitouch" )
-- A general function for dragging physics bodies
local function dragBody( event )
local body = event.target
local phase = event.phase
local stage = display.getCurrentStage()
if "began" == phase then
stage:setFocus( body, event.id )
body.isFocus = true
-- Create a temporary touch joint and store it in the object for later reference
body.tempJoint = physics.newJoint( "touch", body, event.x, event.y )
elseif body.isFocus then
if "moved" == phase then
-- Update the joint to track the touch
body.tempJoint:setTarget( event.x, event.y )
elseif "ended" == phase or "cancelled" == phase then
stage:setFocus( body, nil )
body.isFocus = false
-- Remove the joint when the touch ends
body.tempJoint:removeSelf()
end
end
-- Stop further propagation of touch event
return true
end
-- Add a physics object
crate = display.newImage( "crate.png" )
physics.addBody( crate, { density=0.8 } )
-- Make object draggable
crate:addEventListener( "touch", dragBody )
所有joints的通用特性
除了上面提到的以外,所有的joint类型共享几个通用的属性和函数:
myJoint:getAnchorAmyJoint:getAnchorB
一个函数,其返回对象A和B中joint锚点的(x,y)坐标(两个被连接的对象)。这个值是基于每个对象的本地坐标系的,所以对象中心的一个锚点应该在(0,0)。
x, y = myJoint:getAnchorA()
x, y = myJoint:getAnchorB()
myJoint:getReactionForce
一个函数其返回反作用力(单位:牛顿),在第二个body的joint锚点处。
reactionForceX, reactionForceY = myJoint:getReactionForce()
myJoint.reactionTorque
一个只读属性,其返回反作用角度力( 单位: N*m),在第二个body的joint锚点处。
reactionTorque = myJoint.reactionTorque
销毁joint
为了销毁一个存在的joint,并且释放两个body(然后还可能会碰撞),要用removeSelf():
myJoint:removeSelf()