敵の弾発射に関するコード

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
すずらぎ
記事: 7
登録日時: 1年前

敵の弾発射に関するコード

#1

投稿記事 by すずらぎ » 1年前

はじめて質問させていただきます。C++は半年ほどのまだ初心者です。
現在C++, DxLibで敵が上から落としてくる弾をかわすといったゲームを作っています。そこで敵が弾を打つ際のプログラムについて手間取っております。
とりあえず以下のようなコードで敵が弾を打つのは実装できました。
EnemyManager.h

コード:

#pragma once
#include "Task.h"
#include "AbstEnemy.h"
#include <memory>
#include <vector>
#include "BulletManager.h"

class EnemyManager : public Task
{
public:
	EnemyManager(std::shared_ptr<BulletManager> impl);
	virtual ~EnemyManager() = default;
	bool update() override;
	void draw() const override;

private:
	std::vector<std::shared_ptr<AbstEnemy>> _vector;
};
EnemyManager.cpp

コード:

#include "EnemyManager.h"
#include "Define.h"
#include "Enemy_1.h"

using namespace std;

EnemyManager::EnemyManager(std::shared_ptr<BulletManager> impl)
{
	for (int i = 0; i < Define::BLOCK_NUM; ++i) {
		_vector.emplace_back(make_shared<Enemy_1>(impl,i));
	}
	for (auto enemy : _vector) {
		enemy->initialize();
	}
}
bool EnemyManager::update()
{
	for (auto enemy : _vector) {
		enemy->update();
	}
	return true;
}
void EnemyManager::draw() const
{
	for (const auto enemy : _vector) {
		enemy->draw();
	}
}
Enemy_1.h

コード:

#pragma once
#include "AbstEnemy.h"
#include "BulletManager.h"
#include <memory>

class Enemy_1 final : public AbstEnemy
{
public:
	Enemy_1(std::shared_ptr<BulletManager> impl,int index);
	~Enemy_1() = default;
	bool update() override;
	void draw() const override;

protected:
	//いろいろ関数が入る…
Enemy_1.cpp
[code]
#include "Enemy_1.h"
#include "Image.h"
#include "Define.h"
#include "Error.h"
#include "Macro.h"
#include "eBullet.h"
#include <DxLib.h>



const static float SPEED = 3;

Enemy_1::Enemy_1(std::shared_ptr<BulletManager> impl, int index) :
	_count(0),
	_stateFlag(0),
	_motionState(0),
	_bullet(impl),
	AbstEnemy(index)
{
	_setpoint = (index + 0.5) * Define::BLOCK_W;
}

//ここにupdateやdrawなどの関数…

void Enemy_1::throwBullet()
{
	++_count;
	if (_count == 30) {
		_bullet->GenerateBullet(_index,eBullet::normal);
	}
	if (_count == 90) {
		++_motionState;
	}
	DrawFormatString(200, 150, GetColor(255, 255, 255),"%d",_count);
}
BulletManager.h

コード:

#pragma once
#include "Task.h"
#include "AbstBullet.h"
#include <memory>
#include <vector>
#include "eBullet.h"

class BulletManager : public Task
{
public:
	BulletManager();
	virtual ~BulletManager() = default;
	bool update() override;
	void draw() const override;

	void GenerateBullet(int index,const eBullet ebullet);

private:
	std::vector<std::shared_ptr<AbstBullet>> _vector;
};
BulletManager.cpp

コード:

#include "BulletManager.h"
#include "Define.h"
#include "NormalBullet.h"
#include <DxLib.h>

using namespace std;

BulletManager::BulletManager()
{
}

//updateやdrawなどの関数…

void BulletManager::GenerateBullet(int index, const eBullet ebullet)
{
	switch (ebullet) {
	case normal:
		_vector.emplace_back(make_shared<NormalBullet>(index));
		break;
	default:
		break;
	}
	_vector.back()->initialize();
}

しかしこのやり方では、弾の管理クラスと敵の管理クラスを分けたのに、敵管理クラスや敵クラスが弾管理クラスをインクルードして使っていて気持ち悪いです…

このようなあるクラスから他のクラスを参照したり、インスタンスを生成したりするのに良い方法があれば教えていただきたいです。
(インターフェイスを用いればいいのかと思い、調べて試行錯誤してみましたが結局よくわからないままでした…)

すずらぎ
記事: 7
登録日時: 1年前

Re: 敵の弾発射に関するコード

#2

投稿記事 by すずらぎ » 1年前

すいません, codeの書き方を間違えてEnemy_1.hの中にEnemy_1.cppが入っています…

アバター
usao
記事: 1887
登録日時: 11年前

Re: 敵の弾発射に関するコード

#3

投稿記事 by usao » 1年前

> 弾の管理クラス,敵の管理クラス

「弾の管理」って具体的に何をするの?
「敵の管理」って具体的に何をするの?
こいつらの役割は何なの? 何のために存在するの? 本当にそんな縦割り(?)な感じにする価値はあるの? etc…
が他者には不明(あなたしか知らない)なので,何を問うているのかも不明.

個人的には(問うている事柄とは異なるであろうが)
例えば,BulletManager のメソッド

> void GenerateBullet(int index,const eBullet ebullet);

が ebullet とかいう値でswitchしているのが非常に馬鹿馬鹿しいと思える.
これを誰が呼ぶのか知らんけども,呼ぶ側は知っているハズの事柄をこのメソッド内で再度解決するのは無駄.ただただ手間がかかるようなぼかし方をすることに本当にメリットがあるのか?
まぁ作ってる物次第だろうけども,
例えば,「この弾はこの敵からしか出ない」的な話も相応にあり得るんじゃないの?
あるいは,敵と弾との間で何かしらの相互作用的な事柄とかをやりたくなるとかいうことは無いの?
そういうのは「その敵」の側で定義したり扱ったりしたくなったりしない?

アバター
usao
記事: 1887
登録日時: 11年前

Re: 敵の弾発射に関するコード

#4

投稿記事 by usao » 1年前

EnemyManager なんて,実質,正体は

> std::vector<std::shared_ptr<AbstEnemy>> _vector;

だよね.
これ本当に必要?
(おそらく BulletManager の側もまた同等程度の物でしかないのでは?)

アバター
usao
記事: 1887
登録日時: 11年前

Re: 敵の弾発射に関するコード

#5

投稿記事 by usao » 1年前

現実装を批判しているのではなく,「そういうことから考えてみたらどうかな?」という一個人の意見を言っているだけなので,誤解なきよう.

最初にそういった入れ物(?)だけを増やした結果,本来必要な事柄がやりにくくなっている(あるいは「なんだかなぁ」と思える)のであれば,その実装形態があなたにとっての枷になっているということではあるまいか?
であれば「それらの入れ物の存在を前提としない状態で」一度考えてみてはどうか.

アバター
usao
記事: 1887
登録日時: 11年前

Re: 敵の弾発射に関するコード

#6

投稿記事 by usao » 1年前

で,それはそれとして,

「敵」がやりたいことは「どこかが弾を管理しているから,そこに新しい弾を登録する(:管理下に置いてもらう)」なのであろうから,
「どこか」が実際には何者なのかを知る必要も無く,例えば,

コード:

class IXXX  //名前は知らんが
{//話と関係ない所は全て省略
public:
  //弾を管理下に登録する
  void RegisterBullet( std::shared_ptr<AbstBullet> );
};
みたいなのがあれば良い,
……っていうのが「インタフェースがどうの」いう話だよね.

単純には,「敵」の Update() を

コード:

//必要なら rXXX のメソッドを使って弾をどこかの管理下に登録する
void Update( IXXX &rXXX );
っていう形にでもすれば良い.
(IXXX みたいな型を用意するのではなく std::function みたいなのを使う形も考えられるだろうけど)

すずらぎ
記事: 7
登録日時: 1年前

Re: 敵の弾発射に関するコード

#7

投稿記事 by すずらぎ » 1年前

usaoさん
返信ありがとうございます。

管理クラスについて、敵や弾は将来的にものすごい数になる(かもしれない)のだからまとめないと一つ一つを扱っていくなどできないのではと考えていました。
確かに管理クラスがやっていることは各敵、弾のupdate,drawをfor文で回してるだけですが…


で、提案していただいたことについて
流れとしてはclass IXXXを作り、これを弾管理クラスが継承して、敵クラスがメソッドを用いるという感じですよね。この際敵クラス(もしくはAbstEnemyクラス)がIXXXのポインタを必要としますよね。ですがこれを持ってくるには結局どこかで弾管理クラスをインクルードしてこなきゃいけないように思えるのですが…
単に自分の知識不足なだけだと思いますが、そんな事せずに持ってくることができるのでしょうか?

アバター
usao
記事: 1887
登録日時: 11年前

Re: 敵の弾発射に関するコード

#8

投稿記事 by usao » 1年前

> 結局どこかで弾管理クラスをインクルードしてこなきゃいけない

少なくとも,IXXX のメソッドを呼ぶ側(敵)は IXXX のクラス定義を必要とするから,
「class IXXX の定義が書かれたヘッダ」をインクルードする必要はある.
それは間違いない.

しかし,
「敵」から見れば IXXXのポインタが指す物が何なのか(:動的な型)は分からないし,知る必要も無いのだから
「IXXX を継承した BulletManager」の定義が書かれている BulletManager.h をインクルードする必要はない
ここが違いとなる.

アバター
usao
記事: 1887
登録日時: 11年前

Re: 敵の弾発射に関するコード

#9

投稿記事 by usao » 1年前

「敵」から見れば,自身がアクセスする IXXX がとにかくその役割を約束通りに果たしてくれさえすればよいので,その相手は別に BulletManager じゃなくてもいい( ← ここが変わる).

例えば,「この BulletManager ってやってること少なくね? 別のやつにこいつの機能も持たせちゃえば要らなくね?」とかいう変更をして BulletManager なる存在が消滅したとしても敵の実装には全く影響が及ばない.

アバター
usao
記事: 1887
登録日時: 11年前

Re: 敵の弾発射に関するコード

#10

投稿記事 by usao » 1年前

逆に言えば,「敵」がアクセスするのが実際のところ未来永劫 BullterManager である(つまり,そこには実装変更の自由度は要らない)ということが確定事項なのであれば,あえてそんなことをしなくても良いとも言える.

すずらぎ
記事: 7
登録日時: 1年前

Re: 敵の弾発射に関するコード

#11

投稿記事 by すずらぎ » 1年前

>「IXXX を継承した BulletManager」の定義が書かれている BulletManager.h をインクルードする必要はない
なるほど、「敵」が欲しいのはIXXXのクラス定義なのだからそれを継承しているものについては必要ない(というか教えない方がいい)ということですか。

しかし、実際に実装する場合IXXXのポインタはどこから持ってくるんでしょうか…?

コード:

IXXX *rXXXX
rXXX = new IXXX
なんてことは当然純粋仮想関数なのでできないわけですし、かといってIXXXを継承したBulletManagerのポインタを持ってくることなど可能なんでしょうか?(できはするけどそれこそ関係がぐちゃぐちゃになりそうな気がするのですが)

アバター
usao
記事: 1887
登録日時: 11年前

Re: 敵の弾発射に関するコード

#12

投稿記事 by usao » 1年前

そこは言語文法の範疇なので,ここで素人説明するよりはまともな説明を見て学んでもらうべき事柄と思う.

とりあえずものすごくざっくりと書いておくと,

コード:

//なんか基底クラスがあって
class Base {...};
//それをpublic継承したクラスがあるとき…
class Deriv : public Base {...};

//これが可能
Deriv D;
Base *pBase = &D;
っていうことになってる.

「継承」「仮想関数」「ポリモフィズム」「is-a」「静的な型/動的な型」とかいったような言葉を「C++」と絡めて検索するとかしても相応に必要な情報にたどり着くんじゃないかと思うけど,つまみ食いするよりは,筋道立てて説明があるようなの(本とか)を小一時間読むべきだと思う.

(というか,そこらへんの基礎知識無しに提示されているコードを書いているというのは,あり得ない状況と思うのだが…??)

すずらぎ
記事: 7
登録日時: 1年前

Re: 敵の弾発射に関するコード

#13

投稿記事 by すずらぎ » 1年前

質問の仕方が悪かったのですが…

自分が考えていたのが、今回の場合

コード:

//インターフェースクラス
class IXXX {};
//を継承している弾管理クラス
class BulletManager : public IXXX { };
があり、これを使うために敵クラスで

コード:

class Enemy_1
{
protected:
      IXXX* rxxx;
      ...
public:
     void update() override;
};

void Enemy_1::update()
{
     rxxx->RegisterBullet( bullet );
}
という風に考えていたのですが、これだと結局インターフェースを継承しているクラス(ここでは弾管理クラス)が敵クラスで必要になっているのでは…と思いました。

それとも

コード:

class Enemy_1
{
public:
    void update(IXXX &rxxx) override;
}
として、敵クラスでは特にインスタンス化せず引数として持ってくるのがいいのでしょうか。しかしこの場合updateはオーバーライドしてるので引数変えるなら大幅に変更が必要になりそうなのですが。
それともなにか根本的に分かってないんですかね…最低限勉強はしてきているつもりなのですが

アバター
usao
記事: 1887
登録日時: 11年前

Re: 敵の弾発射に関するコード

#14

投稿記事 by usao » 1年前

> これだと結局インターフェースを継承しているクラス(ここでは弾管理クラス)が敵クラスで必要になっているのでは…と思いました。

必要になってないですね.
そういうのはコンソールアプリのプロジェクトでも作ってそこで小さい検証コードを作ってやってみればよいです.

例えば,このような基底クラスを設けたとする.

コード:

//Base.h
class Base
{
public:
  virtual ~Base(){}
  //(テスト用なのでこの動作自体に特別意味は無いが,とにかく処理内容を以下のように規定する)
  //引数をコンソールに出力する.
  //戻り値として 77 を返す.
  virtual int OutputIntValue( int i ) const = 0;
};
で,これを public に継承したクラスを,↑で規定した約束事を守った形で実装する.

コード:

//Deriv.h
class Deriv : public Base
{
public:
  //基底クラスで定めた事柄を守った実装を提供
  virtual int OutputIntValue( int i ) const override {  std::cout << i;  return 77;  }
};
で,どこかに

コード:

//どこか
#include "Base.h"  //Deriv.h はインクルードしてない

int TestFunc( const Base &rBase ){  return rBase.OutputIntValue( 9999 );  }
とか何とかいう処理でも書いて,この TestFunc() の引数に Deriv 型インスタンスへの参照を渡してみればいい.
この関数が実装されている個所は Deriv 型について一切知らないが,Deriv::OutputIntValue() が実施されることが確認できるハズ.

アバター
usao
記事: 1887
登録日時: 11年前

Re: 敵の弾発射に関するコード

#15

投稿記事 by usao » 1年前

> として、敵クラスでは特にインスタンス化せず引数として持ってくるのがいいのでしょうか。しかしこの場合updateはオーバーライドしてるので引数変えるなら大幅に変更が必要になりそうなのですが。

(敵クラスが「インスタンス化」するという話は,何かを間違っている感を感じますが)
実装形態に関しては好きな形でやれば良いでしょう.

何かしら外部とのやり取りをせねば仕事をできないであろう update() が,あえて(?)「引数無し」にされている理由は私(というか,あなた以外)には不明ですから,どんな形が望ましいのかはあなたにしか判断できません.
あなたにとって最もやりやすい形にするのが良いでしょう.


> 最低限勉強はしてきているつもりなのですが

不足を感じるなら学ぶしかないです.
そこはそれ以外に方法が無いので.

アバター
usao
記事: 1887
登録日時: 11年前

Re: 敵の弾発射に関するコード

#16

投稿記事 by usao » 1年前

おまけ:これは完全に邪推(っていうのか?)だけども,もしも

コード:

class AbstEnemy : public Task { ... };
class AbsBullet : public Task{ ... };
みたいなことになっていて,「Update() は引数無し」という話がこういうところから生じていたりするのであれば,まずそこから考え直すと自由になるんじゃないかな.
すなわち,「こいつらって Task から派生する必要が本当にあるん?」っていう.
(さらには,「そもそもこの Task ってやつ,必要なの?」とか)

すずらぎ
記事: 7
登録日時: 1年前

Re: 敵の弾発射に関するコード

#17

投稿記事 by すずらぎ » 1年前

>これは完全に邪推(っていうのか?)だけども...
まさにその通りです。そしてこれにとらわれてたのが悩んでた原因なのかもしれないです。今のコードでは敵クラスが外部とやりとりできないですが、できるようにすれば敵管理クラス等からできますもんね…

とりあえず一旦、敵クラスのコンストラクタで

コード:

class Enemy_1
{
public:
     Enemy_1(std::shared_ptr<IXXX>& rxxx);
}
という感じで試してみます。

参照魚
記事: 109
登録日時: 6年前

Re: 敵の弾発射に関するコード

#18

投稿記事 by 参照魚 » 1年前

>しかしこのやり方では、弾の管理クラスと敵の管理クラスを分けたのに、
>敵管理クラスや敵クラスが弾管理クラスをインクルードして使っていて気持ち悪いです…悩まれている
悩まれているところの直接的な回答にはりませんが、オブジェクト指向のゲームプログラミングを解説されている方の動画を紹介します。ご参考まで。


こちらの動画の作り方でもクラス間の結合は強いです(むしろべったり)。個人的には各インスタンスが外部への接続点(rxxxxに相当するもの)を保持する方式が以下の点でしっくりきていません。

・引数付きコンストラクタを用意しなければならない
この先、敵クラスが増えるたびに同じ記述をしなければならないのがちょっと面倒に感じています。ツールの機能やC++の文法で多少なりともカバーできる部分ではありますが、「本質的に無駄」なような気がしてならないのです。

・インスタンスの数×ポインター分のメモリがもったいない
全体からみればほんのわずかなのでしょうが、各インスタンスがまったく同じ情報を持つのはなんかもったいないと感じてしまいます。だったらいっそのことグロバール変数でよいのではないかと思ってしまいます(もしくは取得用のグローバル関数を用意する)。

わたしも同じ悩みをずいぶんと長い間かかえたまま、作っては壊してを繰り返して、まだ納得いくところにまで至っていないです。作るゲームの規模や作る人数によって最適解が違うような気もしています。

わたしは現状では、敵クラスは敵マネージャークラスに弾クラスの生成を依頼する方式で作っています。敵クラスは弾の種類と発射情報(位置や角度)を敵マネージャークラスに伝えるだけで、弾クラスの情報(ヘッダ)は知らないです。ひとつのやり方としてご参考までに。

アバター
usao
記事: 1887
登録日時: 11年前

Re: 敵の弾発射に関するコード

#19

投稿記事 by usao » 1年前

なにはともあれ「必要なものだけを書く」から無意味に逸脱しないこと を心掛けると良いんじゃないかなぁ.


「こういう形にしとけば → こういうこともできるよね」
とかいう話があったとしても,その「こういうこと」をそもそもやる予定が無いのであれば,その形にすることを選ぶ理由にはなり得ない.
特に,その形にすることに相応のデメリットが存在するようであれば,そのデメリットしか享受しないことになるのだから,その場合は選ばないのが正解であろう.
「自分が今作っている物の範囲を超えた何かをもサポートできる(かもしれない)枠組み!」みたいなのを目指す意味は無い(そのこと自体が目的でないなら).

「べったり」でOKならそこは「べったり」で書けばいいじゃない(個人の趣味で作ってるなら「その部分には仕様変更は起き得ない」ことを自身で保証できるハズだ).
ちょっとした実装形態の差については別に後から直したって良いのだから,そこは「動いてから気が向いたら直す」みたいな姿勢で取り組めば良いのでは.最初から過度にあれこれ考えるよりも.
何かすっごい考えた形態のコードをこしらえたけども正しい動作はしませんでした^^ では意味ないのだし.

(例えば本件では Task とかいう型の必要性はおそらく無いんじゃないかな,と勝手に想像している."なんとかManager" とかいうぼんやりした名称のやつらも同様.どこかから中途半端に引っ張ってきたコードか何かに無駄に縛られてるように見える.一回そういうの無しで書いてみたらいいのに,と.)

すずらぎ
記事: 7
登録日時: 1年前

Re: 敵の弾発射に関するコード

#20

投稿記事 by すずらぎ » 1年前

参考まであげていただいて…本当にありがとうございます。

先の方法で敵クラスでも敵管理クラスでも特に弾管理クラスをインクルードすることなく実装ができたので一応質問内容は解決しました。

一度それぞれのクラスが何を必要としていて、逆に何を必要とはしないのかを考え直しつつ制作していこうと思います。

dic
記事: 657
登録日時: 13年前
住所: 宮崎県
連絡を取る:

Re: 敵の弾発射に関するコード

#21

投稿記事 by dic » 1年前

設計に悩んでいるのであれば、デザインパターンを学んではどうでしょうか?
https://ja.wikipedia.org/wiki/%E3%83%87 ... %E3%82%A2)
ここがいいかなと思います。
本を買うのがいいのですが、結構高いので吟味して買ったらいいと思います。

アバター
usao
記事: 1887
登録日時: 11年前

Re: 敵の弾発射に関するコード

#22

投稿記事 by usao » 1年前

異なる意見/考え方 が書かれていた方が何かの参考になるかもしれないので,書いてみる.


> ・引数付きコンストラクタ…

「書くのが面倒」とかそういう話ではなくて,「どうしてそうするのか?」という理由/必要性 の観点から考える事柄かと.
(個人的には,本件の「敵」みたいなのは,メソッドの引数として都度受ければ済むだけの話と見えるので,この形にする必要はないと思う.)


> ・インスタンスの数×ポインター分のメモリがもったいない

よほど切羽詰った環境でやってるのでもなければ,その程度のメモリ節約を理由とした設計を考える必要はないんじゃないかなぁ.
グローバルな何かに依存する方向に持っていくというのは,少なくとも本件で目指している方向性ではないと思う.

返信

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