- ベストアンサー
状態とアクションの関係についての設計方法
- PHPの環境で状態とアクションの関係を設計する方法について詳しく説明します。
- 状態を基準にクラスを設計する方法と、アクションを基準にクラスを設計する方法の違いについて考えてみましょう。
- RPGの戦闘のシステムを設計する際に難しい要素や状態の絡み方についても解説します。
- みんなの回答 (40)
- 専門家の回答
質問者が選んだベストアンサー
>>この戦闘処理の状態を状態遷移図みたいなので表すとすると 複合状態(毒+石)も一つの新たな状態として存在することになるのでしょうか? それは、その世界内でのルールしだいだと思います。現実の世界では、「そういうケースは有り得ない」という前提条件で設計していても、何千回、何万回に1回だけど、ありえないとされる事象が発生するとか、パーツである基盤の仕様が間違っていて、有り得ないパターンが発生したりで「システムが停止したぞ!」というような苦情となったりします。 ゲームでは、設計者は「神様」ですから、その複合状態を有り得ないことにするのも、それを認めてそこからの回復条件をちょっと難しくすることも自由だと思います。 ですが状態遷移表を作る場合は、その複合状態も考慮して作成します。 >>つまり毒状態とも石状態とも違う毒+石という新たな状態。 もちろん複合状態もありだとすると、凄い数が増えることになると思うのですが そのとおりで、状態は掛け算で増えてゆき、凄い数になります。ちなみに○と矢印で記述する状態遷移図は、比較的簡単に書けるのですが、これはイベントを全て考慮しない図も書けるので、その場合バグの要因になります。 イベント(状態が変化する原因)とステータス(状態)をマトリクスにした状態遷移表の場合は、そういう抜けは発生しませんが、かなりでかい表になってしまいます。 それを防ぐには、状態遷移表を分割するわけですが、その表分割が不適切だと当然ながらバグ要因になりますね。 >>自分としては「毒+足怪我状態なので攻撃力が大幅に低下した」みたいな付加処理を 多種多様なパターンとして設計してみたいという欲望^^がありまして。。 ステートマシンで作っていれば、その付加処理の状態を「ありえない」としておいて、あとからそういう付加処理を追加するのはとても簡単ですしバグもたぶん発生しないでしょう。そのかわりに、初期段階の設計は複雑になりますね。
その他の回答 (39)
- lv4u
- ベストアンサー率27% (1862/6715)
>> RPGとして町を歩いたり敵との遭遇の仕方はどうするとかは考えていません。 とにかく全自動で戦闘が行われるような感じです。 全自動で戦闘するプログラムであろうとも、その仕様を変えて機能アップするために拡張性を重視すると思うか、とりあえず1回でも全自動で動作すれば自己満足してコードをお蔵入りさせるのか?ってことで、どのやり方で設計するかを判断することになるのではないでしょうか? >> >>自分も本家の戦闘処理はこんな作りしてないだろうなぁとは思っているのですが、基本的な作り方さえも分からない状態なので、どう アプローチしていけばいいのか試行錯誤の状態です・・・ オブジェクト指向でアプローチすれば、プレイヤー・クラスや戦場クラスを作成し、それぞれ必要な数のインスタンスを生成して、適切なパラメータをインスタンスに与え、それぞれのインスタンスでメッセージ(呼び出し)をやりとりして、戦いを行わせるって感じになるのかもしれませんね。 それぞれのクラスのロジックやパラメータ調整をすることで、自分では予想もできない戦闘結果が得られて面白いかも? インタープリタのRubyを使って、自己変更プログラムにし、プレーヤが成長していくってのも面白いかな? ちなみに、市販ゲームは、ロジックや各種パラメータ値の調整による戦闘のバランスを出荷前のテストプレイで調整されているようです。ロジックやパラメータの修正と再テストを短時間で行うために、コンパイル言語だと手間がかかるのでゲーム専用のスクリプト言語を開発して使用しているとどこかで目にしたことがあります。
お礼
ご回答ありがとうございます。 どうせ作るなら拡張性も重視したいのですが、今現在の自分の理解力だと じゃあその為にはどう設計(というか考え方)すればいいのかが見えてこないんです。。 自分の今思ってる範囲だと、例えば何か新しい特技を追加した場合に、 ifで他の条件と絡み合うような形でどんどん複雑になっていくような スパゲッティ状態になるやり方はできれば避けたいと思っています(別にifやcaseで 判断することが悪いって言ってるわけではなくて、それこそ正当なやり方であるとは思うのですが なにかもっと楽って言ったら変ですけど整理されたやり方があるんじゃないかと) ただ今考えていて、石になる状態というのがあるのですが、 石になった状態だと他の状態(例えば足を怪我しただとか毒とか)を所有していても 当然石になった状態が優先されて行動ができなくなります(正確(分かり難く^^)に言えば行動ができないという行動を実行することになる) これは確認済みなのですが、先に毒とかの状態を所有していれば石になったとしても毒の状態は保持したままです。 さすがにこれは 毒+石の状態クラス{ } みたいなやり方でやるのは無理があるし、石の場合は状態クラスのデフォルトの処理が通用しないので 条件分岐しないといけないのかなぁと思ったり。。 やっぱり複数の状態が絡むとまだどうやっていいか分からないことがあります・・・
- bm_hiro
- ベストアンサー率51% (200/388)
すみません。回答ではありません。 俺が書いた#5と#6は無視する方向でお願いします。補足も必要ありません。 「根本的に設計に間違いがある」という意見は変わりませんが、「classで挑戦したい」というのならアリだと思いますので、頑張ってください。 >>#4 COBOLとかGOTOとか レトロな単語が出て来てて、軽く釣られました。(●´ω`●)
- hogehoge78
- ベストアンサー率80% (433/539)
この手の処理はやったことがないので、どうかなと思いつつ、アイデアだけ。 少なくともどの様なシステムでもなんかしらのストレージのような物から基本情報を取り出してきて、その取り出してきた値に対して味付けしていくものと考えれば、普通のRDBMSで考えるのもそんなに大差ないんじゃないのか、と考え、 $players = $this->PlayersTable->findByActive(); //現在のパーティー面子を取得 と例えばした場合に、実際には、 id, username, maxhp, maxmp, hp, mpとかからなるレコードが取得されてきて、ソレをあるオブジェクトに突っ込む。 で通常、Webアプリであれば、コレはレコードのオブジェクトとして、それぞれのセッターとゲッターしか持たないものに、さらに小さなコントローラのようなものを付加してやる。 class Player extends Actor{ public function addHP($num){ $this->hp += $num; } public function subHP($num){ $this->hp -= $num; } public function setAction($action_index, Actor $target_actor, $sub_index){ //action_indexに、「たたかう、じゅもん、どうぐ、とくぎ」の識別子 //target_actorに、Actorクラスを継承するモンスターかプレイヤーを //sub_indexに、「じゅもん、どうぐ、とくぎ」の具体的なもののIDを if($action_index == 1){ //たたかう $this->acton = new NormalAction($target, $this->atk); //自分の攻撃力でも。 }elseif($action_index == 2){ //じゅもん $this->action = $this->SpellTable->findById($sub_index); //じゅもんテーブルからオブジェクトを抽出 }elseif()・・・・以下続く //なお、じゅもんテーブルやとくぎテーブルから返されるオブジェクトにも何らかのインターフェイスで制約だけ付けてやれば //下記のような振り分けの際にも扱いやすい、ですかね・・・ } public doAction(){ //ここらへんがコントローラ的なもの if($this->action instanceof SpecialAttack){ //特技で if($this->action->isSelf()){ //自分に作用を与える内容であれば $this->action->update($this); //自分自身に付加要素を与えてみたり? } } } } とか、といったレコードオブジェクトを取得してきてやり、同様に $monsters = $this->MonsterTable->findByRandom($field_id); とかといったオブジェクトで、やっぱりActorクラスを継承して同じようなオブジェクトを返してきてやり、 最終的に、 $actors = $this->mergeSortSpeed($players, $monsters); とかやってやった後、 foreach($actors as $actor){ $actor->doAction(); } とかでぶんまわして、戦闘が終了するまで繰り返し、最後に、 戦闘が終わったら戻るステータスとかを適当に調整し、経験値とかを付加したら、 $this->PlayerTable->update($players); とかで、テーブルにアップデートを掛けてやる とかできたら、いいかな、と思いました。 結局Actorの継承クラスに記述するdoActionが処理が煩雑になりはするんですが・・・・ 「飛び上がる」といった行動に関してはもっと大雑把に、 ・あるターン数無敵状態になる ・その間、基本攻撃力に攻撃力を付加する ・準備が完了したら普通の攻撃行動を行う という内容と考えられると思うので、そんなに深く考えることはないのかなぁと思います。 最初に書いたとおり、作ったことも、考えたこともない素人のアイデアなので、斜めに読んでください・・・
お礼
ご回答ありがとうございます。 じゅもんテーブルというのがよく分からないですが、オブジェクトを抽出とあるので なんらかのクラスにしてるということですよね? 例えば、今まで自分が考えていたやり方ですけど、 // 通常(打撃)攻撃のクラス class Action_Attack { } // 特技の基本クラス class Action_Skill { } // ダメージ系の特技の基本クラス class Action_Skill_Damage extends Action_Skill { } // 1匹の敵にダメージを与える特技の基本クラス class Action_Skill_DamageOne extends Action_Skill_Damage { } // 全ての敵にダメージを与える特技の基本クラス class Action_Skill_DamageAll extends Action_Skill_Damage { } // ギラクラス(全て(1グループだったかな?)の敵にダメージを与える特技です) class Action_Skill_Gira extends Action_Skill_DamageAll { } みたいな感じで継承を使うことによって、共通の部分はできるだけ省略したいなぁ(こういう考え方が駄目なんでしょけど・・・) と思って、ギラにしろイオナズンにしろダメージの大きさが違うだけで後は全て(メッセージや状態に即したダメージの与え方など)同じなので。 ただこれだとさっきも言ったように、跳ね返しの処理というはAction_Attackの中でも起きますし、 いろんなクラスに跨る処理で、でも継承というのは1箇所からしかできないので、 インターフェイス(Action_Reflectable)を使うことにより、例えばStateManagerでまとめて処理するやり方がいいのかなぁと思ったり。。。 hogehoge78さんが考えるじゅもんテーブルのクラスの大まかな実装で結構ですので、 2,3個種類の違うクラスを記述して頂けないでしょうか?
- bm_hiro
- ベストアンサー率51% (200/388)
本家のドラクエモンスターズを知らないので、補足お願いします。(; ̄ー ̄A ・プレイヤーは自分だけ?複数のパーティ? ・敵は一匹?団体さん? ・特技で高く飛び上がってからの~(通常攻撃 or 特技攻撃 or 回復)とかの分岐はある? ・攻撃順はタイムライン制?(この攻撃を行った後は何秒のインターバルが必要とか)、将棋みたいなターン制? ・敵の攻撃を真似する時に、自分には出来ないスキルを敵が使ってきた場合は? ・跳ね返しは、単純にプレイヤーが食らうはずだったダメージを相手に与えるもの?プレイヤーのパラメータによって与えるダメージは変わる? そこまで考えなくていいよ。的な所まで気になったのは ご容赦ください。
補足
補足ですが、 ・プレイヤーは自分だけ?複数のパーティ? 複数のパーティです。 敵というか、相手も自分(味方)と同じモンスター同士なので 2チーム(teamA、teamB)に分けて戦う感じです。 自分やり方では、 $arr = array( array( 'name' => 'teamA', 'players' => array( array( 'name' => 'player1', 'color' => 'red', 'actions' => array('attack', 'takakutobiagaruSkill'), 'states' => array('reflect'), 'parameter' => array( 'ability' => array( 'HP' => 100,'MP' => 20,'ATK' => 44,'DEF' => 10,'INT' => 300,'AGI' => 20,'LUK' => 33, ), ), ), array( 'name' => 'player2', 'color' => 'white', 'actions' => array('defence', 'defence'), 'states' => array('sleep', 'poison'), 'parameter' => array( 'ability' => array( 'HP' => 130,'MP' => 0,'ATK' => 4,'DEF' => 1,'INT' => 30,'AGI' => 50,'LUK' => 3, ), ), ), ), ), array( 'name' => 'teamB', 'players' => array( //文字数の関係で省略します。 ), ), ); こんな感じで2匹同士2チームのデータを用意して、 TeamAオブジェクト、TeamBオブジェクトを作り それにTeamA_player1オブジェクト、TeamA_player2オブジェクトなど作って代入して あとは素早さなどで攻撃する順番を決めます。とりあえずアクションが2つまでしかないので2ターン用のテストですが。 後々アクションもその都度自動で選択されるように変更していきたいと思います。 ・特技で高く飛び上がってからの~(通常攻撃 or 特技攻撃 or 回復)とかの分岐はある? たぶんないと思いますが、高く飛び上がった状態でアストロン(味方全員が石になる特技)をかけたらどうなるんだろう?^^と 思って実機で試してみたのですが、次のターンに石になって落ちてきました^^ ・攻撃順はタイムライン制?(この攻撃を行った後は何秒のインターバルが必要とか)、将棋みたいなターン制? 将棋のようなターン制です。 ・敵の攻撃を真似する時に、自分には出来ないスキルを敵が使ってきた場合は? おそらく、真似できないか「しかし、何もおこらなかった」かどちらのメッセージが表示されて何もしないと思います。 ・跳ね返しは、単純にプレイヤーが食らうはずだったダメージを相手に与えるもの?プレイヤーのパラメータによって与えるダメージは変わる? これもおそらくですけど、例えばザキ(一撃死の特技)の体制が最高(つまり効かない)場合は、跳ね返えさないと思います。 ちょっと後で確認してみます。 >そこまで考えなくていいよ。的な所まで気になったのは ご容赦ください。 いや自分もそうなんですけど、こういうことの疑問があったままだと作れないですよね・・・ 結構システム(プログラムの構成)に大きな影響を与えそうですので。 試せる環境にあるので、何か他に気になったところがあれば時間が許す限り調べてみます
- bm_hiro
- ベストアンサー率51% (200/388)
今までの回答の流れとか、完全にスルーしちゃってますので、聞き流しでお願いします。 > そもそもこれはRPGの戦闘のシステムを自分なりに設計してみたいと 主に、コレに対する回答です。 あくまで俺の意見ですが、根本的に設計に間違いがある気がします。 毒や足の怪我(鈍足)は「状態異常」であり、それが攻撃の際に どのように影響するかは、「その状態の時の攻撃は通常の攻撃の時より与えるダメージが減少する」という考え方の方が良いかと思います。 毒の場合は 通常の与ダメから20%引いたダメージを与えることが出来る。とかです。 パラメータの持ち方次第ですが、毒の場合はSTR(腕力)から何%引いて計算するとか、鈍足の場合はAGI(敏捷性)から何%引いてHIT率を計算するとか、いろいろです。 更に毒の場合、DOT(ダメージ オーバー タイム。時間ごとにダメージを食らう)のがあるのが一般的かと思います。 30秒の間、5秒間隔で10のダメージを食らう。とか。 炎系の攻撃を受けた場合も、同様で炎上のDOTがあります。 他にも、「インデックスがスペルインターセプトしてるから魔法は使えない」(分かる人にしか分からないネタ混入)的な「詠唱不可」などの状態異常もあります。 各スキルに関しては、完全に個別の処理が必要です。 ・2回行動 →一回行動後、また自分へ戻す。 ・跳ね返し →敵にダメージを返すので、完全に逆の処理。 ・敵の行動を真似する →敵の行動を変数とかに保存しとかなきゃいけない。 ・次のターンまで必要とする(たかくとびあがる、など) →最初のターンのときに、次のターンまでの待機状態をはさむ。 ・攻撃でもない回復でもない攻撃補助でもないジャンル分けし難い特技 →内容によります。 大昔に、遊びで似たようなものは作ったことがあります。(`・ω・´)
お礼
ご回答ありがとうございます。 >毒や足の怪我(鈍足)は「状態異常」であり、それが攻撃の際に どのように影響するかは、「その状態の時の攻撃は通常の攻撃の時より与えるダメージが減少する」という考え方の方が良いかと思います。 自分で言うのも変ですけど、たぶんそのようなやり方(つまり正しいやり方)なんだろうなぁと思います。 ただNo.4でlv4uさんも仰ってた(自分の解釈が間違ってるかもしれませんが・・)ように、 >状態として分けたほうが最初は大変ですが、拡張するときの作業は容易になりますし、 自分も状態として分けたほうがなんにでも応用が利きそうな気がするんです。 まだあれこれ考え方が変わっている最中の試行錯誤の段階ですのであくまで気がするっていう程度ですが・・・ >・跳ね返し >→敵にダメージを返すので、完全に逆の処理。 すいません、もしよろしければこの部分の処理だけというのはあれかもしれませんが 具体的にその周辺も含めてプログラムで記述して頂けないでしょうか? 自分が作成したやり方だとそれぞれのアクションクラスのベースクラスにreflectAction(跳ね返す処理をする関数)を設置して public function reflectAction($target) { if ($target->getStateManager()->has('reflect') && $this->canReflect === true) { Battle::addMessage($target->name . 'は' . $this->owner->name . 'の攻撃(ここにはそれぞれのアクションに応じたメッセージ内容が書ければ理想です)を跳ね返した', 3); $class = get_class($this); $obj = new $class($target); $obj->canReflect = false; $obj->update(); } else { $this->postReflect($target);// ここにはそれぞれのアクションの本処理(ダメージを与えるであったり、眠らせるであったり) } } 跳ね返した場合にアクションクラスの所持者を変えて(この場合は攻撃対象のプレーヤー)インスタンスを作成して 同じアクションを実行するというやり方です。
- lv4u
- ベストアンサー率27% (1862/6715)
>>ただ自分的にこの例の場合、状態遷移図などを使ってまでやるべき(他の方法でできるなら)ことなのか分からない それは、どこまでプログラムを発展させるつもりか?でしょうね。とりあえずRPG的なシンプルプログラムが動作すれば満足なら、現在理解できる方法で作成すれば良いと思います。ただ、使ってみて自分や他の方が「ここをこうすればもっと良くなるんじゃない?」など機能アップの要望が出て、それに答えていく可能性があるなら「急がば回れ」で理解しておくほうがいいと思います。 >>こういう風に考えると凄い複雑になりそうな気がするんですけど、Stateのベースクラスかなんかにデフォルトの行動を用意してて特殊な処理になる場合にだけそういう状態クラスを用意してその特殊な処理のアクションを記述すればいいのかなぁと思っていますがどうなんでしょう? 毒と足の怪我の状態について、それらを状態として分けるか、それとも、飛び上がるアクション処理の中で、case文で分けるか?は判断が難しい点です。状態が増えれば状態遷移図を考えるのが難しくなりますし、シンプルさが失われます。 なので、これも前記の判断と同様に、「どこまで発展(複雑化)するか?」という予想から判断することになると思います。 当然ながら、状態として分けたほうが最初は大変ですが、拡張するときの作業は容易になりますし、パターン抜けによるバグの入る可能性もゼロに近くなるでしょうね。 それから、オブジェクト指向が流行った弊害として、「なんでもクラスにすれば解決」なんて安易な考え方が一般的にあるように思えます。クラス化して継承などしてゆくと、「継承による事実上のGOTO文クラス」というようになると思います。 余談ですが、一部のブログで書かれているように、クラス中心のJavaは一部の大規模システムだけで使われるCOBOLのような古めかしい言語になるのでは、と感じています。
お礼
ご回答ありがとうございます。 >どこまでプログラムを発展させるつもりか?でしょうね。 自分としてはこの戦闘システムの仕組みだけ知りたいと思っていまして、 RPGとして町を歩いたり敵との遭遇の仕方はどうするとかは考えていません。 とにかく全自動で戦闘が行われるような感じです。 >それから、オブジェクト指向が流行った弊害として、「なんでもクラスにすれば解決」なんて安易な考え方が一般的にあるように思えます。 自分も本家の戦闘処理はこんな作りしてないだろうなぁとは思っているのですが、 基本的な作り方さえも分からない状態なので、どうアプローチしていけばいいのか試行錯誤の状態です・・・
- hogehoge78
- ベストアンサー率80% (433/539)
Webプログラムとだいぶ畑が違う気もするので、はずしているかもしれませんが・・・ 基本は、やっぱりMVCなんじゃないですかね。(takagoo100さんは、Zend Frameworkを習得中でしたよね?) Controllerでアクションを振り分けて、Modelで演算したりデータベースからデータを引っ張るということを行うのと同様だと思います。 と、考えれば、doAction(Controller)が実行される前に保存されているステータス($_SERVER変数に与えられるようなものだとか$_SESSIONに与えられるようなものだとか)から、Modelでfindメソッドで、最適な次に行えるアクションを絞り込んで、絞り込まれた中から結果を出す、とかといったことになりませんかね。 Webアプリで考えると、 ・戦闘画面に到達する→ButtleController→indexアクション ・indexアクションで、一定またはランダムにモンスターを選出→セッションに保存 ・indexアクション到達した時点のキャラクターのステータスをモデルからまたはSESSIONから取得 ・行動を決定させるフォームを出力→POST ・POST先のButtleController::buttleアクションを実行 ・POSTされてきた各キャラクターに対するアクションを実行(実行の演算はモデル) ・このとき次のターンで作用するものは、Modelに次のアクションの予約として保存。 ・その後(またはその前)にモンスターのアクションを実行(これもMonsterModelとかで実行する) ・結果を画面出力し、また行動を決定させるフォームを出力→POST ・・・・終わるまで繰り返し とかとなるのでしょうか。Modelに演算させるところが結局面倒そうですが、見通しが良くなりそうですが、いかがでしょう。 一回、自分の作ったことのあるものに置き換えてみると、少し見通しよくなるかもしれません。
お礼
ご回答ありがとうございます。 >このとき次のターンで作用するものは、Modelに次のアクションの予約として保存。 なるほど、もう少し自分なりに考えを整理する時間が欲しいのですが 自分の悩んでいた部分にヒントを与えてくれたような気がします。 hogehoge78さんはというかこのジャンルだとみなさんそうなのかもしれないですが、 このようなRPGの戦闘処理についてあまり考えてみたことがないと思うので お願いし辛いというのはあるのですが、 ただ当然ですが自分より基本的な考え方がしっかりしてるでしょうし 何かアドバイス頂ければと思います。 ※これまでの開発途中の内容のメインの部分です。lv4uさんのアドバイスも頂いたので少し(大幅に?)変えようかと思っています。 $players = self::getPlayersOrderBySpeed($teams); // ここで素早さ順にプレーヤーを並び替える for ($turn = 1; $turn <= 30; $turn++) { self::$turn = $turn; Battle::addMessage($turn . 'ターン目'); foreach ($players as $player) { $player = $player['object']; $player->getStateManager()->preAct(); if (!$player->getStateManager()->canNotAct()) { if (!$player->isDead()) { Battle::addMessage("player=".$player->name, 1); foreach ($player->getActions() as $action) { $player->act($action); } } } else { Battle::addMessage($player->getStateManager()->getCanNotActMessage(), 4); } $player->getStateManager()->postAct(); } foreach ($teams as $team) { foreach ($team->getPlayers() as $player) { $player->getStateManager()->update(); } } } 最後に戦闘内容のメッセージを表示するような仕組みです。 今までのアドバイスを頂いて迷いがなくなった(今後どうなるかは分からないですけど・・)箇所は、 状態を中心に考えていけばいいということで、例えば「たかくとびあがった」というのはアクションですけど その後は「とびあがった状態」になるわけですよね?その状態にカウントのようなものを設けて0になったときに 次のアクション(対象に向かって急降下して攻撃する)を指定してあげればいいかと。 ただ他にもいくつか迷っていることがありまして、「跳ね返し」という特技で これって、打撃でのダメージ攻撃、特技でのダメージ攻撃、特技での補助攻撃(守備力を下げるとか) ほぼ全てに跳ね返しができるというふうに設定すると、アクションごとに毎回相手にダメージなどを与える処理の前に // 跳ね返しの判定 → trueなら攻撃対象のオブジェクト(プレーヤー)を入れ替え(逆にして)、同じ処理をする // 相手にダメージを与える処理 という具合に攻撃系統のアクションは通常の打撃攻撃や特技攻撃に関係なく共通の部分があるので、 その処理は一緒にしたいというか、普通はたぶん switch($this->action) { case 通常の打撃攻撃: /* */ break; case 防御: /* */ break; case 特技: switch($this->skillAction) { case 攻撃特技: break; case 攻撃補助特技: break; case 回復特技: break; } break; } という感じにアクションを分けて考えると思うのですが(どうなのかなぁ) 跳ね返しの場合は、複数のアクションに跨る処理なので、 そこをなんとか一箇所の記述で済むような作り方にできれば理想なのですが、
補足
補足です。。 interface Battle_Player_Action_Reflectable { } これをimplementsしてるアクションクラスなら跳ね返し処理をする、 という感じで考えていたのですが(いやもうこんなやり方駄目だということは分かっているのですが・・・) とにかく試行錯誤しています・・・
- lv4u
- ベストアンサー率27% (1862/6715)
>>状態1と状態2とではそれぞれの行動(Action)が違ってくるので、 この例だと最大、3(3種類の状態)×3(3種類のアクション)=9のcase文とい うかそれぞれの処理を記述しなければいけないと思うんです。 普通に記述すれば9個のcase文あるいは同等なif文が必要になりますが、それに代わる処理をステートマシンのロジックはテーブルで記述しています。そして、対応するアクション処理(関数)のアドレス(関数ポインタ)を格納し、そのアドレスを呼び出しています。なので9個のcase文は不要となりますし、テーブルの記述なので、ロジックのシンプル化と状態やアクションの変更が容易になります。 なお、関数ポインタのテーブル(配列)は、そのままでは、PHPで記述できないと思います。 >>これがこのシステムの根本となる構成だと思うのですが、イベントという考え方 理解できないというか。。 これは、状態遷移図の考え方を理解しないといけないですが、ここで簡単に説明するのは私にとっては難しいです。でもネットで探せば、それなりの情報は見つかると思います。ただ、ネットや図書での情報が「豊富にある」とはいえないような気もしますけどね。 また、状態遷移については、yacc(bison)という構文解析器生成系について勉強されるといいかもしれません。
お礼
ご回答ありがとうございます。 >対応するアクション処理(関数)のアドレス(関数ポインタ)を格納し、そのアドレスを呼び出しています なるほど、なんとなくですがイメージというかやり方は想像できました。 ただ自分的にこの例の場合、状態遷移図などを使ってまでやるべき(他の方法でできるなら)ことなのか分からないで(もちろん理解してるに越したことはないですが) ここからどう考えていくべきか悩んでるのですが、 今気づいたことで、今まで状態を一つ一つ分けて考えていて、例えば 毒の状態 で たかくとびあがるケース 足を怪我している状態 で たかくとびあがるケース もし毒の状態かつ足を怪我している状態ならどうなるんだろう?と思っていたのですが それは毒と足を怪我している状態が合わさったそれも1つの状態になるんだということに今更気づきました^^ つまり 毒+足を怪我している状態クラス{ function doAction(){ switch($this->action){ case たかくとびあがる: /* Battle::addMessage('毒+足を怪我している状態なので少ししかとびあがれなかった'); 与えるダメージをさらに少なくする処理 */ break; case 行動タイプ2: /* 行動タイプ2を実行 */ break; case 行動タイプ3: /* 行動タイプ3を実行 */ break; } } こういう風に考えると凄い複雑になりそうな気がするんですけど、Stateのベースクラスかなんかにデフォルトの行動を用意してて 特殊な処理になる場合にだけそういう状態クラスを用意してその特殊な処理のアクションを記述すればいいのかなぁと思っていますがどうなんでしょう? う~ん、ちょっと視点がずれてるかなぁ。。。
- lv4u
- ベストアンサー率27% (1862/6715)
>>そこに複数の状態も絡んでくるのでどう設計すればいいのか悩んでいます・・・ 一般的には、ステートマシンで解決すると思います。その設計は、状態遷移図を使います。 フリーのツールとしては、以下のURLで紹介されたsttcのようなものもあります。 http://homepage3.nifty.com/~masumoto/embedded/sttc/index.html ただ、生成されるソースは、アセンブラとC言語です。なので、PHPで実現するのはやや難しいといえます。 でも、このツールを使うことで、「状態遷移図」に対応する「動作するロジックを考える」という部分はツールが考えてくれるので相当楽になります。 生成されたC言語のソースを読んで同様なロジックをPHPで記述することは不可能ではないと思います。 ただし、RPGゲームの設計し、それを実際に動作するプログラムにしたいなら、C言語あるいはC++を使われるのが良いと思います。
お礼
ご回答ありがとうございます。 >ただし、RPGゲームの設計し、それを実際に動作するプログラムにしたいなら、C言語あるいはC++を使われるのが良いと思います。 たしかにそうですね。 ですがとりあえず今の段階では大まかな考え方を知りたいと思っていまして、こちらの都合で申し訳ないのですがこのジャンルでお願いします。 >「状態遷移図」に対応する「動作するロジックを考える」 これがいまいち理解できないのですが、要はこちら側 状態1クラス{ function doAction(){ switch($this->action){ case 行動タイプ1: /* 行動タイプ1を実行 */ break; case 行動タイプ2: /* 行動タイプ2を実行 */ break; case 行動タイプ3: /* 行動タイプ3を実行 */ break; } } } を基準に考えていくということですよね? 状態1と状態2とではそれぞれの行動(Action)が違ってくるので、 この例だと最大、3(3種類の状態)×3(3種類のアクション)=9のcase文というか それぞれの処理を記述しなければいけないと思うんです。 それを自動で作成してくれる(この認識が間違ってるかも・・)というのがどういう仕組みなのか理解できないのですが // 1つのセル情報のデータ構造 typedef struct CELL_ { UINT event_no; // イベント番号 UINT next_state_no; // 遷移先の状態番号 StringVector action_list; // アクション名のベクタ } CELL; これがこのシステムの根本となる構成だと思うのですが、イベントという考え方理解できないというか。。 この戦闘システムの例に当てはめるとどうなるのかがイメージできないんですよね・・・ 例えば、プレーヤーが毒の状態だとします。毒の状態だと「たかくとびあがる」という特技が そんなにたかくとびあがれず(^^)相手に与えるダメージも少なくなるとします(実際はそんなことはないのですがあくまで例として) これを自分のやり方だと、 毒状態クラス{ function doAction(){ switch($this->action){ case たかくとびあがる: /* Battle::addMessage('毒の状態なのでそんなにたかくとびあがれなかった'); 与えるダメージを少なくする処理 */ break; case 行動タイプ2: /* 行動タイプ2を実行 */ break; case 行動タイプ3: /* 行動タイプ3を実行 */ break; } } } というふうになるのですが、この例をCELL_(1つのセル情報のデータ構造)を使うとどうなるのか 具体的に1つ示して頂けないでしょうか?
お礼
ご回答ありがとうございます。 なるほど、やはりそういう感じになりますか。参考になりました。 例えば(何でこんな例しか思いつかないんだろう・・)、 「筋肉が増した」状態(攻撃力がアップ) 「足怪我」状態(守備力がダウン) があってその2つとも所有していた場合、 「筋肉が増した」+「足怪我」(素早さがダウン) の新たに第3の状態(というかアクションに付加する処理)ができたとします。 これは素早さがダウンなので、名前的には前の2つの状態をあわせた名前ですが 付加処理が違うので、前の2つの状態の付加処理も行ってもいいと思うんです。 なんかこういう感じでやっていけばいけそうな気がするんですけど、まあでも大変ですよね・・・^^ ところでlv4uさんは何かお勧めの書籍などはありますでしょうか? できればプログラム関係でお願いしたいのですが