添削お願いいたします

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
onehst
記事: 14
登録日時: 9年前
住所: 東京都

添削お願いいたします

#1

投稿記事 by onehst » 8年前

このたび簡単なゲームを作成いたしました。
ゲームは「ストッパーキューブリッジ」といってフレンドパークにあったミニゲームの1つです。
(下は参考動画のurlです)


本家に比べて多少異なる点はありますが(マスがないなど)
どなたかゲームの設計の仕方、処理などにおいて添削をしたいただきたいと思う所存です。
よろしくおねがいします。
添付ファイル
GameProg2.zip
DebugとDxLibフォルダはいれてません
(10.68 KiB) ダウンロード数: 162 回

inemaru
記事: 108
登録日時: 9年前

Re: 添削お願いいたします

#2

投稿記事 by inemaru » 8年前

DrawTitle.cppは不要と判断しました。

ソースコードのやりとりを考慮して、
開発環境の情報を書いた方が良いと思います。

一応共有の情報として
 文字セット :マルチ バイト
 IDE :VisualStudio 2013
の環境でビルドできました。

失礼しました、プロジェクトデータが含まれていたので判断できますね。

軽くチェックしましたが”メモリリークしています”
メモリリークするプログラムは、PCのメモリを圧迫するので注意した方が良いです。
new など動的に確保したメモリの開放を忘れていませんか?

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

Re: 添削お願いいたします

#3

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

C++のクラスで設計しているようですが、考え方がオブジェクト指向になっていません。
ゲームプログラミングの館の設計編で、オブジェクト指向の触りだけ紹介しているように、
各モジュールは
・Initialize
・Update
・Draw
・Finalize
をセットにします。
ピュアCでなくクラスで設計するならInitializeはコンストラクタ、Finalizeはデストラクタに置き換えても可です。

各モジュールの計算・描画は同じクラスでやるべきで、
DrawクラスやCalcクラスがあるのはおかしいです。
取りあえずオブジェクト単位でクラス化し、クラスにはupdateとdrawを必ず実装するようにしてみましょう。

試しに自機移動とショット打つことが出来るだけのサンプルプログラムを書いてみました。
http://dixq.net/BBS/zip/bulletSample.zip
(DxLibも一緒に入れてあるので重いです。VisualStudio2015でコンパイルしてください)

実行結果
result.png
result.png (15.37 KiB) 閲覧数: 5036 回
左右キーで移動、Zで発射できます。

説明はソースコードの中に書いてあります。
プレイヤー(Player)クラスと弾(Bullet)クラスを主に見てください。
(BulletのmanagerクラスはGameMgrと一緒にしてあっていい加減なので流してください)

/** Player.cpp **/

コード:

#include "Player.h"
#include "DxLib/DxLib.h"
#include "Keyboard.h"

//円周率
#define PI 3.141592654f

Player::Player(IBulletRegister *registable)
{
	_registable = registable;//弾を登録するインターフェイスの実装インスタンスポインタ
	_image = LoadGraph("player.png");//プレイヤー画像ロード
	_x = 320;//プレイヤーの初期位置
	_y = 440;
}

Player::~Player()
{
	DeleteGraph(_image);//画像解放
}

bool Player::update()
{
	//左キーが押されていたら左へ移動
	if (Keyboard::getIns()->getPressingCount(KEY_INPUT_LEFT) >= 1) {
		_x -= 2.f;
	}
	//右キーが押されていたら右へ移動
	if (Keyboard::getIns()->getPressingCount(KEY_INPUT_RIGHT) >= 1) {
		_x += 2.f;
	}
	//Zキーが押されていたら弾を登録
	if (Keyboard::getIns()->getPressingCount(KEY_INPUT_Z) == 1) {
		//-PI/2は-90°なので上方向。4.0は弾の移動速度
		_registable->registerBullet(new Bullet(_x, _y, -PI/2, 4.0));//弾を登録
	}
	return true;
}

void Player::draw()
{
	DrawRotaGraphF(_x, _y, 0.5, 0.0, _image, TRUE);//プレイヤーを描画
}
コンストラクタが初期化処理をしており
デストラクタが終了処理をしており、
更新処理であるupdateと
描画処理であるdrawがあるゲームにおけるクラスの実装の仕方の典型例です。

ここでちょっと工夫してあるのが、インターフェイスクラスを使っていることです。
プレイヤークラスは弾クラスのリストを持っているわけではないから追加できません。
インスタンスを持っている親からインスタンスの方向へ指示は出来ますが、
持たれているインスタンスから持っている親へ指示を出すことはできません。

こんな時は親クラスのポインタを持ちます。
このとき親クラスのポインタをそのまま持つとpublicな処理が全部できてしまい権限を渡し過ぎるので、
必要最小限の権限を渡すことが大事です。
このときIBulletRegisterというインターフェイスクラスを実装させ、そのポインタを持つことで親クラスのメソッドを呼ぶことができるようになります。
それが
_registable->registerBullet(new Bullet(_x, _y, -PI/2, 4.0));//弾を登録
の部分です。

また、Task.hを継承するようにしたらupdateとdrawを必ず実装しなければなりません。
これを必ず実装する様にコーディングしてみてください。
プレイヤーならプレイヤーの更新処理と描画処理、
弾なら弾の更新処理と描画処理がセットになるはずです。

また、今回は省略しましたが、ポインタには
http://cpprefjp.github.io/reference/mem ... d_ptr.html
を使うようにしましょう。

onehst
記事: 14
登録日時: 9年前
住所: 東京都

Re: 添削お願いいたします

#4

投稿記事 by onehst » 8年前

inemaru様、管理人様 添削ありがとうございました。
一度に御二方の返信を行なうのをお許しください。

>軽くチェックしましたが”メモリリークしています”
確認したところdeleteを忘れていました。ありがとうございます。
今回はブロックの数を10個に固定したのですが、静的配列(インスタンス)の引数付きのコンストラクタの呼び方が分からなかったのと、
この数はのちのち変わるであろうと考えたので動的確保(New)を使ったのですが、どんな場合でも動的確保を使わず、
場合に応じて静的確保も使った方が良いのでしょうか?

>C++のクラスで設計しているようですが、考え方がオブジェクト指向になっていません。
管理人様にご指摘いただき、ゲーム設計の部分の説明を一通りよんだのですが、明らかに習得不足かつ勘違いをしていました。
この失敗を2度としないように正しい設計の定着に努めます。

>VisualStudio2015でコンパイルしてください。
誠に初歩的なことで申し上げにくいのですが...
今まではVisual C++ 2008 Express を使って設計、コンパイル等を行なっていました。
というのもVisual studio 2015 Communityをダウンロードしてやってみたものの、どのようなプログラムでも次のような
エラーがでてビルドができませんでした....(Windows 7です)

コード:

重大度レベル	コード	説明	プロジェクト	ファイル	行	抑制状態
警告	D4024	unrecognized source file type '/', object file assumed	
警告	LNK4001	オブジェクト ファイルが指定されていません。ライブラリを使用します。	
警告	LNK4068	/MACHINE の指定がありません。X86 を既定とします。	
エラー	LNK1561	エントリー ポイントを定義しなければなりません。
多少調べて解決策を探したものの、よく分からなくなってしまいVC2008 Expressのほうはビルドが出来たので、
未熟な私はこちらの方だけを使っていました....

なのでVS2015でのコンパイルが現在不可能です。申し訳ありません。

サンプルプログラムと管理人様のアドバイスを少しばかり拝見させてもらったところ
クラスの中に・初期化処理 ・更新(計算) ・描画 ・終了処理の4つをいれるということは分かりました。

しかし、インターフェース以下の部分がさっぱりです...
Taskという純粋仮想関数を持った抽象クラスをPlayerが継承するときにupdateとdrawを実装する(オーバーライドする)
と言うことは理解したつもりなのですが...
GameMgrクラスの中に入っている

コード:

 std::list<Bullet*> _bulletList; 
はどういった意味なのでしょうか..?  

その他にも Singletonクラスの意味、使い方など分からない点があり、
もともとインターフェースやテンプレートをきっちり理解していないという己の無知さをあらためて感じさせられました。
きちんと勉強してこようと思います。すこしお時間をいただければなと思います。

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

Re: 添削お願いいたします

#5

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

VisualStudio2008は8年も前の書き方でないと書けないので早く2015に移行した方が良いですよ。
C++11等の便利な最近の書き方ができるようになっています。

DXライブラリのインストールの説明の通りの一つ一つ設定していけばコンパイル通るはずです。
http://dxlib.o.oo7.jp/use/dxuse_vscom2015.html
こちらをよく読んでやってみてください。

std::list<Bullet*> _bulletList;

これはC++のSTLを使ったリストです。
https://www.google.co.jp/search?q=C%2B%2B+STL
Bulletのポインタを要素に持つリストの定義を書いているのです。

C++には標準で便利機能がありますので、STLを学んでみてください。
C言語では難解だったリスト構造などがすごく簡単に使えます。
特に弾のようにリストに追加削除が頻繁に起こるような物には重宝します。

Singletonクラスはテンプレートなどを使っているので少々難しく感じるかもしれません。
これはこれからC++を学ぶ際に必ず付き合うことになるであろう「デザインパターン」という設計方法のひとつです。
必ず一つしかインスタンスが存在しないことを保証できる設計の方法で、キーボードなどの一つしかありえないものなどによく使用します。
最初のうちは、Singletonクラスとキーボードクラスはコピペで使いまわしてブラックボックスだと思っていいと思います。

とりあえず私のプロジェクトが実行できる環境を用意しましょう。
インターフェイスクラスが分からなければ、最初は強引に実装元のクラスのポインタをそのまま実装先のインスタンスへ持たせてもいいので
コールバックして実装するような設計をやってみましょう。
詳しく言えば、GameMgrのthisをnew Player(this);のように渡してPlayerクラス内からGameMgrのメソッドが呼べるようにするということです。

inemaru
記事: 108
登録日時: 9年前

Re: 添削お願いいたします

#6

投稿記事 by inemaru » 8年前

onehst さんが書きました: 静的配列(インスタンス)の引数付きのコンストラクタの呼び方が分からなかったのと、
C++11の基礎サンプルに、
静的配列の初期化で引数付きのコンストラクタを呼び出す方法を織り交ぜました。
VS2015を使用するのであれば、知っておいた方が良いかと思いサンプルを作ってみました。

C++11のコードがコンパイルできる環境でコンパイルしてください。

コード:

// C++11 サンプル
#include <iostream>
#include <array>

// 基底クラス
struct BaseObject
{
	// コンストラクタ
	BaseObject(){}

	// デストラクタ
	virtual ~BaseObject(){}

	// メンバ変数を書き換えないメソッドなら
	// constメソッド(constメンバ関数)にする
	virtual void Disp() const {
		std::cout << "BaseObject" << std::endl;
	}
};

// 継承させないクラスは final をつける
// final をつけたらデストラクタにvirtualをつけない
struct Object final : public BaseObject
{
	// コンストラクタ
	Object()
		: data_(0)  // 初期化を行う際は、「コンストラクタ 初期化リスト」を使用する
	{}

	// 引数付きコンストラクタ
	// 暗黙の呼び出しを許容しないのであればexplicitをつける
	/*explicit*/ Object(int data)
		: data_(data)
	{}

	// デストラクタ
	~Object()
	{}

	// オーバーライドしていることを保証するには override キーワードをつける
	void Disp() const override{
		std::cout << data_ << std::endl;
	}
private:
	// メンバ変数(フィールド)
	// 命名規則はC++の「Googleコーディングガイド」を参考にすると良いと思います。
	// 例:メンバ変数は_で終わるようにする
	int data_;
};

// C++11 は型を後置きできる
auto main() -> int
{
	// STLの関数やクラスはstd名前空間に定義されている
	// 名前空間の使用宣言をすれば std:: を省略できる
	// using namespace std;

	// 静的配列で引数付きコンストラクタを呼ぶ方法
	std::array<Object, 10> objArray = {
		0,  // [0]
		1,  // [1]
		2,  // [2]
		3,  // [3]
		4,  // [4]
		5,  // [5]
		6,  // [6]
		7,  // [7]
		8,  // [8]
		9   // [9]
	};

	// STLのコンテナはインスタンスからサイズが取得できる
	std::cout << "objArrayのサイズは、" << objArray.size() << std::endl;

	// autoは「型推論」
	// autoValueはdouble型になる
	auto autoValue = 3.14;
	std::cout << "autoValueの型は、" << typeid(autoValue).name() << std::endl;

	// 変数から型を取得できる
	// decltype(autoValue)
	decltype(autoValue) doubleValue = autoValue;

	// ラムダ式
	// 関数内でのみ使用するような、関数を手軽に作成できる
	auto func00 = [](){
		std::cout << "Hello, Lambda Expressions!" << std::endl;
	};
	func00();

	// C++11から使用できる「範囲ベースループ」
	// 配列外アクセスする危険性を抑制できる
	for (const auto& obj : objArray){
		obj.Disp();
	}

	// キー入力待ち
	rewind(stdin), getchar();
	return 0;
}
onehst さんが書きました: この数はのちのち変わるであろうと考えたので動的確保(New)を使ったのですが、どんな場合でも動的確保を使わず、
場合に応じて静的確保も使った方が良いのでしょうか?
確かに場合に応じて、静的に確保するか動的に確保するかの判断は必要ですが
今回ようにゲームの難易度調整に使えそうな部分は動的に変更できるようにした方が柔軟だと思います。

メニューの項目だったり、よほどのことが無い限り変動しないような部分は静的配列。
シューティングゲームの弾のような状況に応じて必要な量が変わるデータは動的配列。
みたいな感じですかね。

また、C++を使用するのであれば標準ライブラリのSTLを使用するのが一般的です。

以下は、よく使用されるSTLのクラスです。

静的配列
・std::array

動的配列
・std::vector
(C++11ならemplace_backメソッドについても知っておいた方が良)
・std::list
・std::map

スマートポインタ(自動開放してくれるポインタ)
・std::shared_ptr
・std::unique_ptr
(必要に応じてstd::weak_ptr)

Google検索等で、調べれば使い方の解説をされているサイト等はすぐに見つかると思います。

onehst
記事: 14
登録日時: 9年前
住所: 東京都

Re: 添削お願いいたします

#7

投稿記事 by onehst » 8年前

重ねての返信ありがとうございます。
>VisualStudio2008は8年も前の書き方でないと書けないので早く2015に移行した方が良いですよ。
VS2015におけるコンパイルの問題は
http://dixq.net/forum/viewtopic.php?f=3&t=8567
こちらの質問を参考にさせていただき解決することができました。ありがとうございました。
これでDXライブラリをVS2015で使えるようになりました...

>確かに場合に応じて、静的に確保するか動的に確保するかの判断は必要ですが
今回ようにゲームの難易度調整に使えそうな部分は動的に変更できるようにした方が柔軟だと思います。....

分かりました。以後そのようにします。

>また、C++を使用するのであれば標準ライブラリのSTLを使用するのが一般的です。
>C++には標準で便利機能がありますので、STLを学んでみてください。

御二方のアドバイスの通り、まずSTLから学んでみようと思います。

>Singletonクラスはテンプレートなどを使っているので少々難しく感じるかもしれません。...

分かりました。自分の知識をもう少し増やしてから勉強しようと思います。

一通り勉強が済んだら、今回作ったゲームをより質の良いものにして出来たら、また添削してもらえればなと思います。
繰り返しになりますが、添削してくださってありがとうございました。

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

Re: 添削お願いいたします

#8

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

現在分からないことを全部理解してから書き始めるのも難しいでしょうから、少しずつでいいと思いますよ。
まず取りあえず2点だけ注意して実装して、後は自分の知識(例えばSTLのリストじゃなくただの配列で実装とか)で実装してまた添削依頼してもいいと思います。

1. updateとdrawが存在するクラスを作る
2. 自分のインスタンス所持元に命令をコールバックさせたい時は、親のインスタンスを持たせてコールバックする

閉鎖

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