使用物理引擎Box2D设计类愤怒小鸟的击球游戏--基本架构设置

我们都玩过愤怒的小鸟,该游戏一大特点是,两物体碰撞后,它会模拟现实世界物体碰撞后的反弹效果,因此游戏特别具有体感和逼真感,本节我们利用物理引擎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()
      }

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

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

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

原文发布于微信公众号 - Coding迪斯尼(gh_c9f933e7765d)

原文发表时间:2018-06-29

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏数据小魔方

数据标签太长了,怎么办……

今天给大家讲解在图表中长数据标签的特殊处理方法! ▽ 如果你的图表要求必须添加数据标签的话 最大的困惑就是对于哪些特别长的数据标签 加上之后图表是这样的 ? 看...

38180
来自专栏小文博客

图片无损放大工具PhotoZoom

70020
来自专栏编程

前端开发者常用的 9个JavaScript 图表库

英文: Anton Shaleynikov 译文:葡萄城控件 www.cnblogs.com/powertoolsteam/p/top-9-javascri...

39750
来自专栏理论坞

【教程】C4D样条字设计终极版 | 附源文件工程

在站酷关于C4D的样条约束文字设计教程自己注到的共有三篇, 设计文章写的都非常细致,效果也都是各有长的,有很多学习点 然后最近本人也在国外设计B站看到了几位国外...

11520
来自专栏iOS开发随笔

SCNView

在渲染过程过中,视图模型常常会出现锯齿边缘。这是因为模型是由多边形组成的,当显卡运算频率不够高,或者显存不够大的时候,“多边形”绘制速度比较慢,就会出现锯齿。此...

351130
来自专栏BestSDK

表格设计的六种打开方式,正确提升表格的阅读效率

在设计数据类产品、后台配置产品时,PD 常常会指着一块地方说「这儿放个表格,需要有balabala…」,而表格的结构不外乎这几种类型: 垂直布局 水平布局 矩阵...

32350
来自专栏Jerry的SAP技术分享

SAP成都研究院非典型程序猿,菜园子小哥:当我用UI5诊断工具时我用些什么

身边有些年轻同事曾经向我表达过这种困扰:尽管完成日常工作没有任何问题,但是还想更进一步,把代码写得更好些,做到精益求精。现在写的代码能实现功能,但是不知道可以怎...

17530
来自专栏数据小魔方

数据透视表入门

今天跟大家分享有关数据透视表入门的技巧! 数据透视表是excel附带功能中为数不多的学习成本低、投资回报率高、门槛低上手快的良心技能! 对于日程的排序、汇总、...

38760
来自专栏技术总结

UIKit Dynamics 置身真实世界

273100
来自专栏Guangdong Qi

iOS开发常用之 图表

15910

扫码关注云+社区

领取腾讯云代金券