いぬおさんのおもしろ数学実験室

おいしい紅茶でも飲みながら数学、物理、工学、プログラミング、そして読書を楽しみましょう

Unityでスクリーン座標から空間内の座標を求める

 3Dのシューティングゲームを作るとき、敵機が視界(スクリーン)から消えたときにその敵機を削除するようにプログラミングします(削除せずに放っておくと、見えないだけで存在し続けます。実際にはちょっと見えなくなって、軌道の関係ですぐまた視界に入ることもあるので、スクリーンに映る限界からある程度以上離れたら削除します)。PCのリソースは有限ですから、見えないところにある戦闘機を動かしているわけにはいかないのです。戦闘機は空間内を動くのですから、もちろん空間内の座標(3次元)を使います。「スクリーンの外に出たかどうか?」も空間内の座標を用いて判断します。となると、スクリーンの端の、空間内の座標が分からなければなりません。よい関数 Camera.main.ScreenToWorldPoint() があるのですが、Unityのサイトを始めあちこち見ても使い方がよく分かりませんでした。いくつか実験をして明らかになったことをまとめておきます。

 Unityではスクリーンのサイズが Screen.widht, Screen.height で得られます。スクリーンの左下のスクリーン座標は(0,0)、右上は(Screen.width - 1, Screen.height - 1)です。カメラは空間内でどちらを向いているのか、どこにあるのかいろいろです(インスペクタのRotation、Positionで決まります)。そのカメラから空間内の距離1のところにスクリーンがあります。ここに映るものがPCやスマホで見えるわけです。このスクリーンの四隅の空間座標を求めることができます。例えばスクリーンの右上の空間座標は

Vector3 v = new Vector3(Screen.width - 1, Screen.height - 1, 1);

Vector3 pos = Camera.main.ScreenToWorldPoint(v);

で得られます。この関数は引数は3次元ベクトル v で、v の x, y 成分でスクリーン座標を指定し、z成分でカメラからの距離を指定します。

 他の例を。カメラから距離4の平面上にある点で、その点のスクリーン座標が(30,40)なら、空間座標は

Vector3 v = new Vector3(30, 40, 4);

Vector3 pos = Camera.main.ScreenToWorldPoint(v);

で得られます。

f:id:Inuosann:20210223082018p:plain

 一般的な形にまとめると

スクリーン座標が(x, y)、カメラからの距離がdepthである平面上にある点の空間座標は

Vector3 v = new Vector3(x, y, depth);

Vector3 pos = Camera.main.ScreenToWorldPoint(v);

で計算できます。関数の引数である(x, y, depth)は、空間内の点の座標ではありません。(x, y)がスクリーン座標、depthはカメラから問題の空間点の乗っている平面までの距離です。この「距離」というのはカメラから平面まで下ろした垂線の長さです。

 これで自由自在です。3Dのシーンで、スクリーンのどこかをマウスでクリックし、対応する空間内の点に何かを表示させる、といったことができるようになります。やはりプログラミングは面白いです。

 

2021年2月24日(水) 追記:

従って、カメラの位置は(0f, 0f, 0f)、Rotationは(0f, 0f, 0f)であるとき(要するにカメラは初期状態であるとき)、

Vector3 v = new Vector3(20, 50, 4);

Vector3 pos = Camera.main.ScreenToWorldPoint(v);

を実行すると pos のz成分は引数として与えた4のままになります。つまりpos.z = 4 です。念のため。