【Unity】永久保存版! 座標を変換して狙った位置に表示する方法

クリックした位置にGameObjectを生成したい!
キャラクターの頭上にUIを表示したい!
UIの位置にパーティクルを表示したい!

そんな時、なぜか表示位置がズレて困ったことはありませんか?
ぼくはあります。下手すりゃ意味不明な場所にいっちゃって、表示すらされないことも……。

実機動作で表示がズレまくり愕然とするぼくの図(イメージ)

実はUnityには複数の座標タイプがあり、それぞれの変換方法を知らないと狙った位置に表示できません。

そこで、それぞれの座標の変換方法を網羅しました。
この記事を読めば、もう表示のズレに悩まされることはなくなります!

なお、 RectTransformやワールド座標といった、基本の座標タイプを知っている前提で話を進めます。
座標の種類すら知らない……という方は、まずこの記事で基本を学ぶことをおすすめします。

ワールド座標をRectTransformへ変換する

UIをワールド座標で表示させたい時に使います。
Unityちゃんの頭上にゲージを表示したい時などですね。
なので、UIを表示させるための座標変換です。

ワールド座標のGameObjectをUIの位置に表示させたい時ではないので、間違えないようにしてくださいね。

さっそく解説していきたいのですが、CanvasのRender ModeによってScriptが異なります

結論から言うとWorld Spaceがいちばん簡単なのですが、それぞれ別個に解説していきましょう。

Screen Space – Overlay の場合

先にScriptから。
詳細は後で解説します。

RectTransformUtility.WorldToScreenPoint

ワールド座標をスクリーン座標へ変換します。

RectTransformの座標を設定するには、スクリーン座標という特殊な座標で指定してやる必要があります。

ワールド座標 → スクリーン座標 → RectTransform.positionへ
という流れですね。

offsetというのは、単に表示位置をズラしたいとき用のオプションとして、ぼくが設定しただけです。
先ほどのgifの例で言うと、Unityちゃんの座標は足元を原点にしているので、そのままだと足元にUIが表示されてしまいます。

なので、頭上に表示するために指定した分だけズラす……などの使い方ができます。

Camera.main

「MainCamera」のタグがついたCameraを取得できます。
Scene作成時に最初からあるCameraに、デフォルトでタグが設定されています。

RectTransformUtility.WorldToScreenPoint でスクリーン座標を変換するにあたり、Cameraを指定してやる必要があります。
そりゃそうですよね。Cameraの位置が分からないと、どこを写しているかわからないので。

ただ、Screen Space – Overlayは、いかなる時でもスクリーンの最前面に表示するUIなので、一度スクリーン座標に変換さえしてしまえば、後は Cameraがどこにあろうと関係ありません。
直接RectTransformに渡してやればOKです。便利ですね。

Screen Space – Cameraの場合

ScreenSpace – Cameraの場合、UIの表示位置がCameraに左右されます。
そのため、 ワールド座標をスクリーン座標に変換した後、さらにCameraの描画角を基準とした座標へ変換する必要があります。

ワールド座標 → スクリーン座標までは、Screen Space – Overlayの時と同じなので、説明は省略します。

RectTransformUtility.ScreenPointToLocalPointInRectangle

スクリーン座標を、あるRectTransformから見たローカル座標に変換します。
なぜかローカル座標なので、RectTransformは親を取得しないとバグります。

transform.parent

親のTransformが取得できます。
なにかと便利なので覚えておきましょう。

Canvas.worldCamera

Screen Space – Cameraの時の、Render Camera(UIを描画するCamera)を取得します。

描画対象がMain Cameraとは限らないので、CanvasのRender Cameraで計算する必要があるわけですね。

out演算子

ScreenPointToLocalPointInRectangle の最後の引数に「out」という見慣れない演算子がついています。

これはUnityではなくC#の機能で、メソッド内から外へ値を渡したい時に使われます。
つまり、out pos と書けば、 ScreenPointToLocalPointInRectangle の計算結果がposに格納されます。
これは、 ScreenPointToLocalPointInRectangle を作った人がこういう仕様にしただけなので、深く考えず覚えちゃいましょう。

RectTransform.localPosition

RectTransformのローカル座標を取得します。

先ほども書いたように、 ScreenPointToLocalPointInRectangle はローカル座標を取得するので、操作対象のRectTransformのローカル座標に代入する必要があります。

World Space の場合

そもそも、UIを直接ワールド座標で配置できるようにしたのがWorld Spaceなので、そのままRectTransform.positionにワールド座標をぶち込めば完了です!

……と言いたいんですが、World Spaceの場合、スケールや角度もそのまま反映されるので、近ければ近いほど大きくなりますし、回転の影響も受けます。

でかすぎィ!

なので、事前にInspector上で確認しながらScale値を調整しましょう。
なお、体力ゲージのように常に追従させたい場合は、CanvasごとUIを子Objectにするのが手っ取り早いです。

ただし、子Objectにすると回転まで同期してしまうので、Scriptで常にCamera目線になるように修正します。

RectTransform.LookAt

指定したTransformを向くように、角度を補正するメソッドです。
こういう便利な機能が最初からあるのは、さすがUnityですね。
ちなみに、Transformにも同様のメソッドが搭載されています。

全てのRender Modeに対応する

Render ModeごとにScriptを変えるのも面倒くさいので、Switch文でひとまとめにしちゃいましょう。
以下のScriptなら、どのRender Modeにも対応できます。

ワールド座標 → RectTransformの変換方法は以上です。

Screen Space – Cameraがいちばんややこしいですね。
一度で理解できなくていいので、必要になるたびにこの記事を読み返して、なんとなく感覚がつかめればOKです。

RectTransformをワールド座標に変換する

ワールド空間のGameObjectをUIに合わせたい時に使います。
ゲームクリアのロゴ画像が出た瞬間や、体力ゲージの減りに合わせてパーティクルを飛ばしたい時などに使えますね。
なので、非UIを表示させるための座標変換です。
UIを表示させたい時ではないので注意。

Script

結論から言うと、以下のScriptで変換自体はできます。
しかし、そのままでは表示が崩れるので、対策が必要です(後述)。

なお、CanvasのRender ModeがScreen Space – Overlay以外の前提です。
Overlayは常に最前面に表示させるUIなので、今回は考えないものとします。

表示崩れの問題

ただRectTransformを変換しただけでは、上手く表示できません。
なぜなのかは、説明するより、見てもらったほうが早いです。

例えば、画面左上のアイコンにUnityちゃんを移動させるとして……

こうなります。

奥行きの概念がある以上、そのまま座標を合わせただけでは、思うように表示できません。これは2Dでも同じです。

どうするかはケースバイケースなのですが、簡単に解決策を示します。

2Dの場合

ワールド座標に変換した後、最後にZ座標に0を代入するだけで、大方上手くいきます。

UIの位置にパーティクルを飛ばす

3Dの場合

まず考えられるのは、算出されたワールド座標を、思いっきりCamera側に引き寄せてしまうことです。

かなり嘘っぽいことになりますが、平面的なパーティクルを固定Cameraで表現する分にはこれで事足ります。

ただ、Cameraが動くと嘘がバレてしまうので、そういう仕様のゲームには使えません。

次に考えられるのは、見えるようなサイズになるまでスケーリングする方法です。パーティクルの場合は、Start Sizeを変えることで大きくなります。
Unityちゃんの場合は、Scaleを10にしました。

ただこれにも弊害はあります。
UnityではScaleの値を元にRigidbodyの物理演算がなされるので、安易にScaleを大きくすると、Rigidbodyによる落下や移動、衝突に大きな影響が出てきます。

まあ正直なところ、Rigidbody付きのGameObjectをUIに重ねるという状況が思いつかないわけではありますが……。
例えば図鑑のUIの中に3Dモデルを表示したい場合などは、それ専用にスケーリングしたPrefabを用意するのがベターな方法かなとは思います。

スポンサードサーチ

クリック・タップした位置に表示する

以下の記事にまとめました。

まとめ

以上、各種座標の変換について解説しました。

正直、6000字を超えるほど説明が必要だとはぼくも思っていなかったです。
時間のある時に記事を小分けにするかもしれません。

ぼくのTwitterでは、Unityに限らずゲーム開発に関する情報を発信したり、質問に答えたりしているので、見逃したくないという方はぜひフォローをお願いします!

では、3日間にも及ぶ執筆作業で疲れ果てたので、SEKIROでもしてきます。

人気記事

実際に4年間通ったぼくが、専門学校の闇を全て暴きます。

 

記事をシェアする