- ベストアンサー
状態とアクションの関係についての設計方法
- PHPの環境で状態とアクションの関係を設計する方法について詳しく説明します。
- 状態を基準にクラスを設計する方法と、アクションを基準にクラスを設計する方法の違いについて考えてみましょう。
- RPGの戦闘のシステムを設計する際に難しい要素や状態の絡み方についても解説します。
- みんなの回答 (40)
- 専門家の回答
質問者が選んだベストアンサー
>>この戦闘処理の状態を状態遷移図みたいなので表すとすると 複合状態(毒+石)も一つの新たな状態として存在することになるのでしょうか? それは、その世界内でのルールしだいだと思います。現実の世界では、「そういうケースは有り得ない」という前提条件で設計していても、何千回、何万回に1回だけど、ありえないとされる事象が発生するとか、パーツである基盤の仕様が間違っていて、有り得ないパターンが発生したりで「システムが停止したぞ!」というような苦情となったりします。 ゲームでは、設計者は「神様」ですから、その複合状態を有り得ないことにするのも、それを認めてそこからの回復条件をちょっと難しくすることも自由だと思います。 ですが状態遷移表を作る場合は、その複合状態も考慮して作成します。 >>つまり毒状態とも石状態とも違う毒+石という新たな状態。 もちろん複合状態もありだとすると、凄い数が増えることになると思うのですが そのとおりで、状態は掛け算で増えてゆき、凄い数になります。ちなみに○と矢印で記述する状態遷移図は、比較的簡単に書けるのですが、これはイベントを全て考慮しない図も書けるので、その場合バグの要因になります。 イベント(状態が変化する原因)とステータス(状態)をマトリクスにした状態遷移表の場合は、そういう抜けは発生しませんが、かなりでかい表になってしまいます。 それを防ぐには、状態遷移表を分割するわけですが、その表分割が不適切だと当然ながらバグ要因になりますね。 >>自分としては「毒+足怪我状態なので攻撃力が大幅に低下した」みたいな付加処理を 多種多様なパターンとして設計してみたいという欲望^^がありまして。。 ステートマシンで作っていれば、その付加処理の状態を「ありえない」としておいて、あとからそういう付加処理を追加するのはとても簡単ですしバグもたぶん発生しないでしょう。そのかわりに、初期段階の設計は複雑になりますね。
その他の回答 (39)
- hogehoge78
- ベストアンサー率80% (433/539)
■Reflection そんな感じで問題ないんじゃないかと思います。 一度それで作ってみて問題があるようならまた考えてみれば良いのではないでしょうか。 ■既存フレームワークで作ってみる、という件 現状のフレームワークは、環境変数とか、POSTやGETなどのHTTPのリクエストをハンドリングして、 読み込むControllerを決定し、1回のリクエストで一回のControllerのメソッドが実行されるというのが前提なので、 フレームワークを使う恩恵を受けられる物よりも、邪魔になる機能を削る、必要な機能を付加する方に手間がかかるきがします。 なかでもCakePHPのModelは、レコードを配列で返してくるので、機能を修正するのはかなり無理がありそうです。 今回は、普通にフルスクラッチしてしまったほうが良いと思います。 ライブラリ単位では、既存のORマッパーで使えるものがあるかもしれませんが、自分で作りこんだほうが今回は良さそうですね。 コンソールアプリを作る上で、Zend_Consoleとか、PEARのConsoleとかは使えるかもしれません。
お礼
ご回答ありがとうございます。 >今回は、普通にフルスクラッチしてしまったほうが良いと思います。 なるほど、たしかに無理やり結合させるのは無理がありますよね。。 ちょっとMVCの構造ということで勘違いしてたところがありました・・・ これまでの皆さんからアドバイスを参考に 一応こう設計すればいいんじゃないかなぁという方向性は見えてきた気がします(たぶん)。 自分としてはこのまま質問を締め切らず、 作りながら疑問があればできれば皆さんからのアドバイスを頂きながら進めたいのですが、 時間がかかりそうなのと、これ以上引き伸ばしても迷惑がかかる(今までも十分そうだったか もしれませんが・・・^^)と思うので締め切りたいと思います。 (ここまでたくさん回答を頂いたので名残惜しい気もしますが・・・) 皆さんからのとても参考になるアドバイスを頂き感謝しております。
- hogehoge78
- ベストアンサー率80% (433/539)
setReflectedを上げたのは、Reflection用のラッパークラスを作ってやることで手間になるのであれば、という意味です。 <?php public function catchElement(Element $element){ //跳ね返し状態だったときに、 $element->setReflected(true); $this->element = $element; $this->throwElement(); } ?> として、throwElement側でElementが跳ね返し処理用のElementなのかどうかを判断して、対象者(つまり、setActorで設定した対象に)に投げるとかすれば良いのではないかと思います。
お礼
ご回答ありがとうございます。 なるほど、Reflection用のラッパークラスなしの場合ってことですね。 それだと、自分的には public function throwElement(){ if ($this->element->isReflected()) { $target = $this->element->getActor();//とりあえず攻撃者に跳ね返す $this->element->setActor($this);//自身をセット $target->catchElement($this->element); } elseif(・・・ } // 攻撃を受ける側(Catcher)が跳ね返し状態なら public function onReflectCatcher($element) { if ($element->isReflected()) {//既に跳ね返しが既に行われているアクションなら } else {//まだ跳ね返しが行われてないアクションなら $this->element = $element; $this->element->setReflected(true); $this->throwElement(); } } public function catchElement($element){ //ここで攻撃を受ける側の各状態を処理する(とりあえず今は跳ね返し状態の処理だけ静的に呼び出す) $this->onReflectCatcher($element); if($element instanceof IActionDamage){ $this->hp -= $element->getAtk(); }elseif($element instanceof IActionHealing){ $this->hp += $element->getAtk(); } } という、攻撃をする側(Thrower)の状態と攻撃を受ける側(Catcher)の状態を No.34でのhogehoge78さんの >攻撃をする側の遷移処理と >攻撃を受ける側の遷移処理は分けて考えた方が良さそうですね。 という考え方を元に自分なりにはこんな感じにしたのですが、これでhogehoge78さんのお考えと大体一緒でしょうか? それと全体の設計についても伺いたいのですが、この戦闘処理システムを 例えばZendやCakePHPなどのフレームワークでそのまま利用する場合に、 例えばControllerなら戦闘処理システムを製作中の場合は 継承元の暫定のControllerクラスはとりあえず空(必要なメソッドだけ)のクラスをどこかに作成しておいて require_once '暫定Controller.php'; class BattleController extends 暫定Controller { public function battleAction() { //各処理 $this->view->render(); } } 実際に利用する場合に、「extends Controller」をZendなら「extends Zend_Controller_Action」などと置き換えて そのまま利用できそうな気がするのですが、 Modelの場合、例えばApplication_Model_PlayerはApplication_Model_Object_Actorを継承していますよね? class Model_Player extends Model_Object_Actor { } これをCakePHPで利用しようと思った場合、CakePHPのモデルは基本的にAppModelを継承しているので その場合はModel_Object_Actorも継承したいしAppModelも継承しなくちゃいけないしみたいな、、 う~んうまく説明ができないのですが、とにかくフレームワークなどで利用する場合の なるべく変更する箇所を少なくするような全体の設計についてアドバイス頂けないでしょうか?
- hogehoge78
- ベストアンサー率80% (433/539)
■「せいしんとういつ」について そういう特技があるんですね。 であれば、elementsというように複数のオブジェクトを渡してやるので良いと思います。 ただし、「せいしんとういつ」にて、2回行動が出来る様になるのであれば、それはそういう状態になっていると思いますので、 Actorがその状態を保持しているということですよね。 であれば、その状態で、ターゲット選択を行うと二回行動を選択できる様に振り分けてやる。 全自動でやるんだったら、TargetOrderクラスでそのような振り分けを行ってやれば良いと思います。 ■跳ね返し処理について 遷移が複雑になりますが、 public function catchElement($element){ if($elements instanceof IActionSingle){ //IActionSingleで且つReflectionDamageだったら if($elements instanceof ReflectionDamage){ $ref = $element->getElement(); //ラップした内容を取り出して処理 } } } とか、とする感じじゃないでしょうか。 問題がありそうだったら、Elementに、setReflected(true)とかメソッドくっつけてやって、ソレを見て振り分けを処理する、 という方向性でも良い気はします。
お礼
ご回答ありがとうございます。 >それはそういう状態になっていると思いますので、 なるほど、先ほどからやっていた状態の中で処理すればいいわけですよね? ただ自分としてはあらゆるケースを想定したいというか、例えばパルプンテという呪文を使って 急に2回行動できるようになるケースもありますし、誰かに2回行動をさせるような特技も考えられると思います。 まあこれについてはもう少し自分でどのように設計するか考えてみたいと思います。 跳ね返し処理についての確認なのですが、これは次のように public function catchElement($element){ //本当はif($this->isDead()){とかより後に記述すべきなのですが、とりあえず if($element instanceof IActionSingle){ if($element instanceof ReflectionDamage){ $ref = $element->getElement(); //ラップした内容を取り出して処理というより、それを$elementに代入して $element = $ref; $element->setReflected(true); } } if($this->isDead()){ }elseif($this->isInvisible()){ }elseif($this->isReflection() && (!$element instanceof IActionReflection) && (!$element->isReflected())){ $this->element = new ReflectionDamage($element); $this->throwElement(); }else{ if($element instanceof IActionDamage){ $this->hp -= $element->getAtk(); }elseif($element instanceof IActionHealing){ $this->hp += $element->getAtk(); } } } $element->setReflected(true);を設定して!$element->isReflected()で判断してあげないと無理というか 無限ループになりそうな気がするのですが、hogehoge78さんが考える //ラップした内容を取り出して処理 の具体的な処理を教えてもらえないでしょうか?
- hogehoge78
- ベストアンサー率80% (433/539)
返信が遅くなってしまいましたが、 ■連続攻撃というと特技がありまして・・・ これは、どちらかと言えば、catchElement側ではなくて、throwElement側で実装する感じですかね。 public function throwElement(){ if($this->action instanceof IActionContinuous){ $count = $this->action->getCount(); for($i=0; $i<$count; $i++){ $this->targets[0]->catchElement($this->action); } }elseif($this->action instanceof IActionDamage){ ・・・・ } } とか。 X回攻撃を行う「IActionContinuous」インターフェイスを持った何かつくって、 getCountメソッドを持たせて、getCountに攻撃回数を返させる感じです。 二回攻撃を行うという属性を持っているのは、Skillクラスなので、 Skillクラスから読んでやればよいかなと。 逆に面倒くさければ、IActionDamageインターフェイスにgetCountを持たせてやって IActionDamageで、指定なければgetCountが1を返す様にしてやっても良い気はします。
お礼
ご回答ありがとうございます。 確認なのですが、 >}elseif($this->action instanceof IActionDamage){ はthrowElement()内ではなくて、catchElement()内で記述するものじゃないんですか? throwElement()って1つの攻撃というか行動を最小単位に振り分けることですよね? 例えば、「せいしんとういつ」という次のターンに2回行動できるようになる特技があるんですけど、 これは連続攻撃(IActionContinuous)とは違う性質だと思うんです。つまり異なるアクションが 2回できるようになるわけで、自分としてはhogehoge78さんのやり方で次のような感じを想定しているのですが public function throwElement(){ foreach ($this->elements as $element) { $element->setActor($this); if($element instanceof IActionSingle){ //1人に対するアクション }elseif($element instanceof IActionGroup){ //グループ対するアクション }elseif($element instanceof IActionContinuous){ //連続攻撃(誰に攻撃するかはランダム)のアクション } } } というふうに、複数アクション($this->elements)を持たせる感じにして throwElement内で何回行動できるかを含めて記述しているのですが、こんな感じでいいのでしょうか? それともう一つ、No13での跳ね返しに対する処理の部分なのですが、catchElement内で }elseif($this->isReflection() && (!$element instanceof IActionReflection)){ $this->element = new ReflectionDamage($element); //ReflectionDamageというのは、跳ね返しは全体攻撃でも //攻撃してきた個人に対する攻撃になると思うので、 //IActionSingleインターフェイスとIActionReflection //を与えたElementのラッパーという位置づけにでも。 とあるのですが、 new ReflectionDamage($element); で生成されるオブジェクトにはIActionDamageとかISkillSleepとか具体的なインターフェイスは 含まれてないですよね?というかそのようなインターフェイスというのは動的に付ける(implements)させる ことはできなくないですか? つまりなんでそんなことが気になるかというと、どんなアクション(メラなのか通常攻撃)が跳ね返ってきたのかを 判断するのは常に $element instanceof IActionDamageとかインターフェイスで判断するわけですよね? ReflectionDamageクラスがIActionSingleとIActionReflectionをimplementsしてる(静的に設置できる)ことまでは分かるのですが、 それ以外の具体的な跳ね返ってきたアクションのインターフェイスはこのやり方だとどのようにimplementsさせることができるのでしょうか?
- lv4u
- ベストアンサー率27% (1862/6715)
>>状態フラグについて質問があるのですが、「死」は眠りや石のように状態フラグとして扱ったほうがいいですか? ステートマシンでやるなら、それらはフラグは使わず「状態」で表現しますけどね。 眠りの期間を管理する等、タイマーによる状態変化があるなら、カウンター等を持ち込みますが・・・。 基本的にステートマシンで制御しているところに、フラグ類をもちこむとバグの原因になると思います。ステートマシンで管理しないなら、状態フラグになるでしょうね。
お礼
ご回答ありがとうございます。 基本的にステートマシンで制御しようと設計しようと考えてはいるのですが、 死の状態って眠りや石の状態と決定的に違うのは、カウントがないわけじゃないですか。 なので同じように状態として扱っていいのかなぁと思いまして。 自分としても眠りや石の状態と同じように扱っていいのなら処理が一貫性があって 楽というかそっちの方がいいのかぁとは思います。 すいません、ここでちょっとhogehoge78さんに質問があるのですが、No.13で記述してして頂いた class Actor{ public function catchElement(Element $element){ if($this->isDead()){ }elseif($this->isInvisible()){ }elseif($this->isReflection() && (!$element instanceof IActionReflection)){ }else{ if($element instanceof IActionDamage){ $this->hp -= $element->getAtk(); }elseif($element instanceof IActionHealing){ $this->hp += $element->getAtk(); } } } } について伺いたいことがあるのですが、 連続攻撃というと特技がありまして、これは要は if($element instanceof IActionDamage){ $this->hp -= $element->getAtk(); } を2回繰り返すってことと同じ意味になりますよね? でも連続攻撃という特技の場合は例えば if($element instanceof ISKillRenzokukougeki){ for ($i = 1; $i <= 2; $i++) { $this->hp -= $element->getAtk(); } } という風に特別な処理として扱ったほうがいいのでしょうか? なんか自分として似たような処理は一箇所に記述したい(重複したくない)というのが常にありまして この質問の前半の方で跳ね返し処理に対してあちこち散らばることに自分があーだこーだ言っていたのも そういう考えから来てるんです。そこらへんの考え方に対しても何かアドバイス頂けないでしょうか?
- lv4u
- ベストアンサー率27% (1862/6715)
>>「並列型状態」というのは利用できないでしょうか(勘違いしてたらすいません)? そもそもhogehoge78さんはこの分野に関してはそんなに専門ではないと思うで hogehoge78さんに伺うのは違うかもしれませんが、例えばlv4uさんはこれについてはどうお考えでしょうか? オブジェクト指向プログラミングの起源でもあるSimula言語は、シミュレーションを行うための言語だったようですね。今回作成したいプログラムは、人間がゲーマーとして参加する対戦ゲームではなく、コンピュータ内で兵士オブジェクトたちが与えられた能力を元に戦わせるものですから、作りたいプログラムは戦闘シュミレーションプログラムになると思います。 なので、シュミレーションに参加するオブジェクトである兵士たちが、それぞれが並列状態で変化していくように作るのは当然でしょうね。 そして設計者は神様ですから、兵士ひとりが持つ「状態」を全て把握して設計することになります。 ただし、それは1人の兵士について攻撃と防御などによって生じる状態を完全に設計しますが、複数の兵士たちが参加する戦闘の開始から終了するまでの状態についてすべて設計するものではありません。そんなものを状態遷移表でつくったら大変なことになってしまいます。 設計者は、1人の兵士の攻撃と防御による状態の変化は完璧に設計しますが、複数の兵士どうしが戦う場合にどうなるかは、解りません。たとえば、歩兵50名+狙撃チーム1(スポッターとスナイパーの2名)vs機関銃台座2つ(4名)が戦うとき、歩兵が何人以上なら攻略可能か?機関銃台座が3つに増えた場合はどうなるか?なんてそれぞれの人数での戦闘の勝敗は、設計者は事前に予想できません。 設計者は、歩兵と狙撃チーム、そして機関銃台座の数をループで変化させて、それぞれの戦闘結果を調べることになるでしょうね。 それは、私たち人間が「あの世」といわれる天国から「この世」といわれる現世に生まれる前に、自分のそれまでのカルマ(良いものも悪いものもある)と自分の魂の能力を元に、予想される自分の人生を事前にシュミレーションして生まれてくるのと同様なものです。 貧乏あるいは富豪の両親(シングルマザー?)を設定し、誕生後のいろんなトラブルを設定し、自分の配偶者の候補になる異性たち(第一候補、第2候補・・・)と結婚の約束などしてから、生まれてきます。(一部の方は、詳細な計画を立てないで「旨いものが食えればいい!」だけで生まれたりしますが・・・) でも、現実には、両親の経済状態の変化や2人の人生観によって、生まれることなく中絶されることもあるし、詐欺師に騙されたり、第一候補の結婚相手が事故で死んじゃうとか、友人に奪われるってこともあります。これは自分の能力だけでコントロールできず、周囲の方たちとの関係で、いろいろ変わってしまいます。 そして誰しもが死後に「地獄」行きという結果を想定してはいないのですが、現代では宗教を拒否する人が多いためか、価値観が混乱して、50%以上の人々が天国ではなく、地獄に行っているわけですね。 ちょっと余談になりましたが、ゲームに登場する兵士や武器、戦場などのクラスを用意し、それぞれ必要な数のインスタンスを生成、パラメータを適切に設定し、ゲーム開始して、終了状態(勝敗が決定する)まで、オブジェクトを並行処理させればいいと思います。 複数処理は、単なるループで1つずつオブジェクトの処理をしてもいいですし、スレッドやプロセスを生成するやり方で並行処理を実現するなど、いろいろあると思います。
お礼
ご回答ありがとうございます。 う~ん、やっぱりそこまでする必要はないですかね。。 自分としてもじゃあどのような特殊処理をやりたいのか今の段階で具体的なものはないですし。 ただ設計としてそこまでできたら理想的だなぁというぐらいですからね。 状態フラグについて質問があるのですが、「死」は眠りや石のように状態フラグとして扱ったほうがいいですか?
- hogehoge78
- ベストアンサー率80% (433/539)
バグってましたね。すみません。 <?php public function execute(){ $result = array(); foreach($this->fields as $f){ $result[$f] = call_user_func(array($this->stateobject, "is".ucfirst($f))) ? 1 : 0; } foreach($this->mappings as $actid=>$check){ $isOK = true; foreach($check as $key=>$flag){ if($result[$key] != $flag){ $isOK = false; break; } } if($isOK) return call_user_func(array($this, "on{$key}")); } } ?> こんな感じです。 今回のプログラムが、 攻撃をする→攻撃者が値をElementに上乗せ→攻撃対象に渡す→攻撃対象が値をElementに減算→攻撃対象が攻撃を受け付ける という流れで考えるのであれば、 攻撃をする側の遷移処理と 攻撃を受ける側の遷移処理は分けて考えた方が良さそうですね。
お礼
ご回答ありがとうございます。 >攻撃をする→攻撃者が値をElementに上乗せ→攻撃対象に渡す→攻撃対象が値をElementに減算→攻撃対象が攻撃を受け付ける >という流れで考えるのであれば、 >攻撃をする側の遷移処理と >攻撃を受ける側の遷移処理は分けて考えた方が良さそうですね。 たしかにそれがベストというかそうやるべきなんでしょうけど、 自分としては、攻撃をする側の状態と攻撃をされる側の状態の組み合わせによって 特殊なアクションというか付加処理になるようにしたくて hogehoge78さんのそのやり方だと、例えば 攻撃をする側 → 毒状態(攻撃力ダウン) 攻撃をされる側 → 眠り状態(守備力ダウン) で与えるダメージが大きくなるわけですが、 自分が考えているのはお互いがそのような状態の場合は 例えば「攻撃ができない!」とか特別な処理を行うようにしたいのです。 もちろん全部が全部そういう特別な処理を行うわけではないですけど。 ただそのような仕様にしたら、例えば 3ターン時に攻撃をする側が毒状態で攻撃を受ける側が眠り状態で味方の誰かが死んでる場合に・・・・・・・^^ と際限がなくなってしまうので、どこかで妥協が必要だとは思いますが No.19でlv4uさんが仰ってたように>ゲームでは、設計者は「神様」ですから、 自分の中での許容したい設計の範囲というか最初から考えていたことは そこ(攻撃をする側と攻撃をされる側の状態によって処理が異なる仕様)までなんです ただ普通に考えると(相手の分)フラグを倍にして遷移IDも膨大になると思うので 現実的じゃないと自分でも思っているので、なにか良い方法でそれを実現させることはできないものか それこそ >攻撃をする側の遷移処理と >攻撃を受ける側の遷移処理は分けて考えた方が良さそうですね。 分けてできないものか。。 最近ちょっと状態遷移表に興味を持ち出して自分なりにいろいろ調べてるのですが 「並列型状態」というのは利用できないでしょうか(勘違いしてたらすいません)? そもそもhogehoge78さんはこの分野に関してはそんなに専門ではないと思うで hogehoge78さんに伺うのは違うかもしれませんが、例えばlv4uさんはこれについてはどうお考えでしょうか?
補足
すいません、訂正です。。 >で与えるダメージが大きくなるわけですが、 攻撃力もダウンしてるので別にダメージが大きくはならないですね^^
- lv4u
- ベストアンサー率27% (1862/6715)
>>ところでlv4uさんはクラス(オブジェクトでも)というのはどういうものだとお考えでしょうか? Simula、Smalltalk、lispからC++やJavaを経てRuby、C#などに流れてゆくオブジェクト指向プログラミングの歴史を書籍で読んだり、Java言語勢力の汚い宣伝(商売)手法と、地道だが着実に成果を上げてオブジェクト指向の方向性を決めていったC++を「C++の設計と進化」で読み、そして実際にC++、C#やRubyでプログラムを作った経験から 「オブジェクト指向プログラムはロジックやモジュール構造、データ構造などを整理し、生産性や保守性の向上を実現する手法で、構造化プログラムの延長線上にある便利な道具セットの総称」だと思っています。 そして、クラスは、そのオブジェクト指向プログラムを構成する単なる1つの道具というか表現手法と考えます。 (クラスが必須ではないプロトタイプ・ベース言語と呼ばれるオブジェクト指向言語もありますよ。) そして「道具」ですから、その道具に合ったシーンで有効と思えば使うし、単に複雑・面倒になるだけなら使わないだけです。 また、そのオブジェクト指向プログラムは、その土台に構造化プログラミングやデータ構造の理論をベースに発展しているので、「オブジェクト指向はそれらの理論より新しいから、それらを学ぶ必要は無い」ということは無く、そういう知識があって初めて道具類をうまく使いこなせるし、それら抜きではうまく使いこなせないと考えています。 ちなみに一時期、「UML」がブームになったことがありますが、六本木だか青山でそれとは無関係のセミナーに参加して、昼食を近くの喫茶店でとっていたとき、UMLを企業相手に教えるセミナーを計画している人たち(頭よさそうな若者と綺麗な女性たち、それに老獪な紳士風の人を含めた一団)の金儲け計画を小耳にはさんだことがあります。 それを聞いていて、「雑誌で話題になっているものを銀の弾丸・黄金ハンマーと信じて飛びついてはいけない。」とか、「現実に使い物になるのか、しっかりと確認しないといけない。それは書籍だけでは無理で、実際の開発経験が必要。」だと思ったものですね。
お礼
ご回答ありがとうございます。 >Java言語勢力の汚い宣伝(商売)手法と、地道だが着実に成果を上げてオブジェクト指向の方向性を決めていったC++を「C++の設計と進化」で読み、そして実際にC++、C#やRubyでプログラムを作った経験から なんか自分的には興味があります(ただあまり主題とかけ離れていくのもあれなので^^) >構造化プログラムの延長線上 構造化プログラムとオブジェクト指向の決定的な違いはあるのでしょうか? >「オブジェクト指向はそれらの理論より新しいから、それらを学ぶ必要は無い」ということは無く、そういう知識があって初めて道具類をうまく使いこなせるし、それら抜きではうまく使いこなせないと考えています。 そうですね。むしろ自分は構造化プログラムというか基礎っていったらあれですけど、そういうのをしっかり学びたい(知りたい)と思うようになりました。 オブジェクト指向をかじっている(というよりただクラスを記述しているというレベルですけど)程度だからこそ基礎が知りたくなるというか よくそういう現象ってありますよね
- lv4u
- ベストアンサー率27% (1862/6715)
>>それが本当に戦闘に必要かというのはありますが、もしこのような処理をクラスを用いないでやろうとした場合にlv4uさんなら具体的にどこにこのような処理を記述するのでしょうか? 生成されたサンプルのソースを見ると、/* nop */ とか stop_mid(); の記述がありますよね?この部分に必要な処理を記述します。 ただ、いくつかの箇所で似たような処理を行うことが多いと思える場合は、サンプルにあるように関数にまとめてそれらを呼び出すように作成します。 単にフラグを1つだけ操作する程度のシンプルな処理なら、関数にしないでこの位置に直接命令を書いてもいいのですが、仕様変更や拡張を考えると、単なるフラグ操作でも関数にしたほうがいいようです。 もちろんこの関数をクラスにすることもできるでしょうが、そうするメリットが無いと私には思えます。 でも、「クラスにしたほうが、簡単だし、機能拡張も楽だと思える」と判断されるなら、そうしてもいいと思いますよ。
お礼
ご回答ありがとうございます。 クラスの方が良いかとか拡張性がとかはまだわかりませんが、たしかに例えば 健康な状態から毒の状態になった場合→Battle::addMessage('健康な状態から毒の状態になった'); 眠りの状態から毒の状態になった場合→Battle::addMessage('眠りの状態から毒の状態になった'); 同じ毒の状態(毒フラグ(カウントが1以上)がtrue)になっても遷移の仕方でメッセージや処理も それぞれ違ってきますしね。。 ところでlv4uさんはクラス(オブジェクトでも)というのはどういうものだとお考えでしょうか? hogehoge78さんは >オブジェクトとは、あるパラメータを持っていて、そのパラメータに作用する、または外的要因に作用するメソッドを持っている物、 とオブジェクトについてですが定義を仰ってくれましたが
- hogehoge78
- ベストアンサー率80% (433/539)
■なぜ秒数という概念を取り入れる必要があるの 仰るとおり、別の言語で作った場合にも大体大枠はそうなるかなという想定で書いてみました。 PHPでも結構長い処理を行うプログラムを作るとき(何かのステータスを読んで別の何かに書きだしたりとか) <?php while(true){ } ?> とか、無限ループにして、ある一定の状態遷移に着たらbreak;するなりexit;するなりしてループを終わらすという処理を行ったりします。 Apacheを通さない、PHPを素のスクリプトとして走らせる場合ですね。 その時に結構長い処理なのに、sleep入れないで走らせると、CPU使用率限界の速度でループするので、ある程度間隔をあけてやったりします。 ■Viewクラスに関して これは多分何でもいいと思います。今後も同じプログラムを作っていく、という事であれば何か特別な名前を付けてもいいと思いますが、今回はつくってみる、なので、単純に「View」でも、「Application_View」でも。 他で扱うものでないなら、面倒ですし、CakePHPのCoreクラスのように、「Controller」「View」「Model」という単純な名前でも良いと思います。 ■ステートマシンに関して >兵士クラス(プレーヤー)にステートマシンを所持させる形では駄目なんですか? 所持させますね。 <?php class Solder extends Actor{ public function __construct(){ $this->statemachine = new SolderState($this); } } ?> とか。 自分自身のメンバ変数に持たせ、ステートマシンに自分自身のインスタンスを渡してやる感じですね。 このActorに持たせる理由は、Actor自身が、Modelで、このModelのなかでControllerをもつので、そのControllerが使うModelというところです(MVC的にこれはいいのか、と言われると微妙な気もしますが。) で、ステートマシンの実装部分ですが、もうすこしちゃんと書くと <?php class SoldierState extends StateMachine{ protected $fields = array( 'hoge', 'moge' ); protected $mappings = array( 'XX' => array('hoge'=>1, 'moge'=>0), 'YY' => array('hoge'=>0, 'moge'=>1), ); public function __construct(Actor $soldier){ $this->stateobject = $soldier; } public function onXX(){ return $this->stateobject->hogeAction(); } public function onYY(){ return $this->stateobject->mogeAction(); } public function execute(){ $result = array(); foreach($this->fields as $f){ $result[$f] = call_user_func(array($this->stateobject, "is".ucfirst($f))) ? 1 : 0; } foreach($this->mappings as $actid=>$check){ $isOK = true; foreach($check as $key=>$flag){ if($result[$key] != $flag) break; } if($isOK) return call_user_func(array($this, "on{$key}")); } } } こんな感じです。XXとかYYは、ただのIDです。意味を持ちません。Hoge,Mogeが、Poison,Paraizeとかそういう奴です。 これはYamlとかCSVとかINIとかといった設定ファイルからクラスを自動生成したときに効果があるパターンですね。 ■lv4uさん PHPはネームスペースという概念を持たないので、SolderStateMachineとか、長い名前を必要とします。 他の言語だったら、 package app.state class Solder extends Machine{ } とかでいけるんでしょうけども。
お礼
ご回答ありがとうございます。 なるほど、これも良さげな設計ですね。 ただプログラムの細かいことに質問があるのですが、 executeメソッド内の「$isOK」というのは何のために存在してるのでしょうか? foreachのあとに$isOK = true;とされて、それ以降falseとかを注入した様子がないのですが。 あと「if($result[$key] != $flag) break;」は「if($result[$key] == $flag)」だと思うですが、 $resultの中にはis~が存在していれば1が入ってるわけですよね? 例えば foreach($check as $key=>$flag){ if($result[$key] != $flag) break; } の$keyがmogeの場合に、$result[$key]=1 $flag=1ですからループから抜けて onYYが実行されると思うのですが(つまりそれが期待する動作) それともなにか自分の解釈に間違いがありますでしょしょうか? それとNo.29であったというか今までの状態に対する基本的な考え方として >ACTION_ID,METHOD,Hoge,Moge >XX,hogeAction,1,0 >YY,mogeAction,0,1 >とかといった、遷移IDと、遷移時のメソッド名と、各フラグのONOFFを書いてやって これは攻撃をする側の状態のみの状態遷移プログラムが作成されると思うのですが、 攻撃を受ける側の状態も考慮した場合はNo.29で提案されたテーブルはどのようになるか分かりますでしょうか? 自分の想像ではフラグが倍に増えACTION_IDがさらにもっと増えそうな気はしてるのですが
お礼
ご回答ありがとうございます。 なるほど、やはりそういう感じになりますか。参考になりました。 例えば(何でこんな例しか思いつかないんだろう・・)、 「筋肉が増した」状態(攻撃力がアップ) 「足怪我」状態(守備力がダウン) があってその2つとも所有していた場合、 「筋肉が増した」+「足怪我」(素早さがダウン) の新たに第3の状態(というかアクションに付加する処理)ができたとします。 これは素早さがダウンなので、名前的には前の2つの状態をあわせた名前ですが 付加処理が違うので、前の2つの状態の付加処理も行ってもいいと思うんです。 なんかこういう感じでやっていけばいけそうな気がするんですけど、まあでも大変ですよね・・・^^ ところでlv4uさんは何かお勧めの書籍などはありますでしょうか? できればプログラム関係でお願いしたいのですが