我们都玩过愤怒的小鸟,该游戏一大特点是,两物体碰撞后,它会模拟现实世界物体碰撞后的反弹效果,因此游戏特别具有体感和逼真感,本节我们利用物理引擎Box2D,制作一个类似愤怒小鸟类型的碰撞游戏。

游戏的基本玩法是,用鼠标点击小球,移动鼠标选择小球的发射方向,松开鼠标按钮后,小球按照鼠标指向的方向发射出去,一旦小球与障碍物碰撞后,它会像现实世界那样反复弹跳,如果一系列碰撞后,小球能停留在木架上,游戏就算过关,基本场景如下:

推荐游戏自由度高托卡(使用物理引擎Box2D设计类愤怒小鸟的击球游戏)(1)

它类似于投篮,选定箭头方向,让小球发射后落入到绿色方块中。这个游戏的开发特点在于,我们充分利用物理引擎的帮助来实现像现实世界中的碰撞效果,如果没有引擎,我们必须自己计算小球各个方向的加速度,摩擦力,碰撞后的相互作用力等,那是非常复杂的。有了物理引擎,我们完全可以把各种复杂的细节交给引擎来控制。

接下来我们开始基本场景的设计,先把以前我们准备好的VUE项目复制一份,并改名为BallShooting,同时把相关开发库,例如createjs,Box2D等放入到static目录下:

推荐游戏自由度高托卡(使用物理引擎Box2D设计类愤怒小鸟的击球游戏)(2)

相关的开发库会附带在云课堂的代码附件里。我们进入到根目录,打开index.html,先把各个要用到的第三方库加载进来,代码修改如下:

<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, user-scalable=no, minimal-ui"> <meta name="apple-mobile-web-app-capable" content="yes"> <script type="text/javascript" src="http://img.studyofnet.com./static/tweenjs-0.5.1.min.js"></script> <script type="text/javascript" src="http://img.studyofnet.com./static/easeljs-0.7.1.min.js"></script> <script type="text/javascript" src="http://img.studyofnet.com./static/movieclip-0.7.1.min.js"></script> <script type="text/javascript" src="http://img.studyofnet.com./static/Box2dWeb-2.1.a.3.min.js"></script> <script type="text/javascript" src="http://img.studyofnet.com./static/preloadjs-0.4.1.min.js"></script> <script type="text/javascript"> window.createjs = createjs </script> <title>Shooting A Ball</title> </head> <body> <div id="app"></div> <!-- built files will be auto injected --> </body> </html>

接着进入src/目录,修改App.vue,将内容修改如下:

<template> <div id="app"> <game-container></game-container> </div> </template> <script> import GameContainer from './components/gamecontainer' export default { name: 'app', components: { GameContainer } } </script> <style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; margin-top: 60px; } </style>

我们在主入口组件中引入了一个GameContainer组件,接下来我们就实现该组件,组件的作用是搭建游戏的基本场景,进入components/目录,在里面生成一个gamecontainer.vue文件,然后添加如下内容:

<template> <div> <header> <div class="row"> <h1>Let shoot the Ball</h1> </div> </header> <div> <game-scene></game-scene> </div> </div> </template> <script> import GameScene from './GameSceneComponent' export default { components: { GameScene } } </script> <style scoped> body, h1, h2, p { margin: 0; padding: 0; } </style>

该组件搭建了游戏的html框架后,引入gameSceneComponent组件,我们几乎大部分游戏的逻辑设计都会实现在该组件里。同理在当前目录下新建一个文件,名为gamescenecomponent.vue,然后添加如下内容:

<template> <section id="game" class="row"> <canvas id="debug-canvas" width="480" height="360"> </canvas> <canvas id="canvas" width="480" height="360"> </canvas> </section> </template>

我们在里面设置两个画布组件,其中一个用来调试,另一个用来显示游戏画面,一旦所有设计调试通过后,我们就可以把调试画布组件给去除,留下第二个画布组件。接着我们在组件初始化代码中,将物理引擎中用到的组件都获取到,代码如下:

<script> export default { data () { return { canvas: null, debugCanvas: null, createWorld: null } }, mounted () { this.init() }, methods: { init () { this.cjs = window.createjs this.canvas = document.getElementById('canvas') this.stage = new this.cjs.Stage(this.canvas) // 导出物理引擎的各个组件 this.B2Vec2 = window.Box2D.Common.Math.b2Vec2 this.B2AABB = window.Box2D.Collision.b2AABB this.B2BodyDef = window.Box2D.Dynamics.b2BodyDef this.B2Body = window.Box2D.Dynamics.b2Body this.B2FixtureDef = window.Box2D.Dynamics.b2FixtureDef this.b2Fixture = window.Box2D.Dynamics.b2Fixture this.B2World = window.Box2D.Dynamics.b2World this.B2MassData = window.Box2D.Collision.Shapes.b2MassData this.B2PolygonShape = window.Box2D.Collision.Shapes.b2PolygonShape this.B2CircleShape = window.Box2D.Collision.Shapes.b2CircleShape this.B2DebugDraw = window.Box2D.Dynamics.b2DebugDraw this.B2MouseJointDef = window.Box2D.Dynamics.Joints.b2MouseJointDef this.B2RevoluteJointDef = window.Box2D.Dynamics.b2RevoluteJointDef // 每30个像素点的距离对应现实世界的一米长度 this.pxPerMeter = 30 this.shouldDrawDebug = false } } } </script>

接下来我们需要调用物理引擎,构造一个由引擎驱动的虚拟世界,在这个世界里,物体的碰撞效果由物理引擎来控制,我们所有游戏逻辑的设计都要基于引擎的驱动,相关代码如下:

createMyWorld () { // 设置重力加速度 var gravity = new this.B2Vec2(0, 9.8) this.world = new this.B2World(gravity) // 设置两个暂时实体对象 var bodyDef = new this.B2BodyDef() var fixDef = new this.B2FixtureDef() // 设置实体为静态物 bodyDef.type = this.B2Body.B2_staticBody bodyDef.position.x = 100 / this.pxPerMeter bodyDef.position.y = 100 / this.pxPerMeter // 设置实体形状为多边形 fixDef.shape = new this.B2PolygonShape() fixDef.shape.SetAsBox(500 / this.pxPerMeter, 500 / this.pxPerMeter) this.world.CreateBody(bodyDef).CreateFixture(fixDef) // 设置一个动态实体 bodyDef.type = this.B2Body.b2_dynamicBody bodyDef.position.x = 200 / this.pxPerMeter bodyDef.position.y = 200 / this.pxPerMeter this.world.CreateBody(bodyDef).CreateFixture(fixDef) }

我们的游戏也需要一个主循环来驱动它的运行,在主循环中,我们持续调用物理引擎的接口,让它根据物理定律不断更新页面动态,相关代码如下:

update () { this.world.Step(1 / 60, 10, 10) if (this.shouldBeDrawDebug) { this.world.DrawDebugData() } this.world.ClearForces() }, // 设置用于调试目的的图形绘制 showDebugDraw () { // 为了确保设计的正确性,我们可以把图形先进行调试绘制 // 确定没问题后再把图形绘制到画布里 this.shouldDrawDebug = true var debugDraw = new this.B2DebugeDraw() // 设置调试画布 debugDraw.SetSprite(document.getElementById('debug-canvas').getContext('2d')) debugDraw.SetFillAlpha(0.3) debugDraw.SetLineTickness(1.0) debugDraw.SetFlags(this.B2DebugDraw.e_shapeBit | this.B2Draw.e_jointBit) this.world.SetDebugDraw(debugDraw) }

我们准备了两个画布,一个画布用来调试绘制物体的原型,原型这个概念后面会深入探究,例如愤怒的小鸟它在物理引擎的世界里,对应的其实是一个正方形,而那些被攻击的猪,其原型就是圆形。

接着我们启动主循环,将实体绘制到调试画布中,并让他们运动起来:

start () { this.createMyWorld() this.showDebugDraw() this.cjs.Ticker.setFPS(60) this.cjs.Ticker.addEventListener('tick', this.stage) this.cjs.Ticker.addEventListener('tick', this.tick) }, tick () { if (this.cjs.Ticker.getPaused()) { return } this.update() }

完成上面代码后,我们就完成了基本框架的搭建和物理引擎的启动以及引擎驱动的虚拟环境的构造,上面代码运行后,页面加载后情况如下:

推荐游戏自由度高托卡(使用物理引擎Box2D设计类愤怒小鸟的击球游戏)(3)

页面启动后,在画布里会出现两个正方形,其中一个正方形会像现实世界一样做自由落体运动,它下落有一个加速度,在物理引擎的驱使下,正方形的下落与现实世界中物体的下落是一样的。

在后续章节中,我们将基于本节创建的物理引擎场景开发精美有趣的游戏。

,