Python里那些可愛的遊戲模塊們

一晃自己用python寫小遊戲也有段時間了,自娛自樂之餘,也對這些模塊如數家珍下,僅做一家之言供後來者參考吧。

首先是範疇問題,python適合寫什麼遊戲呢?

簡言之,python適合寫一些2D的小遊戲。比如貪吃蛇啊,超級瑪麗,FC或者90年代街機之類的遊戲,寫這些遊戲的難度,對Python愛好者來說都是no problem的。當然大神級別的嘗嘗3D也是可以滴,比如驚艷的fogleman/Minecraft。其實大神就是一層窗戶紙加上多年的修行,如果對Ctypes,OpenGL瞭然於胸,那任督二脈已然打通了,隨便擺個架勢就能被人當成老司機。不過我實在不建議這樣炫技,畢竟python玩3D還是 toooooooooooooo slow了點。

然後,用什麼模塊來寫遊戲呢?

好吧,這可能只有我這種,天秤座且帶著選擇困難症的人,才會有這種疑問,用嘛玩意來寫呢?單機遊戲,pygame or pyglet,手機遊戲,kivy(雖然不完美,但也沒有別的選擇了)。

開始進入正題,首先是pygame。

在我眼裡,pygame是Python寫遊戲的第一選擇,為什麼這麼說涅,LMGTFY?在pygame官網裡也有自嘲自誇的關於pygame的廢話。

我先來段代碼吧,一起來看看pygame怎麼樣。簡單點,就在屏幕顯示個『hello pygame』。

import pygame as pgnfrom label import Labelnnnclass Game(object):nn def __init__(self,width,height):n self.screen_width = widthn self.screen_height = heightn self.screen = pg.display.set_mode((self.screen_width,self.screen_height))n self.done = Falsen self.clock = pg.time.Clock()n self.fps = 60.0n self.labels = pg.sprite.Group()n self.label = Label(hello pygame,n {center:(self.screen_width/2,self.screen_height/2)},n self.labels,n font_size = 100)nn def event_loop(self):n for event in pg.event.get():n if event.type == pg.QUIT:n self.done = Truen if event.type == pg.KEYDOWN:n if event.key == pg.K_ESCAPE:n self.done = Truenn def draw(self):n self.labels.draw(self.screen)nnn def update(self,dt):n passnn def run(self):n dt = self.clock.tick(self.fps)n while not self.done:n self.event_loop()n self.update(dt)n self.draw()n pg.display.update()nnnif __name__ == __main__:n pg.init()n game = Game(800,600)n game.run()n pg.quit()n

if no time to check the code:

就跳過以下段落

else:

我簡單啰嗦一下。看def run這裡。在遊戲運行時,循環里包括三塊,event_loop,update和draw,這也是遊戲組成的幾個部分。

首先event_loop是獲取玩家輸入的,比如通過WSAD控制角色移動,代碼里實現的是玩家點叉退出和按Esc鍵退出。

其次update處理遊戲邏輯,比如角色和boss距離在100px時boss發起攻擊。代碼里pass,因為只是一個label顯示,如果讓label變成blinker,一閃一閃的。就可以在update裡面調用label.update方法,然後就ok了。

最後是draw和display.update。就是顯示在屏幕上咯,經過邏輯計算後,角色和boss的位置在屏幕上顯示。代碼里就把label在屏幕上顯示出來了。

經過這個過程後,玩家再次輸入,控制角色開始新的循環。如果沒看懂再回過頭再看一遍。

# 其實我感覺代碼已經很清楚了,直接看代碼應該都能理解每句話什麼意思,如果不行,問問身邊Python老司機,如果對裡面fps,clock之類不懂的話,可以看看相關遊戲編程的書:)

怎麼樣,寫遊戲這個事整體有了個宏觀把握了吧,即便是一個連面向對象都不懂的python新手,是不是也可以架構自己的遊戲了?下圖是我在某遊戲編程書里扒拉出來的。

不難看出,用pygame寫遊戲第一個特點,結構清晰,如果你是新手,有助於你了解遊戲的組成結構。

那麼第二個特點,自由度高,如果你是老手,可以上下其手,為所欲為。舉一個平常的例子,簡單的滑鼠操作,單擊選中,雙擊運行,但硬體層面上是不存在雙擊事件的,如果需要,在pygame里當滑鼠單擊的時候,記錄下時間,再次單擊的時候,再一次記錄時間,如果間隔小於某一閾值,返回True。還有屏幕上滑鼠箭頭的這種顯示,也是很底層硬體的東西,但是pygame就可以修改它,而且還不難,強大吧。不要以為這些遊戲里都碰不上,以遊戲怒首領蜂為例,Button A, tap是機槍,hold on是激光,同樣是按鍵,短按是機槍,長按是激光,這種控制在pygame里只需記錄下Button A 的keydown時間就可以實現,所以還是很有用滴。

但是與此同時,帶來了一個問題,自由度大了,代碼量就上來了,如上面的代碼,需要自己去寫一個Label類,這裡我也把代碼貼上來吧。

import copynimport pygame as pgnnLOADED_FONTS = {}nLABEL_DEFAULTS = {n "font_path": None,n "font_size": 12,n "text_color": "white",n "fill_color": None,n "alpha": 255}nndef _parse_color(color):n if color is not None:n try:n return pg.Color(str(color))n except ValueError as e:n return pg.Color(*color)n return colornnclass _KwargMixin(object):nn def process_kwargs(self, name, defaults, kwargs):n settings = copy.deepcopy(defaults)n for kwarg in kwargs:n if kwarg in settings:n if isinstance(kwargs[kwarg], dict):n settings[kwarg].update(kwargs[kwarg])n else:n settings[kwarg] = kwargs[kwarg]n else:n message = "{} has no keyword: {}"n raise AttributeError(message.format(name, kwarg))n for setting in settings:n setattr(self, setting, settings[setting])nnclass Label(pg.sprite.Sprite,_KwargMixin):nn def __init__(self, text, rect_attr, *groups, **kwargs):n super(Label, self).__init__(*groups)n self.process_kwargs("Label", LABEL_DEFAULTS, kwargs)n path, size = self.font_path, self.font_sizen if (path, size) not in LOADED_FONTS:n LOADED_FONTS[(path, size)] = pg.font.Font(path, size)n self.font = LOADED_FONTS[(path, size)]n self.fill_color = _parse_color(self.fill_color)n self.text_color = _parse_color(self.text_color)n self.rect_attr = rect_attrn self.set_text(text)nn def set_text(self, text):n self.text = textn self.update_text()nn def update_text(self):n if self.alpha != 255:n self.fill_color = pg.Color(*[x + 1 if x < 255 else x - 1 for x in self.text_color[:3]])n if self.fill_color:n render_args = (self.text, True, self.text_color, self.fill_color)nn else:n render_args = (self.text, True, self.text_color)n self.image = self.font.render(*render_args)n if self.alpha != 255:n self.image.set_colorkey(self.fill_color)n self.image.set_alpha(self.alpha)n self.rect = self.image.get_rect(**self.rect_attr)nn def draw(self, surface):n surface.blit(self.image, self.rect)n

感受一下,何如?不仔細看就看不明白了吧,如果對pygame不了解,現在已經徹底懵圈了。沒關係,這不是重點,我只是舉這個例子來說明,好多人詬病pygame不是那麼pythonic,這可能就是其中一個原因吧。

其實很多python愛好者對寫遊戲並不感冒,更多的時候只是想找個工具寫寫UI,如果拋開什麼像素碰撞檢測之類純遊戲里的東東,也沒太多精力和興趣自己造軲轆玩,大可以使用pyglet

說個題外話,因為帶娃的緣故,pyglet實在太像piglet(小豬皮傑_百度百科)了,看看pyglet的logo,是不是就是一個豬屁股上安了一個豬尾巴 ……(好吧,原諒我太有想像力了)

同樣,還是先上代碼再分析吧,同樣讓屏幕顯示個『hello pyglet』吧。

import pygletnnclass Game(pyglet.window.Window):n def __init__(self,*args,**kwargs):n super(Game,self).__init__(*args,**kwargs)n self.label = pyglet.text.Label(hello pyglet,n font_size=100,n x=self.width//2,n y=self.height//2,n anchor_x=center,n anchor_y=center)nn def on_draw(self):n self.clear()n self.label.draw()nnif __name__==__main__:n game = Game(width = 800,height = 600)n pyglet.app.run()n

ok,回過頭來看,也就不到20行吧。感覺何如,比pygame簡潔很多,自帶label模塊,自帶app運行模式。這也是許多人用pyglet的原因之一吧,高效啊,國人有個很牛逼的feisuzhu/thbattle project的前端就是用的pyglet。

# 我自己也是個彈幕類遊戲愛好者,但東方系列實在太變態了,說實話我並不怎麼愛玩東方系列,但Zun神絕對是獨立遊戲者的楷模了,全才啊,而且同人文化這種擦邊版權的問題,對Zun神來說似乎起到了幫助宣傳的作用 :)。

和pygame部分模塊用C語言編寫相比,pyglet是純Python。一大特點就是免安裝,拷貝到文件夾下到處運行,如果搭配embed版本的Python,那簡直就是插上優盤到處開車啊。據說pygame在Window下其實也可以拷走免安裝運行,但需要相同的環境。不要小看這個免安裝運行,它可以讓你打包的時候不出錯或者少出錯。記得一年前steam上有款遊戲Switchcars,就是pygame寫的(嚴格的說是 pygame_sdl2,可以理解為pygame 2.0版本,小白鼠可以自行搜索體驗,支持Android哦),作者透露說開始是用sdl1,後來打包steam api的時候遇到了麻煩,改成了sdl2,不過這些都是C語言大神們乾的事,咱們也就聽聽怎麼回事就好。作者從寫這個遊戲到最終上線花了3年多,好吧,國外就是閑人多~~~(其實我是想說人家執著,我也想窩家裡寫三年遊戲,但是我老婆孩子咋弄?)

言歸正傳,pyglet支持多個顯示器,也支持多個窗口,pygame只能有一個顯示器一個窗口,貌似多線程多進程也不行。如果你有多台顯示器,而且要實現多個窗口,那就必須pyglet了。此外pyglet直接關聯OpenGL,雖然這玩意有點out of date趕腳,但現在也號召延遲退休了好吧。pyglet的作者也並非拿來主義,還是對opengl的畫圖操作進行了重組,更容易被新手掌握,但怎麼說呢,一方面opengl是可以調用gpu的,理論上比單純cpu更快,這是pygame達不到的。另一方面python本身實在不適合做3D,如果只用2D,其實還是pygame的Surface操作起來直觀。當然一些渲染之類,還是上opengl更帶勁,比如在pyglet基礎上還有個cocos2d,裡面有很多酷炫效果。接著說opengl的事,如果用這玩意,要用ctypes,這個就挺不倫不類的,比如說創建一個array,代碼里就是『a = (GLfloat * 2)()』,這種一點毛病沒有的寫法,估計讓處女座強迫症們死的心都有了。因為pyglet速度慢,主要是寫3D遊戲而言,有大神要重新造輪子,名字都起好了Cyglet,但我覺得還真是一條路子,但從C往python轉很easy,從python回到C估計如果不是圖速度,沒有人願意讀那些滑鼠谷輪半天也不見底的代碼。

再說說kivy吧,跨平台,實際上就是針對Android和ios的。如果你寫的好,可以上線賣錢哦,比如Google商店裡的mancala就是kivy寫的哦。其實我本人也是新手,沒有太多的經驗,還是直接上代碼來個『hello kivy』吧

from kivy.base import runTouchAppnfrom kivy.lang import BuildernnnrunTouchApp(Builder.load_string(nLabel:n text:hello kivyn font_size:100n)) n

∑q|?Д?|p,是的,你沒看錯,就是這麼短小精悍,kivy本身也是個自造詞,而不是py啥啥啥,這也有點老子就是要來個新語言kvlang的野心。為了了解kivy的來源,我還專門在kivy-dev里發了封郵件問了下,得到的回復是

和我猜的答案也差不多,就是來源於幾維鳥,也可以看看那個動畫,就當是幹了這碗雞湯吧。

其實用kivy和用pygame,pyglet寫遊戲在邏輯上沒有太多的顛覆性的東西,相反有些東西變得更加容易,比如說自帶Animation類,就是不同的easing functions,當然自己去寫一點問題也沒有,但現成的輪子幹嘛不用呢。還有Screenmanager,這就是遊戲里活生生的statemachine啊,而且還自帶切換效果:)

當然有好也有壞,目前我發現用kivy寫遊戲一個很大的問題,木有一個合適的Layout去顯示解析度固定的圖片。大部分遊戲還是基於像素的,如果所有的圖片像素是基於800x600的,那麼在1024x960里就樂呵了,要麼拉伸扭曲,要麼有黑邊(好多war3運行的時候就是有黑邊)。目前我的解決方法是按大比例方向拉伸,如果長度方向拉伸1.5倍,高度方法拉伸2倍,那就都拉伸2倍,只是部分圖片跑到屏幕外邊而已。其實kivy的wiki上有個方法,帶黑邊的viewport,不是很完美啊,還有前面提到的mancala,那哥哥用的是在Floatlayout上改的,一方面別人賣錢的東西我不願意拿來用,因為License也不是MIT。另一方面我發現它也並不是很完美啊。

總之,用kivy開發遊戲還有很多路要走,當然,對Python玩家來說,這不正是我們的菜么?

一晃寫了這麼多了,比起寫文章,我還是願意寫代碼啊:)

ps,再壓軸介紹兩個模塊吧。

pymunk Pymunk - pymunk 5.2.0 documentation

如果遊戲里有物理因素必用,比如憤怒的小鳥之類,具體就不解釋了,誰用誰知道。

pytmx github.com/bitcraft/PyT

如果遊戲里有tiled map必用,比如開源的Tuxemon,同樣不解釋了,誰用誰知道。

順便說下,tuxemon的代碼還是很棒的,如果是新手,模仿裡面的style不失為一種很好的提高途徑。

推薦閱讀:

TAG:Python | pygame | 游戏 |