Siv3D August 2016 の新機能サンプル - Siv3D/Reference-JP GitHub Wiki


⚠ このページは古い Siv3D (August 2016 v2) のリファレンスのアーカイブです。
最新版の Siv3D (OpenSiv3D) については OpenSiv3D Web サイト をご覧ください。


文章読み上げ

Println() と同じように Say() を使うと、テキストやデータを読み上げてくれます。 より細かい制御や設定を行いたい場合は Speech.hpp ヘッダをご覧ください。

# include <Siv3D.hpp>

void Main()
{
	while (System::Update())
	{
		if (Input::KeyA.clicked)
		{
			Say(L"こんにちは。シブスリーティーの文章読み上げ機能です。");
		}
		else if (Input::KeyB.clicked)
		{
			Say(L"Today's lucky number is ", Random(100));
		}

		if (Speech::IsSpeaking())
		{
			const double t = Time::GetMillisec() * 0.002;

			Circle(Window::Center(), 40)
				.drawArc(t, 30_deg, 8)
				.drawArc(t + 120_deg, 30_deg, 8)
				.drawArc(t + 240_deg, 30_deg, 8);
		}
	}
}

3D 描画時のシャドウ

Deferred Rendering 時に、.drawShadow() で影を描画できます。 影をつくる光源の射影情報は Graphics3D::SetShadowLight() で設定します。
シャドウマップの解像度は Graphics3D::SetShadowMapResolution() で 16~8192 の範囲で設定します。デフォルトは 1024 です。

# include <Siv3D.hpp>

void Main()
{
	Graphics::SetBackground(Color(80, 160, 230));
	Graphics3D::SetAmbientLight(ColorF(0.3));

	const Texture textureGround(L"Example/grass.jpg", TextureDesc::For3D);
	const Mesh meshGround(MeshData::Plane(30, 30, { 6, 6 }));
	const Texture textureBox(L"Example/brick.jpg", TextureDesc::For3D);
	const Model model(L"Example/Well/Well.wavefrontobj");

	ShadowLight shadowLight;
	shadowLight.lookat.set(0, 0, 0);

	while (System::Update())
	{
		Graphics3D::FreeCamera();

		const double y = 30 + Sin(Time::GetMillisec() * 0.001) * 10;
		const double angle = Time::GetMillisec() * 0.0005;	
		const Vec3 lightPos = Cylindrical(25, angle, y);

		shadowLight.position.set(lightPos);
		Graphics3D::SetShadowLight(shadowLight);
		Graphics3D::SetLight(0, Light::Directional(lightPos.normalized()));

		meshGround.draw(textureGround);

		Sphere(0, 5, 0, 2).draw().drawShadow();

		model.draw(Mat4x4::Translate(5, 0, 0)).drawShadow(Mat4x4::Translate(5, 0, 0));

		for (auto i : step(6))
		{
			const Vec3 pos = Vec3(0, 0, 6) + Cylindrical(4, Radians(i*60), 0);

			Cylinder(pos, pos + Vec3(0, 4, 0), 0.2).draw(HSV(i*60, 0.6, 1.0)).drawShadow();
		}

		for (auto i : step(6))
		{
			Box(-5 + i * 2, 0.5, -5, 1).draw(textureBox).drawShadow();
		}
	}
}

視錐台

3D カメラに写っている範囲を表す視錐台を Camera 型から取得できます。
.intersects().contains() 関数により、BoxSphere, Triangle3D などの 3D 形状が視錐台と交差しているか、包含されているかを調べられます。

# include <Siv3D.hpp>

void Main()
{
	const Font font(20);

	Array<Box> boxes;

	for (auto i : step(100))
	{
		boxes.emplace_back(RandomVec3({ -30, 30 }, { -30, 30 }, { -30, 30 }), 3.0);
		boxes[i].rotation.rotateRollPitchYaw(Random(TwoPi), Random(TwoPi), Random(TwoPi));
	}

	while (System::Update())
	{
		Graphics3D::FreeCamera();

		for (auto& box : boxes)
		{
			box.rotation.rotateRollPitchYaw(0.01, 0.01, 0.01);
		}

		int32 count = 0;

		const ViewFrustum vf = Graphics3D::GetCamera().calcViewFrustum();

		for (const auto& box : boxes)
		{
			if (vf.intersects(box))
			{
				box.draw();

				++count;
			}
		}

		font(L"描画された Box: {}/{}"_fmt, count, boxes.size()).draw(10, 10, Palette::Orange);
	}
}

Transformer2D

2D 描画やマウスカーソルに対するスケーリングや移動などの座標変換を Push / Pop するクラスです。
Transformer2D オブジェクトのスコープが有効な間、設定した座標変換が、それまでの座標変換に乗算する形で適用されます。 第 3 引数に true を設定すると、マウスカーソルにも座標変換が適用されます。

# include <Siv3D.hpp>

void Main()
{
	const Font font(30);
	
	const Circle circle(200, 200, 100);
	
	const Rect rect(300, 300, 100);

	Stopwatch stopwatch(true);

	while (System::Update())
	{
		const double scale = Max(1.0 - stopwatch.ms() * 0.0001, 0.1);
		const double angle = stopwatch.ms() * 0.001;

		{
			const Transformer2D transformer(Mat3x2::Scale(scale, Window::Center()).rotate(angle, Window::Center()), true);

			circle.draw(circle.mouseOver ? Palette::Red : Palette::Yellow);

			rect.draw(rect.mouseOver ? Palette::Red : Palette::Yellow);

			font(L"Siv3D").draw(400, 200);
		}

		Rect(20, 20, 50).draw();
	}
}

ScalableWindow

マウス座標と 2D 描画の座標をウィンドウに合わせてスケーリングします。
ScalableWindow::Setup() で基準の解像度を設定し、基準の解像度を想定してプログラムを書けば対応できます。
Window::Width(), Window::Height(), Window::Size(), Window::Center() 等は、ウィンドウサイズ変更の影響を受けてしまうため、代わりに Window::BaseWidth(), Window::BaseHeight(), Window::BaseSize(), Window::BaseCenter() を使います。
縮小時に Vec2 型でマウスカーソルの座標を得たい場合には Mouse::PosF() を使います。

参考比較: https://github.com/Siv3D/Reference-JP/wiki/ドットお絵かき

# include <Siv3D.hpp>
# include <HamFramework.hpp>

void Main()
{
	ScalableWindow::Setup(640, 480);

	Graphics::SetBackground(Palette::White);

	const int32 dotSize = 40;

	Grid<int32> dots(Window::BaseWidth() / dotSize, Window::BaseHeight() / dotSize);

	while (System::Update())
	{
		{
			const auto transformer = ScalableWindow::CreateTransformer();

			for (auto p : step({ dots.width, dots.height }))
			{
				const Rect rect(p * dotSize, dotSize, dotSize);

				if (rect.leftClicked)
				{
					++dots[p.y][p.x] %= 4;
				}

				const Color color(240 - dots[p.y][p.x] * 70);

				rect.stretched(-1).draw(color);
			}
		}

		ScalableWindow::DrawBlackBars(HSV(40, 0.2, 0.9));
	}
}

正投影

Cmaera のパラメータを変更すると、3D 描画が正投影になります。詳しくは Camera.hpp ヘッダを確認してください。
現バージョンでは実験的な機能のため、Mouse::Ray() などの座標計算関連の機能とは連動しません。

# include <Siv3D.hpp>

void Main()
{
	Graphics3D::SetAmbientLight(ColorF(0.3));

	Camera camera(Vec3(0, 8, 0), Vec3(0, 0, 0), Vec3(0, 1, 0), -20, 20, -15, 15, 0.1, 200.0);

	Graphics3D::SetCamera(camera);

	while (System::Update())
	{
		Graphics3D::FreeCamera();

		for (auto p : step({ 21,21 }))
		{
			Box(-50 + p.x * 5, 1.5, 50 - p.y * 5, 3).draw(HSV(p.x * 10 + p.y * 3));
		}
	}
}

クリップボードの更新検知

最後に Clipboard::HasChanged() が呼ばれたあとに、クリップボードの中身が変更されたり消去されたりしたときに、true を返してそれを通知します。

# include <Siv3D.hpp>

void Main()
{
	while (System::Update())
	{
		if (Input::MouseR.clicked)
		{
			Clipboard::Clear();
		}

		if (Clipboard::HasChanged())
		{
			Println(L"HasChanged");
		}
	}
}

MeshData::Capsule()

MeshData で作成できる 3D 形状に、カプセル形状が追加されました。

# include <Siv3D.hpp>

void Main()
{
	Graphics::SetBackground(Color(80, 160, 230));
	Graphics3D::SetAmbientLight(ColorF(0.3));

	const Texture textureGround(L"Example/grass.jpg", TextureDesc::For3D);
	const Mesh meshGround(MeshData::Plane(30, 30, { 6, 6 }));

	const Mesh mesh(MeshData::Capsule(1, 1.5));

	while (System::Update())
	{
		Graphics3D::FreeCamera();

		meshGround.draw(textureGround);

		mesh.translated(0, 2, 0).draw().drawShadow();
	}
}

IME::SetCompositionWindowPos()

IME の日本語入力ウィンドウの表示位置を変更します。

# include <Siv3D.hpp>

void Main()
{
	Graphics::SetBackground(Color(160, 200, 100));

	GUI gui(GUIStyle::Default);
	gui.setTitle(L"タイトル");
	gui.add(L"text", GUITextField::Create(6));

	while (System::Update())
	{
		IME::SetCompositionWindowPos(gui.getPos());
	}
}

Box2D と CameraBox2D と ScalableWindiow

Box2D による物理演算を簡単に扱う機能です。
左クリックで長方形を出現させます。
CameraBox2D は、W/A/S/D キーやマウス右ボタン、ホイールで 2D カメラの移動やズームを操作できます。

# include <Siv3D.hpp>
# include <HamFramework.hpp>

void Main()
{
	ScalableWindow::Setup();
	CameraBox2D camera(Vec2(0, 0), 17.0);

	PhysicsWorld world;

	auto ground = world.createLineString(Vec2(0, 0), { Vec2(-20, 20), Vec2(-20, 10), Vec2(20, 0), Vec2(20, 20) }, none, none, PhysicsBodyType::Static);

	Array<PhysicsBody> bodies;

	bodies.push_back(world.createPolygon(Vec2(-8, 9), Geometry2D::CreateStar(1)));

	for (auto i : step(100))
	{
		bodies.push_back(world.createCircle(Vec2(0, 5 + i * 2), 0.5));
	}

	while (System::Update())
	{
		world.update();
		camera.update();
		{
			const auto t1 = camera.createTransformer();
			const auto t2 = ScalableWindow::CreateTransformer();

			if (Input::MouseL.clicked)
			{
				bodies.push_back(world.createRect(Mouse::PosF(), Vec2(1, 2)));
			}

			ground.draw();

			for (const auto& body : bodies)
			{
				body.draw(Palette::Skyblue);
			}
		}
		camera.draw(Palette::Orange);
	}
}

サンプルゲーム

Box2D 機能を使ったサンプル 3D ゲームです。
XInput 対応コントローラでも操作できます。

# include <Siv3D.hpp>
# include <HamFramework.hpp>

void Main()
{
	Window::Resize(1280, 720);
	Window::SetStyle(WindowStyle::Sizeable);
	Graphics::SetBackground(ColorF(0.3, 0.7, 1));
	Graphics3D::SetAmbientLight(ColorF(0.3));
	Graphics3D::SetFog(Fog::SquaredExponential(ColorF(0.3, 0.7, 1), 0.004));

	const PerlinNoise noise;
	Array<Vec2> points = { Vec2(500, -50), Vec2(-1, -50) };
	for (auto i : step(500))
	{
		points.emplace_back((i * 1) - 1, noise.octaveNoise(i*0.01, 4) * 24);
	}
	std::reverse(points.begin(), points.end());

	PhysicsWorld world;
	const auto geround = world.createLineString(Vec2(0, 0), points, PhysicsMaterial(0.0, 0.1, 0.6), none, PhysicsBodyType::Static);
	const auto car = world.createRect(Vec2(0, 1), RectF(-1, -0.1, 2, 3.3), PhysicsMaterial(0.04));
	auto wheel1 = world.createCircle(Vec2(-1, 0.35), 0.4, PhysicsMaterial(1.0, 0.1, 0.9));
	auto wheel2 = world.createCircle(Vec2(1, 0.4), 0.4, PhysicsMaterial(1.0, 0.1, 0.9));

	PhysicsWheelJoint spring1 = world.createWheelJoint(car, wheel1, wheel1.getPos(), { 0.0,1.0 }, WheelJointState(true, 20.0, 4.0));
	PhysicsWheelJoint spring2 = world.createWheelJoint(car, wheel2, wheel2.getPos(), { 0.0,1.0 }, WheelJointState(false, 20.0, 4.0));

	const Model model(L"Example/Well/Well.wavefrontobj");
	const Mesh groundMesh(MeshData::Polygon(Polygon(points), 200.0, { 0.1, 0.1 }, { 0, 0 }));
	TextureAsset::Register(L"Brick", L"Example/brick.jpg", TextureDesc::For3D);
	const Texture brick(L"Example/brick.jpg", TextureDesc::For3D);
	const Texture textureGround(L"Example/Ground.jpg", TextureDesc::For3D);
	const Texture textureParticle(L"Example/Particle.png", TextureDesc::For3D);

	const auto DrawWheel = [](const Vec2& pos, double angle) {
		Cylinder(0.4, 1.0, Quaternion::Yaw(angle).pitch(HalfPi)).asMesh().translated(pos.x, pos.y, 0)
			.draw(TextureAsset(L"Brick")).drawShadow();
	};

	Array<Particle> smokes;
	Graphics3D::SetDepthStateForward(DepthState::TestOnly);

	XInput xinput(0);
	xinput.setRightThumbDeadZone();

	Camera camera;
	Spherical s(15, 80_deg, -90_deg);
	double previousVY = 0.0;

	while (System::Update())
	{
		if (Abs(wheel1.getVelocity().x) > 0.1)
		{
			wheel1.applyForce({ Sign(wheel1.getVelocity().x) * -0.5,0 });
			wheel2.applyForce({ Sign(wheel2.getVelocity().x) * -0.5,0 });
		}
		else if (Abs(wheel1.getVelocity().x) > 0.01)
		{
			wheel1.applyForce({ Sign(wheel1.getVelocity().x) * -4,0 });
			wheel2.applyForce({ Sign(wheel2.getVelocity().x) * -4,0 });
		}

		if ((Input::KeyZ | xinput.buttonX).pressed)
		{
			spring1.setMotor(true, 30.0);
		}
		else if ((Input::KeyX | xinput.buttonA).pressed)
		{
			spring1.setMotor(true, -30.0);
		}
		else
		{
			spring1.setMotor(false);
		}

		s.phi -= xinput.rightThumbX * 0.02;
		s.theta = Clamp(s.theta - xinput.rightThumbY *0.02, 10_deg, 90_deg);

		world.update();

		const Vec3 carPos(car.getPos(), 0);
		camera.pos = Lerp(camera.pos, carPos + s, 0.1);
		camera.lookat = carPos;
		Graphics3D::SetCamera(camera);
		Graphics3D::SetShadowLight(ShadowLight(carPos + Vec3(10, 10, -10), carPos));

		const double currentVY = car.getVelocity().y;
		const double vi = Max(currentVY - previousVY, 0.0) / 1.0;
		previousVY = currentVY;
		xinput.setVibration(vi, vi + Abs(spring1.getMotorSpeed() * 0.003) + Abs(0.005 * car.getVelocity().x));

		if (vi > 0.005)
		{
			const Vec3 pos(carPos + Vec3(System::FrameCount() % 2 ? -0.5 : 1, -0.7, 0));
			smokes.push_back(Particle(pos, 1.5, ColorF(0.6)));
		}

		for (auto& smoke : smokes)
		{
			smoke.pos.moveBy(-0.02f, 0.04f, 0.02f, 0.0f);
			smoke.scaling *= 1.025f;
			smoke.color.w = Pow(Max((1.5f - smoke.pos.z) / 2.0f, 0.0f), 2.0f);
		}

		if (!smokes.empty() && smokes.front().pos.z > 1.5)
		{
			smokes.erase(smokes.begin());
		}

		groundMesh.rotated(HalfPi, 0, 0).draw(textureGround);
		DrawWheel(wheel1.getPos(), wheel1.getAngle());
		DrawWheel(wheel2.getPos(), wheel2.getAngle());

		model.draw(Mat4x4::Rotate(0, 0, car.getAngle()).translated(carPos))
			.drawShadow(Mat4x4::Rotate(0, 0, car.getAngle()).translated(carPos));

		Graphics3D::DrawParticlesForward(smokes, textureParticle);
	}
}
⚠️ **GitHub.com Fallback** ⚠️