RPGにおけるイベントスクリプトの構造

昨日に続いて、RPGネタです。
 
マップに配置したイベントIDをキーに、イベントスクリプトを呼び出す、
という話だったのですが、
今回はその「イベントスクリプト」の構造に触れてみたいと思います。
 

イベントスクリプトは、

イベントID
 ├フラグ操作
 └イベント内容

というのが、最低限必要な構造になります。
 
そして、より汎用的なスクリプトにする場合は、

  • if〜else
  • while
  • 変数

などの制御、データを追加します。
 
…まあ、あんまり汎用的なものを目指すと、
RPG作っているのだか、スクリプト作っているのだか、
良く分からなくなるので(´∀`;
最低限必要な機能に絞ったほうがいいと思います。
 
 
それで、肝心のスクリプトなのですが、
1から作るよりも、XMLを使うと楽ができます。
 
例えば、こんな感じです。

<root>
	<!-- GreetingScript ID=1 -->
	<event id="1">
		<matter no="0">
			<flag if="1" on="2" off="1"/>
			<!-- When first flag stands, -->
			<!--   scond flag turn on -->
			<!--   third flag tunn off -->
			<msg>Good Morning!</msg>
		</matter>
		<matter no="1">
			<flag if="2" on="3" off="2"/>
			<msg>Good Day!</msg>
		</matter>
		<matter no="2">
			<flag if="3" on="1" off="3"/>
			<msg>Good Evening!</msg>
		</matter>
		<matter no="-1">
			<!-- There is no corresponding flag. -->
			<msg>Hello!</msg>
		</matter>
	</event>
	
	<!-- TreasureBoxScript ID=100 -->
	<event id="100">
		<matter no="-1">
			<flag nif="10" on="10"/>
			<money>500</money> <!-- 500yen Get -->
		</matter>
	</event>
	
	<!-- ChangeSceneScript ID=1000 -->
	<event id="1000">
		<matter no="-1">
			<msg>Goto Field?</msg>
			<scene id="1" posx="0" posy="1" />
			<!-- It changes to coordinates(0,1) of SceneID1. -->
		</matter>
	</event>
</root>

XMLは素人なので、これが正しい書き方なのか微妙です(´Д`;
 
と、とりあえず解説すると、トップに「root」があり、
その下にユニークなイベントIDを持ったイベントスクリプト「event」があります。
 
各イベントスクリプトは、発生イベントを1つ以上持っています。
それが「matter」になります。
「matter」はその下にある、「flag」のif属性に一致した場合に、
イベントを実行します。
 
そして、イベント終了後は「flag」のon属性に一致するフラグを立て、
off属性に一致するフラグを下げます。
 
また、「matter」のid属性に「-1」を指定すると、
発生するイベントが存在しなかった場合に発生する
デフォルトのイベントとなります。
 
サンプルとして、PythonでこのXMLを解析するスクリプトをのせておきます。
(そんな需要があるんかいな(´Д`;

from xml.dom.minidom import parse, parseString

class XMLLoader:
	def __init__(self):
		self.map = {}
		self.rootTag = ""
	def initialize(self):
		self.map = {}
		self.rootTag = ""
	def load(self, filepath, rootTag):
		self.rootTag = rootTag
		dom = parse(filepath) # parse an XML file by name
		self.parse(dom, None, None, None)
		dom.unlink()
	def parse(self, dom, map, key, childMap):
		for node in dom.childNodes:
			if(node.nodeType == node.ELEMENT_NODE):
				# ELEMENT
				if(key != node.tagName):
					childMap = None
				key = node.tagName
				# ATTRIBUTE
				if(key == self.rootTag):
					tmpMap = {}
					self.map[node.getAttribute("id")] = self.parse(node, tmpMap, key, None)
				elif(key == "matter"):
					tmpMap = {}
					map[node.getAttribute("no")] = self.parse(node, tmpMap, key, None)
				elif(key == "flag"):
					map["flag"] = {
						"if": node.getAttribute("if"),
						"nif": node.getAttribute("nif"),
						"on": node.getAttribute("on"),
						"off": node.getAttribute("off")
					}
				elif(key == "scene"):
					map["scene"] = {
						"id": node.getAttribute("id"),
						"posx": node.getAttribute("posx"),
						"posy": node.getAttribute("posy")
					}
				else:
					self.parse(node, map, key, None)
			elif(node.nodeType == node.TEXT_NODE):
				# TEXT
				txt = node.data.strip()
				if(txt != ""):
					map[key] = txt
		return map

def main():
	xl = XMLLoader()
	xl.load("event.xml", "event")
	print xl.map

if __name__ == "__main__":
	main()

 
これを実行するとこんな感じです。

{
	u'1': 
	{
		u'1': 
		{
			u'msg': u'Good Day!', 
			'flag': 
			{
				'on': u'3', 
				'nif': '', 
				'off': u'2', 
				'if': u'2'
			}
		}, 
		u'0': 
		{
			u'msg': u'Good Morning!', 
			'flag': 
			{
				'on': u'2', 
				'nif': '', 
				'off': u'1', 
				'if': u'1'
			}
		}, 
		u'2': 
		{
			u'msg': u'Good Evening!', 
			'flag': 
			{
				'on': u'1', 
				'nif': '', 
				'off': u'3', 
				'if': u'3'
			}
		}, 
		u'-1': 
		{
			u'msg': u'Hello!'
		}
	}, 
	u'100': 
	{
		u'-1': 
		{
			u'money': u'500', 
			'flag': 
			{
				'on': u'10', 
				'nif': u'10', 
				'off': '', 
				'if': ''
			}
		}
	}, 
	u'1000': 
	{
		u'-1': 
		{
			u'msg': u'Goto Field?', 
			'scene': 
			{
				'id': u'1', 
				'posx': u'0', 
				'posy': u'1'
			}
		}
	}
}

 
こうして読み出したデータは、
使う側から、なるべくXMLの内部構造を意識しないように、
クラス・関数でラッピングして、
getXXX()メソッドのように利用すると、
XMLの仕様を変えた場合に、デグレが発生しにくくなります。