- ベストアンサー
状態とアクションの関係についての設計方法
- PHPの環境で状態とアクションの関係を設計する方法について詳しく説明します。
- 状態を基準にクラスを設計する方法と、アクションを基準にクラスを設計する方法の違いについて考えてみましょう。
- RPGの戦闘のシステムを設計する際に難しい要素や状態の絡み方についても解説します。
- みんなの回答 (40)
- 専門家の回答
質問者が選んだベストアンサー
>>この戦闘処理の状態を状態遷移図みたいなので表すとすると 複合状態(毒+石)も一つの新たな状態として存在することになるのでしょうか? それは、その世界内でのルールしだいだと思います。現実の世界では、「そういうケースは有り得ない」という前提条件で設計していても、何千回、何万回に1回だけど、ありえないとされる事象が発生するとか、パーツである基盤の仕様が間違っていて、有り得ないパターンが発生したりで「システムが停止したぞ!」というような苦情となったりします。 ゲームでは、設計者は「神様」ですから、その複合状態を有り得ないことにするのも、それを認めてそこからの回復条件をちょっと難しくすることも自由だと思います。 ですが状態遷移表を作る場合は、その複合状態も考慮して作成します。 >>つまり毒状態とも石状態とも違う毒+石という新たな状態。 もちろん複合状態もありだとすると、凄い数が増えることになると思うのですが そのとおりで、状態は掛け算で増えてゆき、凄い数になります。ちなみに○と矢印で記述する状態遷移図は、比較的簡単に書けるのですが、これはイベントを全て考慮しない図も書けるので、その場合バグの要因になります。 イベント(状態が変化する原因)とステータス(状態)をマトリクスにした状態遷移表の場合は、そういう抜けは発生しませんが、かなりでかい表になってしまいます。 それを防ぐには、状態遷移表を分割するわけですが、その表分割が不適切だと当然ながらバグ要因になりますね。 >>自分としては「毒+足怪我状態なので攻撃力が大幅に低下した」みたいな付加処理を 多種多様なパターンとして設計してみたいという欲望^^がありまして。。 ステートマシンで作っていれば、その付加処理の状態を「ありえない」としておいて、あとからそういう付加処理を追加するのはとても簡単ですしバグもたぶん発生しないでしょう。そのかわりに、初期段階の設計は複雑になりますね。
その他の回答 (39)
- hogehoge78
- ベストアンサー率80% (433/539)
■耐性(Resist)には30以上のかなりの数があるのですがそれを例えばこの場合だとActorにメンバ変数としてもたせるのでしょうか? 私の場合は、とりあえず今の段階なら持たせちゃいますね。 とりあえず今現状つくりこんでいるそのオブジェクトの意味合いとしては通ると思うので。ソレが多少面倒くさくても。 それが本当に面倒くさいのとは別に読めない、読みづらい、という事であれば、その時にリファクタリングしていけばいいんじゃないかなと思います。 何度もつくり直して、自然な形になったら、実際にJavaだとかC#なんかで組んでみようと思ったときにも、その失敗の経験なんかも役に立つのではないでしょうか。 ■hogehoge78さんのお勧めの書籍などありますでしょうか? 私は書籍をほとんど読んだことないです。フレームワークやライブラリとにらめっこしながら、PHPで使えるデザインパターンとか参考になるものを発見したり、Googleで検索したりしてました。 書籍や考え方に関しては、lv4uさんのほうが、全体的なアプリケーションの設計の経験があるようなので、今回おっしゃっていたステートマシンや、全般的な設計に関して参考になる書籍や資料をご存知かと思いますが、いかがでしょうか?
お礼
ご回答ありがとうございます。 要はそれぞれのオブジェクトにデータベースから値を取り込んで メンバ変数に割り当てるということですよね? そのhogehoge78さんが考えているObjectに関してなのですが、 No.16で解説されてたのですが、ちょっと自分が曖昧な理解のままできてしまったので Application_Model_Objectについて詳しく知りたいのですが、 例えば Application_Model_Objectクラス と Application_Model_Object_Elementクラス と Application_Model_Spellクラス について具体的な実装例を示して頂けないでしょうか? 特にApplication_Model_Objectの存在意義が良く分からないのです。 なにか値をデータベースから取り込む共通の処理があるのでしょうか? >私は書籍をほとんど読んだことないです。フレームワークやライブラリとにらめっこしながら、PHPで使えるデザインパターンとか参考になるものを発見したり、Googleで検索したりしてました。 なるほど、そうですか。自分としてはただフレームワークを眺めてても分からないことだらけ(=つまらない・・・)なので(^^) 何かその都度その都度解説が欲しいなぁというのがありまして。 どうしても分からないことがあるとこうして質問させて頂くことになって、、 例えばPHPでいえば大抵はhogehoge78さんが解決してくださる結果になるわけで(ほんとhogehoge78さんが自ら書籍を出して欲しいくらいですよ) 基本的なことがある程度理解できてれば、毎回同じようなことでの質問の回数も減らせそうな感じがするんですけど(とは言っても質問って 単に疑問に対する回答を得ることだけじゃなく、プラスαのことも期待してっていうのもありますけどね^^(この質問のように))
- hogehoge78
- ベストアンサー率80% (433/539)
私の場合は、オブジェクトとは、あるパラメータを持っていて、そのパラメータに作用する、または外的要因に作用するメソッドを持っている物、と考えてます。 また、そのようにしておいたほうが、戦闘終了後、Playerのステータスをまたそれを保管するデータベースに対してアップデートしてやれば、状態は簡単に保存出来るはずなので。 っていうのが、基本になっていて私の書いたスクリプトがあります。 そして、前回回答に書いたとおり、1戦闘で管理しなければいけない状態というのも、そこそこ知れた状態なんじゃないのかな、 と仮定しておりますので、状態専用の別クラスで管理をすることを考えていない、といったところです。 また、最初の回答でも伝えたとおり、基本はMVCで考えていて、今回の攻撃を受ける/攻撃を与えるというのが、今回の1回のアクションのController部分かなと。 throwElement/catchElementですね。 これらがControllerなのであれば、そこに状態振り分けを書いてやって最終的な挙動を決める、とあるのが基本だと考えていて、その為、そこに各状態の振り分けを書いています。 っという前口上があった上で、Parameter管理に関してですが、 基本的に、上に書いたとおりなので、作用されるにしても、Actorクラスに存在するメンバ変数に値が返ってきておいて欲しいので、 Perameterを管理しないで、Parameterを計算する計算機をstaticなクラスのメソッドで処理してやる感じかなぁとおもいます。 class Paramter_Calcurator{ public static function injectionSpell(Actor $actor, Element $spell){ if($spell instanceof IActionDamage){ self::injectionSpellDamage($actor, $spell); } } public static function injectionSpellDamage(Actor $actor, IActionDamage $spell){ } //とかなんとか } どうせ、バイキルトやフバーハによる変動とか、(ドラクエはどうだったか忘れましたが)戦闘中における装備変更で 毎回注入するべき値がふわふわ変わるので、大元になるステータス値を持っている各オブジェクトから 攻撃/防御毎に再計算掛ける必要があると思いますので、静的なメソッドで計算かけてやって、各々のオブジェクトに 値を注入してやったほうが良いのかな、と思います。
お礼
ご回答ありがとうございます。 >オブジェクトとは、あるパラメータを持っていて、そのパラメータに作用する、または外的要因に作用するメソッドを持っている物 なるほど、言われてみればたしかにそうだよなぁというか自分もそう思えてきました。それが自然というか。 hogehoge78さんのオブジェクトの考え方だとたしかに、なるほどたしかに自分もParamter_Calcuratorのような staticなクラスのメソッドでやった方がいいように思えるのですが、耐性(Resist)には30以上のかなりの数があるのですが それを例えばこの場合だとActorにメンバ変数としてもたせるのでしょうか? class Actor{ // Ability protected $HP; protected $MP; protected $ATK; protected $DEF; protected $INT; protected $AGI; // Resist protected $MeraResist; protected $GiraResist; ・ ・ ・ } それとも連想配列などで持たせるのでしょうか? また^^話は変わりますが、今の自分の理解力というか知識だと 基本的なことが分かってないのでなかなか先に進めないというか、、 もしよろしければオブジェクト指向関連(じゃなくてもプログラムに対する考え方でも)で hogehoge78さんのお勧めの書籍などありますでしょうか? 本当は実践でそういうのを学んでいけるのが一番いいんでしょうけど、そういう環境でもないので なんかこう自分としては、ひたすらオブジェクト練習ができる問題集^^みたいなのが欲しいんですけど 実際にありそうでないですもんね。。
- lv4u
- ベストアンサー率27% (1862/6715)
ちょっとコメント。 >>ステートマシン図をきちんと書いて、フラグを管理し、ステートマシンの自動生成をすれば、アクション遷移に バグが起きづらいって話なんだと思います。 そうです。ただ、石の攻撃を受けたことをフラグとして表現するか、状態として保持するかの判断はなかなか難しいですね。仕様変更として、新たな攻撃の種類が増えたとき、それをフラグで表現するほうが楽ですが、バグを生む原因になったりします。(苦い経験あり)ステートを増やすのは遷移図の修正も必要になって大変ですがバグは発生しにくいです。 >>石という状態なのに、さらに「石+毒」という状態を追加されると石であって且つ「石+毒」である状態になるバグが出たときに、面倒臭い気がします。 石と毒について、それぞれを「状態」でもつとなると「石」「毒」「石ー>毒」「毒ー>石」のようにステータスが増えますね。もちろん、石の状態なのに、さらに「石」が追加されるとか、「毒」が追加ってのもありえます。この場合は、「すでに石なので、そのまんま」とか「石なので毒攻撃は無視」って状態の遷移を割り当てることになると思います。 つまり、ステートマシンでは全ての状態を検討して、「ありえない」「絶対ここにはこない!」と思える組み合わせでも、そのイベント(原因)にたいする遷移先のステータスを十分に検討しておきます。そして、「○○でバグ発生、コードチェックしろよ!!」って表示して停止するとか、何も効果無しにするなどを決めます。 「このケースは、はありえない」って判断で、いいかげんにステート図を書いていると、実際に異常時のケースとして、その組み合わせが発生して、必要なアクションが指定されてなくてバグとなることがあります。(苦い経験あり)
お礼
ご回答ありがとうございます。 この戦闘処理の状態を状態遷移図みたいなので表すとすると 複合状態(毒+石)も一つの新たな状態として存在することになるのでしょうか? つまり毒状態とも石状態とも違う毒+石という新たな状態。 もちろん複合状態もありだとすると、凄い数が増えることになると思うのですが そういうものなんだということを確認しときたいと思いまして。 ちょっと状態遷移表を見てきて自分が感じたのは、 この戦闘処理の状態とその他の状態の違いは、その複合状態があるかないかで このゲームだとたしかに状態を複数所持することがあるんです(おそらくというか確実にフラグで管理してると思いますが) だからといって本家では特別な付加処理が行われるわけじゃありませんが、 自分としては「毒+足怪我状態なので攻撃力が大幅に低下した」みたいな付加処理を 多種多様なパターンとして設計してみたいという欲望^^がありまして。。
- hogehoge78
- ベストアンサー率80% (433/539)
今私が回答している段階では、特にゲーム全体に関連する話で書いていないので、それらの置き場所とかといったことに関してはあまり考えてなかったです。 ■Actorの位置づけ 私が考えるActorの基本的な位置づけは、ゲーム全体に関するものです。 Object > Actor > Player Object > Actor > Monster マップやダンジョンのフィールド中でも、Playerと回復などの対象となるActorを継承しているPlayerは存在しているはずなので、 どこでも共通で使える場所に置いておく必要があると思います。 なので、Buttleの中に押し込めては逆に使いづらいかなと考えます。 「Object」という基本クラスは、Modelからデータベースで値を拾ったときにControllerにそのレコードを返す場合のレコードを押し込めておく場所という意味合いです。 魔法や、特技、道具なんかは、 Object > Element > Spell(IActionDamage,IActionHealing) Object > Element > Skill(IActionDamage,IActionHealing) Object > Element > Item(IWeapon,ISealdとか。) みたいな感じで。 また、戦闘というものからもっと広げてゲーム全体でいえば Object > MapTip > Wall(英語苦手なんですが、まぁ通過不可能なマップチップ的な) Object > MapTip > Field とか。 とにかく、データベースに格納してあるある一定のステータスを持つレコードを押し込めてやれば良いかなと。 あぁ、ZendFramework風にもし書くとすると、 http://framework.zend.com/manual/en/learning.quickstart.create-model.html こちらのクイックスタートにあるように、 Application_Model_Player extends Application_Model_Object_Actor Application_Model_Spell extends Application_Model_Object_Element ですかね。 ■状態に関して 私もステートマシンについては結局よくわかってないですが、検索してみてなんとなくわかったことは、 ステートマシン図をきちんと書いて、フラグを管理し、ステートマシンの自動生成をすれば、アクション遷移に バグが起きづらいって話なんだと思います。 従って今回のような1戦闘内で管理したいような知れている状態の遷移の話ではなくて、ゲーム全体のフラグ管理に使うのかなと思いました。 RPGツクールでいうとイベントスイッチのような。 そういう意味で、lv4uさんは、「「現世に戻れないテレポート!」って攻撃で、異次元に送り込むと、相手は永遠に異次元をさ迷うとか」といったのかなと思いました。現世に戻れないってことはその後のゲームにその人物が登場出来なくなるので、その状態をゲーム中でずっと持っていないといけないですし。 そして、もし戦闘時でステートマシン図を使うということであっても「石+毒」という状態を「石」と「毒」とは別にもつという状態はちょっとよくわかりません。 石という状態なのに、さらに「石+毒」という状態を追加されると石であって且つ「石+毒」である状態になるバグが出たときに、面倒臭い気がします。 「高く飛び上がる」についても、その状態は、メッセージ上はそうですが、フラグ管理的には ・Xターン凍結する状態 ・Xターンの間無敵状態 ・Xターン中は攻撃力を付加する とかそれぞれ別の特殊効果がつくと考えた方がシンプルだと思います。 ■チーム分けに関して 現状ドラクエの場合、主人公の属しているチーム、と、それ以外のチームで、 モンスターもグループ化されているものと、一体一体で居る物がいると思います。 グループ化されているものの場合はベギラマはそのグループに攻撃出来ますが、グループ化されていない場合は 単体にしかダメージが与えられません。 ソレを考えて、主人公の属するPlayerは戦闘時に必ずgroup_id=99とし、ソレ以外は適当にランダムな値をふる と考えました。 なので、わざわざTeamクラスを作って管理しなくても、TargetOrderクラスで上手くターゲットをつけられたら、groupというステータスを持つActorオブジェクトで判別可能だし、混乱時のランダム攻撃だった場合はgroup無視でランダムにターゲットを選べればいいんじゃないかなと思います。
お礼
ご回答ありがとうございます。 なるほど、たしかに自分は戦闘処理という狭い範囲で物事を考えすぎてたところがありますね・・・ そもそもそういう主題だったかもしれませんが、もう少し広い視点じゃないと駄目ですね。 ただチーム分けに関しては狭い広いというより、単純にグループ化されているということを忘れてました・・・^^ たしかにそうですね Playerの位置づけに関しても、例えば戦闘が終了しても毒の状態のままだったりしますし 死というのが状態だとしたら死んだままですし、hogehoge78さんの分け方が良いと思います。 ただ状態に関しては、状態を複数個所有している場合に($this->states = array('stone', 'poison')) 自分が考えていたのは正確(全然分かり難いと思いますけど・・)にいえば 状態というより戦闘処理におけるアクションにどのような処理を割り込ませるかというか 例えば、石の状態だと仮にどんなアクションが選択(選択されるような仕組みじゃないかもしれませんが)されていても、 何もできない(「石なのでギラを唱えることはできなかった!」とかメッセージを挿入してもいいかもしれませんね)ですし それぞれの状態には上下の優先順位を持たせてあって、それ以降の優先順位の状態クラスの割り込ませる処理を 行うかの返り値も返すようクラスのメソッドに持たせてあります。石の状態だと下の優先順位の処理は実行されません。 なので石+毒という例が悪くて、実際にはそのようなクラスは設けなくていいかもしれませんね。 例えば、毒+足怪我という(複合)状態クラスも上下の優先順位を持たせてあって、 当然、単独の毒や足怪我よりも優先順位は上で、この複合状態の実行後は単独の毒や足怪我は実行されません。 ただ他の優先順位が下の状態を所有していれば処理は実行されます。もちろんアクションを受ける側の状態も 同じように考慮しないといけませんが、でもこれってそこまで考えるなら アクションする側のモンスターが毒の状態で、アクション受ける側のモンスターが足怪我の状態で その複合状態まで考える必要があるとすると、、、やっぱり無理がありますね・・・ 状態に関しては自分的にしっくりくるというか正直納得できていない(何かに期待してしまう)ので、 つまりhogehoge78さんとかが考えている「これでいいんだ(これが自然なやり方)」という 境地に、自分はそこまでの理解力というかそういうのがないのでまだなれないんですよね。。 う~ん、すいません、ちょっと話は変わりますが先ほどのParameterクラスについて伺いたいのですが、 そもそもなんで別個にParameterクラスを設けようかと思ったかというと、 HP,MP,攻撃力などの「能力(Ability)」とは別にギラや水攻撃に対する「耐性(Resist)」というパラメータがあって それぞれ微妙に処理が違っていて、能力の方はcurrent(現在値)とmax(最大値)があって HP,MPなどはcurrentがmaxを超えることはありませんが、攻撃力や守備力は戦闘時にcurrentがmaxの2倍まで バイキルト(攻撃力が2倍になる特技)などで上がることがあります。 ただ耐性はcurrentがmax以下になることがあっても超えることはありません。 なのでParameterクラスにManagerを設けて class Battle_Player_Parameter_Manager_Abstract { protected $parameters = array(); public function __construct($config) { $tips = split('_Manager_', get_class($this)); $class = $tips[0] . '_' . $tips[1]; foreach ($config as $key => $value) { $this->$key = new $class(); $this->$key->current = $value; $this->$key->max = $value; } } public function __get($key) { return array_key_exists($key, $this->parameters) ? $this->parameters[$key] : null; } public function __set($key, $value) { $this->parameters[$key] = $value; } }
補足
すいません、補足を借ります。。 class Battle_Player_Parameter_Manager_Ability extends Battle_Player_Parameter_Manager_Abstract { public function __construct($config) { parent::__construct($config); $this->HP->upperLimit = $this->HP->max; $this->MP->upperLimit = $this->MP->max; $this->ATK->upperLimit = $this->ATK->max * 2; $this->DEF->upperLimit = $this->DEF->max * 2; } } abstract class Battle_Player_Parameter_Abstract { protected $_data; // protected $current; // protected $max; public function get($name, $default = null) { $result = $default; if (array_key_exists($name, $this->_data)) { $result = $this->_data[$name]; } return $result; } public function __get($name) { return $this->get($name); } public function __set($name, $value) { $this->_data[$name] = $value; } public function addCurrent($value) { if ($this->current + $value < $this->upperLimit) { $this->current = $this->current + $value; } else { $this->current = $this->upperLimit; } return $this; } public function subCurrent($value) { if ($this->current - $value > $this->lowerLimit) { $this->current = $this->current - $value; } else { $this->current = $this->lowerLimit; } return $this; } } class Battle_Player_Parameter_Ability extends Battle_Player_Parameter_Abstract { } $arr = array( 'HP' => 100,'MP' => 20,'ATK' => 44,'DEF' => 10,'INT' => 300,'AGI' => 20,'LUK' => 33, ); $abilityManager = new Battle_Player_Parameter_Manager_Ability($arr); $abilityManager->ATK->addCurrent(100); print $abilityManager->ATK->current;// 88 というふうに、能力(Ability)、耐性(Resist)のパラメータの設定を それぞれManager_Ability、Manager_Resistクラスの中でしてあげればいいのかなと思ったのですが、 hogehoge78さんはこのParameterクラスについてはどのようにお考えでしょうか?
- hogehoge78
- ベストアンサー率80% (433/539)
>targetの選別の仕方に悩んでて、それ専用のクラスを設けて取得しようか考えていました。 targetの選別は、最初のコマンドで選ばせるのが基本で、ソレ以外の敵モンスター、パパスのようなAIの人が 自動的に選ぶ場合の話ですかね。 私の書いた部分っていうのは、Playerクラスがすでにtargetを選択済みであることを前提に書いてあります。 上に書いたような、自動的に選ばせたい場合、TargetOrderクラスを実装する、というのは必要ですね。 で、選ぶのは当然Actorなので、Actorにインターフェイスとして public setTargetAuto($targets){ $order = new TargetOrderXX($this, $targets); $this->targets = $order->execute(); } とかとシンプルにしてやって、TargetOrderという抽象クラスを継承した、具象クラスを作る感じでしょうか。 abstract class TargetOrder{ protected $enemies = array(); protected $buddies = array(); public function __construct(Actor $user, $targets){ //とりあえず、敵と相棒を分断 $group_id = $user->group_id; //ある特定のグループを主人公グループするとし、それ以外を敵グループとする //今回は99を主人公グループとする $isPlayer = $group_id === 99; foreach($targets as $target){ if($isPlayer === true){ if($target->group_id !== 99){ $this->buddies[] = $target; }else{ $this->enemies[] = $target; } }else{ if($target->group_id === 99){ $this->enemies[] = $target; }else{ $this->buddies[] = $target; } } } } abstract public function execute(); } } class TargetOrderEnemyRand extends TargetOrder{ public function execute(){ //死んでる相手は攻撃自体が不可能だと思うので、死んでたら相手にしない。 $enemies = array(); foreach($this->enemies as $enemy){ if(!$enemy->isDead()) $enemies[] = $enemy; } //ランダムに選出 shuffle($enemies); return $enemies; //または全部を返してやる //return array_merge($enemies, $this->players); } } とか。 以前ご質問されていた、Randamで取得したい値ごとにサブクラスを作ってやる、といった内容と良く似ている状態だと思います。 ・味方から選別する ・死んでいる仲間から選出する(ザオラル、ザオリク、世界樹の葉) ・グループ攻撃の場合は一番先頭にソートした敵のグループと同じグループを攻撃するようにすればよい ・完全に混乱状態で、敵味方自分区別なく選出する とか、パッと思いつくだけでもそれなりに区分けしたいTargetOrderは存在しますね。
お礼
ご回答ありがとうございます。 なるほど、たしかにこれもサブクラスとして分けた方がいいですね。 なんか自分としてもコンストラクタ内で分岐処理するのは変な感じしてたのですが、これですっきりしました。 みなさんの意見を参考にしつつ(というかほぼ真似ですけど^^)自分なりに作り直しているのですが、 そこでhogehoge78さんに質問がありまして、 確認なのですがActorクラスというのはPlayerクラスの基本クラス(つまりclass Player extends Actor) の位置づけですよね?ほぼ一緒だと思うのでActorクラスだけでもいいかもしれませんが、 仮にPlayerクラスを設けるとした場合のフォルダ構造に関するアドバイスを頂きたいのですが、 自分としてはこの戦闘処理システムの全体構造として ┬ Battle(戦闘処理関連のフォルダ) │├ Player ││├ Action(それぞれ固有のアクション、ギラ、たかくとびあがる、など) │││├ Abstract.php │││├ Attack.php │││├ Defence.php │││├ Skill.php │││├ Gira.php │││└ Takakutobiagaru.php ││├ State(自分は状態主体で考えてみたいと思います。死、石、毒、石+毒、など) │││├ Abstract.php │││├ Dead.php │││├ Stone.php │││├ Poison.php │││├ StonePoison.php(複合状態は必要に応じて設ける。なくても構いません) │││└ Takakutobiagatteiru.php(なんだこれ^^) ││├ Parameter(パラメータ関係。これについても伺いたいのですが、一度に多く聞くのもあれなので、もしよろしければ後でお願いしたいと思います) │││├ Manager ││││├ Abstract.php ││││├ Ability.php ││││└ Resist.php │││├ Abstract.php │││├ Ability.php(HP、MP、攻撃力など) │││└ Resist.php(ギラや行動封じなどの耐性) ││├ StateMachine.php ││└ Actor.php │├ Player.php(このBattle_PlayerクラスでBattle_Player_Actorクラスを継承する) │└ Team.php(基本的に相手(敵)チームと自軍チームの2つのチームが戦う構想) └ Battle.php(戦闘会場のようなもの) と考えているのですが、(是非はありますが)Playerフォルダを設けた場合に Playerクラスそのものはファルダ外(つまり上位)においてActorクラスはPlayerフォルダ内に置く ということでいいのでしょうか?別にActorクラスはPlayerクラスファイル内にまとめ記述しちゃってもいいかもしれませんが、 自分としてはとりあえず分けて考えたい(以前のZend Framework関連の質問からなんとなく察しがつくかと^^)と思っていまして、 でももしそのようにすると、フォルダとクラス名の規則というかそういうのがあった場合に Battle_Playerクラス Battle_Player_Actorクラス というActorのクラス名がなんか気持ち悪い感じになるというか、でもこれはこれでいいのでしょうか? それともう一つ、Battle.php(戦闘会場)の置き場所もどこにするか迷ったのですが、 これもさっきと同じ理由で、本当はBattleフォルダ内に入れてしまいたかったのですが、そうするとクラス名が Battle_Battleクラス になってしまうというかそういう規則ならなると思うんです。 なのでBattleフォルダ外に設置したのですが、入れてしまった方がすっきりして持ち運びにも便利なんじゃないかぁと。
- lv4u
- ベストアンサー率27% (1862/6715)
>>本題にもあるように状態とアクションの関係を整理したいというかより深く知りたいので 何かいい方法はないか考え中なのですが・・ ラリー・テスラー氏は、手間=複雑性について以下のような法則を述べています。 「あらゆるプロセスには本来備わっている複雑性があり、「臨界点」以降はその複雑性は簡略化できず、移動のみ可能である。」 これは、状態とアクションの関係のプログラムをなんらかの手法で合理化・簡略化を図って、ある程度は簡潔にすることは出来ても、ある限界値を超えると、複雑性は簡略化できず、どこかに移動するだけってことです。 今回の例では、状態遷移表を作成し、ステートマシンで解決する手法は、コーディングは簡単だし、一発でバグ無しプログラムとして動作させることもさほど困難では無いと思います。つまりはプログラミング・デバッグ工程は簡略化される。 けれどステートマシンの考え方を学習するという壁がありますし、それを理解しても、状態遷移表を作成するのは、なかなか大変な作業です。今回の場合は、複数の表に分割しないとうまく整理できない可能性もありますし、その分割がまずいとある状態から抜け出すことが永久に不可能ってことになるかもしれません。(攻撃の種類によっては、その状態になるのが正しいってこともあるかもしれませんが・・・「現世に戻れないテレポート!」って攻撃で、異次元に送り込むと、相手は永遠に異次元をさ迷うとか・・・) 別の例では、C++言語ではSTLってのがあります。このライブラリを理解して使うと、数百行あるコードが十数行で記述でき、しかもコンパイルさえ通れば、最初からほぼ完璧に動作するものが作れたりすることがあります。 でも、それを理解できるまでは、なかなか学習が大変です。 「このSTLで手軽にバグ無しコードとして書ける!」と喜んでも、学習の壁の高さに諦める人も多いと思います。 まあ、「プログラミングに王道なし」ってことで、地道に学んでゆくしかないと思います。 中の見えない容器に水を入れていると、いつ溢れ出すのか分かりません。でも、入れ続けていれば、いつかは溢れ出す。それを信じて入れ続けることですね。(これは、進歩しないで悩んでいるとき、霊界の方より頂いたアドバイスだったりします。)
お礼
ご回答ありがとうございます。 >「あらゆるプロセスには本来備わっている複雑性があり、「臨界点」以降はその複雑性は簡略化できず、移動のみ可能である。」 今の自分の理解力だと当然ながらその本当の真意みたいなのが分からないですが、 そうなるんだということを心に留めておきます。 ちょっとlv4uさんからアドバイス頂いた状態を中心に考える(違うかもしれませんがとりあえず自分はそのように解釈しました^^) から閃いたものがありまして、今現在具体的に作リ直してる最中なのですが、 如何せん能力のなさもあって完成までにある程度時間がかかりそう(その時点でどうなのかなぁ^^)で、 もし完成したら、できれば評価というかそれについてここは違うとかアドバイス頂けたらうれしいのですが、 結構全てだと量もありそうなので、どうお見せすればいいのか考えているのですが、 さすがに「この回答に補足をつける」の空きを利用するわけにもいきませんし、 どこかにアップロードしてURLを貼るのがいいんでしょうか。。。 >「プログラミングに王道なし」 それについては自分が何も言える立場というか技量もありませんが、 要はなにするにしても人(間)が関わってるわけですよね。 例えば、「それめんどくさいなぁ」と思ったら何か楽な方法を探すか諦めるかでしょうし、 共同開発なら他の人と協調しながら作らないといけないでしょうし。 この質問自体も自分(まあ低レベルですけど^^)という人間が、状態とアクションの関係に疑問を持ち、 みなさんからアドバイスを頂くことによって、少しでも疑問が晴れればという思いで立てました(想像通り自分にとって参考になる回答が得られました)
- hogehoge78
- ベストアンサー率80% (433/539)
ちょっと考えてみましたら、私の実装は、間違ってましたね。 PlayerがSpellを実行し、MonsterにSpellを渡す という実装が必要なところを、 PlayerがSpellを実行して、SpellにMonsterを渡すとSpellが実行される とかちょっとわからない状態になってました。その為、状態のフラグが分散してしまうように感じました。 なので、それらの状態のフラグは、すべてAuthor側で処理をする。 ある「道具」を投げる前に自分の必要なパラメータを付加してやって、攻撃力などを調整して、 ある「道具」を受け取るAuthorで、その道具から、自分の防御力などのパラメータから減算を行って ある「道具」による影響を与えてやる。 <?php class Actor{ public function catchElement(Element $element){ //ここで、受付可能かを判断する //攻撃を受け取るActorで判断すれば同じ処理が分散することはなくなる。 if($this->isDead()){ }elseif($this->isInvisible()){ }elseif($this->isReflection() && (!$element instanceof IActionReflection)){ //無敵状態である道具が与えられたので //お返しする。 $this->element = new ReflectionDamage($element); //ReflectionDamageというのは、跳ね返しは全体攻撃でも //攻撃してきた個人に対する攻撃になると思うので、 //IActionSingleインターフェイスとIActionReflection //を与えたElementのラッパーという位置づけにでも。 //必要なら、ReflectionHealing/ReflectionSpecialなどを付けたり? $this->throwElement(); }else{ if($element instanceof IActionDamage){ $this->hp -= $element->getAtk(); }elseif($element instanceof IActionHealing){ $this->hp += $element->getAtk(); } } } public function throwElement(){ $this->element->setActor($this); //実行するのは誰なのかという署名を道具に渡してやる。 if($this->element instanceof IActionSingle){ $this->targets[0]->catchElement($this->element); }elseif($this->element instanceof IActionGroup){ $current = $this->targets[0]; $group = array($current); foreach($this->targets as $target){ if($current !== $target && $current->group_id == $target->group_id){ $group[] = $target; } } foreach($group as $target){ $target->catchElement($this->element); } } //とかといった感じ。 } public function setAction($select_id, $targets, $sub_id){ //ここの振り分けの実装は一緒 $this->element = $element; //次回アクションに必要な「道具」を渡してやる $this->targets = $targets; //ターゲットは誰か、を選択するのはAuthor $this->addParameter(); //受け取った道具に自分のステータス値から値を付加してやる処理 } public function doAction(){ if($this->canAction()){ //アクション可能なら $this->throwElement(
お礼
ご回答ありがとうございます。 なるほど、そのやり方もいいですね。catchElementとかthrowElementとかの考え方が ちょっと今の自分には思いつかないというか斬新だったので参考になりました。 自分も(は)targetの選別の仕方に悩んでて、それ専用のクラスを設けて取得しようか考えていました。 例えば、 class TargetOrder implements Iterator { const ALIVE_OPPONENT_RANDOM_ONE = 0;// 生きている敵の誰か const ALIVE_OPPONENT_ALL = 1;// 生きている敵の全て const OPPONENT_ALL = 2;// 敵の全て //など protected $owner = null; protected $position = 0; protected $array = array(); public function __construct($owner, $type = self::ALIVE_OPPONENT_ALL) { $this->owner = $owner; switch ($type) { case self::ALIVE_OPPONENT_RANDOM_ONE: $players = $this->owner->getTeam()->getOpponents()->getAlivePlayers(); $this->array[] = $players[mt_rand(0, count($players) - 1)]; break; case self::ALIVE_OPPONENT_ALL: $players = $this->owner->getTeam()->getOpponents()->getAlivePlayers(); $this->array = $players; break; case self::OPPONENT_ALL: $players = $this->owner->getTeam()->getOpponents()->getPlayers(); $this->array = $players; break; } } public function current() { return $this->array[$this->position]; } //各種、Iteratorに必要な処理 } どうもこのやり方だと悪そうですね・・・。最初から死んでいる状態のプレーヤーを除去して選ぶよりも hogehoge78さんのやり方のように、その都度死んでるか確認(if($this->isDead()){)した方が良いというかそうしないと駄目ですね。
- hogehoge78
- ベストアンサー率80% (433/539)
基本的な現存する状態は、全部Actorクラスにそれぞれフラグ立てておく必要があるかと思います。非常に面倒くさいですが、それってそういうものなのかなと思います。 毒、麻痺、鉄の塊(これはつまりアストロンのことでしたか)、しに、気絶・・・・などなど それぞれ、is○○メソッドつくっておくしかない気もします。 どちらにしてもそれぞれでそれぞれ別のメッセージも必要になると思いますし。 また、ソレが簡単に出来ないから、lv4uが、ステートマシンという物を教えてくれた訳ですね。 なので、私の考えでは、takagoo100さんの書かれた実装と概ね一緒です。 まぁ、今回は「じゅもん」、「とくぎ」の事に関するものだしドラクエなので、攻撃を受け付けるか受け付けないか、の二択だし、 普通にif文で振り分けてもそこまで複雑にならない気もします。 class SpellDamage extends Spell implements IActionDamage{ public function execute(){ $target = $this->targets[0]; if($target->isReflection()){ //跳ね返し処理 }elseif($target->isDead()){ //死んでる場合 } } } で、考えてみると、このままでは、その「じゅもん(またはとくぎ)」の実行者のステータスに関わる処理が全く出来ないので、 setActionメソッド中に、 public function setAction(.....){ ・・・・・・ $action->setTargets($targets); //と一緒に $action->setActor($this); //とか自分自身も渡してやる必要がありそうですね。 } ただ、この実装をすると今度は、Spellのexecuteメソッド中でdoActionとか間違えて実行すると永久ループになったりしそうでもありますので、クラスActorとは別に、クラスActorStatusをつくって、Actorクラスに内包させる、とかそういう事が必要なのかもしれません。 具体的な処理に関しては、今回の私の回答は、抽象度の高い外側として、そのようになっていたら割とわかりやすいんじゃない?といった程度の内容なので、きっちり仕様を把握していかないと上手く落とせないですね。 >自分もGoFのデザインパターンを一通り見たことがあるのですが、実際に応用となると自分には難しいですね・・・ 自分で振っといてなんですが、私はあまりデザインパターンを勉強したことがなく、 各種フレームワークやライブラリ等のソース読んでみて、なんとなく手法を把握しただけです。すみません。
お礼
ご回答ありがとうございます。 え?跳ね返しの判定はそれぞれのアクションクラスの中に散らばせて記述するやり方なのですか? たしかに跳ね返しはそれぞれ処理が微妙に違うでしょうしね。例えば一番分かりやすい例だと 通常攻撃の跳ね返しのメッセージは「~は~の攻撃を跳ね返した」 ギラ魔法の跳ね返しのメッセージは「~は~ギラ(呪文)を跳ね返した」 なので個別に処理しなければいけないかもしれません。ただせっかく(本来そういう目的じゃないのかもしれませんが) IActionDamage(あるいはIActionReflectacle)インタフェースを実装しているので、 どこか(doActionとか)一箇所で記述できれば効率がいいというか楽だと思うんですけどどうなんだろう。。 例えば状態なんですけど、死の状態って状態の中でも最も特殊な状態で、 死んだら他の全ての状態が解除されると思います。ちょっと細かいことですけど if($target->isReflection()){ //跳ね返し処理 }elseif($target->isDead()){ //死んでる場合 } ではなくて if($target->isDead()){ //死んでる場合 }elseif($target->isReflection()){ //跳ね返し処理 } だと思います。 つまり何が言いたい(間違いを指摘することではなく)かというと、そんなに状態の種類は多くはないかもしれませんが やっぱり優先順位というものがあって1個や2個の数ならそんなに気にするようなではないですが 多くなるにつれ厳しくなるので、なにか状態専用の仕組みというか決まりごとが欲しいですね。 たしかに戦闘システムを作ることが目的でもあるのですが、 本題にもあるように状態とアクションの関係を整理したいというかより深く知りたいので 何かいい方法はないか考え中なのですが・・
- hogehoge78
- ベストアンサー率80% (433/539)
書ききれなかったので追記します。 >継承を使うことによって、共通の部分はできるだけ省略したいなぁ(こういう考え方が駄目なんでしょけど・・・) と思って、ギラにしろイオナズンにしろダメージの大きさが違うだけで後は全て(メッセージや状態に即したダメージの与え方など)同じなので。 ここの部分は、抽象クラスとしてある程度大雑把な群をつくってやっても良いと思います。 SpellDamageFireとかといった炎系の群(特定の魔法が効きづらい敵とか防具とかってありましたよね。)みたいな。 ただ、ソレ以外のほとんどの魔法と特技は、何か特定の状態に作用するものが多いので、結局具象的なクラスは必要になると思います。 SpellSpecialRula(ルーラ)とか、SpellSpecialBaikiruto(バイキルト)とか。 とりあえず私の思いつくことはこんなところです。ただ、ここまで色々実装をクラスを使ってするなら、GoFのデザインパターンとか勉強したほうが良さそうですね。もっと効率のいい実装がある気がしました。
お礼
ご回答ありがとうございます。 いいですね。さすがに当然ですけど自分の考えてるやり方よりしっかりしてるというか(っていうか自分のが駄目すぎるってのもあるんですけど・・) とにかく参考になりました。ちょっとそのやり方をベースに自分も作り直した方がいいですかね。。 ただいくつか疑問があるんですけど、 >それぞれのActorの中で、毒フラグとか麻痺フラグとかそれぞれの状態のフラグを全部書いてやって >public function execute(){ //ここらへんで状態フラグとか確認して、 //条件にあったターゲットに とあるのですが、状態は複数絡み合っていたとしてもifなどで分岐して判断していくやり方ということでしょうか? 例えば、石(正確には鉄の塊でしたね^^めんどくさいので石にします^^)+毒+麻痺の状態の場合に、 プログラム的に一番優先されるのはたぶん石の状態で、 例えばメッセージなどで「石になっているので動けない」というふうに表示されアクションができないと思います。 つまり毒+麻痺の状態でもあるんですけど、最優先に判断されるのは石の状態なんです。 自分としては、それぞれの状態に優先順位を付けようか考えていたことがあって、そうすることにより なるべく条件分岐を行わず処理できるんじゃないかなぁと理想を抱いていたことがあります^^ それと跳ね返しアクションではなくて跳ね返し状態(うーんややこしい・・)での疑問で、 確認なのですがhogehoge78さんのやり方だと相手が跳ね返し状態だとすると、 public doAction(){ if($this->action instanceof IActionDamage){ //ここで跳ね返し状態の判断 if ($this->targets[0]->state === 'Reflection') { //跳ね返しの処理(ここにhogehoge78さんが考えいる具体的なやり方を記述して頂けると助かります) } else { $this->action->execute(); } } else { } というやり方ですよね?つまりそれぞれのアクションクラスの中に跳ね返し処理を散らばせて記述するわけではないですよね? class SpellDamage extends Spell implements IActionDamage{ public function execute(){ //ここで跳ね返し状態の判断 if ($this->targets[0]->state === 'Reflection') { } else { $this->damage($this->targets[0]); } } } >ただ、ここまで色々実装をクラスを使ってするなら、GoFのデザインパターンとか勉強したほうが良さそうですね。もっと効率のいい実装がある気がしました。 困り度がこれで言うのもあれですが、この問題ってすぐ解決できるような感じじゃないと思うんです。 なので自分としてはhogehoge78さんの>もっと効率のいい実装がある気がしました。 に(まことに勝手ですが)期待したいのですが、これも勝手ながら気長に待ちたいです。 自分もGoFのデザインパターンを一通り見たことがあるのですが、実際に応用となると自分には難しいですね・・・
- hogehoge78
- ベストアンサー率80% (433/539)
私の書いてるテーブルとレコードは、普通にSQLiteとかMySQLのテーブルとレコードのことです。 今回の目的は恐らく、どうやってやったら作れるか、っていうイメージを実際実装して、試してみたい、だとおもうので、 データ管理をする倉庫としては、WebアプリではおなじみのRDBMSで考えると分かりやすいかなと思います。 そして、 オブジェクト指向って、多分、名詞(クラス)があって動詞(メソッド)があるんだと思うんですが、 名詞が、○○を動詞する っていうパターンを考えると、 データベースのテーブルから抽出されるレコードを収めるクラスは、抽象クラスObjectであり、 そのなかで細分化され、 抽象クラスActorを継承したPlayerとMonsterがあって、doActionメソッドとsetActionメソッドを持たせます。 例)Playerが○○をdoActionする つまり、何かの別の要素を実行出来るクラスです。 尚、setActionメソッドは、 <?php public function setAction($select_id, $targets, $sub_id){ //$select_idは、選ばれたコマンドのID(たたかう、じゅもん、とくぎ、どうぐなどを連番として) //$targetsは、前回Actor $targetだったんですが、indexが最初の物を初期ターゲットとして出現するActor全部を渡してやれば //例えば、ターゲットがすでに「しに」だった場合にとなりのActorに攻撃を与えたいとか、 //グループ攻撃、全体攻撃をするじゅもんなどで扱いやすくなりそうです。 //$sub_idは、具体的な、じゅもんとかとくぎの種類の管理ID(1=>メラとか、2=>ピオリムとか。) } ?> 抽象クラスSpell(呪文)を継承したSpellDamage,SpellHealing,SpellGroupDamageなどがあって、これらは、ある特定のアクションを行う制約を設けます。それらを例えばIActionDamage,IActionHealingといったインターフェイスです。 同様なネーミングで、SkillとかItemもつくって、大雑把な実装である共通のインターフェイスを設けると。 そうすると、 Playerが(setAcitonした)SpellDamageをdoAction(SpellDamage::execute)する ことになりそうです。 Spell,Skill,Itemはそれぞれ全部executeという大雑把な実行メソッドを必ず持たせるとして、 interface IActionDamage{ protected function damage(Actor $a); } abstract class Spell extends Object{ public function execute(); } class SpellDamage extends Spell implements IActionDamage{ public function damage(Actor $a){ $a->subHP($this->atk); //atkという攻撃量のメンバ変数をActorクラスに実装してあるHPを減算するメソッドに与えてやる } public function execute(){ $this->damage($this->targets[0]); } } といったイメージです。状態管理に関しては、それぞれのActorの中で、毒フラグとか麻痺フラグとかそれぞれの状態のフラグを全部書いてやって、確認出来るようにすればよいと思いました。 また、Reflection(跳ね返し)とか、Jamp(飛び上がる)とかといった、特殊行動も、 IActionSpecialとかといったインターフェイスで、specialというメソッドの実装をかけてやり、 class SkillSpecialReflection extends Skill implements IActionSpecial{ public function execute(){ //ここらへんで状態フラグとか確認して、 //条件にあったターゲットに $this->special($this->targets[$i]); //メソッドを適用して、 } public function special(Actor $a){ //具体的な処理内容を書く $a->setFreezTurn(3); //3ターン凍結とか //といったことをゴリゴリ書く } }
お礼
ご回答ありがとうございます。 なるほど、やはりそういう感じになりますか。参考になりました。 例えば(何でこんな例しか思いつかないんだろう・・)、 「筋肉が増した」状態(攻撃力がアップ) 「足怪我」状態(守備力がダウン) があってその2つとも所有していた場合、 「筋肉が増した」+「足怪我」(素早さがダウン) の新たに第3の状態(というかアクションに付加する処理)ができたとします。 これは素早さがダウンなので、名前的には前の2つの状態をあわせた名前ですが 付加処理が違うので、前の2つの状態の付加処理も行ってもいいと思うんです。 なんかこういう感じでやっていけばいけそうな気がするんですけど、まあでも大変ですよね・・・^^ ところでlv4uさんは何かお勧めの書籍などはありますでしょうか? できればプログラム関係でお願いしたいのですが