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 pg from label import Labelclass Game(object):def __init__(self,width,height):self.screen_width = widthself.screen_height = heightself.screen = pg.display.set_mode((self.screen_width,self.screen_height))self.done = Falseself.clock = pg.time.Clock()self.fps = 60.0self.labels = pg.sprite.Group()self.label = Label('hello pygame',{'center':(self.screen_width/2,self.screen_height/2)},self.labels,font_size = 100)def event_loop(self):for event in pg.event.get():if event.type == pg.QUIT:self.done = Trueif event.type == pg.KEYDOWN:if event.key == pg.K_ESCAPE:self.done = Truedef draw(self):self.labels.draw(self.screen)def update(self,dt):passdef run(self):dt = self.clock.tick(self.fps)while not self.done:self.event_loop()self.update(dt)self.draw()pg.display.update()if __name__ == '__main__':pg.init()game = Game(800,600)game.run()pg.quit()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 copy import pygame as pgLOADED_FONTS = {} LABEL_DEFAULTS = {"font_path": None,"font_size": 12,"text_color": "white","fill_color": None,"alpha": 255}def _parse_color(color):if color is not None:try:return pg.Color(str(color))except ValueError as e:return pg.Color(*color)return colorclass _KwargMixin(object):def process_kwargs(self, name, defaults, kwargs):settings = copy.deepcopy(defaults)for kwarg in kwargs:if kwarg in settings:if isinstance(kwargs[kwarg], dict):settings[kwarg].update(kwargs[kwarg])else:settings[kwarg] = kwargs[kwarg]else:message = "{} has no keyword: {}"raise AttributeError(message.format(name, kwarg))for setting in settings:setattr(self, setting, settings[setting])class Label(pg.sprite.Sprite,_KwargMixin):def __init__(self, text, rect_attr, *groups, **kwargs):super(Label, self).__init__(*groups)self.process_kwargs("Label", LABEL_DEFAULTS, kwargs)path, size = self.font_path, self.font_sizeif (path, size) not in LOADED_FONTS:LOADED_FONTS[(path, size)] = pg.font.Font(path, size)self.font = LOADED_FONTS[(path, size)]self.fill_color = _parse_color(self.fill_color)self.text_color = _parse_color(self.text_color)self.rect_attr = rect_attrself.set_text(text)def set_text(self, text):self.text = textself.update_text()def update_text(self):if self.alpha != 255:self.fill_color = pg.Color(*[x + 1 if x < 255 else x - 1 for x in self.text_color[:3]])if self.fill_color:render_args = (self.text, True, self.text_color, self.fill_color)else:render_args = (self.text, True, self.text_color)self.image = self.font.render(*render_args)if self.alpha != 255:self.image.set_colorkey(self.fill_color)self.image.set_alpha(self.alpha)self.rect = self.image.get_rect(**self.rect_attr)def draw(self, surface):surface.blit(self.image, self.rect)感受一下,何如?不仔細看就看不明白了吧,如果對pygame不了解,現在已經徹底懵圈了。沒關系,這不是重點,我只是舉這個例子來說明,好多人詬病pygame不是那么pythonic,這可能就是其中一個原因吧。
其實很多python愛好者對寫游戲并不感冒,更多的時候只是想找個工具寫寫UI,如果拋開什么像素碰撞檢測之類純游戲里的東東,也沒太多精力和興趣自己造轱轆玩,大可以使用pyglet。
說個題外話,因為帶娃的緣故,pyglet實在太像piglet(小豬皮杰_百度百科)了,看看pyglet的logo,是不是就是一個豬屁股上安了一個豬尾巴 ……(好吧,原諒我太有想象力了)
同樣,還是先上代碼再分析吧,同樣讓屏幕顯示個‘hello pyglet’吧。
import pygletclass Game(pyglet.window.Window):def __init__(self,*args,**kwargs):super(Game,self).__init__(*args,**kwargs)self.label = pyglet.text.Label('hello pyglet',font_size=100,x=self.width//2,y=self.height//2,anchor_x='center',anchor_y='center')def on_draw(self):self.clear()self.label.draw()if __name__=='__main__':game = Game(width = 800,height = 600)pyglet.app.run()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 runTouchApp from kivy.lang import BuilderrunTouchApp(Builder.load_string(''' Label:text:'hello kivy'font_size:100 '''))∑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?https://github.com/bitcraft/PyTMX
如果游戲里有tiled map必用,比如開源的Tuxemon,同樣不解釋了,誰用誰知道。
順便說下,tuxemon的代碼還是很棒的,如果是新手,模仿里面的style不失為一種很好的提高途徑。
來源:https://zhuanlan.zhihu.com/p/26661987
總結
以上是生活随笔為你收集整理的Python里那些可爱的游戏模块们的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 恒昌理财投资安全吗
- 下一篇: python游戏开发的五个案例分享