一定フレームの間自動で移動する。ゲーム・クラスの設計方法

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
YM

一定フレームの間自動で移動する。ゲーム・クラスの設計方法

#1

投稿記事 by YM » 14年前

前回ここでフラグの上手な命名法について教えていただきました。
後日クラス化について少し考え直すことがあったのですが,頭の中で思い描く理想的なコードを書き起こすまで至らず,
あるのは目の前にある(形だけ)きちんと動いているごちゃごちゃコードだけ。

考えているのが,自機移動を一定フレームだけ動かす時の処理方法です。
とりあえずこのような自機クラスを私は作りました。(関数外にCjiki jiki というグローバルインスタンスを作っています)

コード:

class Cjiki{
	private:
		int x;
		int y;
		int count;
	public:
		int get_x() {return x;}
		int get_y() {return y;}
		void add_x(int val) { x += val;}
		void add_y(int val) { y += val;}
		void sub_x(int val) { x -= val;}
		void sub_y(int val) { y -= val;}
		void init() {x = 100; y = 100;}
		void add_count() {count ++;}
		void reset_count() {count  = 0;}
		int get_count() {return count;}
};
(add,subは引数を自然数で扱いたかったため)
また,グローバル変数として f.xxxxxというフラグを別の場所にまとめてあります。(struct f { bool xxxxx; } )


具体的な話になってしまうのですが,
左キー入力があったとき,初期位置 x = 100 から x = 0 に移動させようと思ったときに,
jiki.sub_x(100) としたら瞬間移動してしまうので,ゆっくり動かすためにjiki.sub_x(10)を10回,1フレーム1回で実行します
左キーが押されたときにフラグ:f.MoveLeft をオンに,jiki_reset_count()実行
(別のところで)f.MoveLeftがオンならばjiki_sub_x(10)とjiki.add_count(),最後 if(count == 10) のときにf.MoveLeftをオフにしています。
他,左キーが押されたときに当たり判定とキー入力を一時的に無効にするフラグも同時にオンにしています(つまり3本オンに)

最近クラス化の概念を改めて見直し,メンバ関数に上記と同じ挙動を実現するvoid move_left()のような関数があるべきだと思いました。
そうすれば左キーが押される⇒jiki.move_left()を実行するだけで10フレームの間動いてくれて,読むほうも分かりやすいかなと。

しかし,10フレームの間動かすということは同じ処理を10回呼び出さないと行けないので,毎フレームのカウンタの取得と移動処理は必須です。
―という自分の考えだとメンバ関数にするメリットがほとんど感じられず,「じゃあ今のままでもいいか…」と悩んでいます。
この場合,どのようにしておくのが良いでしょう? いろいろと技量不足の状態で作ったプログラム全体を書き換えるのは結構辛いのですが,
自動移動処理を行うナイスな方法があれば教えてください。


また,メンバ関数が大きくなってきたときにちょっと気になったのですが,プログラムで使う全てのクラスの宣言は(上コピペコード) class.h
に書き,メンバ関数の定義を class.cppに書いておいて他ファイルは#include "class.h" するのがいいのか,それとも自機に関する
クラスを jiki.h に書いてクラスも分けた方がいいのでしょうか?
※jiki.cpp/h enemy.cpp/hがあったら,それぞれのヘッダにクラスの宣言,ソースファイルに定義(インスタンス作成)とメンバ関数の定義,その他の一般関数の定義を書いてある状態

YM

Re: 一定フレームの間自動で移動する。ゲーム・クラスの設計方法

#2

投稿記事 by YM » 14年前

追記
VC++2010 DXライブラリ を使っています。忘れていました。

アバター
Dixq (管理人)
管理人
記事: 1662
登録日時: 14年前
住所: 北海道札幌市
連絡を取る:

Re: 一定フレームの間自動で移動する。ゲーム・クラスの設計方法

#3

投稿記事 by Dixq (管理人) » 14年前

グローバル変数に状態があって、それに依存して自機が動く設計は良くないと思います。
自機の移動計算は自機の内部でやるべきであって、外から座標を計算するかのような設計になっているのは少し疑問です。
また、自機の移動パターンが様々あるのであれば、「動き」を計算するクラスを作り、そこに処理を「委譲」すれば良いかと思います。

今は公開関数があまりに具体的過ぎると思います。
なるべくブラックボックスかのように包み込み、外からは中がどんな仕組みであろうと簡単に操作できる仕組みが理想です。

例えば、テレビのリモコンで例えると、そこに公開している操作は「ボタンを押す」位のものですよね。
それが今は「回路ABCと回路DEFにパルスを走らせる」のような、詳細を熟知している設計者でなければつかえないものになっています。
リモコンを使う人は内部の設計を理解せずとも「ボタンを押す」だけで操作出来た方が便利ですよね。

更に、「自機」はもっと抽象的なものに分けられそうです。きっとボスや雑魚みたいなものも、共通化出来る所があると思いますから、
BaseCharactorのような抽象クラスから派生させると良いかと思います。
今のままではEnemyにもjikiにも似たような変数や処理が重複しそうです。

ということで、状態を表すフラグや、指定時間で既定の処理をする操作は自機の内部に実装してはいかがでしょうか。

アバター
a5ua
記事: 199
登録日時: 14年前

Re: 一定フレームの間自動で移動する。ゲーム・クラスの設計方法

#4

投稿記事 by a5ua » 14年前

【サンプルコード】
► スポイラーを表示
初心者にとって、割とわかりにくいコードになってしまいましたが、サンプルコードを書いてみましたので、参考にどうぞ

コードだけだと味気ないので、解説も少々。
Dixq (管理人) さんが書きました: また、自機の移動パターンが様々あるのであれば、「動き」を計算するクラスを作り、そこに処理を「委譲」すれば良いかと思います。
基本的には、この考えを実装しています

「動き」を計算するクラスが、Motionクラスであり、
様々な移動パターンに対応するために、Motionクラスから派生して、
CountingMoveMotionクラスやMouseTrackingMotionクラスを作っています。

そして、自機の移動処理(Jiki::step)は、Motionクラスに処理を「委譲」しています。
YM さんが書きました: 最近クラス化の概念を改めて見直し,メンバ関数に上記と同じ挙動を実現するvoid move_left()のような関数があるべきだと思いました。
そうすれば左キーが押される⇒jiki.move_left()を実行するだけで10フレームの間動いてくれて,読むほうも分かりやすいかなと。
これを実現しているのが、以下のコードです。

コード:

if (CheckHitKey(KEY_INPUT_LEFT)) {
	jiki.set_motion(counting_move(-10, 0, 10));	// 左に移動を10回繰り返す
}
現在のモーションが継続中の場合は、set_motionが呼び出されてもセットしないという設計にしているので、
キー入力の無効化と同等のことが実現できます。

ちなみに、このように処理の方法を外部から設定する設計は、Strategyパターンと呼ばれています。
Motionクラスは、座標(x, y)を移動させるStrategy(戦略)となっています。


本題とは関係ないですが、上に示したコードでは
Jikiクラスが保持するモーションの型として「Motion *」ではなく、「unique_ptr<Motion>」というクラスを使っています
unique_ptrはコンストラクタでnewした結果を受け取ることで、デストラクタで自動的にdeleteしてくれるクラスです。
unique_ptrを使用する利点は、newしたオブジェクトの所有者が唯一(unique)であることが保証できることです。
Motion *を使うと、そのポインタの所有者がどのオブジェクトにあるのか慎重に見極めなければなりません。
また、所有権の移動はstd::moveという関数で行うことができます。(Jiki::set_motionで使っています。)

YM

Re: 一定フレームの間自動で移動する。ゲーム・クラスの設計方法

#5

投稿記事 by YM » 14年前

お二人様ありがとうございます。言われたことを参考に
クラスの継承とやらを参考書で開いていろいろと試行錯誤しながら勉強することにしました(最中)。
そして他の上手な人の具体例を見て学べるのでコードのサンプルはとってもありがたいです。感謝します

ちなみに今後,このことに関して追加の質問があればこちらでよろしいですか?
それとも一度終了してから新しい題材でトピックを建てた方がいいでしょうか?

YM

Re: 一定フレームの間自動で移動する。ゲーム・クラスの設計方法

#6

投稿記事 by YM » 13年前

古い投稿を盛り返してすみません。

上記のありがたいサンプルコードを頑張って読み進めたのですが,オブジェクト指向はほぼ未経験だったので難しいばかりでなく,
unique_ptr<>やstd::move()などが使われていて,そこで何が起きているのかイマイチ理解できません。

これを理解できるようになるのは当分先だな,とは感じるのですが,
もしも可能ならばunique_ptrを使わない例と,引数についてもう少し詳しい説明を頂けないでしょうか。

精いっぱいの解釈をしてみました
キーボードを押された時の関数呼び出しの順に追って:
CountingMoveMotion* counting_move(1,2,3)
→ new CountingMoveMotion A される
→ return Aのポインタ(アドレス) 
※unique_ptrならいつかdeleteしてくれるらしい(?)

bool set_motion(Motion* motion) 
引数は基底クラスのポインタで,派生クラスのポインタを扱える。
引数に入るのは派生クラスのインスタンスAへのポインタ
return されたポインタを元に条件分岐するよう。

コード:

if (m_motion && m_motion->moving())    // Motion* m_motion = ?
    return false;
else
    m_motion = std::move(motion); // ←Motion* m_motion == ?
    return true;
良く分からないけれども,
メンバ変数Motion* m_motionにnewしたインスタンスAのアドレスが入れられた

m_motion->move(m_x,m_y) と・・・。




分かるようで分かりません><
よければよろしくお願いします。

アバター
a5ua
記事: 199
登録日時: 14年前

Re: 一定フレームの間自動で移動する。ゲーム・クラスの設計方法

#7

投稿記事 by a5ua » 13年前

unique_ptrを使わないバージョンです。といっても全体の構成はほとんど変わりません。
違いは、Jiki::set_motionにnewしたオブジェクトを直接渡していることと、
unique_ptrが行なってくれていたポインタの所有権の管理を自分で行なっている点です。
► スポイラーを表示

コード:

if (m_motion) {
	// 何か処理
}
という記述は、m_motionがヌルポインタでない場合処理をするという意味になります。
このコードは、以下の3つのコードと同じ意味です。

コード:

if (m_motion != NULL) {
	// 何か処理
}
if (m_motion != 0) {
	// 何か処理
}
if (m_motion != nullptr) {
	// 何か処理
}

コード:

if (m_motion && m_motion->moving()) {
	// 何か処理
}
このコードは、m_motionがヌルポインタの場合、
m_motion->moving()でヌルポインタにアクセスすることを避けるためです。
短絡評価により、m_motionがヌルポインタの場合、m_motion->moving()は評価されません。

YM

Re: 一定フレームの間自動で移動する。ゲーム・クラスの設計方法

#8

投稿記事 by YM » 13年前

2回目もありがとうございますmm これを原点に複数のクラスを使った処理の書き方や流れを勉強してみます

ちなみにC++では混乱したので,全て忘れて最初からオブジェクト指向性が強いらしい?C#を一からお試し中です。
ある程度学んで,再びC++に戻ってくる頃には少しは理解がはかどるのではないか,と信じて。

閉鎖

“C言語何でも質問掲示板” へ戻る