Corona中文站

强大、易学的跨平台(iOS/Android)开发框架。QQ群1:74390406(满) 群2:221929599

导航

五分钟学会Corona(三十三) - 物理引擎
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()
<< 五分钟学会Corona(三十二) - Application Configuration Options扩展corona的ui.lua功能,实现radio单选按钮功能代码 >>

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

最近发表

Powered By Z-Blog 1.8 Walle Build 100427 Copyright 2011-2015 BuildApp.Net. All Rights Reserved.