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

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


VUE游戏开发:使用Box2D模拟球体的飞行和撞击特效_第1张图片
屏幕快照 2018-07-07 上午11.26.04.png

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

VUE游戏开发:使用Box2D模拟球体的飞行和撞击特效_第2张图片
屏幕快照 2018-07-07 上午11.26.27.png

左上角红色小球就是碰撞后停留在障碍物上,具体的动画特效请参更详细的讲解和代码调试演示过程,请点击链接

首先我们用代码构造上图中的小球和由三个方块构造出的篮球架,在gamescenecomponent.vue中添加代码如下:

// change 1
createGameLevel () {
    this.createHoop()
    // 生成一个小球
    this.spawnBall()
},

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

// 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后,我们就在物理引擎的虚拟世界里制造了一个小球。

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

// 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)
      }

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

// change 3
      ballPosition () {
        var pos = this.ball.GetPosition()
        return {
          x: pos.x * this.pxPerMeter,
          y: pos.y * this.pxPerMeter
        }
      },

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

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

VUE游戏开发:使用Box2D模拟球体的飞行和撞击特效_第3张图片
屏幕快照 2018-07-10 下午3.56.28.png

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

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

VUE游戏开发:使用Box2D模拟球体的飞行和撞击特效_第4张图片
屏幕快照 2018-07-10 下午4.06.30.png

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

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

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),这样的话,在页面绘制时,小球就会有一个自旋效果。

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

createMyWorld () {
        // 设置重力加速度
        var gravity = new this.B2Vec2(0, 9.8)
        this.world = new this.B2World(gravity, true)
        // change 8
        this.createGameLevel()
      },

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

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毫秒后,在原位置重新绘制一个新的小球。

上面代码完成后,就可以实现本文开头所描述的功能,具体效果请参看视频:
更详细的讲解和代码调试演示过程,请点击链接

更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:


VUE游戏开发:使用Box2D模拟球体的飞行和撞击特效_第5张图片
这里写图片描述

你可能感兴趣的:(VUE游戏开发:使用Box2D模拟球体的飞行和撞击特效)