是時候讓我們的遊戲活潑起來了。電腦遊戲和桌面遊戲的一個巨大差別,想來就是這個“動”。偉大的哲學家們告訴我們,“運動是絕對的,靜止時相對的”,同樣的在遊戲中,只有活動起來,遊戲才會擁有生命,否則和看連環畫有什麼差別呢?
這幾章講述的東西需要一些線性代數的知識,好吧有些誇張,如果你不明白,完全沒關係,高中物理的知識就絕對足夠了(或者說嫌多了)!
現實生活中的物體,運動起來總是按照某種規律的(去問問牛頓就知道了),而遊戲中,有些動作就可以非常的不靠譜,比如吃豆人,大嘴巴永遠以恆定的速度前進,可以瞬間轉身或停止,要知道,這可是逆天的行為……現在的遊戲中,製作者總是儘量的把運動做的和現實貼近(尤其是賽車遊戲等),一輛車的運動,可能是上百種力同時作用的結果。不過幸好,我們只要知道一些基礎的東西,很多運動和力的計算,都有現成的程式碼供我們使用。
理解幀率
這是一個被說爛了的詞,FPS(Frame Per Second)是遊戲和硬體間較量的永恆話題,我也不想多插話了,相信玩遊戲的朋友都知道。
只是記住幾個常用的量:一般的電視畫面是24FPS;30FPS基本可以給玩家提供流程的體驗了;LCD的話,60FPS是常用的重新整理率,所以你的遊戲的幀率再高也就沒什麼意義了;而絕大多數地球人都無法分辨70FPS以上的畫面了!
直線運動
我們先來看一下初中一開始就學習的直線運動,我們讓一開始的程式中出現的那條魚自己動起來~
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 |
background_image_filename = 'sushiplate.jpg' sprite_image_filename = 'fugu.png' import pygame from pygame.locals import * from sys import exit 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) # sprite的起始x座標 x = 0. while True: for event in pygame.event.get(): if event.type == QUIT: exit() screen.blit(background, (0,0)) screen.blit(sprite, (x, 100)) x+= 10. #如果你的機器效能太好以至於看不清,可以把這個數字改小一些 # 如果移動出螢幕了,就搬到開始位置繼續 if x > 640.: x = 0. pygame.display.update() |
我想你應該需要調節一下“x += 10.”來讓這條魚遊的自然一點,不過,這個動畫的幀率是多少的?在這個情形下,動畫很簡單,所以應該會很快;而有些時候動畫元素很多,速度就會慢下來。這可不是我們想看到的!
關於時間
有一個解決上述問題的方法,就是讓我們的動畫基於時間運作,我們需要知道上一個畫面到現在經過了多少時間,然後我們才能決定是否開始繪製下一幅。pygame.time模組給我們提供了一個Clock的物件,使我們可以輕易做到這一些:
1 2 3 |
clock = pygame.time.Clock() time_passed = clock.tick() time_passed = clock.tick(30) |
第一行初始化了一個Clock物件;第二行的意識是返回一個上次呼叫的時間(以毫秒計);第三行非常有用,在每一個迴圈中加上它,那麼給tick方法加上的引數就成為了遊戲繪製的最大幀率,這樣的話,遊戲就不會用掉你所有的CPU資源了!但是這僅僅是“最大幀率”,並不能代表使用者看到的就是這個數字,有些時候機器效能不足,或者動畫太複雜,實際的幀率達不到這個值,我們需要一種更有效的手段來控制我們的動畫效果。
為了使得在不同機器上有著一致的效果,我們其實是需要給定物體(我們把這個物體叫做精靈,Sprite)恆定的速度。這樣的話,從起點到終點的時間點是一樣的,最終的效果也就相同了,所差別的,只是流暢度。看下面的圖試著理解一下~
我們把上面的結論實際試用一下,假設讓我們的小魚兒每秒遊動250畫素,這樣遊動一個螢幕差不多需要2.56秒。我們就需要知道,從上一幀開始到現在,小魚應該遊動了多少畫素,這個演算法很簡單,速度*時間就行了,也就是250 * time_passed_second。不過我們剛剛得到的time_passed是毫秒,不要忘了除以1000.0,當然我們也能假設小魚每毫秒遊動0.25畫素,這樣就可以直接乘了,不過這樣的速度單位有些怪怪的……
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 |
background_image_filename = 'sushiplate.jpg' sprite_image_filename = 'fugu.png' import pygame from pygame.locals import * from sys import exit 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) # Clock物件 clock = pygame.time.Clock() x = 0. # 速度(畫素/秒) speed = 250. while True: for event in pygame.event.get(): if event.type == QUIT: exit() screen.blit(background, (0,0)) screen.blit(sprite, (x, 100)) time_passed = clock.tick() time_passed_seconds = time_passed / 1000.0 distance_moved = time_passed_seconds * speed x += distance_moved # 想一下,這裡減去640和直接歸零有何不同? if x > 640.: x -= 640. pygame.display.update() |
好了,這樣不管你的機器是更深的藍還是開啟個記事本都要吼半天的淘汰機,人眼看起來,不同螢幕上的魚的速度都是一致的了。請牢牢記住這個方法,在很多情況下,通過時間控制要比直接調節幀率好用的多。
斜線運動
下面有一個更有趣一些的程式,不再是單純的直線運動,而是有點像屏保一樣,碰到了壁會反彈。不過也並沒有新的東西在裡面,原理上來說,反彈只不過是把速度取反了而已~ 可以先試著自己寫一個,然後與這個對照一下。
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 46 47 48 49 50 |
background_image_filename = 'sushiplate.jpg' sprite_image_filename = 'fugu.png' import pygame from pygame.locals import * from sys import exit 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() x, y = 100., 100. speed_x, speed_y = 133., 170. while True: for event in pygame.event.get(): if event.type == QUIT: exit() screen.blit(background, (0,0)) screen.blit(sprite, (x, y)) time_passed = clock.tick(30) time_passed_seconds = time_passed / 1000.0 x += speed_x * time_passed_seconds y += speed_y * time_passed_seconds # 到達邊界則把速度反向 if x > 640 - sprite.get_width(): speed_x = -speed_x x = 640 - sprite.get_width() elif x < 0: speed_x = -speed_x x = 0. if y > 480 - sprite.get_height(): speed_y = -speed_y y = 480 - sprite.get_height() elif y < 0: speed_y = -speed_y y = 0 pygame.display.update() |
OK,這次的運動就說到這裡。仔細一看的話,就會明白遊戲中的所謂運動(尤其是2D遊戲),不過是把一個物體的座標改一下而已。不過總是不停的計算和修改x和y,有些麻煩不是麼,下次我們引入向量,看看使用數學怎樣可以幫我們減輕負擔。