using System.Reflection;
using UnityEditor;
using UnityEngine;
namespace ServiceSim.EditorTools
{
///
/// 真上の画面の隅に出す「色の意味」の凡例(見た目だけ)。
///
/// 字幕(ServiceSimLabels)は Scene ビューにだけ描くのでカメラに写らない。
/// 凡例はカメラに写す必要があるので、床の上に置く板と文字(TextMesh)で作る。
/// 当たりは付けないので NavMesh には焼かれない。
///
/// Unity の NavMesh の表示(通行者の中心が行ける場所)の切り替えもここに置く。
/// 見る人には「ロボットはこの細い帯しか通れない」と読めてしまうので、初期値は消す。
///
public static class ServiceSimLegend
{
const string RootName = "FloorPaint_Legend";
const string MenuNav = "ServiceSim/Show NavMesh(中心の範囲・確認用)";
const string NavKey = "ServiceSim.ShowNavMesh";
///
/// 真上のカメラの左上に凡例を作る。カメラの画角から位置を決めるので、
/// 店の大きさが変わってもはみ出さない。
///
public static void Build(Transform parent, Camera topCam, float aspect,
Material robotMat, Material peopleMat)
{
Clear(parent);
if (parent == null || topCam == null || !topCam.orthographic) return;
float halfZ = topCam.orthographicSize;
float halfX = halfZ * Mathf.Max(0.01f, aspect);
Vector3 c = topCam.transform.position;
// 画面の左上から少し内側
float x = c.x - halfX + 0.35f;
float z = c.z + halfZ - 0.55f;
float y = 0.05f; // 床の塗りより上
GameObject root = new GameObject(RootName);
root.transform.SetParent(parent, false);
Row(root.transform, new Vector3(x, y, z), robotMat, "ロボットがふだん走る床");
Row(root.transform, new Vector3(x, y, z - 0.75f), peopleMat, "細い通路(狭い道で入る)");
}
static void Row(Transform parent, Vector3 at, Material mat, string text)
{
// 色の板
GameObject chip = GameObject.CreatePrimitive(PrimitiveType.Quad);
chip.name = "FloorPaint_LegendChip";
Object.DestroyImmediate(chip.GetComponent());
chip.transform.SetParent(parent, false);
chip.transform.position = at;
chip.transform.rotation = Quaternion.Euler(90f, 0f, 0f);
chip.transform.localScale = new Vector3(0.55f, 0.4f, 1f);
MeshRenderer mr = chip.GetComponent();
if (mat != null) mr.sharedMaterial = mat;
mr.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;
mr.receiveShadows = false;
// 文字
GameObject label = new GameObject("FloorPaint_LegendText");
label.transform.SetParent(parent, false);
label.transform.position = at + new Vector3(0.45f, 0f, 0f);
label.transform.rotation = Quaternion.Euler(90f, 0f, 0f);
TextMesh tm = label.AddComponent();
tm.text = text;
tm.anchor = TextAnchor.MiddleLeft;
tm.alignment = TextAlignment.Left;
tm.characterSize = 0.08f;
tm.fontSize = 56;
tm.color = new Color(0.08f, 0.09f, 0.12f);
// フォントとマテリアルは必ず入れる(入れないと何も描かれない)。
// このプロジェクトは Built-in(URP の .asset は script 参照が切れていて
// currentRenderPipeline は null)なので、フォントが持つマテリアル
// (GUI/Text Shader)がそのまま使える。念のため壊れていたら探し直す
Font font = JapaneseFont();
if (font == null) font = Resources.GetBuiltinResource("LegacyRuntime.ttf");
if (font == null) font = Resources.GetBuiltinResource("Arial.ttf");
if (font != null) tm.font = font;
MeshRenderer lr = label.GetComponent();
if (lr == null) return;
Material textMat = font != null ? font.material : null;
if (textMat == null || textMat.shader == null ||
textMat.shader.name == ServiceSimRender.ErrorShaderName)
{
Shader sh = Shader.Find("GUI/Text Shader");
if (sh != null && font != null)
{
textMat = new Material(sh);
textMat.mainTexture = font.material != null ? font.material.mainTexture : null;
}
}
if (textMat != null) lr.sharedMaterial = textMat;
}
static Font _font;
static bool _fontTried;
///
/// 日本語が出るフォントを探す。組み込みの Arial には日本語が無いので、
/// OS のフォントから作る。見つからなければ組み込みのまま(文字が□になる)。
///
static Font JapaneseFont()
{
if (_fontTried) return _font;
_fontTried = true;
string[] names =
{
"Hiragino Sans", "HiraginoSans-W3", "Hiragino Kaku Gothic ProN",
"Yu Gothic", "YuGothic", "Noto Sans CJK JP", "Arial Unicode MS"
};
try { _font = Font.CreateDynamicFontFromOSFont(names, 56); }
catch { _font = null; }
if (_font == null)
Debug.LogWarning("[ServiceSim] 凡例に使える日本語フォントが見つかりませんでした。" +
"文字が□になります(ServiceSimLegend.JapaneseFont の名前を増やしてください)");
return _font;
}
public static void Clear(Transform parent)
{
if (parent == null) return;
for (int i = parent.childCount - 1; i >= 0; i--)
{
Transform t = parent.GetChild(i);
if (t.name == RootName) Object.DestroyImmediate(t.gameObject);
}
}
// ---------------- Unity の NavMesh の表示(確認用)----------------
/// Unity の NavMesh の表示(通行者の中心が行ける場所)を出すか。初期値は消す
public static bool ShowNavMesh
{
get { return EditorPrefs.GetBool(NavKey, false); }
set { EditorPrefs.SetBool(NavKey, value); Apply(); }
}
[MenuItem(MenuNav, false, 106)]
static void Toggle()
{
ShowNavMesh = !ShowNavMesh;
Debug.Log("[ServiceSim] Unity の NavMesh の表示(中心の範囲): " +
(ShowNavMesh ? "出す" : "消す") +
"\n これは「通行者の中心が行ける場所」です。" +
"人に見せる絵の床の塗り分け(ロボットの体が入れる床)とは別物です");
}
[MenuItem(MenuNav, true)]
static bool ToggleValidate()
{
Menu.SetChecked(MenuNav, ShowNavMesh);
return true;
}
[InitializeOnLoadMethod]
static void OnLoad()
{
Apply();
EditorApplication.playModeStateChanged -= OnPlayMode;
EditorApplication.playModeStateChanged += OnPlayMode;
}
///
/// 再生中は、Unity の NavMesh の表示(中心の範囲)を必ず消す。
/// 床の塗り分け(本体が行ける範囲)と重なって、卓のまわりに灰色の帯と
/// 三角形の線が出て見えてしまうため。Play を抜けたら設定どおりに戻す。
///
static void OnPlayMode(PlayModeStateChange state)
{
if (state == PlayModeStateChange.EnteredPlayMode) Set(false);
else if (state == PlayModeStateChange.EnteredEditMode) Apply();
}
///
/// Unity の NavMesh の表示を切り替える。
/// API の名前が版で変わるので、反射で探して、無ければ何もしない。
///
static void Apply() { Set(ShowNavMesh); }
static void Set(bool on)
{
try
{
System.Type t = System.Type.GetType(
"UnityEditor.AI.NavMeshVisualizationSettings, UnityEditor");
if (t == null) return;
PropertyInfo p = t.GetProperty("showNavigation",
BindingFlags.Public | BindingFlags.Static);
if (p == null) return;
p.SetValue(null, on ? 1 : 0, null);
SceneView.RepaintAll();
}
catch
{
// 版によって無いことがある。表示の切り替えだけなので、黙って見送る
}
}
}
}