前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用Box2D实现物体的碰撞检测和实现自动化背景布置

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

作者头像
望月从良
发布2018-08-16 15:48:58
9230
发布2018-08-16 15:48:58
举报
文章被收录于专栏:Coding迪斯尼Coding迪斯尼

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

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

代码语言:javascript
复制
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时,它就可以被其他问题“穿过”,上面代码完成后,效果如下,两个小方块间多增加了一个长方体:

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

代码语言:javascript
复制
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设置用户数据,当碰撞发生后,如果我们能从传入的对象中得到用户数据,那表明我们关注的物体发生了碰撞事件,这一点我们下面会看到。

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

代码语言:javascript
复制
     // 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传入的数据。完成上面代码后,我们在初始化时执行上面代码就可以设置碰撞监听机制了,代码如下:

代码语言:javascript
复制
createGameLevel () {
        this.createHoop()
        // 生成一个小球
        this.spawnBall()

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

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

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

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

代码语言:javascript
复制
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
              }
            ]
          }
        ]   
     }

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

代码语言:javascript
复制
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数组中的信息即可,不需要对逻辑代码做修改,这样就能保证整个系统的稳定性。

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

代码语言:javascript
复制
init () {
    ....
    this.currentLevel = this.levels[2]
}

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

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-08-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Coding迪斯尼 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档