Products
GG网络技术分享 2025-03-18 16:14 3
我们都玩过愤怒的小鸟,该游戏一大特点是,两物体碰撞后,它会模拟现实世界物体碰撞后的反弹效果,因此游戏特别具有体感和逼真感,本节我们利用物理引擎Box2D,制作一个类似愤怒小鸟类型的碰撞游戏。
游戏的基本玩法是,用鼠标点击小球,移动鼠标选择小球的发射方向,松开鼠标按钮后,小球按照鼠标指向的方向发射出去,一旦小球与障碍物碰撞后,它会像现实世界那样反复弹跳,如果一系列碰撞后,小球能停留在木架上,游戏就算过关,基本场景如下:
它类似于投篮,选定箭头方向,让小球发射后落入到绿色方块中。这个游戏的开发特点在于,我们充分利用物理引擎的帮助来实现像现实世界中的碰撞效果,如果没有引擎,我们必须自己计算小球各个方向的加速度,摩擦力,碰撞后的相互作用力等,那是非常复杂的。有了物理引擎,我们完全可以把各种复杂的细节交给引擎来控制。
接下来我们开始基本场景的设计,先把以前我们准备好的VUE项目复制一份,并改名为BallShooting,同时把相关开发库,例如createjs,Box2D等放入到static目录下:
相关的开发库会附带在云课堂的代码附件里。我们进入到根目录,打开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=\"./static/tweenjs-0.5.1.min.js\"></script>
<script type=\"text/javascript\" src=\"./static/easeljs-0.7.1.min.js\"></script>
<script type=\"text/javascript\" src=\"./static/movieclip-0.7.1.min.js\"></script>
<script type=\"text/javascript\" src=\"./static/Box2dWeb-2.1.a.3.min.js\"></script>
<script type=\"text/javascript\" src=\"./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()
}
完成上面代码后,我们就完成了基本框架的搭建和物理引擎的启动以及引擎驱动的虚拟环境的构造,上面代码运行后,页面加载后情况如下:
页面启动后,在画布里会出现两个正方形,其中一个正方形会像现实世界一样做自由落体运动,它下落有一个加速度,在物理引擎的驱使下,正方形的下落与现实世界中物体的下落是一样的。
在后续章节中,我们将基于本节创建的物理引擎场景开发精美有趣的游戏。
在vue中引入joint.js的问题,之前在网上搜了很多,都没有给出一个确切的答案,捣鼓了两天终于弄明白了,做个记录。
首先,我参考了一篇来自stackoverflow的文章
看完这篇文章,大家应该至少大致怎么做了,下面我们来具体看一下:
首先在vue项目中运行npm install jointjs --save
然后在入口文件,我的是main.js,也有可能是app.js中加入下面两行,把joint.js和jquery作为全局变量
window.$ = require(\'jquery\'); window.joint = require(\'jointjs\'); |
这里需要注意的是,joint.js依赖backbone、jquery和lodash,在通过script方式引入时,需要一一引入这些文件,但通过vue的npm时不需要,npm引入的joint.js已经默认封装好了这些。
通过这样引入还不够,可能会遇到图可以正常加载,但无法拖拽的问题,遇到这些问题一般是joint.js和自己vue项目中的环境冲突了,导致无法读取或者读取错误。
我原来的项目中安装了element、iview、axios、vuex、jquery,再安装joint.js后,jointjs无法正常加载,后来重新建了一个项目,只安装了element、axios、vuex,为避免jquery和joint.js中的jquery冲突,后来没有装jquery。
这样就行了么?就可以运行上文链接中的例子了么?像这样:
<template> <div> <h1>Home</h1> <div id=\"myholder\"></div> </div> </template> <script> export default { created() { let graph = new joint.dia.Graph; let paper = new joint.dia.Paper({ el: $(\'#myholder\'), width: 600, height: 200, model: graph, gridSize: 1 }); let rect = new joint.shapes.basic.Rect({ position: { x: 100, y: 30 }, size: { width: 100, height: 30 }, attrs: { rect: { fill: \'blue\' }, text: { text: \'my box\', fill: \'white\' } } }); let rect2 = rect.clone(); rect2.translate(300); let link = new joint.dia.Link({ source: { id: rect.id }, target: { id: rect2.id } }); graph.addCells([rect, rect2, link]); } } </script> |
NoNoNo,注意到这里是把渲染放在了created的生命周期里,根据vue的生命周期,是无法找到joint的挂载div的el: $(\'#myholder\'),也就是说,运行会报错,我的解决方法是把div放了一个click,把joint的内容从created中拿出,放在methods中,需要点击一下才可显示哦,还不太完美,以待改进(~ ̄▽ ̄)~
也就是说,代码会变成这样:
<template> <div> <div id=\"myholder\" @click=\"click_joint\"></div> </div> </template> <script> export default { methods:{ click_joint() { let graph = new joint.dia.Graph; let paper = new joint.dia.Paper({ el: $(\'#myholder\'), width: 600, height: 200, model: graph, gridSize: 1 }); let rect = new joint.shapes.basic.Rect({ position: { x: 100, y: 30 }, size: { width: 100, height: 30 }, attrs: { rect: { fill: \'blue\' }, text: { text: \'my box\', fill: \'white\' } } }); let rect2 = rect.clone(); rect2.translate(300); let link = new joint.dia.Link({ source: { id: rect.id }, target: { id: rect2.id } }); graph.addCells([rect, rect2, link]); } } } </script> |
点明一下,通过npm引入只要install jointjs就可以,不需要install lodash、backbone、jquery,也不需要在页面中导入joint.css文件。笔者之前通过script方式引入joint.js,试了很多次,都没有成功,一直读取joint.js文件出错,如果其他小伙伴尝试成功,欢迎交流分享。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
Demand feedback