iT邦幫忙

2021 iThome 鐵人賽

DAY 9
0
Modern Web

從零開始打造網頁遊戲-造輪子你也辦的到!系列 第 9

Chapter2 - Canvas動畫(III)讓我們跳過微積分 用輕鬆的方式畫落葉吧

接下來終於要談談,讓我們更輕鬆的物件了

其實網路上有很多相關的文章,都可以帶你更深入JS時,但常常問題在於,他們的舉例都不夠實際,並不是說不好,而是「需求的問題」,如果你和我一樣,在早期只是單純自我學習,而不是靠JS養活自己,那麼很容易沒有方向,即使那些文章說的頭頭是道,仍然很無感,因為平常根本沒有這樣的用途!

而這次的系列文,透過一個很明確的目標,帶大家實作,就是為了讓大家有感,從一開始造輪子,漸漸會發現大大小小的輪子散落四方,開始越來越難整理,今天便是一個分歧點,當我們要進入複雜的programming,單純的變數命名已經不符合我們的需求,那我們需求是什麼呢?我們要為遊戲做動畫,我們要讓一個東西動起來,還要讓無數個東西動起來,那要怎麼用有限的程式碼,實現無限個動畫呢?讓我們從第二章節娓娓道來:

落葉歸根

首先,還是先從實作面開始,就從一個落葉解釋為什麼需要物件吧!要做一個"完整"的落葉動畫效果,你覺得要至少準備幾個參數呢?以下讓我們實際演示一遍

首先我們定義原點:

let originX;
let originY;

接著我們定義初始時間、動畫總生命週期:

let timestamp;
let lifeCycle; // 單位:毫秒(s)

然後設計落葉的初始角度、角速度和大小:

let rotateTheta; // 單位:rad
let rotateOmega; // 單位:rad/s
let size;

以為這樣就結束了嗎?不只這些,待會還要用到兩個方程式:

  1. X 路徑方程式
  2. Y 路徑方程式

一般來說,為了得到路徑方程式,需要用速度對時間做積分:

  1. (X 速度方程式) 對時間的積分
  2. (Y 速度方程式) 對時間的積分

但是這樣的系統太複雜,說好要跳過微積分的!我們可以簡化它,直接思考落葉是怎麼動的,印象中的葉子會左右擺動,緩慢飄弱,是不是很像鐘擺的往復運動呢?這種周而復始的循環,用到的是三角函數,其最簡單的形式是簡諧運動,也就是說,只需要有公轉角度、公轉速度:

let revolveTheta; // 單位:rad
let revolveOmega; // 單位:rad/s

我們就可以跳過以上複雜的方程式,繼續用國中數學畫圖了!

好了,材料備好了,用昨天的Demo改寫,實作看看吧

第一步: 滑鼠點擊時,取得初始條件

canvas.addEventListener('click', SetMouse);
function SetMouse(e){
    originX = (e.pageX - Rect.left) * RATIO;
    originY = (e.pageY - Rect.top) * RATIO;
    size = 200;
    rotateTheta = 0 / 180 * Math.PI;
    rotateOmega = 40 / 180 * Math.PI; // 每秒旋轉40度
    revolveTheta = 0 / 180 * Math.PI;
    revolveOmega = 90 / 180 * Math.PI; // 每秒公轉90度
    timestamp = Date.now();
    lifeCycle = 6; // 生命週期6秒
}

幫大家回憶一下,Rect是用canvas.getBoundingClientRect()原生方法取得畫布在畫面中位置

第二步: 計算當前時間,判斷動畫是否進行中

let deltaTS = (Date.now() - timestamp) / 1000;
if(lifeCycle > deltaTS){
    Clear(context);
    // 第三步 + 第四步寫在這裡面
}

Clear是接續上次架構的清除函式

第三步: 計算當前旋轉角度、公轉角度,並代進方程式

let rotateNow = rotateTheta + rotateOmega * deltaTS;
let revolveNow = revolveTheta + revolveOmega * deltaTS;
cursorX = originX + 500 * Math.sin(revolveNow);
cursorY = originY + 200 * Math.sin(1.5 * revolveNow)
                  + 100 * deltaTS;

設計讓落葉水平來回搖擺、垂直落下則會遇到微風的影響,一下快一下慢

第四步: 進行座標的轉換並繪製圖案

if(leafImg.complete){
    let width = size;
    let height = size * (leafImg.height / leafImg.width);
    context.save();
    context.translate(cursorX, cursorY);
    context.rotate(rotateNow);
    context.drawImage(leafImg, -width/2, -height/2, width, height);
    context.restore();
}
  • save: 儲存當前畫布的狀態(座標、平移、縮放、旋轉等等),目前原點(0, 0)為最左上角
  • translate: 平移公式,將原點移到圖案的正中心
  • rotate: 以原點為中心旋轉,因為剛剛平移過了,因此會根據圖案的中心旋轉
  • restore: 返還已儲存的畫布狀態,回到一開始以左上角為原點(0, 0)的狀態

這樣總算是完成一個落葉動畫了!請看Demo

變數一大堆,維護程式碼略顯吃力

一開始你猜測會用到幾個變數呢?除了一開始定義的9個變數,還有途中計算的當前時間等等參數,至少就用到了12個變數欸!更不用說原本就定義好的cursorX和cursorY以及其他我們事先準備好的架構,難道沒有一個更好的方式去整理嗎?

沒錯,答案就是物件XD,而現在的狀況是,這個落葉的參數四散各處,我們想要的其實就是一種「可以將這個落葉視為一個整體的方法」,明天就帶大家一起反向思考,我們先談我們想要什麼,再談物件是什麼,你就會發現,哇!JS的物件真的是設計得太好了,直接給JS的創始人一個大大的讚


上一篇
Chapter2 - Canvas動畫(II)用國中數學拆解Ease-out和Ease-in
下一篇
Chapter2 - 用物件看真實世界(I)寫程式為什麼需要物件?如何簡化畫落葉的流程?
系列文
從零開始打造網頁遊戲-造輪子你也辦的到!31

尚未有邦友留言

立即登入留言