寫這個項目的直接緣由是最近推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上沒有翻譯的說……