プロジェクトシートK(仮)

技術の吐き溜めなどなど

【8】Siv3D プレイヤー弾のショット、および敵の実装をする

今まで数回に渡ってシューティングゲームを作成するための手法を紹介してきましたが、今回はいよいよというものを実装してみます。シューティングゲームですから、ボスのような存在があった方が盛り上がるでしょう。そしてそのボスを倒すために、プレイヤーから放たれる弾というものも実装してみます。俗にいうshotってやつですね。

今回は、コイツが敵として攻撃を仕掛けてくるようなコードを書いてみます。
f:id:konkea:20161208232357p:plain
コイツ(提供:8r1kさん)

敵にはHPを持たせ、プレイヤー0のショットでHPを減らしていく。HPを削ると敵が攻撃を変えてくる…そんな仕様で作ってみます。


ちなみに大変申し訳ないのですが、コードを書いてるのに夢中になりすぎた挙句ブログの文章を書く時間・気力を奪われてしまいましたため、コード・動画と必要最小限の解説のみとなります…すみません!



コード

# include <Siv3D.hpp>
struct MyCircle {
private:
	int radius;
	Vec2 place;
	int color;
	Vec2 velocity = Vec2(0, 0);
public:
	MyCircle(int r, Vec2 p, int c) {
		radius = r; place = p; color = c;
	}
	void setRadius(int r) { radius = r; }
	void setPlace(Vec2 p) { place += p; }
	void setColor(int c) { color = c; }
	void setVelocity(Vec2 v) { velocity = v; }
	int getRadius(void) { return radius; }
	Vec2 getPlace(void) { return place; }
	int getColor(void) { return color; }
	bool deleteCheck(void) { return ((place.x < -10) || (place.x > 650) || (place.y < -10) || (place.y > 490));}
	Vec2 getVelocity(void) { return velocity; }
};

struct Player {
private:
	Vec2 place;
	int radius;
	int hp = 3;
	int shotFlg = 2;
public:
	Player(int r, Vec2 p) {
		radius = r; place = p;
	}
	void setRadius(int r) { radius = r; }
	void setPlace(Vec2 p) {
		place += p; 
		place.x = Clamp(place.x, 10.0, 630.0);
		place.y = Clamp(place.y, 10.0, 470.0);
	}
	void hit(void) { --hp; if (hp == 0) { place = Vec2(1000, 1000); } }
	int getRadius(void) { return radius; }
	Vec2 getPlace(void) { return place; }
	int getHP(void) { return hp; }
	bool shot(void) {
		++shotFlg;
		if (shotFlg == 3) {
			shotFlg = 0;
			return true;
		}
		return false;
	}
};

struct Enemy {
private:
	Vec2 place;
	int radius;
	int hp = 300;
	int dir = 0;
public:
	Enemy(int r, Vec2 p) {
		radius = r; place = p;
	}
	void setRadius(int r) { radius = r; }
	void setPlace(void) {
			place.x += dir;
		if(place.x < 40 || place.x > 580) {
			dir = -dir;
		}
	}
	void hit(void) {
		--hp;
		if (hp == 200) { dir = 2; }
		if (hp == 100) { dir = dir * 3; }
		if (hp == 0) { place = Vec2(2000, 2000); } 
	}
	int getRadius(void) { return radius; }
	Vec2 getPlace(void) { return place; }
	int getHP(void) { return hp; }
};

void Main()
{
	Array<MyCircle> array;
	Array<Vec2> shots; //プレイヤーの弾を格納
	int radius = 10;
	int count = 0;
	Vec2 spawn(340, 60);
	int speed = 5;

	Player player(20, Vec2(320,400));
	Enemy enemy(40, spawn);

	const Rect frame(10, 10, 620, 460); //枠
	const Texture texture(L"Example/texture.png"); //背景テクスチャ
	const Texture star(L"Example/star.png"); //球テクスチャ
	const Texture chara(L"Example/Enemy.png"); //敵テクスチャ

	while (System::Update())
	{
		//背景の表示
		texture.draw(10, 10);

		//敵の表示
		enemy.setPlace();
		Circle ene(enemy.getPlace(), 100); //敵の当たり判定用
		chara.draw(enemy.getPlace() + Vec2(-50,-60)); //テクスチャ表示

		//球の発生
		MyCircle cir(radius, enemy.getPlace(), 100);
		cir.setVelocity(Vec2(-cos(count/Pi),sin(count/Pi)).setLength(speed));
		array.push_back(cir);

		//プレイヤー処理
		const Triangle playerView(player.getPlace(), player.getRadius());
		if (player.getHP() != 0) {
			//キーボード入力部
			Vec2 direction(Input::KeyRight.pressed - Input::KeyLeft.pressed, Input::KeyDown.pressed - Input::KeyUp.pressed);
			if (!direction.isZero())
				player.setPlace(direction.setLength(5));

			//Sキーでショット
			if (Input::KeyS.pressed) {
				if (player.shot()) {
					shots.push_back(player.getPlace());
				}
			}
			//プレイヤー描写
			playerView.draw(Palette::Red);
		}

		//自分が出した弾の処理
		for (auto& shot : shots) {
			Circle c(shot,4);
			c.draw(Palette::Gray);
			if (c.intersects(ene)) { 
				enemy.hit(); 
				shot = Vec2(-1000, -1000);
			}
			shot += Vec2(0, -10);
		}
		Erase_if(shots, [](const Vec2& s) { return s.y < -10.0; });

		//オブジェクトの描写・セット・当たり判定チェック
		for (auto& object : array) {
			//場所のセット
			object.setPlace(object.getVelocity());
			//オブジェクトの定義・描写
			Circle c(object.getPlace(), object.getRadius());
			c.draw(HSV(object.getColor()));
			star.draw(object.getPlace()-Vec2(30,30)); //テクスチャ表示
			//当たり判定チェック
			if (playerView.intersects(c)) {
				object.setPlace(Vec2(1000,1000));
				player.hit();
			}
		}

		//オブジェクトの削除
		Erase_if(array, [](MyCircle& c) { return c.deleteCheck(); });

		PutText(L"残り残機:", player.getHP()).from(12, 12);
		PutText(L"敵HP:", enemy.getHP()).from(500,12);
		count++;
		count = count % 180;

		//枠の表示
		frame.drawFrame(2, 0, Palette::Yellow);
		frame.drawFrame(0, 10, Palette::Black);
	}
}

www.youtube.com

大事なところの解説

①プレイヤー専用の構造体の作成

struct Player {
private:
	Vec2 place;
	int radius;
	int hp = 3;
	int shotFlg = 2;
public:
	Player(int r, Vec2 p) {
		radius = r; place = p;
	}
	void setRadius(int r) { radius = r; }
	void setPlace(Vec2 p) {
		place += p; 
		place.x = Clamp(place.x, 10.0, 630.0);
		place.y = Clamp(place.y, 10.0, 470.0);
	}
	void hit(void) { --hp; if (hp == 0) { place = Vec2(1000, 1000); } }
	int getRadius(void) { return radius; }
	Vec2 getPlace(void) { return place; }
	int getHP(void) { return hp; }
	bool shot(void) {
		++shotFlg;
		if (shotFlg == 3) {
			shotFlg = 0;
			return true;
		}
		return false;
	}
};

radiusで自機の大きさを決める

・hpは3で固定。コンストラクタの引数でセットするようにしても、別途セット用の関数を定義してもよいかも。どの手法が一番良いかは謎(?)

・プレイヤーの位置の、限界位置のチェックをこの構造体の関数setPlaceでするように。

・敵の弾が当たったらhit()を呼び出す。HPを1ずつ減らし、0になれば死亡扱いとなる。

・shotはSキー3フレーム押すことによって発射される。弾の数を抑えるためにこのような処理としている。



②敵専用の構造体の作成

struct Enemy {
private:
	Vec2 place;
	int radius;
	int hp = 300;
	int dir = 0;
public:
	Enemy(int r, Vec2 p) {
		radius = r; place = p;
	}
	void setRadius(int r) { radius = r; }
	void setPlace(void) {
			place.x += dir;
		if(place.x < 40 || place.x > 580) {
			dir = -dir;
		}
	}
	void hit(void) {
		--hp;
		if (hp == 200) { dir = 2; }
		if (hp == 100) { dir = dir * 3; }
		if (hp == 0) { place = Vec2(2000, 2000); } 
	}
	int getRadius(void) { return radius; }
	Vec2 getPlace(void) { return place; }
	int getHP(void) { return hp; }
	void update(void) { place.x += dir; }
};

・ほとんどはPlayeyと同じ。

・hpに応じて敵の動きを変えるために、移動ベクトル用の変数dirを用意した。Vec2型ではなくint型なのは、x軸のみの移動という前提条件を付けたため。

・Playerが出した弾が被弾するとhit()が呼び出される。そこで残りHPに応じた処理を行い、敵の行動パターンを変更する。

・敵の動きはsetPlace()にて更新。引数は与えない(PlayerのsetPlaceと挙動を統一させるため、別途updateという名前にリネーミングした方がいいかも?)


③ショット用のArrayの作成

・Sキーが押されるとplayer.shot()が処理される。ここで弾が出されるフラグが立つと(player.shot()がtrueを返す時)、プレイヤーの位置にVec2型のショット弾情報が格納される。

・ショットは拡張for文により、ひとつずつその位置を上に10ずつずらしていく。そこで弾が敵にあたればその弾は消滅し、敵にダメージを与える関数enemy.hit()が呼び出される。

反省

かなりごちゃごちゃしたコードになってきました。改めて、必要な仕様を整理して構造体の設計から見直した方がいいかもしれません。また、現時点では構造体の中でシステム面の処理のみを行っていますが、同時に描写もやってしまっていいのかもしれません。というかそうしないとmain関数が手に負えないことになってしまいそう…。

また、当たり判定周りのコード記述がやや強引なのでこれもどうにかしたいですね…とりあえず改善できる部分を探してみることにします。


シューティングゲームを完成させるのに、後残るのは

・タイトル画面・ゲームオーバー画面作成

・各種エフェクト

・ステージの作成

・キャラクター

・音楽

・その他細かいところ


こんな感じでしょうか。割と終わりが見えてきて感動していますが、ここまできたからには立派な作品を作り上げたい…!