前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >VUE游戏开发:使用Box2D模拟球体的飞行和撞击特效

VUE游戏开发:使用Box2D模拟球体的飞行和撞击特效

作者头像
望月从良
发布2018-07-19 18:30:03
8510
发布2018-07-19 18:30:03
举报
文章被收录于专栏:Coding迪斯尼Coding迪斯尼

本节,我们将利用Box2d引擎在页面中实现球体飞行和撞击效果。在现实中我们向外抛出一个球时,它在重力加速度的情况下会飞出一个弧线,撞到物体后它会反弹折射,我们利用Box2D可以在页面里模拟这些特性。我们将在页面里绘制一个小球,然后设置一些障碍物,我们用鼠标控制小球向外抛出的方向,小球碰到障碍物后会像现实中一样发生反弹和折射。完成本节后,我们得到效果如下:

如上图,右下角是一个圆球,左上角是障碍物,用鼠标点击小球并向左上角拖动时,小球就会模拟受到一股像外抛出的力量。当小球与左上角障碍物相撞后,会发生反射,效果如下:

左上角红色小球就是碰撞后停留在障碍物上,具体效果请点击‘阅读原文’参看视频。 首先我们用代码构造上图中的小球和由三个方块构造出的篮球架,在gamescenecomponent.vue中添加代码如下:

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

createGameLevel用于选择游戏的难度和关卡,在这里,我们先直接用来绘制篮球架和小球,其中createHoop用于生成篮球架,spawnBall用于生成小球。我们先看看小球绘制的实现:

代码语言:javascript
复制
// change 2 
      spawnBall () { 
        var positionX = 300 
        var positionY = 200 
        var radius = 13 
        // 构造球体的形状和表面积 
        var bodyDef = new this.B2BodyDef() 
        var fixDef = new this.B2FixtureDef() 

        fixDef.density = 0.6 
        fixDef.friction = 0.8 
        fixDef.restitution = 0.1 

        bodyDef.type = this.B2Body.b2_staticBody 
        bodyDef.position.x = positionX / this.pxPerMeter 
        bodyDef.position.y = positionY / this.pxPerMeter 

        fixDef.shape = new this.B2CircleShape(radius / this.pxPerMeter) 
        this.ball = this.world.CreateBody(bodyDef) 
        this.ball.CreateFixture(fixDef) 
      },

物体的生成需要定义两个属性变量,一个是body, 一个是fixture,body的设置决定物体的形状,fixuture决定物体的表皮属性,在代码中我们通过density设置物体密度,fricition设置物体的摩擦力,restitution设置物体碰撞后的恢复力,在设置body时,我们把小球指定为静态物体,然后通过B2CircleShape构造一个圆形体型,当我们调用world.CreateBody后,我们就在物理引擎的虚拟世界里制造了一个小球。

接下来我们看看篮球架的绘制,代码如下:

代码语言:javascript
复制
// change 6 
      createHoop () { 
        var hoopX = 50 
        var hoopY = 100 

        var bodyDef = new this.B2BodyDef() 
        var fixDef = new this.B2FixtureDef() 

        fixDef.density = 1.0 
        fixDef.friction = 0.5 
        fixDef.restitution = 0.2 

        bodyDef.type = this.B2Body.b2_staticBody 
        bodyDef.position.x = hoopX / this.pxPerMeter 
        bodyDef.position.y = hoopY / this.pxPerMeter 
        bodyDef.angle = 0 

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

        var body = this.world.CreateBody(bodyDef) 
        body.CreateFixture(fixDef) 

        bodyDef.type = this.B2Body.b2_staticBody 
        bodyDef.position.x = (hoopX + 45) / this.pxPerMeter 
        bodyDef.position.y = hoopY / this.pxPerMeter 
        bodyDef.angle = 0 

        fixDef.shape = new this.B2PolygonShape() 
        fixDef.shape.SetAsBox(5 / this.pxPerMeter, 5 / this.pxPerMeter) 
        body = this.world.CreateBody(bodyDef) 
        body.CreateFixture(fixDef) 

        // 构建篮板 
        bodyDef.type = this.B2Body.b2_staticBody 
        bodyDef.position.x = (hoopX - 5) / this.pxPerMeter 
        bodyDef.position.y = (hoopY - 40) / this.pxPerMeter 
        bodyDef.angle = 0 

        fixDef.shape = new this.B2PolygonShape() 
        fixDef.shape.SetAsBox(5 / this.pxPerMeter, 
        40 / this.pxPerMeter) 
        fixDef.restitution = 0.05 

        var board = this.world.CreateBody(bodyDef) 
        board.CreateFixture(fixDef) 
      }

篮球架由两个正方体和一个长方体组成,代码先绘制两个正方体,然后在绘制竖直的长方体,他们合在一起就形成了篮板。接着我们实现小球的弹射功能,这是本节的重点和难点。我们先实现一个获取小球所在位置的函数:

代码语言:javascript
复制
// change 3 
      ballPosition () { 
        var pos = this.ball.GetPosition() 
        return { 
          x: pos.x * this.pxPerMeter, 
          y: pos.y * this.pxPerMeter 
        } 
      },

接下来我们确定小球的发射方式,想要弹射小球时,鼠标先在小球上面按下,然后移动鼠标到目的地,然后松开鼠标,这时小球就会弹射出去。鼠标按下是的位置,与鼠标松开时的位置构成了一个方向向量,小球会根据这个方向发射出去。

在现实世界中,我们向某个方向抛出一个物体时,会对物体沿着指定方式施加一个冲击力,学过初中物理就可以知道,一个方向的力根据平行四边形法则,可以分解成任意两个方向的作用力,在这里,我们要把作用力分解成水平方向和竖直方向的作用力,如下图:

上面三角形中,r所对应的边就是外力的方向,根据平行四边形法则,我们把r分解成两个方向的力,分别是竖直方向的y和水平方向的x,竖直方向力的大小为r*sin(θ),水平方向的力大小为r*cos(θ),由于小球受到重力的作用,重力的方向与r所产生的竖直方向的力相反,因此竖直方向上的力y不断减少,直到变成负数,也就是竖直方向的力从向上转为向下,这就是为何小球被抛出后,它先向上做曲线运动,然后再向下做曲线下落。我们需要计算x和y的大小,把它合成一个向量,调用Box2D的接口,这样才能模拟力r作用到小球上。接下来我们需要计算θ的大小。

θ值不难计算,在上图中,向量r的低点就是鼠标在小球上按下时的位置,高点其实就是鼠标松开时的位置,我们把两个位置的y坐标和x坐标相减,就能得到上图的y和x,由此我们可以计算tan(θ),然后我们调用Math.atan计算tan的反函数就可以得到θ的大小。但是我们在计算时还需考虑到方向的问题,如下图:

中间的ball position其实就是鼠标按下时的位置,cursor就是鼠标松开时的位置,我们计算出θ值后,还得根据cursor所在的象限对θ值做一个变化,当鼠标在第一象限松开时,θ值不变,在第二,三象限松开时,θ需要加上π,在第四象限时,需要加上2*π。因此角度的计算代码如下:

代码语言:javascript
复制
// change 4 
      launchAngle (stageX, stageY) { 
        // 根据鼠标方向设置小球发射方向 
        var ballPos = this.ballPosition() 
        var diffX = stageX - ballPos.x 
        var diffY = stageY - ballPos.y 

        var degreeAddition = 0  //Q1 
        if (diffX < 0 && diffY > 0) { 
          console.log('launchAngle: Q2') 
          degreeAddition = Math.PI 
        } else if (diffX < 0 && diffY < 0) { 
          degreeAddition = Math.PI 
          console.log('launchAngle: Q3') 
        } else if (diffX > 0 && diffY < 0) { 
          console.log('launchAngle: Q4') 
          degreeAddition = Math.PI * 2 
        } 

        var theta = Math.atan(diffY / diffX) + degreeAddition 
        return theta 
      },

函数中传入的stageX,stageY就是鼠标松开时所在的页面坐标,我们计算出x,y,得到tan(θ)的值,然后判断鼠标松开时在哪个象限,根据所在象限确定θ是否需要加上π,还是2*π,或者是不加,有了角度之后,我们就需要确定r的大小,然后将r分解成两个方向上的力量。

弹射力r的大小如何计算呢?我们根据鼠标按下到松开的时间间隔来计算,这就像弹弹弓,当你把弹弓拉的越久,松手后弹射力就越强,我们看看代码的实现:

代码语言:javascript
复制
shootBall (stageX, stageY, ticksDiff) { 
        this.ball.SetType(this.B2Body.b2_dynamicBody) 
        var theta = this.launchAngle(stageX, stageY) 
        var r = Math.log(ticksDiff) * 50 

        var resultX = r * Math.cos(theta) 
        var resultY = r * Math.sin(theta) 
        // 让球产生自转 
        this.ball.ApplyTorque(30) 
        var vec = new this.B2Vec2(resultX / this.pxPerMeter, resultY / this.pxPerMeter) 
        // 给球体添加指定方向的冲击力从而让球发射出去 
        this.ball.ApplyImpulse(vec, this.ball.GetWorldCenter()) 

        this.ball = undefined 
      },

函数传入参数stageX,stageY表示鼠标松开时的页面坐标,ticksDiff记录鼠标按下到松开的时长,代码先调用SetType把小球有静止物体转变为运动物体,然后调用launchAngle计算出力分解的夹角,在这里需要注意的是,弹射力r的确定,这里使用的是log(ticksDiff)*50,也就是将鼠标按下到松开的时间取对数后再乘以50.着意味着鼠标按着的时间越久,弹射力就越大,然而力量的大小很难直接从鼠标按下的时间来决定,力量的大小不好根据时间来线性增加,我们这里默认力量的大小与时间成一个对数关系,当然你也可以用另外一种数学关系来确定弹射力r与鼠标按下时间的连续,上面只是一种经验性的做法。

有了弹射力r,以及分解角度,我们就可以计算水平方向和竖直方向的作用力,然后将两个力组合成向量B2Vec2,当我们把这个力的向量作为参数,调用ApplyImpulse函数后,引擎就会模拟弹射力r作用到小球身上,在现实世界中,当球抛出去后,它自己会有一个自旋,为了实现这个效果,我们调用ApplyTorque(30),这样的话,在页面绘制时,小球就会有一个自旋效果。

接下来我们再完成一些相关代码:

代码语言:javascript
复制
createMyWorld () { 
        // 设置重力加速度 
        var gravity = new this.B2Vec2(0, 9.8) 
        this.world = new this.B2World(gravity, true) 
        // change 8 
        this.createGameLevel() 
      },

在调用createMyWorld构建虚拟世界时,我们就调用createGameLevel来构造小球和篮板。

代码语言:javascript
复制
data () { 
      return { 
        canvas: null, 
        debugCanvas: null, 
        createWorld: null, 
        // change 9 
        isPlaying: true, 
        tickWhenDown: 0, 
        tickWhenUp: 0 
      } 
    }, 
    ... 
    methods: { 
      init () { 
        ... 
        // change 10 
        this.stage.on('stagemousedown', this.stageMouseDown) 
        this.stage.on('stagemouseup', this.stageMouseUp) 
        ... 
      }, 
      // change 11 
      stageMouseDown (e) { 
        console.log('mouse down') 
        if (!this.isPlaying) { 
          console.log('mouse down return') 
          return 
        } 
        this.tickWhenDown = this.cjs.Ticker.getTicks() 
        console.log('mousedown', this.tickWhenDown) 
      }, 
      stageMouseUp (e) { 
        if (!this.isPlaying) { 
          return 
        } 

        this.tickWhenUp = this.cjs.Ticker.getTicks() 
        var ticksDiff = this.tickWhenUp - this.tickWhenDown 
        this.shootBall(e.stageX, e.stageY, ticksDiff) 
        // 发射后500毫秒再生成一个小球 
        setTimeout(this.spawnBall, 500) 
      }, 
    }

我们监听两个鼠标事件,分别是按下事件和松开事件,当鼠标按下时,我们开始记录按下时间,当鼠标松开时,计算鼠标按下了多久,同时得到此时鼠标所在的坐标,然后调用shootBall引发小球受到作用力r后的弹射特效,同时在500毫秒后,在原位置重新绘制一个新的小球。

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

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

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

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

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