シーンについて再考する

アバター
usao
記事: 1889
登録日時: 12年前
連絡を取る:

シーンについて再考する

投稿記事 by usao » 2年前

シーンとかいうやつについて ちょっと前にごちゃごちゃ考えた けど,根本的な所を誤解していた気もするぜ!

というのは,そもそも

CODE:

class Scene
{//※面倒だし話と関係ないから他のいろんなメソッドとか仮想デストラクタとかは省略
public:
  virtual void Update() = 0;
};
っていう話じゃねぇよな,ってことだ.

シーンってのは「ゲームのステート」なんだから,こうだろうよ.

CODE:

class Game
{
private:
  class Scene{};  //※中身は省略
};
Sceneは完全独立した型じゃなくて,Gameという特定の型の具体実装の一部(を切り出したもの)なんだ.

だから,各Sceneの具体型の実装というのは,Gameなる型が「Sceneの各具体型のオブジェクトをどのように作ったり破棄したりしているのか(:長期保持しているのか動的に生成破棄しているのか),どんな順序でSceneのメソッドを呼んだり呼ばなかったりしているのか」等々のGameの具体的な実装形態の事情にまるっきり依存して良い存在なんだ.
例えば,GameがXXXSceneのオブジェクトを new で作っているならば,XXXSceneの実装はそのことを前提として唐突に delete this; とか書いたって良い.
そういう(その程度の)存在だ.

Game 型の実装は,そもそも複数種類の Scene の具体型を全て統一的に扱う必要すら無いんだから,

CODE:

class Game
{
  ...
private:
  XXXScene m_XXXScene;  //単純にインスタンスを保持
  YYYScene *m_pYYYScene;  //途中で new でもするのかな?
  std::shared_ptr<ZZZScene> m_spZZZScene;  //誰とshareする気だ?
};
とかなっていたってかまわないし,むしろそれが自然体だろうよ.
最後に編集したユーザー usao on 2022年9月16日(金) 18:14 [ 編集 1 回目 ]

アバター
usao
記事: 1889
登録日時: 12年前
連絡を取る:

Re: シーンについて再考する

投稿記事 by usao » 2年前

そしたらもう

> ・どうやって「カレントのシーン」を切り替えるの?
> ・シーンの具体実装のインスタンスって誰がいつ作るの? それを誰が保持して誰がいつ捨てるの?
> ・シーンAからシーンにBに移る際に相応の情報伝達が必要な場合,その情報ってのは最悪AとBの組み合わせ毎に異なり得るかもしれないのだけど,どうするの?
> ・「シーンA型」は「シーンB型」を知るべきなの?
> ・そもそもシーンA自身が「この場合は…シーンBにいくべきだ」みたいな話を知るべきなの?(遷移に関する判断の主体は誰なの?)
> ・etc...

とか,本当にどうでもいい事柄だったにゃん.
統一的な方法が必要なわけではないんだから,何ならAとBの組み合わせの数だけ好きな形で書き散らかせばいいんだニャ.

> 遷移に関する判断の主体

って,どこに書いてもそれは Game 以外の何物でもなかったにゃー.

アバター
usao
記事: 1889
登録日時: 12年前
連絡を取る:

Re: シーンについて再考する

投稿記事 by usao » 2年前

……と,まぁ, 役割的/意味的 には内部クラスなんじゃないの? と思うわけなんだけども,残念なことに,内部クラスってコード書きにくいというか見難いというかっていう点での辛さが半端ない気がするから困る.

CODE:

class 外側
{
  //ここにクラス定義を丸ごと書くのがうざい
  class 内部クラス
  {
    ...
  };
};
内部クラスが複数個とかなると,もう本当に嫌な気分になる.困る.
「じゃあコードを複数のファイルに分けようぜ」って考えるんだけど,なんかそれが随分と厄介な感じ.単に俺が頭悪いからうまくやれないだけなのか? 辛い.

CODE:

class 外側
{
  #inlclude "内部クラスの実装を書いたファイル"
};
とか一瞬考えてしまうくらいだが,もちろん,こんなことはしたくない.

アバター
usao
記事: 1889
登録日時: 12年前
連絡を取る:

Re: シーンについて再考する

投稿記事 by usao » 2年前

Scene なるインタフェースクラスと,それを継承した XScene とか YScene とかがあって,それらがみんな Game の内部型…ってことは,こうか.

CODE:

//Game.h
class Game
{
private:  //内部クラスの名前だけ列挙するぜ!
  class Scene;
  class XScene;
  class YScene;
  
  //この辺には↑の内部型の定義が必要なコードは書けない
};
で,Scene クラスの定義を書くヘッダがあって…

CODE:

//Scene.h
class Game::Scene
{  /*略.仮想関数群が並ぶと思われ*/  };
XScene クラスの定義を書くヘッダもあって…

CODE:

//XScene.h
class Game::XScene : public Game::Scene
{  /* 略.override がどうのとか */  }
そしたら XScene クラスのメソッド群の実装を書くcppがこんな感じである,と.
includeの順序を気を付けないとならんね.

CODE:

//XScene.cpp
#include "Game.h"  //まずこれを書いて
#include "Scene.h"  //次がこれで
#include "XScene.h"  //最後がこれ

XScene::XScene() { ... }
YScene も XScene と同様.

Game.cpp の側も同様に,include 順を間違わずに書く必要があるね.

CODE:

#include "Game.h"  //まずこれを書いて
#include "Scene.h"  //次がこれで

#include "XScene.h"
#include "YScene.h"

Game::Game(){ ... }
まぁ,こんな感じで分けることは可能か.
でも例えば ZScene が増えたら,XScene.cpp や YScene.cpp もコンパイルし直しになるっていう.つらい.

アバター
usao
記事: 1889
登録日時: 12年前
連絡を取る:

Re: シーンについて再考する

投稿記事 by usao » 2年前

とかなんとか,
「内部状態なんだから内部クラスで書けばいいだろ」派と
「内部クラスはコードの扱いが何か面倒だからやめたい」派が
脳内で無意味にせめぎ合っていたんだけど,
質問掲示板の方に「選択肢」とかいう話題が来ていて,どっちかというとそっちの方が興味が移った感.

---

要はあれだよな,RPGとかでボタン押すと
{しらべる,アイテム,…}みたいなのが出てきて,選んだ項目次第ではさらに入れ子のメニューが出てきて… っていうやつだな.

大元のメニュー →(アイテムを選択した場合は)→ アイテムを選ぶメニュー → 選んだもの次第では対象を選択するメニュー

みたいな.
つまり,最終的な選択が行われた結果として
「アイテムXをキャラクタYに使用して,アイテムXの所持数が1個減る」とか
「調べたけど何もなかった」とか
何かしらのアクションが起きるわけだ.
ある場所である方向を向いてあるアイテムを使ったらなんかイベントがどうのとか言い出すかもしれない.
うーん,これは面倒そうだね.

アバター
usao
記事: 1889
登録日時: 12年前
連絡を取る:

Re: シーンについて再考する

投稿記事 by usao » 2年前

メニュー → サブメニュー っていうGUI部品的な関係性については,まぁスタック的な感じの実装ができるんだろうな,と思うけど,
問題は「そいつらが何らかのコンテキストを共有して1つのアクションを決定する」という点をどう作るん?っていうあたりかな.
メニューの選ばれ方によってアクションの種類は全く異なるものになるし.

あと,そのアクションが実施された際に,メニューの状態はどうなるんだろう? とかを制御したい,というのもあるだろう.
同じ「アイテムを使う」処理でも,
ポーションを1個使ったらメニューが丸ごと閉じるとかだと不便すぎるから,事後のメニュー状態としては「使うアイテムを選択する」階層(か,使う対象を選ぶ階層)の状態に行って欲しい気がするけど,
前述したような「イベントが起きる」とかの場合はメニューはイベントが始まる時点では全部閉じて欲しいだろうし.

なんだかなぁ,
普段何気なく使っているウィンドウとかダイアログとかコントロールがメッセージドリブンで動いているような「仕組み」を自分で一から全部実装しないといかんみたいな話だとしたらすっごい嫌だから,あまり「共通な動き」みたいなのを考えずに,べたべたに書いた方が良いのかもしれんね.

アバター
usao
記事: 1889
登録日時: 12年前
連絡を取る:

Re: シーンについて再考する

投稿記事 by usao » 2年前

話をシーンに関する物に戻す.

大抵,「Scene とかいうインタフェースクラスに Update() なる仮想関数があって…」みたいな話になってるけど,この Scene::Update() の戻り値でシーン遷移の要求とかを返したい,って思うわけ.

というのは,(以前の日記にちょろっと書いたけども)
【Scene::Update() の内部から「別シーンに遷移させるための関数/メソッド」を呼ぶ】という形だと,その関数/メソッドから処理が返るよりも前に呼び出し元オブジェクト(:カレントのシーン)の寿命が尽きるかもしれない…という不安に駆られるので,何か個人的に嫌なのだ.
だから,そういう要求は Scene::Update() からreturnした後で実施して欲しい.

そのための機構(「別シーンに遷移させるための関数/メソッド」は即座にそれを実施するのではなくて要求をプールしておいてどうの…っていうような?)をわざわざ Scene::Update() を呼ぶ側の世界に用意する…っていうような話も考えられるけど,無意味にまわりくどい.
そんなのを作り込むよりは,「処理結果は戻り値で返すぜ」っていう C や C++ の最も基本的な形の方でやりたい.すなわち,要求を return する,っていう形にしたい.

もちろん,Scene::Update() が返すべき情報(というか要求と言うか)は他にも色々とあるだろうから,だったら「要求の集合」を return すればいい…のかな.
最後に編集したユーザー usao on 2022年9月28日(水) 16:17 [ 編集 1 回目 ]

アバター
usao
記事: 1889
登録日時: 12年前
連絡を取る:

Re: シーンについて再考する

投稿記事 by usao » 2年前

> 「要求の集合」

とは何か?

CODE:

struct 有りうる要求の集合
{
  bool Is_AAA_Requested;  //AAAを要求するか?
  bool Is_BBB_Requested;  //BBBを要求するか?
  ...
  シーン遷移のための何らかの情報;  //←どう書くかわからんけど
};
みたいなのだと,コレを return された側のコードが面倒な形(ifの列挙とか)になりそうだから嫌だ.

各要求をオブジェクトだとして

CODE:

class Scene
{
   ...
   virtual 【0個以上の要求】 Update( /*何か*/ ) = 0;
};
とかいう形にすれば,受け取った側はループで処理できそうだ.

Scene::Update() が返す「要求」とは,性質上は全体的な処理フローに関する要求であろうから,例えばそういう雰囲気の名前で

CODE:

class IFlowControl
{
   ...
   virtual void operator()() = 0;  //operator() でいいか.引数や戻り値は必要な形にするとして.
};
とかいうようなのを用意してやれば良いか.

【0個以上の要求】とは具体的にはどんな型なん?

CODE:

class Scene
{
   //このメソッドを呼んだ側は,
   //このメソッドの戻り値の全要素について(先頭から順に) operator() を呼ぶぞ,っていうルールにする.
   virtual std::vector<IFlowControl*> Update( /*何か*/ ) = 0;
};
みたいな?
「とにかく呼び出し側では(例外とかで死亡しない限りは)必ず戻り値の全要素の operator() を呼ぶから.大丈夫だから.」って話にしておけば,IFlowControl* っていう生ポインタな型でやりとしても何とかなるだろう…っていう気がする.

アバター
usao
記事: 1889
登録日時: 12年前
連絡を取る:

Re: シーンについて再考する

投稿記事 by usao » 2年前

で,実際に IFlowControl の具体実装を書けるのは,Scene よりも外側の世界であろうから,
Scene からすれば,何かわからんけども具体実装(のインスタンスへのポインタ)を取得する手段があればいい,ハズ.

…ってことは,結局のところ,

> 【Scene::Update() の内部から「別シーンに遷移させるための関数/メソッド」を呼ぶ】

が,

【Scene::Update() の内部から「別シーンに遷移させるための手段(上記ポインタだね)を得るための関数/メソッド」を呼ぶ】

になるっていうだけなんだな.うん.

CODE:

//ここでは謎の引数から要求オブジェクトへのポインタを得られるのだとして…
std::vector<IFlowControl*> XXXScene::Update( Arg &arg )
{
   ...
   return { arg.ChangeSceneToYYY_Request(), arg.Redraw_Request() };  //YYYシーンへの遷移要求と,再描画要求
}
みたいな.
まぁ実際には状況次第でどの要求を出すべきかっていう判断があるだろうから,そうそうこんな形(一行)では書けないのだろうけど.

なんだか微妙に「もうちょっと何とかならねぇの?」っていう感じがしないでもないけど,とりあえずこんな形にすれば,
Scene 側は自身が いつ/どのように 生成/管理/削除 されてるのか,みたいな外側の都合のことを一切把握しなくても良さそうだ.
YYYシーンへの遷移時には何か情報(データ)を与える必要があるぞ,みたいな場合は arg.ChangeSceneToYYY_Request() に引数を持たせればいいのだし.

---

…みたいなことを,寝る前とかに微妙な意識状態でぐだぐだ考えてみたりするんだけども,行き着く先は良く見る形でしかないっていう.
最後に編集したユーザー usao on 2022年9月28日(水) 16:15 [ 編集 1 回目 ]

アバター
usao
記事: 1889
登録日時: 12年前
連絡を取る:

Re: シーンについて再考する

投稿記事 by usao » 2年前

From 今の俺 to ちょっと前の俺:

> 【Scene::Update() の内部から「別シーンに遷移させるための関数/メソッド」を呼ぶ】という形だと,その関数/メソッドから処理が返るよりも前に呼び出し元オブジェクト(:カレントのシーン)の寿命が尽きるかもしれない…という不安

とか何とか言ってるけどさぁ.そもそもこんな不安が生じるのは
「各シーンのオブジェクトが必要に応じて誰かによって動的に生成され,不要になったタイミングでこれまた誰かによって唐突に破棄されたりする」
という,一般的(?)な形を 漠然と 考えているからだよな.



しかし,そもそもこのこと自体が根本的に間違っているとしたらどうかね?
つまりだね,シーンオブジェクトの生成と破棄のタイミングってのを,各シーンクラス側の仕様にしてしまえるとしたら,その「不安」は消滅するのではないのかね?

だってさ,同一シーンのオブジェクトを同時に複数個作る機会ってのは多分無いに等しいよな?
同一ステートのオブジェクト2つ作って何かうれしいんか? お?

そしたらもうね,こいつらの実装なんて,例えば「インスタンスは static メンバです!」とかそんなのでも十分通用するんちゃうの?
「シングルトン(笑)」ではないけども,それに近い雰囲気のやつだな.