首页 > 编程语言 >2D物理引擎 Box2D for javascript Games 第四章 将力作用到刚体上

2D物理引擎 Box2D for javascript Games 第四章 将力作用到刚体上

时间:2023-10-16 19:58:28浏览次数:47  
标签:javascript 将力 Box2D 球体 fixtureDef var new brick

2D物理引擎 Box2D for javascript Games 第四章 将力作用到刚体上

将力作用到刚体上

Box2D 是一个在力作用下的世界,它可以将力作用于刚体上,从而给我们一个更加真实的模拟。

但是,如果你想要移动刚体,发射子弹,抛掷小鸟,驾驶汽车和当你在玩物理游戏时你看到的一切令人起劲的事情,那么你必须要手动的将力作用刚体上。

在本章,你将学习怎样通过不同的方法将力作用于刚体上使它移动,管理很多新的Box2D特征,包含如下:

  • 将力作用到刚体上
  • 将冲量作用到刚体上
  • 设置刚体的线性速率
  • 知道刚体的质量
  • 根据你的游戏设置需要应用正确的力的作用方式
  • 在你的游戏中使用物理和非物理资源

通过本章的学习,你将有能力创建愤怒的小鸟的一个关卡,包含小鸟的发射器。

苹果掉落,修正

传说牛顿发现重力是因为一个苹果掉下来砸到了他。你将学习用力将一些对象从地面提升起来。

我们将从三个 dynamic 类型的球体放置在一个 static 类型的地面上的脚本开始。

下面将是你开始的基础代码

function init() {
    var   b2Vec2 = Box2D.Common.Math.b2Vec2
    ,  b2AABB = Box2D.Collision.b2AABB
    ,	b2BodyDef = Box2D.Dynamics.b2BodyDef
    ,	b2Body = Box2D.Dynamics.b2Body
    ,	b2FixtureDef = Box2D.Dynamics.b2FixtureDef
    ,	b2Fixture = Box2D.Dynamics.b2Fixture
    ,	b2World = Box2D.Dynamics.b2World
    ,	b2MassData = Box2D.Collision.Shapes.b2MassData
    ,	b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape
    ,	b2CircleShape = Box2D.Collision.Shapes.b2CircleShape
    ,	b2DebugDraw = Box2D.Dynamics.b2DebugDraw
    ,  b2MouseJointDef =  Box2D.Dynamics.Joints.b2MouseJointDef
    ;

    var worldScale = 30; // box2d中以米为单位,1米=30像素
    var gravity = new b2Vec2(0, 10);
    var sleep = true;
    var world;
    var velIterations = 10;// 速率约束解算器
    var posIterations = 10;// 位置约束解算器
    var sphereVector;

    function main(){
    world = new b2World(gravity, sleep);
    debugDraw();
    floor();

    sphereVector = [];
    for(var i=0; i<3; i++){
        sphereVector.push(sphere(170+i*150, 410, 40));
    }

    setInterval(updateWorld, 1000 / 60);
    }
    main();


    function sphere(px, py, r){
    var bodyDef = new b2BodyDef();
    bodyDef.position.Set(px/worldScale, py/worldScale);
    bodyDef.type = b2Body.b2_dynamicBody
    bodyDef.userData = py;
    var circleShape = new b2CircleShape(r/worldScale);
    var fixtureDef = new b2FixtureDef();
    fixtureDef.shape = circleShape;
    fixtureDef.density = 2;
    fixtureDef.restitution = .4
    fixtureDef.friction = .5;
    var theSphere = world.CreateBody(bodyDef);
    theSphere.CreateFixture(fixtureDef);
    return theSphere;
    }

    function floor(){
    var bodyDef = new b2BodyDef();
    bodyDef.position.Set(320/worldScale, 465/worldScale);
    var polygonShape = new b2PolygonShape();
    polygonShape.SetAsBox(320/worldScale, 15/worldScale);
    var fixtureDef = new b2FixtureDef();
    fixtureDef.shape = polygonShape;
    fixtureDef.restitution = .4;
    fixtureDef.friction = .5;
    var theFloor = world.CreateBody(bodyDef);
    theFloor.CreateFixture(fixtureDef);
    }


    function updateWorld() {
    world.Step(1/30, 10, 10);// 更新世界模拟
    world.DrawDebugData(); // 显示刚体debug轮廓
    world.ClearForces(); // 清除作用力
    }

    //setup debug draw
    function debugDraw(){
    var debugDraw = new b2DebugDraw();
    debugDraw.SetSprite(document.getElementById("canvas").getContext("2d"));
    debugDraw.SetDrawScale(worldScale);
    debugDraw.SetFillAlpha(0.5);
    debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
    world.SetDebugDraw(debugDraw);
    }
};

源码在: article/ch04/ch04-1.html

你应该很熟悉上面的代码了,它只是对之前章节的图腾脚本稍作修改而成的。

虽然向 sphere()方法传入以像素为单位的中心坐标和半径后,会创建一个 dynamic 类型的球体,但是 updateWorld、floor、debugDraw 和之前是一样没变

测试网页,你会发现三个球体依次摆地面上

image

每一个球体都存储在 sphereVector 中,从左到右分别是:sphereVector[0],sphereVector[1],sphereVector[2]。

我们开始让球体弹跳吧。在这个示例中有三个 dynamic 类型的刚体,因为它们将用来让你学习三种不同的方式将力作用于刚体上,每一种都有优点和缺点。

力,冲量和线速率

让我们直接来看看Main()方法中新加的这几行代码:

function main(){
    world = new b2World(gravity, sleep);
    debugDraw();
    floor();

    sphereVector = [];
    for(var i=0; i<3; i++){
        sphereVector.push(sphere(170+i*150, 410, 40));
    }

    var force = new b2Vec2(0,-15);
    var sphereCenter = sphereVector[0].GetWorldCenter(); 
    sphereVector[0].ApplyForce(force, sphereCenter);
    sphereCenter = sphereVector[1].GetWorldCenter();
    sphereVector[1].ApplyImpulse(force,sphereCenter);
    sphereVector[2].SetLinearVelocity(force);

    setInterval(updateWorld, 1000 / 60);
}

这是本章的核心,并且有着很多新的知识,所以让我们,一行行的来解释它们:

var force = new b2Vec2(0,-15);

首先,我们需要创建一个 b2Vec2 类型的变量,它将代表我们想要作用到所有球体上的力。设置为(0,-15),意味着这是一个垂直方向的力,它将是球体跳跃起来。

光有一个力是不够的,我们还需要一个应用力的作用点。在本例中,我们想将力的作用在每个球体的质心。

var sphereCenter = sphereVector[0].GetWorldCenter();

我们可以使用 b2Body 的 GetWorldCenter() 方法,来确定刚体质心。它将返回一个 b2Vec2 对象的质心。

在这里,shpereCenter 变量将包含左边球体的质心。

sphereVector[0].ApplyForce(force,sphereCenter);

之前的那些代码展示了第一种将力作用到刚体上的方法。

ApplyForce() 方法将力作用到一点上,通常使用的牛顿(N)为单位。如果力没有作用在质心,它将产生一个扭矩并影响角速度,这就是为什么我们想要获得质心的原因。

如果刚体在睡眠状态,ApplyForce() 将可以唤醒它们。

sphereCenter=sphereVector[1].GetWorldCenter();

当我们完成来了将力作用的左边的球体时,那么让我们将逐一集中到中间的球体上。

我们将像之前一样获取质心,然后添加另一种作用力的方法。

sphereVector[1].ApplyImpulse(force,sphereCenter);

ApplyImpulse() 方法将一个冲量作用到一个点上,通常使用牛顿每秒(n/s),将会立刻改变刚体的速率。

如果冲量的作用点不是质心的话,那么它将会改变角速度,但是在本例中没有出现,ApplyImpulse() 方法也将唤醒在睡眠状态的刚体。

现在我们完成了对中间的球体作用力,让我们来看看右边的球体:

sphereVector[2].SetLinearVelocity(force);

SetLinearVelocity 方法设置质心的线速度。

它不像 ApplyForce() 和 ApplyImpulse() 方法那样需要第二个参数,因为它一直是作用在质心的。

你是否对球体将怎样反应力的作用而刚到疑惑?

那么我们添加一些代码来看看球体做了些什么吧。

首先我们在 sphere() 方法中将球体的垂直坐标存储到 userData 属性中:

function sphere(px, py, r){
    var bodyDef = new b2BodyDef();
    bodyDef.position.Set(px/worldScale, py/worldScale);
    bodyDef.type = b2Body.b2_dynamicBody
    bodyDef.userData = py;
    var circleShape = new b2CircleShape(r/worldScale);
    var fixtureDef = new b2FixtureDef();
    fixtureDef.shape = circleShape;
    fixtureDef.density = 2;
    fixtureDef.restitution = .4
    fixtureDef.friction = .5;
    var theSphere = world.CreateBody(bodyDef);
    theSphere.CreateFixture(fixtureDef);
    return theSphere;
}

这里没有什么新的知识,因此我们将不再赘述 userData 属性的作用。

现在,我们将在输出窗口输出一些文本,向下面这样改变 updateWorld() 方法:

function updateWorld() {
    var maxHeight;
    var currHeight;
    var outHeight;
    world.Step(1/30,10,10);
    for (var i = 0; i<3; i++) {
        maxHeight=sphereVector[i].GetUserData();
        currHeight=sphereVector[i].GetPosition().y*worldScale;    
        maxHeight=Math.min(maxHeight,currHeight);
        sphereVector[i].SetUserData(maxHeight);
        outHeight=sphereVector[i].GetUserData();
        console.log("Sphere "+i+":"+Math.round(outHeight));
    }
    world.DrawDebugData(); // 显示刚体debug轮廓
    world.ClearForces(); // 清除作用力
}

我想保持跟踪每个球体达到的最大高度,所以我们将看看每一个球体的坐标最小值(y轴是从上向下递增),以像素为单位。

我们看到只是将 userData 属性中存储 userData 和当前跟踪坐标的最小值,它代表了球体达到的最大高度,然后将结果输出到控制台。

测试影片,你将看到最右边的球体弹跳的最高,然而剩下的球体看起来几乎静止在地面上

image

源码在: article/ch04/ch04-2.html

同时,我们来看看输出窗口中证实我们在影片中看到的球体的弹跳高度的文本。

最右边的文本达到了80像素,上升高度为330像素,中间的球体只弹跳了2像素,最左边的球体没有弹跳。

  • Sphere 0:410
  • Sphere 1:408
  • Sphere 2:80

我们可能会认为响应力作用的球体太重(但是最右边的球体弹跳的却很高),所以我们在 sphere()方 法中将它们的密度从 2 减低到 1:

function sphere(px, py, r){
    var bodyDef = new b2BodyDef();
    bodyDef.position.Set(px/worldScale, py/worldScale);
    bodyDef.type = b2Body.b2_dynamicBody
    bodyDef.userData = py;
    var circleShape = new b2CircleShape(r/worldScale);
    var fixtureDef = new b2FixtureDef();
    fixtureDef.shape = circleShape;
    fixtureDef.density = 1;
    fixtureDef.restitution = .4
    fixtureDef.friction = .5;
    var theSphere = world.CreateBody(bodyDef);
    theSphere.CreateFixture(fixtureDef);
    return theSphere;
}

让我们再次测试网页并看看发生了什么:

  • Sphere 0:410
  • Sphere 1:401
  • Sphere 2:80

第一件事你应该注意到了,最右边的球体达到的高度和之前一样,和密度的改变没有关系。

这是因为 SetLinearVelocity() 方法只设置刚体的线速度,与刚体的质量和之前的速度无关。它只设置速度,就是这样!

另一方面,中间的球体比之前弹跳的高度多了一点,所以这意味着ApplyImpulse()方法依赖于刚体的质量。

源码在: article/ch04/ch04-3.html

应用冲量来得到线速度

现在,给两个睡眠的球体相同的质量和作用力

SetLinearVelocity() 方法设置速度和质量无关,

而 ApplyImpulse()方法则受质量的影响,

如果我们将 ApplyImpulse() 方法中的力设置为 SetLinearVelocity() 方法中力的质量倍数(将SetLinearVelocity()方法设置的力乘以刚体质量),

向下面这样改动 main() 方法:

function main(){
    world = new b2World(gravity, sleep);
    debugDraw();
    floor();

    sphereVector = [];
    for(var i=0; i<3; i++){
        sphereVector.push(sphere(170+i*150, 410, 40));
    }

    var force = new b2Vec2(0 ,-15);
    var forceByMass = force.Copy();
    forceByMass.Multiply(sphereVector[1].GetMass());
    var sphereCenter = sphereVector[0].GetWorldCenter();
    sphereVector[0].ApplyForce(force,sphereCenter);
    sphereCenter = sphereVector[1].GetWorldCenter();
    sphereVector[1].ApplyImpulse(forceByMass, sphereCenter);
    sphereVector[2].SetLinearVelocity(force);

    setInterval(updateWorld, 1000 / 60);
    }
    main();

这里有几个新概念。

首先,看看 Copy() 方法,它是用来拷贝一份 b2Vec2 对象,为了避免原来的矢量通过引用被修改。

然后,Mutiply() 方法将 b2Vec2 值乘以一个给定的数值,在本例中,这个数值是球体的质量。

b2Body 的 GetMass() 方法将返回刚体的质量单位为千克。

最后,我们定义一个新的力,变量名为 forceByMass,我们将它作用到中间的球体上。

测试网页,你将看到中间和右边的球体弹跳到相同的高度:

image

源码在: article/ch04/ch04-4.html

控制台输出的文本如下:

  • Sphere 0:410
  • Sphere 1:80
  • Sphere 2:80

中间和右边的球体都以相同的方式响应力的作用了,然而 左边的球体仍然停留在原先的位置。

到底出了什么问题呢?

这与时间相关,一个冲量作用的时间是一瞬间,一个力作用时间是持续的,然而通常处理物理问题的计量单位是秒。

这意味着我们想要左边的球体像其它两个球一样做出反应,我们应该将中间球体的力乘以模拟一秒所需要的时间步。

应用力来获得线速度

因为 Step() 方法的工作周期是 1/30 秒,我们需要将力乘以 30,所以我们向下面这样改变 main() 方法:

function main(){
    world = new b2World(gravity, sleep);
    debugDraw();
    floor();

    sphereVector = [];
    for(var i=0; i<3; i++){
        sphereVector.push(sphere(170+i*150, 410, 40));
    }

    var force = new b2Vec2(0, -15);
    var forceByMass = force.Copy();
    forceByMass.Multiply(sphereVector[1].GetMass());
    var forceByMassByTime = forceByMass.Copy();
    forceByMassByTime.Multiply(30);
    var sphereCenter = sphereVector[0].GetWorldCenter();
    sphereVector[0].ApplyForce(forceByMassByTime, sphereCenter);
    sphereCenter = sphereVector[1].GetWorldCenter();
    sphereVector[1].ApplyImpulse(forceByMass, sphereCenter);
    sphereVector[2].SetLinearVelocity(force);

    setInterval(updateWorld, 1000 / 60);
}
main();

这里的概念和之前我们创建新的力和应用力到最左边的球体时一样。我将中间球体的力乘以 30。

测试网页,现在所有的球体将会有着相同的反应:

image

源码在: article/ch04/ch04-5.html

同时,输出窗口中输出的文本证实了我们通过三种不同的方式控制三个相同的球体获得了相同的效果:

  • Sphere 0:80
  • Sphere 1:80
  • Sphere 2:80

到目前为止,你学习了将力应用到刚体上的基础知识。

总之,这个过程很简单,因为你只是将力作用的睡眠状态的刚体上。

为了在移动的或与其它刚体发生碰撞的刚体上实现相同效果将会困难的多。

虽然你可以选择你喜欢的方式将力作用到刚体上,但是,下面关于根据游戏开发的需要选择使用力的方式的建议是很有用的:

  • 当你想要停止一个刚体(无论它处于什么状态,碰撞或移动)并让它开始一个新的状态时,我们使用线速度(LinearVelocity)来设置。

    在游戏设计中,试想一下一个移动的平台或一个敌人的巡逻区域:在每个时间步中,你可以设置它的线速度然后它将直接向另一个方向移动。

  • 当你想在单个时间步中应用所有力到已有力作用的刚体上时,使用冲量(Impulse)来设置。在游戏设计中,将使用冲量使角色跳动。

  • 当你想要给你的角色在一定时间内作用一个推力时,使用力(Force)来设置。例如:一个喷气火箭背包。

但是,最重要的是尝试!相同的事情可以使用不同的方法,这取决你找到一种适合你的作用力的方式。

将力应用到真实的游戏中

现在,让我将力应用到愤怒的小鸟的世界中吧!

image

上图是愤怒的小鸟浏览器版的第一个关卡,这也是我们将要模仿创建的关卡。

目前,搭建愤怒的小鸟的关卡与搭建图腾破坏者关卡并没有什么不同,正如你所看到的,我重复使用了在之前章节中已解释过的大部分方法。

所以,main 函数将如下所示:

<script> 
function init() {
    var   b2Vec2 = Box2D.Common.Math.b2Vec2
        ,  b2AABB = Box2D.Collision.b2AABB
        ,	b2BodyDef = Box2D.Dynamics.b2BodyDef
        ,	b2Body = Box2D.Dynamics.b2Body
        ,	b2FixtureDef = Box2D.Dynamics.b2FixtureDef
        ,	b2Fixture = Box2D.Dynamics.b2Fixture
        ,	b2World = Box2D.Dynamics.b2World
        ,	b2MassData = Box2D.Collision.Shapes.b2MassData
        ,	b2PolygonShape = Box2D.Collision.Shapes.b2PolygonShape
        ,	b2CircleShape = Box2D.Collision.Shapes.b2CircleShape
        ,	b2DebugDraw = Box2D.Dynamics.b2DebugDraw
        ,  b2MouseJointDef =  Box2D.Dynamics.Joints.b2MouseJointDef
        ;

        var worldScale = 30; // box2d中以米为单位,1米=30像素
        var gravity = new b2Vec2(0, 5);
        var sleep = true;
        var world;
        function main(){
        world = new b2World(gravity, sleep);
        debugDraw();
        floor();

        brick(402,431,140,36);
        brick(544,431,140,36);
        brick(342,396,16,32);
        brick(604,396,16,32);

        brick(416,347,16,130);
        brick(532,347,16,130);
        brick(474,273,132,16);
        brick(474,257,32,16);

        brick(445,199,16,130);
        brick(503,199,16,130);
        brick(474,125,58,16);
        brick(474,100,32,32);
        brick(474,67,16,32);

        brick(474,404,64,16);
        brick(450,363,16,64);
        brick(498,363,16,64);
        brick(474,322,64,16);

        setInterval(updateWorld, 1000 / 60);
    }
    main();
    

    function brick(px, py, w, h, s){
        var bodyDef = new b2BodyDef();
        bodyDef.position.Set(px/worldScale, py/worldScale);
        bodyDef.type = b2Body.b2_dynamicBody;
        bodyDef.userData = s;
        var polygonShape = new b2PolygonShape();
        polygonShape.SetAsBox(w/2/worldScale, h/2/worldScale);
        var fixtureDef = new b2FixtureDef();
        fixtureDef.shape = polygonShape;
        fixtureDef.density = 2;
        fixtureDef.restitution = .4;
        fixtureDef.friction = .5;
        var theBrick = world.CreateBody(bodyDef);
        theBrick.CreateFixture(fixtureDef);
    }

    function sphere(px, py, r){
        var bodyDef = new b2BodyDef();
        bodyDef.position.Set(px/worldScale, py/worldScale);
        bodyDef.type = b2Body.b2_dynamicBody
        bodyDef.userData = py;
        var circleShape = new b2CircleShape(r/worldScale);
        var fixtureDef = new b2FixtureDef();
        fixtureDef.shape = circleShape;
        fixtureDef.density = 1;
        fixtureDef.restitution = .4
        fixtureDef.friction = .5;
        var theSphere = world.CreateBody(bodyDef);
        theSphere.CreateFixture(fixtureDef);
        return theSphere;
    }

    function floor(){
        var bodyDef = new b2BodyDef();
        bodyDef.position.Set(320/worldScale, 465/worldScale);
        var polygonShape = new b2PolygonShape();
        polygonShape.SetAsBox(320/worldScale, 15/worldScale);
        var fixtureDef = new b2FixtureDef();
        fixtureDef.shape = polygonShape;
        fixtureDef.restitution = .4;
        fixtureDef.friction = .5;
        var theFloor = world.CreateBody(bodyDef);
        theFloor.CreateFixture(fixtureDef);
    }

    
    function updateWorld() {
        world.Step(1/30, 10, 10);// 更新世界模拟
        world.DrawDebugData(); // 显示刚体debug轮廓
        world.ClearForces(); // 清除作用力
    }

    //setup debug draw
    function debugDraw(){
        var debugDraw = new b2DebugDraw();
        debugDraw.SetSprite(document.getElementById("canvas").getContext("2d"));
        debugDraw.SetDrawScale(worldScale);
        debugDraw.SetFillAlpha(0.5);
        debugDraw.SetFlags(b2DebugDraw.e_shapeBit | b2DebugDraw.e_jointBit);
        world.SetDebugDraw(debugDraw);
    }
};
</script>

上面的代码只是单纯的复制/粘贴之前的脚本,所以在这里没有什么

要解释。测试网页,你应该看到你所搭建的关卡:

image

源码在: article/ch04/ch04-6.html

我们搭建的关卡中并没有创建小猪,因为现在你还没有能力去消灭它,所以它与目前的学习没有关系,我们将在下一个章节再提到它。

此刻,我们只要摧毁它的房子有就可以了。

在我们粉碎小猪们的藏身之处之前,我们需要绘制出一些代表橡皮弹弓和小鸟的图形。

橡皮弹弓主要是一个让小鸟可以在其内部移动的圆圈。

此刻,我们将使用另一个圆来代表小鸟。

物理游戏不只是关于物理

因为这部分内容是为了实现让玩家发射小鸟的功能,与 Box2D 无关。

所以,我会很快的讲解这些知识,那么我们开始吧!

在开始绘图之前,我想让你知道,你可以在你的游戏中混合使用物理和非物理的脚本,就像我现在要做的一样。

在本例中,玩家的交互不是由 Box2D 管理,它只有在小鸟被释放的时候才会被使用。

我在这里引入了 createjs 用于绘制图形与交互

我在 番外篇(https://www.cnblogs.com/willian/p/10391319.html) 里介绍过了,如果你还不了解,那么需要回顾一下 源码在 extra.html

首先,我们需要一些变量:

var theBird = new createjs.Shape();
var slingX = 100;
var slingY = 250;
var slingR = 75;

theBird 代表了可拖拽的小鸟,slingX 和 slingY 是橡皮弹弓的中心,以像素为单位,然后

slingR 是橡皮弹弓可拖拽区域的半径,以像素为单位。

现在,我们需要在舞台上绘制一些东西。我们将绘制一个大圆圈代表橡皮弹弓的可拖拽区域,然后小的圆圈代表小鸟。

同时,我们将添加一些监听,去选择,拖拽以及释放小鸟。

将下面的代码添加到 main 方法中:

function main(){
    world = new b2World(gravity, sleep);
    debugDraw();
    floor();

    brick(402,431,140,36);
    brick(544,431,140,36);
    brick(342,396,16,32);
    brick(604,396,16,32);

    brick(416,347,16,130);
    brick(532,347,16,130);
    brick(474,273,132,16);
    brick(474,257,32,16);

    brick(445,199,16,130);
    brick(503,199,16,130);
    brick(474,125,58,16);
    brick(474,100,32,32);
    brick(474,67,16,32);

    brick(474,404,64,16);
    brick(450,363,16,64);
    brick(498,363,16,64);
    brick(474,322,64,16);

    // 画出大圆
    var slingCanvas = new createjs.Shape();
    slingCanvas.graphics.setStrokeStyle(1, "round").beginStroke("white");
    slingCanvas.graphics.drawCircle(0, 0, slingR);
    stage.addChild(slingCanvas);
    slingCanvas.x = slingX;
    slingCanvas.y = slingY;
    
    // 画出小鸟
    theBird.graphics.setStrokeStyle(1, "round").beginStroke("white");
    theBird.graphics.beginFill('white').drawCircle(0,0,15);
    stage.addChild(theBird);
    theBird.x = slingX;
    theBird.y = slingY;

    // 拖动小鸟
    theBird.on("pressmove", birdMove);
    theBird.on("pressup", birdRelease)

    createjs.Ticker.timingMode = createjs.Ticker.RAF;
    createjs.Ticker.on("tick", function(){
        stage.update();// 这是 CreateJS 舞台更新所需要的

        world.DrawDebugData(); // 为了显示出createjs对象,这里不再绘制box2d对象至canvas
        world.Step(1/30, 10, 10);// 更新世界模拟
        world.ClearForces(); // 清除作用力
    });
    }
    main();

注意,由于要结合 2d 图形库,原 updateWorld 方法迁到了 createjs.Ticker.on("tick", ()=>{...}) 内

我们只是绘制了一些东西,所以没有什么需要说明的。现在,当玩家在小鸟上按下

鼠标时,我们需要一个方法来执行它 birdMove

function  birdMove(e){
    const mouseX = e.stageX;
    const mouseY = e.stageY;
    theBird.x = mouseX;
    theBird.y = mouseY;
    var distanceX = theBird.x-slingX;
    var distanceY = theBird.y-slingY;
    if (distanceX*distanceX+distanceY*distanceY>slingR*slingR) {
        var birdAngle = Math.atan2(distanceY,distanceX);
        theBird.x=slingX+slingR*Math.cos(birdAngle);
        theBird.y=slingY+slingR*Math.sin(birdAngle);
    }
}

只要玩家按压鼠标按钮并移动,birdMove() 方法将被调用,然后将在橡皮弹弓的区域内移动小鸟

注意,我们没有在舞台上使用 Box2D。因为用小鸟来瞄准不涉及物理,所以无需使用它,并且有一个黄金原则:在需要时,才使用物理引擎。

只有一件事情与我们的物理世界有关,那就是玩家释放小鸟时的位置。

根据小鸟的位置和橡皮弹弓的中心,我们可以决定怎样发射小鸟。

因此,birdRelease() 方法在此刻将只是在输出窗口输出释放小鸟的坐标:

function birdRelease(e){
    console.log(`bird released at ${e.target.x}, ${e.target.y}`)
}

测试网页,你将能够在橡皮弹弓的区域内移动你的小鸟。

image

源码在: article/ch04/ch04-7.html

一旦你释放小鸟,你将看到在输出窗口有一些释放坐标的文本。

在前面的截图中,一个小鸟被释放,它的位置将在控制台被输出:

bird released at 44,274

这是我们发射物理小鸟所需要的一切。

放置小鸟

在这里,Box2D 将要起到它的作用。

正如我们近似的将小鸟看作圆形,那么我们需要在玩家释放鼠标的相同位置创建一个 Box2D 圆形。

向下面这样改变 birdRelease() 方法:

function birdRelease(e){
    var sphereX = theBird.x/worldScale;
    var sphereY = theBird.y/worldScale;
    var r = 15 / worldScale;
    var bodyDef = new b2BodyDef();
    bodyDef.position.Set(sphereX,sphereY);
    var circleShape  = new b2CircleShape(r);  
    var fixtureDef = new b2FixtureDef();
    fixtureDef.shape=circleShape;
    fixtureDef.density = 4;
    fixtureDef.restitution = 0.4;
    fixtureDef.friction = 0.5;
    var physicsBird = world.CreateBody(bodyDef);
    physicsBird.CreateFixture(fixtureDef);
    stage.removeChild(theBird);
}

这里没有什么新的知识,我们只要创建一个和小鸟坐标和尺寸相同的 static 类型的圆形即可。

测试网页,通过拖拽和释放圆圈。它将会转变为一个 static 类型的 Box2D 圆形刚体。

image

源码在: article/ch04/ch04-8.html

现在,我们终于可以应用力并运行模拟。

发射物理小鸟

我们需要在 birdRelease() 方法中添加一些代码:

function birdRelease(e){
    var distanceX = theBird.x-slingX;
    var distanceY = theBird.y-slingY;
    var velocityX = distanceX*-1/5;
    var velocityY = distanceY*-1/5;
    var birdVelocity = new b2Vec2(velocityX,velocityY);

    var sphereX = theBird.x/worldScale;
    var sphereY = theBird.y/worldScale;
    var r = 15 / worldScale;
    var bodyDef = new b2BodyDef();
    bodyDef.position.Set(sphereX,sphereY);

    bodyDef.type = b2Body.b2_dynamicBody;

    var circleShape  = new b2CircleShape(r);  
    var fixtureDef = new b2FixtureDef();
    fixtureDef.shape=circleShape;
    fixtureDef.density = 4;
    fixtureDef.restitution = 0.4;
    fixtureDef.friction = 0.5;
    var physicsBird = world.CreateBody(bodyDef);

    physicsBird.SetLinearVelocity(birdVelocity);

    physicsBird.CreateFixture(fixtureDef);
    stage.removeChild(theBird);
}

最后,我们添加了一些力,所以是时候来解释一下这些代码:

var distanceX = theBird.x-slingX;
var distanceY = theBird.y-slingY;

distanceX 和 distanceY 这两个数值代表了小鸟和橡皮弹弓中心之间的距离。

距离越大,则作用的力将越大。

var velocityX = distanceX*-1/5;
var velocityY = distanceY*-1/5;

我们之前说过力与距离有关,所以上面的代码将计算水平和垂直的速度。

首先,我们将水平或垂直距离乘以 -1,因为力与距离的方向相反,否则小鸟将被发射到错误的方向。

然后,我们再将之前的乘积除以 5,只是为了让小鸟发射的速度减弱一些,否则我们将发射一只超音速小鸟。

改变这个值将影响游戏的设置,所以需要看效果来一点点调整:

var birdVelocity = new b2Vec2(velocityX,velocityY);

最后,velocity(X/Y)变量被用来创建 b2Vec2 对象,将分配力道小鸟上。

bodyDef.type=b2Body.b2_dynamicBody;

很显然,在我们将力作用到刚体上之前,我们必须将刚体设置为 dynamic 类型。

physicsBird.SetLinearVelocity(birdVelocity);

现在,小鸟可以起飞然后摧毁小猪的老巢了。

我使用 SetLinearVelocity() 方法将力作用到小鸟上

是因为我想让在同一点发射的所有类型的小鸟获得相同的速度,而与它们的尺寸和质量无关。

显然,我可以通过像本章节开始时所展示的那样,对 birdVelocity 进行乘法运算来使用 ApplyForce() 和 ApplyImpulse() 方法

但是,何必要自寻烦恼呢?我们的 SetLinearVelocity() 方法可以很好的胜任这个任务了,不是吗?

测试网页,通过拖拽然后释放小鸟,你将能够摧毁下面的场景。

image

源码在: article/ch04/ch04-9.html

如前所说,这便是你的愤怒的小鸟的关卡。虽然没有小猪和跟踪小鸟的摄像机,但是你将在下面的章节学习怎样去实现它们。

小结

在本章,你学习了怎样在你的 Box2D世界中使用力移动刚体。

通过应用力,可以实现很多事情。

从一个模型到一个撞击游戏,每一个物理游戏中玩家移动刚体都是通过一些方式来使用力实现的

那么现在,你知道了怎样应用它们。


本文相关代码请在

https://github.com/willian12345/Box2D-for-Javascript-Games

注:转载请注明出处博客园:王二狗Sheldon池中物 ([email protected])

标签:javascript,将力,Box2D,球体,fixtureDef,var,new,brick
From: https://www.cnblogs.com/willian/p/17768205.html

相关文章

  • 《Head First JavaScript程序设计》高清高质量 原版电子书PDF+源码
    下载:https://pan.quark.cn/s/9b368a216582......
  • 从零开发一款图片编辑器(使用html5+javascript)
    最近开发了一个图片编辑器,类似于photoshop的网页版,源码参考自GitHub上,顺便也总结下使用html+js开发一个编辑器需要用到哪些知识点。预览地址:https://ps.gitapp.cngithub地址:https://github.com/photopea/photopea架构设计选型:jquery.js和blueimp-canvas.js都是强大的......
  • 深入理解 JavaScript 时间分片:原理、应用与代码示例解析
    JavaScript时间分片(TimeSlicing)是一种优化技术,用于将长时间运行的任务拆分为多个小任务,以避免阻塞主线程,提高页面的响应性和性能。本文将详细解释JavaScript时间分片的原理、应用场景,并通过代码示例帮助读者更好地理解和应用该技术。本文首发于:kelen.cc概念时间分片(TimeSl......
  • Javascript、axios、vue基础命令快速学习
    1.js:JavaScript基础学习JavaScript基础学习简单案例1.点击img1,则展示img1图片默认,点击img2则展示img2图片2.输入框鼠标聚焦onfocus后,显示小写toLowerCase(),失去焦点onblur后显示大写toUpperCase()3.点击全选按钮,所有复选框为被选中状态,点击反选则取消勾选状态JavaScrip......
  • [JavaScript]arguments对象
    当我们不确定有多少个参数传递的时候,可以使用arguments来获取。在JavaScript中,arguments实际上是充当函数的内置对象。所有函数都内置了一个arguments对象,arguments对象中存储了传递的所有实参。arguments展示形式是一个伪数组,可以进行遍历。伪数组具有以下特点:1.......
  • 【华为OD统一考试B卷 | 100分】 报数问题 (1到3报数)(C++ Java Python javaScript)
    华为OD在线刷题平台平台涵盖了华为OD机试A卷+B卷的真题。平台的题库不断更新,确保能够涵盖华为OD机试的所有真题。点击链接注册并开始你的刷题之旅:点击立即刷题华为OD统一考试A卷+B卷新题库说明2023年5月份,华为官方已经将的2022/0223Q(1/2/3/4)统一修改为OD统一考试(A卷)和OD统......
  • 在JavaScript中如何检查数组是否包含某个值?
    内容来自DOChttps://q.houxu6.top/?s=在JavaScript中如何检查数组是否包含某个值?在JavaScript中,最简洁、高效的方法来检查数组是否包含某个值是什么?这是我所知的唯一方法:functioncontains(a,obj){for(vari=0;i<a.length;i++){if(a[i]===obj)......
  • 在JavaScript中,如何获取时间戳?
    内容来自DOChttps://q.houxu6.top/?s=在JavaScript中,如何获取时间戳?我想要一个单独的数字,代表当前的日期和时间,就像Unix时间戳一样。毫秒级时间戳要获取自Unix纪元以来的毫秒数,调用Date.now:Date.now()或者使用一元运算符+来调用Date.prototype.valueOf:+newDate......
  • 2023.10.13 JavaScript DOM
    文档对象模型获取对象1.根据id属性值获取,返回单个对象varh1=document.getElementById('h1');2.根据标签名获取,返回对象数组vardivs=document.getElementByTagName('div');3.根据name属性值获取,返回对象数组varhobbys=document.getElementByName('hobby');4.根......
  • [JavaScript]扩展运算符
    顺便补充一点Array.reduce((prev,curv)=>{}, ori)的运用letarr1=[1,3,5,7,9]letarr2=[2,4,6,8,10]console.log(...arr1);//展开一个数组----->13579letarr3=[...arr1,...arr2]//连接数组//......