こんにちは、Unityエンジニアのオオバです。

お悩みさん
お悩みさん
  • スクリーン座標からワールド座標に変換ってどうやるの?
  • Unityに座標変換機能って用意されている?
  • オオバ
    オオバ
    本記事ではこれらの悩みを解決します。

    ゲーム開発中、スクリーン座標からワールド座標に変換することはよくありす。例えばスマホゲームの場合、画面をタップした場所に連動して3Dオブジェクトを配置したり、エフェクトを発生させたりするときです。

    「座標変換」と聞くと数学的な知識が必要そうに感じて難しく思うかもしれません。しかし、 Unityには元から便利機能がそろっているためとても簡単に座標変換できます。 結論、数学的な知識は不要です。

    ※もちろん数学知識があった方が理解は深まります

    ゲームではないですが、スクリーン座標をワールド座標に変換するスキルを身につけると次のようなペイントアプリも作れます。

    【Unity】スクリーン座標をワールド座標に変換する方法_0

    仕組みはとても簡単で、マウス座標にメッシュを生成しているだけ。興味ある方は次の記事もあわせて読んでみてください。

    👉 【Unity】ペイントアプリのようなメッシュの作り方

    座標変換できると表現の幅が広がります ので、ぜひ、この記事を最後まで読んでスクリーン座標からワールド座標に変換するスキルを手に入れてください。

    CameraコンポーネントのScreenToWorldPointを使う

    結論を簡単にまとめます。

    CameraコンポーネントのScreenToWorldPointを使う

    ①Cameraコンポーネントの「ScreenToWorldPointメソッド」を使用

    ②マウスのスクリーン座標はInput.mousePositionで取得

    ③スクリーン座標のZ値は「カメラからの距離」を指定

    結論Cameraコンポーネントの ScreenToWorldPointメソッド を使います。メソッド名からも推測できますが、引数にスクリーン座標を代入するとワールド座標が返ってくるのです。

    注意点としてはScreenToWorldPointに代入するスクリーン座標がVector3型であること。「スクリーン座標だからVector2型なのでは?」と思いがちですが、ここが落とし穴です。

    詳しくは後の章で解説していきます。

    👉DOTweenの教科書を読んでUnityアニメーションをプログラミングしてみよう!

    スクリーン座標とワールド座標とは?

    スクリーン座標からワールド座標に変換する前に、そもそもスクリーン座標とワールド座標が何なのか解説します。

    【Unity】スクリーン座標をワールド座標に変換する方法_1

    上図にスクリーン座標とワールド座標をわかりやすくまとめてみました。

    これらの座標の意味を正しく理解することでより学びが深まります。

    スクリーン座標とはマウスやタップした画面内の座標

    スクリーン座標とは結論 「マウスやタップした画面内の座標」 です。そして解像度の影響を受ける座標です。

    下の画像、UnityのGameビューはスクリーン座標そのものです。

    【Unity】スクリーン座標をワールド座標に変換する方法_2

    スクリーン座標の基準点は左下のため、Gameビューの左下が(0, 0)、右上は(Screen.width, Screen.height)となります。

    Screen.widthScreen.height は画面解像度の幅と高さです。例えば640px 480pxのモニタだとしたら、

    という値が入ります。これがスクリーン座標です。

    スクリーン座標の取得方法

    前述のとおり、 スクリーン座標はマウスやタップした画面の座標 です。実装的にはマウス座標、タップした座標を取得することでスクリーン座標を取得できるのです。

    ちなみに「マウス」と「タップ」は別物です。Unityが提供する機能として別機能として用意されています。

    👉 【Unity】クリックとタッチの違いを理解しよう

    マウス、タップそれぞれスクリーン座標を取得するサンプルコードを紹介します。

    💻ソースコード : マウスからスクリーン座標の取得するサンプル
    void Update()  
    {
        // スクリーン座標の取得  
        var screenPosition = Input.mousePosition;  
    
        // クリックしたときのスクリーン座標の取得  
        if (Input.GetMouseButtonDown(0)) {  
            screenPosition = Input.mousePosition;  
        }
    }
    

    マウスからスクリーン座標を取得したい場合は Input.mousePositionを使います。

    💻ソースコード : タップからスクリーン座標の取得するサンプル
    void Update()  
    {
        // スクリーン座標の取得  
        var screenPosition = Input.GetTouch(0).position;  
    
        // タップしたときのスクリーン座標の取得  
        if (Input.touchCount > 0 && Input.GetTouch(0).phase == TouchPhase.Began) {  
            screenPosition = Input.GetTouch(0).position;  
        }
    }
    

    スマホ画面などのタップを取得する場合はInputの「Touch」を使います。

    Touch(タップ)はUnityエディタでは動作しないため、マウス座標で置き換えます。スマホゲーム開発をする場合、以下のように UNITY_EDITORシンボル を使って開発することが多いです。

    void Update()  
    {
    #if UNITY_EDITOR
        // スクリーン座標をマウスから取得(Unityエディタ用)  
        var screenPosition = Input.mousePosition;  
    #else
        // スクリーン座標をタップから取得(スマホ実機用)  
        var screenPosition = Input.GetTouch(0).position;  
    #endif
    }
    

    このように #if〜#else〜#endif で区切られた行はコンパイル時に指定されたプラットフォームで有効化されます。

    例えば #if UNITY_EDITOR〜#else の場合は、Unityエディタで動かしているときに有効化します。逆に #else〜#endif はUnityエディタでは有効化されず、iOSやスマホに書き出されたアプリ上で有効化するのです。

    ワールド座標とは3Dオブジェクトの座標

    ワールド座標はとてもシンプルです。結論、3Dオブジェクトが配置された座標です。

    【Unity】スクリーン座標をワールド座標に変換する方法_3

    つまりHierarchyウィンドウのルート階層のオブジェクトの場合、 InspectorウィンドウのPositionの値がワールド座標 ということです。

    注意点は、Transformコンポーネントの階層化です。

    【Unity】スクリーン座標をワールド座標に変換する方法_4

    子階層のオブジェクトは親階層の座標を基準にした相対座標です。つまり、親オブジェクトの座標が原点ではない場合、子階層のオブジェクトはワールド座標と一致しません。

    ワールド座標を確認する場合は、階層関係になっていないかチェックしましょう。

    スクリーン座標とワールド座標それぞれ理解できたところで、本題の変換処理について解説していきます。

    スクリーン座標からワールド座標に変換する方法

    本題のスクリーン座標からワールド座標に変換する方法について解説します。結論、CameraコンポーネントのScreenToWorldPointを使います。ScreenToWorldPointはスクリーン座標をワールド座標に変換するメソッドです。

    ScreenToWorldPoint(スクリーン座標);  
    

    このようにスクリーン座標を代入するとワールド座標に変換されます。

    【Unity】スクリーン座標をワールド座標に変換する方法_5

    では変換したワールド座標を↑上の3Dオブジェクト(直方体)に適用してみましょう。次のソースコードをご覧ください。

    using UnityEngine;  
    
    public class ScreenToWorld : MonoBehaviour  
    {
        void Update()  
        {
            // スクリーン座標取得  
            var screenPos = Input.mousePosition;  
            // ワールド座標に変換  
            var worldPos = Camera.main.ScreenToWorldPoint(screenPos);  
            // 3Dオブジェクトの座標に代入  
            transform.localPosition = worldPos;  
        }
    }
    

    一見、良さそう見えるコードですが、落とし穴にハマっています。確認のためにUnityを実行してみましょう。

    【Unity】スクリーン座標をワールド座標に変換する方法_6

    すると画面には何も表示されません。マウスを移動ささせても特に変化は起きないのです。

    原因を探るためにシーンビューを見てみましょう。

    【Unity】スクリーン座標をワールド座標に変換する方法_7

    すると、このように3Dオブジェクトとカメラの座標が一致しているのです。3Dオブジェクトとカメラの距離が近すぎてカリングされ、 カメラには何も映らない状態 ができているというわけです。

    つまり、カメラと3Dオブジェクトの位置を離す必要があります。そこで重要なのがスクリーン座標のZ座標です。

    スクリーン座標のZ座標はカメラからの距離

    結論から話すと、ScreenToWorldPointの引数のZ値はカメラからの距離になります。

    【Unity】スクリーン座標をワールド座標に変換する方法_8

    つまり上図のようなイメージです。

    ではソースコードに変更を加えていきます。

    var mousePos = Input.mousePosition;  
    // スクリーン座標のZ値を5に変更  
    var screenPos = new Vector3(mousePos.x, mousePos.y, 5f);  
    // ワールド座標に変換  
    var worldPos = Camera.main.ScreenToWorldPoint(screenPos);  
    // ワールド座標を3Dオブジェクトの座標に適用  
    transform.localPosition = worldPos;  
    

    このように、ScreenToWorldPointのZ値を変えました。カメラから5m離れた場所を指定しています。

    【Unity】スクリーン座標をワールド座標に変換する方法_9

    すると上の通り3Dオブジェクトは表示され、マウスを追いかけるようになりました。

    平行投影カメラの場合の対応

    カメラの投影方法には「Perspective」と「Orthographic」の2種類あります。前述の説明は全て「Perspective」でした。では平行投影である「Orthographic」ではどうなるのでしょうか。

    【Unity】スクリーン座標をワールド座標に変換する方法_10

    投影方法はCameraコンポーネントのProjectionから変更できます。

    結論、OrthoGraphicもPerspectiveと考え方は同じです。以下の図は平行投影(Orthographic)をわかりやすくまとめた図です。

    【Unity】スクリーン座標をワールド座標に変換する方法_11

    平行投影はカメラからの距離に関わらず表示される大きさは変わりません。つまりスクリーン座標からワールド座標に変換する際にカメラからの距離はいくつにしても表示サイズに変更はありません。

    【Unity】スクリーン座標をワールド座標に変換する方法_12

    プログラミング自体は スクリーン座標からワールド座標に変換する方法で解説したソースコードで動きます。

    カメラからの距離はClipping Planesに注意

    Orthographic設定のカメラで、スクリーン座標からワールド座標に変更した際にカメラに映らないときは、Clipping Planesを疑いましょう。Clipping Planesとはカメラの撮影範囲です。

    【Unity】スクリーン座標をワールド座標に変換する方法_13

    Clipping Planesはカメラの「Near(手前)」と「Far(奥)」の2つのパラメータで指定します。上図の場合、Nearが0.3、Farが1000です。つまり、 カメラから30cmの場所から1000mの場所まで撮影する ということです。

    スクリーン座標からワールド座標に変換してカメラに映らない場合は、Clipping Planesの値を調整する、またはワールド座標に変換する際のZ値を調整しましょう。

    Z値 : 0Z値 : 5
    【Unity】スクリーン座標をワールド座標に変換する方法_14
    【Unity】スクリーン座標をワールド座標に変換する方法_14
    カメラに映らないカメラに映る

    スクリーン座標をワールド座標に変換する方法まとめ

    記事の内容を簡単にまとめます。

    スクリーン座標をワールド座標に変換する方法まとめ
    • ScreenToWorldPointでスクリーン座標をワールド座標に変換可能
    • スクリーン座標のZ値はカメラからの距離
    • Input.mousePositionのZ値はデフォルト0なので注意

    こんな感じです。

    座標変換はゲーム開発では避けては通れないスキルです。難しく考えてしまいがちですが、Unityはよく使う座標変換は便利機能として提供されていることが多いです。

    スクリーン座標からワールド座標に変換は、座標変換の1つですが、これ以外にもさまざまな座標変換が存在します。困ったときはまずUnity側に提供されていないか確認してみましょう。 意外とUnity側が先回りして用意されていることが多い です。

    慣れてきたら座標変換の内部処理がどうなっているのか学んでいくとより3Dプログラミングの知識が深まるのでおすすめです。

    座標変換される仕組みを理解することで、ロジックの不具合に適切に対応できますし、より最適なコードを導き出すこともできます。ぜひチャレンジしてみてください。

    この記事があなたのゲーム開発に少しでもお役に立てたら嬉しいです。

    オススメ記事
    検証環境