Builderパターンによる調合システムの構築
今回は、Builderパターンを使って、最近はやりの「調合システム」を構築してみます。
kenmoの勝手な考察ですが、調合システムは、
- 単体では弱いが、組み合わせをすることにより強力なアイテムになる
- 「あとアレがあれば、強力なアイテムが作れるのにー」といった「待たされ感」
- 未知のアイテムを組み合わせによって探し出すという「隠し」要素
など、ゲームデザイン的に優れた要素がいくつかあるため、
最近のゲームでは良く使われるシステムであると思います。
それはさておき、Builderパターンです。
Builderパターンとは
例えば、物を作る場合には、「素材」と「作成手順」が必要になります。
Builderパターンの特徴は、
その「素材」と「生成手順」を明確に分離して分かりやすくし、
そして拡張しやすくすることにあります。
クラス図
では例を見てみます。
以下のクラス図は、某アトリエシリーズの調合システムを実現するものです。
説明としては、
Materialクラスが、「素材」の基底クラスとして存在し、
派生クラスとして、
- Carrot(にんじん)
- Gram(ひよこ豆)
- WellWater(井戸水)
- Bohnensuppe(豆のスープ)
が存在します。
これらは、調合するための「素材」になり、
調合した結果の「生産物」ともなります。
BlendingMakerというのは調合を管理するクラスです。
「作成手順」を実装するところです。
ここに素材を詰め込んで、実行すると、「生産物」のインスタンスを返してくれます。
Viorartというのは、プレイヤーです。
プレイヤーは、「素材」をどこかから拾ってきたり、
BlendingMakerに「素材」を詰め込んだり、実行したりします。
実装例
Pythonによる実装例は以下のようになります。
#!/usr/bin/env python # -*- coding: utf-8 -*- class Material: """ 素材基底クラス """ def getId(self): return self.ID def getName(self): return self.NAME class Carrot(Material): """ にんじん """ ID = 1 NAME = "にんじん" class Gram(Material): """ ひよこ豆 """ ID = 2 NAME = "ひよこ豆" class WellWater(Material): """ 井戸水 """ ID = 3 NAME = "井戸水" class Bohnensuppe(Material): """ 豆のスープ """ ID = 10 NAME = "豆のスープ" class BlendingMaker: """ 調合Director """ def init(self): self.materials = [] def setMaterial(self, material): self.materials.append(material) def blend(self): """ 調合実行 """ tmp = [] for material in self.materials: print "%sを調合します。"%material.getName() tmp.append(material.getId()) print "|\n|\nV" # 調合判定 if(tmp.count(Carrot.ID) == 1 and tmp.count(Gram.ID) == 2 and tmp.count(WellWater.ID) == 1): return Bohnensuppe() return None def main(): # 素材生成 carrot = Carrot() gram1 = Gram() gram2 = Gram() wellwater = WellWater() # 調合クラス生成 maker = BlendingMaker() maker.init() maker.setMaterial(carrot) maker.setMaterial(gram1) maker.setMaterial(gram2) maker.setMaterial(wellwater) # 調合実行 ret = maker.blend() if ret is None: print "調合に失敗したよ..." else: print "%sの調合に成功したよ!"%ret.getName() if __name__ == "__main__": main()
ポイントは、調合判定の部分ですね。
# 調合判定 if(tmp.count(Carrot.ID) == 1 and tmp.count(Gram.ID) == 2 and tmp.count(WellWater.ID) == 1): return Bohnensuppe() return None
豆のスープのレシピは、
- にんじん×1
- ひよこ豆×2
- 井戸水×1
なので、このような書き方をしています。
今、重要なことに気がついたのですが、、、
この判定は「ポーカー」とか「麻雀」とかに応用できそうですね。
…なるほど。そもそも調合も、麻雀の「役」をそろえていくのと同じシステムですねー。
それはさておき、実行結果はこんな感じです。
にんじんを調合します。 ひよこ豆を調合します。 ひよこ豆を調合します。 井戸水を調合します。 | | V 豆のスープの調合に成功したよ!
以上、Builderパターンでしたー。
補足1
例では「素材」の生成は、「プレイヤー」が行っていますが、
本当のBuilderパターンは、Director(例におけるBlendingMaker)が
「素材」のインスタンス生成も管理しています。
補足2
例では調合判定をべた書きしていますが、
実際は判定処理が膨大な量になるので、外部スクリプトなどで用意した方が、
メンテナンス性が向上すると思います。