python制作galgame引擎(五)

*爱你&永不变心* 提交于 2019-11-28 21:21:51

  姗姗来迟的续篇不是么(笑)。嗯哼,不要在意上一篇充满忧郁的离别气氛~~~

  这一篇是新增语法的设计和解释,分别是立绘的绘制(渲染?或许用render更靠谱?)语法和跳转指定帧的语法。前者很麻烦,后者很简单。

  所谓人物立绘,galgame游戏中,就是人物的肖像,一般来说就是对话对方的肖像。非gal玩家估计比较难想象……有兴趣的话可以查一下相关定义。顺便,虽然不是gal,但是东方的zun绘,依然是人物立绘……

  值得注意的是,人物立绘出现的频度很频繁,这就要求语法定义必须要很简单。但是问题是,立绘的渲染本身需要的参数就很多,这算是一个不大不小的矛盾。

  如果对立绘参数要求很多这一点有异议,请仔细考虑一下。人物立绘的渲染,到底需要哪些参数:本地人物图片的右下角(切割图片位置),缩放大小,屏幕位置。对吧,至少需要三个参数。为了简便起见,后面也只考虑这三个参数。

  考虑需求的频度较高,使用[xxxxxx]语法就过于麻烦了,这里我使用{}来标明立绘的语法域。

  因为立绘有几个特征:同样的图片可以在画面移动(用来表现走路啊什么的);同样大小的图片,虽然位置不动,但是图片本身改变了(人物表情变化);图片和图片位置(中心位置)不改变,大小变化(人物由远及近)。处于便捷性考虑,每次都填写同样的东西并不爽,于是允许缺失值,缺失值会直接使用之前一帧的值,这样的思路要好一点。不太好理解?请看一下我定义的语法吧:

 

{'sanae.png';1.(600,600);2.(200,200);3.(200,200)}

{'sanae.png';1.(400,600);3.(100,200)}

 

  第一行是初始定义,格式 {portrait_name;1.pos;2.pos;3.pos}

  语法域为{}之内,分号分割参数。参数是四个,分别为:图片的名字;原图的切割位置(右下角坐标);图片在屏幕上的大小;图片在屏幕上的位置。估计你也注意到了,有诡异的1,2,3的标号----抱歉,这个不能少,这个是用来确定后面的值是属于哪个参数。1就是切割位置,2是大小,3是屏幕位置,这样。

  初始化的时候一个都不能少,否则会抛出异常。

  第二行是有缺失项的情况,也就是2那项不存在,这种情况下,程序会沿用前一次的(200,200)。

   

  因为单帧中,立绘人数不一定是一,所以也支持多行解析,比如这样:

 

{'sanae.png';1.(600,600);2.(200,200);3.(200,200)

'sanae1.png';1.(600,600);2.(300,300);3.(0,0)}

 

  请严格遵照格式,一点都不能错的……恩,因为我的解析器写得很傻。

  如果图片名字后面不跟任何参数的话,这幅图片会被移除,比如这样:

   

{'sanae1.png';

'sanae.png';}

 

  使用上面的语法,sanae1.png 和sanae.png 就会被移除。嗯哼,即使没有sanae1.png也没关系,没什么问题的。

  如果多幅图片都希望无脑移除的话,{}语法可以拯救你。

   

{}

 

  会移除所有的人物立绘。

   

  上面就是人物立绘的语法,很麻烦。事实上,程序也很麻烦。我使用的数据结构是一个字典的字典,顿时觉得坑爹了?基本类似于这样:

   

 {'backgound.jpg': {'clip_pos': (100, 100), 'screen_pos': (212, 54), 'name': 'aa.jpg', 'size': (121, 44)}, 'gg.jpg': {'clip_pos': (100, 100), 'screen_pos': (212, 54), 'name': 'aa.jpg', 'size': (121, 44)}, 'aa.jpg': {'clip_pos': (100, 100), 'screen_pos': (212, 54), 'name': 'aa.jpg', 'size': (121, 44)}}

 

  看着很绕吧?其实我也看着绕,但是我这里没什么更好的思路,如果有更好地思路,请务必at我。这个字典正如上面所说是一个嵌套字典,外层的key是每幅图片的名称,用来区分不同图片。value是解析出来的值。内层的话,字典是这样{clip_pos': (100, 100), 'screen_pos': (212, 54), 'name': 'aa.jpg', 'size': (121, 44)},key分别是几个参数的名称,value是对应的值。

   

  嘛,就这样,上面就是定义的语法和使用的数据结构,下面是代码,没什么可说的:

   

Parser部分:

## 初始化正则式
    self.RPPortrait = self.__InitReParserPortrait()
    ##正则式匹配出立绘的语法域
    def __InitReParserPortrait(self):
        pat = r'\{(.*?)\}'
        return re.compile(pat,re.DOTALL)
        
    ## parser方法中增加下面内容
    if self.RPPortrait.search(target):
        target = self.RPPortrait.search(target).group(1)
        if target:
            self.Portrait = self.__parserPortrait(target)
        else:
            self.Portrait = {}
        
    ## 用来解析的函数
    def __parserPortrait(self,target):
        portraits = {}
        target = target.split('\n')
        for element in target:
            portrait = {}
            element = element.split(';')
            ## The way of using eval is dangerous
            ## pray?
            portrait['name'] = eval(element[0])
            ## add the flag which means
            ## the image will remove
            if element[1] == '':
                portrait['flag'] = True
            else:
                for i in element[1:]:
                    i = i.split('.')
                    assert len(i) == 2
                    if int(i[0]) == 1:
                        portrait['clip_pos'] = eval(i[1])
                    elif int(i[0]) == 2:
                        portrait['size'] = eval(i[1])
                    elif int(i[0]) == 3:
                        portrait['screen_pos'] = eval(i[1])
                    else:
                        print 'Wrong Gammer.Please check %d' % self.NodeIndex  
                        raise SystemExit
            portraits[portrait['name']] = portrait
        return portraits

 

NodeItem部分

## 说明,我这里改写了载入图片的方法,以求得复用 
    def __updateImage(self,name,colorkey=None,clip_pos = None,size = None):
        if clip_pos and size :
            fullname = os.path.join('PORTRAIT',name)
        elif not clip_pos and not size:
            fullname = os.path.join('BG',name)
            self.BGName = name
        else:
            raise SystemExit,message
        try:
            image = pygame.image.load(fullname)
        except pygame.error,message:
            print 'Cannot load image:',name
            raise SystemExit, message
        image = image.convert()
        if clip_pos:
            image = image.subsurface((0,0),clip_pos)
        if size:
            image = pygame.transform.scale(image,size)
        else:
            image = pygame.transform.scale(image,(SCREENWIDTH,SCREENHEIGHT))
        if colorkey is not None:
            if colorkey is -1:
                colorkey = image.get_at((0,0))
            image.set_colorkey(colorkey, RLEACCEL)
        return image

    ## 渲染图片到屏幕上
    def __updatePortrait(self,portraits):
        if portraits == {}:
            self.Portraits = {}
        else:
            ## update self.Portraits
            for key in portraits:
                if key not in self.Portraits:
                    temp_dict = {}
                    temp_dict[key] = portraits[key]
                    self.Portraits.update(temp_dict)
                else:
                    for sec_key in portraits[key]:
                        self.Portraits[key][sec_key] = portraits[key][sec_key]
            ## delete items which isn't contained portraitz
                ## list production can't apply the exceptions
                ##delete_key = [i for i in self.Portraits.keys() if self.Portraits[i]['flag']]
            delete_key_list = []
            for i in self.Portraits.keys():
                try:
                    if self.Portraits[i]['flag']:
                        delete_key_list.append(i)
                except KeyError:
                    pass
            for i in delete_key_list:
                self.Portraits.pop(i)

 

  当然,你总得知道,上面的都不是完整的代码,完整的代码在github上,我还是再贴一遍吧 github

   

  上面是人物立绘相关……挺难的不是么?下面的很简单,强制跳入指定一帧……语法是[next]

  恩,随手写个示例吧:

   

[next=21]

   

  这样就强制跳到21帧的位置了,这个语法在你写错了index的时候很有用……后面的save和load的实现上,也使用了这个方式。代码很短。

   

Parser部分:

def __InitReParserNextIndex(self):
        pat = r'''^\[next\s*?=\s*?(\d+?)\]$'''
        return re.compile(pat,re.M)

 

NodeItem部分

def __updateNextIndex(self,next_index):
        if next_index:
            self.NextIndex = next_index
            
    def setNextIndex(self,index = -1):
        if index == -1:
            self.NextIndex = self.Index + 1
        else:
            self.NextIndex = index

 

  后面那个setNextIndex是支持外部设定nextindex,后面很有用。

  就这样吧,写起来实话说真费力……顺便,我的博客意外发现可以直接用https的方法登录,省得FQ了,真好真好。宣传一下:早苗的空想庭院,欢迎围观~~~

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!