よくSFアニメにスクリーンやモニターが出てきます。オペレータが見ているモニターで、飛んでくるミサイルに「missile: 2」などと注釈がついていることがあります。ミサイルから「missile: 2」まで、線分が表示されることも。SFアニメの華です。前、そういうのを一応実装してみました。
しかし再利用しづらい感じに書いてしまったので、新しい設計でやり直しです。その過程で自分の理解に曖昧な部分があることが分かり、頑張って整理しました。結果を載せておきます。
まず用語を整理しておきます。これまで結構曖昧に済ませていて、そのためにあちこちでつまづきました。
CanvasやUI部品(PanelやImageなど)はRectTransformを持っています。サイズやどこにあるのかなどの位置情報その他が格納されたコンポーネントです。RectTransformはアンカー、ピボットというプロパティを持ちます。つまり、CanvasやPanelなどはアンカー、ピボットを持つわけです。また、CanvasもPanelもImageも、それぞれがローカル座標系を持ちます。それぞれに別々の座標軸がくっついているということです。さて、今、例えばCanvasにPanelが配置してあるとします。このとき、Panelのアンカーは、親のCanvas内の点で、Canvasのローカル座標の原点です。例えば(0.5, 0.5)と設定すればCanvasのちょうど真ん中。(0, 0)なら左下。つまり割合で表すのです。ところがPanelにも広さがありますから、Panelの位置をそのローカル座標のどこかに決めるとき、panel内のどこがそこに来ればよいのか、それを指定しておかなければなりません。それがPanelのピボットです。ピボットも割合で指定します。panelのちょうど真ん中なら(0.5, 0.5)です。ピボットには役割がもうひとつあり、例えばCanvasのピボットはCanvasのローカル座標系の原点がCanvasのどこにあるか、を表します。以上で用語の解説は終わりです。
Unityの座標変換の関数
RectTransformUtility
.ScreenPointToLocalPointInRectangle(
canvasRect, screenPt, cam, out pos)
は、スクリーン上の点screenPtをcanvasRectのピボット(インスペクタで見ると(0.5, 0.5)になっている。グレーですが)を原点としたローカル座標系でどういう点になるか、変数posに返してくれます。スクリーン上の位置screenPt(左下は(0, 0))に立体でもUI部品でもよいですが、何かが映っているとき、そのcanvasRect上のローカル座標はposだということです。ここで、panelをちょうどこの位置に表示したいとしましょう。Canvasのピボット(ローカル座標の原点)は(0.5, 0.5)なのですから、panelのアンカー(親の座標系の原点をどこにするか)を(0.5, 0.5)にセットします。ピボットはpanelの真ん中、(0.5, 0.5)でよいでしょう。こうした上で
panel.RectTransform.anchoredPosition = pos
とやればOKです。
気をつけましょう。panelのanchoredPositionは、panelのアンカー(panelの位置を表現するための、Canvasのローカル座標の原点)をさっきのCanvasのピボット(Canvasのローカル座標の原点)に一致させないと当然おかしな結果になってしまいます。panelのアンカーは、「Canvasのローカル座標の原点」というより「仮にCanvasのローカル座標の原点がここだったら」ということです。
インスペクタで見ると、アンカーのところにはMax、Minなどとあり、幅を持たせることも可能ですが、1点に絞るほうが指定した場所にUI部品を表示させるなどの目的には向くそうで、ぼくはそうしています。具体的にはMaxの欄のX, Yを同じ値(0とか0.5とか)にします。Minもそうします。ピボットはX, Yを0.5, 0.5などとするだけです。また、ピボットのX, Yは同じ値にする必要はありません。
次の図は、空間内のcube2つの間に線分を描いたものです。線分自体は空間内ではなく、スクリーンに描かれています。適当にやっていると、線分の位置がズレます。今回の記事を理解すれば、それがなくなります。
