main() blog

プログラムやゲーム、旅、愛する家族について綴っていきます。

【Unity】Unityで覚えるC#(不定期更新)

はじめに

Unityを始めるにあたりそもそもC#も触っていなかったので基本的な言語仕様や構文等のスタディの備忘録を付けておきます。 C#固有、Unity固有の何かがあればそれもメモしていきます。

基本のデータ型

C#本来のsystem名前空間の組み込みデータ型。

範囲(ビット)
byte 符号なし整数 8 0 ~ 255
sbtyte 符号付き整数 8 -128 ~ 127
int 符号付き整数 32 -2147483648 ~ 2147483647
uint 符号なし整数 32 0 ~ 4294967295
short 符号付き整数 16 -32768 ~ 32767
ushort 符号なし整数 16 0 ~ 65535
long 符号付き整数 64 -922337203685477508 ~ 922337203685477507
ulong 符号なし整数 64 0 ~ 18446744073709551615
float 単精度浮動小数点型 32 -3.402823e38 ~ 3.402823e38
double 倍精度浮動小数点型 64 -1.79769313486232e308 ~ 1.79769313486232e308
char 単一Unicode文字 16 テキストで使用される Unicode 記号
bool 論理ブール型 8 true または false
object 他すべてのの型の基本型
string 文字列

例)

public class Hoge
{
    // コンストラクタ.
    public Hoge()
    {
        Debug.Log("### 基本のデータ型\n");

        byte by = 0;
        sbyte sby = 0;
        int i = 0;
        uint ui = 0;
        short s = 0;
        ushort us = 0;
        long l = 0;
        ulong ul = 0;
        float f = 0.0f;
        double d = 0.0;
        char c = 'a';
        bool b = true;
        string str = "aaa";

        Debug.Log("  byte : " + by + "\n");

        // 書式指定での出力.
        Debug.Log( string.Format("  uint : 0x{0:X}\n", ui );    // unity4.6まで.
        Debug.LogFormat(" uint : 0x{0:X}\n", ui );              // unity5から.

        // {0}は引数の番号?.
        // 可変引数で複数の引数がある場合は{0},{1}で指定していく?.
        Debug.LogFormat(" int:[{0}] uint:[{1}]\n", i, ui );
    }
}

var

auto変数みたいなもの? メソッドスコープ内で宣言される変数に使用することができる。

func()
{
    // string型で宣言しなくてもstringとして扱われる.
    var name = "山田太郎";
}

MS的には右辺の代入の型が明示的な場合は極力varを使うことをMSDNのコーディング規約にも書かれている。

foreach

C#でのforeachの書き方。

int[] = new int[10];

foreach(int v in array)
{
    Debug.LogFormat("  {0}", v);
}

// varでも書ける.
foreach(var v in array)
{
    Debug.LogFormat("  {0}", v);
}

配列

func()
{
    // ↓NG
    // C,C++の様な書き方.
    int arry[ 10 ];
    // ↓OK
    int[] arry = new int[10];
  // 初期値を入れる.
    int[] arry = new int[10]{1,2,3,4,5,6,7,8,9,10};
    // ↓NG
    // サイズが指定されている場合は要素数を入れないとエラーになる.
    int[] arry = new int[10]{1,2,3,4,5};
    // サイズが無ければ初期化の要素数が配列の要素数となる.
    int[] arry = new int[]{1,2,3,4,5}

    arry[ 0 ] = 1;

    for( int i = 0; i < arry.Lehgth; i++ )
    {
        Debug.LogFormat("  [{0}]:{1}\n", i, arry[i]);
    }

    // foreachでの書き方.
    foreach( int v in arry )
    {
        Debug.LogFormat("  {0}\n", v );
    }
}

可変長配列

STLvectorみたいなものです。 List<>というのがあります。 これはC#で用意されているものです。

using System.Collections.Generic;  // List<>のnamespace

func()
{
    List<int> list = new List<int>();

    // 追加.
    list.Add(10);
    list.Add(20);
    list.Add(30);
    list.Add(40);

    // 挿入.
    list.Insert(2, 100);

    Debug.LogFormat("count : [{0}]\n", list.Count);

    // 配列の様にアクセスできるみたい...
    for( int i = 0; i < list.Count; i++ )
    {
        Debug.LogFormat("   [{0}] : [{1}]\n", i, list[i]);
    }

    foreach( int v in list)
    {
        Debug.LogFormat("   [{0}]\n", v);
    }

    // sort.
    list.Sort();
    foreach( int v in arry )
    {
        Debug.LogFormat("  [{0}]\n", v );
    }
}

連想配列

STLのmapの様なものです。 Dictionary<>というのがあります。 keyとvalを指定して指定したkeyでvalを引っ張ってくることができます。

using System.Collections.Generic;

func()
{
    Dictionary<int,string> dic = new Dictionary<int,string>();

    dic.Add(1,"Hoge1");
    dic.Add(2,"Hoge2");

    dic[3] = "Hoge3";

    Debug.LogFormat("  val:[{0}]", dic[1]);

    // キーの列挙.
    foreach(var key in dic.Keys)
    {
        Debug.LogFormat("  key:[{0}]", key);
    }

    // 要素の列挙.
    foreach(var val in dic.Values)
    {
        Debug.LogFormat("  val:[{0}]", val);
    }

    // キーと要素の列挙.
    foreach(KeyValuePair<int,string> pair in dic)
    {
        Debug.LogFormat("  [{0}] : [{1}]", pair.Key, pair.Value);
    }
}

define定義

[Unity] 全体に反映されるマクロ(シンボル)を定義する

・プラットフォーム依存コンパイル Unityで用意されているプラットフォームごとの#defineディレクティブ。

“#if"のディレクティブは使えるらしい…

“#define"定義はファイルの先頭で行わないとダメ?

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

#define _ENABLE_HOGE

public class Main : MonoBehaviour {

以下の様なエラーが出ました。

Assets/Scripts/Main.cs(6,0): error CS1032: Cannot define or undefine preprocessor symbols after first token in file

ファイルの先頭に持っていったらエラーは出なくなりました。

#define _ENABLE_HOGE

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

public class Main : MonoBehaviour {

if文(制御構文)

C#ではif文は必ずbooleanになっていないとダメです。

enum Attr
{
    None = 0,
    Fire = 0x1 << 0,    // 火属性.
    Water = 0x1 << 1,   // 水属性.
    Earth = 0x1 << 2,   // 土属性.
    Light = 0x1 << 3,   // 光属性.
    Dark = 0x1 << 4,    // 闇属性.
}

func()
{
    int i = 0;

    // ↓ダメ.
    // 数値ではエラーになる.
    if( i )
    {
    }

    // ↓OK
    if( i == 0 )
    {
    }

  // ビット演算だと判定が冗長になってしまう...
    Attr attr = Attr.None;

    attr = Attr.Fire;

    // ↓NG
    // ビット演算ではboolにならない.
    if( attr & Attr.Fire )
    {
    }

    // ↓OK
    if( (attr & Attr.Fire) == Attr.Fire )
    {
        Debug.Log("### 火属性.\n");
    }

    attr = Attr.Water | Attr.Light;
    if( (attr & (Attr.Water | Attr.Light)) == (Attr.Water | Attr.Light) )
    {
        Debug.Log("### 水&光属性.\n");
    }

    // 良いかどうかは置いておいて次の様な書き方でも判定できる.
    if( (attr & (Attr.Water | Attr.Light) != 0 )
    {
        Debug.Log("### 水&光属性.\n");
    }
}

【Unity】HasFlag 関数を Unity でも使用できるようにする拡張メソッド

swithc文

func()
{
    int val = 1;

    switch(i)
    {
        case 0:
            break;
        case 1:
            break;
        default:
            break;
    }
}

文字列も値として扱うことができる。

func()
{
    string val = "TEST001";

    switch(val)
    {
        case "TEST001":
            break;
        case "TEST002":
            break;
        default:
            break;
    }
}

C#におけるクラス

クラスがメンバーに持てるもの

  • フィールド 変数。 通常あまり生のメンバー変数を公開するものではないが、Unityの場合パブリックフィールドにしないとインスペクターに表示されない。

  • メソッド/コンストラクタ/デストラクタ 関数。 コンストラクタとデストラクタは少し特殊な関数。

  • プロパティ アクセサの簡易表記法。

  • インデクサー オブジェクトを配列のように扱えるようにするための機能。

  • イベント 処理の一部を外部に委譲したり、何かのタイミングを通知するための機能。

  • コンストラクタ オブジェクトの初期化処理を行うためのメソッド。 ただし、UnityはMonoBehaviourを継承したクラスがコンストラクタを実装することを推奨していません。 Awake/Startを使いましょう。

  • デストラクタ オブジェクトの破棄処理を行ためのメソッド。 C#ではGCが動いていて明示的な破棄はしなくてよいので、通常は意識する必要はありません。 C#で破棄のタイミングを管理したいクラスはIDisposableを継承して、Dispose()メソッドを実装し、using文と合わせて使います。 ただ、Dispose()の実装は慣れていないと難しい…

  • クラスメンバーとインスタンスメンバー

  • クラス修飾子 ・sealed sealed修飾子を付けたクラスは継承できなくなります。

  • メンバーフィールド修飾子 ・const ・readonly

  • メンバーメソッド修飾子 ・virtual ・override ・extern

  • 属性(アトリビュート

自分でクラスを定義してみます。

public class Hoge
{
    public Hoge()
    {
        Debug.Log("### Hoge::Hoge()");
    }

    // デストラクタ.
    // publicを付けるとビルドエラーになる.
    ~Hoge()
    {
        Debug.Log("### Hoge::~Hoge()");
    }

    public int x = 0;

    public void SetY(int i)
    {
        y = i;
    }

    public int GetY()
    {
        return y;
    }

    private int y = -1;
    private int z = 0;
}

func()
{
    Hoge hoge = new Hoge();

    hoge.SetY(10);

    Debug.LogFormat("  x:[{0}]", hoge.x);
    Debug.LogFormat("  z:[{0}]", hoge.GetY());
}

Unityのinspectorに表示してみます。

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

public class TestAttribute : MonoBehaviour {

    // publicはinspectorに表示される.
    public int test_int = 10;    // 初期値も反映される.
    public float test_float = 20.0f;
    public string test_string = "test";
    public GameObject test_gameobject;
    public ElementAttr test_enum;

    // privateは表示されない.
    private int _test_int;

    // Use this for initialization
    void Start () {
        
    }
    
    // Update is called once per frame
    void Update () {
        
    }
}
  • 表示結果

変数名の先頭の小文字は勝手に大文字に変換されるようです。

unity_000.png

プロパティ

C#(VB)固有の言語仕様。 アクセサの簡易表記。

プロパティ

public class Hoge
{
    // プロパティ.
    // 自動プロパティ.
    public int X { get; set; }

    public int Z
    {
        // setアクセサ(setterとも言う).
        set
        {
            // valueという名前の変数に代入された値が入る.
            if(!(value >= 0 && value <= 10))
            {
                Debug.Assert(false);
                return;
            }
            
            z = value;
        }

        // getアクセサ(getterとも言う).
        get
        {
            return z;
        }
    }

    private int z = 0;
}

func()
{
    Hoge hoge = new Hoge();

    // プロパティ.
    hoge.X = 100;

    Debug.LogFormat("  X:[{0}]", hoge.X);

    // エラーが拾える.
    hoge.Z = 100;

    Debug.LogFormat("  Z:[{0}]", hoge.Z);

    hoge.Z = 10;

    Debug.LogFormat("  Z:[{0}]", hoge.Z);
}

属性(アトリビュート

属性(attribute)とはクラスやメンバーに追加情報を与えるもの。

C#の言語仕様についてはこちらを参考に。 ・属性

Unityで使う場合はこちらを参考に。 ・UnityのAttribute(属性)についてまとめてメモる。Unity/C# - Inspector表示の変更①

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

public class TestAttribute : MonoBehaviour {

    // publicはinspectorに表示される.
    public int test_int = 10;    // 初期値も反映される.
    public float test_float = 20.0f;
    public string test_string = "test";
    public GameObject test_gameobject;
    public ElementAttr test_enum;

    [Range(0.0f, 10.0f)]
    public float test_float_range = 5.0f;
    public float test_float2;    // ← ここにはattributeは適用されない.

    [Header("----")]

    // ↓privateもinspectorに表示することができる.
    [SerializeField]
    private int test_int_private;

    [Multiline(2)]
    public string test_multiline;

    [TextArea(2, 5)]
    public string test_textarea;

    // privateは表示されない.
    private int _test_int;

    // Use this for initialization
    void Start () {
        
    }
    
    // Update is called once per frame
    void Update () {
        
    }
}
  • 表示結果

unity_001.png

構造体

public struct Point
{
    public int x;
    public int y;

    public Point(int x, int y)
    {
        this.x = x;
        this.y = y;
    }
}

func()
{
    Point point = new Point(10, 20);

    Debug.LogFormat("  x:[{0}] y:[{1}]");
}

インターフェイス

記述中…

キャスト、型情報の利用

-キャストすべきかasするべきか-

  • キャスト
TestClass test = (TestClass)obj;
TestClass test = obj as TestClass;

キャストとは逆に「変換先の型」が右側に来ます。 参照型にしか使用できません。 値型(struct等)には使用できません。 変換できなかった場合はnullが返ります。

キャストを試してみます。

func()
{
    int i;
    float f;

    // OK.
    f = i;

    // ↓コンパイルエラーになる.
    i = f;

    // 明示的なキャストが必要.
    i = (int)f;
}
Assets/Scripts/Main.cs(453,8): error CS0266: Cannot implicitly convert type `float' to `int'. An explicit conversion exists (are you missing a cast?)
public class A {}
public class B {}
public class D : B {}

public interface I1 {}
public interface I2 {}
public class E : I1, I2 {}

{
    D d = new D();
    B b = d;    // キャストは不要.
}

{
    E e = new E();
    I1 i1 = e;    // キャストは不要.
    I2 i2 = e;
}

{
    B b = new D();
    D d = (D)b;    // 明示的なキャストが必要.
}

{
    A a = new A();
    B b = (B)a;    // コンパイルエラー.
}
Assets/Scripts/Main.cs(490,13): error CS0030: Cannot convert type `Main.A' to `Main.B'

as演算子を試してみます。

{
    A a = new A();
    // ↓これだとコンパイルエラーになる.
    //B b = a as B;
    // object型にキャストしてからだと大丈夫.
    B b = (object)a as B;

    if(b == null)
    {
        // 変換できないとnullが返る.
    }
}
{
    I1 i1 = new E();
    I2 i2 = i1 as I2;
    E e = i2 as E;
}

パーシャルクラス、パーシャルメソッド

記述中…

拡張メソッド

既存のクラスに機能を追加する方法です。

public static class 適当なクラス名
{
    public static void Method( this 拡張したいクラス self )
    {
    }
}

例えばstringに拡張メソッドを追加する場合は以下の様に記述します。

public static class StringExt
{
    public static void ExtMethod( this string self )
    {
    }
}

func()
{
    string str;

    str = "aaa";

    str.ExtMethod();
}

リフレクション

記述中…

ジェネリック

いわゆるテンプレート。 List<>もDictionay<>もジェネリックで記述されている。

ジェネリック

// ジェネリッククラス.
class GenericClass<T>
{
    T item;

    public void SetItem(T v)
    {
        item = v;
    }

    public T GetItem()
    {
        return item;
    }
}

func()
{
    // ジェネリック.
    GenericClass<int> i = new GenericClass<int>();

    i.SetItem(5);
    Debug.LogFormat("  i:[{0}]", i.GetItem());

    GenericClass<float> f = new GenericClass<float>();

    f.SetItem(10.0f);
    Debug.LogFormat("  f:[{0}]", f.GetItem());
}

デリゲート

記述中…

イベント

記述中…

ラムダ式

記述中…

LINQ

記述中…

コルーチン

記述中…

デザインパターン

記述中…

コーディング規約

記述中…

参考

Unityで覚えるC#

MSDN : C# プログラミング ガイドMSDN : C# のコーディング規則 (C# プログラミング ガイド)MSDN : ジェネリック型の型パラメーター (C# プログラミング ガイド) MSDN : 名前に関するガイドライン

Unityでよく使うデザインパターン