python製做galgame引擎(一)

  寫這個項目的直接緣由是最近推galgame推得有點過頭,gal推過頭的直接結果就是YY能力上漲,抱着「我也想寫好玩的劇本」的輕率念頭,也就開始了這個項目。不過從直接感受來講,galgame畢竟也是開發成本(我的)以及技術要求最低的遊戲類別之一,這固然也算是緣由。 python

  因而到了如今,一個半成品式的框架就搭好了。實話實說,gal引擎開發,技術難度不算大。可是,須要考慮的方面卻至關多,許多看起來很簡單的東西開發起來卻很麻煩,看上去很麻煩的東西只要換個思路,就會大爲簡化。 app

  這篇文章涉及的只是一個Galgame引擎,劇情和素材方面,直接使用guochaoer君的Sakia。後者是一個自制的galgame,在google code上託管,開發時間先於我一年。guochaoer君的代碼,給了我很多思路和代碼方面的啓迪,在此給予深深感謝。有興趣的話,請務必拜會guochaoer君的項目,地址 Sakia 框架

   

  很少說了,如今考慮引擎的編寫。 函數

  咱們須要幾個初期的目標,個人初期目標爲: this

  1.一個能穩定運行的,可擴展的框架 google

  2.代碼邏輯和劇情內容完全分開 spa

  第一個目標是顯然的,關鍵是第二個。之因此提出這個目標也正是由於guochaoer君的代碼,他的代碼實際上寫得不錯,可是最大的弊病是代碼和劇情文本混在一塊兒,總體上來看,就顯得比較凌亂,耦合度過高。這樣作還致使一個問題:代碼擴展性不太好。若是後面須要增添功能的話,頗有可能大改,在效率上不太划算,也容易失誤。 翻譯

  因此,我作了這樣的設計:代碼的執行部分放在run.py這個程序中,而劇情,圖片,音樂,放在一個script文本中,代碼和文本經過一個Parser解析器來聯繫。 設計

  文本的解析,就我掌握的部分---也是一般的手段,就是正則式。 code

  爲了解析方便,規定特定的語法是必要的。

  在定義語法以前,先明確哪些語法是必須的,這涉及到一個問題是:一個gal,最基本的元素是什麼。也就是說,捨棄掉哪些內容,gal纔是gal,而不會被認爲是別的什麼?

  我老是傾向於先搭建出一個最簡單,最基本的的框架,複雜部分在整個框架搭建好後,慢慢添加。好處就是,能儘快體驗到竣工時的成就感,而隨後添加複雜內容的時候,也能檢驗代碼是否足夠解耦並且易擴展,而且有能對質量差的代碼進行重構的機會。這是之前崔老師教給個人,真是無往不利。固然壞處也很明顯,容易挖坑不填……恩。

  就我我的來看,一個gal,最簡單的固然是隻有背景圖片,背景音樂,劇情文本三種元素的遊戲。人物對話並非必要的,一個好例子就是,不少時候,深夜推gal懶得戴耳機,直接把psp聲音關掉也不影響劇情體驗……按鈕的話,也有緣之空那種整個遊戲只有三個選擇支的gal,三個,基本等於沒有……人物的圖片,單純是我找不到合適的png……

  還考慮,鼠標點擊以前和以後實際上元素髮生了切換。可是最簡化模型,能夠把切換都省了,直接在畫面上顯示一幅圖片,幾行文本,播放音樂----這至關簡單。

  ok,背景圖片,背景音樂,文本,三種元素。爲這三種元素定義各自的語法並不困難,好比我本身,就定義成這樣-----

   

[background = 'xxx'] ---- 解析這個得到背景圖片的名字,也就是xxx部分

[BGM = 'xxx'] ----------- 解析背景音樂的名字

 

  這彷佛是天然而然的。

  可是文本有點不一樣。考慮到文本是gal中最頻繁使用的元素,每次使用[Text = ]這樣的語法,就太麻煩了,可是若是直接使用.*匹配,又太過粗放。因而我使用了<>,一對尖括號進行區分,這樣寫正則式也至關簡單。

  綜上所述,咱們就有了三個很好的正則式:

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

    ## I think paser a gammer like [BGM=xxx] is a good way
    def __InitReParserBGM(self): 
        pat = r'''^\[BGM\s*?=\s*?'(.+?)'\]$'''
        return re.compile(pat,re.M)

    ##I'd to use THE spcial way to define TEXT,like this:
    def __InitReParserText(self):
        pat = r'\<(.+?)\>'
        ##TELL re to match \n
        return re.compile(pat,re.DOTALL)

  我用了三個簡單函數包裹各自的正則式。

  考慮到事實上它們都是用來解析的,能夠打包成一個類-----理所應當吧?還額外有一個好處:正則式的編譯部分至關花時間,包裹成類的話,直接在初始化的時候完成編譯是至關合算的。

  還沒完,雖然如今的簡單框架不涉及畫面的切換,可是咱們仍是得爲切換作準備,直白地說,咱們須要一種方式區分兩幀---這裏的幀是我本身定義的一個術語,大體就是指任意時刻遊戲中包含的可感覺的內容,對gal來講,就是某一刻的背景圖片,音樂,文本的總和。

  簡單的作法是用空行進行區分,事實上,這種方式至關有效。

  那麼,使用定義的語法,若是寫script的話,就這樣。

 

[background = 'B1.png']

[BGM = '1-16.ogg']

<同人社團「5年目の放課後」實際的成員只有Kantoku一人,Kantoku,日文寫做カントク,意思是「監督」,他出生於1985年3月,活躍在ACG多領域的人氣畫師(風笳補註:須要注意,SAVE功能並未添加)>

 

< Kantoku 並不是自幼就開始接觸繪畫或者購買ACG商品,小學時候他還僅僅只是個沉迷於四驅車的小男孩,升入初中後,名做《魔卡少女櫻》紅遍全日本的時候,Kantoku也無可避免的成爲了這部漫畫的fan,>

 

  上面的內容,直接截取自我這裏,也就是guochaoer君的劇本……嘛,很容易讀和解釋吧?因而Parser類的代碼以下:

   

import re

class Parser():
    def __init__(self):
        self.NodeIndex = 0  ##Def any frame to a Node,and to point the SEQ,it is need a index
        self.Background =None
        self.BGM = None
        self.Name = ''  ##The argv about the speaker's name,maybe who is NONE
        self.Text = ''
        self.RPBackground = self.__InitReParserBackground()
        self.RPBGM = self.__InitReParserBGM()            
        self.RPText = self.__InitReParserText()

        ##There of above are REGULAR EXPRESSION,to paser the Gammer which I define
        ##only ONE compile can cut some time,MAYBE.....
    
    def __InitReParserBackground(self):
        pat = r'''^\[background\s*?=\s*?'(.+?)'\]$'''
        return re.compile(pat,re.M)

    ## I think paser a gammer like [BGM=xxx] is a good way
    def __InitReParserBGM(self): 
        pat = r'''^\[BGM\s*?=\s*?'(.+?)'\]$'''
        return re.compile(pat,re.M)

    ##I'd to use THE spcial way to define TEXT,like this:
    def __InitReParserText(self):
        pat = r'\<(.+?)\>'
        ##TELL re to match \n
        return re.compile(pat,re.DOTALL)
    
    ##split the script by each empty line
    def split(self,target):
        script = open(target)
        LNode = []
        Node = ''
        for line in script:
            if line != '\n':
                Node += line
            else:
                LNode.append(Node)
                Node = ''
        LNode.append(Node)
        return LNode
        
    def parser(self,target):

        if self.RPBackground.search(target):
            self.Background = self.RPBackground.search(target).group(1)

        if self.RPBGM.search(target):
            self.BGM = self.RPBGM.search(target).group(1)

        if self.RPText.search(target):
            t = self.RPText.search(target).group(1)

 

  上面的代碼截取自最終項目代碼的Parser類,固然只是一部分,可是也能看出一些東西。首先,類進行初始化的時候,對正則式進行編譯。定義若干self屬性,來保存解析後的內容。之因此用self屬性,是我但願若非指名,下一幀的屬性值直接使用上一幀的屬性值。帶來的好處就是,[background= 'xxx']或者[BGM = 'xxx'],只須要某一幀定義一次,接下來,若是不是顯式改變這個值,都保留着,直到結束或者更改成止。

  split方法用來分割幀,返回一個待解析的字符串列表,parser方法用來解析。

  原本打算直接結束Parser類的解說的,貌似涉及後面一些設計,仍是就這樣吧……

   

  ps.還有什麼劇情好的gal麼?漢化的最好,psp版首選;日語(生肉)的話,pc版的就好,psp就不用了,psp上沒有翻譯的說……

相關文章
相關標籤/搜索