2017年7月3日

Unity Handles.Labelがヘンなとこに表示される問題


Handles.Labelを使用したとき、意図しない位置にもテキストが表示されてしまう問題。その対策をメモメモ…


※このページの内容の動作確認にはUnity5.3を使用しています。
シーンビューの3D空間にテキストを表示させたい場合、Handles.Labelを使うと便利ですよね。たとえば、パズルゲームのレベルデザインなんかをしていて、それぞれのマス目の位置に番号を表示させたい場合を想定してみましょう。

まずEditorフォルダの中にスクリプトを作成して…

using UnityEngine;
using System.Collections;
using UnityEditor;

[CustomEditor(typeof(Map))]
public class MapEditor : Editor {
 
 void OnSceneGUI() {
  Map map = (Map)target;
  
  GUIStyle style = new GUIStyle();
  style.alignment = TextAnchor.MiddleCenter;
  
  foreach (SquareData square in map.squares) {
   Handles.Label(square.gameObject.transform.position, square.index.ToString(), style);
  }
 }
}

こんな感じで入力すると、Mapという名前のスクリプトが管理するsquaresというマス目情報リストをひとつずつ読み込み、そのマス目のオブジェクトの位置にインデックス番号を表示させることができるようになります(Mapスクリプトがくっついたオブジェクトを選択したときのみ)。

こんな感じ

Handles.Label(square.gameObject.transform.position, square.index.ToString(), style);

ここではHandles.Label(位置, 文字列, スタイル)を使って指定した位置に文字列を描画しています。今回はスタイルを変更して中央揃えにしています(style.alignment = TextAnchor.MiddleCenter)が、別に必要なければスタイルは指定しなくても構いません。あと、文字ではなく画像なんかも表示できたりするっぽいです。詳しくはHandles.Labelのリファレンスをご覧ください。

ただ、このままだとマス目情報リスト全体を毎フレーム参照しちゃっているので、マス目が増えると処理が重くなりそうです。できれば任意のタイミングで表示するようにしたほうがいいかもしれませんが、まあとりあえず今のところはOKということで…

とにかくこれでマス目の位置を把握するのが楽になりました!よ~し、張り切ってステージを作るぞ~!

(カメラクルッ)

!?

なんか反対側にも数字が浮かんでますけど!?

なんでしょうこれ?どうやらカメラを挟んで、オブジェクトのちょうど反対側の点対称の位置に表示されているようです。直接的な解決策を調べてみたのですがよくわかりませんでした。念のためUnityのバージョンを5.6にしても変わらず。うーん、そういう仕様なのかな…

このままだと気持ち悪いので付け焼刃ではありますが一応対策をしておきます。

using UnityEngine;
using System.Collections;
using UnityEditor;

[CustomEditor(typeof(Map))]
public class MapEditor : Editor {
 
 void OnSceneGUI() {
  Map map = (Map)target;
  
  GUIStyle style = new GUIStyle();
  style.alignment = TextAnchor.MiddleCenter;
  
  Camera sceneCamera = SceneView.currentDrawingSceneView.camera;

  foreach (SquareData square in map.squares) {
   Vector3 pos = square.gameObject.transform.position;
   if(sceneCamera.WorldToScreenPoint(pos).z > 0)
    Handles.Label(pos, square.index.ToString(), style);
  }
 }
}

まず14行目の

Camera sceneCamera = SceneView.currentDrawingSceneView.camera;

これでシーンビューのカメラを取得しています。Camera.currentでもたぶん機能すると思います。

Vector3 pos = square.gameObject.transform.position;
if(sceneCamera.WorldToScreenPoint(pos).z > 0)
 Handles.Label(pos, square.index.ToString(), style);

そして次にWorldToScreenPointでマス目オブジェクトの位置をワールド座標からスクリーン座標に変換。マス目の位置がカメラの正面方向にあればzの値がプラスに、カメラの反対側にあればマイナスになるので、これを使って正面にあるマス目にのみHandles.Labelが実行されるようにしています。

あるいは、マス目とカメラとの位置関係さえわかればいいので

if(Vector3.Dot(pos - sceneCamera.transform.position, sceneCamera.transform.forward) > 0)

というようにドット積(内積)を利用して書いてもいいかもしれません。

なにはともあれ、これでカメラの反対側にあるオブジェクトに対してはテキストが描画されなくなりました。

消えました

もっと簡単に設定できる方法があるよ、とか、そもそもHandles.Labelの挙動はそういうもんだよ、みたいなこともあるとは思いますが、正直EditorやGUIの知識がほとんどないのでよくわかりません。なんとなくモヤモヤした気持ちを抱えながら寝ることにします…


0 件のコメント:

コメントを投稿