专栏首页Coding迪斯尼使用Box2D实现物体的碰撞检测和实现自动化背景布置

使用Box2D实现物体的碰撞检测和实现自动化背景布置

我们本节要实现的是,当用户把小球投入篮框,如果小球能从篮框中间漏下去,那么就可以算得分。这就需要我们进行碰撞检测,Box2D给我们提供良好机制能实现这点功能。我们在篮框的两个小方块之间构造一个物体,当小球击中这个物体时,就相当于穿越了篮框。

我们现在两个小方块间增加一个长方体作为碰撞感应器,一旦小球穿过篮框时,一定会从两个小方块中间穿过,于是它一定会和中间的长方体碰撞,相应代码如下:

createHoop () {
        ...
        // change 1 在两个小方块间构造一个长方体用于碰撞检测
        bodyDef.type = this.B2Body.b2_staticBody
        bodyDef.position.x = (hoopX + 20) / this.pxPerMeter
        bodyDef.position.y = hoopY / this.pxPerMeter
        bodyDef.angle = 0

        fixDef.isSensor = true
        fixDef.shape = new this.B2PolygonShape()
        fixDef.shape.SetAsBox(20 / this.pxPerMeter, 3 / this.pxPerMeter)
        body = this.world.CreateBody(bodyDef)
        body.CreateFixture(fixDef)
      }

当一个物体的纹理对象的isSensor设置为true时,它就可以被其他问题“穿过”,上面代码完成后,效果如下,两个小方块间多增加了一个长方体:

接下来我们要设立一个碰撞监听函数,一旦小球碰到中间的横杆,我们的函数就能及时被调用,代码如下:

setupContactListener () {
        var B2ContactListener = this.Dynamics.b2ContactListener
        var contactListener = new B2ContactListener()
        contactListener.BeginContact = function (contact, mainfold) {
          if (contact.GetFixtureA().IsSensor() || contact.GetFixtureB().IsSensor()) {
            this.increaseScore()
          }
          var userDataA = contact.GetFixtureA().GetBody().GetUserData()
          var userDataB = contact.GetFixtureB().GetBody().GetUserData()
          if (userDataA !== null && userDataA.isBoundary ||
        userDataB !== null && userDataB.isBoundary) {
            // body 对应的是小球
            var body = contact.GetFixtureB().GetBody()
            if (userDataB !== null && userDataB.isBoundary) {
              body = contact.GetFixtureA.GetBody()
            }

            this.bodiesToRemove.push(body)
          }
        }.bind(this)

        this.world.SetContactListener(contactListener)
      },
      increaseScore () {
        this.score += 1
        console.log('ball fall through')
      },

Box2D中的Dynamics对象会导出一个子对象叫b2ContactListener,它会导出一系列接口把碰撞相关的数据或事件传递给我们。我们把自己开发的函数提交给它的beginContact接口,一旦有物体碰撞发生时,Box2D框架会调用我们的接口,并把碰撞的对象传入给我们。任何产生碰撞信息的物体一定会把isSensor设置为true,就像我们前面构造两个方块中间的长方体那样,于是传入BeginContact的两个碰撞对象,一旦我们提供的碰撞处理函数被调用了,那么很可能是小球穿过了两个方块中间的长方体,于是我们经过判断,确实是小球穿过长方体的话,就调用increaseScore()来增加投篮得分。如果我们想关注某个物体的碰撞事件,那么在构造该物体时,我们调用它的SetUserData设置用户数据,当碰撞发生后,如果我们能从传入的对象中得到用户数据,那表明我们关注的物体发生了碰撞事件,这一点我们下面会看到。

接下来我们在画面底部构造一个横条作为“地板”,小球投出后最终会落入到“地板”上,相关代码如下:

     // change4 在底部产生一个边界,小球投出后最终会停留在边界上
      createWorldBoundary () {
        console.log('create world boundary')
        var bodyDef = new this.B2BodyDef()
        var fixDef = new this.B2FixtureDef()

        bodyDef.type = this.B2Body.b2_staticBody
        bodyDef.position.x = -800 / this.pxPerMeter
        bodyDef.position.y = 300 / this.pxPerMeter
        bodyDef.angle = 0

        fixDef.shape = new this.B2PolygonShape()
        fixDef.shape.SetAsBox(2000 / this.pxPerMeter, 10 / this.pxPerMeter)

        var body = this.world.CreateBody(bodyDef)
        body.CreateFixture(fixDef)
        // 设立用户数据用于碰撞发生时进行判断
        body.SetUserData({isBoundary: true})
      }

注意到上面代码中,我们使用SetUserData设置了用户数据,一旦小球撞到地板时,我们在BeginContact的回调函数中会从传入的对象中,通过调用GetUserData获得上面通过SetUserData传入的数据。完成上面代码后,我们在初始化时执行上面代码就可以设置碰撞监听机制了,代码如下:

createGameLevel () {
        this.createHoop()
        // 生成一个小球
        this.spawnBall()

        // change 5
        this.createWorldBoundary()
        this.setupContactListener()
      },

当上面代码完成后,加载进页面执行结果如下:

在底部会产生一个绿色长条状地板,一旦球射出后会散落在绿色地板上,同时一旦求经过中间长方体时,BeginContact函数会被调用,进而increaseScore会被调用,我们可以在控制台上看到”ball fall through”的输出结果。

接下来我们要实现布景的动态设置,当前我们小球和篮框的位置都固定死,我们希望在不同的关卡,这些布景能够灵活变动,于是我们添加如下布景表示的代码:

data () {
      return {
      ....
              // change 6
        balls: {
          'SlowBall': {
            className: 'SlowBall',
            radius: 13,
            density: 0.6,
            friction: 0.8,
            restitution: 0.1
          },
          'BouncyBall': {
            className: 'BouncyBall',
            radius: 10,
            density: 1.1,
            friction: 0.8,
            restitution: 0
          }
        },
        levels: [
          {
            hoopPositon: {x: 50, y: 150},
            ballName: 'Slow ball',
            ballPosition: {x: 350, y: 250},
            ballRandomRange: {x: 60, y: 60},
            obstacles: []
          },
          {
            hoopPosition: {x: 50, y: 200},
            ballName: 'bouncy ball',
            ballPosition: {x: 350, y: 250},
            ballRandomRange: {x: 80, y: 80},
            obstacles: [
              {
                type: 'rect',
                graphicName: 'BrownSquare',
                position: {x: 150, y: 160},
                dimension: {width: 10, height: 10},
                angle: 45
              }
            ]
          },
          {
            hoopPosition: {x: 50, y: 160},
            ballName: 'slow ball',
            ballPosition: {x: 350, y: 250},
            ballRandomRange: {x: 80, y: 80},
            obstacles: [
              {
                type: 'rect',
                graphicName: 'BrownSquare',
                position: {x: 200, y: 160},
                dimension: {width: 10, height: 10},
                angle: 0
              },
              {
                type: 'rect',
                graphicName: 'BrownSquare',
                position: {x: 200, y: 120},
                dimension: {width: 10, height: 10},
                angle: 0
              }
            ]
          }
        ]   
     }

上面代码是不同关卡的布景设置,接下来我们会看上面代码如何使用,我们在原来的初始化函数中做一些修改:

createGameLevel () {
        //change 7
        var level = this.currentLevel
        this.createHoop(level)
        this.createObstacles(level)
    ....
}
//change 8
      createHoop (level) {
        var hoopX = level.hoopPosition.x
        var hoopY = level.hoopPosition.y
        ....
      }
/ change 9
      spawnBall () {
       var level = this.currentLevel
        var ball = this.balls[level.ballName]
        var positionX = level.ballPosition.x + Math.random() * level.ballRandomRange.x - level.ballRandomRange.x / 2
        var positionY = level.ballPosition.y + Math.random() * level.ballRandomRange.y / 2 - level.ballRandomRange.y / 2
        var radius = ball.radius
        // 构造球体的形状和表面积
        var bodyDef = new this.B2BodyDef()
        var fixDef = new this.B2FixtureDef()

        fixDef.density = ball.density
        fixDef.friction = ball.friction
        fixDef.restitution = ball.restitution
    ....
}

我们在绘制篮框或小球时,相关信息全部从levels数组中获取,如此一来,我们就可以通过增加levels数组中的内容,或改变其中相关内容进而非常容易的去修改页面上各种物体的绘制。想象一下我们游戏要开发多种关卡,每种关卡难度不一样,于是篮框的高度,小球的位置,小球发射后的速度等设置要根据关卡的难度而不同,为了迎合这种多样性的需求,我们通过修改levels数组中的信息即可,不需要对逻辑代码做修改,这样就能保证整个系统的稳定性。

完成上面代码之后,我们在初始化函数中增加一行代码:

init () {
    ....
    this.currentLevel = this.levels[2]
}

加载页面时,得到结果如下:

通过设置currentLevel,指向levels数组中的不同元素,页面布景就能轻松的进行相应绘制。

本文分享自微信公众号 - Coding迪斯尼(gh_c9f933e7765d)

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

原始发表时间:2018-08-01

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

我来说两句

0 条评论
登录 后参与评论

相关文章

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

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

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

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

    望月从良
  • VUE+WebPack游戏设计:欲望都市城市图层的设计

    望月从良
  • bug 回忆录(四)

    @author Ken @time 2020-09-27 21:30:59 @description 转载请备注出处,谢谢

    公众号---人生代码
  • 【Flutter 专题】61 图解基本 Button 按钮小结 (一)

    Button 在日常中是必不可少的,和尚尝试过不同类型的 Button,也根据需求自定义过,今天和尚系统的学习一下最基本的 Button;

    阿策小和尚
  • php实现img转ASCII编码图片

    经过3晚上的研究,成功实现用php将图片转换成ascii编码图 主要原理:分析像素点的灰度值,用不同字符的深浅度表示(@和.),然后进行字符串组合,输出

    仙士可
  • WordPress博客网站下雪特效

    沈唁
  • HTML5 Canvas炫酷的火焰风暴动画

    越陌度阡
  • .glb格式的模型怎么在three.js中展示

    3D软件中导出的格式一般有.obj 和.glb ,下面是blender 2.8.2 生成模型并在three.js中展示的流程

    tianyawhl
  • 实现一个同步的RenderApplication

    逍遥剑客

扫码关注云+社区

领取腾讯云代金券