僕たちは2Ⅾシューティングゲームが大好きだ(作りたい)

シューティングゲームの途中で夏休みが終わってしまいました(完全に僕の時間軸)、それからの間にC++の授業がかなり進んで、

  • classの基本、カプセル化
  • コンストラクタ、デストラクタ
  • オーバーロード
  • classの継承
  • オーバーライド
  • ポリモーフィズム

こんなのやってまいりました。
これらを使って、ちょっと実践的なプログラムを作ろうということで、シューティングゲーム(スペースインベーダー的なやつ)を作ってみよう。をやってましたね。
プレイヤーや敵のオブジェクトを、GameChara型を基本形として、継承とオーバライドを使っていくことで組み立てていく。

基本となるGameCharaクラスは以下のように設計されている。

Listing. 1: GameCharaクラスの設計
"GameChara.h"
class GameChara
{
public:
	Vec2 pos_;    //場所
	bool isAlive_; //生死
	Texture tex_;  //キャラクタの画像
	double speed_; //移動スピード
	RectF rect_;   //バウンディングボックス
	Vec2 moveDir_; //移動方向
 
public:
	GameChara(Vec2 _pos);  //コンストラクタのオーバーロード
	GameChara();
	~GameChara(){}
 
	void SetAlive(bool _alive) { isAlive_ = _alive; }
	void SetSpeed(double _speed) { speed_ = _speed; }
	void SetMoveDir(Vec2 _movedir) { moveDir_ = _movedir; }
	void SetCharaRect(SizeF _size);
	void SetPosition(Vec2 _pos);
	RectF GetCharaRect() { return rect_; }//当たり判定用にバウンディングボックスを取得できるようにする
	bool isActive() { return isAlive_; }      //isAliveがtrueかどうか
	void DeActivateMe() { isAlive_ = false; } //isAliveをtrueに
	void ActivateMe() { isAlive_ = true; }    //isAliveをfalseに
	bool IsMyRectHit(RectF _rect); //当たり判定を行う(ボックスとボックスで判定)
	virtual void Update();
	virtual void Draw();
};

これの基本機能はさすがにもう見たらわかると思うので、詳しい説明は省く。
あとは、ゲームに出てくるオブジェクトは、のクラスを必ず継承して、

  • Update()で現在のパラメータ状態(メンバ変数)の更新
  • Draw()で現在の状態を使って、オブジェクト(キャラクタ)の描画を行う
  • (バウンディングボックスを表示したいときは、メンバのrect_をDraw()のなかでdrawFrameする)

プレイヤーのクラスを作る。
必要なものはプレイヤーのキャラを表す画像と、画像サイズやバウンディングボックスのサイズなど。
画像はMainの上のほうでAsset化しておいたほうが後々使いやすい
継承時に追加する機能としては、キーボード入力によるキャラクタの移動処理

Listing. 2: Playerクラスの作成
"Player.h"
#include "GameChara.h"
#include <vector>
 
using std::vector;
 
const int PLAYER_CHR_SIZE{ 48 }; //がぞうさいず
const double PLAYER_RECT_SIZE{ 45 }; //当たり判定用のBBのサイズ
const double PLAYER_MOVE_SPEED{ PLAYER_CHR_SIZE * 4.5 }; //プレイヤーの動作スピード
const int PLAYER_MAX_BULLET_NUM{ 5 }; //持ってる弾丸の最大数
 
enum direction
{
	UP, LEFT, DOWN, RIGHT, NONE
};
 
class Player :
    public GameChara
{
public:
	Player();
	~Player();//解放処理ここでやる?
	void Initialize();
	void Release();//それともここでやる?
	direction GetDirection();
	void Update() override;
	void Draw() override;
};

enum directionとGetDirectionがキーボード入力関係で増えた部分である。
方向に名前を付けて、配列と組み合わせることでif分の羅列を防ぐことができる。
(今回は中途半端にif文を並べてしまった。。。)

Updateには、キーボード入力(左右の身を取得)し、Player::speed_で押した方向に自機の座標を更新する処理を書く。
Drawには、現在の位置(pos_)に自機の画像(tex_)を表示(drawAt)する処理を書く。
ちなみに自機のサイズは定数でPLAYER_CHR_SIZEとかで設定しているので、自分の使っている画像のサイズに合わせること。
あと、当たり判定の時にどうせ死んだら表示しないとか書かなきゃないので初めからDraw関数の中ではif(isActive())しておく
(この辺はclass使わないバージョンと大して変わらないから詳細書かなくて大丈夫だよね?)

MainにPlayerを登場させる

画像はこの辺のものを使った。(3.png)
こいつをMainの上部で

TextureAsset::Register(U"PLAYER", U"images\\ships\\3.png");

しておいて、Playerクラスのtex_に割り当てる。
そんで、

Listing. 3: PlayerクラスをMain内に召還して降臨するまで
"Main.cpp"
# include <Siv3D.hpp> // OpenSiv3D v0.6.10
#include "Player.h"
 
void Main()
{
	// 背景の色を設定する | Set the background color
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });
	TextureAsset::Register(U"PLAYER", U"images\\ships\\3.png");
 
	Player* p = new Player;
 
	while (System::Update())
	{
		p->Update();
		p->Draw();
	}
}
結果画像
Fig. 1: 結果の画面

敵の画像は、

  • game-engineer/classes/2023/something-else/summertime-special-cource/cosmichooligun-class-0.txt
  • 最終更新: 2年前
  • by root