iT邦幫忙

2022 iThome 鐵人賽

DAY 10
1
Modern Web

30個遊戲程設的錦囊妙計系列 第 10

Trick 9: 活塞運動的嘆息:sin與cos

  • 分享至 

  • xImage
  •  

三角函數是遊戲程式設計師必定要鑽研的課題之一,除了在向量的運算中需要大量的三角函數之外,光是正弦(sine)與餘弦(cosine)這兩個數學式本身,就能帶給遊戲莫大的好處。我們今天就來談談一些sine與cosine的應用。

三角函數

sin cos
我知道同學們對這些基本的三角函數都很熟了,不過還是在這裏快速介紹一下什麼是正弦函數和餘弦函數。如果一個直角三角形的斜邊長度是1,其中一個銳角是θ(唸作theta),那麼在這個角對面的邊長長度就是正弦sin(θ),另一個在角旁邊的底邊長度就是餘弦cos(θ)。

在JavaScript/TypeScript中,正弦函數可由Math.sin(θ)計算出來,其中的θ單位是弧度(角度180度=弧度π),餘弦函數則是由Math.cos(θ)來計算。這兩個函數有以下一些明顯的特性,

  1. 不管θ是多少,-1 ≤ sin(θ) ≤ 1-1 ≤ cos(θ) ≤ 1
  2. sin²(θ) + cos²(θ) = 1(畢氏定理)。
  3. cos(θ) = sin(90°-θ)(90°-θ就是三角形的另一個銳角)。

polar coord
如果我們把三角形長度為1的斜邊從θ為0°轉到90°、180°,再繼續轉到270°、360°,就會發現這個斜邊滑過的面積剛好是一個圓,而圓周上的點,剛好可以用 x = cos(θ)y = sin(θ) 來表示,這也就是極座標和xy座標轉換公式的來源。

/** 將極座標轉換成x,y座標的函式,參數依序是
 * length: 距離原心的距離
 * angle: 和x軸的夾角(以弧度表示)
 */
function polarToXY(length: number, angle: number): Point {
    return new Point(
        Math.cos(angle) * length, // x值
        Math.sin(angle) * length  // y值
    );
}

如果在一個座標平面把θ的變化放在x軸,sin(θ)和cos(θ)放在y軸,那畫出來的就是波的形狀。
wave graph

大致瞭解了sine與cosine的特性之後,我們就能來看看這兩個函數到底能怎樣被遊戲拿來運用。

活塞運動

記得剛剛我們講的,把直角三角形的斜邊繞了360度畫出一個圓形嗎?圓上點的座標可以用(cos(θ) ,sin(θ) )來表示,其中不管是x值的變化,或是y值的變化,都會呈現活塞運動的軌跡。

那麼,如果我們把時間作為θ放進三角函數算座標,然後只把圓上座標的y值拿出來使用,就可以模擬小魔女飛在空中上下飄浮的動畫。
漂浮魔法師     武器飄浮
在俯視地圖中,我們也可以把三角函數放入武器的縮放值,來營造物體飄浮的感覺。

/** 模擬小魔女在空中飄浮
 * witch: 魔女的圖案
 * center: 魔女飄浮的中心點
 * time: 目前的時間(毫秒)
 */
function updateWitch(witch: PIXI.Sprite, center: Point, time: number) {
    // 飄浮上下震動的頻率(次數/毫秒)
    let frequency = 0.001;
    // 飄浮上下震動的震幅(次數/毫秒)
    let yHalfRange = 30;
    // 以目前的時間和頻率計算現在的θ
    let theta = frequency * time * Math.PI * 2;
    // 更新魔女的y
    witch.y = center.y + Math.sin(theta) * yHalfRange;
}

淡入淡出

類似於活塞運動的應用,但只取θ在0°到90°(π)區間內的sin(θ),就可以把線性的數值變化改成淡出(即越靠近動畫的尾聲,值改變地越少)。
fadein fadeout
我們來寫寫程式,把淡出的數學寫成一個函式。

/** 寫一個淡出的百分比變化
 * 參數是目前經過的時間 time
 * 以及整個動畫的時間 duration
 * 回傳一個0到1的數值,這個數值的變化會有淡出的效果
 */
function fadeout(time: number, duration: number): number {
    // 先算出線性的百分比值
    let percent = time / duration;
    // 限制這個比值在0到1的區間
    percent = Math.max(0, Math.min(1, percent));
    // 使用sin讓percent轉變為淡出(Math.PI/2就是90°)
    return Math.sin(percent * Math.PI / 2);
}

如果θ取在-90°(-π)到0°之間,那數值的變化就可以拿來製作淡入效果,不過因為值會從-1跑到0,所以在應用的時候,要把sin(θ)的值加上1,調整為0到1。

/** 淡入的百分比變化
 * 參數是目前經過的時間 time
 * 以及整個動畫的時間 duration
 * 回傳一個0到1的數值,這個數值的變化會有淡入的效果
 */
function fadein(time: number, duration: number): number {
    // 先算出線性的百分比值
    let percent = time / duration;
    // 限制這個比值在0到1的區間
    percent = Math.max(0, Math.min(1, percent));
    // 使用sin讓percent轉變為淡出(θ要移-90°)
    let value = Math.sin(percent * Math.PI / 2 - Math.PI / 2);
    // 最後的值要加上1,才會讓值從0變化至1
    return value + 1;
}

同理,θ取在-90°(-π)到90°(π)之間,那就可以淡入後淡出,但是在取得sin(θ)值之後,要加上1再除以2,才能將值調整至0到1。

/** 淡入且淡出的百分比變化
 * 參數是目前經過的時間 time
 * 以及整個動畫的時間 duration
 * 回傳一個0到1的數值,這個數值的變化會有淡入且淡出的效果
 */
function fadeinAndOut(time: number, duration: number): number {
    // 先算出線性的百分比值
    let percent = time / duration;
    // 限制這個比值在0到1的區間
    percent = Math.max(0, Math.min(1, percent));
    // 使用sin讓percent轉變為淡出(θ要移-90°)
    let value = Math.sin(percent * Math.PI - Math.PI / 2);
    // 最後的值要加1再除以2,才會讓值從0變化至1
    return (value + 1) / 2;
}

CG示範專案

三角函數的運用方式千變萬化,同學們可以進行各種實驗,試著把曲線變成畫面上的美麗波動吧。

如果想進一步看看還有什麼數學曲線可資利用,請參考拙作,
數學妹子與遊戲漢子的相遇: (科普)Easing函式將遊戲可愛化了


上一篇
Trick 8: 狙擊槍的彈著點是在哈囉?
下一篇
Trick 10: 向量的旋轉原來要歪看正著
系列文
30個遊戲程設的錦囊妙計32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言