一晃自己用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怎么样。简单点,就在屏幕显示个‘hello pygame’。
import pygame as pg
from label import Label
class Game(object):
def __init__(self,width,height):
self.screen_width = width
self.screen_height = height
self.screen = pg.display.set_mode((self.screen_width,self.screen_height))
self.done = False
self.clock = pg.time.Clock()
self.fps = 60.0
self.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 = True
if event.type == pg.KEYDOWN:
if event.key == pg.K_ESCAPE:
self.done = True
def draw(self):
self.labels.draw(self.screen)
def update(self,dt):
pass
def 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 pg
LOADED_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 color
class _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_size
if (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_attr
self.set_text(text)
def set_text(self, text):
self.text = text
self.update_text()
def update_text(self):
if self.alpha != 255:
self.fill_color &#61; pg.Color(*[x &#43; 1 if x <255 else x - 1 for x in self.text_color[:3]])
if self.fill_color:
render_args &#61; (self.text, True, self.text_color, self.fill_color)
else:
render_args &#61; (self.text, True, self.text_color)
self.image &#61; self.font.render(*render_args)
if self.alpha !&#61; 255:
self.image.set_colorkey(self.fill_color)
self.image.set_alpha(self.alpha)
self.rect &#61; self.image.get_rect(**self.rect_attr)
def draw(self, surface):
surface.blit(self.image, self.rect)
感受一下&#xff0c;何如&#xff1f;不仔细看就看不明白了吧&#xff0c;如果对pygame不了解&#xff0c;现在已经彻底懵圈了。没关系&#xff0c;这不是重点&#xff0c;我只是举这个例子来说明&#xff0c;好多人诟病pygame不是那么pythonic&#xff0c;这可能就是其中一个原因吧。
其实很多python爱好者对写游戏并不感冒&#xff0c;更多的时候只是想找个工具写写UI&#xff0c;如果抛开什么像素碰撞检测之类纯游戏里的东东&#xff0c;也没太多精力和兴趣自己造轱辘玩&#xff0c;大可以使用pyglet。
说个题外话&#xff0c;因为带娃的缘故&#xff0c;pyglet实在太像piglet(小猪皮杰_百度百科)了&#xff0c;看看pyglet的logo&#xff0c;是不是就是一个猪屁股上安了一个猪尾巴 ……(好吧&#xff0c;原谅我太有想象力了)
同样&#xff0c;还是先上代码再分析吧&#xff0c;同样让屏幕显示个‘hello pyglet’吧。
import pyglet
class Game(pyglet.window.Window):
def __init__(self,*args,**kwargs):
super(Game,self).__init__(*args,**kwargs)
self.label &#61; pyglet.text.Label(&#39;hello pyglet&#39;,
font_size&#61;100,
x&#61;self.width//2,
y&#61;self.height//2,
anchor_x&#61;&#39;center&#39;,
anchor_y&#61;&#39;center&#39;)
def on_draw(self):
self.clear()
self.label.draw()
if __name__&#61;&#61;&#39;__main__&#39;:
game &#61; Game(width &#61; 800,height &#61; 600)
pyglet.app.run()
ok&#xff0c;回过头来看&#xff0c;也就不到20行吧。感觉何如&#xff0c;比pygame简洁很多&#xff0c;自带label模块&#xff0c;自带app运行模式。这也是许多人用pyglet的原因之一吧&#xff0c;高效啊&#xff0c;国人有个很牛逼的feisuzhu/thbattle project的前端就是用的pyglet。
# 我自己也是个弹幕类游戏爱好者&#xff0c;但东方系列实在太变态了&#xff0c;说实话我并不怎么爱玩东方系列&#xff0c;但Zun神绝对是独立游戏者的楷模了&#xff0c;全才啊&#xff0c;而且同人文化这种擦边版权的问题&#xff0c;对Zun神来说似乎起到了帮助宣传的作用 &#xff1a;)。
和pygame部分模块用C语言编写相比&#xff0c;pyglet是纯Python。一大特点就是免安装&#xff0c;拷贝到文件夹下到处运行&#xff0c;如果搭配embed版本的Python&#xff0c;那简直就是插上优盘到处开车啊。据说pygame在Window下其实也可以拷走免安装运行&#xff0c;但需要相同的环境。不要小看这个免安装运行&#xff0c;它可以让你打包的时候不出错或者少出错。记得一年前steam上有款游戏Switchcars&#xff0c;就是pygame写的(严格的说是 pygame_sdl2&#xff0c;可以理解为pygame 2.0版本&#xff0c;小白鼠可以自行搜索体验&#xff0c;支持Android哦)&#xff0c;作者透露说开始是用sdl1&#xff0c;后来打包steam api的时候遇到了麻烦&#xff0c;改成了sdl2&#xff0c;不过这些都是C语言大神们干的事&#xff0c;咱们也就听听怎么回事就好。作者从写这个游戏到最终上线花了3年多&#xff0c;好吧&#xff0c;国外就是闲人多~~~(其实我是想说人家执着&#xff0c;我也想窝家里写三年游戏&#xff0c;但是我老婆孩子咋弄&#xff1f;)
言归正传&#xff0c;pyglet支持多个显示器&#xff0c;也支持多个窗口&#xff0c;pygame只能有一个显示器一个窗口&#xff0c;貌似多线程多进程也不行。如果你有多台显示器&#xff0c;而且要实现多个窗口&#xff0c;那就必须pyglet了。此外pyglet直接关联OpenGL&#xff0c;虽然这玩意有点out of date赶脚&#xff0c;但现在也号召延迟退休了好吧。pyglet的作者也并非拿来主义&#xff0c;还是对opengl的画图操作进行了重组&#xff0c;更容易被新手掌握&#xff0c;但怎么说呢&#xff0c;一方面opengl是可以调用gpu的&#xff0c;理论上比单纯cpu更快&#xff0c;这是pygame达不到的。另一方面python本身实在不适合做3D&#xff0c;如果只用2D&#xff0c;其实还是pygame的Surface操作起来直观。当然一些渲染之类&#xff0c;还是上opengl更带劲&#xff0c;比如在pyglet基础上还有个cocos2d&#xff0c;里面有很多酷炫效果。接着说opengl的事&#xff0c;如果用这玩意&#xff0c;要用ctypes&#xff0c;这个就挺不伦不类的&#xff0c;比如说创建一个array&#xff0c;代码里就是‘a &#61; (GLfloat * 2)()’&#xff0c;这种一点毛病没有的写法&#xff0c;估计让处女座强迫症们死的心都有了。因为pyglet速度慢&#xff0c;主要是写3D游戏而言&#xff0c;有大神要重新造轮子&#xff0c;名字都起好了Cyglet&#xff0c;但我觉得还真是一条路子&#xff0c;但从C往python转很easy&#xff0c;从python回到C估计如果不是图速度&#xff0c;没有人愿意读那些鼠标谷轮半天也不见底的代码。
再说说kivy吧&#xff0c;跨平台&#xff0c;实际上就是针对Android和ios的。如果你写的好&#xff0c;可以上线卖钱哦&#xff0c;比如Google商店里的mancala就是kivy写的哦。其实我本人也是新手&#xff0c;没有太多的经验&#xff0c;还是直接上代码来个‘hello kivy’吧
from kivy.base import runTouchApp
from kivy.lang import Builder
runTouchApp(Builder.load_string(&#39;&#39;&#39;Label:text:&#39;hello kivy&#39;font_size:100&#39;&#39;&#39;))
∑q|&#xff9f;Д&#xff9f;|p&#xff0c;是的&#xff0c;你没看错&#xff0c;就是这么短小精悍&#xff0c;kivy本身也是个自造词&#xff0c;而不是py啥啥啥&#xff0c;这也有点老子就是要来个新语言kvlang的野心。为了了解kivy的来源&#xff0c;我还专门在kivy-dev里发了封邮件问了下&#xff0c;得到的回复是
和我猜的答案也差不多&#xff0c;就是来源于几维鸟&#xff0c;也可以看看那个动画&#xff0c;就当是干了这碗鸡汤吧。
其实用kivy和用pygame&#xff0c;pyglet写游戏在逻辑上没有太多的颠覆性的东西&#xff0c;相反有些东西变得更加容易&#xff0c;比如说自带Animation类&#xff0c;就是不同的easing functions&#xff0c;当然自己去写一点问题也没有&#xff0c;但现成的轮子干嘛不用呢。还有Screenmanager&#xff0c;这就是游戏里活生生的statemachine啊&#xff0c;而且还自带切换效果&#xff1a;)
当然有好也有坏&#xff0c;目前我发现用kivy写游戏一个很大的问题&#xff0c;木有一个合适的Layout去显示分辨率固定的图片。大部分游戏还是基于像素的&#xff0c;如果所有的图片像素是基于800x600的&#xff0c;那么在1024x960里就乐呵了&#xff0c;要么拉伸扭曲&#xff0c;要么有黑边(好多war3运行的时候就是有黑边)。目前我的解决方法是按大比例方向拉伸&#xff0c;如果长度方向拉伸1.5倍&#xff0c;高度方法拉伸2倍&#xff0c;那就都拉伸2倍&#xff0c;只是部分图片跑到屏幕外边而已。其实kivy的wiki上有个方法&#xff0c;带黑边的viewport&#xff0c;不是很完美啊&#xff0c;还有前面提到的mancala&#xff0c;那哥哥用的是在Floatlayout上改的&#xff0c;一方面别人卖钱的东西我不愿意拿来用&#xff0c;因为License也不是MIT。另一方面我发现它也并不是很完美啊。
总之&#xff0c;用kivy开发游戏还有很多路要走&#xff0c;当然&#xff0c;对Python玩家来说&#xff0c;这不正是我们的菜么&#xff1f;
一晃写了这么多了&#xff0c;比起写文章&#xff0c;我还是愿意写代码啊&#xff1a;)
ps&#xff0c;再压轴介绍两个模块吧。
如果游戏里有物理因素必用&#xff0c;比如愤怒的小鸟之类&#xff0c;具体就不解释了&#xff0c;谁用谁知道。
如果游戏里有tiled map必用&#xff0c;比如开源的Tuxemon&#xff0c;同样不解释了&#xff0c;谁用谁知道。
顺便说下&#xff0c;tuxemon的代码还是很棒的&#xff0c;如果是新手&#xff0c;模仿里面的style不失为一种很好的提高途径。