using UnityEditor; using UnityEngine; using UnityEngine.UI; using TMPro; using BaseGames.UI; using BaseGames.UI.Theme; namespace BaseGames.Editor.UI { /// /// 输入提示控件脚手架:生成 / 放置 UI_Control_InputPrompt 预制件 /// —— 一个「按键图标 + 文字」的可复用提示(如菜单底部「Ⓐ 确定」「Ⓑ 返回」)。 /// /// 图标走已有的设备适配链路(零代码): /// 控件内 据 ActionName 经 IInputIconService 解析 /// 当前设备(键鼠/PS/Xbox/Switch)+ 改键后的实际绑定 → 自动显示正确按键图标, /// 设备切换/改键时自动刷新。前提:美术把按键 sprite 填入 ICN_Keyboard/Xbox/PlayStation/Switch 资产。 /// /// 策划/美术用法: /// 菜单「向选中节点放置 ▸ InputPrompt」放进任意 Canvas, /// 在 Icon 的 InputIconImage 填 ActionName(如 Interact/Jump),Label 填提示文字(可挂 LocalizedText)。 /// /// 菜单:BaseGames/UI/控件库/... /// public static class UIInputPromptScaffold { private const string ControlsDir = "Assets/_Game/Prefabs/UI/Controls"; private const string ThemePath = "Assets/_Game/Data/UI/Themes/UI_Theme_Default.asset"; private const string PfPrompt = "UI_Control_InputPrompt"; [MenuItem("BaseGames/UI/控件库/生成输入提示控件(UI_Control_InputPrompt)")] public static void GeneratePrompt() { EnsureFolder(ControlsDir); BuildPrompt(); AssetDatabase.SaveAssets(); AssetDatabase.Refresh(); Debug.Log($"[UIInputPrompt] 已生成 {ControlsDir}/{PfPrompt}.prefab。\n" + "用法:菜单「向选中节点放置 ▸ InputPrompt」放进 UI,Icon 填 ActionName、Label 填文字。\n" + "图标随当前设备自动切换;需美术把按键 sprite 填入 ICN_Keyboard/Xbox/PlayStation/Switch 资产。"); } [MenuItem("BaseGames/UI/控件库/向选中节点放置 ▸ InputPrompt")] private static void PlacePrompt() { string path = $"{ControlsDir}/{PfPrompt}.prefab"; var prefab = AssetDatabase.LoadAssetAtPath(path); if (prefab == null) { EditorUtility.DisplayDialog("控件库", $"未找到预制件:{path}\n请先执行「生成输入提示控件(UI_Control_InputPrompt)」。", "确定"); return; } Transform parent = Selection.activeTransform; if (parent == null) { var canvas = Object.FindObjectOfType(); parent = canvas != null ? canvas.transform : null; } var instance = (GameObject)PrefabUtility.InstantiatePrefab(prefab, parent); if (instance == null) return; Undo.RegisterCreatedObjectUndo(instance, $"Place {PfPrompt}"); Selection.activeGameObject = instance; EditorGUIUtility.PingObject(instance); } // ── 构建 ───────────────────────────────────────────────────────────── private static void BuildPrompt() { var theme = AssetDatabase.LoadAssetAtPath(ThemePath); // 根:横向布局(图标 + 文字),自适应内容宽度 var root = NewUI(PfPrompt, 120f, 44f); var h = root.AddComponent(); h.spacing = 8f; h.childAlignment = TextAnchor.MiddleLeft; h.childForceExpandWidth = false; h.childForceExpandHeight = false; h.childControlWidth = true; h.childControlHeight = true; var fitter = root.AddComponent(); fitter.horizontalFit = ContentSizeFitter.FitMode.PreferredSize; fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize; // 图标:Image + InputIconImage(同物体,InputIconImage 要求 Image) var iconGo = NewUIChild(root.transform, "Icon", out _); var iconImg = iconGo.AddComponent(); iconImg.preserveAspect = true; iconImg.sprite = Knob(); // 编辑期占位图;运行时由 InputIconImage 替换为当前设备按键图(无图则自动隐藏) var iconLe = iconGo.AddComponent(); iconLe.preferredWidth = 40f; iconLe.preferredHeight = 40f; iconLe.minWidth = 32f; iconLe.minHeight = 32f; var iconComp = iconGo.AddComponent(); SetEnum(iconComp, "_mode", (int)InputIconImage.LookupMode.ByActionName); SetString(iconComp, "_actionName", "Interact"); // 占位 ActionName,策划按需改 // 文字:提示标签(可挂 LocalizedText;默认占位) var lblGo = NewUIChild(root.transform, "Label", out _); var lbl = lblGo.AddComponent(); lbl.text = "提示"; lbl.fontSize = 24f; lbl.alignment = TextAlignmentOptions.MidlineLeft; lbl.color = new Color(0.80f, 0.80f, 0.82f, 1f); lbl.raycastTarget = false; var lblLe = lblGo.AddComponent(); lblLe.preferredHeight = 40f; SetEnum(lblGo.AddComponent(), "_kind", (int)UIThemeRoleKind.Text_Secondary); _ = theme; // 配色随就近父级 UIThemeApplier;本控件不自带 applier,避免嵌套重复应用 string path = $"{ControlsDir}/{PfPrompt}.prefab"; PrefabUtility.SaveAsPrefabAsset(root, path); Object.DestroyImmediate(root); } // ── 助手 ───────────────────────────────────────────────────────────── private static GameObject NewUI(string name, float w, float h) { var go = new GameObject(name, typeof(RectTransform)); ((RectTransform)go.transform).sizeDelta = new Vector2(w, h); return go; } private static GameObject NewUIChild(Transform parent, string name, out RectTransform rt) { var go = new GameObject(name, typeof(RectTransform)); rt = (RectTransform)go.transform; rt.SetParent(parent, false); return go; } private static void SetEnum(Component c, string prop, int value) { var so = new SerializedObject(c); var p = so.FindProperty(prop); if (p != null) { p.enumValueIndex = value; so.ApplyModifiedPropertiesWithoutUndo(); } } private static void SetString(Component c, string prop, string value) { var so = new SerializedObject(c); var p = so.FindProperty(prop); if (p != null) { p.stringValue = value; so.ApplyModifiedPropertiesWithoutUndo(); } } private static Sprite Knob() => AssetDatabase.GetBuiltinExtraResource("UI/Skin/Knob.psd"); private static void EnsureFolder(string dir) { string[] parts = dir.Split('/'); string cur = parts[0]; for (int i = 1; i < parts.Length; i++) { string next = $"{cur}/{parts[i]}"; if (!AssetDatabase.IsValidFolder(next)) AssetDatabase.CreateFolder(cur, parts[i]); cur = next; } } } }