iT邦幫忙

2021 iThome 鐵人賽

DAY 8
0
Modern Web

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

Chapter2 - Canvas動畫(II)用國中數學拆解Ease-out和Ease-in

如何計算每一偵的位移

首先我們改寫一下昨天的格式,還記得昨天我們用到的是這樣的寫法:

cursorX+= distanceX / period;
cursorY+= distanceY / period;

當時的想法也很簡單,因為總共有90偵,因此將座標加上distance / period共90次,就會是完整的位移,那麼,換句話說,其實就是,每1偵加上1/90的距離,可以寫成這樣:

let linear = 1 / period;
cursorX+= linear * distanceX;
cursorY+= linear * distanceY;
// 位移 = 線性常數 * 距離

其實,這個線性常數linear等同時間函數的微分,將它積分後會剛好等於1阿 突然講了很難懂的話

在上面的公式中,輸入值為距離和總偵數(distance&period),輸出值則是每一偵要進行的位移,在這個例子中,因為共有90偵,constant * 90就好剛好會等於1,也就是說,實際上我們想控制這個參數,讓它經過90偵後剛好等於1,因而從原點轉移到目的地,所以我們要做的是便是「操縱0到1的過程」。

操縱0到1的過程

昨天留的思考題,不知道有沒有人解出來了呢?有的扣個1留言一下XD,現在就讓我們解答吧,首先我們先從簡單的開始,以前國中學到一元二次方程式,最先學的那個不外乎就是Y=X^2吧?也就是最簡單的,這個方程式有一個特性,就是當X在0~1之間時,Y也是0~1之間,剛才說我們希望「輸出值的總和」等於1,因此只要控制X在0~1之間就可以了!

  • X = (1 - timer/period)
  • Y = X^2
  • 總位移 = Y * distance

開始困惑的朋友們莫慌!讓我們直接代數字進去看看,在第一偵的時候timer為90,隨著timer的減少

period timer X Y 總位移量
90 90 0 0 0
90 45 0.5 0.25 總距離的1/4倍
90 30 0.666 0.444 總距離的4/9倍
90 0 1 1 總距離的1倍

這樣是不是就很好懂了呢?從一開始幾乎不動,到後面越來越快,最後到達目標位置。

Ease-in & Ease-out

如果沒看過這兩個名詞的話,可以參考這個裡面的文件附圖說明

其實,我們剛剛做的就是ease-in函數,不過跟典型的不太一樣,只是很相似的地步,但也是最好理解的,不過,現在的問題是,我們已經拿到這個方程式,也會算總位移了,那要怎麼取得「每一偵的位移」呢?答案很簡單,就是「拿下一偵的位移減這一偵的位移」:

  • X = (1 - timer/period)
  • Y(X) = X^2
  • 每一偵的位移 = (Y(timer-1) - Y(timer)) * distance

寫成程式碼就是:

let easein = Math.pow(1 - (timer-1) / period, 2) - Math.pow(1 - timer / period, 2);
let cursorX+= easein * distanceX;
let cursorY+= easein * distanceY;

如何?換上這行代碼後,動畫效果是不是多了幾番風味呢?

那反過來說ease-out要怎麼做呢?其實有個偷吃步的做法,就是把ease-in顛倒過來,慢的變快的,快的變慢的,拿1去減掉這個範圍0~1的參數,就會從「0到1」反過來「1到0」,公式:

  • X = 1 - (1 - timer/period)

也就是:

let easeout = Math.pow(timer / period, 2) - Math.pow((timer-1) / period, 2);
let cursorX+= easeout * distanceX;
let cursorY+= easeout * distanceY;

那麼,我們就完成很基本的ease-in和ease-out拉!灑花~

混和方程

接著我們可以進一步混和不同方程式,寫成像這樣的格式:

cursorX+= (0.5 * linear + 0.5 * easein) * distanceX;
cursorY+= (0.5 * linear + 0.5 * easein) * distanceY;

這樣可以使得easein的效果比較緩和,看起來不會那麼做作,不過要注意參數總和必須為1

function MouseAnime(){
    if(!isPathing){
        if(timer > 0){
            let linear = 1/period;
            let easeout = Math.pow(timer / period, 2)
                          - Math.pow((timer-1) / period, 2);
            let easein = Math.pow(1 - (timer-1) / period, 2)
                         - Math.pow(1 - timer / period, 2);
            let a = input.linear;
            let b = input.easein;
            let c = input.easeout;
            cursorX+= (a * linear + b * easein + c * easeout) * distanceX;
            cursorY+= (a * linear + b * easein + c * easeout) * distanceY;
            timer--;
        }
        else{
            cursorX+= (mouseX - cursorX) / 5;
            cursorY+= (mouseY - cursorY) / 5;
        }
    }
    let size = WIDTH * 0.1;
    if(MouseImg.complete) context.drawImage(MouseImg,
                                            cursorX-size/2, cursorY-size/2,
                                            size, size);
}

透過abc參數,給予不同的權重,設計上希望a+b+c=1,不過為了方便直接設計權重比例,最後共同除以(a+b+c)就可以了
這樣混和的方法,也可以創造出新的方程式,比方說回彈機制等等的,

甚至XY可以分別採用不同的動畫模式,例如:X是純Linear、Y是純Ease-in,玩起來還是有Ease-in的味道在,只是相比之下沒有那麼浮誇;混合的方法則可以設計出迴旋鏢等等的騷操作。

我就把這份快樂留給大家試試看吧!可以拿這份架構去修改參數,或者加入更多其他的方程式

Animation Demo

後記

今天花了四個小時坐客運,時間被壓縮了不少,文章也都是親筆撰寫,也花了點時間設計架構,故內容寫少一點請見諒拉,但還是推薦大家可以去玩一下demo,會越來越精彩~明天要開始引入物件的觀念!


上一篇
Chapter2 - Canvas動畫(I)玩轉路徑和位移 動畫原來這麼簡單
下一篇
Chapter2 - Canvas動畫(III)讓我們跳過微積分 用輕鬆的方式畫落葉吧
系列文
從零開始打造網頁遊戲-造輪子你也辦的到!31

尚未有邦友留言

立即登入留言