ページ 1 / 1
ゲーム製作の状態遷移について質問があります。
Posted: 2014年1月31日(金) 22:56
by taketoshi
こんばんわお世話になります。
ゲーム制作をしている中で状態遷移のコーディングの仕方について質問があります。
SRPGを制作しているのですが、switch文で状態遷移を分けています。
非常に多岐にわたっているので整理したいので
http://dixq.net/g/sp_06.html
ここを参考に状態をクラス化する方法を学びました。
//現在のゲーム本体のコード部分
► スポイラーを表示
コード:
/*****************************************/
/******敵ターンはここからです*****/
/*****************************************/
case Enemy_Turn:
int nEnemyRet;
switch(nEnemyTransfer){
case SerchNomoveEnemy://動かす敵の番号を割り出す
nEnemyRet = move_enemy->SetNomoveEnemyData();
if(nEnemyRet == nResultOK){
nEnemyTransfer = AutoScrollEnemy;//状態遷移を次に移す
}else{//動かす敵が居ないので敵ターン終了
Change_nTransfer(Player_TurnReady);
}
break;
case AutoScrollEnemy://割り出した番号の敵ユニットまでオートスクロールする
if(move_enemy->AutoScroll_MoveChar() == nResultOK){
move_enemy->PickUpEnemy(NumPikup);//描写の関係で敵を立たせる
nEnemyTransfer = EnemyMoving;
}
break;
case EnemyMoving://敵キャラクタ移動処理
if(nRet =move_enemy->CalcMoveChar() == true){
nEnemyTransfer = ThinkEnemyMovedAfter;//目的地までキャラクタを移動したら状態遷移を変更する
}
move_enemy->CalcDrawNumber(CharMoving);
break;
case ThinkEnemyMovedAfter:
move_enemy->EndFlag();
nEnemyTransfer = SerchNomoveEnemy;
break;
default:
break;
}
break;
そのうえで、二つ質問があります。
・状態遷移の中で更に状態遷移が分かれた時はどの様にすればよいでしょうか?
(Enemy_Turn状態遷移の中で更にSerchNomoveEnemy等へ状態遷移が分かれている)
(状態遷移を表したクラス内でswitch文するのか、更に状態遷移のクラスを生成するのか・・?)
・ゲームのメインとなるクラスはどこに何処に持たせるべきですか?
(シーンマネージャクラスにポインタを持たせる?それともその親クラスにするべき?)
概念的な事をお尋ねしたいです。よろしくお願いします。
Re: ゲーム製作の状態遷移について質問があります。
Posted: 2014年2月01日(土) 08:22
by h2so5
taketoshi さんが書きました:
・状態遷移の中で更に状態遷移が分かれた時はどの様にすればよいでしょうか?
(Enemy_Turn状態遷移の中で更にSerchNomoveEnemy等へ状態遷移が分かれている)
(状態遷移を表したクラス内でswitch文するのか、更に状態遷移のクラスを生成するのか・・?)
その二つがどう違うのかがよく分かりません。
状態遷移が階層状になっている場合は、状態遷移を表したクラス内でswitchして(下層の)状態遷移のクラスを生成することになると思います。
taketoshi さんが書きました:
・ゲームのメインとなるクラスはどこに何処に持たせるべきですか?
(シーンマネージャクラスにポインタを持たせる?それともその親クラスにするべき?)
メインとなるクラスとは何でしょうか。
Re: ゲーム製作の状態遷移について質問があります。
Posted: 2014年2月01日(土) 09:41
by へにっくす
taketoshi さんが書きました:(状態遷移を表したクラス内でswitch文するのか、更に状態遷移のクラスを生成するのか・・?)
そんなの自分の勝手ですよ 笑
まあそもそも状態遷移をクラスにするのならばすべてにおいてやるべきだと思いますけどね。なんで悩むのかわかりません。コードの大きさ関係ないです。納期があるならまだしも、仕事ではないんですよね?
それにこれからも改変が入るのでしょ?メンテの点から考えても、
やるなら今でしょ!!!!!!!
taketoshi さんが書きました:・ゲームのメインとなるクラスはどこに何処に持たせるべきですか?
どこに持たせるかは、それぞれの状態遷移を扱うクラスにするべき。それ以外にどこに持たせるのか疑問ですね。
何でもかんでもシーンマネージャの直下に置くのは違うってのはわかるよね?
Re: ゲーム製作の状態遷移について質問があります。
Posted: 2014年2月01日(土) 10:08
by せんちゃ
自分の場合は時と場合に応じて、という感じです。
かなり規模のでかいモジュールだったりどうしても複雑なロジックのもので一つ一つのステート処理がでかいのであればクラスにしますし
逆に規模が小さかったり、演出的な処理しかないのであればswitchで分岐してステート毎の関数を作ります。
ゲームシーンは基底処理を書いてシーン毎にクラスを派生させますが、ゲーム内にあるプレイヤーや敵の状態に応じた挙動はswitchで書いています。
バトルなどのゲーム部分はかなり複雑なシーケンスなのでフェーズ毎にクラスで切りたいところですが、switchで書いたほうが無難かなと思うときもあります。
Re: ゲーム製作の状態遷移について質問があります。
Posted: 2014年2月01日(土) 15:07
by dic
リンク先を紹介しておいて、実は自分なりのやりかたでやってます。
ほとんどの人が、管理人さんのコードを引用しているので、
みんなそれでやっているのかと思い、そのリンク先を紹介しました。
私の場合は、switch(...) で使用する変数をひとつにしています。
なので、どうやっても、階層をつくりません。
定数で階層を作ってます。
ビット判定みたいなのです
switch( mode )
{
1100 -> Enemy->Move
1110 -> Enemy->Move_01
1120 -> Enemy->Move_02
1200 -> Enemy->Draw
1210 -> Enemy->Draw_01
}
みたいな感じです。
ここらへんで悩むより、もっとゲームの内容に近いところで
悩んだ方がいいと思います。
私も、いろいろデザインパターンを利用しましたが、
結局は、switch() case 文にもどってきました。
Re: ゲーム製作の状態遷移について質問があります。
Posted: 2014年2月01日(土) 18:58
by ISLe
switchで状態遷移するとしても、switchはオブジェクト個々に完結したものでなければいけません。
複数のオブジェクトを扱う処理に跨ったswitchは、switchを使うべきかどうかという以前に構造化を阻害する要因となります。
taketoshi さんが書きました:・状態遷移の中で更に状態遷移が分かれた時はどの様にすればよいでしょうか?
(Enemy_Turn状態遷移の中で更にSerchNomoveEnemy等へ状態遷移が分かれている)
(状態遷移を表したクラス内でswitch文するのか、更に状態遷移のクラスを生成するのか・・?)
担当するオブジェクトが異なるので状態遷移の中で状態遷移が分かれるということ自体がありません。
taketoshi さんが書きました:・ゲームのメインとなるクラスはどこに何処に持たせるべきですか?
(シーンマネージャクラスにポインタを持たせる?それともその親クラスにするべき?)
フレームワークのメインとなるコードはあっても、ゲームのメインとなるコードはありません。
いわゆるタスクを生成したら個々のタスクは独立して動くようにします。
それを統制するのがマネージャで、マネージャが担当するオブジェクトはマネージャの役割りに応じたものです。
Re: ゲーム製作の状態遷移について質問があります。
Posted: 2014年2月02日(日) 21:08
by taketoshi
皆様色々アドバイスありがとうございます。
色々ご指摘を読み解きながら自分のコードに反映させていきます。
個々にお返事が出来なく申し訳ありません。
色々思考したのですが、dicさんとせんちゃさんが仰っていただいた方法で管理したいとおもいます。
SRPGにおける大雑把な状態の変化はクラス分けによるステート処理を採用します。
(タイトル画面⇒セーブロード⇒幕間イベントシナリオ⇒ゲーム本体部分⇒幕間イベント→・・・・)
で、ゲーム本体部分は更に細かく分岐するのでdicさんのご提示頂いた1段階enum文のswitch処理で分岐させます。
ISLeさんにご指摘いただいた
>複数のオブジェクトを扱う処理に跨ったswitchは、switchを使うべきかどうかという以前に構造化を阻害する要因となります。
この文章を読んでドキッとしました。複数のオブジェクトを扱うスイッチ文だと多態性はどうやって持たせるんだろうかと悩んでいました。
ここは自分のクラス設計の問題なので、設計を見直すことで解消できそうです。
まず、SRPGの作り方も手探りでよくわかっていないので
支離滅裂のswitch文でSRPGの製作を進めてSRPGの作り方を深く学んだうえで再度リファクタリングをします。
その時に頂いたアドバイスが役に立ちます。壺の焼き色が気に食わなくて叩き割る陶芸家の気分です。
//流れだけ記述したテストコードです
//このままでは全く動かないのですが、雰囲気としてはこんな感じで検討しています。
► スポイラーを表示
コード:
typedef enum{
EnemyTurnInitilize,
EnemyThink,
EnemyMove,
EnemyMovedAfter,
EnemyMovedAction
}GameTransfer;
//ゲーム状態遷移
class Game : public BaseScene {
private:
GameTransfer gametransfer;//SRPGのゲーム本体部分を制御する状態遷移変数
enemy_force *lp_enemyforce;//敵軍の実態
public :
Game::Game(ISceneChanger* changer);
void Initialize() override; //初期化処理をオーバーライド。
void Finalize() override; //終了処理をオーバーライド。
void Update() override; //更新処理をオーバーライド。
void Draw() override; //描画処理をオーバーライド。
};
//キャラクタの抽象クラス
class charbase{
public:
GameTransfer think();
GameTransfer move() = 0;
GameTransfer MovedAfterThink() = 0;
GameTransfer MovedAction() = 0;
};
//敵クラス
class enemy:public charbase{
public:
GameTransfer think();
GameTransfer move() override;
GameTransfer MovedAfterThink() override;
GameTransfer MovedAction() override;
};
//敵管理クラス
class enemy_force{
public:
charbase *lpEnemy;//抽象クラスへの参照。ここに動かす敵のポインタを放り込む
GameTransfer TurnBeginInit();//ターン最初に抽象クラスへ動かす敵のポインタを放り込む
vector<enemy> lpEnemyData;//敵の個々の実態
};
//ゲーム本体部分更新処理
void Game::Update(){
swtich(gametransfer){
case EnemyTurnInitilize://敵情報の初期化
gametransfer = lp_enemyforce->TurnBeginInit();
break;
case EnemyThink://キャラクターオブジェクトに実装した動かす敵を割り出すメンバ関数を走らせる
gametransfer = lp_enemyforce->think();//終了したら次の遷移に移る
break;
case EnemyMove://キャラクターオブジェクトに実装した敵位置を動かすメンバ関数を走らせる
gametransfer = lp_enemyforce->lpEnemy->move();//敵を動かす
break;
case EnemyMovedAfter://移動後の思考
gametransfer = lp_enemyforce->lpEnemy->MovedAfterThink();
break;
case EnemyMovedAction://思考に基づき移動後の行動実行
gametransfer = lp_enemyforce->lpEnemy->MovedAction();
break;
//プレイヤーターンも同様に処理
}
}
//更新
void SceneMgr::Update(){
if(mNextScene != eScene_None){ //次のシーンがセットされていたら
mScene->Finalize();//現在のシーンの終了処理を実行
delete mScene;
switch(mNextScene){ //シーンによって処理を分岐
case eScene_Menu: //次の画面がメニューなら
mScene = (BaseScene*) new Menu(this); //メニュー画面のインスタンスを生成する
break;//以下略
case eScene_Game:
mScene = (BaseScene*) new Game(this);
break;
case eScene_Config:
mScene = (BaseScene*) new Config(this);
break;
}
mNextScene = eScene_None; //次のシーン情報をクリア
mScene->Initialize(); //シーンを初期化
}
mScene->Update(); //シーンの更新
}
概念的な事をお聞きしたかったので解決といたします。
後々いったん自分で噛み砕きコーディングしてから解らないときに再度質問させていただきます。
皆様ありがとうございました。