一介のCS人の綴り

トップ > プログラミング > 創作プログラミング
Tweet

今回の主題は?

Siv3D AdventCalendar 2015の20日目の記事です.

Siv3D Advent Calendar 2015

Siv3D開発者である@Reputelessさんの誘いの下、Siv3D Advent Calendar 2015の20日目に参加させていただき、今回はその記事を作成させていただきます。 主題としては、ゲーム作りにそろそろ手をかけてみようかとSiv3Dを導入して一か月した現在の感想と、今までのように簡単なプログラムの実装と解説をしていきたいなと考えております。

ここでは、このライブラリに慣れるという趣旨で、簡単なプログラムを記述し実行して楽しんでいこう!ということで、「Siv3Dで遊ぼう!」というタイトルで数回記事を書いていきます。

Siv3Dを始めて1ヶ月

まず単刀直入に、私はSiv3Dを導入するまで、ゲームを作成したことがほとんどありませんでした!

私は、基本的には競技プログラミングをしていたり、大学の課題をこなしていたりと、しがない大学生の一人です。 ターミナル上で計算結果を標準出力することが基本であった私には、ゲームなど敷居が高いものなのではという感覚が少なからずあったわけですが、 とある知り合いから「第5回名工大ハッカソンに参加しないか?」とのお誘いを受け、 参加するからには少しはゲーム作りを事前に勉強しておこうと導入したのがSiv3Dでした。

Siv3Dで作ったプログラムたち

これらは、以前に記事「Siv3Dで遊ぼう!」で掲載したプログラムたちです。これらのプログラムはゲームプログラミングを続けてきた人々から見れば、どれもつまらないものばかりかもしれません。 しかし、普段ゲームを作らない私からすれば、これらのプログラムを作成する過程で無意識に持ち合わせていたゲーム製作に対するためらいを消し去り、自分でも何か作ってみたいなという感覚を生み出しました。

実際のハッカソンではチームメンバーのスキルと作りたいゲームのタイプを考慮して、残念ながらSiv3Dを用いることはありませんでしたが、他のチームがSiv3Dでゲームを作っていたりと このライブラリの実用性の高さも実感した次第でありました。

また、同じ班に所属したメンバーにSiv3Dを薦めてみたりと、自分の周りにも少しずつこのライブラリの存在が知れ渡りつつある今、友達と一緒に何かゲームを作ってみるのも面白いなと思います。 次のハッカソンは2月に開催するそうです。次はSiv3Dを使った面白いゲームができたらなと思います!

Siv3Dで遊ぼう! - No.4

今回は、「Siv3Dで遊ぼう!」の主旨通り、明確な目的もなくただひたすらにSiv3Dで遊んでみました。

→ クリックで開閉

数値の設定

const int COLOR_SIZE = 4;
const int ROUND = 360;
const double INIT_R = 300.0;
const Vec2 INIT_V = {5.0, 7.0};
const double INIT_Vz = 3.0;
                

今回のプログラムは、パラメータの初期値を頻繁に変更して実装したため、事前に定数として定義することで、その変更を容易にしている。

色を設定する関数

// HSVの色を格納する
inline void HSVnumber(Color colors[], int num){
	colors[2] = HSV(num % ROUND);
	colors[3] = HSV((num + ROUND / 2) % ROUND);
}
// 色を指定するmode変数を循環させている
inline int ringMode(int mode){
	return (mode + 1) % COLOR_SIZE;
}
                

プログラム中に使用するHSV色を格納したり、どのcolors[]の要素を適応するかを定めるmode変数を定義域内で循環させている。

ボールを動かす関数

// ボールが壁に衝突した際、速度を反転させる(完全弾性衝突)
inline void setBallState(double& z, double& v, int center, int wall){
	if (z < center - wall){
		v = -v;  z = center - wall;
	}
	else if (z > center + wall){
		v = -v;  z = center + wall;
	}
}
// 奥行にあわせた数値に変更の後、ボールを動かす関数
void moveBall(double& circleR, Vec2& circleP, Vec2& velocity){
	circleR -= INIT_Vz * circleR / INIT_R;
	if (circleR < 1.0) circleR = INIT_R * 1.5;
	Vec2 wall = Window::Center() * circleR / INIT_R;
	circleP += velocity;

    // 壁に衝突時
	setBallState(circleP.x, velocity.x, Window::Center().x, wall.x);
	setBallState(circleP.y, velocity.y, Window::Center().y, wall.y);
}
                

今回のプログラムでは、ボールに見立てた円を画面内でバウンドさせながら動かす。その際、描画自体は2Dでありながら、 円の半径や壁となる位置を遠近法を用いて変動させることで、奥行きがあり奥のほうへボールが動いているようにみせている。 具体的にいえば、円の半径が小さくなる速度の初速度INIT_Vzに対し、現在の半径circleRを半径の初期値INIT_Rで割った値で乗算し、 その値をcircleRから引くことで半径を小さくし、見かけ上画面奥方向へ動いたようにみせている。

壁の位置の変動については、小さくなった円と元の円が相似であったと同様に、壁を構成する長方形も相似であればバウンドする位置として 適切であると判断し、変動させている。

また、壁との衝突は単に速度を反転させるだけ、つまり完全弾性衝突にしている。 さらに、ボールについても壁に衝突しない限り速度が一定に保つ等速直線運動をしている。(実際には奥行方向が存在するため、速度は見かけ上上がっているように見える。) ここで、反発係数を用いたり、重力加速度などを導入しても面白いかもしれない。

void Main(){→ クリックで開閉

描画準備

    // ウィンドウサイズを 幅 1000, 高さ 400 にする
	Window::Resize(800, 400);
	const Font font(60, Typeface::Medium);
	Color colors[COLOR_SIZE] = {
		Palette::White, Palette::Black, HSV(0), HSV(180)
	};

	// colors[]の中で背景となる色を指定する添え字mode
	int mode = 0;
	Graphics::SetBackground(colors[mode]);
	double circleR = INIT_R;
	Vec2 circleP = { circleR, circleR };
	Vec2 velocity = INIT_V;
                        

ウインドウのサイズや使用する色を格納した配列等をメインループ前に準備している。

ボールや背景の設定

        moveBall(circleR, circleP, velocity);
		HSVnumber(colors, System::FrameCount() % ROUND);
		// マウスの左ボタンがクリックされたら
		if (Input::MouseL.clicked)
		{
			mode = (mode + 1) % COLOR_SIZE;
		}
		// マウスの右ボタンがクリックされたら
		if (Input::MouseR.clicked)
		{
			if (--mode < 0) mode = COLOR_SIZE - 1;
		}
		Graphics::SetBackground(colors[mode]);
		const Circle circle(circleP, circleR);
                                

ここでは、各フレームに対するボールの状態や、ボールや背景の色を決定している。

ここで、画面上を左クリックするとmodeがインクリメントされ、右クリックするとデクリメントされるように実装している。 また、modeはColor colors[COLOR_SIZE]のいずれかを指定するための変数であるため、該当配列の添え字の範囲外であったなら循環させている。

ステンシルステートを用いた描画

        Graphics2D::SetStencilState(StencilState::None);
		circle.draw(colors[ringMode(mode)]);

        // 画面における対角線を描画
		Line(0, 0, Window::Width(), Window::Height()).draw(4, colors[ringMode(mode)]);
		Line(Window::Width(), 0, 0, Window::Height()).draw(4, colors[ringMode(mode)]);

        // 描画を置換する位置を設定
		Graphics2D::SetStencilValue(1);
		Graphics2D::SetStencilState(StencilState::Replace);
		circle.draw();
		
		// 円と一致していない部分の描画
		Graphics2D::SetStencilState(StencilState::Test(StencilFunc::NotEqual));
		font(L"Siv3D\nAdvent Calendar\n2015").drawCenter(20, colors[ringMode(mode)]);
		// 円と一致した部分の描画
		Graphics2D::SetStencilState(StencilState::Test(StencilFunc::Equal));
		font(L"Siv3D\nAdvent Calendar\n2015").drawCenter(15, colors[mode]);
                                

ここでは、先に壁と天井や床の境目を模した画面における対角線を描画している。対角線の交点が遠近法における消失点を意味している。

また、先にボールを描画した上、ステンシルステートを用いてそのボール内の描画とボール外の描画をしている。 このとき、ボール外について文字と背景、またボール内についてボールと文字の色を一対となるように描画している。

←} // while (System::Update())
←} // void Main()
←}

ソースコードの全容

# include <siv3d.hpp>

const int COLOR_SIZE = 4;
const int ROUND = 360;
const double INIT_R = 300.0;
const Vec2 INIT_V = {5.0, 7.0};
const double INIT_Vz = 3.0;

inline void HSVnumber(Color colors[], int num){
	colors[2] = HSV(num % ROUND);
	colors[3] = HSV((num + ROUND / 2) % ROUND);
}
inline int ringMode(int mode){
	return (mode + 1) % COLOR_SIZE;
}
inline void setBallState(double& z, double& v, int center, int wall){
	if (z < center - wall){
		v = -v;  z = center - wall;
	}
	else if (z > center + wall){
		v = -v;  z = center + wall;
	}
}
void moveBall(double& circleR, Vec2& circleP, Vec2& velocity){
	circleR -= INIT_Vz * circleR / INIT_R;
	if (circleR < 1.0) circleR = INIT_R * 1.5;
	Vec2 wall = Window::Center() * circleR / INIT_R;
	circleP += velocity;

	setBallState(circleP.x, velocity.x, Window::Center().x, wall.x);
	setBallState(circleP.y, velocity.y, Window::Center().y, wall.y);
}

void Main(){
	// ウィンドウサイズを 幅 1000, 高さ 400 にする
	Window::Resize(800, 400);
	const Font font(60, Typeface::Medium);
	Color colors[COLOR_SIZE] = {
		Palette::White, Palette::Black, HSV(0), HSV(180)
	};

	// colors[]の中で背景となる色を指定する添え字mode
	int mode = 0;
	Graphics::SetBackground(colors[mode]);
	double circleR = INIT_R;
	Vec2 circleP = { circleR, circleR };
	Vec2 velocity = INIT_V;

	while (System::Update())
	{
		moveBall(circleR, circleP, velocity);
		HSVnumber(colors, System::FrameCount() % ROUND);
		// マウスの左ボタンがクリックされたら
		if (Input::MouseL.clicked)
		{
			mode = (mode + 1) % COLOR_SIZE;
		}
		// マウスの右ボタンがクリックされたら
		if (Input::MouseR.clicked)
		{
			if (--mode < 0) mode = COLOR_SIZE - 1;
		}
		Graphics::SetBackground(colors[mode]);
		const Circle circle(circleP, circleR);

		Graphics2D::SetStencilState(StencilState::None);
		circle.draw(colors[ringMode(mode)]);
		Line(0, 0, Window::Width(), Window::Height()).draw(4, colors[ringMode(mode)]);
		Line(Window::Width(), 0, 0, Window::Height()).draw(4, colors[ringMode(mode)]);

		Graphics2D::SetStencilValue(1);
		Graphics2D::SetStencilState(StencilState::Replace);
		circle.draw();
		
		// 円と一致していない部分の描画
		Graphics2D::SetStencilState(StencilState::Test(StencilFunc::NotEqual));
		font(L"Siv3D\nAdvent Calendar\n2015").drawCenter(20, colors[ringMode(mode)]);
		// 円と一致した部分の描画
		Graphics2D::SetStencilState(StencilState::Test(StencilFunc::Equal));
		font(L"Siv3D\nAdvent Calendar\n2015").drawCenter(15, colors[mode]);
		
	}
}
        

実行結果

これを見て分かったと思いますが、ページ冒頭に張り付けた「Siv3D AdventCalendar 2015」の画像はこのプログラムの実行途中であったのでした!

Siv3Dを導入して1か月でありますので、まだまだ未熟ですがのんびりマイペースに実力をつけ、思ったようなプログラムをかけるようにしたいなと思います。 ここまで読んでいただきありがとうございました。 こちらの個人サイトはリニューアルして1ヶ月ですので記事が少ないですが、今後も良い記事を目指して作成していきますので、今後ともよろしくお願いします!

明日は@yashiheiさんの記事です。よろしくお願いします。

この投稿は Siv3D AdventCalendar 2015の20日目の記事です.

Comments

Top