1. 程式人生 > 實用技巧 >Python第83講:Pygame—提高遊戲的顏值3(影象透明度的處理)

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)