Unityでゲーム開発をしている中で「できないけどできたら便利なこと個人的第一位」な
Transform.position.x = 1;
つまり、 「クラスが持つ座標などの成分のうち、任意の成分のみを書き換えること」 ってできたら便利ですよね。
今はできないこととして慣れたのでこの書き方をすることはないですが、ふとした時に「できたらいいなぁ」と思うことがあります。
同時に「どうして出来ないのだろう」とも思ったため、調べたことを自分なりにまとめてみました。
確認
まずは今回の題材について簡単に確認していきます。
Unityでゲーム開発をしていると、GameObjectが持つTransformのposition(Vector3)を書き換えることがよくあります。
private void Start()
{
// "Test"という名前のGameObjectをシーン上から探す
var go = GameObject.Find("Test");
// "Test"をx座標が2の位置に移動させる
var pos = go.transform.position;
pos.x = 2;
go.transform.position = pos;
}
上記のようにGameObjectのTransformから座標や回転情報などを受け取って変更を加えることは当然できますが、座標や回転情報のうちの どれかの成分のみを書き換えること ができたら嬉しいですよね?というのが今回の題材です。
下のコードも"Test"というGameObjectを移動しようとしていますが、こちらの場合はエラーになってしまいます。
private void Start()
{
// "Test"という名前のGameObjectをシーン上から探す
var go = GameObject.Find("Test");
// "Test"をx座標が2の位置に移動させる
go.transform.position.x = 2;
}
解説
ここからは題材となる書き方ができない理由について説明していきます。
簡単な結論としては 「どこにも代入されていないデータを変更しようとしたから」 となります。
値型と参照型の違いについては本筋とズレる部分があるので割愛しますが、 「値型(構造体)はコピー、参照型(クラス)は共有」 と考えておけば問題ないかと思います。
問題のテーマは 「データのコピー」 です。これを意識して詳しく見ていきましょう。
transform.positionはプロパティであるため get と set があります。
setはsetter、すなわち データを代入する処理 のこと、getはgetter、すなわち データを取得する処理のこと を指します。
positionの戻り値であるVector3は値型(構造体)であるため、getすると座標の値を "コピーしたデータ" を受け取ることができます。
つまりtransform.positionをgetすると、このオブジェクトが持つTransformの座標をコピーした、 "値が全く同じなだけの独立したデータ" を受け取ることになります。
以下の例を見てみましょう。
transform.positionでデータを取得し、新しく作った変数に保存してみました。
もしこの変数の中身がtransform.positionのデータを共有している場合、変数を書き換えるかtransform.positionを書き換えるかすれば、 "transform.position" と "変数" のどちらも変更される はずです。
しかし、実際は出力に差が出ているため、transform.positionで取得したデータはただのコピーであり Transformとは繋がりがなくなっている ことがわかります。
private void Start()
{
// 座標を受け取りposという変数に保存
var pos = transform.position;
// 現在のデータを出力
Debug.Log(transform.position); // (2, 4, 6)
Debug.Log(pos); // (2, 4, 6)
// 座標を書き換える
transform.position = Vector3.zero;
// posに保存したのはtransform.positionのコピーであるため出力が異なる
// もし共有データだった場合はどちらも(0, 0, 0)が出力される
Debug.Log(transform.position); // (0, 0, 0)
Debug.Log(pos); // (2, 4, 6)
}
では、問題となるtransform.position.x = 1;という書き方についてみていきましょう。
transform.position = ~ と transform.position.x = ~ のどちらも "transform.position" が左辺に書いてあるんだからsetみたいに扱えそう
というふうに考えた人は、自分含めて少なからずいそうです。
ここまで具体的でなくとも直感的に違和感を覚えた人はいるのではないでしょうか。
結論から述べると
transform.position = ~ の "transform.position" は set transform.position.x = ~ の "transform.position" は get
つまり、transform.position = ~ のtransform.positionは 書き込む用 のtransform.positionであり、transform.position.x = ~ のtransform.positionは 読み込む用 のtransform.positionであるため、同じように見えても役割が違います。
先ほどのデータのコピーの話と合わせて考えてみましょう。
transform.position.xのtransform.positionがgetだったということは、
transform.position.x = ~ はコピーしたデータの成分に代入しようとしているということになります。
すなわち、 一時的に生成されたデータに対して代入しようとしている ということですから、以下のような無意味なことをやっていることになります。
private void Start()
{
transform.position.x = 1;
// transform.positionではコピーを取得するため
// 以下のようなイメージのことをしているのと同じです
(new Vector3(2, 4, 6)).x = 1;
}
普段私たちがやっていることを考えてみましょう。
そもそもの話、座標を変更する場合はtransform.positionのsetなどによって 3成分を同時に置き換えるしか方法がありません 。
もし座標のうちx成分だけを変更したい場合は、transform.positionをgetして 一度変数に代入する と思います。
これをするのは、変数に代入した座標のx成分だけを変更したのちにこの値をtransform.positionにsetすることで、 事実上、座標のx成分だけを変更することができるから です。
private void Start()
{
// posに(2, 4, 6)を代入
var pos = transform.position;
// posのx成分に1を代入
// ここでposが(1, 4, 6)になる
pos.x = 1;
// transform.positionにposを代入
// 事実上x成分のみを変更した
transform.position = pos;
}
これまでの話を踏まえると、普段の処理を以下のように解釈することができます。
private void Start()
{
// transform.positionと同じ成分になるようなデータを新しく作成
var pos = new Vector3(2, 4, 6);
// posのx成分に1を代入
// ここでposが(1, 4, 6)になる
pos.x = 1;
// transform.positionにposを代入
transform.position = pos;
}
やっていることは同じですが、座標の成分を変更するためには変数に代入する値は transform.positionと同じということが必須 であるため、新しいデータを作り出すのではなくtransform.positionをgetしているというわけですね。
まとめ
今回の話をまとめると
- 構造体をプロパティでgetするとデータのコピーを受け取ることができる
- transform.positionはVector3型という構造体のプロパティである
- transform.position.xのtransform.positionはgetであるため、座標のコピーを受け取っている
- transform.position.xは座標のコピーを直接変更することになり、一時的なオブジェクトを変更することは出来ないためエラーになる
といったところでしょうか。
"構造体をプロパティでgetするとデータのコピーを受け取る" というところが理解できれば問題ないかと思います。
独自の解釈を述べていたのでわかりにくかったかもしれませんが、一つの発想として捉えていただければ幸いです。
余談
Unity6のRigidbody2Dには linearVelocityX と linearVelocityY というプロパティがあります。これらは名前の通り、速度のうちx成分のみ・y成分のみを変更することができるものです。
内部的には普段我々がx座標だけを変更したい時にやるようなことをやっているため、実装そのものが難しいものではありません。
2Dだけとはいえ便利なプロパティであるため、RigidbodyやTransformにも同じように追加して欲しいものですが色々不都合があるのでしょうかね…?