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 { // 版によって無いことがある。表示の切り替えだけなので、黙って見送る } } } }