用Pygame和Python做遊戲-從入門到精通(9)

pythontab發表於2012-12-20

用Python和Pygame寫遊戲-從入門到精通(9)

上次我們說到了向量,不得不說向量是一個偉大的發明,在單純的數字運算之中,居然就把方向也包含其中。對於如今的我們來看,非常普通的事情,幾百年前的人們能夠考慮到這個,實在是非常的不容易。不過同時我們也要有這樣的意識——我們現在所使用的數學,未必就是最完美的。時代發展科技進步,或許我們會有更好的方式來詮釋我們的世界。想想一片葉子飄落,有它獨特的軌跡,如果要人類計算出來那個軌跡,即便可能,也是無比繁雜的。葉子懂我們的數學嗎?不,它不懂,但它就優雅的落了下來。自然有著我們尚無法理解的思考方式,我們現在所使用的工具,還是太複雜!人類要向“道”繼續努力才行啊。

魔法禁書目錄,一方通行

“一方通行”憑藉向量的力量成為了學園第一能力者,我們是否也應該好好學習向量?

扯遠了,雖然不記得學校裡是什麼時候開始接觸到向量的,不過肯定也不會太晚,如果你不知道什麼是向量,最好先找一本書看看吧,這裡只會有一些最最核心的講解。

引入向量

我們先考慮二維的向量,三維也差不多了,而遊戲中的運動最多隻用得到三維,更高的留給以後的遊戲吧~

向量的表示和座標很像,(10,20)對座標而言,就是一個固定的點,然而在向量中,它意味著x方向行進10,y方向行進20,所以座標(0,0)加上向量(10,20)後,就到達了點(10,20)。

向量可以透過兩個點來計算出來,如下圖,A經過向量AB到達了B,則向量AB就是(30, 35) – (10, 20) = (20, 15)。我們也能猜到向量BA會是(-20, -15),注意向量AB和向量BA,雖然長度一樣,但是方向不同。

建立向量

在Python中,我們可以建立一個類來儲存和獲得向量(雖然向量的寫法很像一個元組,但因為向量有很多種計算,必須使用類來完成):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Vector2(object):
    def __init__(self, x=0.0, y=0.0):
        self.x = x
        self.y = y
    def __str__(self):
        return "(%s, %s)"%(self.x, self.y)
 
    @classmethod
    def from_points(cls, P1, P2):
        return cls( P2[0] – P1[0], P2[1] – P1[1] )
#我們可以使用下面的方法來計算兩個點之間的向量
A = (10.0, 20.0)
B = (30.0, 35.0)
AB = Vector2.from_points(A, B)
print AB

原理上很簡單,函式修飾符@不用我說明了吧?如果不明白的話,可以參考Python的程式設計指南。

向量的大小

向量的大小可以簡單的理解為那根箭頭的長度,勾股定理熟稔的各位立刻知道怎麼計算了:

1
2
    def get_magnitude(self):
        return math.sqrt( self.x**2 + self.y**2 )

把這幾句加入到剛剛的Vector2裡,我們的向量類就多了計算長度的能力。嗯,別忘了一開始要引入math庫。

單位向量

一開頭說過,向量有著大小和方向兩個要素,透過剛剛的例子,我們可以理解這兩個意思了。在向量的大家族裡,有一種比較特殊的向量叫“單位向量”,意思是大小為1的向量,我們還能把任意向量方向不變的縮放(體現在數字上就是x和y等比例的縮放)到一個單位向量,這叫向量的規格(正規)化,程式碼體現的話:

1
2
3
4
    def normalize(self):
        magnitude = self.get_magnitude()
        self.x /= magnitude
        self.y /= magnitude

使用過normalize方法以後,向量就成了一個單位向量。單位向量有什麼用?我們以後會看到。

向量運算

我們觀察下圖,點B由A出發,透過向量AB到達,C則有B到達,透過BC到達;C直接由A出發的話,就得經由向量AC。

向量加法

由此我們得到一個顯而易見的結論向量AC = 向量AB + 向量BC。向量的加法計算方法呼之欲出:

(20, 15) + (-15, 10) = (20-15, 15+10) = (5, 25)

把各個方向分別相加,我們就得到了向量的加法運演算法則。很類似的,減法也是同樣,把各個方向分別想減,可以自己簡單驗證一下。程式碼表示的話:

1
2
3
4
    def __add__(self, rhs):
        return Vector2(self.x + rhs.x, self.y + rhs.y)
    def __sub__(self, rhs):
        return Vector2(self.x - rhs.x, self.y - rhs.y)

兩個下劃線“__”為首尾的函式,在Python中一般就是過載的意思,如果不知道的話還需要稍微努力努力:)當然,功力稍深厚一點的,就會知道這裡super來代替Vector2可能會更好一些,確實如此。不過這裡只是示例程式碼,講述一下原理而已。

有加減法,那乘除法呢?當然有!不過向量的乘除並不是發生在兩個向量直接,而是用一個向量來乘/除一個數,其實際意義就是,向量的方向不變,而大小放大/縮小多少倍。如下圖:
向量乘除法

1
2
3
4
    def __mul__(self, scalar):
        return Vector2(self.x * scalar, self.y * scalar)
    def __div__(self, scalar):
        return Vector2(self.x / scalar, self.y / scalar)

向量的運算被廣泛的用來計算到達某個位置時的中間狀態,比如我們知道一輛坦克從A到B,中間有10幀,那麼很顯然的,把步進向量透過(B-A)/10計算出來,每次在當前位置加上就可以了。很簡單吧?

更好的向量類

我們創造的向量類已經不錯了,不過畢竟只能做一些簡單的運算,別人幫我們已經寫好了更帥的庫(早點不拿出來?寫了半天…… 原理始終是我們掌握的,自己動手,印象更深),是發揮拿來主義的時候了(可以嘗試使用easy_install gameobjects簡單的安裝起來)。下面是一個使用的例子:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from gameobjects.vector2 import *
A = (10.0, 20.0)
B = (30.0, 35.0)
AB = Vector2.from_points(A, B)
print "Vector AB is", AB
print "AB * 2 is", AB * 2
print "AB / 2 is", AB / 2
print "AB + (–10, 5) is", AB + (10, 5)
print "Magnitude of AB is", AB.get_magnitude()
print "AB normalized is", AB.get_normalized()
 
# 結果是下面
Vector AB is ( 20, 15 )
AB * 2 is ( 40, 30 )
AB / 2 is ( 10, 7.5 )
AB + (-10, 5) is ( 10, 20 )
Magnitude of AB is 25.0
AB normalized is ( 0.8, 0.6 )

使用向量的遊戲動畫

終於可以實幹一番了!這個例子比我們以前寫的都要帥的多,小魚不停的在我們的滑鼠周圍遊動,若即若離:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
background_image_filename = 'sushiplate.jpg'
sprite_image_filename = 'fugu.png'
 
import pygame
from pygame.locals import *
from sys import exit
from gameobjects.vector2 import Vector2
 
pygame.init()
 
screen = pygame.display.set_mode((640, 480), 0, 32)
 
background = pygame.image.load(background_image_filename).convert()
sprite = pygame.image.load(sprite_image_filename).convert_alpha()
 
clock = pygame.time.Clock()
 
position = Vector2(100.0, 100.0)
heading = Vector2()
 
while True:
 
    for event in pygame.event.get():
        if event.type == QUIT:
            exit()
 
    screen.blit(background, (0,0))
    screen.blit(sprite, position)
 
    time_passed = clock.tick()
    time_passed_seconds = time_passed / 1000.0
 
    # 引數前面加*意味著把列表或元組展開
    destination = Vector2( *pygame.mouse.get_pos() ) - Vector2( *sprite.get_size() )/2
    # 計算魚兒當前位置到滑鼠位置的向量
    vector_to_mouse = Vector2.from_points(position, destination)
    # 向量規格化
    vector_to_mouse.normalize()
 
    # 這個heading可以看做是魚的速度,但是由於這樣的運算,魚的速度就不斷改變了
    # 在沒有到達滑鼠時,加速運動,超過以後則減速。因而魚會在滑鼠附近晃動。
    heading = heading + (vector_to_mouse * .6)    
 
    position += heading * time_passed_seconds
    pygame.display.update()

雖然這個例子裡的計算有些讓人看不明白,但是很明顯heading的計算是關鍵,如此複雜的運動,使用向量居然兩句話就搞定了~看來沒有白學。

動畫總結

  • 正如上一章所說,所謂動畫,不過是在每一幀上,相對前一幀把精靈的座標在加減一些而已;
  • 使用時間來計算加減的量以在不同效能的計算機上獲得一致的動畫效果;
  • 使用向量來計算運動的過程來減輕我們的勞動,在3D的情況下,簡單的使用Vector3便可以了。

如今我們已經學習到了遊戲動畫製作的精髓,一旦可以動起來,就能創造無數讓人歎為觀止的效果,是不是應該寫個程式在朋友們面前炫耀炫耀了?
在下面,我們要學習接受輸入和遊戲裡的物體互動起來。


相關文章