【ゲーム開発のためのC#入門講座・応用学習編】構造体でデータをひとまとめにしよう【#1】

2022-02-204.0_C#応用学習編

ようこそ、応用学習編へ!

この応用学習編では、現代プログラミングの真髄であるオブジェクト指向について学習します(ただし初回のみ別。詳細は後述)。

もともとオブジェクト指向という考え方や、オブジェクト指向から生まれた技術というものはプログラミングの世界には存在しませんでした。基礎編で学んできたことがベースとしてあり、オブジェクト指向は新しく加えられたもの、いわば追加機能なのです。

そのため、基礎編をまだ学んでないよ、という方がいらっしゃれば、よかったらまずは基礎編で土台をしっかり築いておくと理解がスムーズになるかと思います。

オブジェクト指向を学習する心構えについて

C#を含め、現在世に多く出回っているメジャーなプログラミング言語の多くはオブジェクト指向言語と呼ばれています。ようはオブジェクト指向の考え方と技術をベースにしているんですね。

そのせいで、本来は後から追加された応用技術であるはずのオブジェクト指向が、あたかも基礎のように取り扱われているという現状がプログラミング学習の難易度を跳ね上げていると個人的には感じています。

実際、プログラミングの入門書には必ずオブジェクト指向の解説があります。もちろんオブジェクト指向言語の入門書なのにオブジェクト指向を解説しない訳にはいかない、という事情も理解できるのですが、そのせいで「入門書に書かれている内容も理解できないなんて、自分はプログラミング向いてないのかな」なんて思ってしまう人もいるんじゃないかなと思います。

なので、自分はこのオブジェクト指向を応用編に置く構成としました。

実際に自分がプログラマーとして働いているから言えることでもありますが、プロでもこのオブジェクト指向を理解し切れていない人は結構います。それだけ難しいということでもあるし、それでもプログラマーとして働くことはできるということでもあります。

オブジェクト指向は100%理解できなくても使うことができます。使うことさえできれば、もちろんゲーム開発もできます。そして使っていくうちに少しずつ、理解の範囲が広がっていきます。

なので、まずは100%理解できなくても進んでみようという心構えで挑んでもらえれば嬉しいです。

プログラムを作成できる環境を用意しよう

環境構築編で導入した「Visual Studio」または「Visual Studio Code」を使って、新しいプロジェクトを作成するか、上書きしても問題ないプロジェクトを起動してください。

もし環境構築がまだ終わっていない、という方は環境構築編をご確認下さい。

また、今後は「Visual Studio」または「Visual Studio Code」でプロジェクトを開きプログラミングができる状態であることを前提とし、このアナウンスは行いません。ご了承下さいませ。

オブジェクト指向を学ぶ前に構造体を学ぼう

それでは早速オブジェクト指向を学びましょう、と言いたいところなのですが、オブジェクト指向を理解するために、初回はあえてオブジェクト指向が登場する前から存在したものを学習します。

それが構造体です。

構造体は配列と同様、複数データをひとつにまとめることができます。

ただし配列と違うのは、異なる型のデータでもひとつにまとめられるということ。

早速「Program.cs」上にコードを記述して確認してみましょう。

BattleCharacter hero = new BattleCharacter();
hero.Name = "Link";
hero.HP = 25;
hero.ATK = 10;
hero.DEF = 5;
hero.CRI = 5.2f;
​
Console.WriteLine("キャラクター名:" + hero.Name);
Console.WriteLine("HP:" + hero.HP);
Console.WriteLine("ATK:" + hero.ATK);
Console.WriteLine("DEF:" + hero.DEF);
Console.WriteLine("CRI率:" + hero.CRI);
​
public struct BattleCharacter
{
    public string Name = "";
    public int HP = 0;
    public int ATK = 0;
    public int DEF = 0;
    public float CRI = 0;
}
コンソール画面

キャラクター名:Link
HP:25
ATK:10
DEF:5
CRI率:5.2

確かに「hero」というひとつの変数に、string型・int型・float型と、異なる型のデータがまとまっていますね。しかも配列のようなインデックスではなく、「Name」「HP」「ATK」「DEF」「CRI」といった名前でアクセスできるようになっています。

このように、構造体は複数の変数をひとつにまとめた新しい型を定義できる機能です。

構文は下記の通り。

public struct 型名
{
    データ型 データ名 = 初期値;
    ……増やしたいデータの数だけ繰り返し……
}

使用する時は「new」キーワードを使います。

構造体の型名 変数名 = new 構造体の型名();

ただし、構造体は代入の際に自分自身をコピーするという特殊な仕様になっています。そのため、「new」というキーワードを使っているものの、構造体は参照型ではなく値型に該当します。

BattleCharacter hero = new BattleCharacter();
hero.Name = "Link";
​
// ↓値型なので、アドレスではなく実データをコピーしている
BattleCharacter hero2 = hero;
​
// ↓同じアドレスを参照していないので、別々の名前が表示される
hero2.Name = "Zelda";
Console.WriteLine("キャラクター名:" + hero.Name);
Console.WriteLine("キャラクター名:" + hero2.Name);
​
public struct BattleCharacter
{
    public string Name = "";
    public int HP = 0;
    public int ATK = 0;
    public int DEF = 0;
    public float CRI = 0;
}
コンソール画面

キャラクター名:Link
キャラクター名:Zelda

これまたややこしいのですが、頭の隅にでも覚えておいてもらえればありがたいです。

なお、配列のように変数作成と同時に値を設定することも可能です。

BattleCharacter hero = new BattleCharacter()
{
    Name = "Link",
    HP = 25,
    ATK = 10,
    DEF = 5,
    CRI = 5.2f
};

今までは体力や攻撃力毎に配列を作って、インデックスが同じものを同一キャラのステータスと見なしていましたが、もうそんなことをする必要はありません。

// ↓こんな風に頑張る必要はもうない!
int[] playersHP = {
    25, // 1人目のHP
    30, // 2人目のHP
    21  // 3人目のHP
};
int[] playersAttack = {
    10, // 1人目のAttack
    12, // 2人目のAttack
    6   // 3人目のAttack
};

構造体を使えば個々のキャラクターのデータをひとまとめにすることができる訳です。

// 個々のキャラクター単位でデータを定義できる!
Player player1 = new Player()
{
    HP = 25,
    ATK = 10
};
​
Player player2 = new Player()
{
    HP = 30,
    ATK = 12
};
​
Player player3 = new Player()
{
    HP = 21,
    ATK = 6
};
​
public struct Player
{
    public int HP = 0;
    public int ATK = 0;
}

めちゃくちゃ便利ですね!

Unityでの活躍ポイント

Unityでも構造体は頻繁に扱われています。

最もよく見かける構造体は、座標や移動量などを表す「Vector3」でしょう。

この「Vector3」は「X」「Y」「Z」という、縦軸・横軸・奥行き(3D用)を表す変数をひとまとめにした構造体です。

Vector3 position = new Vector3();
position.X = 5;
position.Y = 5;
position.Z = 5;
GameObject obj = Instantiate(prefab, position, Quaternion.identity);

分かりやすくするために少し加工していますが、上記は指定の座標に新しいオブジェクトを表示する実装例です。

関連性のある複数データをひとまとめにするのに、構造体は非常に便利です。今後学んでいくオブジェクト指向の登場により利用頻度は下がったものの、利用機会はきちんとあるので、ここでマスターしておきましょう。

実践演習

それでは実際に構造体を使ってみましょう。

演習①

仕様

下記の構造体を定義してください。
構造体名:Hero
データ :string型 Name
int型 HP = 0(体力)
int型 MP = 0(マジックポイント)
int型 ATK = 0(攻撃力)
int型 DEF = 0(防御力)
int型 AGI = 0(敏捷)
float型 CRI = 0(クリティカル率)

テンプレート
Hero[] party =
{
    new Hero()
    {
        Name = "Link",
        HP = 50,
        // MPは初期値0のままでOKなので、定義しない
        ATK = 10,
        DEF = 5,
        AGI = 7,
        CRI = 5.3f
    },
    new Hero()
    {
        Name = "Zelda",
        HP = 30,
        MP = 25,
        ATK = 5,
        DEF = 3,
        AGI = 5
        // CRIは初期値0fのままでOKなので、定義しない
    }
};
​
for (int i = 0; i < party.Length; i++)
{
    Console.WriteLine(party[i].Name);
    Console.WriteLine(party[i].HP);
    Console.WriteLine(party[i].MP);
    Console.WriteLine(party[i].ATK);
    Console.WriteLine(party[i].DEF);
    Console.WriteLine(party[i].AGI);
    Console.WriteLine(party[i].CRI);
}
​
// ↓ここに構造体を定義しよう!
​
// ここまで

演習②

下記の実行結果を予想してください。

※構造体は値型である点に注意です!

Vector3 position = new Vector3()
{
    x = 5,
    y = 5,
    z = 5
};
​
Vector3 moveVector = position;
moveVector.x += 5;
moveVector.y += 10;
moveVector.z += 15;
​
Console.WriteLine(position.x + moveVector.x);
Console.WriteLine(position.y + moveVector.y);
Console.WriteLine(position.z + moveVector.z);
​
public struct Vector3
{
    public int x = 0;
    public int y = 0;
    public int z = 0;
}

答え合わせ

演習①の答え

Hero[] party =
{
    new Hero()
    {
        Name = "Link",
        HP = 50,
        // MPは初期値0のままでOK
        ATK = 10,
        DEF = 5,
        AGI = 7,
        CRI = 5.3f
    },
    new Hero()
    {
        Name = "Zelda",
        HP = 30,
        MP = 25,
        ATK = 5,
        DEF = 3,
        AGI = 5
        // CRIは初期値0fのままでOK
    }
};
​
for (int i = 0; i < party.Length; i++)
{
    Console.WriteLine(party[i].Name);
    Console.WriteLine(party[i].HP);
    Console.WriteLine(party[i].MP);
    Console.WriteLine(party[i].ATK);
    Console.WriteLine(party[i].DEF);
    Console.WriteLine(party[i].AGI);
    Console.WriteLine(party[i].CRI);
}
​
public struct Hero
{
    public string Name = "";
    public int HP = 0;
    public int MP = 0;
    public int ATK = 0;
    public int DEF = 0;
    public int AGI = 0;
    public float CRI = 0f;
}

演習②の答え

コンソール画面

15
20
25

まとめ

  • オブジェクト指向は応用技術だから、難しくて当たり前という気持ちでOK
  • 使っていくうちに理解の範囲が広がっていくので、まずは100%理解できなくても進んでみよう
  • 構造体は、複数の変数をひとつにまとめた新しい型を定義できる機能
  • とても便利だけれど、参照型じゃなくて値型という特殊な仕様だけ注意

次回からはこれまで学んできた基礎編の知識とこの構造体によるプログラミングが、オブジェクト指向の登場によりどう変化したのかを学んでいきます。お楽しみに!

それでは、今回もお疲れ様でした!

また次の記事でお会いしましょう!

Posted by yuumekou