pygame 筆記-9 圖片旋轉及邊界反彈


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-01

代碼中的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)
leaf-02

總算不亂跑,開始正經的旋轉了: 

但是仔細觀察,還是有點小問題,旋轉過程中,葉子的中心位置總在晃動,預期效果最好是旋轉過程中,中心點不變。至於晃動的原因,葉子圖片並不是一個圓形,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)
leaf-02-with-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     # 校正旋轉圖片的中心點
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)
leaf-03

 運行后的對照圖如下(左側為中心晃動的版本,右側為中心點不變的版本)

思考一下:為什么左側的圖,綠色的矩形框,一直在左上角,而右側的綠矩形框,會在中心?

答案: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)
View Code

 基本達到效果了,但是細心觀察的話,發現右邊界和下邊界,碰撞檢測其實不夠完美,從視覺上看,明明已經到了邊界,但是沒有及時反彈。

修正一下:

 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)
View Code

看上去好多了

最后再加點料,根據葉子的運動情況,動態調整背景顏色:

 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)
View Code

下邊界檢測時,還能做些變化,比如:葉子落到地面以下,就重新放回頂端,這樣就有漫天落葉,綿綿不斷的感覺:

 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)
View Code

參考:https://www.pygame.org/wiki/RotateCenter?parent=CookBook


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM