【Unity2D】2Dゲームを作るときのMonoBehaviourを使いやすくするクラスの設計


## はじめに
Unityで、一番最初にスクリプトを書き始めて気になってしまうのが、ゲームに必要な情報へアクセスするための記述が長くなってしまうことです。

例えば座標を取得するには `transform` にアクセスする必要があります。

```csharp
  // X座標を取得する
  float x = transform.x;
```

さらに座標を変更する場合には、直接代入ができず一時変数を経由する必要があります。

```csharp
  // X座標を代入する
  Vector3 v = transform;
  // X=20へ移動する
  v.x = 20;
  // 座標を反映する
  transform = v;
```

少しでもゲームライブラリを作ったり、他のゲームエンジンを使ったことがある人にとって、これはとても面倒な記述です。他のゲームエンジンであれば、たいていのゲームオブジェクトはメンバ変数に「x」などの座標変数を持っていて直接取得や書き換えができます。

Unityがコンポーネント指向を採用して汎用的な作りになっているので、これは仕方ないことなのですが、コードベースでゲームを作ることが多い人にとってこれは面倒なことです。

そこで、私が独自で作ったMonobehaivourのラッパークラスを紹介します。ただここで紹介するのは2Dゲーム専用なので3Dには使えません。またこれは実装例なので必ずこのようにしなければならないというものではなく、「こんなプロパティやメソッドがあると便利だよね」という参考程度にしてもらえればと思います。

## クラス名と必須コンポーネント
クラス名は「Token」としています。ゲームキャラクターの場合「Actor」というのがよく使われる名称ですが、厨二病をこじらせているので、人があまり使わない名前を使うようにしています。

```csharp
/// キャラクター基底クラス.
/// SpriteRendererが必要.
[RequireComponent (typeof(SpriteRenderer))]
public class Token : MonoBehaviour
{
  ……
```

ゲームキャラクターの場合、たいてい描画を行うので、`SpriteRenderer`コンポーネントを必須としています。

## プロパティ
プロパティには以下のものを用意しました。

| 名前 | 型 | 概要 | 説明 | 必須コンポーネント |補足|
|:----|:---|:----|:-----|:----------------|:--|
|X|float|X座標||-||
|Y|float|Y座標||-||
|VX|float|移動量(X)|X方向への移動量|Rigidbody 2D||
|VY|float|移動量(Y)|Y方向への移動量|Rigidbody 2D||
|Direction|float|移動方向|移動角度。0〜359|Rigidbody 2D|getのみ|
|Speed|float|移動速度|Mathf.Sqrt(VX^2 + VY^2)|Rigidbody 2D|getのみ|
|GravityScale|float|重力加速度||Rigidbody 2D||
|RigidBody|Rigidbody2D|Rigidbody2Dの参照||Rigidboby 2D|getのみ|
|Exists|bool|生存フラグ|オブジェクトが存在するかどうか|-||
|ScaleX|float|スケール値(X)|X方向の拡大縮小値|-||
|ScaleY|float|スケール値(Y)|Y方向の拡大縮小値|-||
|Scale|float|スケール値|XYスケール値をまとめて変更|-||
|Visible|bool|描画フラグ|falseで描画しなくなる|SpriteRenderer||
|Angle|float|回転角度|回転角度(Z軸回転)|SpriteRenderer||
|Alpha|float|透過値|透過値(0.0〜1.0f)|SpriteRenderer||
|SpriteWidth|float|スプライトの幅||SpriteRenderer||
|SpriteHeight|float|スプライトの高さ||SpriteRenderer||
|SortingLayer|string|ソーティングレイヤー名||SpriteRenderer||
|SortingOrder|int|ソーティングオーダー番号||SpriteRenderer||
|Renderer|SpriteRenderer|SpriteRendererの参照||SpriteRenderer|getのみ|
|CircleCollider|CircleCollider2D|CircleCollider2Dの参照||CircleCollider|getのみ|
|CollisionRadius|float|CircleColliderの半径||CircleCollider 2D||
|CircleColliderEnabled|bool|Circle Collider2D有効フラグ|有効であればtrue|Circle Collider 2D||
|BoxColliderWidth|float|BoxColliderの幅||Box Collider 2D||
|BoxColliderHeight|float|BoxColliderの高さ||Box Collider 2D||
|BoxColliderEnabled|bool|Box Collider2D有効フラグ|有効であればtrue|Box Collider 2D||

長くなってしまいましたが、カテゴリとしては、だいたい以下の4つです。

* transform系
* SpriteRender系
* Rigidbody系
* コリジョン系

スクリプトからコリジョン系のパラメータを変更することはあまりないのですが、たまに使いたいときがあります

## メソッド
メソッドは以下のものを用意しました

| 名前 | 概要 | 説明 | 必須コンポーネント |補足|
|:----|:----|:-----|:----------------|:--|
|AddPosition|座標を加算する||-||
|SetPosition|座標を設定する||-||
|SetScale|スケール値を設定する||-||
|AddScale|スケール値を加算する||-||
|MulScale|スケール値を乗算する||-||
|SetVelocity|移動速度を設定する|引数は「角度」と「速さ」|Rigibody 2D||
|SetVelocityXY|移動速度を設定する|引数はXYの移動量|Rigidbody 2D||
|MulVelocity|移動速度を乗算する||Rigidbody 2D||
|SetSprite|スプライトを設定する||SpriteRenderer||
|SetColor|スプライトの色を設定する|RGBをそれぞれ0.0〜1.0で指定|SpriteRenderer||
|SetAlpha|スプライトの透過値を指定する|0.0〜1.0|SpriteRenderer||
|GetAlpha|スプライトの透過値を取得する|0.0〜1.0|SpriteRenderer||
|SetSize|オブジェクトのサイズを設定する|ClampScreenで使用する|-||
|ClampScreenAndMove|カメラの範囲内に座標を丸める||-|移動した上で座標を丸める|
|ClampScreen|カメラの範囲内に座標を丸める||-||
|IsOutside|カメラの範囲外に出たかどうか||||
|GetWorldMin|カメラ左下のワールド座標を取得する||-||
|GetWorldMax|カメラ右上のワールド座標を取得する||-||
|DestroyObj|オブジェクトを破棄する||-|Destroy(gameObject)と同じ。メモリから削除する|
|Vanish|オブジェクトを無効化する||-|メモリからは破棄しない|
|Revive|Vanishで無効化したオブジェクトを復活する||-||

## プレハブからインスタンスを生成する
プレハブからインスタンスを生成するために `GetPrefab` と `CreateInstance`の2つの関数を用意しています。

```csharp:Token.cs
  /// プレハブ取得.
  /// プレハブは必ず"Resources/Prefabs/"に配置すること.
  public static GameObject GetPrefab (GameObject prefab, string name)
  {
    return prefab ?? (prefab = Resources.Load ("Prefabs/" + name) as GameObject);
  }

  /// インスタンスを生成してスクリプトを返す.
  public static Type CreateInstance<Type> (GameObject prefab, Vector3 p, float direction = 0.0f, float speed = 0.0f) where Type : Token
  {
    GameObject g = Instantiate (prefab, p, Quaternion.identity) as GameObject;
    Type obj = g.GetComponent<Type> ();
    obj.SetVelocity (direction, speed);
    return obj;
  }
```

こ関数の使い方ですが、"Assets/Resources/Prefabs/Enemy"プレハブを使ってインスタンスを生成する方法は以下のようになります。

```csharp:Enemy.cs
/// 敵
public class Enemy : Token {
	/// プレハブ
	static GameObject _prefab = null;

	/// インスタンス生成
	public static Enemy Add(int Id, float x, float y, float direction, float speed) {
		// プレハブ取得
		_prefab = GetPrefab(_prefab, "Enemy");
		return CreateInstance<Enemy>(_prefab, x, y, direction, speed);
	}
```

static関数から生成を行いたい場合に、この関数を使ってインスタンスを生成することができるようになります。

## ソースコード
ではソースコードです。

* [Token.cs](https://gist.github.com/syun77/99af299ea8c756c890e3 "Token.cs")

```csharp:Token.cs
using UnityEngine;
using System.Collections;

/// キャラクター基底クラス.
/// SpriteRendererが必要.
[RequireComponent (typeof(SpriteRenderer))]
public class Token : MonoBehaviour
{
  /// プレハブ取得.
  /// プレハブは必ず"Resources/Prefabs/"に配置すること.
  public static GameObject GetPrefab (GameObject prefab, string name)
  {
    return prefab ?? (prefab = Resources.Load ("Prefabs/" + name) as GameObject);
  }

  /// インスタンスを生成してスクリプトを返す.
  public static Type CreateInstance<Type> (GameObject prefab, Vector3 p, float direction = 0.0f, float speed = 0.0f) where Type : Token
  {
    GameObject g = Instantiate (prefab, p, Quaternion.identity) as GameObject;
    Type obj = g.GetComponent<Type> ();
    obj.SetVelocity (direction, speed);
    return obj;
  }

  public static Type CreateInstance2<Type> (GameObject prefab, float x, float y, float direction = 0.0f, float speed = 0.0f) where Type : Token
  {
    Vector3 pos = new Vector3 (x, y, 0);
    return CreateInstance<Type> (prefab, pos, direction, speed);
  }

  /// 生存フラグ.
  bool _exists = false;

  public bool Exists {
    get { return _exists; }
    set { _exists = value; }
  }

  /// アクセサ.
  /// レンダラー.
  SpriteRenderer _renderer = null;

  public SpriteRenderer Renderer {
    get { return _renderer ?? (_renderer = gameObject.GetComponent<SpriteRenderer> ()); }
  }

  /// 描画フラグ.
  public bool Visible {
    get { return Renderer.enabled; }
    set { Renderer.enabled = value; }
  }

  /// ソーティングレイヤー名.
  public string SortingLayer {
    get { return Renderer.sortingLayerName; }
    set { Renderer.sortingLayerName = value; }
  }

  /// ソーティング・オーダー.
  public int SortingOrder {
    get { return Renderer.sortingOrder; }
    set { Renderer.sortingOrder = value; }
  }

  /// 座標(X).
  public float X {
    set {
      Vector3 pos = transform.position;
      pos.x = value;
      transform.position = pos;
    }
    get { return transform.position.x; }
  }

  /// 座標(Y).
  public float Y {
    set {
      Vector3 pos = transform.position;
      pos.y = value;
      transform.position = pos;
    }
    get { return transform.position.y; }
  }

  /// 座標を足し込む.
  public void AddPosition (float dx, float dy)
  {
    X += dx;
    Y += dy;
  }

  /// 座標を設定する.
  public void SetPosition (float x, float y)
  {
    Vector3 pos = transform.position;
    pos.Set (x, y, 0);
    transform.position = pos;
  }

  /// スケール値(X).
  public float ScaleX {
    set {
      Vector3 scale = transform.localScale;
      scale.x = value;
      transform.localScale = scale;
    }
    get { return transform.localScale.x; }
  }

  /// スケール値(Y).
  public float ScaleY {
    set {
      Vector3 scale = transform.localScale;
      scale.y = value;
      transform.localScale = scale;
    }
    get { return transform.localScale.y; }
  }

  /// スケール値を設定.
  public void SetScale (float x, float y)
  {
    Vector3 scale = transform.localScale;
    scale.Set (x, y, (x + y) / 2);
    transform.localScale = scale;
  }

  /// スケール値(X/Y).
  public float Scale {
    get {
      Vector3 scale = transform.localScale;
      return (scale.x + scale.y) / 2.0f;
    }
    set {
      Vector3 scale = transform.localScale;
      scale.x = value;
      scale.y = value;
      transform.localScale = scale;
    }
  }

  /// スケール値を足し込む.
  public void AddScale (float d)
  {
    Vector3 scale = transform.localScale;
    scale.x += d;
    scale.y += d;
    transform.localScale = scale;
  }

  /// スケール値をかける.
  public void MulScale (float d)
  {
    transform.localScale *= d;
  }

  /// 剛体.
  Rigidbody2D _rigidbody2D = null;

  public Rigidbody2D RigidBody {
    get { return _rigidbody2D ?? (_rigidbody2D = gameObject.GetComponent<Rigidbody2D> ()); }
  }

  /// 移動量を設定.
  public void SetVelocity (float direction, float speed)
  {
    Vector2 v;
    v.x = Mathf.Cos (Mathf.Deg2Rad * direction) * speed;
    v.y = Mathf.Sin (Mathf.Deg2Rad * direction) * speed;
    RigidBody.velocity = v;
  }

  /// 移動量を設定(X/Y).
  public void SetVelocityXY (float vx, float vy)
  {
    Vector2 v;
    v.x = vx;
    v.y = vy;
    RigidBody.velocity = v;
  }

  /// 移動量をかける.
  public void MulVelocity (float d)
  {
    RigidBody.velocity *= d;
  }

  /// 移動量(X).
  public float VX {
    get { return RigidBody.velocity.x; }
    set {
      Vector2 v = RigidBody.velocity;
      v.x = value;
      RigidBody.velocity = v;
    }
  }

  /// 移動量(Y).
  public float VY {
    get { return RigidBody.velocity.y; }
    set {
      Vector2 v = RigidBody.velocity;
      v.y = value;
      RigidBody.velocity = v;
    }
  }

  /// 方向.
  public float Direction {
    get {
      Vector2 v = rigidbody2D.velocity;
      return Mathf.Atan2 (v.y, v.x) * Mathf.Rad2Deg;
    }
  }

  /// 速度.
  public float Speed {
    get {
      Vector2 v = rigidbody2D.velocity;
      return Mathf.Sqrt (v.x * v.x + v.y * v.y);
    }
  }

  /// 重力.
  public float GravityScale {
    get { return RigidBody.gravityScale; }
    set { RigidBody.gravityScale = value; }
  }

  /// 回転角度.
  public float Angle {
    set { transform.eulerAngles = new Vector3 (0, 0, value); }
    get { return transform.eulerAngles.z; }
  }

  /// スプライトの設定.
  public void SetSprite (Sprite sprite)
  {
    Renderer.sprite = sprite;
  }

  /// 色設定.
  public void SetColor (float r, float g, float b)
  {
    var c = Renderer.color;
    c.r = r;
    c.g = g;
    c.b = b;
    Renderer.color = c;
  }

  /// アルファ値を設定.
  public void SetAlpha (float a)
  {
    var c = Renderer.color;
    c.a = a;
    Renderer.color = c;
  }

  /// アルファ値を取得.
  public float GetAlpha ()
  {
    var c = Renderer.color;
    return c.a;
  }

  /// アルファ値.
  public float Alpha {
    set { SetAlpha (value); }
    get { return GetAlpha (); }
  }

  /// サイズを設定.
  float _width = 0.0f;
  float _height = 0.0f;

  public void SetSize (float width, float height)
  {
    _width = width;
    _height = height;
  }

  /// スプライトの幅.
  public float SpriteWidth {
    get { return Renderer.bounds.size.x; }
  }

  /// スプライトの高さ.
  public float SpriteHeight {
    get { return Renderer.bounds.size.y; }
  }

  /// コリジョン（円）.
  CircleCollider2D _circleCollider = null;

  public CircleCollider2D CircleCollider {
    get { return _circleCollider ?? (_circleCollider = GetComponent<CircleCollider2D> ()); }
  }
  // 円コリジョンの半径.
  public float CollisionRadius {
    get { return CircleCollider.radius; }
    set { CircleCollider.radius = value; }
  }
  // 円コリジョンの有効無効を設定する.
  public bool CircleColliderEnabled {
    get { return CircleCollider.enabled; }
    set { CircleCollider.enabled = value; }
  }

  /// コリジョン（矩形）.
  BoxCollider2D _boxCollider = null;

  public BoxCollider2D BoxCollider {
    get { return _boxCollider ?? (_boxCollider = GetComponent<BoxCollider2D> ()); }
  }

  /// 矩形コリジョンの幅.
  public float BoxColliderWidth {
    get { return BoxCollider.size.x; }
    set {
      var size = BoxCollider.size;
      size.x = value;
      BoxCollider.size = size;
    }
  }

  /// 矩形コリジョンの高さ.
  public float BoxColliderHeight {
    get { return BoxCollider.size.y; }
    set {
      var size = BoxCollider.size;
      size.y = value;
      BoxCollider.size = size;
    }
  }
  // 箱コリジョンのサイズを設定する.
  public void SetBoxColliderSize (float w, float h)
  {
    BoxCollider.size.Set (w, h);
  }
  // 箱コリジョンの有効無効を設定する
  public bool BoxColliderEnabled {
    get { return BoxCollider.enabled; }
    set { BoxCollider.enabled = value; }
  }

  /// 移動して画面内に収めるようにする.
  public void ClampScreenAndMove (Vector2 v)
  {
    Vector2 min = GetWorldMin ();
    Vector2 max = GetWorldMax ();
    Vector2 pos = transform.position;
    pos += v;

    // 画面内に収まるように制限をかける.
    pos.x = Mathf.Clamp (pos.x, min.x, max.x);
    pos.y = Mathf.Clamp (pos.y, min.y, max.y);

    // プレイヤーの座標を反映.
    transform.position = pos;
  }

  /// 画面内に収めるようにする.
  public void ClampScreen ()
  {
    Vector2 min = GetWorldMin ();
    Vector2 max = GetWorldMax ();
    Vector2 pos = transform.position;
    // 画面内に収まるように制限をかける.
    pos.x = Mathf.Clamp (pos.x, min.x, max.x);
    pos.y = Mathf.Clamp (pos.y, min.y, max.y);

    // プレイヤーの座標を反映.
    transform.position = pos;
  }

  /// 画面外に出たかどうか.
  public bool IsOutside ()
  {
    Vector2 min = GetWorldMin ();
    Vector2 max = GetWorldMax ();
    Vector2 pos = transform.position;
    if (pos.x < min.x || pos.y < min.y) {
      return true;
    }
    if (pos.x > max.x || pos.y > max.y) {
      return true;
    }
    return false;
  }

  /// 画面の左下のワールド座標を取得する.
  public Vector2 GetWorldMin (bool noMergin = false)
  {
    Vector2 min = Camera.main.ViewportToWorldPoint (Vector2.zero);
    if (noMergin) {
      // そのまま返す.
      return min;
    }

    // 自身のサイズを考慮する.
    min.x += _width;
    min.y += _height;
    return min;
  }

  /// 画面右上のワールド座標を取得する.
  public Vector2 GetWorldMax (bool noMergin = false)
  {
    Vector2 max = Camera.main.ViewportToWorldPoint (Vector2.one);
    if (noMergin) {
      // そのまま返す.
      return max;
    }

    // 自身のサイズを考慮する.
    max.x -= _width;
    max.y -= _height;
    return max;
  }

  /// 消滅（メモリから削除）.
  public void DestroyObj ()
  {
    Destroy (gameObject);
  }

  /// アクティブにする.
  public virtual void Revive ()
  {
    gameObject.SetActive (true);
    Exists = true;
    Visible = true;
  }

  /// 消滅する.
  public virtual void Vanish ()
  {
    gameObject.SetActive (false);
    Exists = false;
  }
}

```