せんちゃの修行日記~修羅場だけど私は元気です~

アバター
せんちゃ
記事: 50
登録日時: 15年前
住所: 江別市東野幌町
連絡を取る:

せんちゃの修行日記~修羅場だけど私は元気です~

投稿記事 by せんちゃ » 12年前

もうプロジェクトも終盤に差し掛かってきています、
終電帰りがデフォになりつつある…

とはいっても土日は休ませてもらえるのでまだラッキーなほうでしょうか…
この2週間くらいは先輩にフルボッコにされる日々でした…
自分のコードの粗がバンバン指摘され「なんで面倒なつくりにするんだこの野郎!ハゲ!死ね!」的な感じで、ね。
※ハゲとか死ねとかは言われてません


タスクシステムといいますかなんというか
一つ一つのタスクが独立したつくりでなければいけない、その一方で変化があった時のトリガーがほしい時もあれば
個々に各々演技をさせる必要もあり…
じゃあそのタイミングってどうやって手に入れるの?って話になり、私はこれに結構悩まされました。


クラスのインスタンスをstaticなポインタに渡して外に公開するという愚行を働き結果的にめちゃめちゃ怒られました。

その方法論はどうしたらいいもんか…
というときに所謂「親子階層」を作ってしまうというわけですね。
一つ一つのタスクを管理する親タスクがあって、仮にParentTaskとしましょうか…

CODE:

// 子タスク
// 使い捨てなのでヘッダーには公開しない
class Child1 : public Task{
protected : 
void update(){}
};

class Child2 : public Task{
protected : 
void update(){}
};

class Child3 : public Task{
protected : 
void update(){}
};

// シーケンス
class Scene{
private :
    ParentTask parent; // これは何にもなりうる。UIレイアウトのコントロールとか。

public :
void setup()
{
    // ParentTaskの中にはリンクリストのChildが存在する。そこにタスクを積むということ
    parent.entryChild( new Child1() );
    parent.entryChild( new Child2() );
    parent.entryChild( new Child3() );
}

};
こいつらChildはそれぞれなにも干渉しません。
全員独立していて各々がupdateを処理します。
じゃあなにか命令があったらどうするの?というのが問題点。

親タスクには実は自身が持つ子タスク達に一斉にコールバックを呼ばせる仕組みが用意されます。
たとえば...


CODE:

// 子タスク
// 使い捨てなのでヘッダーには公開しない
class Child1 : public Task{
protected : 
virtual void update(){}
virtual void command( int param )
{
    if( param == CMD_SND_01 )
    {
        playSound( "SE_1" );
    }
}

};

class Child2 : public Task{
protected : 
void update(){}
virtual void command( int param )
{
    if( param == CMD_SND_02 )
    {
        playSound( "SE_2" );
    }
}
};

class Child3 : public Task{
protected : 
void update(){}
virtual void command( int param )
{
    if( param == CMD_SND_03 )
    {
        playSound( "SE_3" );
    }
}
};

// シーケンス
class Scene{
private :
    ParentTask parent; // これは何にもなりうる。UIレイアウトのコントロールとか。

public :
void setup()
{
    // ParentTaskの中にはリンクリストのChildが存在する。そこにタスクを積むということ
    parent.entryChild( new Child1() );
    parent.entryChild( new Child2() );
    parent.entryChild( new Child3() );
    parent.commandToChilds( CMD_SND_01 );
}

};
commandToChildsは適当に考えた関数です。というか上記のコードは仕事のコードではありません。
守秘義務の関係上すべて私が日記を書きながら即興で考えた名前です。ライブラリもこれっていうものは考えてません。

commandToChildsは子タスク達にコマンドを送ります。
こいつらはイテレーターで反復処理され、command()メソッドがそれぞれ呼ばれます。

たとえば音声01を再生したいならCMD_SND_01をパラメータにしてあげれば、
それを受け取ったタスクが「じゃあ音声ならすわ!」といって音声を鳴らしてくれます。

反応するのはChild1クラスのcommandです。
それ以外は無視されます。
コマンドには引数すら必要ないこともあります。
あくまでもこれはトリガーなのであって、トリガーが立った時に親が公開しているパラメーターを見に行ったって問題はありません。
公開したいデータだけstaticにしてしまう。
親となるクラスだけでstaticを管理すれば比較的管理も楽です。外のクラスからは参照しかできないようにすれば良い
セットしたいのであれば要検討となりますが、どのみち窓口を作ればさまざまな事象に対応しやすそうです。


これはあくまでも一例ですが、
たとえばゲームのメニュー画面とか、STGならスコア画面やゲームボードなどの所謂UI絡みは
一つの「レイアウトクラス」としてのタスクリストで制御するとわかりやすい。


たとえば、ある位置にカーソルが指定された
指定されない画像は半透明にしてカーソル番号が一致した画像だけ普通に表示する。
というのは

CODE:

void update()
{
    if( Scene::getCursorIndex() == m_MyCursorNumber ){
        setAlpha( 255 );
        return;
    }
    setAlpha( 128 );
}
と基底に書けばあとは継承したすべてのUIはm_MyCursorNumberにそれぞれの担当する番号を初期値に振れば
あとは各々が判断して透明になったりならなかったりしてくれます。


選択画面では街頭する項目にカーソルを合わせたら基本的に決定キーを押します。
押されたらUIがどっかに飛んでいくと非常に格好いいです。

ボタンが押されたタイミングでparentがchildたちに「動け!」とコマンドを送ると、
それぞれ動いてくれそうな感じがしますね。

CODE:

void Child1::command( int param )
{
    if( param == CMD_MOVE ){
        vect.x = 10;
    }
}

void Child2::command( int param )
{
    if( param == CMD_MOVE ){
        vect.x = -10;
    }
}

void Child3::command( int param )
{
    if( param == CMD_MOVE ){
        vect.x = 10;
        vect.y = 10;
    }
}

void Child4::command( int param )
{
    if( param == CMD_MOVE ){
        vect.x = -10;
        vect.y = 10;
    }
}
なんて子タスクごとにパラメーター指定してあとはupdateのたびposにvectを計算してあげた場合、
こいつらは右に行ったり左に行ったりななめに行ったり個々にアクションします。
なんかそれだけで本格的なゲームっぽい演出が作れそうです。


つまりは情報を参照するときは「子は親の情報を頼りにする」
というのが望ましい。
逆に親が子の情報に依存するとたちまち崩壊してしまうと、つまりはそんな話なのですね。
そして公開範囲はなるべく限定的に。。。


そんなこんなのUIの操作の仕方を勉強の一週間でした。
まだまだ書きたいことは山ほどあるなぁ…

アバター
h2so5
副管理人
記事: 2212
登録日時: 14年前

Re: せんちゃの修行日記~修羅場だけど私は元気です~

投稿記事 by h2so5 » 12年前

せんちゃ さんが書きました: あくまでもこれはトリガーなのであって、トリガーが立った時に親が公開しているパラメーターを見に行ったって問題はありません。
公開したいデータだけstaticにしてしまう。
親となるクラスだけでstaticを管理すれば比較的管理も楽です。外のクラスからは参照しかできないようにすれば良い
セットしたいのであれば要検討となりますが、どのみち窓口を作ればさまざまな事象に対応しやすそうです。
staticにするというのは、具体的にどんな感じになるのでしょうか。うまく想像が付かないもので。

アバター
せんちゃ
記事: 50
登録日時: 15年前
住所: 江別市東野幌町
連絡を取る:

Re: せんちゃの修行日記~修羅場だけど私は元気です~

投稿記事 by せんちゃ » 12年前

特に難しい意味合いではなく、普通にクラス変数という役割でのstaticですね。
日記に書いた考え方ですとカーソル番号(int型)はstaticで、これは一番上の階層にあるSceneクラスが持つデータとなります。
一番上が公開しているstaticなクラス変数は下の階層のタスクが参照して、それに応じて動きます。
子を掌握するコントロール源は親ということですね

ISLe
記事: 2650
登録日時: 15年前

Re: せんちゃの修行日記~修羅場だけど私は元気です~

投稿記事 by ISLe » 12年前

わたしはpush型ではなくpull型で実装しますね。
むかしはループを回すだけでけっこうな負荷だったのでその名残でもありますが。

連携するオブジェクトはコンテキストを持つので、コンテキストにイベントキューを含めておいて、各オブジェクトは自分に関係のあるイベントが発生したかどうかを必要に応じてチェックするという方法です。

ゲームプログラムで神の視点を無くすことはできないですが、神は特定のオブジェクトを贔屓してはいけないです。
贔屓とは、同列以下にいるオブジェクトの情報を教えてやることです。
キャラが動けないときは、囲まれていると教えるのではなく、動ける方向が無いとだけ伝える。
メニュー項目なら、お前は何番でいま何番が選択されていると教えるのではなく、お前は選択されている/いないとだけ伝える。

親(というか上位層)に頼るといっても、親の情報にアクセスすべきではないと考えます。
プログラムでは子は親からの指示のみで動くというのが理想ではないでしょうかね。

(追記)
pull型のメリットをいくつか思い出しました。
イベント通知のインターフェースを整備してなくてもイベントの中身の検証ができます。
なのでイベントの仕様を固めてからインターフェースの仕様を決めることができます。
最後に編集したユーザー ISLe on 2013年3月16日(土) 18:46 [ 編集 2 回目 ]

taketoshi
記事: 222
登録日時: 14年前

Re: せんちゃの修行日記~修羅場だけど私は元気です~

投稿記事 by taketoshi » 12年前

なんか日記のタイトルで魔女の宅急便を思い出しました。

すいません意味不明なコメントで。春なんで許してください。
最後に編集したユーザー taketoshi on 2013年3月17日(日) 16:18 [ 編集 1 回目 ]

アバター
せんちゃ
記事: 50
登録日時: 15年前
住所: 江別市東野幌町
連絡を取る:

Re: せんちゃの修行日記~修羅場だけど私は元気です~

投稿記事 by せんちゃ » 12年前

日記タイトルは結構糸井重里の言葉を元ネタにしているのでその反応はむしろ期待していたものなので大丈夫ですよ!

アバター
せんちゃ
記事: 50
登録日時: 15年前
住所: 江別市東野幌町
連絡を取る:

Re: せんちゃの修行日記~修羅場だけど私は元気です~

投稿記事 by せんちゃ » 12年前

>ISLeさん
そうですね、プッシュはどちらかというとあい個人的には初回のタイミングがほしい時などに利用しているような気がします。
基本は更新処理で各々がどうするべきかを判断する形式でしょうか。
プッシュとプルの関係はゲームプログラミングではよく出てくるようなので私自身まだ使い分けができていない状態ですが、これも慣れなのでしょうか。

ISLe
記事: 2650
登録日時: 15年前

Re: せんちゃの修行日記~修羅場だけど私は元気です~

投稿記事 by ISLe » 12年前

せんちゃ さんが書きました:基本は更新処理で各々がどうするべきかを判断する形式でしょうか。
プッシュとプルの関係はゲームプログラミングではよく出てくるようなので私自身まだ使い分けができていない状態ですが、これも慣れなのでしょうか。
UIの場合フレームワーク側から見たらプッシュ型のメソッドが多くなると思いますけど、アプリケーション層ではもっぱらプル型の実装ですね。

プッシュ型は特定の状態を持つオブジェクトに対して参照を保持しておいて状態の変化を通知するような場合に使って、不特定多数のオブジェクトに通知する場合はプル型を使うようにしてます。
先に書いたようにループのコストが気になるからですが、環境によっても違いますし、一般的にはどうなんでしょうね。