解説

C#の基礎【Unityの教科書#2】

この記事ではゲームを制作する上で必要不可欠なC#について、基本的なコードの書き方を解説していきます!

変数と型

プログラミングをする上ではデータを操作する必要があります。そんなデータの基本的な扱い方を解説していきます。

変数とは

変数とは値を格納するものです。正確ではありませんが、数学の変数xのようなものです。数学では「x=3」や「x=5」のように書いてxに数字を代入する場面がありますよね?プログラミングでも同様に変数に値を代入していきます。この=は数学の等号という意味ではなく代入であることに注意してください。

プログラミングの変数は数字だけでなく文字など様々なデータを入れることができます。詳しくは後ほど。

型とは

型とは変数がどんな種類のデータを持つのかを定義するものです。いきなりaという変数を与えられても、コンピュータはその変数の値が整数なのか小数なのか、はたまた文字なのかを識別することができません。そのため型を用いて定義する必要があるのです。

型の種類

型にはさまざまな種類があります。以下にC#で扱う組み込み型をいくつか紹介します。

  型  型名格納する値
intイント整数型整数
floatフロート浮動小数点型(実数型)実数
boolブールブール型true か false
charチャー文字型文字
stringストリング文字列型文字列

これとは別にVector2と呼ばれる変数もあります。使用方法が少し違うので後で説明します。

型の種類の説明の前に(Debug.Log())

型の種類について学ぶ前に、Debug.Logという処理について説明しましょう。
これは本来ゲームのデバッグに用いられるものなのですが、この教科書では単にUnity上で変数の値を表示するために使っていきます。

最初にC#スクリプトを作成します。Unityの基礎【Unityの教科書#1】で作成したプロジェクトを開いてください。
開いたらプロジェクトウィンドウで右クリックをし、Create>C# Scriptと選択してC#スクリプトを新規作成してください。このとき、本来は作成したい処理に応じてファイル名を変更しますが、今回はとりあえず”NewBehaviorScript”のままにしましょう。

次に、作成したスクリプトをゲームに適用させるための作業を行います。
BlockゲームオブジェクトにC#スクリプトをドラッグ&ドロップしてください。

AddComponentでも直接GameObjectにドラッグ&ドロップしても結果は同じになります。

Blockゲームオブジェクトのインスペクターが下の画像のようになれば成功です。

画像を見ると、コンポーネントとして新しいスクリプトが追加されたのがわかると思います。これも他の他のコンポーネントと同じようにアタッチと言います。
アタッチをしないとスクリプト、つまりプログラムは動きません。

スクリプトをダブルクリックすると、VS Codeで作成したスクリプトが開かれます。
デフォルトでvoid Start()void Update()という文が書かれています。

void Start(){} の{}内にDebug.Log(1);と書いてください。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class NewBehaviourScript : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        Debug.Log(1);
    }

    // Update is called once per frame
    void Update()
    {

    }
}

行の末尾に ; (セミコロン)を書くのを忘れないでください。
C#では文の終わりに ; を付ける必要があります。

そしてゲームを実行します。
デバッグログはコンソールウィンドウで見ることができます。コンソールウィンドウはここを押すと表示できます。

正常に動作すれば、下の画像の通りコンソールウィンドウに1が表示されているはずです。この箇所に表示されるものをこの教科書ではコンソールメッセージと呼びます。

コンソールメッセージは先程のようにプログラマーがDebug.Logで設定するものもあれば、エラーが起こったときにUnityが表示してくれるものもあります。

このようにDebug.Log();は、()内のデータをUnityに表示することができます。
Debug.Log();で計算結果を表示しながら演算子について学んでいきましょう。

デバッグログをクリックしたとき。

cs:10と書いてある事から10行目のDebug.Logが反応した事がわかります。

intの解説

intは整数を格納する型です。Startのところを下記のコードに書き換えてください。

    void Start()
    {
        int a;
        a = 5;
        Debug.Log(a);
        Debug.Log(-24);
    }
  • int a; は整数の値が格納される変数aを用意しています。これを「int型の変数aを宣言した」と表現します。
  • a = 5; は変数aに5という値を代入しています。
  • Debug.Log(a); はコンソールウィンドウにaの値を出力しています。

int型に限らず、全ての型は <変数型> <変数名> の順で宣言されます。

floatの解説

floatは実数を格納する型です。以下のコードを書いてください。

    void Start()
    {
        float b;
        b = 5.3f;
        Debug.Log(b);
        Debug.Log(1.0f);
        Debug.Log(-9f)
    }

基本的な処理はint型の時と同じです。ただし、b = 5.3f のようにfloat型の変数に小数を代入するときは末尾に「f」をつける必要があります。これがないとC#ではfloat型であると認識されません。

double

浮動小数点型(実数型)としてfloat型の他にもdouble型というものがあります。double型のほうがサイズが大きい一方、精度が高いという特徴があります。Unityでは実数は基本的にfloat型の値として保持されているため、多くの場合はfloat型を使うのが良いでしょう。

doubleは以下のように初期化できます。

double c = 5.3; // 5.3d としてもよい
double d = 2.0;

boolの解説

boolはtrueもしくはfalse(真偽値)を格納する型です。以下のコードを書いてください。

    void Start()
    {
        bool c;
        c = true;
        Debug.Log(c);
        Debug.Log(false);
    }

こちらも基本的な処理はint型の時と同じです。

このbool値は主に制御文と組み合わせて利用します

関連する定数の集合を表すEnum

bool型は真偽値を示したいとき、例えば「ジャンプ中かどうか」「ぶつかっている状態か」「昨日健康的な生活をしたか」などを示す用途に使えます。一方で、bool型はtrueかfalseという2つの値しか取らないため、3つ以上の値をそのまま示すことはできません。

複数の値を取るとき、特に関連する定数の集合を表すときによく使うのがEnumと呼ばれるものです。例えば、プレイヤーの状態として、「止まっている、歩いている、走っている、ジャンプしている」という4つの状態を表すには、以下のようにEnumを定義できます。細かい使い方は省略するので、使うときに調べればいいでしょう。

enum PlayerState
{
    Idle,
    Walking,
    Running,
    Jumping
}

このEnumのWalkingという状態であるということを変数に割り当てるには以下のように書けます。

PlayerState currentState = PlayerState.Idle;

このようにEnumを使うことで、関連する集合をまとめることができ、コードを読みやすくできます。

charの解説

charは文字を格納する型です。以下のコードを書いてください。

    void Start()
    {
        char d;
        d = 'あ';
        Debug.Log(d);
    }

こちらも基本的な処理はint型の時と同じです。ただし、d = 'あ' のように文字は ' (シングルクォーテーション)」でくくる必要があります。

stringの解説

stringは文字列を格納する型です。以下のコードを書いてください。

    void Start()
    {
        string e;
        e = "あいうえお";
        Debug.Log(e);
    }

こちらも基本的な処理はint型の時と同じです。ただし、e = "あいうえお" のように文字列は " (ダブルクォーテーション)」でくくる必要があります。

変数に関する補足

変数の初期化

変数の宣言と代入は一行にまとめることもできます。これを変数の初期化と言います。

    void Start()
    {
        int a;
        a = 3;
        Debug.Log(a);
    }
    void Start()
    {
        int a = 3;
        Debug.Log(a);
    }

上のスクリプトはどちらも同じ値を出力します。ただ、初期化をした左のほうが見栄えや効率がいいため、宣言と代入をまとめられるときはそうするのがおすすめです。

代入の方向性

「=」は数学の等号の意味とは異なります。数学では「a = 10」「10 = a」も同じ意味ですが、C#ではそうもいきません。C#の「 = 」は代入であり、右の値を左の変数に代入するという方向性があります。
そのため、「10 = a」とC#で書いてしまうと「10という変数にaという値を代入する」となってしまい、エラーが出てしまいます。今後もこの方向性は大事になるので頭の中に入れておいてください。

変数の再代入

変数には値を何度も入れることができます。

    void Start()
    {
        int a = 5;
        Debug.Log(a);
        a = 7;
        Debug.Log(a);
    }

これを実行すると出力は7になります。この様に、変数に値を再び代入することはできますが、もともとの値(ここでは5)は消えてしまうので注意しましょう。

変数名のすゝめ

これまでのコードでは、変数名はa,bといった一文字で表してきました。しかし、実際にゲーム制作などを行う際は長くとも意味が分かる変数名にすることを強くオススメします!
(長すぎて読みにくくなるのは止めましょう)

int a;
float b;
bool c;
int playerJumpPower;
float playerPosition;
bool isJumping;

上のコードを見たとき、左は変数名がx,y,zで何を表しているのか一瞥しただけではわかりません。しかし、右は変数名が英単語の連続になっており、少し見ただけでも何の値を表しているのかが分かります。複数人で開発したり、他の人に助けてもらったりするときだけでなく、数日ぶりに自分が開発するときもわかりやすいので今のうちから理解しやすい変数名を付ける癖を持っておきましょう。

また、変数名を考えるのが面倒くさい時はChatGPTなどを活用しましょう。

インスペクターから変数を管理しよう!

これまで書いてきたのはスクリプト上だけで変数を管理する方法です。ですが、Unity上からでも変数を変更することができます。
変数をStartの外側に宣言する際、変数型の直前にpublicと書いてください。

using System.Collections.Generic;
using UnityEngine;

public class NewBehaviourScript : MonoBehaviour
{
    public int a;

    void Start()
    {
        Debug.Log(a);
    }
}

これでインスペクター上で見ることができます。

publicの代わりに[SerializeField]を書くことでもインスペクター上で見ることができます。これらの違いやプログラム上の意味についてはスクリプトの全体像【Unityの教科書#4】を参照。

Vector2型

実際のゲーム制作においてはX座標とY座標の二つの値を一度に使用することが多くなります。2つのfloat値を管理し、座標や方向の計算を容易にするために、Vector2型が利用されます。

先ほどまでの変数とは違い、初期化の際にnew Vector2(,);を使う必要があります。

    void Start()
    {
        // 初期化は new Vector2(〇,〇)を代入する必要があります
        Vector2 f = new Vector2(0,1);
        Debug.Log(f);

        // 各座標を取得することもできます(float型です)
        Debug.Log(f.x);
        Debug.Log(f.y);

    }

これを実行すると出力は(0,1)となります。また、0と1がそれぞれ出力されているはずです。

変数の命名規則

先程、変数名は長くてもわかりやすいのにしろと記述しましたが、複数の単語を組み合わせて変数にする時には注意点があります。それが命名規則です。命名規則は識別子(型や変数に割り当てる名前)がどういうものなのかを判別しやすくするために必要です。例えば変数かメソッドかクラスか分かるようにして違いをつけます。(メソッドについてはこちらの記事で解説しています。)命名規則は言語やプロジェクトによって異なり、一定の規則に従って書くのが重要です。C#とUnityで命名規則が少し違うものもありますが、ここでは一部のみを簡単に紹介します。

変数の命名規則

変数の名前は、先頭の語の最初は小文字で他の構成語の最初は大文字にするキャメルケースと呼ばれる書き方で書きましょう。以下に例を記します。

  • playerJumpPower
  • getInputReader
  • greatSuperDeluxe

bool型の命名の注意

bool型の変数名は、「〜かどうか」を表現するためにis,can,hasなどで始める場合が非常に多いです。以下に例を記します。

  • isJumping
  • canUseItem
  • hasSuperStar

クラスやメソッドの命名規則

クラスやメソッドの名前は、全構成語の最初を大文字にするパスカルケースと呼ばれる書き方で書きます。以下に例を記します。

  • PlayerController: プレイヤーの動きや行動を管理するクラス
  • HealthSystem: キャラクターの体力やダメージ処理を扱うクラス
  • MovePlayer: プレイヤーを動かすためのメソッド
  • JudgeIsInWater: 水中ならtrueを返すメソッド

メソッド名には動詞、クラス名には名詞を使う傾向があります。

演算子

Int型などの変数を扱うには、当然ながら足し算や引き算などの演算が必要です。
これらの、変数を演算する際に用いる処理の方法を演算子と呼びます。

算術演算子(+、-、*、%、++)

さて、まずは基本の演算子から書こうと思います。
“演算子”という物々しい字面からは気が引けるとは思いますが、まずはごく簡単なところから取りかかりましょう。
算術演算子は、私たちが算数で行ってることをスクリプトに書くことで行ってくれます。
下記の表を見ながら、先程と同じようにStart上に例文を書き、ちゃんと計算できているか確かめてみましょう。

書き方説明例文
+足し算Debug.Log(1 + 1); // 2
引き算Debug.Log(2 - 3); // -1
*かけ算Debug.Log(3 * 5); // 15
/割り算の商を求めます。
整数で割り切れない場合の答えは計算内の数字が整数型かそうじゃないかで変わります。
Debug.Log(9/3); // 3
Debug.Log(5/3); // 1
Debug.Log(5f/3f); // 1.66666666666667
%割り算の余りを求めます。Debug.Log(5%3); // 2
++インクリメントと言います。x++の場合、x=x+1を行います。int a = 5;
a++;
Debug.Log(a); // 6
デクリメントと言います。x--の場合、x=x-1を行います。int a = 2;
a--;
Debug.Log(a); // 1

代入演算子(=、+=)

数学における=は、C#では代入演算子と呼ばれます。
下記のコードを見てください。

    void Start()
    {
        float a;
        a = 5 + 3;
        Debug.Log(a);
    }

注目してほしいのは二行目のa = 5 + 3;
これは数学の文字式のように思えますし実際に行われることも同じなのですが、この行為をC#では『変数に計算式を代入する』と言います。代入演算子という名前の由来はこれですね。

変数の中に値を入れることを代入と言い、そして代入は計算式や数字である必要もありません。

    void Start()
    {
        float a;
        float b = 4;
        a = b;
        Debug.Log(a);
    }

上記のように、変数の中の値を代入することも可能です。

=以外の、特殊な代入演算子が存在します。
例えば、aという変数の値に4を加算させたい場合、数学に慣れている方はいま教えた方法だと、a = a + 4;という少し違和感を感じるコードになってしまいます。(プログラミングについて学んだばかりの方は、より違和感があるかもしれませんね)
このとき、a += 4;とコードを書くと、aにa + 4の値が代入されます。このような書き方をすることで、aという変数を繰り返し書くのを避けることができるということですね。

+=のような、変数に変数自身を加工した値を代入する特殊な代入演算子は、-=*=&=など(&については後述)、ほとんどの演算子に存在するので、使いたくなったときに各自調べてみてください。

bool値を扱う演算子

さて、これまではInt型などの数値型で使われる演算子を紹介しましたが、この項では数値だけでなく、bool値に関する演算子を紹介していきます。
正確には、bool型の値が解として出力される演算子です。
変数についての項でも紹介しましたが、bool型はとある事項が正しい(true)か間違っている(false)のかを知るための変数型です。
そのようなbool型の値を出力するということはつまり、今から紹介する演算子はそういった確認をするためのツールということになります。

等値演算子、非等値演算子(==!=

等値演算子 (==はある値同士が同じかどうかを調べる演算子です。
非等値演算子(!=はその逆で、ある値同士が同じではないことを調べる演算子です。
同じことを調べているように思うかもしれませんが、出力されるbool値が違います。

    void Start()
    {
        int appleCount = 5;
        int orangeCount = 5;
        int bananaCount = 3;

        bool areApplesEqualToOranges = appleCount == orangeCount; // りんごとオレンジの数が等しいか
        bool areApplesBananasEqual = appleCount == bananaCount; // りんごとバナナの数が等しいか
        bool areApplesNotEqualOranges = appleCount != orangeCount; // りんごとオレンジの数が等しくないか

        Debug.Log(areApplesEqualToOranges);
        Debug.Log(areApplesBananasEqual);
        Debug.Log(areApplesNotEqualOranges);
    }

この例文の場合、areApplesEqualToOrangesはtrue、areApplesBananasEqualはfalse、areApplesNotEqualOrangesはfalseを示します。
比較される値同士が同じではないとき等値演算子はfalseを返し、非等値演算子はtrueを返すということですね。

(例文に少し違和感を感じるかもしれませんが、これは演算子があまり使われない用途で使っているのが原因です。よくある使い方はif文の項で解説しています。)

比較演算子(>、>=、<、<=)

比較演算子は言うなれば大なり小なりです。数値型の場合だけ使用でき、>の場合は大なり、<の場合は小なりであればtrue、そうでなければfalseを返します。>=<=の場合は、値が同じ時でもtrueを返します。数式のように=<ではなく<=と書くことに留意してください。

    void Start()
    {
        int x = 2;
        int y = 50000;
        int z = 50000;
        Debug.Log(x > y);
        Debug.Log(y <= z);
    }

上のデバッグログはfalse、下はtrueを示します。

bool論理演算子(!、&&、||)

bool論理演算子はbool値を使用し、bool値を出力する演算子です。

!を直前に置くと、bool値の値は反転します。truefalseに、falsetrueにすることです。
&&は両脇にbool値を置き、両方のbool値がtrueのときにtrueのbool値を返します。日本語で言うなら「~かつ~」です。
||&&と同様に、両脇にbool値を置きます。こちらは片方のbool値がtrueであればtrueのbool値を返します。こちらは「~または~」ですね。

比較演算子で扱った例文を引き続き使って、これらの演算子を例示していきます。

    void Start()
    {
        int x = 2;
        int y = 50000;
        int z = 50000;
        Debug.Log(x > y);
        Debug.Log(y <= z);
        Debug.Log(!(x > y) && y <= z);
        Debug.Log(x > y || y <= z);
    }

どうなるか予想して、実際に実行して確認してみてください。
!の後のx>yに丸かっこ()をつけているのは!が否定している値を、x単体ではなくx>yであることをC#へ伝えるためです)

他にも論理AND演算子(&)、論理排他的OR演算子(^)、論理OR演算子(|)があります。これらはbool値間の演算やビット演算で使います。

型変換

異なる型の値変換したいことがよくあります。変換方法には様々なやり方がありますが、ここではいくつか紹介します。

int型に変換

float型をint型に変換

float型の値をint型に変換する一般的な方法は変換演算子(int)を使う方法です。

    void Start()
    {
        float floatValue = 123.456f;
        int intValue = (int)floatValue;
        Debug.Log(floatValue);
        Debug.Log(intValue); // 123
    }

他にも四捨五入して変換したり、小数点以下を切り上げたい場合があります。そのようなときはMathfのメソッドを使ってから変換できます。

    void Start()
    {
        float myFloat1 = 3.14f;
        int myInt1 = (int)Mathf.Round(myFloat1); // 四捨五入
        Debug.Log(myInt1); // 3

        float myFloat2 = 3.99f;
        int myInt2 = Mathf.FloorToInt(myFloat2); // 切り捨て
        Debug.Log(myInt2); // 3

        float myFloat3 = 3.01f;
        int myInt3 = Mathf.CeilToInt(myFloat3); // 切り上げ
        Debug.Log(myInt3); // 4
    }

Mathfは数学に関する様々な機能があるので一度調べてみるとよいでしょう。(Mathf – Unity スクリプトリファレンス

Mathf(UnityEngine.Mathf)はUnityが提供している機能で、他にも似た機能を持ったC#が提供しているMath(System.Math)というものがありますが、Mathでは浮動小数点はdouble型を返すのに対してMathfはfloat型を返すなどの特徴があり、Unityでは基本的にMathfを使うとよいでしょう。

string型をint型に変換

string型の値をint型に変換する方法として、int.Parse()int.TryParse()というメソッドがあります。文字列が数値でない場合は変換できないため、それをどう処理するかが異なります。細かい説明は省略します。必要なときに調べてみてください。

string型に変換

char型をstring型に変換

char myChar = 'a';
string myString1 = myChar.ToString();
string myString2 = $"{myChar}";

int型をstring型に変換

int number = 482;
string text1 = number.ToString();
string text2 = $"{number}";

float型をstring型に変換

float myFloat = 12.5f;
string myString1 = myFloat.ToString(); // 12.5
string myString2 = myFloat.ToString("0.00"); // 12.50
string myString2 = $"{myFloat}"; // 12.5
string myString3 = $"{myFloat:0.00}"; // 12.50

文字列補間

文字列の中に値をいれたいことがよくあります。このような場合、文字列補間を使って簡単に書くことができます。文字列補間は先ほども少し出てきましたが文字列の前に$をつけ、{}(波括弧)の中に変数や値を入れます。これにより文字列の中にその値が挿入されます。

string playerName = "Azuki";
int score = 80;
string message = $"{playerName}は${score}ポイントです。";

他にも+演算子を用いて文字列の連結をすることができますが、文字列補間の方が簡潔で直感的に書くことができる場面が多いです。

string playerName = "Azuki";
int score = 80;
string message = playerName + "は" + score + "ポイントです。";

明示的変換と暗黙的変換

先程までは基本的に明示的に型の変換を行う例を見てきました。このようにプログラマーが明確に型の変換を指定することを明示的変換といいます。例えば、float型の値をint型に変換するには小数点以下の扱いをどうするか指定する必要がありますね。

float myFloat = 48.58f;
int myInt = (int)myFloat; // 明示的変換が必要

逆にfloat型はint型よりも広い範囲の値を表現できるため、特に指定しなくても自動的に型変換が行われます。このように、型変換によって情報が失われる可能性が非常に低い場合に、暗黙的に変換が行われることをが暗黙的変換といいます。

int myInt = 10;
float myFloat = myInt; // 暗黙的変換が行われる

制御文

ここでは各制御文の意味や書き方について説明していきます。制御文を使うと特定の条件に応じて処理を分岐させることや、一部の処理を好きなだけ繰り返すことができるようになります。

実際にゲームを作るときにはこの制御文をたくさん使うことになるので、頑張って学習していきましょう!

if文

まず最初に、if文について説明していきます。if文は、ある特定の条件を満たしているときだけこの処理を行いたい、というときに使います。

書き方

if文の書き方は以下の通りです。

if (条件式)
{
    処理
}

条件を満たしているかどうかについてはbool値で表現します。そのため、条件式を書くときは演算子のところで学んだ比較演算子や論理演算子を用いて書きましょう。
このように書くと、条件式がtrueだった場合に{}内に記述された処理を実行します。また、条件式がfalseだった場合は{}内の処理は行われません

else文

条件式がfalseだった場合の処理を追加したいときには、else文を使いましょう。書き方は以下の通りです。

if (条件式)
{
    処理1
}
else
{
    処理2
}

このように書いた場合、条件式がtrueだった場合は処理1を、falseだった場合は処理2を行います

else if文

今までは一つの条件を満たしているかどうかによって処理を分岐させていましたが、分岐させたい処理が複数になることもあると思います。そういう場合には、else if文を使います。書き方は以下の通りです。

if (条件式1)
{
    処理1(条件式1がtrueの場合の処理)
}
else if (条件式2)
{
    処理2(条件式1がfalseかつ条件式2がtrueの場合の処理)
}
else
{
    処理3(すべての条件式がfalseの場合の処理)
}

ここで最も気を付けてほしいポイントは、一つ条件を満たして処理が行われるとそれ以降の処理はすべて行われません。スクリプトは上から順番に処理されるので、条件と処理を書く順番には注意しましょう。

また、else if文は何個でも追加することが可能ですが、else if文で終わってはいけません。必ず、else文で終わるか何も書かずに終わるようにしましょう。

使用例

では、if文を使って実際にコードを書いてみましょう!

以下は、age(年齢)というint型の変数が20以上のときに”成人”と表示させるスクリプトです。

    void Start()
    {
        int age = 19; //int型変数:ageの宣言

        if (age >= 20) //条件式:成人判定
        {
            Debug.Log("成人"); //条件式がtrueの場合の処理
        }
    }

実行すると今回はageが19なので何も表示されません。ageの値を書き換えて成人になるようにしてみましょう。

以下は、2の倍数か3の倍数か15の倍数か判定するスクリプトです。

    void Start()
    {
        int num = 12; //int型変数:numの宣言

        if (num % 15 == 0) //条件式1:15で割り切れるか
        {
            Debug.Log("15の倍数"); //条件式1がtrueの場合の処理
        }
        else if (num % 3 == 0) //条件式2:numが3で割り切れるか
        {
            Debug.Log("3の倍数"); //条件式1がfalseかつ条件式2がtrueの場合の処理
        }
        else if (num % 5 == 0) //条件式3:numが5で割り切れるか
        {
            Debug.Log("5の倍数"); //条件式1がfalseかつ条件式2がfalseかつ条件式3がtrueの場合の処理
        }
        else //すべての条件式がfalseの場合
        {
            Debug.Log("3の倍数でも5の倍数でも15の倍数でもありません"); //すべての条件式がfalseの場合の処理
        }
    }

コメント

「//」を使うと、それ以降その行はコメントとして扱われます。複数行のコメントをしたい場合は「/**/」を使います。ただ、後述するショートカットキーを使えば複数行のコメントは実装できるので「//」を主に使います。

//この行はコメントとして扱われるよ

/*このようにすると
複数行にわたって
コメントができるよ*/

コメントを入れることによってスクリプト中に処理の説明などをメモのように書き込むことができます。また、コメントは実際の処理に影響を及ぼすことはありません。

後からスクリプトの修正を行うときや、大人数で作業を行うときに非常に役に立つので積極的にコメントを残すようにしましょう!

指定した範囲をコメントにするショートカットキー

複数の行をまとめてコメントにしたいときに便利なショートカットキーを紹介します。まず、コメントにしたい部分を選択します。その状態で Ctrl + / を押すと選択した範囲がコメントになります。コメントを解除したい場合も、コメントにするときと同様の手順で解除することができます。

for文

続いてfor文について説明していきます。for文は、ある処理を指定した回数だけ繰り返し実行したい、というときに使います。

書き方

for文の書き方は以下の通りです。

for (初期値の設定; 処理を続ける条件; カウントする変数)
{
    処理
}

これだけでは分かりにくいと思うので、実際の例を用いて説明します。

for (i = 0; i < 11; i++)
{
    処理
}

このfor文は以下の意味を持っています。

  • 初期値:iは0から始まる。(初期値の設定)
  • iが11未満の間は処理を繰り返す。(処理を続ける条件)
  • iは1ずつ増える。(カウントする変数)

このfor文を実行した場合、iが11未満かどうかを確認し、11未満であれば処理を行い、iを1増やすという一連の処理を繰り返し、11未満でない場合は繰り返しを終了します。つまりこの場合は、11回処理を行うことになります。

使用例

では、for文を使って実際にコードを書いてみましょう!

    void Start()
    {
        for (int i = 0; i <= 10; i++) //11回{}内の処理を繰り返す
        {
            Debug.Log(i); //コンソールウィンドウにiの値を表示する
        }
    }

このコードを実行してみると、コンソールウィンドウに0から10までの値が順番に表示されていると思います。

初期値の設定や処理を続ける条件を変えるとどうなるかを、各自で色々と試してみてください。

while文

ここからは、while文について説明していきます。while文は、ある条件を満たしている限りこの処理を繰り返し行いたい、というときに使います。

書き方

while文の書き方は以下の通りです。

while (条件式)
{
    処理
}

if文と書き方は似ていますが、実際の処理は大きく違います。最初に条件式がtrueかどうかを確認して、trueだった場合は{}内の処理を行います。そして、{}内の処理がすべて終わるたびに条件式がtrueかどうか確認します。trueだった場合は再び{}内の処理を行い、falseだった場合には処理の繰り返しが終了します。注意点として、最初から条件式がfalseだった場合は{}内の処理は一度も行われません。また条件式がfalseにならない場合は、{}内の処理を無限回行うことになります。条件式の設定は注意して行いましょう。

使用例

では、while文を使って実際にコードを書いてみましょう!

void Start()
{
    int i = 0;
    while (i <= 10)
    {
        Debug.Log(i);
        i++;
    }
}

このコードを実行してみると、先ほど書いたfor文の例と同様の結果を得られると思います。このことからわかる通り、for文とwhile文でできることはほとんど一緒ですが、繰り返し回数が不明瞭なときはwhile文が適しています。

無限ループ

while文をうまく活用すると、while文内の処理を無限回繰り返すことができます。これを無限ループといいます。書き方は以下の通りです。

while (true)
{
    無限回行われる処理
}

このように、while文の条件式を書くところに直接trueと書くことで無限ループを表現できます。ただし、この状態で実行しないようにしてください。

while文以外で無限ループを表現

無限ループを表現する方法にはwhile文のほかにdo-while文があります。また、for文を用いても無限ループを表現することが可能です。以下に示すコードの処理は、すべて上記のwhile文の処理と同じです。

do-while文

do
{
    無限回行われる処理
} while (true)

for文

for (; ; )
{
    無限回行われる処理
}

ループを抜ける方法

無限ループは文字通り無限回行われるため、終わることがありません。無限ループを実行し続けると、パソコンに大きな負荷がかかってしまうためとても危険です。そのため、無限ループを書く際はループを抜けるための処理を書く必要があります。ループを抜ける際に使うのがbreak文です。
具体的な使用例は以下の通りです。実際にコードを書いてみましょう!

void Start()
{
    int i = 0;
    while (true) //無限回{}内の処理を繰り返す
    {
        if (i > 4) //ループを抜ける条件
        {
            break; //ループを抜ける
        }
        Debug.Log(i); //コンソールウィンドウにiの値を表示する
        i++; //iの値を1増やす
    }
}

基本的に、無限ループを作る際はif文を用いてループを抜ける条件を設定します。その条件を満たした場合に、if文の{}内にあるbreak文でループを抜けます。実際にこのコードを実行すると、iが4より大きくなったところで処理が行われなくなっていることが確認できます。

break文はwhile文以外にもfor文などでも使うことができます。

クラッシュ時の対応

誤ってbreak文のない無限ループを実行すると、パソコンがクラッシュしてしまう可能性があります。もしクラッシュしてしまった場合には以下の手順に従ってください。ただし、前提として即座にUnityを開きなおさないようにしてください。開いてしまうとデータを復旧させることができなくなってしまいます。

手順1:使用していたプロジェクトのフォルダからTempフォルダを開く

手順2:Tempフォルダ内の_BackupscenesフォルダをTempフォルダの外に移動させる

手順3:プロジェクトを開いて、最後に開いていたシーンを開く

手順4:ゲームを再生して、再生中に別の場所に移動させておいた_BackupscenesフォルダをTempフォルダ内に戻す。

手順5:ファイルを置き換えるかどうか聞かれるので「ファイルを置き換える」を選択する。

手順6:ゲームの再生を停止する。

以上で復旧作業は終了です。落ち着いてクラッシュの原因を解明しましょう。

メソッド(ReturnValue Method(Argument arg){})

これまで例文やデバッグログなどを使ってC#というプログラミング言語を学んできましたが、その中に出てきたStart()Debug.Log()に対して、「これって結局なんなの?」と疑問を持った方もいると思います。目ざとい方であれば、そこに()という共通点を見出すかも知れません。

この2つは両方メソッドという物に関連しており、これを学ぶことで本格的なC#プログラミングが可能になります。

Start()やLog()はUnityから提供されたメソッド

まず、用語の説明をさせていただきます。メソッドとは、C#で作ることのできる(または既に作られた)処理のまとまりのことです。

ここで、最初に作ってもらったスクリプトを見てみましょう。

    void Start()
    {
        Debug.Log(1);
    }

メソッドには波括弧{}が付随し、その中に実際に行う処理内容を書きます。
上の例文の場合は、Startというメソッドの処理内容をDebugというモノ1の中に存在するLogというメソッドを呼び出すように書いているということです。
そして、Log()の中に1という数字が書かれていますね。この1のように、メソッドの()に入力する変数や値を引数と呼びます。

さて、ここで紹介した2つのメソッドは両方Unityから提供されているものです。自分でメソッドを作る方法は最後に教えますが、その前にUnityから提供されたメソッドについて学び、メソッドの概念を完全に理解しましょう。

Start()……ゲーム起動時に呼び出されるメソッド

StartメソッドはUnityがゲーム実行時に自動的に呼び出してくれるメソッドです。(正確にはゲーム実行時だけでなくシーン開始時に呼び出されますが、これはまだ習っていない事項ですので気にしなくていいです。)
これまでStart内に書いていたDebug.Logや計算式が問題なく動いていたのは、UnityがStartメソッドを呼び出してくれていたからなのです。

もし下の例文のようにメソッド名を打ち間違えてしまえば、変数aの宣言も、1+1すらしてくれません。Unityが呼び出すのはStartメソッドであり、Starratなどというメソッドなどは知らないのです。Debug.Logなどもっての外でしょう。

    void Starrat()//誤字っちゃった!
    {
        int a;//実行されない。
        a = 1+1;//実行されない。
        Debug.Log(a);//星のネズミってなんですか?
    }

もちろん、Startメソッド内でStarratメソッドを呼び出せば、ゲーム実行時にStarratメソッドを実行することはできます。

    void Start()
    {
        Starrat();
    }
    void Starrat()//誤字っちゃった!
    {
        int a;//実行される。
        a = 1 + 1;//実行される。
        Debug.Log(a);//星のネズミってなんですか?
    }

Update()……毎フレーム呼び出されるメソッド

Updateメソッドは、Startメソッドと同じく、Unityでスクリプトを作った際に自動的に設定されるメソッドです。これまでも、初めてVS Codeを開いたときから視界の端に見えていたことでしょう。

これまたStartメソッドと同じく、Unityが自動的に呼び出してくれるメソッドですが、頻度が違います。このメソッド内の処理はフレームという時間単位ごとに毎回呼び出されるのです。

例文を実行したデバッグログを見て、どれくらいの頻度でUpdateメソッドが実行されるか見てみましょう。

using UnityEngine;

public class NewBehaviourScript : MonoBehaviour
{
    float frameCount = 0;

    void Update()
    {
        Debug.Log(frameCount);
        frameCount++;
    }
}

デバッグログの濁流にビビったらゲームの実行を中止し、楽しくなってきたのならゲームの実行を続けてください。

フレームとは?

上記の例文で実感された通り、フレームは決して一秒1フレームで換算できるものではありません。また、フレームの間隔は一定ですらないのです。

フレームとは現実の時間ではなく、パソコンが計測する時間です。なので、パソコンの処理が遅くなっているときはフレームの間隔、そしてUpdateメソッドの呼び出しがその分遅くなります。
例文をUnityで実行し、フレームの不規則性を実感しましょう。

using UnityEngine;

public class NewBehaviourScript : MonoBehaviour
{
    float frameCount = 0;

    void Update()
    {
        float passTimeBetweenFrames = Time.deltaTime; //Time.deltaTimeは前回のフレームから今回までのフレームまでに経過した現実時間を示す
        float realTime = Time.time; //Time.timeはゲーム開始時から経過した時間を示す
        Debug.Log("フレーム数:" + frameCount + "|フレーム間の秒数:" + passTimeBetweenFrames + "|現実時間:" + realTime);
        frameCount++;
    }
}

Debug.Log(Object message, Object context)……引数をとるメソッド

さて、上記の2つのメソッドはその役割について焦点をあてながら解説させていただきました。しかし、Debug.Logのほうはすでに何度も使ってきて、その役割も理解できている頃だと思います。なので、ここでは引数(argument)のあるメソッドの具体例として、Log()を扱おうと思います。

引数とは、そもそもメソッドを呼び出すときに入力する値(数値に限りません)ですが、引数を要求するメソッドは、受けた引数の値によって挙動を変えます。取得した引数をそのままUnityのコンソール上に表示するLogは、その顕著な例ですね。

また、メソッドは引数を複数取ることもでき、Log()も、2つ引数を取る同名のメソッドがあります。(一つ引数を取るDebug.Logと二つ引数を取るDebug.Logが存在するという意味です。)
例文のように書いたスクリプトを、2つのゲームオブジェクトの両方にアタッチしてください。

void Start()
{
    Debug.Log(1, gameObject); //gameObjectは、スクリプトがアタッチしているゲームオブジェクトを指す
}

ゲームを実行すると、いつも通り1とデバッグログが出ると思います。今回は2つのオブジェクトにスクリプトをアタッチしているので、出てくるデバッグログも2つです。当然、見ているだけではどちらのゲームオブジェクトがどちらのデバッグログを発したかはわかりません。
ですが、デバッグログをクリックしてみてください。ヒエラルキー上でゲームオブジェクトがハイライトされ、デバッグログを発したゲームオブジェクトが強調表示されます。
Logの第二引数には、デバッグログをクリックした時に強調させたいゲームオブジェクトを設定する、ということですね。

メソッドを作ろう

ここから、自分でメソッドを作る方法について教えたいと思います。
ですがその前に、メソッド関連のメモを確認してください。ほとんどが既出ですが、一つだけこれまで出てこなかった返り値という概念が登場します。

  • 引数……メソッドを呼び出す際に必要な値。複数の引数があるものや、引数を取らないメソッドもある。メソッドの処理に影響する。
  • 返り値……メソッド内の演算結果など、呼び出し元にメソッドが返す値。返り値の型はメソッド名の前に宣言され、返り値がない場合はvoidと書かれる。StartUpdateには返り値がないのでvoidと書かれている。
  • メソッドの呼び出し方……メソッド名()の形式で呼び出す。引数が必要な場合は()内に書く。複数の引数を書く場合はカンマ,で区切る。返り値がある場合、その型によっては計算式や制御文の条件式にメソッドが存在することもあり得る。

これを踏まえて、四種類のメソッドを作り使用する例文を見ていきましょう。

引数も返り値も存在しないメソッド

作りたいメソッドに引数もメソッドも必要がない場合、メソッドはvoid メソッド名() {}で作られます。StartUpdateと同じですね。

    void TestMethod()
    {
        Debug.Log("Test");
    }

これでデバッグログでTestと表示させるメソッドが完成しました。このメソッドをStartUpdateで呼び出せば、一度または毎フレームごとにTestと表示させることができます。

返り値はなく、引数が存在するメソッド

引数に応じて処理を変えるメソッドを作る場合、メソッドはvoid メソッド名(引数の型 任意な引数の名前) {}で作りましょう。引数には想定している型と、メソッドの中で使うときのための名前も宣言しなければなりません。

   void Start()
    {
        SeventhOfKuku(1);
        SeventhOfKuku(3);
        SeventhOfKuku(5000);
    }
    void SeventhOfKuku(int number)
    {
        if (number <= 9)
        {
            Debug.Log(number * 7);
        }
    }

これは引数が9以下だったときに引数と7の積を表示する、九九の七の段を行ってくれるメソッドです。
またnumberを全て別の名前に書き換えても、このメソッドは動作します。numberはあくまで、メソッド内で識別するためだけに付けた名前ということですね。

また、カンマ,で区切ることで複数の引数を宣言できます。今度は九九のメソッドを作りましょう。

    void Start()
    {
        Kuku(4,2);
        Kuku(3,7);
        Kuku(1, 10);
        Kuku(11,9);
        Kuku(100000,1000000);
    }
    void Kuku(int firstNumber,int lastNumber)
    {
        if (firstNumber <= 9&&lastNumber<=9)
        {
            Debug.Log(firstNumber*lastNumber);
        }
    }

引数はなく、返り値が存在するメソッド

メソッド内の計算結果を呼び出し元に返したい場合、メソッドは返り値の型 メソッド名() {return 返り値;}と書きましょう。返り値を返す場合、メソッドの内容に値を返す処理を書く必要が出てきます。

    int a;
    void Start()
    {

        a = 0;
        Debug.Log(SayCanDevideByTwo());
        a = 100001;
        Debug.Log(SayCanDevideByTwo()+"それにしても返り値がstring型のメソッドを書く例文が思いつかないな");
    }
    string SayCanDevideByTwo()
    {
        if (a%2==0)
        {
            return "aは偶数だ。";
        }
        else
        {
            return "aは奇数だ。";
        }
    }

例文のように、返り値のあるメソッドをそのまま演算子による計算に組み込むこともできます。

返り値も引数もあるメソッド

返り値と引数の両方があるメソッドは返り値の型 メソッド名(引数の型 任意な引数の名前) {return 返り値;}と書きます。といっても、ここまででやってきたことを組み合わせるだけですね。

なので、今回は少し難易度の高い例文を書きたいと思います。これまでの総復習のような例文になっていれば幸いです。

    public int x;
    public int y;
    void Start()
    {
        Debug.Log(x + "と" + y + "の最大公約数は" + Euclid(x, y) + "だ。");
    }
    int Euclid(int a, int b)//ユークリッドの互除法
    {
        int noMean;//仮置き用のなんの意味もない変数
        int aChanging=a;
        int bChanging=b;
        while (!CanBeDivide(aChanging,bChanging))
        {
            if (aChanging>=bChanging)
            {
                noMean = aChanging % bChanging;
                aChanging = bChanging;
                bChanging = noMean;
            }
            else
            {
                noMean = bChanging % aChanging;
                bChanging = aChanging;
                aChanging = noMean;
            }
            noMean = 0;
        }
        return bChanging;
    }
    bool CanBeDivide(int c,int d)//c>=dを前提にしている
    {
         return c % d == 0;
    }

配列(array)とリスト(List)

これまで学んできた方法では、変数とは一つ一つ宣言され、変更されていくものでした。
ですがこのままゲームを制作していくと、似たような変数を何度も宣言する羽目になりかねません。そんなときは、配列、そしてリストを使いましょう。
これらは同じ型の変数を複数格納できます

この項目は最初はちゃんと読まなくても良いです!

ここまで教科書を読んで目が疲れた方や、さっさと実践的な内容に入りたいと思った方は、「配列やリストというものもある」程度の認識に留めておき、#3のページに進んでいただいて構いません。
もちろん、この項目はここまでの教科書を読んでいれば理解できる内容にはなっているので、まだまだ体力が残っている方は臆さず読み進めてください。

配列(array[index])

配列の宣言方法は変数型[] 配列名 = new 変数型[要素数]です。(ここでは配列内やリスト内の個々の変数を要素と呼びます。)(newについてはここでは無視してください。このページの最後に少し触れます。)
宣言と同時に代入することもでき、その場合は変数型[] 配列名 =new 変数型[要素数]{要素0,要素1,要素2,要素3,要素4,……}と書きます。(最初の要素が0なのは誤字ではありません。詳しくは後述。)
これだけではわかりにくいと思うので、例文を読んでください。

   void Start()
    {
        int[] numbers= new int[4];
        string[] week = new string[7] { "Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"};
    }

int型の配列numbersは宣言だけされ、string型の配列weekには宣言とともに各要素が代入されています。
しかしこの方法で代入する場合、配列に定めたい要素数と過不足がないように要素を代入しなければなりません。(上の例文の場合、一度にすべての曜日を代入する羽目になっています。試しに”Sunday”を消してみると、エラーが出るはずです。)

一度にすべてを代入するのが不都合な場合、個々の要素を指定することで個別に代入できます。
要素を指定するときには、配列名[添え字]といったように、要素ごとに順番に割り振られた添え字(index)と呼ばれる数字を使用するのですが、これがあまり直感的ではありません。
先程例文で作ったweekを使って、添え字と要素の対応を見ていきましょう。

添え字0123456
weekの要素MondayTuesdayWednesdayThursdayFridaySaturdaySunday

最初の要素を指定する際は添え字に0を使用します。これが先程要素0という単語が出てきた理由です。
つまり、配列内の要素を指定するときは配列名[n]と書けばn+1番目を指定できます。
実際に例文を見て確かめてみましょう。

    void Start()
    {
        int[] numbers= new int[4];
        string[] week = new string[7] { "Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"};
        numbers[0] = 0;
        numbers[1] = 1;
        numbers[2] = 2;
        numbers[3] = 3;
        //numbers[4] = 100000000;//numberの要素4は存在しないのでコメントアウト。コメントアウトを外してUnity上で実行するとエラーが出る。
        Debug.Log(week[4]);
    }

リスト(List[index])

リストも、配列と同じように複数の同じ型の変数を格納します。
リストの宣言方法はList<変数型> リスト名=new List<変数型>();です。配列と同じように{}で初期値を設定することができます。
要素数を定める手順はありません。
これが配列と違うところで、Listはいくらでも要素数を増やすことができます。
要素の追加はリスト名.Add(追加したい要素)というメソッドで行います。
例文を読んで学習しましょう。意欲的な方は自分でコードを打ちこんで要素を増やしまくってください。

void Start()
{
List<string> ice = new List<string>();
ice.Add("Vanilla");
ice.Add("Chocolate");
ice.Add("Strawberry");
Debug.Log(ice[0]);
}

デバッグログでわかるように、Listも配列と同じように最初の要素の添え字は0です。

リストに関わるメソッド(読み飛ばし推奨)

リストに関するメソッドは無数に用意されており、今回は比較的簡単かつ有用(と思われる)なメソッドを扱いたいと思います。当然ここにはないものもありますので、リストに関するなんらかの処理が必要になったら、メソッドを自作する前に調べてみると目当てのものが見つかるかも知れません。

(配列にももちろん有用なメソッドは用意されているのですが、こちらはUsing System;という今は学んでいないものが必要になってしまうので割愛しました。配列にも.Sort().IndexOf()などのメソッドが存在するので、意欲的な方は調べてみてください。)

List.AddRange()……リストに他のリストや配列の要素をまるごと追加する

List.Add()では一つ一つ要素を追加していましたが、List.AddRange()では他のリストまたは配列の全要素を追加することで多くの要素を追加できます。引数は(配列またはリスト)です。

    void Start()
    {
        List<string> favorite= new List<string>();
        string[] favoriteGame = new string[3] { "Splatoon3","MineCraft","LobotomyCorporation"};
        favorite.AddRange(favoriteGame);
        Debug.Log("My favorite game is "+favorite[0]+".");//My favorite game is Splatoon3.と出る。
        favorite.AddRange(new List<string> { "Ice", "Udon", "Mushidori" });
        Debug.Log("My favorite food is "+favorite[4]+"."); //My favorite food is Udon.と出る。
    }
List.Insert()……リストの途中に要素を追加する

List.Add()では要素を最後尾に継ぎ足すように追加していましたが、List.Insert()はリストの途中に要素を挿入できます。引数は(追加する場所の添え字,追加したい要素)です。

    void Start()
    {
        List<int> numbers = new List<int>() { 1,2,3,5};
        numbers.Insert(3, 4);
        Debug.Log(numbers[0]);
        Debug.Log(numbers[1]);
        Debug.Log(numbers[2]);
        Debug.Log(numbers[3]);
        Debug.Log(numbers[4]);
    }

List.AddRange()と同じように、List.InsertRange()も存在します。

List.Sort()……リストの順番を整える

List.Sort()はリスト内の要素を昇順に並べ替えます。引数はありません。

    void Start()
    {
        List<int> heights = new List<int>() { 169,143,182,70,155};
        heights.Sort();
        Debug.Log(heights[0]);//最小値の70を表示する
    }

for文を使って多数の要素に処理をしよう

配列やリストと、制御文の項目で説明したfor文は相性がいいです。
for文を使うことで、全要素への処理が可能です。
例文のfor文の()内の式を見て、どういう処理をしているか理解しましょう。
わかったらUnityを実行し、デバッグログを確かめてください。

void Start()
{
    int[] numbersArray = new int[4] { 1, 2, 3, 4};
    for (int i=0; i<numbersArray.Length; i++)//.Lengthは配列の要素数を教えてくれる。この場合は4。
    {
        Debug.Log(numbersArray[i]);
    }
    List<int> numbersList = new List<int> {5,10,6,100,7,1000,8};
    for (int i=0;i<numbersList.Count;i++)//.Countはリストの要素数を教えてくれる。この場合は7。
    {
        if (i % 2 == 0)
            {
                Debug.Log(numbersList[i]);
            }
    }
}

配列の全要素とリストの奇数番目の要素がデバッグログに表示されたと思います。

foreach文で同じことをしてみよう!

foreach文は、コレクション(配列やリストなど)を扱う制御文で、全ての要素に同じ動作を行わせることができます。つまり、先程の例文のnumbersArrayのfor文は、foreach文に書き替える事ができるということです。
foreach文の書き方はforeach(要素の変数型 任意な個々の要素の名前 in 配列またはリスト名){個々の要素に行う作業}です。要素の変数型 任意な個々の要素の名前の部分は要素の名前を宣言しています。実際に、foreach文の中でそれぞれの要素を扱う際はここで宣言した名前が使われます。
numbersArrayのfor文を、foreach文に書き替えたコードがこちら。

        foreach (int number in numbersArray)
        {
            Debug.Log(number);
        }

かなり簡素なものになりましたね。for文に比べ、わかりやすくなったように感じます。
実際にUnity上で動かしても、for文で動かした場合と同じデバッグログが出る事がわかると思います。
しかしforeach文でできないこともあります。例えば、numbersListでのfor文のような、for文のiが実行される処理に影響するような文は、そのままforeach文にすることはできません。
ある程度文章を工夫すればできないわけではありませんが、こういった場合はfor文を使ったほうが良いでしょう。また、foreach文内で要素の変更はできません。

便利なインデックス演算子を使おう!

インデックス演算子とは、配列やリスト等における添え字を扱う演算子です。
今までの添え字は[0]が最初、[1]が二番目を指すように、前から順番に数字が増えていきました。
ですが、最後列から数えた値で要素を指定したいときもあるでしょう。そういったとき、^という記号を使えば便利です。例文で使い方を見てください。

    void Start()
    {
        List<int> numbersList = new List<int> {0,1,2,3,4 };
        //Debug.Log(numbersList[^0]);//存在しない要素を指定しているのでエラーになってしまう。
        Debug.Log(numbersList[^1]);//[^1]が最後尾を指す。
        Debug.Log(numbersList[^2]);
        Debug.Log(numbersList[^3]);
        Debug.Log(numbersList[^4]);
        Debug.Log(numbersList[^5]);
    }

インデックス演算子は他にももう一つあるのですが、特に使う可能性の高いこちらを紹介しました。もう一つは範囲指定なのですが、使いたくなった時に調べてみてください。

なぜnewで宣言する必要があるのか?

配列、リストの宣言には、必ずnewが使われています。
普通の変数の宣言と違っていて混乱した方もいると思いますが、それもそのハズ。配列やリストは変数とはまた分類が違い、構造体型と呼ばれるものなのです。と分類されるものを新しく宣言するにはnew式が必要なのですが、これを『インスタンスを作成する』と呼びます。
ここではそこまで覚えなくて構いません。#4でまた同じことを学ぶことになります。

『コレクション』

配列とリストについて、最初に「同じ型の変数を複数格納できるもの」であると言いました。こういった性質を持つ構造体型をコレクションと言います。

実は、配列やリスト以外にもコレクションと呼ばれるものは存在します。ここでは具体的な他コレクションの説明はしませんが、配列やリストの機能が足りないように感じた時、「C# コレクション」とGoogleで検索することも視野に入れると良いと思います。

多次元配列、List型のList

配列、リストについてこれまで学んでもらいましたが、彼らの真価はこんなものではありません。

皆さんは人生で一度はこんな表を見たことはないでしょうか?

0123
0
1
2
3
概念のオセロ

これ、実は配列で表現できます。
この表のような縦、横といった多種類の添え字がある配列を多次元配列と言います。

試しに、上の表を配列で再現してみましょう。二種類の添え字のある二次元配列を宣言する場合、変数型[,] 配列名=new 変数型[x方向の要素数,y方向の要素数];となります。三次元配列の場合は変数型[,,] 配列名=new 変数型[x方向の要素数,y方向の要素数,z方向の要素数];と、次元が増えれば増えるほどカンマ(,)と宣言する要素数が増えます。

実際の宣言文は以下のようになります。

char[,] othello = new char[4, 4];

多次元配列は複数の座標を持っているので、要素を指定するときも複数座標を入力する必要があります。その形式は配列目[x方向の添え字,y方向の添え字]です。

左上の”黒”を配列に代入するときは、表のように座標を参照しましょう。

0123
0
1
2
3

この場合、othllo[1,1]='黒';と書きます。

表を例文で再現してみました。

    void Start()
    {
        char[,] othello = new char[4, 4];
        for(int i=0;i<4;i++)
        {
            othello[i, 0] = '緑';
            othello[i, 3] = '緑';
            othello[0, i] = '緑';
            othello[3, i] = '緑';
        }
        othello[1, 1] = '黒';
        othello[2, 2] = '黒';
        othello[1, 2] = '白';
        othello[2, 1] = '白';
    }

多次元配列のようなことを、リストが直接行うことはできません。
しかし、リストの要素型をListにすれば、多次元配列と同じようなことも可能です。

    void Start()
    {
        List<List<char>> othello = new List<List<char>>();
        othello.Add(new List<char> { '緑','緑','緑','緑'});
        othello.Add(new List<char> { '緑','黒','白','緑'});
        othello.Add(new List<char> { '緑','白','黒','緑'});
        othello.Add(new List<char> { '緑','緑','緑','緑'});
    }

終わりに

これでC#の基礎は終了です。お疲れ様でした!
次回はUnity特有のものである「コンポーネントの操作」について学んでいきます。

  1. Debugのようなものをクラスと呼ぶのですが、この用語については#4で教えられます。 ↩︎
タイトルとURLをコピーしました