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

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

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

左上角红色小球就是碰撞后停留在障碍物上,具体效果请点击‘阅读原文’参看视频。 首先我们用代码构造上图中的小球和由三个方块构造出的篮球架,在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 
        } 
      },

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

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

上面三角形中,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*π。因此角度的计算代码如下:

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

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

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

原始发表时间:2018-07-10

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

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏HT

根据矩阵变化实现基于 HTML5 的 WebGL 3D 自动布局

在数学中,矩阵是以行和列排列的数字,符号或表达式的矩形阵列,任何矩阵都可以通过相关字段的标量乘以元素。矩阵的主要应用是表示线性变换,即f(x)= 4 x等线性函...

27550
来自专栏一心无二用,本人只专注于基础图像算法的实现与优化。

SSE图像算法优化系列十二:多尺度的图像细节提升。

无意中浏览一篇文章,中间提到了基于多尺度的图像的细节提升算法,尝试了一下,还是有一定的效果的,结合最近一直研究的SSE优化,把算法的步骤和优化过程分享给大家。...

29680
来自专栏racaljk

A星寻路算法(A* Search Algorithm)

你是否在做一款游戏的时候想创造一些怪兽或者游戏主角,让它们移动到特定的位置,避开墙壁和障碍物呢?

68930
来自专栏游戏杂谈

as3中颜色矩阵滤镜ColorMatrixFilter的使用

上面的例子,也是游戏开发中比较常用的功能,与“怪物”战斗后,将其“灰”掉。这其中最重要的还是对AS3颜色矩阵滤镜(ColorMatrixFilter)的使用。

27130
来自专栏Golang语言社区

Go项目开发----2048小游戏(上)

刚接触go语言不久,前段时间看到一个2048的项目开发教程,于是就试着练了下手。我的环境采用的是Ubuntu Linux环境。 源码下载: https://gi...

37440
来自专栏小红豆的数据分析

小蛇学python(9)matplotlib的基本使用

matplotlib作为python中可视化最经典的库,是个不得不学习的东西。尽管长江后浪推前浪,涌现出了很多更好的可视化库,比如Plotly。不过,它们几乎全...

20330
来自专栏钱塘大数据

【干货】这17个技能,让你的Excel飞一样的提升

1、最快数据行公式求和 选取空行,点Σ(或按Alt + =)可以快速设置求和公式 ? 2、多区域最快求和 如果求和的区域有多个,可以选定位,然后再点Σ(或按A...

38760
来自专栏GIS讲堂

Arcgis for Js之GeometryService实现测量距离和面积

距离和面积的测量时GIS常见的功能,在本节,讲述的是通过GeometryService实现测量面积和距离。先看看实现后的效果:

45210
来自专栏向治洪

自定义Interpolator

nterpolator这个东西很难进行翻译,直译过来的话是补间器的意思,它的主要作用是可以控制动画的变化速率,比如去实现一种非线性运动的动画效果。那么什么叫做非...

22970
来自专栏hightopo

基于HTML5和WebGL的3D网络拓扑结构图

29330

扫码关注云+社区

领取腾讯云代金券