iT邦幫忙

2021 iThome 鐵人賽

DAY 9
0

今天!!是令人興奮的實作!!不再是單純呼叫API文件的範例碼,而是具備意義/情境的程式。之後只要出現這個標題,表示我們要用學到的知識分階段來完成我們的彈珠台。

記得我們 Day3 的時候曾經開過一系列的需求嗎?沒關係,我相信快一個禮拜的現在,大家都忘了差不多了,我們這邊幫大家 Recap 一下需求:

  • 建立方釘和顏色釘
  • 方釘和顏色釘會是固定的
  • 建立圓球
  • 圓球需要自由落體,受到重力影響
  • 圓球要能和方釘碰撞產生彈跳
  • 圓球和顏色釘碰撞的時候要偵測到碰撞並變色
  • 鏡頭移動,上帶到下
  • 在開始前,畫面要是靜止的,直到我們發出Signal
  • 滑鼠和球體互動 (追加功能)

沒錯,粗體的就是我們今天要來完成的項目!好的,我們話不多說,直接開始吧!

今日的Demo
今日的Demo原始碼

https://ithelp.ithome.com.tw/upload/images/20210924/20142057ONXdGD3iwo.png

今天會專注在世界的基礎建造,也就是初始化。
一開始的共用模組跟一些常數我會統一宣告在外層全域。

// Global Settings / Variables
var Engine = Matter.Engine,
    Render = Matter.Render,
    Runner = Matter.Runner,
    Bodies = Matter.Bodies,
    Composite = Matter.Composite,
    Events = Matter.Events,
    BodyM = Matter.Body,
    Vertices = Matter.Vertices;

var engine;
var render;
var runner;
const canvasWidth = 400;
const canvasHeigh = 500;
const blockSize = 20;
const mainBallRadius = 15;
const minimumBlockGenerateX = 50;
const minimumBlockGenerateY = 100;
const blockSeparateX = canvasWidth - minimumBlockGenerateX - 50;
const blockSeparateY = canvasHeigh - minimumBlockGenerateY  - 100;
const minimumDistanceBetweenBlocks = 60;

再來過往我們直接做的初始化世界,我們用一個 init 的 function 來包住,這邊筆者多設想了一個重置世界的 case,所以才會選擇包起來。

function init()
{
    engine = Engine.create();
    render = Render.create({
        element: document.body,
        engine: engine,
        options:{
            wireframes:false,
            showIds:true,
            background: '#bfe9f5', //"#bfe9f5"
            width:canvasWidth,
            height:canvasHeigh
        }
    });
    formHiddenWall();
    formMainBall();
    formRandomBlocks(15);

    Render.run(render);
    runner = Runner.create();
}

~~好的!我們今天完工了!~~我們今天本來就是初始化世界就差不多完成需求了,所以其實所有要做的事情都寫在 init 裡了,我們帶大家看一下我們怎麼初始世界的,在 init 的流程如果用白話來說會是:

創建引擎與渲染相關物件 → [[加入引擎中的世界] 加入左右兩側的牆防止球跑出去 → 加入主要的球體 → 加入隨機產生的柱子] → 讓渲染跑起來 → 預先宣告runner但不讓它跑

完成以上後應該會看到如同我們今天一開始貼的圖,會是靜止的球在空中,當你按下 Run the runner 的按鈕,球就會掉下來了!

我們從牆的創建開始看 - formHiddenWall()

function formHiddenWall()
{
    var wallLeft = Bodies.rectangle(-21, canvasHeigh/2, 40, canvasHeigh, { isStatic: true });
    var wallRight = Bodies.rectangle(canvasWidth+21, canvasHeigh/2, 40, canvasHeigh, { isStatic: true });
    Composite.add(engine.world, [wallLeft,wallRight]);
}

牆的創建算是最單純的,基本上就是參考 canvas 的屬性來決定牆的位置與長寬,位置要注意因為給的座標是牆的中心,所以會用這種取半的寫法來讓牆不要顯示在畫面內。

另外因為是一道不能移動的牆,我們在 options 中會設定 isStatic: true ,來表示它是一個靜止的物體。

最後再加入 world 中就大功告成了!

第二個是我們的主角,球 - formMainBall()

function formMainBall()
{
    const mainBallInitX = canvasWidth/2;
    const mainBallInitY = 50;
    
    var mainBall = Bodies.circle(mainBallInitX, mainBallInitY, mainBallRadius, options = {
        restitution: 1,
        render:{
            fillStyle:"#FFFFFF"
        }
    }, 100);
    Composite.add(engine.world, [mainBall]);
}

球的話要注意的是球的初始位置,我們會放在畫面的中上方,render 因為是會被看到的,我們設成白色的,另外,因為我們預期球是會發生彈跳的,這邊我們給 options 中的 restitution 值,可以依據個人喜好決定彈性碰撞的程度,範例中我們用 1 來做完全彈性碰撞。

最後是比較麻煩的亂數生成方塊 - formRandomBlocks(blockCount)

function formRandomBlocks(blockCount)
{
    var blockOptions ={
        render : {
            fillStyle : "#569cd8",
        },
        isStatic : true,
        angle : getRadiusByDegree(45)
    };
    
    var blockCoordinateList = [];
    
    for(var i=0; i<blockCount; i++)
    {
        var blockCoordinate = getRandomCoordinateForBlocks(blockCoordinateList);
        var block = Bodies.rectangle(blockCoordinate.x, blockCoordinate.y, blockSize, blockSize, blockOptions);
        blockCoordinateList.push(blockCoordinate);
        Composite.add(engine.world, [block]);
    }            
}

方塊本身的建立是相對單純的,麻煩的會是考量彈珠台的實作,球不能被卡在方塊中間,所以我們會定義一個方塊最小相距常數,在亂數產生的函式中( getRandomCoordinateForBlocks )會讓方塊彼此間的距離不要太近。另外在 options 比較多的時候,我們可以像這裡一樣把 options 抽成單獨一個變數,會比較乾淨也能和其他物體共用(如果有需要的話啦),我們這邊設定的 options依序是藍色填充、靜止物體、旋轉角度45度。

流程上來說會是

設定方塊創建選項 → 宣告一個具有所有方塊座標的陣列(用於檢查距離) → [[迴圈產生指定數量的方塊] → 隨機產生符合距離規範的方塊座標 → 用 Bodies 中的方法以及參數創建方塊 → 將方塊加入世界中]

細部的函示筆者就不走進去細節了,大家可以先看看理解一下,或是嘗試自己實作。
完成你的彈珠台後,可以按下 Run the runner 的按鍵,檢視你今天的傑作!
嘿,球動起來了!它和那些方塊碰碰撞撞!

我們最後加碼一個,如果我們要 reset 的話會怎麼做?我們看到 reInit 這個函式:

function reInit()
{
    event.preventDefault();
    Engine.clear(engine);
    Render.stop(render);
    Runner.stop(runner);
    render.canvas.remove();
    render.canvas = null;
    render.context = null;
    render.textures = {};
    init();
}

這邊依序是終止了發生中事件、清除引擎、停止渲染、停止跑動迴圈,移除對應的canvas與紋理 ─ 最後,我們在執行一次我們一開始抽出來的初始化函式!

嘿!按下 Re Init的按鈕,世界就這樣又重新來過了!你可以一次次的讓球重新掉落、重新生成碰撞方塊了!

我們的彈珠台看起來已經有模有樣了,不是嗎?明天,讓我們來熟悉其他的模組,一起為完成彈珠台繼續努力!


上一篇
Day8. 依點成形,創造物件 - RigidBody(下)
下一篇
Day10. 人與人之間偶有摩擦,物體與物體之間叫做碰撞 - Collision(上)
系列文
在JS的世界碰碰撞撞乒乒乓乓!30天一起玩Matter.js!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言