python制作galgame引擎(三)

2013年1月27日 | 分类: Python | 标签:

  正如上一篇所见,试验代码相当丑陋,效果也极度不堪……而且内部代码全部暴露,甚至没有一个接口,增添功能时也必然是伤筋动骨。于是,这一篇的目标是:

  1.对代码进行封装,并且代码中不能出现常量或常量字符。

  2.增加异常的处理—-主要是pygame的异常

   

  说起来,对异常貌似有一个讨论,出自joel?不太记得了,大意是异常不是必要的编程元素。对于这一点,我持保留态度,个人认为引入异常能使得错误更容易被定位,给用户的提示也更加友好,还可以通过引入各种异常来提供某些特定的功能。请大家也仁者见仁。说到joel,那本《More Joel on Software》还是相当不错的一本书,适合吃饭后读着玩,因为其相当易读。Joel的话,这里有一篇他的文章,真的推荐看看:我的七个建议

  貌似我总是习惯性离题?

  恩恩,封装的话,得先明确封装那些元素。对galgame来说,这倒是一个易于回答的问题,而就目前的进度而言,更是一个易于回答的问题:需要封装的元素是 背景图片,背景音乐,剧情文本。

  Ok,那开始吧。

  首先,为这个封装类起一个恰当的名字?我是随手用了NodeItem这个名字—-估计是Sakia的影响……说到Sakia,guochaoer君在定义的时候,把文本,图片,音乐都各自用一个类来封装起来,并且都继承自pygame.Sprite.sprite。这当然是一种不错的思路,很清晰。但是我觉得,事实上文本,图片和音乐都只是字符串,大可以简单地处理处理完事。而且,通常来说galgame都是静态的,继承Sprite类也没什么益处。所以我是直接使用object这个超类的。嘛,总之,各有各的想法。我这边的话,直接写代码,就是这样:

   

# -*- 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))

 

  上面就是一个封装好的类,事实上,工作得很好,当然也能看出来,其实和上一篇博文涉及的代码差不多,恩恩,就是分量增加了不少~~~我的注释很详细,有兴趣的话,请稍微仔细点阅读,谢谢了~~

  不过说起来,文本显示有一个地方我没解决。我这里是35个字符为一行显示,问题是,当字符中有英文,日文或者空格的时候,因为字符宽度比中文字符宽度窄,结果就会出现上下行宽度不均匀的情况……个人觉得不太好看。有高人能提出点简单的点子么?

             

  1. 2013年1月28日16:19

    赞一个,好文章!