iT邦幫忙

2021 iThome 鐵人賽

DAY 27
0
Modern Web

在JS的世界碰碰撞撞乒乒乓乓!30天一起玩Matter.js!系列 第 27

Day27. 雖然今年是2021,但我們要做2048(1)

  • 分享至 

  • xImage
  •  

賣了那麼多天關子的最後實作,今天就要揭曉我們的題目了:不知道大家有沒有玩過前些年很火紅的一款小遊戲呢?如果沒有,那請讀者先到這個網址來線上玩一下。

這是一款叫做 2048 的遊戲,遊戲的目的是要盡可能的合出數值較大的方塊。要怎麼合出大數值呢?當朝一個方向滑動的時候,所有的物體會往該方向移動到底,同時在這個移動過程,如果兩個相同數值碰在一起,便會相加,變成一個更大的數值。數值基底是 2,也就是會 2 → 4 → 8 → ... → 2048 這樣遞增合成。

沒錯!我們就要用 Matter.js 做一款 2048!

但是用一個物理碰撞引擎做一個看起來這麼平面也不用碰撞的遊戲,也太大材小用了,何況 2048 的仿作已經滿街都是了,我們要稍微來做點改版 ─ 方形容器裡的 2048!就像日本的搖彩球的感覺。

https://ithelp.ithome.com.tw/upload/images/20211012/20142057wKgXfXNPyw.jpg

我們可以滑動畫面,操控容器裡的重力,目的一樣是把球狀的物體越滾越大!

今天的Demo
今天的Demo原始碼

https://ithelp.ithome.com.tw/upload/images/20211012/20142057O2plREbfvO.png

那跟彈珠台一樣,我們先列一下我們的需求清單

  • 基本容器宣告與畫面初始
  • 依據滑動改變重力
  • 隨機初始圓形
  • 滑行後生出新的圖形但是短暫 Sleep
  • 滑行後喚醒全部的 Sleep 圖形
  • 碰撞時判定合併
  • 合併計分
  • 自適應畫面大小

清單是筆者目前想到先列的,隨後可能會因每天的內容有所刪改。

今天先是個起頭,我們會做基本的 Setup,所以我們會做到的有:

  • 基本容器宣告與畫面初始
  • 依據滑動改變重力

這次我們直接用資料夾來開專案,js 相關程式碼也會依作用分檔。

在配色上推薦大家一個網站: Coolors ,在對配色還沒有太多想法的時候,可以用這個網站來隨機顏色,並逐步固定顏色來幫你發想一套完整的顏色。

我們這次的配色也是用這個網站隨機慢慢配出來的。

首先建構的初始化函式,我們一樣包在一個 init 的函式中,方便我們做反覆初始化。

共用的模組與變數則直接在函式外部作為全域宣告。

// main.js
// module aliases
var Engine = Matter.Engine,
Render = Matter.Render,
Runner = Matter.Runner,
Bodies = Matter.Bodies,
Composite = Matter.Composite,
Events = Matter.Events,
Plugins = Matter.Plugins;

var engine;
var render;
var runner;
const canvasWidth = 600;
const canvasHeight = 600;
const wallThickness = 30;

製作容器的時候,我們會顧慮到後面要做自適應,也就是畫面大小是有可能調整的,但比例不變,所以我們這邊宣告容器宣告長寬、位置的時候都用相對位置做宣告創建。同時因為是容器,我們會讓他是靜止不受力的,也就是 isStatic 為 true。

// main.js
var wallA = Bodies.rectangle(canvasWidth/4, canvasHeight/4, canvasWidth, wallThickness, { isStatic: true, angle : getRadiusByDegree(315), render:{fillStyle:"#BC6C25"}});
var wallB = Bodies.rectangle(canvasWidth*3/4, canvasHeight/4, canvasWidth, wallThickness, { isStatic: true, angle : getRadiusByDegree(45), render:{fillStyle:"#DDA15E"}});
var wallC = Bodies.rectangle(canvasWidth/4, canvasHeight*3/4, canvasWidth, wallThickness, { isStatic: true, angle : getRadiusByDegree(45), render:{fillStyle:"#283618"}});
var wallD = Bodies.rectangle(canvasWidth*3/4, canvasHeight*3/4, canvasWidth, wallThickness, { isStatic: true, angle : getRadiusByDegree(315), render:{fillStyle:"#606C38"}});

至於我們的主角球球,我們預計在這次的教學中僅以顏色和大小來表示不同數值,對,我們期望(隱藏的)數值越大的球,體積就要越大,每個大小,都有不同的顏色。

這邊關係到筆者有點小懶惰,因為 matter.js 的物體沒有提供直接附加文字顯示的功能,要做到有數字內容顯示會需要做 texture,我們先省工用顏色和大小來區別就好。

// formObject.js
function createBall(side)
{
    var x;
    var y;
    var offset = 20;
    switch(side)
    {
        case "top":
            x = canvasWidth / 2;
            y = wallThickness + offset;
            break;
        case "left":
            x = wallThickness + offset;
            y = canvasHeight / 2;
            break;
        case "right":
            x = canvasWidth - wallThickness - offset;
            y = canvasHeight / 2;
            break;
        case "down":
            x = canvasWidth / 2;
            y = canvasHeight - wallThickness - offset;
            break;
    }
    var ballInfo = getBallInfo(1);
    var ball = Bodies.circle(x, y, ballInfo.size, options = { render : {fillStyle: ballInfo.color}, isSleeping : true}, 80);
    Composite.add(engine.world, [ball]);
}

function getBallInfo(level)
{
    const ballColor = ["#D8F3DC","#B7E4C7","#95D5B2","#74C69D","#52B788","#40916C","#2D6A4F","#1B4332","#1B4332","#081C15"];
    var ballSzie = Math.sqrt(4*level)* 10;
    return {
        color : ballColor[level-1],
        size : ballSzie
    }
}

可以看到我們把方法拆成兩個,一個是創建初始球用的,一個是拿來獲取球類創建資訊的。這邊暫且寫死初始只會創建最低層級的球(以2048是創建 2/4,我們暫時先只創建最低層級),高層級的球會在後面透過碰撞來創建。把球的創建資訊拆出來就是為了讓之後其他需要拿球創建資訊的地方可以共用。

我們定義了一個顏色陣列來做顏色渲染,由淺到深,同時球的大小我們用 (4 X 層級數)後開根號 * 10 來平衡,也就是從最小的層級 1 - 預計最高的層級 10,實際半徑大小會是從 20 - 60,我們先暫時這樣定義線性變大,如果後面不夠明顯我們再調整。

再來我們滑動偵測引用 這個 函式庫,有別人造好的輪子我們就不重複造輪子啦。

這是筆者在找他人寫法的時候找到的,使用上也很單純,引入對應的 min.js 檔案,在 document 的 加上對應的 EventListener 事件, Call Back 裡面寫上要做的事件。

// swipedEvent.js
document.addEventListener('swiped-up', function(e) {
    swipeScreen("up");
});
document.addEventListener('swiped-down', function(e) {
    swipeScreen("down");
});
document.addEventListener('swiped-left', function(e) {
    swipeScreen("left");
});
document.addEventListener('swiped-right', function(e) {
    swipeScreen("right");
});

function swipeScreen(side)
{
    gravityChange(side);   
}

我們把 swipeScreen 包成一個函式,接著在裡面做我們所有畫面滑動後要做的事情,以今天的目標來說就是要做重力方向變化的處理。

關於重力的變化,我們這樣做:

// swipedEvent.js
function gravityChange(side)
{
    switch(side)
    {
        case "up":
            engine.gravity.x = 0;
            engine.gravity.y = -1;
            return;
        case "down":
            engine.gravity.x = 0;
            engine.gravity.y = 1;
            return;
        case "left":
            engine.gravity.x = -1;
            engine.gravity.y = 0;
            return;
        case "right":
            engine.gravity.x = 1;
            engine.gravity.y = 0;
            return;
    }

}

記得 Day22 那天我們看的內容嗎?重力被安在 engine 底下,同時有 x,y 兩個方向的施力,我們假設每次滑動都會因為滑動的方向讓整體世界的重力變成往滑動方向。

為了讓電腦版也能操作測試(滑動本身行為會在手機上被偵測到),我們加上了幾個上下左右的按鈕,模擬對應方向的滑動,可以試試讓 runner 跑起來,按按按鈕,看一下我們今天的實作內容。

明天,我們進一步進到遊戲的核心部分,也就是處理碰撞後的融合、計分等等機制,還有滑動的時候球類的生成,敬請期待。


上一篇
Day26. 星多天空亮,人多智慧廣 - Plugins
下一篇
Day28. 雖然今年是2021,但我們要做2048(2)
系列文
在JS的世界碰碰撞撞乒乒乓乓!30天一起玩Matter.js!30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
juck30808
iT邦研究生 1 級 ‧ 2021-10-12 18:38:16

恭喜大大即將完賽XD !!!

感謝你的支持!
一起加油:)

我要留言

立即登入留言