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.

プログラマ勉強会 第4回

Last updated at Posted at 2024-07-14

構造体

前回では、キャラクターの座標を保持するためにx, yというように変数を2つ作っていました。

また、複数のキャラクターの座標を保持する場合はposXs, posYsの2つの配列を作って保持していました。
しかし、キャラクターの座標の情報が2つの変数に分かれて保持されているのってなんか気持ち悪くないですか?
同じキャラクターの情報は同じ変数にまとめて管理できたら便利ですよね。

そこで使うのが構造体です。
構造体を使うことで、複数の型の変数をまとめて管理できる、新しい型を作成することが出来ます。

以下は、座標の情報を構造体にまとめたコードの例です。

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

struct Position {
	int32 x;
	int32 y;
};

void Main()
{
	Position playerPos;
	playerPos.x = 400;
	playerPos.y = 300;

	while (System::Update()) {
		if (KeyUp.pressed()) playerPos.y -= 5;
		if (KeyDown.pressed()) playerPos.y += 5;
		if (KeyLeft.pressed()) playerPos.x -= 5;
		if (KeyRight.pressed()) playerPos.x += 5;

		Circle{ playerPos.x, playerPos.y, 20 }.draw();
	}

}

以下の部分でPositionという名前で構造体を宣言しています。Positionという新しい型を作成しているとも言えます。
Positionという構造体は、int32型のxという名前の変数と、int32型のyという変数を持っています。
この、構造体がもつ変数のことをメンバ変数といいます。(メンバ変数はフィールドと呼ぶこともあります)
構造体を使うことで、複数の変数をグループのようにしてまとめることが出来ます。

struct Position {
	int32 x;
	int32 y;
};

構造体の宣言

struct 構造体の名前 {
    メンバ変数の型名1 メンバ変数名1;
    メンバ変数の型名2 メンバ変数名2;
    ...
}

以下の箇所で、上で宣言したPosition型の変数を定義(作成)しています。
この、Position型で作成されたplayerPosという変数に入っているデータのことを、インスタンスといいます。
インスタンスはオブジェクトと呼ばれることもあります。
また、このようにインスタンスを新しく作成することをインスタンス化といいます。

Position playerPos;

このplayerPosという変数は、メンバ変数(フィールド)としてxyという変数を中に持っています。
なので、以下のように構造体型の変数名.メンバ変数名というように書くことで中に持っている変数にアクセスできます。

playerPos.x = 400;
playerPos.y = 300;
if (KeyUp.pressed()) playerPos.y -= 5;
if (KeyDown.pressed()) playerPos.y += 5;
if (KeyLeft.pressed()) playerPos.x -= 5;
if (KeyRight.pressed()) playerPos.x += 5;

Circle{ playerPos.x, playerPos.y, 20 }.draw();

また、構造体はインスタンス化と同時にメンバ変数の初期値を指定することも出来ます。
変数名の右に{}で囲って,区切りでメンバ変数の初期値を指定します。

+Position playerPos{ 400, 300 };
-Position playerPos;
-playerPos.x = 400;
-playerPos.y = 300;

以下のように初期値を指定するメンバ変数名を指定することもできます。

Position playerPos{ .y = 300 };
playerPos.x = 400;

以下のような書き方もあります。

Position playerPos = Position{ 400, 300 };

インスタンスの初期値の指定

構造体名 変数名{ メンバ変数1の値, メンバ変数2の値, ... };
構造体名 変数名{ .メンバ変数名 = 初期値, .メンバ変数名 = 初期値, ... };
構造体名 変数名 = 構造体名{ 初期値, ... };

また、構造体を宣言する際にデフォルトの値を設定することも出来ます。
メンバ変数名の右に=記号で初期値を指定します。

struct Position {
	int32 x = 400;
	int32 y = 300;
};

デフォルトの値が設定されていても、インスタンス化の際に初期値が指定されている場合は初期値が優先されます。

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

struct Position {
	int32 x = 400;
	int32 y = 300;
};

void Main()
{
	Position pos1;
	Print << pos1.x; // 400
	Print << pos1.y; // 300

	Position pos2{ 100, 200 };
	Print << pos2.x; // 100
	Print << pos2.y; // 200

	while (System::Update()) {

	}

}

構造体の配列

以下のように配列の型を構造体型にすることで、構造体の配列を作ることが出来ます。
構造体のインスタンスをコード上で表現するには、
構造体名{ メンバ変数の値1, メンバ変数の値2, ... }
というように書きます。

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

struct Position {
	int32 x = 400;
	int32 y = 300;
};

void Main()
{
	Array<Position> positions;
	positions << Position{}; // positions[0]
	positions << Position{ 200, 100 }; // positions[1]

	Print << positions[0].x; // 400
	Print << positions[0].y; // 300

	Print << positions[1].x; // 200
	Print << positions[1].y; // 100

	while (System::Update()) {

	}

}

構造体のまとめ

構造体の宣言

デフォルトの値はなくても良い。

struct 構造体名 {
    メンバ変数の型名1 メンバ変数名1;
    メンバ変数の型名2 メンバ変数名2 = デフォルトの値;
    ...
}

struct Position {
    int32 x = 400;
    int32 y = 300;
}
struct Person {
    String name;
    int32 age;
}

構造体のインスタンス化(構造体の変数の作成)

どの書き方でも良い

構造体名 変数名{ メンバ変数1の初期値, メンバ変数2の初期値, ... };
構造体名 変数名{ メンバ変数名 = 初期値, メンバ変数名 = 初期値, ... };
構造体名 変数名 = 構造体名{ メンバ変数1の初期値, メンバ変数2の初期値, ... }
構造体名 変数名 = 構造体名{ メンバ変数名 = 初期値, メンバ変数名 = 初期値, ... }

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

struct Position {
	int32 x = 400;
	int32 y = 300;
};

void Main()
{
	Position pos1;
	Print << pos1.x; // 400
	Print << pos1.y; // 300

	Position pos2{ 100, 200 };
	Print << pos2.x; // 100
	Print << pos2.y; // 200

	Position pos3{ .y = 0 };
	Print << pos3.x; // 400
	Print << pos3.y; // 0

	Position pos4 = Position{ 100, 200 };
	Print << pos4.x; // 100
	Print << pos4.y; // 200

	Position pos5 = Position{ .y = 0 };
	Print << pos5.x; // 400
	Print << pos5.y; // 0

	Position pos6 = { 100, 200 };
	pos6 = Position{ 0, 0 };
	Print << pos6.x; // 0
	Print << pos6.y; // 0

	while (System::Update()) {

	}

}

構造体の配列

Array<構造体名> 変数名; // 配列の作成
変数名 << 構造体名{ 初期値, ... }; // 配列に値を追加

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

struct Position {
	int32 x = 400;
	int32 y = 300;
};

void Main()
{
	Array<Position> positions;

	positions << Position{}; // positions[0]
	positions << Position{ 100, 200 }; // positions[1]
	Position pos{ 0, 0 };
	positions << pos; // positions[2]


	Print << positions[0].x; // 400
	Print << positions[0].y; // 300

	Print << positions[1].x; // 100
	Print << positions[1].y; // 200

	Print << positions[2].x; // 0
	Print << positions[2].y; // 0

	while (System::Update()) {

	}

}

練習問題1

第3回の練習問題で作った、3つのキャラをSpaceキーで切り替えて操作できるプログラムを、構造体を使った書き方に書き換えてみよう。
X座標、Y座標をそれぞれ別の配列で保持するのではなく、XY座標を持つ構造体の配列として保持するようにします。

# 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

struct Position {
	int32 x;
	int32 y;
};

void Main()
{
	Array<Position> positions; // X,Y座標を持つ構造体の配列
	int32 speed = 5; // 速度は5で固定

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

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

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

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

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

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

}

おまけ問題

プレイヤーの操作、スピードの変化、移動範囲制限、描画処理を関数にまとめた以下のコードで、XY座標を構造体に置き換えてみよう。

テンプレート
# 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になる)
		}
	}

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

struct Position {
	int32 x;
	int32 y;
};

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

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

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

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

void Main()
{
	Array<Position> positions; // 3つのキャラの座標を格納する配列

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

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

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

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

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

}

おまけ シューティングゲームの作り方

弾を一つまでしか撃てない

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

struct Position {
	int32 x;
	int32 y;
};

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

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

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

void Main()
{
	Position playerPos{ 400, 300 };
	Position bulletPos{ -100, -100 }; // 弾の初期座標(最初は画面外にしておく)

	while (System::Update())
	{
		playerControl(playerPos, getSpeed()); // プレイヤーの操作
		playerMovementRestriction(playerPos); // プレイヤーの移動範囲制限

		Circle{ playerPos.x, playerPos.y, 30 }.draw(); // プレイヤーの描画

		if (KeySpace.down()) {
			// 弾の座標をプレイヤーの座標に設定
			bulletPos.x = playerPos.x;
			bulletPos.y = playerPos.y;
		}

		bulletPos.y -= 10; // 弾を上に移動させ続ける
		Circle{ bulletPos.x, bulletPos.y, 10 }.draw(Palette::Yellow); // 弾の描画
	}
}

複数の弾を撃てるようにするには
配列を使う

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

struct Position {
	int32 x;
	int32 y;
};

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

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

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

void Main()
{
	Position playerPos{ 400, 300 };
	Array<Position> bullets; // 弾の座標を格納する配列

	while (System::Update())
	{
		playerControl(playerPos, getSpeed()); // プレイヤーの操作
		playerMovementRestriction(playerPos); // プレイヤーの移動範囲制限

		Circle{ playerPos.x, playerPos.y, 30 }.draw(); // プレイヤーの描画

		if (KeySpace.down()) {
			// Spaceキーが押されたら弾を生成
			bullets << playerPos; // 弾の座標を配列に追加(playerPosをコピー)
		}

		// 範囲for文を使ってすべての弾の移動と描画を行う
		for (Position& bullet : bullets) {
			bullet.y -= 10; // 毎フレーム弾を上に移動
			Circle{ bullet.x, bullet.y, 10 }.draw(Palette::Yellow); // 弾の描画
		}
	}
}
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?