読者です 読者をやめる 読者になる 読者になる

Transform.positionの代入とCS1612エラーで色々考えた

Unity

UnityでC#のプログラムを書いていて、
ひと月に一回くらいウッカリ遭遇するエラーがあって、

transform.position.x = 10.0f;

とか書いたときに、

error CS1612: Cannot modify a value type return value of `CubeBehaviour.AAA.position'. Consider storing the value in a temporary variable

という。
正しく書くのは単にpositionのx,y,zは直接代入しないで、
new Vector3したものをpositionに入れればよい。

たまに忘れたころにエラー出てきて、おっとっと。みたいな。
僕がボケてるだけかもしれないけど、
ちょいちょい引っかかるので、
これなにがどうしてこうなってんの、どうにかなんないのっていうのを考えてみた話(たいしたオチはない)

エラー自体の意味するところは以下のものとなる。
Compiler Error CS1612


プロパティを介して代入しようとしてもダメだよ、プロパティは実体はメソッドだから。
ということである。

なにがプロパティなのかというとpositionなわけで、
定義を見てみると、

namespace UnityEngine
{
public class Transform : Component, IEnumerable
{
...
public Vector3 position { get; set; }

となっている。

プロパティでエラーになるっていうのがどういうことか、
もうちょっと分かりやすくpublic変数と比べてみると、

class AAA
{
public Vector3 position { get; set; }
}

class BBB
{
public Vector3 position;
}


void Start()
{
AAA a = new AAA();
a.position.x = 1.0f; // error: CS1612

BBB b = new BBB();
b.position.x = 1.0f;
}

AAA,BBBという2つのクラスに、
外から見て同じように扱えるpositionというものがあるが、
これは上のAAAのpositionへの代入だけエラーが出る。
プロパティだから。

ここで、じゃあTransformのpositionもpublicな変数にしちゃえば良いじゃない、
とか一瞬思ってしまったわけだが、
まあ、そうはなかなかいかないというものだと思う。

内部の詳細は当然分からないけど、
Transformの内部処理、エンジン側としてはオブジェクトの情報に変更があったということは知っておかなきゃいけない。
単純に座標の更新があったというだけすればいいわけじゃなくて
dirtyフラグを立てるであるとかして、
可視判定の更新をかけるかであるとか、
最適化で動いてないものは処理をスキップするとか
物理のほうでも位置の参照をしているであるとか、もしくは衝突判定とか、
親のTransformのほうでもAABBの更新するとかどうとか。とかとか。
妄想だけど、まあ色々やることはありそう。
なので実体はメソッドであるプロパティでないと都合は悪いのだろう。

なので解決方法としては、
"自分がしっかりする"、しかない。

まあ、それでもなにか少しできることであれば
拡張メソッドを用意しておいて、
そちらを使うことを習慣にしておく、
とかがひとまず打てる手かな、、と。

using UnityEngine;
using System.Collections;

public static class TransformExtension
{
    public static void SetPosition(this Transform self, float x, float y, float z)
    {
        self.position = new Vector3(x, y, z);
    }

    public static void SetPositionX(this Transform self, float x)
    {
        self.position = new Vector3(x, self.position.y, self.position.z);
    }

    public static void SetPositionY(this Transform self, float y)
    {
        self.position = new Vector3(self.position.x, y, self.position.z);
    }

    public static void SetPositionZ(this Transform self, float z)
    {
        self.position = new Vector3(self.position.x, self.position.y, z);
    }
}

例えばこんな風に。
座標のxだけ更新したい場合とかもちょっとスッキリ書けるようになるし。
transform.SetPositionX(10.0f);
とか。
メソッド名は微妙だが。