這又是Pygame教程系列的一個——OVA篇,類似於py2exe篇一樣,額外寫的,也許不是pygame遊戲開發必須的東西,但是知道了絕對大有裨益。因此友情大放送~
看pygame模組介紹的時候,細心的人會發現有一個pygame.sprite模組,而在講動畫的時候,雖然引入了精靈這個概念,卻沒有使用這個模組。在官方文件上也說了,這個模組是輕量級的,在遊戲開發中也未必要使用。講解動畫的時候為了避免太多新東西,直接把一個surface畫來畫去,難道沒有人覺得不和諧麼:)我們這次試著使用Sprite把動畫變的更簡單一些(不過這裡沒有使用GameObjects,兩者結合更健康~)。
“sprite”,中文翻譯“精靈”,在遊戲動畫一般是指一個獨立運動的畫面元素,在pygame中,就可以是一個帶有影象(Surface)和大小位置(Rect)的物件。 精靈特別適合用在OO語言中,比如Python。
pygame.sprite.Sprite是pygame精靈的基類,一般來說,你總是需要寫一個自己的精靈類繼承一下它然後加入自己的程式碼。舉個例子:
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 |
import cStringIO, base64 import pygame from pygame.locals import * class Ball(pygame.sprite.Sprite): def __init__(self, color, initial_position): pygame.sprite.Sprite.__init__(self) ball_file = cStringIO.StringIO(base64.decodestring( """iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAYAAADE6YVjAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJ bWFnZVJlYWR5ccllPAAABBJJREFUeNqsVj2PG1UUvfPp8XictXfHa+9mlyJCNEQRWiToqACJAgGC LqJNlQZR0IFEj8RPSJkGGooUpEWJkGhR0tAAElI2tsfjjxnPjIdz7oyDF2wSUK72yN43793z7rkf Y8N2HFmbbVliGIYiyzIpy1Isy3oHeMswzLOyXJ2tVit9VhTFAxz5Cfge+A7IZIcZmySObQudwIE0 veanraB1w/O8l5x6D9eXy6UkSaJYLBa6BvsNuAV8uY3sCQlvX4LANM0Xw/Dgdhj2Xm02m+K6LqPR PXmeS5qmMp/PZTabyXQ6lclkosS1/QJcB+5vkthrAkoAuc4uHx//0B8MvCAIxG/5jEg0kpIkmcwX icTxBIhlHWEURXoedgW4B3wIfHuBJM9yMQ3j5PTk5N7g6MjtdrrS3e9Ku90GUUvc2hkdMYJx5Ivn NRC19UReRlRLR/sGeB34UUkMJBcJlcHg6K4SdDvS7/el1+tJp7MnQdCWRqMhDGWZLmWCCFog9rBm GBYc50rOKON4uqkSC+IQSC3moeX7N09PX/i4AwLkAoQDxeFhHziU8CCUzt6e+EFLc2QaJi4mFQHy kQLZMpME+WJF1sabdYA7Nq4jQbv9OZPs+75cgkSMYH9/X6PhJ9dpTLjruFLkBRyjACBd1BoLzzY8 T3O0IRntJvCZDXsTTnq262CzrzmgRHu4+QEIQhAxNzRWU1mTxfjOwvBIAOlIYNnWtja5bqM33mN/ sBEdx9bNPOQ1PWlqZJdAFKoMrEI6R+9gj6t7cUl1zjKnjFvsfaybr1Uqlv94ypXSKCud+aefpezs 7O3LL9s4c5U65gCrhGDDpUkqyWIuU1STweNlJRe7nAlmA+ZaVbnmiD4KFNEWC+3VqjB5YImDdMA+ YKONx2OVgxefojRL8CzmCxkOhxLhWYy+mGIvz6RKmv096X91PErP4Byazapbs3vZB45bVQqTzBzQ kjQBQSTnjx7JcDTCRSLkKNY9SbKACsttHKZdrIqHILnGCNhoDU0qG83U5mNUVTOKShRPYo3m8fAc nT/S/3mWFy2KrXKNOFbuI+Rr1FvLsB731Ho2m2pU7I1Sx8pSHTLaESIZjob6nfso2w77mSR3IMsN zh4mmLOIBAkO6fjAgESdV1MYiV4kiUZHRDjD3E0Qza580D+rjsUdAQEj4fRl8wUkqBttPeo5RlJI uB71jIASc8D+i4W8IoX8CviC5cuI+JlgpLsgcF1ng6RQyaoX1oWX1i67DTxe9w+9/EHW9VOrngCW ZfNFpmvVWOfUzZ/mfG0HwHBz4ZV1kz8nvLuL+YPnRPDJ00J8A/j9fzrnW+sjeUbjbP8amDyj86z+ tXL5PwzOC4njj4K3gavA8cazczYacLd+p/+6y8mfAgwAsRuLfp/zVLMAAAAASUVORK5CYII=""")) self.image = pygame.image.load(ball_file, 'file').convert_alpha() self.rect = self.image.fill(color, None, BLEND_ADD) self.rect.topleft = initial_position pygame.init() screen = pygame.display.set_mode([350, 350]) ball = Ball((255, 0, 0), (100, 100)) screen.blit(ball.image, ball.rect) pygame.display.update() while pygame.event.poll().type != KEYDOWN: pygame.time.delay(10) |
那一大堆的字串,相信懂Python的人會明白的,不明白的請去查閱一下base64編碼和Python對應的StringIO、base64庫。我這裡使用這種方法而不是直接讀取檔案,只是想告訴大家pygame.image.load方法不僅僅可以讀取檔案,也可以讀取檔案物件。是不是感覺一下子思路開闊了?Python那麼多方便的檔案物件,以後遊戲的資原始檔就可以不用一一獨立放出來了,使用zipfile,我們很容易就可以把資原始檔打包起來,這樣看起來我們的遊戲可就專業多了~這是後話,以後有機會再講。
而本例沒有直接畫一個圓,而是使用用了顏色混合的方法,這樣可以畫出有立體感的球體,效果如下圖。而上面一大堆的字串,其實就是那個球體的影象檔案編碼以後的東西。這個和本教程沒啥大聯絡,請自行學習光與色的知識……
但是但是,看了上面的程式碼大家一定會有意見了,這樣感覺比直接用Surface寫的程式碼還多啊!一點好處都沒有的樣子。確實會有這樣的錯覺,但是一個球看不出好處來,多個球呢?我們就可以這麼寫了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
balls = [] for color, location in [([255, 0, 0], [50, 50]), ([0, 255, 0], [100, 100]), ([0, 0, 255], [150, 150])]: boxes.append(Box(color, location)) ... for b in balls: screen.blit(b.image, b.rect) pygame.display.update() # 我們還能用一種更牛的重繪方式 # rectlist = [screen.blit(b.image, b.rect) for b in balls] # pygame.display.update(rectlist) # 這樣的好處是,pygame只會重繪有更改的部分 |
我就不給出完整程式碼和效果圖了,請大家自己試驗。
不過光這樣還不足以體現sprite的好處,sprite最大的優勢在於動畫,這裡就需要用一下update方法,舉一個例子,把第一個程式,從33行開始換成下面的程式碼:
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 |
class MoveBall(Ball): def __init__(self, color, initial_position, speed, border): super(MoveBall, self).__init__(color, initial_position) self.speed = speed self.border = border self.update_time = 0 def update(self, current_time): if self.update_time < current_time: if self.rect.left < 0 or self.rect.left > self.border[0] - self.rect.w: self.speed[0] *= -1 if self.rect.top < 0 or self.rect.top > self.border[1] - self.rect.h: self.speed[1] *= -1 self.rect.x, self.rect.y = self.rect.x + self.speed[0], self.rect.y + self.speed[1] self.update_time = current_time + 10 pygame.init() screen = pygame.display.set_mode([350, 350]) balls = [] for color, location, speed in [([255, 0, 0], [50, 50], [2,3]), ([0, 255, 0], [100, 100], [3,2]), ([0, 0, 255], [150, 150], [4,3])]: balls.append(MoveBall(color, location, speed, (350, 350))) while True: if pygame.event.poll().type == QUIT: break screen.fill((0,0,0,)) current_time = pygame.time.get_ticks() for b in balls: b.update(current_time) screen.blit(b.image, b.rect) pygame.display.update() |
我們可以看到小球歡快的運動起來,碰到邊界就會彈回來,這才是sprite類的真正用處。每一個Sprite類都會有各自的速度屬性,每次呼叫update都會各自更新自己的位置,主迴圈只需要update+blit就可以了,至於各個小球到底在一個怎樣的狀態,完全可以不在意。不過精靈的魅力還是不僅在此,上面的程式碼中我們把每個精靈加入一個列表,然後分別呼叫每個精靈的update方法,太麻煩了!使用pygame.sprite.Group類吧,建立它的一個例項balls,然後用add方法把精靈加入,然後只需要呼叫balls.update(args..)就可以了,連迴圈的不用寫。同樣的使用balls.draw()方法,你可以讓pygame只重繪有變化的部分。請嘗試使用(記住還有一個balls.clear()方法,實際寫一下就知道這個方法用來幹嘛了)。
儘管我們已經說了很多,也著實領略到了精靈的好處,但故事還沒有結束,pygame.sprite有著層與碰撞的概念。層的引入是因為Group預設是沒有次序的,所以哪個精靈覆蓋哪個精靈完全就不知道了,解決方法嘛,使用多個Group、使用OrderedUpdates,或者使用LayeredUpdates,至於具體使用方法,相信如果您需要用到的時候,已經到相當的高度了,隨便看看文件就明白了,我就不多說了;而碰撞,又是一個無底洞啊,下次有機會再講吧~