<bdo id="4g88a"><xmp id="4g88a">
  • <legend id="4g88a"><code id="4g88a"></code></legend>

    2D物理引擎 Box2D for javascript Games 第四章 將力作用到剛體上

    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 用于繪制圖形與交互

    我在 番外篇(http://www.diao-diao.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", ()=>{...}) 內

    我們只是繪制了一些東西,所以沒有什么需要說明的?,F在,當玩家在小鳥上按下

    鼠標時,我們需要一個方法來執行它 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池中物 (willian12345@126.com)

    posted @ 2023-10-16 19:44  池中物王二狗  閱讀(295)  評論(0編輯  收藏  舉報
    轉載入注明博客園 王二狗Sheldon Email: willian12345@126.com https://github.com/willian12345
    免费视频精品一区二区_日韩一区二区三区精品_aaa在线观看免费完整版_世界一级真人片
    <bdo id="4g88a"><xmp id="4g88a">
  • <legend id="4g88a"><code id="4g88a"></code></legend>