Move Your Body!! —三角函數
在遊戲中,各個角色例如玩家、怪物,都需要移動,如果是水平或垂直移動,那很容易,只要增減x, y座標即可達成;但如果今天要移動的方向並非0, 90, 45度,而是60度呢?
或者,該如何找到朝向一個座標的方向?讓物件能夠行走到指定的位置。甚至是將座標旋轉,製造出可以旋轉的多邊形。
這些,都需要利用三角函數。
三角函數是什麼?
正如同上面我所描述的各個需求,三角函數其實就是 「轉換長度與角度」。
想一想,今天我們要朝著一個角度移動,我們不就是要將這個「角度」給轉換成x與y分別移動的「長度」嗎?算出到一個座標的「角度」,也是將x與y的「長度」換算而來。
因此,三角函數在遊戲開發中有著非常廣泛的用途,牽涉到一切物件的移動都需要依賴它。很幸運的,在Python中已經有開發好的函式庫,我只要呼叫並傳入數字就好,因此我們只需要專注在邏輯上面。
讓我朝著60度前進吧!
該如何讓玩家朝向60度前進?由於在遊戲中我們都是使用直角座標,因此我們要將「角度」以及「移動距離」轉換成x和y分別增加的量。這時就要使用三角函數最廣為人知的兩位大大:sin and cos !!
上圖連結自維基百科
先談談一個三角形的各部位名稱,首先請看 角A ,角A這個角很重要,在接下來的說明中,我們所提到的「角度」都會是指這個角的角度,而這個角度我們就稱為 theta ,由於電腦沒辦法打出來,接下來我會以 angle 代稱之。
而圖中的 邊a 則是所謂的對邊,我們y座標的值就是對邊的長度;而邊b則是鄰邊,我們x座標的值就是鄰邊的長度。最後邊h,就是斜邊,我們前進的距離就是斜邊的長度。
那在這個例子中,我們就是要將 a的角度 與 斜邊的長度 替換成 鄰邊與對邊的長度 。
那接下來介紹我們的幫手大大,首先是 sin 大大,sin大大會負責將我們的角度angle轉換成 斜邊為1時 對邊的長度,只要再將此數值乘上我們的斜邊長,就可以求出對邊長,也就是y座標的值了。
那 cos 大大其實做的也是差不多的工作,只是它負責的是鄰邊,也就是x。所以我們只要將cos所求出的值乘上斜邊長,就可以得到x的長度了。
那該如何請這兩位大大工作呢?由於sin與cos兩位大大都是函數,我們只要將angle放入「括號」中,送給它們處理,它們計算完,就會告訴我們答案了,是不是很簡單呢?
但是如果你就這樣傻傻的直接把60傳給它,它可是會森77的喔!因為sin與cos以及全部的三角函數,使用的都不是我們平常習慣的,360一圈的「度」,而是「弧度」。
弧度
弧度跟度一樣,是一種角度單位,度是360為一個單位,而弧度則是2倍圓周率為一個圓。
聽起來…好像是有點討厭的東西,算個角度也要扯到圓周率?真是夠了。但其實弧度的定義是十分優雅而簡潔的:
單位弧度定義為圓弧長度等於半徑時的圓心角
—維基百科
哇…我到底看了什麼…簡單來說,就是當一個扇形,它的弧長跟半徑相等時,它的圓心角就稱為「一弧度」,而一弧度則約為57.2957795度…看了很討厭?沒關係我們是工程師,這種工作就交給電腦去算就好了。
一個完整的圓它的弧度就會是 2pi,我們要將60度換算成弧度,就是將 (60 / 360) * 2pi = 60 * pi / 180 。
那我們就來寫一個換算的函數,來將「度」換成「弧度」。1
2
3
4
5
6
7
8
9
10'''
定義 函數 deg_to_rad 將「度(degree)」換為「弧度(rad)」
輸入:度
輸出:弧度
'''
import math #引用了Python的 數學庫
def deg_to_rad (deg):
return deg * math.pi / 180
# 其中的 math.pi 是一個定義在Python math 函式庫的常數,我們透過呼叫它來取得較精準的 pi 值。
# 實作者:林宏信
完成了弧度的處理,我們回到前面來計算三角函數。
大致的流程如下:將角度(angle)換為弧度(rad_angle),再來將值分別傳入sin 與 cos 之中,將回傳值乘上移動距離(distance),即可算出x, y分別須移動的量。
1 | ''' |
叫那隻殭屍…給我滾開!!
在我的遊戲中,有一種怪物 —殭屍,它會不停朝著玩家移動,玩家碰到就會被攻擊。但問題來了,我們只會朝著一個「角度」前進,並不會朝著一組「座標」前進啊!
要朝著一組座標前進其實有兩種解法,一是先算出朝向目標的角度,再等速前進;二是算出x與y分別平均須移動多少。
在這個單元我會先介紹第二個方法,找出角度的部份我會放在下個單元介紹。
平均的 x 與 y
假設我們要從 (0, 0) 移動到 (3, 4) ,那我們就是將x座標增加3、y座標增加4。你可能會覺得:嗯…這還用說嗎?但是!假如我們今天要慢動作呢?
假如今天我不要一瞬間就從 (0, 0) 移動到 (3, 4),而是分成十次進行呢?
簡單嘛!把 3 / 10、 4 / 10,一次移動 (0.3, 0.4) 不就好了?
是的,剛才這一串很像是廢話的描述,其實就是解題的關鍵!
怎麼說?從我們剛才的推論,我們得知,要將x平均移動,只要把總共需要移動的x長度,在本範例中就是3,除上分解的次數,在本範例中就是10。
不過,有時候我們並不知道要分解成幾次啊。如果我想要等速運動,我只會知道我的速度會是多少,但不會知道我總共要分解幾次,這該怎麼辦?
我們搬出距離與速度公式: 距離 = 速度 * 時間。所以時間就會等於 距離 / 速度。速度我們已經設定好了,那距離就是用畢氏定理,以此範例來說就是5,我們接下來用 distance 作為代號。
因此,計算x的完整算式就是:x / (distance / speed),我們化簡一下,就會變成 x / distance speed。那y就會是 y / distance speed。
這一段…怎麼看起來有點眼熟?x就是鄰邊,distance就是斜邊,鄰邊除上斜邊…天啊!我們正在算三角函數!cos的定義就是鄰邊除上斜邊,而這正是我們現在正在做的!
所以其實我們並不一定要使用三角函數才能達成,有的時候,我們甚至可以透過實際的數字來「自己算出三角函數」。
那以下實做將定義在殭屍類別 Zombie 之中的物件方法:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16'''
定義 Zombie 與 Zombie.near 方法,near輸入target目標與移動速度
輸入:target :帶有 x, y兩個屬性
speed :移動的速度
輸出
'''
class Zombie:
def __init__(self):
self.x = 0
self.y = 0
def near (self, target, speed):
x, y = target.x - self.x, target.y - self.y
d = (x ** 2 + y ** 2) ** 0.5
self.x += x / d * speed
self.y += y / d * speed