从最热门游戏排行榜和flash游戏网站上,你能看到什么?许多2D游戏都有非常出色的物理学和美术设计。现在我们要学习那些游戏使用了什么物理学以及如何用Box2D制作它们。
除了知道是“什么”,更重要的是知道“如何做”,首先,我想问读者一个问题:如果你想复制物理游戏的机制或行为,你需要什么技术和方法?
一年以前,我问了自己同样的问题,《6 Dimensions》就是问题的答案。这款游戏是一个创意的盒子,每一面都包含一组使用Box2D物理学再加上视觉美学技术制作的不同的游戏机制。在此,我将与大家分享这款游戏。我做这款游戏是为了改进游戏引擎Codea(是由Crabitron开发的),而我写了这篇教程是为了向大家介绍写实物理学、美术和游戏设计……共同提高我们的游戏开发水平。
在我的游戏中,我设计了6个面,借同样的思路,我将给大家介绍我运用了哪些从其他游戏中复制而来的物理、机制和美术技术。
1、形状 《Thomas was alone》、《愤怒的小鸟》、《蜡笔物理学》crayon physics
在《蜡笔物理学》中,你可以在屏幕上用手指或鼠标作画,比如画圆、三角形、矩形等,当你松开鼠标/拿开手指,线条就会变成立体物理对象(在虚拟世界中)。
这是怎么做的? 事实上相当简单,你要把鼠标/手指从开始到结束的绘画路径的各个坐标点保存起来,当释放事件发生,你就调用Box2D的一个根据这些点生成多边形的功能:
local body = physics.body(POLYGON, unpack( points ) )
你得先认识一下Box2D中的有什么形状:
POLYGON(多边形): 用于封闭形状如基本几何图形(非圆形),它使用一系列按各个API指定的顺序排列的顶点(x,y)
CIRCLE(圆): 可以做球、水珠、星体,等等。
EDGE(边): 用于制作墙、地面、只有起点和终点的线段。
CHAIN(链): 与边相同,但你可以闭合它(像多边形但不是凸多边形)或不闭合它(像边但点超过2)
知道了刚体形状(body shape)后,你还要了解它们的不同行为,或叫作刚体类型(body types):
STATIC(静态):如名称所示,这种刚体会在指定的x,y(地面、墙、柱基或绳基,等等)上保持静止不动。
DYNAMIC(动态):它与其他对象碰撞并移动
KINEMATIC(运动):碰撞但不随着动态对象移动,你只能通过改变它的x,y或者赋给它一个线性速度或对它施加力来使它移动。
真正的含义要在API的执行中理解,在本文中我使用这个是因为它是我能找到的最简单的代码了。但改变成任何语言的代码都非常简单,Box2D有几乎所有的语言版本(Flash as3、c++、objc、java、javascript、java+processing,等等)。
你得保存那个功能的结果为自定义变量如body.position(位置)、body.radius(半径)、body.linearVelocity(线性速度)、body.angularVelocity(角速度)、body.mass(质量),等等。
当刚体制作出来时,你可能想给它定义一些属性如restitution(恢复)、gravityScale(重力大小)和damping(衰减)等,这些属性可以赋给物理对象弹跳或漂浮状态。
Box2D的复杂度当然不止这些,具有这种物理游戏机制的其他游戏(《Magic Pen》)也比较复。在《Magic Pen》中,你可以画一些东西看起来像“node(节点)”的东西,但开发者叫它们“joint(关节)”,它们是用于连接刚体的,有若干种,取决于你想要的机制;还可以用于制作连接着的刚体之间的行为:
physics.joint(REVOLUTE,bodyA, bodyB,)
刚体围绕着一个固定点(anchor)旋转
例如:小车的车轮、《蜡笔物理学》和《Magic Pen》中的红色节点
physics.joint( PRISMATIC, bodyA, bodyB, anchorA, direction )
在刚体各自的固定点之间保持固定距离。两个joint之间的初始距离取决于虚拟空间中的这两个固定点之间的初始距离。给joint设置frequency率和damping率可以使它产生软弹簧的行为。
physics.joint(DISTANCE, bodyA, bodyB, anchorA, anchorB )
旋转joint迫使两个刚体沿着某两个固定点之间的轴作运动。允许伸缩运动,但限制两个刚体之间的相对旋转。
physics.joint( WELD, bodyA, bodyB, anchor )
接合joint限制两个刚体之间的运动和相对旋转,实际上使它们变成一个刚体。因为求解器的迭代性质,当置于压力之下时接合joint可能会变形;当承受的力太大或几个接合joint被链接成一个更大的对象时,接合joint可能会完全失效。
physics.joint( ROPE, bodyA, bodyB, anchorA, anchorB, maxLength )
绳子joint限制两个刚体之间的最大距离
例如:《割绳子》中的绳子
概述: 1)创建:带有触点刚体或盒子或任何其他多边形几何体(一组2D点:x,y),给它设置我们需要的物理属性(如《Thomas was alone》中的不同行为),比如,如果刚体是static类型,那么就可以设置它的质量、密度、重力大小,等等。
2)可选属性:依附(attach)到另一个刚体上,比如说,你可以把一个刚体依附到另一个被设置为传感器的static刚体(不影响游戏世界的物理,但有碰撞事件),然后激活REVOLUTE joint的enableMotor(能动)属性,这还需要motorSpeed(速度)、maxMotorTorque(转矩)和maxMotorForce(力量),才能确定这个对象的旋转情况。
3)美术(Visual Art):有了刚体后,如果你想绘制它,不是作为形状绘制出来,而是具有颜色或纹理的实体,你就要把这些点三角化生成多边形网格模型(mesh)并给它设置颜色和贴材质。
例子:
Box2D_POLYGONS
thomas Was Alone Boxes
对于《Thomas Was Alone》中的盒子的行为,你可以设置一个简单的“juice”系统动画(从“中间帧”演化来的),这样,当你选择方块并按下跳跃键(或它与其他不同的物理刚体发生碰撞),它就会触发“juice command= animation”命令——产生挤压、摇晃等动画效果,各个动作都有自己的动画参数,比如质量、线性速度和衰减等物理属性。
box Examples
对于《愤怒的小鸟》,你可以通过给盒子定义不同的属性来制作一个关卡,绘制不同的子画面或制作不同材质的mesh,这样,在碰撞事件中,盒子刚体就会更加生动,通过改变盒子的纹理使之与当前状态更加协调(断掉的木头、快碎的玻璃,等)。
你可以用简单的刚体applyForce(vec2(x,y)) 函数做出小鸟的发射。各种小鸟也都有自己的质量、衰减等属性……
2、水体 《Where is my water?》、《Sprinkle Ilsands》……
当你问网上的代码达人,如何制作上述游戏那样的水体物理时,他们会跟你谈Metaball(变形球):
Metaball_contact_sheet
但在游戏中使用Metaball技术既麻烦也不容易,而且要进行大量计算,除非你发现一些技巧和给它贴上一些美术材质。
那就是为什么运用水体物理学的游戏并不多见。几个月前我谈到这个问题,多亏了许多人的帮助,我得到了一个很棒的水体物理模型。在那个模型中,我用Box2D lib中的CIRCLE刚体做出动态球。
模型的代码很容易理解,球就是物理刚体,这些刚体具有使它产生水滴行为的参数如estitution(复原)、friction(摩擦)、damping(衰减)、linear velocity(线性速度),然后,我们用着色器(GLSL)的技术和材质绘制这些球,需要一个mesh,就像波纹fx或使用材质的其他GLSL着色器样本,我们把这个mesh的宽和高设为整个屏幕,从中间开始:
mesh:addRect(WIDTH / 2, HEIGHT / 2, WIDTH, HEIGHT)
这样,我们可以使用各个球的位置(x,y)在虚拟空间中绘制它们,各个球都有渐变的纹理效果。
for k,b in ipairs(balls) do
sprite(ballTexture, b.x, b.y)
end
然后,你得使用额外的扭曲模式,给这些着色的球添加材质,并与背景混合。
例子:
Box2D_water Physics
正如我所说的,各个球都有纹理(程序生成的渐变),可以与其他使用低级过滤器的球材质相融合。
where is my water
《Where is my water?》 你可以对各种行为使用不同的层,或者把所有液体或所有动态地形做成同一层来做出相同的水体fx,然后在着色器中改变过滤值和颜色(水体、岩浆,等等)。
举一个碰撞的例子,当两个刚体发生碰撞时,你必须查看碰撞的bodyA和bodyB是什么类型的刚体,如果一个是气体(gravityScale/mass/density值实际上是0,所以它会飘浮)而另一个是“冰”,那么你就把这个球变成水……
再举一个例子,如果bodyA是岩浆,那么bodyB就变成气体……就像改变球的属性一样简单,所以它会改变在box2d中响应,你要重新绘制游戏状态。
地形的例子: 静态地形可以是一个POLYGON刚体,它是用一个读取整个地形图象和建立一系列非透明像素的x,y (vec2)的函数制作的,然后返回给box2d函数。
动态地形可以只是一个mesh,当你碰它时,你会移除座标x,y上的触点,你必须用新的mesh重制这个物理刚体。
例如,当一个水滴(物理刚体CIRCLE)溅到一只鸭子(具有激活的传感器的物体刚体),你必须删除那个水滴,并改变鸭子的动画,使新状态呈现,直到它完全被水充满,然后删除鸭子并记录结果。
水滴有很小的痕迹,这些是用linearVelocity和angularVelocity属性绘制的,你可以得到方向和速度,这样你就可以计算痕迹的角度和距离。
事实上,你想要什么行为都有。
sprinkle_islands_boss
在《Sprinkle Ilsands》中,水体着色器跟我们所学习的那个是不同的,它除了使用粒子fx,还多了linearVelocity属性。但行为可能还是一样的,当水球(刚体CIRCLE)击中火传感器,那么火就会熄灭,海里的水mesh也一样。至于岩石,你可以添加一些细节如颗粒效果等。
在这一面,我们找不到任何joint,那就是为什么它可能不必要,在《Sprinkle Ilsands》,软管就是绳子,这是我们在下一面中要分析的。
3、橡皮筋 《Contre Jour》、《割绳子》、《水果忍者》 我花了一个月时间才做出上述游戏的绳子原型,但我做完绳子后,我就觉得软刚体很容易做了,因为我更加理解接头了。
要制作一个逼真的绳子,你得创建一组刚体(CIRCLE或者POLYGON都行),把它们都依附在作为基座的STATIC刚体上。用于结合这些绳子刚体的joint有两种,DISTANCE或者REVOLUTE,但处于最末端(DYNAMIC)的连接基座(STATIC)的joint只能是制作弹力绳的ROPE joint。通过restitution 和frequency属性来调整response(反应)/damping(衰减)/elasticity(弹性)。
例子:
Box2D_ElasticRopes
为了制作一个软刚体,你得围绕另一个中心刚体(可以是STATIC或DYNAMIC)制作一系列CIRCLE刚体,它当然会影响其他刚体,如果你改变joint的类型,你会发现这个刚体会自动变形,你必须用mesh绘制整组刚体。
Contre-Jour
《Contre Jour》 在这款游戏中,你可以找到软刚体:可变形的地形;两种类型的绳子:弹性绳和固定绳。这些固定绳使用的技术比弹性绳的更高级。
snotDiagram
js Rope Segmented
《割绳子》cut-the-rope
这是Box2d物理做绳子的最佳案例。游戏中的绳子也是动态的,你可以看到沿着基座到球的mesh,球的一端是连着糖果的。
你可以像上一个例子一样做出这种绳子,设置球(糖果)的物理属性—-mass、density、gravityScale,可以做出泡泡效果。你可以用多层混合模式绘制出泡泡。另一种方法是把刚体变成传感器,并且你自己的重力算法移动它,但我们到第五面时才学习这种技术。
如果泡泡-刚体-球与青蛙或蜘蛛碰撞,或者玩家触击到泡泡,泡泡就会爆炸,为此你要给泡泡添加爆炸动画并再次改变糖果的物理属性……
案例代码:
if (vec2:distance( bFrog_Mouth, bCandy ) < maxDistance) then
– 把青蛙的动画从“空闲”改为“吃”
– 暂停输入
– 补间并触发游戏结束动画
end
4、重力 在这一面,我们可以发现许多使用力来对抗重力的游戏,但这是一种游戏玩法。例如,你可以根据box2d的正弦函数生成简单的地形,它会返回链或边形状的STATIC刚体。
tiny wings 2
你可以用Box2d做出你自己的《Tiny Wings》。基本原理就是,球(CIRCLE刚体)在重力的作用下下落,你可以通过触击屏幕增加下落的linearVelocity(线性速度),当触击在山丘合适的部分(你可以查看你的正弦函数的高度)释放时,下落速度会增加……另一种方法是只使用力。
例子:
Box2D_JumpRun
为了绘制循环,给拾取、发热状态等添加颗粒效果。材质可以用程序成生随机颜色图像做出来,用高斯噪声添加细节、边界,等等……
Jetpack-Joyride
《Jetpack Joyride》 你可以看出这款游戏的特征吗?如果你已经读过前面的例子了,那么你应该知道角色刚体球有相同的行为,你一定是用力对抗重力、各个飞行器的不同物理属性、导弹和各种交通工具,等等。
《Madcoaster》、《Rocket Chicken》、《Whale Trail》等游戏都是一样的。
但这个面还有其他机制,如行星物理、引力。
你可以使用简单的公式来模拟零重力physics.gravity(0,0),行量的吸引力如下图所示:
Box2D_Forces_Gravity
function Planet:attract(m)
– Direction of the force
local force = self.body.position – m.body.position
local d = force:len() — = m.body.position:dist(self.body.position)
force = force:normalize()
local dir = vec2(self.mass/m.body.mass, self.mass/m.body.mass)
– Magnitude of the force
local strength = (GRAVITY * self.mass * m.body.mass)/(d*d)
force = force * strength
m.body:applyForce(force)
stroke((1+math.floor(force.y))*110, (1+math.floor(force.x))*110, 10, 255)
– draw line between attractor/mover
line(m.body.x+force.x, m.body.y+force.y, (self.body.x), (self.body.y))
end
这个函数会使角色球绕着行星转。
5、线面 《拯救种子》、《蜡笔物理学》…… 只有线:通过绘制线条,你可以做出形状类CHAIN的刚体和刚体类STATIC或DYNAMIC。
对于关卡设计,障碍物也是STATIC,可以是EDGE或POLYGON……
saving-seeds-hd-doodle-physics-screenshot
用那种结构,你可以复制出一款像《拯救种子》一样的游戏。
Box2D_Lines
代码和第一面的是一样的,但你必须改变游戏的规则,你要从暂停的物理引擎开始,然后绘制和生成CHAIN静止形状,当玩家按下开始键时,游戏必须生成玩家的球(以及恢复、重力、质量等参数),重新开启物理引擎,只要一个指令(physics.pause() and physics.resume())就能完成了。
它只留在游戏循环中,用于确认碰撞和线性速率、改变游戏状态……
你可以通过打开或关闭重力,来改变整个游戏的现实,就像《Thomas was alone》或《ibb and obb》那样。
6、交通工具
《小轮车冒险》、《登山赛车》 如果你已经看到这里了,那么做一款关于疯狂交通工具的游戏吧。
做交通工作,只要把接头和轮子想成CIRCLE刚体,用锚点正确的旋转接头把POLYGON(小车、自行车等的形状)和它们连接起来。
用带纹理的mesh绘制自行车/小车的主要刚体、车轮的子画面,除非你使用软刚体做这些,否则添加痕迹、颗粒fx等。
代码生成的例子:
Box2D_BezierRampage
对于道路,使用一些噪点或正弦,可以是STATIC或DYNAMIC,你可以使用Bezier曲线。
code example
《小轮车冒险》 我会知道这款游戏和它的物理,多亏看了某人的一篇文章。
但文章作者没有提到任何有关Box2D的东西,但我猜这款游戏就是使用了Cocos2d(和Corona SDK)。无论如何,你现在知道怎么制作交通工具和横冲直撞的效果了。
在《Canvas Rider》中,有两种自行车模型,你可以在游戏中改变,你会发现自行车的刚体是一个允许一定damping的接头结构,当你改变自行车时,这个动态刚体就被破坏了,然后游戏就生成新的自行车类型。
另外,你在游戏中的自行车可以触到的线是静态CHAIN,当你设计道路时,鼠标触击的是x,y……像我们之前做的那样。
以上。希望你能用Box2D做出一些成果。
当然,使用Box2D,通过不同的方法制作的游戏还有很多,但它们可能综合使用了上述几种,例如,《时空幻境》、《超级食肉男孩》等,用可以用合适的刚体、机制和着色器制作出来。