ルーグの備忘録

主にC#についてまとめてます。

【C#】名前空間

名前空間とは

クラスを種類ごとに分けて管理するための機構。
名前空間中に定義したクラスを参照するには完全修飾名を書くかusing ディレクティブを用いる必要がある。

定義方法

namespace Animal
{
    public class Cat { }
}

// C# 10.0 から
namespace Flower;

public class Lavender  { }

用法

完全修飾子名を書くパターン。

class NameSpaceTest
{
  static void Main()
  {
       Animal.Cat cat = new Cat ();
  }
}

usingディレクティブを用いるパターン。

using Animal;

class NameSpaceTest
{
  static void Main()
  {
       Cat cat = new Cat ();
  }
}

【C#】classとインスタンス

classとは

後述するインスタンスを作るために定義できる設計図。
変数や関数は全てclassの中に書く。classの中に書かれた変数をメンバー変数、関数をメソッド(メンバー関数)という。

定義方法

1. public(アクセス修飾子)
2. class
3. クラス名
4. {}の中にクラスの実装
の順に書く。

// public class クラス名
public class Person
{
    // 具体的な実装
    // 変数とかメソッドとかを書く

    public string m_Name = Lisa;

    public void ChangeName(string name)
    {
        m_Name = name;
    }
}

publicは省略可能で、省略してもpublic扱いとなる。classは基本的に全てpublicとしてしか使えず、privateなど他のアクセス修飾子を付けるとエラーとなる。(しかし厳密にいうと、内部クラスというものにはpublic以外のアクセス修飾子を付けることができる)
自作したクラスはintやstringと同じようにデータ型として扱うことができる。

// Person型の変数を定義
private Person m_Person;

インスタンスとは

classを設計図として作られた実体のこと。classはただの設計図だから、classのままだと利用することができない(少し例外はある)。なので利用するときはそのclassを元にインスタンスを生成して使う。

インスタンスの生成(newキーワード)

newキーワードを使う。

// インスタンスが生成されて m_Person にインスタンスが入る
private Person m_Person = new Person();

インスタンスの変数やメソッドを参照する

// m_Nameの値を取得 nameに代入
string name = m_Person.m_Name;

// ChangeNameメソッドを呼び出す
m_Person.ChangeName("Bob");

このとき、参照したい変数やメソッドのアクセス修飾子がprivateになっていると参照できないので注意。エラーが出るときはpublicになっているかどうかを確認する。

実践 ~インスタンスの取得~

インスタンスの取得について、よくありそうなCharacterクラスを例にとって解説する。

/// <summary>
/// Characterクラス
/// </summary>
public class Character : MonoBehaviour
{
    /// <summary>
    /// ヒットポイント
    /// </summary>
    private int m_Hp = 100;

    /// <summary>
    /// 攻撃力
    /// </summary>
    private int m_Atk = 10;

    /// <summary>
    /// ダメージを受けるメソッド
    /// </summary>
    /// <param name="atk">相手の攻撃力</param>
    /// <returns>被ダメージ後の残りHp</returns>
    public int OnDamage(int atk)
    {
        // Hpをatkだけ減らす
        m_Hp -= atk;

        // 被ダメージ後のHpの値を返す
        return m_Hp;
    }

    /// <summary>
    /// 攻撃をするメソッド
    /// </summary>
    /// <param name="enemy">敵のCharacterインスタンス</param>
    private void OnAttack(Character enemy)
    {
        int remainingHp = enemy.OnDamage(m_Atk);
    }

    /// <summary>
    /// 衝突時のコールバック
    /// </summary>
    /// <param name="collision">衝突したゲームオブジェクト</param>
    private void OnCollisionEnter(Collision collision)
    {
        Character enemy = collision.gameObject.GetComponent<Character>();
        OnAttack(enemy);
    }
}

OnAttackメソッド内の

int remainingHp = enemy.OnDamage(m_Atk);

にて、相手のCharacter型のインスタンスのOnDamageメソッドを呼ぶことでダメージを与えている。
問題は「どうやって相手のCharacter型のインスタンスを取得するか」だが、それはOnCollisionEnter関数で取得をしている。

解説

イベント関数

イベント関数とは、MonoBehaviourを継承したクラスで実行される特別な関数。簡単にいうと、class名の隣に「: Monobehaviour」が付いてるとなぜか使えてしまう便利なメソッドである。
実装すると、特定のタイミングでその関数を勝手に呼んでくれる優れもの。馴染み深いであろうStart関数やUpdate関数もイベント関数の一種である。

OnCollisionEnter関数

OnCollisionEnter関数は、衝突した瞬間に呼ばれるイベント関数。引数に衝突した相手の情報が詰まったCollision型のインスタンスを勝手に持ってきてくれる。
Collisionっていう型はUnityが用意しているクラス。今回は

collision.gameobject

で衝突した相手のGameObject型のインスタンスを取得している。
GameObjectっていう型もUnityが用意しているクラス。後述するGetComponent関数を実行するために必要。

GetComponent関数

そもそもコンポーネントとは

ゲットコンポーネントの「コンポーネント」とは何か。
コンポーネントとは、インスタンスである。Unityではインスタンスのことをコンポーネントってよく呼ぶ。それだけ。
GameObject型のインスタンス = GameObjectコンポーネント である。

用法
// 取得したいクラスのインスタンス = GameObject型のインスタンス.GetComponent<取得したいクラス名>();
private void OnCollisionEnter(Collision collision)
{
    Character enemy = collision.gameObject.GetComponent<Character>();
    OnAttack(enemy);
}

※ 衝突したGameObjectがCharacterコンポーネントを所持していないとエラーが出るので注意。

つまり、一連の流れを超絶細かく順序立てて説明するとこうなる。

1. 引数から衝突した相手のCollisionコンポーネントをもらう。

2.

collision.gameObject

までで、GameObjectコンポーネントを参照。

3.

collision.gameObject.GetComponent<Character>()

までで、Characterコンポーネントを取得。

4.

Character enemy = collision.gameObject.GetComponent<Character>();

で、ローカル変数enemyに、取得したCharacterコンポーネントを代入。

5. OnAttackメソッドの引数にenemyを渡して呼び出す。

インスタンスコンポーネント)の取得にはGetComponent関数を使う。インスタンスの取得ができるようになると、自由にゲームを作れるようになるのでぜひ理解して欲しい。

インスタンスコンポーネント)についての理解がまだふわふわしている方はこれを読むと何か得るものがあるかも。
hakase0274.hatenablog.com

【C#】関数(メソッド)

変数の解説と被る部分が多いので、変数について何も知らない人は先にこちらをチェック。

lougestudy.hatenablog.jp

関数とは

別名メソッド。
メソッドが担う役割は2つある。

  • まとまった処理を実行する。
  • 処理の結果に応じて呼び出し元に値を返す。

2つとも大事なので覚えておいて欲しい。

定義と用法

定義方法

1. アクセス修飾子
2. 戻り値のデータ型
3. メソッド名
4. ()の中に引数のデータ型と名前
5. {}の中に具体的な処理
の順に書く。
引数については後で詳しく書くが、引数は複数あってもよいし無くてもよい。

// アクセス修飾子 戻り値の型 メソッド名
// ()の中に 引数の型 引数名
// {}の中に 具体的な処理
private int SampleMethod(int num)
{
    // 具体的な処理
}

用法

引数の値は適当。

// 戻り値が不要な場合
SampleMethod(10);

// 戻り値が必要な場合 valueに戻り値が入る
int value = SampleMethod(20);

難しいので分かりやすいところから説明していく。

アクセス修飾子 ☆

メソッドの呼び出しができる範囲を決定するもの。
privateにすると、そのクラス内でしかメソッドを呼び出すことができない。publicにすると、外のクラスからでもメソッドを呼び出すことができる。
省略して書くと、そのメソッドは暗黙的にprivateとして扱われる。

// privateとして扱われる
void Foo() { }

メソッド名

メソッドの名前。自由に名前を付けていいが、他で既に使われている名前だとエラーが出たりする。
分かりやすい名前にしないと、後から見たときにわけが分からなくなるので注意。
メソッドの名前は全ての単語の先頭を大文字で書くのが主流。この命名規則パスカルケースという。

// パスカルケース
private void FinalFlashBigBangAttack() { }

具体的な処理

メソッドの中身。メソッド名に合った処理を書いてあげよう。
前述した「まとまった処理を実行する」という役割を果たす。

private void AppearCatLog()
{
    Debug.Log("ねこ"); // 具体的な処理
}

引数

引数とは

メソッドを呼び出すときに、メソッドに渡すことができる値を引数という。メソッドの実行に必要な情報を与えることが目的。
前述したが、引数は複数あってもよいし無くてもよい。
引数の名前は先頭の単語だけ全て小文字にして、あとの単語の頭文字を大文字で書くのが主流。この命名規則をローワーキャメルケースという。

// ローワーキャメルケース
private void Bar(int argValue) { }

引数の活用例

メソッドの定義
/// <summary>
/// 引数に与えられた文字列をログとして出力する
/// </summary>
/// <param name="message">ログに出力する文字列</param>
private void AppearLog(string message)
{
    Debug.Log(message); // messageにはメソッドを呼び出す側が与えた値が入る。
}
メソッドの呼び出し

引数に任意の値を入れてメソッドを呼ぶ。このメソッドの場合、引数で与えられた文字列をログとして出力する。

AppearLog("ねこ");

戻り値

戻り値とは

メソッドの呼び出し元に返す値のことを戻り値という。返り値ともいう。
前述した「処理の結果に応じて呼び出し元に値を返す」という役割を果たす。
戻り値があるメソッドは必ずreturn文で値を返さなければならない。また、メソッド内にif文などで条件分岐を作った場合、どの条件分岐を辿ったとしても戻り値が存在するようにしなければならない。

戻り値の活用例

メソッドの定義

メソッドの最後に「return 値」と書くことで、メソッドの実行を終了して値を返す。

/// <summary>
/// 引数で与えられた2つの値を足す
/// </summary>
/// <param name="a">前項</param>
/// <param name="b">後項</param>
/// <returns>計算結果</returns>
private int Addition(int a, int b)
{
    int value = a + b; // int型のローカル変数を定義
    return value; // returnで値を返す
}
メソッドの呼び出し

戻り値を取得して、計算結果をログとして出力する。

int value = Addition(5, 3); // valueに5 + 3の計算結果が入る
Debug.Log(value); // ログに8が出力される

voidメソッド

voidとは「何もない」ことを意味するデータ型。戻り値にvoidを設定すると、戻り値がないvoidメソッドを作ることができる。voidメソッドはreturn文が不要。
戻り値なんて要らないけど、処理の実行はしたい。そんなときにはvoidメソッドを作る。

// ログを出力するだけのメソッド
// 戻り値がないのでreturn文が不要
private void AppearLog(string message)
{
    Debug.Log(message); 
}

// メソッド呼び出し
AppearLog(“ねこ”);
// 足し算をして、さらに計算結果を戻り値として返すメソッド
// 戻り値があるのでreturn文が必要
private int Addition(int a, int b)
{
    int value = a + b;
    return value; // returnで値を返す
}

// -----メソッド呼び出し----- //

// 戻り値を取得する
int value = Addition(5, 3);

// 戻り値を取得しなくてもよい
Addition(5, 3);

正味、voidメソッドが1番使う機会多そう。でも戻り値があるメソッドも分かっていると、よりきれいなコードを書けると思う。

実践 ☆

以下のコードはキャラクターのclass実装例。OnDamageメソッドが引数と戻り値のある難しいメソッドになっている。
こんな感じにメソッドを作るよ、という例です。

public class Character
{
    /// <summary>
    /// ヒットポイント
    /// </summary>
    private int m_Hp;

    /// <summary>
    /// ダメージを受けるメソッド
    /// </summary>
    /// <param name="atk">相手の攻撃力</param>
    /// <returns>被ダメージ後の残りHp</returns>
    public int OnDamage(int atk)
    {
        // Hpをatkだけ減らす
        m_Hp -= atk;

        // 被ダメージ後のHpの値を返す
        return m_Hp;
    }
}

OnDamageメソッドの呼び出し方法例。

// 戻り値が不要な場合
OnDamage(10);

// 戻り値が必要な場合 remainingHpに被ダメ後の残りHpが入る
int remainingHp = OnDamage(10);

【C#】演算子

演算子とは

変数に対して処理を行うために必要な記号のこと。+、-、=など。

「=」 の意味

数学での「=」とプログラミングでの「=」はそれぞれ意味が違うので注意が必要。
例えば「X = 10」の場合

  • 数学では「Xと10は等しい」という意味(比較演算子)。
  • プログラミングでは「Xに10を代入する」という意味(代入演算子)。
// Xに10を代入
private int X = 10;

用法

算術演算子

public class Calculator
{
    private int X;

    private void Calculate()
    {
        X = 15 + 5; // 足し算 Xは20
        X = 15 - 5; // 引き算 Xは10
        X = 15 * 5; // 掛け算 Xは75
        X = 15 / 5; // 割り算 Xは3

        X = 15 % 2; // 割った余りを計算 Xは1
    }
}

なお、整数の割り算は0に向かって丸められる。

インクリメントとデクリメント

値に1を加える処理をインクリメントという。反対に、値から1を減じる処理をデクリメントという。
インクリメントとデクリメントは使う機会が多いので、次のようにして短く書けるようになっている。

public class Calculator
{
    private int X = 5;
    private int Y = 3;

    private void Calculate()
    {
        ++X; // インクリメント Xは6
        --Y; // デクリメント Yは2
    }
}

「++X」は「X = X + 1」と同じ意味。「--Y」は「Y = Y - 1」と同じ意味。

複合代入演算子

代入先の値を演算前に参照したい。そんなときに使える省略記法。
説明するよりコードを見てもらう方が早い。

public class Calculator
{
    private int X = 5;

    private void Calculate()
    {
        X += 3; // X = X + 3と同じ Xは8
    }
}

もちろん「-=」「*=」「/=」「%=」なども可。

他にもまだまだたくさんある

もっと知りたい人はこちらをチェック。

ufcpp.net

目次

【C#】変数

変数とは

特定の「値」を持つことができる便利なもの。
値とは、例えば3や10.5みたいな数値とか、“ねこ”みたいな文字列とかのこと。
変数ごとにどの種類の値を持つことができるかを決めなければならない。値の種類のことを「データ型」と呼ぶ。
変数が持っている値はコロコロ変えたりすることができる(逆に固定値にしたりもできる)のでとても便利。

定義方法

1. アクセス修飾子
2. データ型
3. 変数名
の順に書く。

// アクセス修飾子 データ型 変数名
private float Speed;

順に説明していく。

アクセス修飾子 ☆

変数の中身の取得や変更ができる範囲を決定するもの。
privateにすると、そのクラス内でしか変数の値を見たり変えたりすることができない。publicにすると、外のクラスからでも変数の値を見たり変えたりできる。あとUnityのinspectorタブでも値をいじれるようになる。
他にもprotectedとかinternalがあるけど、とりあえずprivateとpublicが分かれば大丈夫。
ちなみに省略して書くと、その変数は暗黙的にprivateとして扱われる。

// privateとして扱われる
float Speed;

データ型

前述の通り、値の種類のこと。自分は単に「型」と呼んだりもする。
例えば、intなら整数が入るし、floatなら小数が入るし、stringなら文字列が入る。
決められた型ではない値を変数に代入しようとすると基本的には怒られるので注意。

// "速い"はfloat型ではなくstring型なのでエラー
private float Speed = "速い";

変数名

変数の名前。自由に名前を付けていいが、他で既に使われている名前だとエラーが出たりする。
分かりやすい名前にしないと、後から見たときにわけが分からなくなるので注意。
ちなみに自分はいつも、変数名の頭に「m_」を付けるようにしている。こうするとパッと見で、それが自分の定義した変数だと分かるのでおすすめ。

// m_を付けると分かりやすい
private float m_Speed;

変数の初期値

変数は定義するとき、初期値を設定することができる。

// = を使って初期値を設定
private float m_Speed = 3f;

初期値を設定しない場合、その変数には既定値(デフォルト値)が入る。既定値は型によって異なる。例えば、floatなら既定値は0f。

// 初期値を設定していないので既定値が代入される
private float m_Power; // m_Powerは0f

既定値について詳しく知りたい人はこちらをチェック。

alunote.hatenablog.com

ローカル変数

メソッドの中にも変数を定義することができる。メソッドの中で定義された変数をローカル変数という。

private void SampleMethod()
{
    // ローカル変数
    // 基本的性質は普通の変数とは変わらない
    int num;
}

定義するときにアクセス修飾子は不要。ローカル変数はそのメソッド内でしか使うことができない。
メソッド内にポンと定義して使い捨てができる便利な変数、そんなイメージで使う。

メソッドについて詳しく知りたい方はこちらをチェック。

lougestudy.hatenablog.jp

【C#】ジェネリクス

ジェネリクスとは

型をパラメタとして与えられるようにすることで、型違いで同じ内容のクラスや関数を生成できるという仕組み。上手く使うとクラスや関数を型の数だけ定義する手間が省ける。
日本語だと総称的プログラミング(generic programming)。

活用例

二次元配列で指定されたindexを中心として、周囲の要素をDictionaryで取れるようにする構造体。

/// <summary>
/// 周囲のセルを取得
/// </summary>
public readonly struct AroundCell<T>
{
    public T CenterCell { get; }
    public Dictionary<DIRECTION, T> AroundCells { get; }

    public AroundCell(T[,] map, int centerX, int centerZ)
    {
        CenterCell = map[centerX, centerZ];
        AroundCells = new Dictionary<DIRECTION, T>();

        int xLength = map.GetLength(0);
        int zLength = map.GetLength(1);

        for (int x = -1; x <= 1; x++)
            for (int z = -1; z <= 1; z++)
            {
                if (x == 0 && z == 0)
                    continue;

                int checkX = centerX + x;
                int checkZ = centerZ + z;

                if (checkX >= 0 && checkX < xLength && checkZ >= 0 && checkZ < zLength)
                {
                    var dir = Positional.GetDirection(x, z);
                    AroundCells.Add(dir, map[checkX, checkZ]);
                }
            }
    }
}

参考

ufcpp.net