Python第83講:Pygame—提高遊戲的顏值3(影象透明度的處理)
影象是特定畫素的組合,而Surface 物件是Pygame裡面對影象的描述,在Pygame裡面到處都是Surface 物件,set_mode() 返回的是一個Surface物件,在介面上列印文字也需要先把文字渲染成 Surface 物件,然後再貼上去,小蛇在上面爬呀爬,其實就是不斷調整Surface物件上的一些特定的畫素的位置,把小蛇所在位置的畫素進行移動,就是小蛇在上面爬,就是呼叫 blit() 方法。imag.load() 方法載入影象並會返回一個 Surface 物件,我們此前都是直接拿來用,並沒有進行任何轉換,這樣子就是效率比較低的做法。如果你希望你的Pygame可以儘可能高效的處理你的圖片,你應該在圖片載入之後立刻呼叫 convert() 方法進行轉換。
如:bg = pygame.image.load(“bg.jpg”).convert()
事實上,遊戲都是由各種不同的圖片組成的,例如說:背景是一個圖片、裡面的主角是圖片、反派也是圖片、還有路人甲乙丙都是圖片。你總不可能用一個圓形或者矩陣畫一個主角吧。我們只能在現實中先用Photoshop 畫一個惟妙惟肖的主角,然後貼進去,然後使用 blit() 方法讓它移動。
有的人就很好奇了,不是說 image.load() 返回一個 Surface 物件嗎,那還轉換個毛線啊,這裡的轉換隻是畫素格式的轉換,而不是說轉換為Surface 物件,因為image.load() 載入之後就是一個 Surface 物件,但是我們載入之後(例如我們載入一個JPG格式的圖片,JPG圖片也是由畫素組成的,而這些畫素都是有顏色的,另外我們還可以將這個JPG圖片儲存為PNG,GIF格式,你會發現尺寸會發生改變了,這是因為裡面的畫素格式發生改變了,也就是說它組合這些畫素,把它描述的形式發生改變了),Surface 也有它自己的畫素格式,所以我們這裡的轉換指的就是圖片的畫素格式的轉換。
如果我們沒有在 image.load() 之後立刻對它進行轉換,但是轉換非常重要,Pygame也會你在 呼叫 blit() 方法時自動進行轉化,就是將一個圖片貼到另一個圖片之上的時候,因為兩個圖片要進行復制拷貝的操作,它們的畫素格式必須相同,因此在每次 blit() 的時候,它都會強制轉換一次,這樣子效率就相當低了,與其讓它每次迴圈去轉換一次,我們還不如在載入時呼叫 convert() 方法轉換為 Surface 的畫素格式。
雖然現在的CPU速度都很快,這一點細微的差別你可能看不出來,但是我們都希望我們的程式小一點、效率高一點。今後我們都會在 image.load() 之後立刻呼叫 convert()。
還有一個就是 convert_alpha() 方法:
如:turtle = pygame.image.load(“python.png”).convert_alpha()
這兩個方法有什麼區別呢?一般情況下,我們使用 RGB 來描述一個顏色,然而在遊戲開發中,我們常常用到的是 RGBA (RGBA是代表Red(紅色) Green(綠色) Blue(藍色)和 Alpha的色彩空間)來描述。Alpha 通道是用來表示透明度的,A 佔用一個位元組,也就是8位(0-255,256種層次),用序號來索引,就是0-255,0表示完全透明,255表示完全不透明。
我們都知道,image.load() 支援多種格式的圖片匯入,例如 gif、jpg、png等,這些都是當前流行的圖片格式,對於包含 Alpha 通道的圖片,我們就要用 convert_alpha() 方法來轉換格式了,其它的就用 conmvert() 方法。
我們也知道,jpg 格式的圖片是不包含 Alpha 通道的,因為它不能來表示透明。我們在做圖片的時候,我們知道,兩種常用的 透明格式就是 png和 gif 格式,而gif 還支援動圖,動圖在Pygame 裡面是不能解析的,一般我們在Pygame 裡面做的圖片都是以 png 圖片為主,因為 jpg 是有損的,你放大縮小它會損失精度,png 是無失真壓縮。
jpg 是不支援透明的,所以我們載入這類圖片就用 convert();而 png 是支援透明的,所以載入就用 convert_alpha()。
大家可以看一下下面兩張圖片:
左邊是 png 格式的原圖,是透明背景的,當我另存為 jpg 格式時,背景就不透明瞭。
如果你載入左邊的透明 png 圖片,使用 convert_alpha() 方法轉換和使用 convert() 方法轉換,比較一下。
import pygame import sys pygame.init() size = width, height = 900, 300 bg = (0, 255, 0) #為了便於區分,背景設為金色 clock = pygame.time.Clock() screen = pygame.display.set_mode(size) pygame.display.set_caption("Python Demo") turtle1 = pygame.image.load("turtle.png") #左圖 turtle2 = pygame.image.load("turtle.png").convert() #中圖 turtle3 = pygame.image.load("turtle.png").convert_alpha() #右圖 position1 = turtle1.get_rect() position1.center = width // 6, height // 2#居左顯示 position2 = turtle2.get_rect() position2.center = width // 2, height // 2#居中顯示 position3 = turtle3.get_rect() position3.center = 5 * width // 6, height // 2#居右顯示 while True: for event in pygame.event.get(): if event.type == pygame.QUIT: sys.exit() screen.fill(bg) screen.blit(turtle1, position1) screen.blit(turtle2, position2) screen.blit(turtle3, position3) pygame.display.flip() clock.tick(30)
這貌似跟正常出的結果不一樣,這可能是顏色本身的問題。(需要進一步研究~)
Pygame 支援三種透明度型別:colorkeys,surface alphas 和 pixel alphas(溫馨提示:colorkeys 是指設定影象中的某個顏色值為透明(比如說上面的烏龜有很多種綠色,我把其中一種綠色變為透明,那麼在這個圖片裡面,與這個顏色相同的部分都不見了,取而代之的是背景的顏色,因為透明事實上就是把背景顯示出來。),surface alphas 是調整整個影象的透明度,pixel alphas 則是獨立設定影象中每一個畫素的透明度)。
png 就是一個 piexl alphas,所以它每個畫素都有一個 Alpha 通道,指定這個畫素是否要變透明,透明度是多少。
surface alphas 可以和 colorkeys 混合使用,而 pixel alphas 不能和其他兩個混合。
說起來很複雜,其實說白了,convert() 方法轉換出來的 就支援 surface alphas 可以和 colorkeys 設定透明度,而且他們是可以混合設定的。而 convert_alpha() 方法轉換之後呢,就只支援 piexl alphas ,也就是說這個影象本身每個畫素就帶有Alpha 通道,我們載入一個帶有 Alpha 通道的圖片,我們會看到有一部分是透明的,就像我們上面的小烏龜,它的背景就是透明的。
我們來做一下實驗:
我們這裡有兩張圖片,一種是 jpg,背景白色;一張是 png,背景透明。
我們首先載入這張 jpg 圖片,為了更好區分,我們加了一個背景。
import pygame import sys from pygame.locals import * pygame.init() size = width, height = 640, 480 bg = (0, 0, 0) clock = pygame.time.Clock() screen = pygame.display.set_mode(size) pygame.display.set_caption("Python Demo") turtle = pygame.image.load("wagger.png").convert() background = pygame.image.load("green_grue.jpg").convert() position = turtle.get_rect() position.center = width // 2, height // 2 while True: for event in pygame.event.get(): if event.type == QUIT: sys.exit() screen.blit(background, (0, 0)) screen.blit(turtle, position) pygame.display.flip() clock.tick(30)
現在分別使用 set_colorkey() 把所有的白絲變為透明,turtle.set_alpha(200)把整幅影象變透明,以及同時使用兩者,來看一下效果:
import pygame import sys from pygame.locals import * pygame.init() size = height,width = 640,480 bg=(0,0,0) clock = pygame.time.Clock() screen = pygame.display.set_mode(size) pygame.display.set_caption("遇見你真好") turtle = pygame.image.load("wagger.png").convert() bg = pygame.image.load("green_grue.jpg").convert() position =turtle.get_rect() position.center = height // 2,width // 2 ############################################################# #試圖使用 set_colorkey() 把所有的白絲變為透明 turtle.set_colorkey((255, 255, 255)) ############################################################# #用 set_alpha() 方法來調節整個影象的透明度為200 turtle.set_alpha(200) ############################################################# while True: for event in pygame.event.get(): if event.type == quit: sys.exit() screen.blit(bg,(0,0)) screen.blit(turtle,position) pygame.display.flip() clock.tick(30)
仔細觀察我們可以發現,set_colorkey()結果並不優秀,因為邊緣並不是純白色的,結果並不是我們想要的,並不理想。set_alpha() 方法將整個圖片都變得微微透明瞭,但是這個把背景也帶上了,我們就想要小烏龜變透明,不想要白色邊框。兩種混合使用的話,效果依然不優秀。
wagger.jpg的影象沒有上述wagger.png影象的效果好。由於我們這個 png 是帶 Alpha 通道的,而且我們在做這個圖片的時候,已經把它的背景給扣成透明的了,下面的陰影不透明,但是不影響美觀(是故意做出的效果)。我們想把整個小烏龜調為透明度 200,讓小烏龜有一種隱身的既視感。但是我們說這個是 piexl alphas,也就是每個畫素都有一個 Alpha 通道,因此我們不能使用 turtle.set_alpha(200) 方法,我們可以使用 get_at() 方法來獲得單個畫素的透明度,並且用 set_at() 方法來修改它。
這樣可以在獲取單個畫素的透明度的情況下,
############################################################# #嘗試是否能夠獲得 單個畫素的透明度 print(turtle.get_at(position.center)) #獲取中間那個畫素的顏色 ############################################################# >>> ============================== RESTART ============================== pygame 1.9.4 Hello from the pygame community. https://www.pygame.org/contribute.html (130, 131, 26, 255)
利用迴圈把透明度設定為200。
############################################################# for i in range(position.width): for j in range(position.height): temp = turtle.get_at((i, j)) if temp[3] != 0: temp[3] = 200 turtle.set_at((i, j), temp) #############################################################
結果如下:
PS:上圖有一個小瑕疵,不知道你們看出來了沒有,就是青蛙不能居中顯示,具體的原因和解決方法有待研究。
效果還是不優秀啊。而且就算效果優秀了,你這樣一個一個畫素的來計算透明度,效率未免也很低
沒問題,程式是死的,人是活的,我這裡教大家一個新技能來 搞定這個問題:
先看一下解決方案,我們再來分析:
import pygame import sys from pygame.locals import * pygame.init() size = width, height = 800, 600 bg = (0, 0, 0) clock = pygame.time.Clock() screen = pygame.display.set_mode(size) pygame.display.set_caption("Python Demo") turtle = pygame.image.load("wagger.png").convert_alpha() background = pygame.image.load("white.png").convert() position = turtle.get_rect() position.center = width // 2, height // 2 ############################################################# def blit_alpha(target, source, location, opacity): x = location[0] y = location[1] temp = pygame.Surface((source.get_width(), source.get_height())).convert() temp.blit(target, (-x, -y )) temp.blit(source, (0, 0)) temp.set_alpha(opacity) target.blit(temp, location) ############################################################# while True: for event in pygame.event.get(): if event.type == QUIT: sys.exit() screen.blit(background, (0, 0)) ############################################################ #screen.blit(turtle, position) blit_alpha(screen, turtle, position, 200) ############################################################ pygame.display.flip() clock.tick(30)
PS:這效果又跟平常的不一樣,需要課後研究深入瞭解一下。
據說,效果是這樣的:
這是如何實現的呢?我們來逐句分析一下:
我們封裝了一個名為 blit_alpha() 的函式,下面我們就不要呼叫 blit() 方法了,而是呼叫blit_alpha(),因為我們在函式的末尾 呼叫了 blit() 方法。
在函式中,我們首先建立一個 Surface ,傳進來的是一個帶 Alpha 的,我們將其變為不帶 Alpha 的,其實我們只是需要它的一個矩形區域,
即:temp = pygame.Surface((source.get_width(), source.get_height())).convert()
temp 就是下圖種黑色矩形的位置,其實就是小烏龜圖片的覆蓋區域範圍。
然後我們在上邊繪製背景
即:temp.blit(target, (-x, -y ))
為什麼是(-x, -y)呢?
我們知道,x = location[0],y = location[1],而形參 location對應的實參是 position,就是小烏龜影象的位置,其實(x, y)就是下圖的A點,這個位置是相對於頂點B(0,0)(對於screen 來說,B就是(0,0))來說的,而temp.blit()是相對於小烏龜影象的來貼上背景,所以B相對於A點來說,就是(-x,-y)。
然後我們就在A的位置貼上背景透明的 小烏龜圖片
即:temp.blit(source, (0, 0))
然後我們將整個 temp 區域設為 透明度 200,其實現在的整個 temp 區域(Surface 物件)就是A區域大小,然後背景是該區域的草地,主角是小烏龜的一幅圖片。如圖所示:(這個就是目前的temp了)
我們將這個圖片 透明度設為 200。
即:temp.set_alpha(opacity) #opacity=200
這就巧妙的避開了 帶Alpha通道的Surface 不能呼叫set_alpha()方法的問題。因為現在的temp 是不帶Alpha通道的。
然後我們就把這個透明度為200的圖片貼在背景螢幕上,
即:target.blit(temp, location)