python galgameエンジンの作成(四)


この主な議論の内容はフレームの切り替えとボタンの処理である.
このフレームは、普遍的な意味でのフレーム数などではありませんが、実際には、これは私自身が定義した概念です.フレームが何なのか分からない場合は、必ず最初の内容を見てください.この用語はそこで定義しましたが、ここでは説明しません.
前の3編で議論したものを合わせて作った効果も静的な面白くないもので、galgameとは全く呼ばれません.しかし,第3編でカプセル化されたクラスを用いて,真のgalgameを実現することは容易である.実際には、1フレームと1つのNodeItemクラスを同等にすることができます.各フレームは、実際にはNodeItemのインスタンス呼び出しupdateメソッドです.これにより、フレームを切り替えるプロセスは、NodeItem値を更新するプロセスに等しくなる.必要な値は、解析対象の文字列リストに含まれています.各項目を解析し、NodeItemに値を割り当てるだけで、更新プロセスは完了します.OK、今、すべての材料が完成したようですが、コードを書くべきかもしれません.
ゆっくりと、「動く」という目標をよりよく実現するためには、遊び、あるいは概念:Frame Indexを導入する必要があります.まぁ、この商品も勝手に取った名前なんだけど……でも、分析した通り、この値はいらないし、切り替えてもいいんじゃないかな?はい、この値がなければ、切り替えも可能であり、しかも容易である---この値を導入したのは、ボタンの実現のために敷き詰めるためである---ボタンの導入は、順序読取要素という大前提を破壊し、ランダム読取をサポートするためにindexが必要であることを知っておく必要があります.また、その後saveやloadをサポートする必要がある場合はindexも必要です.
明らかにindexはフレームごとに必要であり、スクリプトを書くときにそんなに痛くないように、この文法を簡単に使用することができます.
   
0
 
ご覧のように、単独の数字でいいので、正規式も書きやすいです.
    def __InitReParserIndex(self):
        pat = r'^\d+?$'
        ##TELL re to match any line
        return re.compile(pat,re.M)
简単に言えないでしょ?:-)
正直に言うと、自由な読み取りをサポートする必要がある場合は、リストを辞書に変えることをお勧めします.まず、辞書のランダムな読み取り速度が速く、次に、indexをkeyとして使うと、値を取るのも自然なので・・・どうせお勧めします.コードは次のとおりです.

LNode = parser.split('script.sanae')
dirNode = {}
for i in LNode:
    dirNode[parser.searchIndex(i)] = i

searchIndexメソッドはindexに対する正規マッチングであり,この辞書は1つの数字(index)をキーとし,解析対象文字列を値とする.
これだけ引っ張って、次はメインイベント、選択ボタンの実現です.
script構文を考慮します.各フレームは、複数のボタンである可能性が高く、各ボタンにはlabelが必要です.同時に、各ボタンの役割は、フレームを読み出すindexの値を返すことである.選択枝の出現頻度は高くないので,文法認識度が高い限り混同しないとよい.
私が採用した文法はこうです.
[choices]
くるみ(kurumi)->20
ひいらぎ(hiiragi)->200
[/choices]
実際、その[choices]は必要ではなく、正則的に一致する内容は->しかありませんが、[choices]を読むとscriptを読むときにより明確になります.
 

##       
self.RPChoice = self.__InitReParserChoice()

def __InitReParserChoice(self):
    pat = r'(.+?)->(\d+)'
    return re.compile(pat)
    
##   
## findall  ,       None,  
##      tuple,       label,
##         index
self.ChoiceBranch = self.RPChoice.findall(target)

 
次にボタンクラスを書きます.これも簡単です.
   

class Button(object):
    ##As a Button,these properties are necessary:
    ##A RECT:Contains the pos and size
    ##THE label:A micro text
    ##THE Image:decide the LOOK of the button
    ##       ,          ---   
    ##      。       ,       
    ##   。  ,       ?
    ##          ,      ,    
    ##        ,         
    ## pos      ,            ,
    ##      ,     
    ##        ,   nodeitem   ……
    ##           ,      Orz
    ##        ……
    def __init__(self,pos,size,image,font,label = '',bgcolor = None,fontSize = 24):
        self.pos = pos
        self.size = size
        self.surface = pygame.Surface(size,SRCALPHA)
        self.label = label.decode(DECODE)
        self.fontColor = (0xFF,0xFF,0xFF)

        ##To make sure the function is'n too long
        ##I split the code to three functions
        self.image = self.__LoadImage(image,bgcolor)
        self.font = self.__LoadFont(font,fontSize) 
        self.__Combination()


    def __LoadImage(self,image,bgcolor):
        ## Use the pure color
        if bgcolor != None:
            try:
                self.surface.fill(bgcolor)
            except pygame.error:
                print 'Cannot use the color'
                raise SystemExit
            return self.surface
        ## Use a image
        else:
            try:
                Image = pygame.image.load(image).convert_alpha()
            except pygame.error:
                print 'Could not load the image'
                raise SystemExit
            Image = pygame.transform.scale(Image,self.size)
            return Image

    ##Maybe I should consider reuse the code
    ##Now It is stupid
    def __LoadFont(self,font,fontSize):
        try:
            font = pygame.font.Font(font,fontSize)
        except pygame.error,message:
            print 'Cannot load font:',name
            raise SystemExit,message
        return font

    def __Combination(self):
        Image = self.image
        labelSurface = self.font.render(self.label,True,self.fontColor)
        xPos = (Image.get_width() - labelSurface.get_width())/2
        yPos = (Image.get_height() - labelSurface.get_height())/2
        Image.blit(labelSurface,(xPos,yPos))
        self.surface.blit(Image,(0,0))

    ##                 
    ##       ,surface       
    def render(self,surface):
        x,y = self.pos
        w,h = self.surface.get_size()
        x -= w/2
        y -= h/2
        surface.blit(self.surface,(x,y))

    ## check one point is in THIS BUTTON
    ## Orz,repeat codes....I'm lazy...
    ##               ,   
    ##     collidepoint,      
    ##    surface.get_rect()    
    ##  rect   ……      
    def is_over(self,point):
        x,y = self.pos
        w,h = self.surface.get_size()
        x -= w/2
        y -= h/2
        rect = pygame.Rect((x,y),self.size)
        return rect.collidepoint(point)

   
上にはボタン類があり、自分を描くことと判断することの2つの機能しかなく、かなり純粋です・・・
そして使用しても、これは難しくありません.
 

CHOICEBUTTONFROMTOP = 50
CHOICEBUTTONSIZE = (200,40)
def __updateChoice(self,choice_branch):
    dir_button = {}
    num_choice = len(choice_branch)
    button_distance = (SCREENHEIGHT-2*CHOICEBUTTONFROMTOP)/(1+num_choice)
    for (count,i) in enumerate(choice_branch):
        dir_button[i[0]] = (i[1],Button(\
        (SCREENWIDTH/2,CHOICEBUTTONFROMTOP+button_distance*(1+count)),\
        CHOICEBUTTONSIZE,'button.png',os.path.join('FONT','hksn.ttf'),\
        i[0]))
    
    self.ChoiceButtons = dir_button

 
一連のボタンを初期化して描画します.(SCREENHEIGHT-2*CHOICEBUTTONFROMTOP)/(1+num_choice)は私自身が出した小さな公式ですが・・・よく機能しているようです・・・大体スクリーントップとスクリーンボトム50画素の範囲内でボタンを等間隔に並べています.私の実験の結果から見ると、8つでもいいですが、9つ以上は試したことがありません.混んでいて見苦しいと思いますか.ところで、ボタンは必ずクリア処理をしなければなりません.忘れないでください.私たちのエンジンは、ある属性に値を渡さないとき、その属性は前のフレームの値にそのまま沿っています.他の属性は求められないので、ボタンはだめです.結局、1フレームしか現れません.
最後に完全なupdate方法を見て、この第1編の時に現れたことがありますが、完全ではありません.今は完全です.
   

def update(self,parser):
    self.__updateNodeIndex(parser.getNodeIndex())
    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)
    
    if parser.getChoice() != []:
        self.__updateChoice(parser.getChoice())
        for i in self.ChoiceButtons.keys():
            self.ChoiceButtons[i][1].render(self.Surface)
    else:
        self.ChoiceButtons = {}

   
ほとんどの方法は紹介されていますが、完全ではありません.興味のある方は、プロジェクトプロジェクト全体のソースファイルを直接見てください.github
実はまだ终わっていません......run.pyファイル.このファイルは実行を担当したり、フレームを切り替えたり...

# -*- coding: utf-8 -*-
import pygame
from pygame.locals import *
from sys import exit
import NodeItems
import Parser

##        index   ,       
##   index 65535  ,        
##   ,       ……miku     
ERRORINDEX = 65535

pygame.init()
screen = pygame.display.set_mode((800,600),0,32)
clock = pygame.time.Clock()

##           ,     
parser = Parser.Parser()
LNode = parser.split('script.sanae')
dirNode = {}
for i in LNode:
    dirNode[parser.searchIndex(i)] = i

## Create an instance of the NodeItem
## which contains the musics,images,and
## text,buttons etc.
## In face,One NodeItem represents a
## frame which you are watching

##         index=0  ,   
##          ……        ,
##        ……
nodeItem = NodeItems.NodeItem(screen)
parser.parser(dirNode[0])
nodeItem.update(parser)

while True:
    for event in pygame.event.get():
        if event.type == QUIT:
            exit()
        if event.type == MOUSEBUTTONDOWN:
            ## check where the click point on
            ##            。 ,      ,
            ##    ?        ……   
            ##               ,  
            if nodeItem.getChoiceButtons():
                click_point = event.dict['pos']
                dict_buttons = nodeItem.getChoiceButtons()
                ##Through over which button been clicked
                ##Surily if the point out of any button's
                ##area,we make it freeze :-)
                for key in dict_buttons.keys():
                    ## each dict_button is a tuple,
                    ## like this (index,button)
                    ## index is the next Node index
                    ## to change the control stream.
                    ## button is a instance of Button
                    index = int(dict_buttons[key][0])
                    button = dict_buttons[key][1]
                    if button.is_over(click_point):
                        ##      index  
                        nodeItem.setNextIndex(index)
                        break
            
            else:
                ## setNextIndex()       
                ##  self.index+1
                ## self.index      index
                nodeItem.setNextIndex()

            
            ##The following codes update screen
            ##             ……    。    script  
            ##    。      ,             
            ##       。
            NextIndex = nodeItem.getNextIndex() ##get key of one Node
            try:
                Node = dirNode[NextIndex] ##get a Node which is a string
                parser.parser(Node)
                nodeItem.update(parser)
                
            except KeyError:
                print 'Cannot search the index:',NextIndex
                Node = dirNode[ERRORINDEX] ## freeze the inscreasing of index
                parser.parser(Node)
                nodeItem.update(parser)
        
    clock.tick(5)

    
    pygame.display.update()


   
はい、終わりました.皆さん、ありがとうございました.プロジェクト全体の基本的な考え方と実現はここまでです.まぁ、実は次編には付録のようなものがあって、プラットフォームとテキストの位置合わせを議論します.@solu、今日は彼と長い間話し合った.具体的にはソロを見てください
最後に、ここまで読んでお疲れ様でした!お疲れ様でした.おつかれさまです!