[unity]面倒な座標の記述を楽にする方法

座標移動の記述は冗長

例えばプログラムで、UI/Image の座標を 0~99 まで繰り返す場合。

Image image;

void Start()
{
  image = GetComponent<Image>();
}

void Update()
{
  Vector3 trans = image.gameObject.transform.localPosition;
  int x = (int)trans.x;
  if (++x >= 100)
  {
    x = 0;
  }
  trans.x = x;
  image.gameObject.transform.localPosition = trans;
}

こんな感じで、image クラスを取っておいて、そこからアクセスする事がわたしは多いのですが、毎回記述するのは…。
GameObject を取っといてもいいのですが、

Image image;
GameObject imageObject;

void Start()
{
  image = GetComponent<Image>();
  imageObject = image.gameObject;
}

void Update()
{
  Vector3 trans = imageObject.transform.localPosition;
  int x = (int)trans.x;
  if (++x >= 100)
  {
    x = 0;
  }
  trans.x = x;
  imageObject.transform.localPosition = trans;
}

image は sprite 変更用(など)に取っておき、座標用に imageObject …。
2 つも取っておく手間の割に、Update の記述もたいして楽になっていません。
じゃあ transform 取っとけば! と思うのですが、transform はライブラリの都合で取っておけない。

希望

本当は、こんな風に書きたいところ。

Image image;

void Start()
{
  image = GetComponent<Image>();
}

void Update()
{
  int x = (int)image.GetX();
  if (++x >= 100)
  {
    x = 0;
  }

  image.SetX(x);
}

解決策

C# の 拡張メソッド を使います。
これを使えば、あたかも Image(イメージに限らずどんなクラスもOK)から呼び出すようなメソッドを追加することができます。
クラス名は正直あまり意味なくて(どんな名前でもいい)、public static にすることと、第1引数に this Image self と書いてあるのがポイントです。

using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// image extensions
/// </summary>
public static class ImageExtensions
{
  // 座標取得
  public static float GetX(this Image self)
  {
      return self.gameObject.transform.localPosition.x;
  }

  public static float GetY(this Image self)
  {
      return self.gameObject.transform.localPosition.y;
  }

  // 座標設定
  public static void SetX(this Image self, float x)
  {
      Vector3 trans = self.gameObject.transform.localPosition;
      trans.x = x;
      self.gameObject.transform.localPosition = trans;
  }

  public static void SetY(this Image self, float y)
  {
      Vector3 trans = self.gameObject.transform.localPosition;
      trans.y = y;
      self.gameObject.transform.localPosition = trans;
  }

  public static void SetXY(this Image self, float x, float y)
  {
      Vector3 trans = self.gameObject.transform.localPosition;
      trans.x = x;
      trans.y = y;
      self.gameObject.transform.localPosition = trans;
  }
}

中身を見ての通り、面倒な部分を拡張メソッド内で済ませています。
このため、大量に 3D を移動…みたいな用途では速度ロスが問題になるかもしれませんが、2D を動かす程度なら全然問題ないと思います。

ついでにサイズ変更

Image RectTransform であることに注意します。こんな感じで書きましょう。
width や height だけ書き換えるメソッドが必要であれば、自分で追加してください。

public static class ImageExtensions
{
  public static void SetSize(this Image self, float width, float height)
  {
      var size = self.rectTransform.sizeDelta;
      size.x = width;
      size.y = height;
      self.rectTransform.sizeDelta = size;
  }
}

Set Native Size ボタンのような機能も

もちろん作成可能です。先ほどの SetSize があること前提です。
Sprite の登録がない場合は自動的に 100×100 にしていますが、お好みで変えましょう。

public static class ImageExtensions
{
  public static void SetNativeSize(this Image self)
  {
      if (self.sprite != null)
      {
          SetSize(self, self.sprite.rect.width, self.sprite.rect.height);
      }
      else
      {
          SetSize(self, 100, 100);
      }
  }
}

全部盛り

一応ここまで書いたものをすべて入れたコードも載せておきます。
他にも自分が使いやすいものを登録していくと、コードがスッキリしてよさそうですね。

using UnityEngine;
using UnityEngine.UI;

/// <summary>
/// image extensions
/// </summary>
public static class ImageExtensions
{
  // 座標取得
  public static float GetX(this Image self)
  {
      return self.gameObject.transform.localPosition.x;
  }

  public static float GetY(this Image self)
  {
      return self.gameObject.transform.localPosition.y;
  }

  // 座標設定
  public static void SetX(this Image self, float x)
  {
      Vector3 trans = self.gameObject.transform.localPosition;
      trans.x = x;
      self.gameObject.transform.localPosition = trans;
  }

  public static void SetY(this Image self, float y)
  {
      Vector3 trans = self.gameObject.transform.localPosition;
      trans.y = y;
      self.gameObject.transform.localPosition = trans;
  }

  public static void SetXY(this Image self, float x, float y)
  {
      Vector3 trans = self.gameObject.transform.localPosition;
      trans.x = x;
      trans.y = y;
      self.gameObject.transform.localPosition = trans;
  }

  // サイズ設定
  public static void SetSize(this Image self, float width, float height)
  {
      var size = self.rectTransform.sizeDelta;
      size.x = width;
      size.y = height;
      self.rectTransform.sizeDelta = size;
  }

  // スプライトの元サイズにする
  public static void SetNativeSize(this Image self)
  {
      if (self.sprite != null)
      {
          SetSize(self, self.sprite.rect.width, self.sprite.rect.height);
      }
      else
      {
          SetSize(self, 100, 100);
      }
  }
}

どこかの会社の拡張で Component.SafeGetComponent<T>()(コンポーネントがなかったら強制的に追加して取得)なんてのが鬼推奨されていましたが、個人的には使いませんでした…ただ、本当に右も左もわからないプログラマがたとえミスをしようが吸収する、という意味合いではいいメソッドだったかもしれません。