h5或flash中,可以直接對矢量對象,比如line, rectange旋轉,但是pygame中,僅支持對image旋轉,本以為這個是很簡單的事情,但是發現還是有很多小貓膩的,記錄一下:
先看一個錯誤的版本:

1 import pygame 2 import sys 3 4 pygame.init() 5 6 SIZE = WIDTH, HEIGHT = 200, 400 7 BLACK = 0, 0, 0 8 angle = 1 9 10 screen = pygame.display.set_mode(SIZE) 11 leaf = pygame.image.load("leaf.png") 12 leafRect = leaf.get_rect() 13 # 定位到舞台中心 14 leafRect = leafRect.move((WIDTH - leafRect.width) / 2, (HEIGHT - leafRect.height) / 2) 15 16 clock = pygame.time.Clock() 17 18 while True: 19 20 for event in pygame.event.get(): 21 if event.type == pygame.QUIT: 22 sys.exit() 23 24 # 旋轉圖片 25 leaf = pygame.transform.rotate(leaf, angle) 26 angle += 1 27 28 # 默認背景為白色,所以每渲染一幀,要對背景重新填充,否則會有上一幀的殘影 29 screen.fill(BLACK) 30 # 將旋轉后的圖象,渲染矩形里 31 screen.blit(leaf, leafRect) 32 # 正式渲染 33 pygame.display.update() 34 # 控制幀數<=100 35 clock.tick(100)
代碼中的leaf.png圖例如下:
跑一把:
這明顯跟我想的不一樣!代碼里並沒有對葉子做移動操作,只是每幀旋轉1度而已,為啥它要飄到舞台之外?
仔細review了下代碼,25行:leaf = pygame.transform.rotate(leaf, angle) 這里有問題,pygame在這方面做得說實話不算太好,字面上的意思,這行的效果,應該是每次在原來的基礎上,繼續加速旋轉(因為每次angle+1,相當於每幀旋轉角度越來越大)。
需要用一個新變量存儲旋轉后的圖片,參考下面的代碼:

1 import pygame 2 import sys 3 4 pygame.init() 5 6 SIZE = WIDTH, HEIGHT = 200, 400 7 BLACK = 0, 0, 0 8 angle = 1 9 10 screen = pygame.display.set_mode(SIZE) 11 leaf = pygame.image.load("leaf.png") 12 leafRect = leaf.get_rect() 13 leafRect = leafRect.move((WIDTH - leafRect.width) / 2, (HEIGHT - leafRect.height) / 2) 14 15 clock = pygame.time.Clock() 16 17 while True: 18 19 for event in pygame.event.get(): 20 if event.type == pygame.QUIT: 21 sys.exit() 22 23 keys = pygame.key.get_pressed() 24 25 # 旋轉圖片(注意:這里要搞一個新變量,存儲旋轉后的圖片) 26 newLeaf = pygame.transform.rotate(leaf, angle) 27 angle += 1 28 29 screen.fill(BLACK) 30 screen.blit(newLeaf, leafRect) 31 pygame.display.update() 32 clock.tick(100)
總算不亂跑,開始正經的旋轉了:
但是仔細觀察,還是有點小問題,旋轉過程中,葉子的中心位置總在晃動,預期效果最好是旋轉過程中,中心點不變。至於晃動的原因,葉子圖片並不是一個圓形,pygame中,任何一個Surface對象,總有一個外切的矩形對象(通過get_rect()方法可以獲得),圖片旋轉后,這個外切Rect對象的尺寸跟着變化,導致中心點也變化了。
校正方法如下(為了方便觀察,我們把葉子外圍正切的矩形也畫出來)
做為對比,剛才的中心晃動版本,也把矩形畫出來,參考以下代碼:

1 import pygame 2 import sys 3 4 pygame.init() 5 6 SIZE = WIDTH, HEIGHT = 200, 400 7 BLACK = 0, 0, 0 8 angle = 1 9 10 screen = pygame.display.set_mode(SIZE) 11 leaf = pygame.image.load("leaf.png") 12 leafRect = leaf.get_rect() 13 leafRect = leafRect.move((WIDTH - leafRect.width) / 2, (HEIGHT - leafRect.height) / 2) 14 15 clock = pygame.time.Clock() 16 17 while True: 18 19 for event in pygame.event.get(): 20 if event.type == pygame.QUIT: 21 sys.exit() 22 23 keys = pygame.key.get_pressed() 24 25 # 旋轉圖片(注意:這里要搞一個新變量,存儲旋轉后的圖片) 26 newLeaf = pygame.transform.rotate(leaf, angle) 27 newRect = newLeaf.get_rect() 28 angle += 1 29 30 screen.fill(BLACK) 31 screen.blit(newLeaf, leafRect) 32 pygame.draw.rect(screen, (255, 0, 0), leafRect, 1) 33 pygame.draw.rect(screen, (0, 255, 0), newRect, 1) 34 pygame.display.update() 35 clock.tick(100)
校正中心點后的版本:

1 import pygame 2 import sys 3 4 pygame.init() 5 6 SIZE = WIDTH, HEIGHT = 200, 400 7 BLACK = 0, 0, 0 8 angle = 1 9 10 screen = pygame.display.set_mode(SIZE) 11 leaf = pygame.image.load("leaf.png") 12 leafRect = leaf.get_rect() 13 leafRect = leafRect.move((WIDTH - leafRect.width) / 2, (HEIGHT - leafRect.height) / 2) 14 15 clock = pygame.time.Clock() 16 17 while True: 18 19 for event in pygame.event.get(): 20 if event.type == pygame.QUIT: 21 sys.exit() 22 23 keys = pygame.key.get_pressed() 24 25 # 旋轉圖片(注意:這里要搞一個新變量,存儲旋轉后的圖片) 26 newLeaf = pygame.transform.rotate(leaf, angle) 27 # 校正旋轉圖片的中心點 28 newRect = newLeaf.get_rect(center=leafRect.center) 29 angle += 1 30 31 screen.fill(BLACK) 32 # 這里要用newRect區域,繪制圖象 33 screen.blit(newLeaf, newRect) 34 pygame.draw.rect(screen, (255, 0, 0), leafRect, 1) 35 pygame.draw.rect(screen, (0, 255, 0), newRect, 1) 36 pygame.display.update() 37 clock.tick(100)
運行后的對照圖如下(左側為中心晃動的版本,右側為中心點不變的版本)
思考一下:為什么左側的圖,綠色的矩形框,一直在左上角,而右側的綠矩形框,會在中心?
答案:Rect對象默認生成時,其left,top屬性都是0, 所以旋轉后的新圖片,其外切矩形一直是在(0,0)位置,但是校正后的版本,get_rect(center=...)這里指定了中心點,所以newRect對象被對齊到跟紅色Rect一樣的中心位置。
(題外話:至於需要不需要中心點校正,完全看游戲場景,就本例而言,如果只是模擬一片樹葉落下,好象中心點晃動,也並不影響視覺感受,所以下面的示例,均沒有做中心點校正處理)
結合之前學到的東西,再加點趣味性,讓葉子在舞台上飄動起來,同時加入邊界碰撞檢測,但是要注意:葉子在旋轉過程中,外要的矩形尺寸,也會隨之變化(這會影響邊界檢測),為了觀察方便,在葉子外面畫一個框框。

1 import pygame 2 import sys 3 4 pygame.init() 5 6 SIZE = WIDTH, HEIGHT = 200, 400 7 BLACK = 0, 0, 0 8 RED = 255, 0, 0 9 SPEED = [1, 1] 10 angle = 1 11 12 screen = pygame.display.set_mode(SIZE) 13 originLeaf = pygame.image.load("leaf.png") 14 originRect = originLeaf.get_rect() 15 16 clock = pygame.time.Clock() 17 18 while True: 19 20 for event in pygame.event.get(): 21 if event.type == pygame.QUIT: 22 sys.exit() 23 24 # 利用矩形對象的move方法,讓rect移動 25 originRect = originRect.move(SPEED[0], SPEED[1]) 26 # 注意:這里一定要用一個新變量newLeaf,保存旋轉后的image對象 27 newLeaf = pygame.transform.rotate(originLeaf, angle) 28 angle += 1 29 30 # 注意:這里要定義一個新rect對象,因為圖象旋轉后,其外切的矩形尺寸會變化 31 newRect = newLeaf.get_rect() 32 # 默認的newRect位置在(0,0),要實現矩形外框跟隨,必須賦值到新位置 33 newRect.left, newRect.top = originRect.left, originRect.top 34 35 # 左右邊界反彈的處理 36 if newRect.left <= 0 or newRect.right >= WIDTH: 37 SPEED[0] = -SPEED[0] 38 39 # 上下邊界反彈的處理 40 if newRect.top <= 0 or newRect.bottom >= HEIGHT: 41 SPEED[1] = -SPEED[1] 42 43 # 默認背景為白色,所以每渲染一幀,要對背景重新填充,否則會有上一幀的殘影 44 screen.fill(BLACK) 45 # 畫新矩形 46 pygame.draw.rect(screen, RED, newRect, 1) 47 # 將旋轉后的圖象,渲染到新矩形里 48 screen.blit(newLeaf, originRect) 49 # 正式渲染 50 pygame.display.update() 51 # 控制幀數<=100 52 clock.tick(100)
基本達到效果了,但是細心觀察的話,發現右邊界和下邊界,碰撞檢測其實不夠完美,從視覺上看,明明已經到了邊界,但是沒有及時反彈。
修正一下:

1 import pygame 2 import sys 3 4 pygame.init() 5 6 SIZE = WIDTH, HEIGHT = 200, 400 7 BLACK = 0, 0, 0 8 RED = 255, 0, 0 9 SPEED = [1, 1] 10 angle = 1 11 12 screen = pygame.display.set_mode(SIZE) 13 originLeaf = pygame.image.load("leaf.png") 14 originRect = originLeaf.get_rect() 15 16 clock = pygame.time.Clock() 17 18 while True: 19 20 for event in pygame.event.get(): 21 if event.type == pygame.QUIT: 22 sys.exit() 23 24 # 利用矩形對象的move方法,讓rect移動 25 originRect = originRect.move(SPEED[0], SPEED[1]) 26 # 注意:這里一定要用一個新變量newLeaf,保存旋轉后的image對象 27 newLeaf = pygame.transform.rotate(originLeaf, angle) 28 angle += 1 29 30 # 注意:這里要定義一個新rect對象,因為圖象旋轉后,其外切的矩形尺寸會變化 31 newRect = newLeaf.get_rect() 32 # 默認的newRect位置在(0,0),要實現矩形外框跟隨,必須賦值到新位置 33 newRect.left, newRect.top = originRect.left, originRect.top 34 35 # 左右邊界反彈的處理 36 if newRect.left <= 0 or newRect.right >= WIDTH: 37 SPEED[0] = -SPEED[0] 38 # 圖片移動到接近右邊界時(比如:right=198),由於旋轉的作用,可能導致葉子一下橫過來了, 39 # right突然會變成210,這樣就算速度取反了,由於SPEED[0]=-1,需要10幀后,才能從視覺上真正看到反彈成功(即:210減到200,需要10次) 40 if newRect.right > WIDTH: 41 originRect.left = WIDTH - newRect.width 42 43 # 上下邊界反彈的處理 44 if newRect.top <= 0 or newRect.bottom >= HEIGHT: 45 SPEED[1] = -SPEED[1] 46 # 類似右邊界的校正處理,防止葉子接近下邊界時,由於旋轉,一下從橫到豎,高度突然加大,導致越界 47 if newRect.bottom > HEIGHT: 48 originRect.top = HEIGHT - newRect.height 49 50 # 默認背景為白色,所以每渲染一幀,要對背景重新填充,否則會有上一幀的殘影 51 screen.fill(BLACK) 52 # 畫新矩形 53 pygame.draw.rect(screen, RED, newRect, 1) 54 # 將旋轉后的圖象,渲染到新矩形里 55 screen.blit(newLeaf, originRect) 56 # 正式渲染 57 pygame.display.update() 58 # 控制幀數<=100 59 clock.tick(100)
看上去好多了
最后再加點料,根據葉子的運動情況,動態調整背景顏色:

1 import pygame 2 import sys 3 import random 4 import math 5 6 pygame.init() 7 8 SIZE = WIDTH, HEIGHT = 200, 400 9 BACKGROUND_COLOR = (0, 0, 0) 10 11 screen = pygame.display.set_mode(SIZE) 12 leaves = [] 13 14 15 class Leaf(object): 16 def __init__(self, pos=[10, 10], speed=[1, 1]): 17 self.imageSrc = pygame.image.load("leaf.png") 18 self.rect = self.imageSrc.get_rect() 19 self.image = self.imageSrc 20 self.speed = speed 21 self.angle = 0 22 self.pos = pos 23 self.rect = self.rect.move(pos[0], pos[1]) 24 25 def move(self): 26 self.rect = self.rect.move(self.speed[0], self.speed[1]) 27 new_rect = self.image.get_rect() 28 new_rect.left, new_rect.top = self.rect.left, self.rect.top 29 if new_rect.left < 0 or new_rect.right > WIDTH: 30 self.speed[0] = -self.speed[0] 31 if new_rect.right > WIDTH: 32 self.rect.left = WIDTH - new_rect.width 33 if new_rect.left < 0: 34 self.rect.left = 0 35 if new_rect.top < 0 or new_rect.bottom > HEIGHT: 36 self.speed[1] = -self.speed[1] 37 if new_rect.bottom > HEIGHT: 38 self.rect.top = HEIGHT - new_rect.height 39 40 def draw(self): 41 screen.blit(self.image, self.rect) 42 43 def rotate(self): 44 self.image = pygame.transform.rotate(self.imageSrc, self.angle) 45 self.angle += random.randint(1, 5) 46 if math.fabs(self.angle) == 360: 47 self.angle = 0 48 49 50 def init(): 51 for i in range(0, 3): 52 leaf = Leaf([random.randint(50, WIDTH - 50), random.randint(30, HEIGHT - 200)], 53 [random.randint(1, 2), random.randint(1, 2)]) 54 leaf.move() 55 leaves.append(leaf) 56 57 58 def to255(x): 59 if x > 1: 60 x = 1 61 return int(255 * math.fabs(x)) 62 63 64 clock = pygame.time.Clock() 65 init() 66 67 while True: 68 69 for event in pygame.event.get(): 70 if event.type == pygame.QUIT: 71 sys.exit() 72 73 first_rect = leaves[0].rect 74 75 # 根據第1片葉子的運動情況,隨機切換背景色 76 color_r = to255(first_rect.top / HEIGHT) 77 color_g = to255(first_rect.left / WIDTH) 78 color_b = to255(math.fabs(first_rect.left) / (math.fabs(first_rect.top) + math.fabs(first_rect.left) + 1)) 79 # print(color_r, color_g, color_b) 80 screen.fill((color_r, color_g, color_b)) 81 82 # 將旋轉后的圖象,渲染到新矩形里 83 for item in leaves: 84 item.rotate() 85 item.move() 86 item.draw() 87 88 # 正式渲染 89 pygame.display.update() 90 # 控制幀數<=100 91 clock.tick(100)
下邊界檢測時,還能做些變化,比如:葉子落到地面以下,就重新放回頂端,這樣就有漫天落葉,綿綿不斷的感覺:

1 import pygame 2 import sys 3 import random 4 import math 5 6 pygame.init() 7 8 SIZE = WIDTH, HEIGHT = 200, 400 9 BACKGROUND_COLOR = (230, 255, 230) 10 11 screen = pygame.display.set_mode(SIZE) 12 leaves = [] 13 14 15 class Leaf(object): 16 def __init__(self, pos=[10, 10], speed=[1, 1]): 17 self.imageSrc = pygame.image.load("leaf.png") 18 self.rect = self.imageSrc.get_rect() 19 self.image = self.imageSrc 20 self.speed = speed 21 self.angle = 0 22 self.pos = pos 23 self.rect = self.rect.move(pos[0], pos[1]) 24 25 def move(self): 26 self.rect = self.rect.move(self.speed[0], self.speed[1]) 27 new_rect = self.image.get_rect() 28 new_rect.left, new_rect.top = self.rect.left, self.rect.top 29 if new_rect.left < 0 or new_rect.right > WIDTH: 30 self.speed[0] = -self.speed[0] 31 if new_rect.right > WIDTH: 32 self.rect.left = WIDTH - new_rect.width 33 if new_rect.left < 0: 34 self.rect.left = 0 35 if new_rect.top > HEIGHT: 36 self.rect.bottom = 0 37 38 def draw(self): 39 screen.blit(self.image, self.rect) 40 41 def rotate(self): 42 self.image = pygame.transform.rotate(self.imageSrc, self.angle) 43 self.angle += random.randint(1, 5) 44 if math.fabs(self.angle) == 360: 45 self.angle = 0 46 47 48 def init(): 49 for i in range(0, 5): 50 leaf = Leaf([random.randint(50, WIDTH - 50), random.randint(30, HEIGHT)], 51 [random.randint(1, 2), random.randint(1, 2)]) 52 leaf.move() 53 leaves.append(leaf) 54 55 56 clock = pygame.time.Clock() 57 init() 58 59 while True: 60 61 for event in pygame.event.get(): 62 if event.type == pygame.QUIT: 63 sys.exit() 64 65 screen.fill(BACKGROUND_COLOR) 66 67 # 將旋轉后的圖象,渲染到新矩形里 68 for item in leaves: 69 item.rotate() 70 item.move() 71 item.draw() 72 73 # 正式渲染 74 pygame.display.update() 75 # 控制幀數<=100 76 clock.tick(100)
參考:https://www.pygame.org/wiki/RotateCenter?parent=CookBook