Siv3D サンプル - JPN takeshima

シューティングゲームのサンプルを擬似3Dにしてみた(2015-05-02)

Siv3Dのサンプルソースコードです。
サンプルのシューティングゲームを擬似3Dにしてみました。 3Dカメラパラメータをうまく設定することで 3Dオブジェクトを2D座標のように指定しても だいたい位置と大きさが合うことが特徴です。
// @file Main.cpp
// @date 2015-05-02
// サンプル シューティングゲームを
// 擬似3Dにしてみた。
// 元ソースは以下。
// http://play-siv3d.hateblo.jp/entry/jp/example/shooting

#include <Siv3D.hpp>
#include "HamFramework\SceneManager.hpp"

Image CreateSkyImage() {
	Noise noise;
	Image image(640, 960);

	for (int y = 0; y < image.height; ++y)
		for (int x = 0; x < image.width; ++x)
			image[y][x] = HSV(220, 0.5 + 0.4 * noise.octaveNoise(x / 160.0, Abs(y / 120.0 - 4.0), 8), 0.8);

	return image;
}

struct Data {
	int score = 0;
	int highScore = 0;
	int crash = 0;

	Array<Vec2> shots, bullets, enemies;

	Texture texture;
	Font font;
	Triangle player;
};

using MyApp = ham::SceneManager < String, Data >;

struct MainScene : MyApp::Scene {
	int count = 0;
	bool pseudo3D = true;

	void init() override {
		Window::SetTitle(L"Siv Shooting 擬似3D化 | [X]: 切り替え / [Z]: shot / 十字キー: 移動");

		m_data->texture = Texture(CreateSkyImage());
		m_data->font = Font(20);
		m_data->player = Triangle(300, 200, 20.0);


		// 640x480の場合に近似するためのカメラ配置
		Camera camera;
		camera.pos = { 320, 240, 16384 };
		camera.lookat = { 320, 240, 0 };
		camera.up = Vec3::Down;
		camera.fovDegree = 1.68;
		Graphics3D::SetCamera(camera);

		Graphics3D::SetAmbientLight(ColorF(0.25, 0.25, 0.25));
		Graphics3D::SetLight(0, Light::Directional(Vec3(-1, -1, 1)));
	}

	void update() override {
		++count;

		if (Input::KeyX.clicked) {
			pseudo3D = !pseudo3D;
		}

		if (count % (24 - Min(count / 60, 18)) == 0)
			m_data->enemies.emplace_back(Random(40, 600), -40);

		const Vec2 dir(Input::KeyRight.pressed - Input::KeyLeft.pressed, Input::KeyDown.pressed - Input::KeyUp.pressed);

		if (!dir.isZero)
			m_data->player.moveBy(dir.normalized() * (Input::KeyShift.pressed ? 4.5 : 9.0));

		m_data->player.setCentroid(Clamp(m_data->player.centroid.x, 0.0, 640.0), Clamp(m_data->player.centroid.y, 0.0, 480.0));

		if (Input::KeyZ.pressed && count % 4 == 0)
			m_data->shots.push_back(m_data->player.p0);

		for (auto& shot : m_data->shots)
			shot.y -= 8.0;

		for (auto& bullet : m_data->bullets)
			bullet.y += 4.0;

		for (auto& enemy : m_data->enemies) {
			enemy.y += 2.0;

			if (count % 60 == 0)
				m_data->bullets.push_back(enemy);
		}

		// 侵略条件は厳しいのでカット
		//if (AnyOf(m_data->bullets, [=](const Vec2& b){ return m_data->player.intersects(b); })
		//	|| AnyOf(m_data->enemies, [=](const Vec2& e){ return e.y > 490.0; })) {
			if (AnyOf(m_data->bullets, [=](const Vec2& b){ return m_data->player.intersects(b); })) {
			count = m_data->score = 0;
			m_data->crash = 60;
		}

		Erase_if(m_data->shots, [](const Vec2& s){ return s.y < -10.0; });

		Erase_if(m_data->bullets, [](const Vec2& b){ return b.y > 490.0; });

		Erase_if(m_data->enemies, [&](const Vec2& e){
			if (AnyOf(m_data->shots, [=](const Vec2& s){ return e.distanceFrom(s) < 20.0; })) {
				++m_data->score;
				return true;
			}
			else return e.y > 490.0;
		});

	}

	void draw() const override {
		const int skyOffset = System::FrameCount() % 960 * 8;
		m_data->texture(0, -skyOffset / 2, 640, 480).draw();
		m_data->texture(0, -skyOffset, 640, 480).draw(Alpha(80));
		if (pseudo3D) {
			Graphics::Render2DBackground();
		}


		for (const auto& shot : m_data->shots) {
			if (pseudo3D == false) {
				Circle(shot, 7).drawFrame(4, 0, Palette::Orange);
			}
			else {
				Sphere({ shot.x, shot.y, 0 }, 7).draw(Palette::Orange);
			}
		}

		for (const auto& bullet : m_data->bullets) {
			if (pseudo3D == false) {
				Circle(bullet, 4).draw();
			}
			else {
				Sphere({ bullet.x, bullet.y, 0 }, 4).draw();
			}
		}

		for (const auto& enemy : m_data->enemies) {
			if (pseudo3D == false) {
				RectF(30, 30).setCenter(enemy).rotated(enemy.y / 100.0).draw(Palette::Black);
			}
			else {
				double ang = enemy.y / 100.0;
				double pitch = ang * 0.0;
				Box({ enemy.x, enemy.y, 0 }, 30,
					Quaternion::RollPitchYaw(ang,pitch,0)).draw(Palette::Dimgray);
			}
		}

		if (pseudo3D == false) {
			m_data->player.draw();
		}
		else {
			// p0は前の1点、p1,p2は後ろの2点
			Cone({ m_data->player.p0.x, m_data->player.p1.y, 0 }, 10, 10 * Sqrt(3), Quaternion::Roll(Pi)).draw();
		}

		if (m_data->crash)
			Rect(640, 480).draw(Alpha(--m_data->crash * 3));

		m_data->highScore = Max(m_data->score, m_data->highScore);

		m_data->font(L"Hi:", m_data->highScore, L"\n", m_data->score).draw(20, 20);
	}
};

void Main() {
	MyApp myApp;
	myApp.add<MainScene>(L"MainScene");
	while (System::Update()) {
		if (!myApp.updateAndDraw()) {
			break;
		}
	}
}