Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

プログラマ勉強会 第3回

Last updated at Posted at 2024-07-08

Siv3Dとは

2D/3Dグラフィックス、サウンド、コントローラーやキーボードの入力などを簡単に扱えるようにするフレームワークです。

Siv3Dの環境構築

Siv3Dの公式ページにそれぞれのOSでの環境構築方法が書かれているので、自分が使っているOSの説明を読んで環境構築してください。

Siv3Dでの開発方法

一通り環境構築を行うと、以下のようなテンプレートが生成されていると思います。

# include <Siv3D.hpp> // Siv3D v0.6.14

void Main()
{
	// 背景の色を設定する | Set the background color
	Scene::SetBackground(ColorF{ 0.6, 0.8, 0.7 });

	// 画像ファイルからテクスチャを作成する | Create a texture from an image file
	const Texture texture{ U"example/windmill.png" };

	// 絵文字からテクスチャを作成する | Create a texture from an emoji
	const Texture emoji{ U"🦖"_emoji };

	// 太文字のフォントを作成する | Create a bold font with MSDF method
	const Font font{ FontMethod::MSDF, 48, Typeface::Bold };

	// テキストに含まれる絵文字のためのフォントを作成し、font に追加する | Create a font for emojis in text and add it to font as a fallback
	const Font emojiFont{ 48, Typeface::ColorEmoji };
	font.addFallback(emojiFont);

	// ボタンを押した回数 | Number of button presses
	int32 count = 0;

	// チェックボックスの状態 | Checkbox state
	bool checked = false;

	// プレイヤーの移動スピード | Player's movement speed
	double speed = 200.0;

	// プレイヤーの X 座標 | Player's X position
	double playerPosX = 400;

	// プレイヤーが右を向いているか | Whether player is facing right
	bool isPlayerFacingRight = true;

	while (System::Update())
	{
		// テクスチャを描く | Draw the texture
		texture.draw(20, 20);

		// テキストを描く | Draw text
		font(U"Hello, Siv3D!🎮").draw(64, Vec2{ 20, 340 }, ColorF{ 0.2, 0.4, 0.8 });

		// 指定した範囲内にテキストを描く | Draw text within a specified area
		font(U"Siv3D (シブスリーディー) は、ゲームやアプリを楽しく簡単な C++ コードで開発できるフレームワークです。")
			.draw(18, Rect{ 20, 430, 480, 200 }, Palette::Black);

		// 長方形を描く | Draw a rectangle
		Rect{ 540, 20, 80, 80 }.draw();

		// 角丸長方形を描く | Draw a rounded rectangle
		RoundRect{ 680, 20, 80, 200, 20 }.draw(ColorF{ 0.0, 0.4, 0.6 });

		// 円を描く | Draw a circle
		Circle{ 580, 180, 40 }.draw(Palette::Seagreen);

		// 矢印を描く | Draw an arrow
		Line{ 540, 330, 760, 260 }.drawArrow(8, SizeF{ 20, 20 }, ColorF{ 0.4 });

		// 半透明の円を描く | Draw a semi-transparent circle
		Circle{ Cursor::Pos(), 40 }.draw(ColorF{ 1.0, 0.0, 0.0, 0.5 });

		// ボタン | Button
		if (SimpleGUI::Button(U"count: {}"_fmt(count), Vec2{ 520, 370 }, 120, (checked == false)))
		{
			// カウントを増やす | Increase the count
			++count;
		}

		// チェックボックス | Checkbox
		SimpleGUI::CheckBox(checked, U"Lock \U000F033E", Vec2{ 660, 370 }, 120);

		// スライダー | Slider
		SimpleGUI::Slider(U"speed: {:.1f}"_fmt(speed), speed, 100, 400, Vec2{ 520, 420 }, 140, 120);

		// 左キーが押されていたら | If left key is pressed
		if (KeyLeft.pressed())
		{
			// プレイヤーが左に移動する | Player moves left
			playerPosX = Max((playerPosX - speed * Scene::DeltaTime()), 60.0);
			isPlayerFacingRight = false;
		}

		// 右キーが押されていたら | If right key is pressed
		if (KeyRight.pressed())
		{
			// プレイヤーが右に移動する | Player moves right
			playerPosX = Min((playerPosX + speed * Scene::DeltaTime()), 740.0);
			isPlayerFacingRight = true;
		}

		// プレイヤーを描く | Draw the player
		emoji.scaled(0.75).mirrored(isPlayerFacingRight).drawAt(playerPosX, 540);
	}
}

色々と書かれていますが、この中で重要なのは以下の部分のみです。

# include <Siv3D.hpp> // Siv3D v0.6.14

void Main()
{

	while (System::Update())
	{
		
	}
}

実行するとvoid Main() {}{ }で囲まれた中身が実行されていきます。

while (System::Update()) {}

System::Update()は、ウィンドウが閉じられたりEscキーが押されるなどのゲーム終了時にfalseになります。
つまり、ゲームが終了するまで上のwhile文はループし続けるということです。

キーボード入力を受け取ってプレイヤーを動かしたり、敵を動かしたりなどのゲーム中の処理は基本的にここに書いて行きます。
逆に、変数の定義や、プレイヤーの初期位置の設定など、ゲーム開始前の初期化処理はwhile文の上に書くことになります。

Siv3Dで使える新しい型

Siv3Dでは、intstd::stringなど、今までに使っていた型の代わりに以下の型が使用できます。

従来の型 Siv3Dでの型 扱えるデータ
int, long int32 整数値
std::string String 文字列
float, doubleはそのまま 小数値

Siv3Dで使える型については以下のチュートリアルにまとめられています。

int32

Siv3Dでは整数値を扱うのにint32という型を使います。
末尾の32は32ビットで表せる整数値まで扱えるという意味です。
32ビットで使える範囲は-2,147,483,6482,147,483,647です。
他にも、int8, int16, int64などがありますが、特に理由がなければint32を使うことが多いです。

String

今まで、文字列型の変数を扱うためにstd::stringという型を使っていましたが、Siv3DではStringという型が使えるのでこれを使っていきます。

いままで、コード上で文字列を表現するのにダブルクォーテーション""を使っていましたが、String型で文字列を扱う場合は書き方が変わります。

String text = U"Hello World!";

↑のように""の前にUを付ける必要があります

Siv3Dでの文字列の操作に関しては以下のページにまとまっています。

Siv3Dでの出力

画面に表示する

Siv3Dで画面に文字を表示したい場合、以下のように書きます。

# include <Siv3D.hpp> // Siv3D v0.6.14

void Main()
{
	String name = U"ぱんどど";
	int32 age = 20;

	Print << name << U"は" << age << U"歳です";

	while (System::Update())
	{
		
	}
}

whileはゲームが終了するまで無限ループするので、while文の中にPrintを書くと文字列が表示され続けてしまいます。

# include <Siv3D.hpp> // Siv3D v0.6.14

void Main()
{
	String name = U"ぱんどど";
	int32 age = 20;

	while (System::Update())
	{
    	Print << name << U"は" << age << U"歳です";		
	}
}

コンソールに出力する

Printで画面に表示する方法では、画面からはみ出した内容は見えなくなってしまいます。
過去に出力された内容を全部見れるようにしたい、出力された内容をコピペしたい、などといった場合は出力先にConsoleを使います。

# include <Siv3D.hpp> // Siv3D v0.6.14

void Main()
{
	String name = U"ぱんどど";
	int32 age = 20;

	Console << name << U"は" << age << U"歳です";

	while (System::Update())
	{
		
	}
}

↑のコードを実行すると、通常の画面とは別にコンソールウィンドウが出現して、そこに内容が出力されます。
デバッグに便利なので積極的に使っていきましょう!

Siv3Dの機能 その1 図形の描画、キーボード入力

図形の描画

参考公式チュートリアル

Siv3Dでは簡単に図形を描画できます。
以下のコードを実行してみてください。

# include <Siv3D.hpp> // Siv3D v0.6.14

void Main()
{

	while (System::Update())
	{
		Circle{ 400, 300, 100 }.draw();
	}
}

コードを実行すると、以下のようにウィンドウの中心に円が描画されていると思います。
image.png

以下の箇所で円を描画しています。
400がx座標、300がy座標として円を表示する位置を指定しています。
100は半径です。

Circle{ 400, 300, 100 }で円オブジェクトを作り、.draw()でそのオブジェクトに対応したメソッド(関数のようなもの)を呼び出して描画しています。
オブジェクト、メソッドについては次回のクラスの内容で説明します。

Circle{ 400, 300, 100 }.draw();

円を描画したい場合は以下のように書きます。

Circle{ X座標, Y座標, 半径 }.draw();

円の描画処理をwhile文の中に書いているのは、System::Update()という関数が呼び出されるたびに描画された内容がリセットされてしまうからです。
関数については後で説明するので、今はwhile文が1ループするたびに描画内容が消されると覚えていてください。

試しに以下のコードを実行してみると、実行した直後の一瞬だけ円が描画されて、すぐに真っ暗になってしまうと思います。

# include <Siv3D.hpp> // Siv3D v0.6.14

void Main()
{
	Circle{ 400, 300, 100 }.draw();

	while (System::Update())
	{

	}
}

長方形の描画方法

Rect{ X座標, Y座標, 横の長さ, 縦の長さ }.draw();

Printで表示した内容はwhile文に書かなくても消えることはありません。
ただし、

ClearPrint();

と書くことで、手動で削除することが出来ます。

Siv3Dでは、他にもいろいろな図形が描画出来ます。
図形の描画方法について詳しくは以下のサイトが参考になります。

キーボード入力

参考公式チュートリアル

以下のコードを実行してみてください。

# include <Siv3D.hpp> // Siv3D v0.6.14

void Main()
{
	while (System::Update())
	{
		if (KeyEnter.down()) {
			Print << U"Enterキーが押されました!";
		}
	}
}

以下のコードで、キーボードが押されているかを調べることが出来ます。
キー名はKeyA, KeyB, KeySpace, KeyUpなどがあります。
キー名.down()はキーボードが押されるとtrueになります。つまり、if文の中が実行されるというわけです。

if (キー名.down()) {
    キーが押されている場合の処理
}

.down().pressed()にすると、キーが押されている間はずっとtrueにすることが出来ます。
.up()にすると、キーが離された瞬間だけtrueになります。
<<コード例>>

# include <Siv3D.hpp> // Siv3D v0.6.14

void Main()
{
	while (System::Update())
	{
		if (KeyEnter.down()) {
			Print << U"Enterキーが押されました!";
		}

		if (KeyEnter.up()) {
			Print << U"Enterキーが離されました!";
		}

		if (KeyEnter.pressed()) {
			Print << U"Enterキーが押され続けています!";
		}
	}
}

練習問題1

問題1-1

以下のコードを完成させて、円の図形を十字キーで操作できるようにしてみましょう。

# include <Siv3D.hpp> // Siv3D v0.6.14

void Main()
{
	int32 x = 400;
	int32 y = 300;

	while (System::Update())
	{
		if (KeyUp.pressed()) {
			// 上に移動する
			y -= 3; // y座標を上に3ピクセルずらす
		}

		if (KeyDown.pressed()) {
			// 下に移動する
		}

		if (/*左キー入力*/) {
			// 左に移動する
		}

		if (/*右キー入力*/) {
			// 右に移動する
		}

		Circle{ x, y, 50 }.draw(); // 円を描画する
	}
}
解答例
# include <Siv3D.hpp> // Siv3D v0.6.14

void Main()
{
	int32 x = 400;
	int32 y = 300;

	while (System::Update())
	{
		if (KeyUp.pressed()) {
			// 上に移動する
			y -= 3; // y座標を上に3ピクセルずらす
		}

		if (KeyDown.pressed()) {
			// 下に移動する
			y += 3; // y = y + 3 でも良い
		}

		if (KeyLeft.pressed()) {
			// 左に移動する
			x -= 3;
		}

		if (KeyRight.pressed()) {
			// 右に移動する
			x += 3;
		}

		Circle{ x, y, 50 }.draw(); // 円を描画する
	}
}

問題1-2

問題1で作ったコードの数値を変えて、円が移動する速度を変えてみよう。

問題1-3

速度を変えるために、上下左右すべての値を変更するのは面倒ですよね。
while文の上に速度を保持する変数を定義して、速度を書き換える手間を省きましょう!

ヒント

移動するときは以下のように変数の値で加算、減算します。

y -= speed;
解答例
# include <Siv3D.hpp> // Siv3D v0.6.14

void Main()
{
	int32 x = 400;
	int32 y = 300;
+	int32 speed = 3;

	while (System::Update())
	{
		if (KeyUp.pressed()) {
			// 上に移動する
-			y -= 3;
+			y -= speed;
		}

		if (KeyDown.pressed()) {
			// 下に移動する
-			y += 3;
+			y += speed;
		}

		if (KeyLeft.pressed()) {
			// 左に移動する
-			x -= 3;
+			x -= speed;
		}

		if (KeyRight.pressed()) {
			// 右に移動する
-			x += 3;
+			x += speed;
		}

		Circle{ x, y, 50 }.draw(); // 円を描画する
	}
}

問題1-4

Shiftキーでダッシュできるようにしてみましょう。

ヒント

Shiftキーが押されたら速度を変える処理を書いてみよう。

Shiftキーが押された瞬間だけtrueにしたいならKeyShift.down()
Shiftキーが離された瞬間だけtrueにしたいならKeyShift.up()

解答例

解答例1

# include <Siv3D.hpp> // Siv3D v0.6.14

void Main()
{
	int32 x = 400;
	int32 y = 300;
	int32 speed = 3;

	while (System::Update())
	{
+		if (KeyShift.down()) speed = 10;
+		if (KeyShift.up()) speed = 3;

		if (KeyUp.pressed()) {
			// 上に移動する
			y -= speed;
		}

		if (KeyDown.pressed()) {
			// 下に移動する
			y += speed;
		}

		if (KeyLeft.pressed()) {
			// 左に移動する
			x -= speed;
		}

		if (KeyRight.pressed()) {
			// 右に移動する
			x += speed;
		}

		Circle{ x, y, 50 }.draw(); // 円を描画する
	}
}

解答例2

# include <Siv3D.hpp> // Siv3D v0.6.14

void Main()
{
	int32 x = 400;
	int32 y = 300;
-	int32 speed = 3;

	while (System::Update())
	{
+		int32 speed = 3;
+		if (KeyShift.pressed()) speed = 10;

		if (KeyUp.pressed()) {
			// 上に移動する
			y -= speed;
		}

		if (KeyDown.pressed()) {
			// 下に移動する
			y += speed;
		}

		if (KeyLeft.pressed()) {
			// 左に移動する
			x -= speed;
		}

		if (KeyRight.pressed()) {
			// 右に移動する
			x += speed;
		}

		Circle{ x, y, 50 }.draw(); // 円を描画する
	}
}

問題1-EX

画面の端にぶつかったら進めなくなるような処理を加えてみよう。

ヒント1

画面サイズはX座標が0~800、Y座標が0~600であることに注目

ヒント2

x座標が0を下回ったら0に戻す、800を上回ったら800に戻す。
y座標が0を下回ったら0に戻す、600を上回ったら600に戻せばよさそう!

解答例
# include <Siv3D.hpp> // Siv3D v0.6.14

void Main()
{
	int32 x = 400;
	int32 y = 300;
	int32 speed = 3;

	while (System::Update())
	{
		int32 speed = 3;
		if (KeyShift.pressed()) speed = 10;

		if (KeyUp.pressed()) {
			// 上に移動する
			y -= speed;
		}

		if (KeyDown.pressed()) {
			// 下に移動する
			y += speed;
		}

		if (KeyLeft.pressed()) {
			// 左に移動する
			x -= speed;
		}

		if (KeyRight.pressed()) {
			// 右に移動する
			x += speed;
		}

+		if (x < 0) x = 0;
+		if (x > 800) x = 800;
+		if (y < 0) y = 0;
+		if (y > 600) y = 600;

		Circle{ x, y, 50 }.draw(); // 円を描画する
	}
}

配列

参考公式チュートリアル

プレイヤーの所持アイテムを変数で保持することを考えます。
以下のようにitem1, item2, item3, ...というように何個も変数を作らなければいけないのでしょうか?

# include <Siv3D.hpp> // Siv3D v0.6.14

void Main()
{
	String item1 = U"木の剣";
	String item2 = U"魔法の杖";
	String item3 = U"全回復ポーション";

	Print << item1;
	Print << item2;
	Print << item3;

	while (System::Update())
	{
		
	}
}

それはあまりにも面倒ですよね。
そこで使うのが配列です。

配列とは、同じ型の複数のデータを一つの変数だけで扱える型です。
先のコードは配列を使えば以下のように書き換えられます。

# include <Siv3D.hpp> // Siv3D v0.6.14

void Main()
{
	Array<String> items;
	items << U"木の剣";
	items << U"魔法の杖";
	items << U"全回復ポーション";
	

	Print << items[0];
	Print << items[1];
	Print << items[2];

	while (System::Update())
	{
		
	}
}

配列の定義(作成)は以下のように書きます。
型名の部分にStringint32などの扱いたい型を書きます。

Array<型名> 変数名;

配列を作成したら、<<を使って配列に値を追加することができます。

変数名 << 追加したい値;

追加した値は、追加した順に0から順番にインデックス番号で取り出すことができます。
つまり、
items[0]と書けば、itemsに最初に追加した値木の剣が、
items[1]と書けば、itemsに2番目に追加した値魔法の杖
取り出せます。

変数名[インデックス番号]

以下のように値を書き換えることもできます

変数名[インデックス番号] = 新しい値

以下のように配列の作成と同時に値の設定を行うこともできます。
{ }で囲って、初期値として入れたい値を,区切りで書くことで、値0, 値1, 値2がそれぞれ変数名[0], 変数名[1], 変数名[2]でアクセスできます。

Array<変数の型> 変数名 = {
    値0,
    値1,
    値2
}

また、配列のインデックスは整数値の変数で指定することもできるので、for文を使って以下のように値を取り出すこともできます。

# include <Siv3D.hpp> // Siv3D v0.6.14

void Main()
{
	Array<String> items;
	items << U"木の剣";
	items << U"魔法の杖";
	items << U"全回復ポーション";

	for (int32 i = 0; i < 3; i++) {
		Print << items[i];
	}
	
	while (System::Update())
	{
		
	}
}

さらに、配列の要素数は変数名.size()で取得できるので以下のように書き直すことができます。

# include <Siv3D.hpp> // Siv3D v0.6.14

void Main()
{
	Array<String> items;
	items << U"木の剣";
	items << U"魔法の杖";
	items << U"全回復ポーション";

-	for (int32 i = 0; i < 3; i++) {
+	for (int32 i = 0; i < items.size(); i++) {
		Print << items[i];
	}
	
	while (System::Update())
	{
		
	}
}

さらに、範囲for文というC++での特殊なfor文の書き方を使うと以下のように簡潔に書くことができます。

# include <Siv3D.hpp> // Siv3D v0.6.14

void Main()
{
	Array<String> items;
	items << U"木の剣";
	items << U"魔法の杖";
	items << U"全回復ポーション";

-	for (int32 i = 0; i < items.size(); i++) {
+	for (String item : items) {
		Print << item;
	}
	
	while (System::Update())
	{
		
	}
}

以下の部分で、items配列にある要素を先頭から取り出して、itemという変数に入れています。

for (String item : items)

以下のように書いた場合、配列の値を書き換えることはできません。
これは、item変数はitems配列の要素ののコピーであり、これを書き換えても元の値は変えられないからです。

for (String item : items) {
    item = U"石ころ";
}

ただし、参照渡しという方法を使うことで、値のコピーではなく参照を渡すことができます。
この方法だと、値を変更することができます。

-for (String item : items) {
+for (String& item : items) {
    item = U"石ころ";
}

参照については次で詳しく説明します。

練習問題2

問題1-1

1~100までの値を持つ配列を作って、その配列の中身を画面に表示してみよう。
配列に値を追加する文を100回書くのは大変です。for文を使ってみましょう。

Siv3Dでは配列の変数をPrintに渡すだけで中身を表示できます。
このように、Siv3Dで用意されている型は大抵Printにいれたら中身を表示してくれるので、デバッグをするときに重宝します。

Array<int32> array;
array << 5;
Print << array;
Console << array;

出力例
image.png

解答例
# include <Siv3D.hpp> // Siv3D v0.6.14

void Main()
{
	Array<int32> ary;

	for (int i = 0; i < 100; i++) {
		ary << i + 1;
	}

	Print << ary;
	Console << ary;

	while (System::Update())
	{
		
	}
}

問題1-2

2~200の偶数の配列を作って、その配列の中身を画面に表示してみよう。

出力例
image.png

解答例

解答例1

# include <Siv3D.hpp> // Siv3D v0.6.14

void Main()
{
	Array<int32> ary;

	for (int i = 1; i <= 100; i++) {
		ary << i * 2;
	}

	Print << ary;
	Console << ary;

	while (System::Update())
	{
		
	}
}

解答例2

# include <Siv3D.hpp> // Siv3D v0.6.14

void Main()
{
	Array<int32> ary;

	for (int i = 1; i <= 100; i += 2) {
		ary << i;
	}

	Print << ary;
	Console << ary;

	while (System::Update())
	{
		
	}
}

問題2

配列を駆使して、3つのキャラをSpaceキーで切り替えて操作できるようなコードを書いてみよう。
以下のコードを完成させてください。

# include <Siv3D.hpp> // Siv3D v0.6.14

void Main()
{
	Array<int32> posXs; // x座標を保持する配列
	Array<int32> posYs; // y座標を保持する配列
	int32 speed = 5; // 速度は5で固定

	int32 selectedIndex = 0; // 現在操作しているキャラ(配列のインデックス)

	for (int i = 0; i < 3; i++) { // 3回ループする
		// 一旦全部真ん中に配置する
		posXs << 400;
		posYs << 300;
	}

	while (System::Update()) {
        // 選択中のキャラの移動
		if (KeyUp.pressed())    posYs[selectedIndex] -= speed; // 選択中のインデックスの値を加減算する
		if (KeyDown.pressed())  /* ここを書く */;
		/* 左移動(ここを書く) */
		/* 右移動(ここを書く) */

		if (KeySpace.down()) { // Spaceキーを押した瞬間
			// 操作キャラの切り替え(ここを書く)
            // インデックスが範囲外になったら0に戻すのを忘れずに!
		}

		// すべてのキャラの描画(円の描画)
		for (int32 i = 0; i < posXs.size(); i++) {
			int x, y;
			x = posXs[i];
			y = // 配列からキャラのy座標を取得する(ここを書く)

			if (i == selectedIndex) {
				Circle{ x, y, 50 }.draw(Palette::Yellow); // 選択中のキャラだったら黄色で描画する
			}
			else {
				Circle{ x, y, 50 }.draw(); // 選択中のキャラでなかったらデフォルト(白色)
			}
		}
	}

}

図形の色を指定して描画したい場合、以下のように.draw()()の中にPalette::色名を引数として渡すと、色を変えられます。

Circle{ 400, 300, 50 }.draw(Palette:Yellow);
解答例
# include <Siv3D.hpp> // Siv3D v0.6.14

void Main()
{
	Array<int32> posXs; // x座標を保持する配列
	Array<int32> posYs; // y座標を保持する配列
	int32 speed = 5; // 速度は5で固定

	int32 selectedIndex = 0; // 現在操作しているキャラ(配列のインデックス)

	for (int i = 0; i < 3; i++) { // 3回ループする
		// 一旦全部真ん中に配置する
		posXs << 400;
		posYs << 300;
	}

	while (System::Update()) {
		// 選択中のキャラの移動
		if (KeyUp.pressed())    posYs[selectedIndex] -= speed; // 選択中のインデックスの値を加減算する
		if (KeyDown.pressed())  posYs[selectedIndex] += speed;
		if (KeyLeft.pressed())  posXs[selectedIndex] -= speed;
		if (KeyRight.pressed()) posXs[selectedIndex] += speed;

		if (KeySpace.down()) { // Spaceキーを押した瞬間
			selectedIndex++; // インデックスを次に進める
			if (selectedIndex >= posXs.size()) { // インデックスが配列のサイズ以上になったら
				selectedIndex = 0; // インデックスを0に戻す
			}
		}

		// キャラの描画(円の描画)
		for (int32 i = 0; i < posXs.size(); i++) {
			int x, y;
			x = posXs[i];
			y = posYs[i];

			if (i == selectedIndex) {
				Circle{ x, y, 50 }.draw(Palette::Yellow); // 選択中のキャラだったら黄色で描画する
			}
			else {
				Circle{ x, y, 50 }.draw(); // 選択中のキャラでなかったらデフォルト(白色)
			}
		}
	}

}
配列の中の値をリアルタイムで見れるコード
# include <Siv3D.hpp> // Siv3D v0.6.14

void Main()
{
	Array<int32> posXs; // x座標を保持する配列
	Array<int32> posYs; // y座標を保持する配列
	int32 speed = 5; // 速度は5で固定

	int32 selectedIndex = 0; // 現在操作しているキャラ(配列のインデックス)

	for (int i = 0; i < 3; i++) { // 3回ループする
		// 一旦全部真ん中に配置する
		posXs << 400;
		posYs << 300;
	}

	Font font(32);
	while (System::Update()) {
		// 選択中のキャラの移動
		String output = U"selectedIndex: {}"_fmt(selectedIndex);
		for (int i = 0; i < posXs.size(); i++) {
			output += U"\nposXs[{}]={}, posYs[{}]={}"_fmt(i, posXs[i], i, posYs[i]);
		}
		font(output).draw(0, 0);

		if (KeyUp.pressed())    posYs[selectedIndex] -= speed; // 選択中のインデックスの値を加減算する
		if (KeyDown.pressed())  posYs[selectedIndex] += speed;
		if (KeyLeft.pressed())  posXs[selectedIndex] -= speed;
		if (KeyRight.pressed()) posXs[selectedIndex] += speed;

		if (KeySpace.down()) { // Spaceキーを押した瞬間
			selectedIndex++; // インデックスを次に進める
			if (selectedIndex >= posXs.size()) { // インデックスが配列のサイズ以上になったら
				selectedIndex = 0; // インデックスを0に戻す
			}
		}

		// キャラの描画(円の描画)
		for (int32 i = 0; i < posXs.size(); i++) {
			int x, y;
			x = posXs[i];
			y = posYs[i];

			if (i == selectedIndex) {
				Circle{ x, y, 50 }.draw(Palette::Yellow); // 選択中のキャラだったら黄色で描画する
			}
			else {
				Circle{ x, y, 50 }.draw(); // 選択中のキャラでなかったらデフォルト(白色)
			}
		}
	}

}

ポインタ、参照渡し

ポインタ、アドレス

以下のように変数名の前に&を書くと、変数の値ではなくアドレスを取得することができます。
アドレスとは、変数の値が実際に保存されている場所を表す、住所のようなものです。

int32 age = 20;
Print << &age;

これを実行すると、以下のような16進数の値が出力されます。(値は環境によって変わります)
これがアドレスです。

000000CCE57FEE14

そして、アドレスを値として持つ型をポインタ型といいます。
ポインタ型の変数は以下のようにして定義(作成)できます。

型名* 変数名;

ポインタ型の変数は、変数名の前に*をつけることでそのアドレスに格納されている値を取り出すことができます。

*変数名

以下のように、アドレスに格納されている値を書き換えることもできます。

*変数名 = 新しい値;

以下のコードを実行してみてください。

# include <Siv3D.hpp> // Siv3D v0.6.14

void Main()
{
	int32 age = 20;
	int32* ageAddress = &age;

	Console << U"age = " << age;
	Console << U"&age = " << &age;
	Console << U"ageAddress = " << ageAddress;
	Console << U"*ageAddress = " << *ageAddress;
	Console << U"";

	*ageAddress = 80;


	Console << U"age = " << age;
	Console << U"*ageAddress = " << *ageAddress;
	
	while (System::Update())
	{
		
	}
}

以下でageAdderssというポインタ変数を作って、そこにage変数の値が格納されているアドレスを代入しています。

int32* ageAddress = &age;

以下で、ageAddress変数に入っているアドレスを見に行って、実際にそのアドレスに格納されている値を取り出しています。
age変数の値20と同じ値が出力されています。

Console << U"*ageAddress = " << *ageAddress;

以下で、ageAddress変数に入っているアドレスを見に行って、そこに格納されている値を80に書き換えています。

*ageAddress = 80;

以下の部分の出力で
age変数の値が80に書き換わっているのがわかります。

Console << U"age = " << age;
Console << U"*ageAddress = " << *ageAddress;

参照渡し

ポインタとは別に、参照型というものがあります。
参照型の変数は以下のように定義できます。

型名& 変数名;

参照型は、代入を行うときに値をコピーするのではなく、変数のアドレスをコピーして自分自身もそのアドレスを参照するようにします。

以下のコードを実行してみてください。

# include <Siv3D.hpp> // Siv3D v0.6.14

void Main()
{
	int32 age = 20;
	int32& ageRef = age;

	Console << U"age = " << age;
	Console << U"ageRef = " << ageRef;
	Console << U"&age = " << &age;
	Console << U"&ageRef = " << &ageRef;
	Console << U"";

	ageRef = 80;

	Console << U"age = " << age;
	Console << U"ageRef = " << ageRef;
	
	while (System::Update())
	{
		
	}
}

以下の箇所で、age変数のアドレスをageRef変数のアドレスにコピーしています。
そのため、age変数とageRef変数はおなじアドレスを参照している、言い換えると、同じ箇所を編集、見ていることになります。

int32& ageRef = age;

以下の部分の出力で、ageageRefが同じアドレスを参照していることを確認してください。

Console << U"&age = " << &age;
Console << U"&ageRef = " << &ageRef;

以下の部分で、ageRef変数に80を新しく代入していますが、ageRefage変数は同じ箇所を編集しているので、
この操作によってage変数の値も80に変わることになります。

ageRef = 80;

以下の部分の出力で、age変数とageRef変数の値がどちらも80になっていることを確認してください。

Console << U"age = " << age;
Console << U"ageRef = " << ageRef;

関数

関数を使うことで、よく使うコードをまとめて、ショートカットのようにして呼び出せるようにすることができます。

簡単な関数

関数は、以下のようにして定義(作成)することができます。

void 関数名() {
    処理内容(まとめたいコード)
}

関数にまとめたコードは、以下のようにして呼び出すことができます。
関数を呼び出すと、上で定義した関数の処理内容が1行ずつ実行されて行きます。

関数名();

以下のコードを実行してみてください。
では、画面にHello World!と出力するコードをまとめて、printHelloWorldという関数を作成し、それを3回呼び出しています。

# include <Siv3D.hpp> // Siv3D v0.6.14

void printHelloWorld() {
	Print << U"Hello, World!";
}

void Main()
{
	printHelloWorld();
	printHelloWorld();
	printHelloWorld();
	
	while (System::Update())
	{
		
	}
}

以下で、画面にHello, World!と表示する処理を、printHelloWorldという関数名で定義しています。
これで、以降はprintHelloWorld()と書いてこの関数を呼び出すだけで、画面にHello, World!と表示できるようになります。

void printHelloWorld() {
	Print << U"Hello, World!";
}

以下の部分で、printHelloWorld関数を3回呼び出しています。
つまり、printHelloWorld関数を定義したときに書いた処理内容
Print << U"Hello, World!";
が3回実行されることになります。

printHelloWorld();
printHelloWorld();
printHelloWorld();

引数

関数を呼び出すときに、値を渡して、その値によって処理を変えたいとします。
例えば、名前を文字列として受け取って、Hello, 名前!と表示する機能を関数にまとめたい場合を考えます。
そういうときは、関数の引数という機能を使うことができます。

引数を使う場合、以下のようにして関数を定義できます。

void 関数名(受け取りたい値の型 受け取った値を格納する変数名) {
    処理内容
}

以下のコードを実行してみてください。

# include <Siv3D.hpp> // Siv3D v0.6.14

void printHelloWorld(String name) {
	Print << U"Hello, " << name << U"!";
}

void Main()
{
	printHelloWorld(U"ぱんどど");
	printHelloWorld(U"PandD");
	
	while (System::Update())
	{
		
	}
}

以下の箇所で、ぱんどどという文字列を引数として渡して、printHelloWorld関数を呼び出しています。
ここで渡す、ぱんどどという値を実引数といいます。

printHelloWorld(U"ぱんどど");

渡された引数は、関数の定義時に書いた変数nameに格納されます。
ここで格納するnameという変数を仮引数といいます。
この変数は関数の内部でしか使用できません。
(スコープが関数内だけのため)

void printHelloWorld(String name) {
	Print << U"Hello, " << name << U"!";
}

実引数が仮引数に渡されるとき、値はコピーされるため、関数内で仮引数を書き換えても元の値は変わりません。
値を変えたい場合は、参照渡しを使いましょう。

void changeName(String& name) {
    name = U"新しい名前";
}

複数の引数

引数は複数受け取ることも出来ます。

以下のコードを実行してみてください。

void printAdd(int32 a, int32 b) {
    int32 answer = a + b;
    Print << a << U" + " << b << " = " << answer;
}

void Main()
{
    printAdd(3, 5); // 3 + 5 を表示する
    printAdd(119, 121); // 10 + 12 を表示する
	
	while (System::Update())
	{
		
	}
}

引数を,区切りで書くことで、複数の引数を受け取ることが出来ます。

戻り値

関数は、呼び出した先で処理した結果の値を、呼び出し元に返すことが出来ます。

以下のコードを実行してみてください。

# include <Siv3D.hpp> // Siv3D v0.6.14

int32 add(int32 a, int32 b) {
	int answer = a + b;
	return answer;
}

void Main()
{
	int32 a = 5;
	int32 b = 6;
    int32 answer = add(a, b);
	Print << a << U" + " << b << U" = " << answer;
	
	while (System::Update())
	{
		
	}
}

以下の箇所では、2つの数値を引数として受け取り、足し算した結果を戻り値として返しています。

int32 add(int32 a, int32 b) {
	int answer = a + b;
	return answer;
}

以下の箇所で、引数として変数abの値、つまり56を渡して、
関数内で処理された結果、つまり足し算した結果を戻り値として受け取って、answer変数に代入しています。

int32 answer = add(a, b);

戻り値を利用する場合は、以下のように関数名の左に戻り値として返したい値の型名を書きます。

戻り値の型名 関数名(引数の型名 引数名, ...) {
    処理内容
    return 戻り値として返したい値;
}

戻り値を返す必要がない場合は、戻り値の型名に、戻り値がないことを示すvoid型を書きます。
戻り値がない場合はreturn文は省略でき、省略した場合は関数の終点に到達した時点で関数の処理が終了します。

void 関数名(引数の型名 引数名, ...) {
    処理内容
    return;
}

以下のように式の中で関数を呼び出して、その戻り値を値として使うことも出来ます。

# include <Siv3D.hpp> // Siv3D v0.6.14

int32 add(int32 a, int32 b) {
	int answer = a + b;
	return answer;
}

void Main()
{
	int32 a = 5;
	int32 b = 6;
-   int32 answer = add(a, b);
-	Print << a << U" + " << b << U" = " << answer;
+	Print << a << U" + " << b << U" = " << add(a, b);
	
	while (System::Update())
	{
		
	}
}

以下のように関数の中で関数を呼び出し、その戻り値を引数として渡すといった事もできます。
このコードを実行した結果は
(2+3)+(1+4) = 5 + 5すなわち10になります

add(add(2, 3), add(1, 4))

関数まとめ

最終的にに、関数を定義(作成)する書き方は以下のようになります。

戻り値の型名 関数名(引数の型名1 引数名1, 引数の型名2 引数名2, ...) {
    処理したい内容(まとめたいコード)
    return 処理結果として返したい値(戻り値の型と同じ型でないといけない)
}

戻り値や引数などの機能を組み合わせることで、複雑な計算処理などを関数にまとめて、呼び出し元を簡潔に保ち、コードの可読性を上げることが出来ます。

関数の使用例

キャラクターの残りHPによって異なる色名を返す関数。

String getHPColor(int32 hp) {
    if (hp >= 6) {
        return U"緑";
    } else if (hp >= 3) {
        return U"青";
    } else if (hp >= 2) {
        return U"黄色";
    } else {
        return U"赤";
    }
}

練習問題3

問題1-1

練習問題1で作ったプレイヤーを十字キーで操作するプログラムの、キー操作の部分を関数に切り出してみよう。
以下のコードを埋めてください。

# include <Siv3D.hpp> // Siv3D v0.6.14

void playerControl(/*引数を考えてみよう*/)
{
	if (KeyUp.pressed()) y -= speed;
	if (KeyDown.pressed()) y += speed;
	if (KeyLeft.pressed()) x -= speed;
	if (KeyRight.pressed()) x += speed;
}

void Main()
{
	int32 x = 400;
	int32 y = 300;
	int32 speed = 5;

	while (System::Update()) {
		playerControl(/*引数を考えてみよう*/);

		Circle{ x, y, 50 }.draw();
	}

}
ヒント

関数内で値を書き換えるには参照渡しにしないといけない。

解答例
# include <Siv3D.hpp> // Siv3D v0.6.14

void playerControl(int32& x, int32& y, int32 speed)
{
	if (KeyUp.pressed()) y -= speed;
	if (KeyDown.pressed()) y += speed;
	if (KeyLeft.pressed()) x -= speed;
	if (KeyRight.pressed()) x += speed;
}

void Main()
{
	int32 x = 400;
	int32 y = 300;
	int32 speed = 3;

	while (System::Update()) {
		playerControl(x, y, speed);

		Circle{ x, y, 50 }.draw();
	}

}

問題1-2

speed変数を廃止して、speedは関数から取得するようにしてみよう。
Shiftキーが押されているときは10、押されていないときは3を戻り値として返して、ダッシュ処理を実装しよう。

ヒント テンプレート(問題1-1が終わるまで見ないでね!)
# include <Siv3D.hpp> // Siv3D v0.6.14

void playerControl(int32& x, int32& y, int32 speed)
{
	if (KeyUp.pressed()) y -= speed;
	if (KeyDown.pressed()) y += speed;
	if (KeyLeft.pressed()) x -= speed;
	if (KeyRight.pressed()) x += speed;
}

int32 playerSpeed() {
    // Shiftキーが押されているときは10、押されていないときは3を戻り値として返す
    /* ここを埋める */
}

void Main()
{
	int32 x = 400;
	int32 y = 300;

	while (System::Update()) {
		playerControl(x, y, /* ここを埋める */);

		Circle{ x, y, 50 }.draw();
	}

}
解答例
# include <Siv3D.hpp> // Siv3D v0.6.14

void playerControl(int32& x, int32& y, int32 speed)
{
	if (KeyUp.pressed()) y -= speed;
	if (KeyDown.pressed()) y += speed;
	if (KeyLeft.pressed()) x -= speed;
	if (KeyRight.pressed()) x += speed;
}

int32 playerSpeed() {
	if (KeyShift.pressed()) return 10;
	return 3;
}

void Main()
{
	int32 x = 400;
	int32 y = 300;

	while (System::Update()) {
		playerControl(x, y, playerSpeed());

		Circle{ x, y, 50 }.draw();
	}

}

問題1-3

プレイヤーが画面からはみ出ないようにする処理を関数にしてまとめてみよう。

ヒント テンプレート(問題1-2が終わるまで見ないでね!)
# include <Siv3D.hpp> // Siv3D v0.6.14

void playerControl(int32& x, int32& y, int32 speed)
{
	if (KeyUp.pressed()) y -= speed;
	if (KeyDown.pressed()) y += speed;
	if (KeyLeft.pressed()) x -= speed;
	if (KeyRight.pressed()) x += speed;
}

int32 playerSpeed() {
	if (KeyShift.pressed()) return 10;
	return 3;
}

void playerMovementRestriction(/* 引数を考える */)
{
    // 画面をはみ出たら押し戻す
    // ここを書く
}

void Main()
{
	int32 x = 400;
	int32 y = 300;

	while (System::Update()) {
		playerControl(x, y, playerSpeed());
		playerMovementRestriction(/* 引数を考える */);

		Circle{ x, y, 50 }.draw();
	}

}
解答例
# include <Siv3D.hpp> // Siv3D v0.6.14

void playerControl(int32& x, int32& y, int32 speed)
{
	if (KeyUp.pressed()) y -= speed;
	if (KeyDown.pressed()) y += speed;
	if (KeyLeft.pressed()) x -= speed;
	if (KeyRight.pressed()) x += speed;
}

int32 playerSpeed() {
	if (KeyShift.pressed()) return 10;
	return 3;
}

void playerMoveRestriction(int32& x, int32& y)
{
	if (x < 0) x = 0;
	if (x > 800) x = 800;
	if (y < 0) y = 0;
	if (y > 600) y = 600;
}

void Main()
{
	int32 x = 400;
	int32 y = 300;

	while (System::Update()) {
		playerControl(x, y, playerSpeed());
		playerMoveRestriction(x, y);

		Circle{ x, y, 50 }.draw();
	}

}

おまけ

練習問題2 問題2を関数を使って書いた例

# include <Siv3D.hpp> // Siv3D v0.6.14

// プレイヤーの操作
void playerControl(int32& x, int32& y, int32 speed)
{
	if (KeyUp.pressed()) y -= speed;
	if (KeyDown.pressed()) y += speed;
	if (KeyLeft.pressed()) x -= speed;
	if (KeyRight.pressed()) x += speed;
}

// Shiftキーの押下状態によってスピードを変える
int32 getSpeed() {
	if (KeyShift.pressed()) return 10;
	else return 3;
}

// プレイヤーの移動範囲制限
void playerMovementRestriction(int32& x, int32& y)
{
	if (x < 0) x = 0;
	if (x > 800) x = 800;
	if (y < 0) y = 0;
	if (y > 600) y = 600;
}

// 円の描画
// isSelectedがtrueのときは黄色で描画
void draw(int32 x, int32 y, bool isSelected)
{
	if (isSelected) {
		Circle{ x, y, 50 }.draw(Palette::Yellow);
	}
	else {
		Circle{ x, y, 50 }.draw();
	}
}

void Main()
{
	Array<int32> posXs; // x座標を保持する配列
	Array<int32> posYs; // y座標を保持する配列

	int32 selectedIndex = 0; // 現在操作しているキャラ(配列のインデックス)

	for (int i = 0; i < 3; i++) { // 3回ループする
		// 一旦全部真ん中に配置する
		posXs << 400;
		posYs << 300;
	}

	while (System::Update()) {
		// 選択中のキャラの移動
		playerControl(posXs[selectedIndex], posYs[selectedIndex], getSpeed());
		// 移動範囲制限
		playerMovementRestriction(posXs[selectedIndex], posYs[selectedIndex]);

		if (KeySpace.down()) { // Spaceキーを押した瞬間
			selectedIndex++;
			if (selectedIndex >= posXs.size()) selectedIndex = 0;
		}

		// すべてのキャラの描画(円の描画)
		for (int32 i = 0; i < posXs.size(); i++) {
			draw(posXs[i], posYs[i], i == selectedIndex); // 選択中のキャラは黄色で描画(isSelectedがtrueになる)
		}
	}

}
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?