GameLoop'でWait

moba
記事: 82
登録日時: 9年前

GameLoop'でWait

投稿記事 by moba » 9年前

 RPGツクールやWOLF RPGエディターには、「ウェイト」コマンドがあります。これがあれば、ゲームのフロー?を直接書けます。

CODE:

void TurnBasedBattleLoop
{
    while (true)
    {
        OnTurnStart();
        ThinkAll();
        ExeActionAll();
        OnTurnEnd();
        if( IsFinished() )
            return;
    }
}
 ウェイトコマンドがあれば、ドラクエ式のターン制戦闘はこれでOKです。けれども、C++にはウェイトコマンドがありません。
 ほとんどの講座サイトでは、状態遷移が勧められているようです。シューティングやアクションのように、全ての物体が毎フレーム動くゲームでは状態遷移が有効なのは分かります。ただ、そのアイデアだけでは、僕にはターン制のゲーム(のコード)を考えられませんでした。オープンソースのローグライクのコードを見てみましたが、読んでも意味が分かりませんでした。

 反則的だと思いますが、ウェイトコマンドをC++で実現する方法もあります。

CODE:

void GameLoop()
{
    while (true)
    {
        // メッセージ処理や画面の更新をして、
        UpdateMain();
        UpdateSub(); // 並列的な処理など
        Render();
        gFPS.Wait(); // 1フレーム待つ
    }
}

void Wait( int nWait )
{
    while (true)
    {
        if( nWait <= 0 )
            return;
        // メッセージ処理や画面の更新をして、
        UpdateSub();
        Render();
        nWait--;
        gFPS.Wait(); // 1フレーム待つ

    }
}
 どうしてもプログラミング言語の作法が身につかなかったらこれを使おうと思います。
 プログラミング言語でゲームを作るのは、僕には本当に敷居が高いです。
最後に編集したユーザー moba on 2016年5月30日(月) 00:35 [ 編集 2 回目 ]

アバター
usao
記事: 1889
登録日時: 12年前

Re: GameLoop'でWait

投稿記事 by usao » 9年前

ゲーム作る人じゃないので話がよくわかりませんが,
「Aして,Bして,5秒待って,Cする」とかいう時,A,B,Cという作業や「待つという作業」を例えば

CODE:

//やるべき細かい仕事
class Work
{
  virtual void Proc();  //何か仕事を(ちょっとだけ)進める
  virtual bool IsFinished();  //仕事が終わったかどうか
}
みたいのを派生したクラスとして表現したらどうでしょう.
そうすると,↓みたいに,やるべきことリストとして作業のシーケンスを表すことができると思います.

CODE:

std::list WorkList;  //やるべきことリスト(リスト要素の型がWork*とかでいいのかわからんけど)

//ゲームループ
while( ... )
{
  //毎回,やることリストの先頭にある作業をちょろっと進める
  WorkList.front()->Proc();
  //やることリストの先頭にある作業が完了したなら,やることリストから取り除く
  if( WorkList.front()->IsFinished() )
  {  WorkList.erase( WorkList.begin() );  }

  ...
}
やるべきことリストの内容をあれこれ動的に変更すればきっと何かが進んでいきます.

ウェイトもWorkから派生したクラスとして作り,
単にProc()が規定回数呼ばれたらIsFinished()がtrueを返すものにしておけば良さそうです.
最後に編集したユーザー usao on 2016年6月03日(金) 13:54 [ 編集 1 回目 ]

moba
記事: 82
登録日時: 9年前

RE: GameLoop'でWait

投稿記事 by moba » 9年前

usaoさんありがとうございます。かなり便利になりそうです。

例えば
 攻撃演出、ウェイト、ダメージ処理・SE・ログ、ウェイト、終了演出、ウェイト
という場合や、
 SE、カーソル移動アニメーション、ウェイト
の場合も、Workを挿入していけばいいですね。

後は、

1.Work間のメッセージのやり取りを引数・返り値で行えない
2.ウェイトの前後で状態を引き継ぐのが手間になる(ウェイトコマンドが無いと)
3.Workによってゲームが動くのが前提になる

が課題かと思いました。

1.メッセージのやり取り
自分で考えたのは、これの派生クラスを渡しておく方法です。これで値を返します。

CODE:

template
class IReturnable
{
public:
	virtual void Return( T ) = 0;
	virtual IReturnable() {}
};
2.状態の保持
これはコルーチン? 無しなら仕方がないかと思いました。

CODE:

void TurnBasedGame::ExeActionAll()
{
	auto action = mActionQue[mActionIte].get();
	action->Perform(); // 演出やそのためのウェイトがgWorkQueにpush_back()される
	mActionIte++;
	if ( i < mActionQue.size() ) {
		gWorkQue.push_back(this); // WaitWorkが入らない限り止まらない仕組み
	} else {
		gWorkQue.push_back(new OnTurnEnd());
	}
}
3.Workに依存
Workがフレームワーク?だとしたら、
フレームワークにここまで依存していいのか疑問です。


プログラミング言語だと、よくない方向にいくらでも走っていけてしまうのが恐ろしいです(;一_一)
最後に編集したユーザー moba on 2016年6月03日(金) 23:19 [ 編集 6 回目 ]

アバター
usao
記事: 1889
登録日時: 12年前

Re: GameLoop'でWait

投稿記事 by usao » 9年前

このリストの機構を,プログラムの全作業の進行の骨子として使うよりも
なんらかの一連の演出的表示(アニメーション)の再生機構としてだけ使うとかが良いように思います.
メインの処理とは別に,並列に動く感じ,というか.

例えば,戦闘時に,AがBを殴ることになったとき,
・ダメージ計算→BのHPを減らす→BのHPが0以下だったら… とかいう実際のゲームデータに対する処理
・{攻撃モーション表示→ダメージ数値表示と同時にSE鳴らして→すこしウェイト→Bの絵が点滅→…}みたいな一連の表示演出
の2つの仕事が発生します.
前者だけやればいいなら,その場で処理してしまえばよくて,次の処理(次はCがAに2回攻撃して1回当たって6のダメージ…)に移り…
として,ゲーム処理を進めていくことができるわけですが,
後者側処理が 時間と共に少しずつ進めなくてはならない性質 なのが厄介なわけですよね.

そこで,
(1)AがBを殴る となったときに,まず前者側の処理を済ませる.
(2)その結果(ダメージ量とか,Bがまだ健在かそれとも倒れたのかとか)を踏まえた
  {攻撃演出、ウェイト、ダメージ数値の表示とか・SE、ウェイト、終了演出、ウェイト}みたいな表示用処理リストをここで作ります.
  (このリストの内容はあとは放っておいても進められていくわけです.)
(3)(2)のリストの表示の進行とは無関係に,ゲーム処理としては次の処理(CがAに2回攻撃して1回当たって6のダメージ…)を進めてしまってもよくて
  そこでまたCの攻撃に関する表示演出の必要性が発生したら,(2)のリストの末尾に追加すればいい.
  ...
(4)必要なときに(ターン性戦闘ならターンの最後とか適当な区切りで),表示リストの内容が全て完了することだけを待つ.

みたいな.

CODE:

//表示とかSEとか関係のやることリスト
//(このままだと名前が良くないけれども,わかりやすいように前回と同じ名前のままにしてます)
std::list WorkList;
 
//メインループ
while( ... )
{
  //---メッセージ処理とか---
 ...

  //---表示とかの進行---
  //毎回,やることリストの先頭にある作業をちょろっと進める
  WorkList.front()->Proc();
  //やることリストの先頭にある作業が完了したなら,やることリストから取り除く
  if( WorkList.front()->IsFinished() )
  {  WorkList.erase( WorkList.begin() );  }
 
  //---ゲーム処理---
  //ゲームの進行のための処理.(毎回どのくらいずつ処理が進むのかわからんけど)
  GameProc();
}

//ゲームを進める処理
void GameProc()
{
  if( 表示演出の完了を待つべきとき && !WorkList.empty() )return;

  ...
}

moba
記事: 82
登録日時: 9年前

RE: GameLoop'でWait

投稿記事 by moba » 9年前

本当にありがとうございます_(._.)_
イメージをつかめてきました。元々は混同していましたが、演出のためのウェイトであって、(MVCの)Modelにあたる部分は一瞬で更新してもいいですね。例外も思いつきませんでした。
演出(Work)がModelを参照する場合は、(Modelの更新時に)死亡したModelの消去を遅らせるなどしようと思いました。

Workについては、並列でもいいのですが、上に上げた例ならば状態を使ってみようと思いました。

// actionを実行し、返り値の演出を実行する
auto works = action->Perform();
stateMachine.Push(new DoingWorks(works)); // スタック式ステートマシンにDoingWorks状態をpush

Workと似た仕組みを考えたときは、全てのWorkを毎フレーム同時に実行し、ディレイが設定されていたらディレイカウントを減らすつもりでした。これだとディレイの設定が面倒だったのですが、Workが逐次実行で、WaitWorkがあるなら、分かりやすくていいですね。
また、いままで"コモンイベント"(「ウェイト」を使える関数)を使っていた部分を、状態で置き換えて考えられるようになってきました。
おかげさまでできそうな気がしてきました。ありがとうございます>_<!
最後に編集したユーザー moba on 2016年6月04日(土) 11:17 [ 編集 1 回目 ]