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

技術の吐き溜めなどなど

【5】Siv3Dでオブジェクトの後処理(削除)をする

こんばんは。珍しくブログ更新が続いている、そんなAdventCalendar5日目です。まだまだネタは切れていません。

前回の記事では、複数のオブジェクトを発生させるというプログラムを記述しました。
【4】Siv3Dで複数のオブジェクトを処理する - プロジェクトシートK(仮)

 
これでシューティングゲームのように、ひたすらオブジェクトを発生させるようなことが出来るようになりました。しかし、このままでは不都合が生じます。オブジェクトを発生させまくるのはいいものの、後処理をしていないためにいらない処理を続けてしまいます。つまり処理落ちが起こります。

www.youtube.com



特にシューティングゲームにおける処理落ちは致命的なので、避けなければいけません。今回はそのための手法を紹介させていただきます。

コード

コードは前回の記事のものを流用しています。

# include <Siv3D.hpp>
using namespace std;
struct MyCircle {
private:
	int radius;
	Vec2 place;
	int color;
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; }
	int getRadius(void) { return radius; }
	Vec2 getPlace(void) { return place; }
	int getColor(void) { return color; }
	bool deleteCheck(void) { return ((place.x < -20) || (place.x > 660) || (place.y < -20) || (place.y > 500));}
};

void Main()
{
	vector<MyCircle> vec;
	int radius = 20;
	int count = 0;

	while (System::Update())
	{
		MyCircle cir(radius, Vec2(0, 20 + 40 * (count % 12)), count * 2);
			vec.push_back(cir);
		int size = vec.size();
		for (auto i = 0; i < size; ++i) {
			Circle(vec[i].getPlace(), vec[i].getRadius()).draw(HSV(vec[i].getColor()));
			vec[i].setPlace(vec[i].getPlace() + Vec2(6, 0));

			//オブジェクトの削除
			if (vec[i].deleteCheck()) {
				vec.erase(vec.begin() + i);
				--i;
				--size;
			}
		}
		count++;
		count = count % 360;
	}
}

解説

大事な部分は2箇所あります。
まず、構造体の中に「オブジェクトを削除するべきか、しないべきかをboolで返す関数」を作成します。

bool deleteCheck(void) {
	return ((place.x < -20) || (place.x > 660) || (place.y < -20) || (place.y > 500));
}

この部分ですね。

していることは実に簡単です。具体的には「オブジェクトの位置が画面外にあるとfalseを返す」ことをしています。今回はオブジェクトの大きさが半径20の円なので、画面のサイズ(640,480)より20幅を持たせて判定を行いました。この辺は用いるパラメータに応じて調整しましょう。オブジェクトの場所の位置は同構造体の中にあるので、判定もこのように簡単にすることができます。

続いて、オブジェクトの削除判定をした後に、実際に削除を行う必要が出てきます。つまり、vector型に格納されている要素を削除しなければなりません

for (auto i = 0; i < size; ++i) {
	...略...
	//オブジェクトの削除
	if (vec[i].deleteCheck()) {
		vec.erase(vec.begin() + i);
		--i;
		--size;
	}
}

この部分ですね。

まず、オブジェクト一つ一つに対してdeleteCheck()を行います。もしそれに引っかかれば、vectorのその位置のオブジェクトを関数erase()で削除します。vectorはある位置の要素が削除されるとそれより後の要素が1つずつ前に詰められるという仕様があるため、処理を正しく機能させるためにindexを1つマイナスし、sizeも1つマイナスします。これで、正しく処理をさせた状態でオブジェクトの削除が行えます!

www.youtube.com



これでかなり安心ができる挙動になりました。ここからは適当にオブジェクトを追加して、処理して、やりたい放題できるはずです!仮にオブジェクトを円だけでなく、四角や画像を使いたいという場合には、構造体をちょちょっと拡張・追加すればいいでしょう。場合に応じてオブジェクトをいくつかに種類分けし、いろいろな挙動をさせたい場合には、vector型の格納リストを別途用意すればいいでしょう。また、構造体の中には速度や加速度なんかのフィールドを持たせると便利かもしれません。

さて、ここから先は好き勝手に作っていけばいいのですが、肝心な自機とその操作がまだ実装できていません。のんびりとただ弾幕を見続けるゲームもそれはそれで斬新ですが、せっかくだからプレイできるようにしたいですね。ということで、次回は自機の実装とかでも解説してみようかなと思います!(もしくはオブジェクトの拡張とかになるかも...!)


※2016/12/06追記
コメントにて「オブジェクトの削除はErase_ifを使うといいよー!」とのご指摘を受けました。実際に使ってみると、この記事で紹介した手法よりもかなりスマートで簡潔な記述となりましたので、別記事にて補足させていただきました。ご指摘ありがとうございました…!
Siv3Dにおけるコンテナ(Array)を使ってみる - プロジェクトシートK(仮)