专栏首页Coding迪斯尼使用物理引擎Box2D设计类愤怒小鸟的击球游戏--基本架构设置

使用物理引擎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)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

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

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

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 实现小球在弹射前的拉伸特效和动态障碍物特效

    当前我们实现小球弹射时,会先用鼠标点击小球,然后移动鼠标,当松开鼠标时,小球会弹射向鼠标松开的位置。我们按住小球的时间越长,小球弹射的力度就越大,但有一个问题是...

    望月从良
  • 实现红警式的建筑物拖拽生成特效

    望月从良
  • 寿司开卖:实现寿司制作特效和音响特效

    本节我们将继续上一节完成若干个小功能。首先要完成的是,当客户动画在主页面出现时,它左上角会冒泡,显示它想购买何种寿司,此时玩家可以点击左下角面板中各种元素,组合...

    望月从良
  • Flutter基础widgets教程-AppBar篇

    青年码农
  • 一款轮播组件的诞生

    早在几个月前,就想自己动手写个轮播图组件,因此也看了许多文章,断断续续过了几个月,今天终于有时间腾出手来给此插件做个总结,因此有了这篇文章。话不多说,先上 De...

    小皮咖
  • JS回调函数中的 this 指向(详细)

    1. obj.fun() fun 中的 this->obj ,自动指向.前的对象

    TimothyJia
  • 【译】理解JavaScript中的This,Bind,Call和Apply

    this关键词在JavaScript中是个很重要的概念,也是一个对初学者和学习其他语言的人来说晦涩难懂。在JavaScript中,this是一个对象的引用。th...

    嘉明
  • 狂热「小工」的9款Creator游戏源码及图文教程,等你来拿!

    他在 Cocos 论坛上公开了自己9款小游戏作品,完成度相当之高,是不可多得的Creator学习资源,下面是论坛链接地址:https://forum.cocos...

    张晓衡
  • 7. 偷用Swiper简改

    看这段代码应该就很清楚了,如果是android系统就渲染Pager如果是ios就使用横向的ScrollView,修改后的app首页如下:

    MasterVin
  • 实现红警式的建筑物拖拽生成特效

    望月从良

扫码关注云+社区

领取腾讯云代金券