正如上一篇所见,试验代码至关丑陋,效果也极度不堪……并且内部代码所有暴露,甚至没有一个接口,增添功能时也必然是伤筋动骨。因而,这一篇的目标是:python
1.对代码进行封装,而且代码中不能出现常量或常量字符。算法
2.增长异常的处理----主要是pygame的异常编程
提及来,对异常貌似有一个讨论,出自joel?不太记得了,大意是异常不是必要的编程元素。对于这一点,我持保留态度,我的认为引入异常能使得错误更容易被定位,给用户的提示也更加友好,还能够经过引入各类异常来提供某些特定的功能。请你们也仁者见仁。说到joel,那本《More Joel on Software》仍是至关不错的一本书,适合吃饭后读着玩,由于其至关易读。Joel的话,这里有一篇他的文章,真的推荐看看:个人七个建议。 函数
貌似我老是习惯性离题?字体
恩恩,封装的话,得先明确封装那些元素。对galgame来讲,这却是一个易于回答的问题,而就目前的进度而言,更是一个易于回答的问题:须要封装的元素是 背景图片,背景音乐,剧情文本。编码
Ok,那开始吧。spa
首先,为这个封装类起一个恰当的名字?我是随手用了NodeItem这个名字----估计是Sakia的影响……说到Sakia,guochaoer君在定义的时候,把文本,图片,音乐都各自用一个类来封装起来,而且都继承自pygame.Sprite.sprite。这固然是一种不错的思路,很清晰。可是我以为,事实上文本,图片和音乐都只是字符串,大能够简单地处理处理完事。并且,一般来讲galgame都是静态的,继承Sprite类也没什么益处。因此我是直接使用object这个超类的。嘛,总之,各有各的想法。我这边的话,直接写代码,就是这样:code
# -*- coding: utf-8 -*- import pygame from pygame.locals import * import os ## 这些都是须要使用到的一些常数,具体的意思,看看就明白了 SCREENWIDTH = 800 SCREENHEIGHT = 600 TEXTRECTHEIGHT = 160 LINENUMBER = 35 OFFY = 10 OFFX = 35 VSIZE = 30 ALPHA = 180 class NodeItem(object): ## 由于得最后绑定到游戏屏幕上,因此类初始化的时候, ## 就能够传入一个对screen这个surface的引用 ## 就我试验的结果的话,python传递类是实参传递,因此大可放心 ## 之因此初始化这些变量,是为了能保存值,毕竟传来的某项是有 ## None的可能性的,当该项为None是,意味着使用之前的值 ## 就是这样。这种方式免去了每次书写相同BG或BGM的麻烦 def __init__(self,surface): self.BGName = '' self.BGMName = '' self.Text = '' ## 这下面几行都和文本显示有关。TextBox是文本显示的容器 ## 就galgame效果来讲,就是文本显示时那个半透明的文本框 ## TextBox其实由于有半透明这个缘由,我是单独放到一个函数 ## 里面去实现的。 ## Font的话,其实也须要考虑不少东西,还要考虑异常。因此 ## 我也是单独用一个函数去实现。 ## 顺便一说,python函数的代价其实挺高的,可是考虑到这个 ## 程序计算不密集,资源消耗也不严重,因此放心使用就好。 ## TextBoxRect是TextBox的区域,TextBoxPos固然是位置 ## 固然,是TextBox左上角的坐标。貌似通常都是 ## fgColor是控制文字的颜色,bgColor是文本框的颜色 self.TextBox , self.TextBoxRect = self.__initTextRect() self.TextBoxPos = (0,SCREENHEIGHT-TEXTRECTHEIGHT) self.Font = self.__initFont() self.bgColor = ((0x00,0x00,0x00)) self.fgColor = ((0xFF,0xFF,0xFF)) ## 绑定传入的surface self.Surface = surface ## 对TextBox的初始化 def __initTextRect(self,colorkey = ALPHA): size = (SCREENWIDTH,TEXTRECTHEIGHT) TextRect = pygame.Surface(size) ## 文本框的底色……我通常习惯黑色为底,这样设置 ## 透明以后很好看,你喜欢的话,能够设置成其余的 TextRect.fill(self.bgColor) ## 这里设置文本框的透明度 ## 180这是我以为比较合适的值,能够本身多试试, ## 若是传入None的话,就是只有底色,恩,有人 ## 会喜欢吧? if colorkey is not None: TextRect.set_alpha(colorkey) return TextRect , TextRect.get_rect() ## 字体的初始化,考虑的问题是传入的字体并不存在,这样的 ## 话会跳出一个异常。固然,为了便于字体的管理,放在FONT ## 这样的文件夹也是理所应当的。 def __initFont(self,name = 'hksn.ttf',size = 20): fullname = os.path.join('FONT',name) try: font = pygame.font.Font(fullname,size) except pygame.error,message: print 'Cannot load font:',name raise SystemExit,message return font ###############以上是初始化工做################### ## 提供update方法,该方法做用为接受解析器,并把解析器中内容 ## 渲染到屏幕上,该方法为一个关键方法 ## 为了避免使得这个函数太臃肿,把这玩意分解成了多个子方法 ## 而且这一行为也利于扩展,当须要增长方法时,添加一个方法 ## 就能够了。 def update(self,parser): self.__updateBGM(parser.getBGM()) self.__updateBackground(parser.getBackground()) self.__updateText(parser.getName(),parser.getText()) self.Surface.blit(self.Background,(0,0)) self.Surface.blit(self.TextBox,self.TextBoxPos) ## 更新背景图片,为了为透明背景提供支持(恩?有这个可能性?) ## 顺便一提,背景图片也是须要转换大小的,嘛,显然的。 ## 这个函数借鉴了pygame官网上某个打大猩猩(……)游戏的教程 def __updateBackground(self,name,colorkey=None): fullname = os.path.join('BG',name) try: image = pygame.image.load(fullname) except pygame.error,message: print 'Cannot load image:',name raise SystemExit, message self.BGName = fullname image = image.convert_alpha() if colorkey is not None: if colorkey is -1: colorkey = image.get_at((0,0)) image.set_colorkey(colorkey, RLEACCEL) image = pygame.transform.scale(image,(SCREENWIDTH,SCREENHEIGHT)) self.Background = image ## 一样借鉴了打大猩猩游戏的代码,我的以为写得很妙 ## 特别是定义一个小类来充当哑值这一点,历来没碰见过…… def __updateBGM(self,name): class NoneSound: def play(self):pass if not pygame.mixer: return NoneSound() fullname = os.path.join('BGM',name) if self.BGMName == fullname: return try: pygame.mixer.music.load(fullname) except pygame.error, message: print 'Cannot load sound:',fullname raise SystemExit,message self.BGMName = fullname self.play() ##播放函数,没啥好说的 def play(self): pygame.mixer.music.play(-1,0.0) ## 这个函数其实挺麻烦的…… def __updateText(self,name,text): ## 刷新文本框。 self.TextBox.fill(self.bgColor) ## 这里是控制字符编码的部分,兼实现了跨平台 ## 这个程序开始是在Linux下开发的,因此我先 ## 说一下。中文的显示,必须把字符编码转成 ## utf8,不然全是乱码。这部分我之后还会详述 ## 暂且就这样 if WINDOWS: name = name.decode('gb18030') text = text.decode('gb18030') else: name = name.decode('utf8') text = text.decode('utf8') ## 把长字符串分隔开,每35个字为一个列表,再分别render ## 之因此这样写,是由于render函数只支持单行……因此得用一个 ## 循环来处理。分割成列表这个列表推导式,出自felinx,我以为 ## 很好玩。注意的是,字符必须是统一的编码,否则就悲剧了 textLines = [text[i:i+LINENUMBER] for i in range(len(text)) if i % LINENUMBER == 0] ## 这个是名字,指名当前文本的说话人,都见过吧? ## 这个值可能为None,也可能有,若是有的话,把它放第一行显示 if name != '': name += ' :' textLines.insert(0,name) vSize = VSIZE ## render显示文字的话,把坐标必定要算对,否则很差看 ## 我这里是左对齐的算法,中对齐的话是注释掉的那个 ## Sakia用的是中对齐,我的以为很差看…… ## 这里用到的常量全是试验出来的…… for lineNum in range(len(textLines)): currentLine = textLines[lineNum] fontSurface = self.Font.render(currentLine,True,self.fgColor,self.bgColor) ##xPos = (SCREENWIDTH- fontSurface.get_width())/2 xPos = OFFX yPos = OFFY + lineNum * vSize self.TextBox.blit(fontSurface,(xPos,yPos))
上面就是一个封装好的类,事实上,工做得很好,固然也能看出来,其实和上一篇博文涉及的代码差很少,恩恩,就是份量增长了很多~~~个人注释很详细,有兴趣的话,请稍微仔细点阅读,谢谢了~~orm
不过提及来,文本显示有一个地方我没解决。我这里是35个字符为一行显示,问题是,当字符中有英文,日文或者空格的时候,由于字符宽度比中文字符宽度窄,结果就会出现上下行宽度不均匀的状况……我的以为不太好看。有高人能提出点简单的点子么?继承