土屋つかさの技術ブログは今か無しか

土屋つかさが主にプログラミングについて語るブログです。

#uniy transform.position.xに直接値を代入できない理由

GameObjectを制御する時に、ある座標軸だけ値を更新したい時がありますが(ありますよね?)、以下のコードはコンパイル出来ません*1

transform.position.x = 0.0f;

 transrate()を使うこともできますが、その場合相対座標指定になってしまいます *2 。一つの座標軸を絶対座標で更新したい場合は以下のように一度Vector3構造体に値をコピーして、要素を更新し、その構造体を書き戻します。

Vector3 pos = transform.position;
pos.x = 0.0f;
transform.position = pos;

 毎フレームVector3変数を生成するのが嫌な場合、クラスメンバを持っておく事になるでしょう(これもっと上手く書けないものかと毎回思っています。上手い方法あったら教えてください><)。

 そもそもの話として、どうして最初の書き方はコンパイルを通らないのでしょう? もっと言えば「transform.position.xを直接更新できないのに、transform.positionをまるごとなら更新できるのは何故なのか」が、ぱっと見の印象として不思議です。

 これはC#の言語仕様に起因していまして、これについて土屋自身がちゃんとは理解してなかったので、ここでまとめておきます*3

前提1:戻り値と値渡しについて

 C#では、メソッドからの戻り値は基本的に値渡しになります。メソッドが値を返す場合、それはヒープメモリにスタックされ、呼び出し元はそのヒープメモリから値を読み取るわけです。これは戻り値が構造体の場合も同じです。

前提2:transform.positionについて

 transform.positionはプロパティで、値の取得時にはgetter、代入時にはsetterが呼び出されます。getter/setterはprivate変数であるVector3 構造体にアクセスして必要な処理を行います。以下このprivate変数の事を「内部position構造体」と呼ぶことにします。

transform.position.xに直接値を代入できない理由。

 transform.position.xが評価された場合、まずgetterが内部position構造体を返し、その値はヒープメモリに格納されます。transform.position.xはヒープメモリ上のx要素を返すわけですが、仮にそこに値を代入できたとしても、それはヒープメモリ上の値が書き換わるだけで意図した結果になりません。そのためコンパイルエラーになるわけです*4

transform.positionまるごとであれば更新できる理由

 transform.positionにVector3構造体を代入した場合、setterは値渡しでそのデータを受け取り、内部position構造体を上書きします。

おわり

 整理して考えればそりゃそうかと思うのですが、直感的に把握しづらくもやもやする部分でした。勘違いや間違いなどありましたら指摘頂けると助かります。

参考リンク

今回の記事は以下のエントリを元にしました。
https://forum.unity.com/threads/set-value-on-gameobject-transform-position-x-with-c.66768/

*1:CS1612エラー。下記リンクの後半の説明に該当 https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/compiler-messages/cs1612

*2: 同じく相対移動ですが、transform.position += pos;という書き方もあります

*3:以下ヒープメモリ云々と書いてますがこれは土屋の想像で実際どういう処理なのかは知りません

*4:あるいは、transform.position.xが値として評価されるため、そもそも左辺に配置出来ないと判断されるのかも。どっちなのか知らない