MUGENと言う格闘ゲームエンジンで遊んでいるのですが、
キャラクターやステージを自作しようとすると、出来はともかく、
スクリプトの記述に繰り返しが非常に多い。
何十回も書くのは面倒なので、Pythonでどうにかできないか、と、作ってみました。
まず、埋め込んだPythonコードを実行するクラス。
#preprocessor.py import re class Preprocessor(object): def __init__(self, vars, debug=False): self.vars = vars self.debug = debug self._regex = None def regex(self): pat = r""" {exec_begin} (?P.*?) {exec_end}(\\(\n|$))? | {eval_begin} (?P.*?) {eval_end} | {raw_begin} (?P.*?) {raw_end}(\\(\n|$))? | (?P.+?(?={exec_begin}|{eval_begin}|{raw_begin}|$)) """.format( exec_begin =re.escape("<["), exec_end =re.escape("]>"), eval_begin =re.escape("$"), eval_end =re.escape("$"), raw_begin =re.escape("<!"), raw_end =re.escape("!>"), ) return re.compile(pat, re.DOTALL | re.VERBOSE | re.MULTILINE) def process(self, src, **kw): pp = self.vars["preprocessor"] pp.__dict__.update(**kw) def iter_strs(): for m in self.regex().finditer(src): if self.debug: pprint(m.groupdict()) if m.group("exec"): ss = m.group("exec") pp.clear() exec ss in self.vars yield pp.getvalue() elif m.group("eval"): ss = m.group("eval") yield eval(ss, self.vars) elif m.group("raw"): yield m.group("raw") elif m.group("others"): yield m.group("others") return "".join(map(unicode, iter_strs()))
次に、埋め込んだPythonから使うコード
#macro.py from StringIO import StringIO import codecs class _PPWriter(object): def __init__(self): self._fp = StringIO() self.encoding = "utf8" def write(self, *a, **kw): self._fp.write(*a, **kw) def getvalue(self): return self._fp.getvalue() def clear(self): self._fp = StringIO() preprocessor = _PPWriter() del _PPWriter def pw(s): preprocessor.write(s) def include(path, encoding=None): if encoding is None: encoding = preprocessor.encoding with codecs.open(path, encoding) as fp: return fp.read()
最後にPreprocessorを操作する本体
#main.py from __future__ import print_function from preprocessor import Preprocessor with open(sys.argv[1]) as fp: src = unicode(fp.read()) vars = {} exec "from macro import *" in vars pp = Preprocessor(vars) print(pp.process(src))
それで、たとえば、次のようなステージ用スクリプトを書く
;spam_stage_.def [Info] name = "Spam Stage" <[ size = 10 temp = ''' [BG python] type = normal spriteno = 1, {index} start = {p[0]}, {p[1]} ''' def bg(index): y = index * size x = 0 return temp.format(index=index, p=(x, y)) for i in xrange(3): pw(bg(i)) ]>
実行すると、次のように出力されます。
D:\Owner\temp>python main.py "spam_stage_.def" ;spam_stage_.def [Info] name = "Spam Stage" [BG python] type = normal spriteno = 1, 0 start = 0, 0 [BG python] type = normal spriteno = 1, 1 start = 0, 10 [BG python] type = normal spriteno = 1, 2 start = 0, 20
"<[ ]>"で囲んだ部分が、Pythonコードとして認識されて、execされます。
"<[ ... ]>"の中でpw関数を呼び出すと、
引数の文字列がその場所に書き出されます。
また、"$ $" で囲むと、evalされて、結果が書き出されます。
たとえば、spam_stage_.defはこう書いても良い
;spam_stage_.def [Info] name = "Spam Stage" <[ size = 10 temp = ''' [BG python] type = normal spriteno = 1, {index} start = {p[0]}, {p[1]} ''' def bg(index): y = index * size x = 0 return temp.format(index=index, p=(x, y)) ]> $pw(0)$ $pw(1)$ $pw(2)$
あと、""で囲んだ部分は、そのまま書き出されます。
まぁ、実際にMUGENユーザーに使ってもらえるかどうかはわかりませんが、
そのうち、Windows用の実行ファイルにして出してみようと思います。
ちなみに、テンプレートエンジンは、本格的なものもありますが、
軽量のものとしては、
inforno::埋め込みPythonを実装してみました
のものがあるようです。
実際、わざわざ自分で作らなくても・・・いやいや!作ってみる事に意味があるのです!