地图系统
This commit is contained in:
656
Assets/_Game/Scripts/Editor/UI/MapUIScaffoldWizard.cs
Normal file
656
Assets/_Game/Scripts/Editor/UI/MapUIScaffoldWizard.cs
Normal file
@@ -0,0 +1,656 @@
|
||||
using System.Collections.Generic;
|
||||
using TMPro;
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.UI;
|
||||
using BaseGames.UI;
|
||||
using BaseGames.UI.Menus;
|
||||
using BaseGames.World.Map;
|
||||
using BaseGames.Localization;
|
||||
|
||||
namespace BaseGames.Editor.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// 地图 UI 脚手架(对照 <see cref="HUDScaffoldWizard"/>)。
|
||||
/// 在当前活动场景生成完整的地图 UI 层级与预制,并按规范绑定引用:
|
||||
/// <list type="bullet">
|
||||
/// <item>Cell / Pin / ExitConnector 预制 + MapPinConfig 占位资产</item>
|
||||
/// <item>HUD 下小地图(MinimapHUD + MinimapInputHandler)</item>
|
||||
/// <item>Map Canvas 下全屏地图(MapPanel + MapInputHandler,PanelStack 管理)</item>
|
||||
/// <item>传送确认框(ConfirmDialogController)+ MapTeleportConfirmController(接 MapPanel)</item>
|
||||
/// <item>登记 UIManager._panels[Map],绑定 EVT_MapOpen</item>
|
||||
/// </list>
|
||||
/// 执行路径:BaseGames ▸ Scene ▸ Setup ▸ Scaffold Map UI
|
||||
/// <para>占位为纯色块/空 Sprite,美术后续替换;运行依赖 [GameManagers] 下的
|
||||
/// MapManager / MapPlayerTracker / MapPinManager / TeleportService 已存在(另由 Persistent 脚手架搭建)。</para>
|
||||
/// </summary>
|
||||
public static class MapUIScaffoldWizard
|
||||
{
|
||||
private const string UiPrefabDir = "Assets/_Game/Prefabs/UI";
|
||||
private const string MapDataDir = "Assets/_Game/Data/Map";
|
||||
|
||||
[MenuItem("BaseGames/Scene/Setup/Scaffold Map UI", priority = 204)]
|
||||
public static void ScaffoldMapUI()
|
||||
{
|
||||
var report = new List<string>();
|
||||
Undo.SetCurrentGroupName("Scaffold Map UI");
|
||||
int undoGroup = Undo.GetCurrentGroup();
|
||||
|
||||
// ── 共享资产:Cell / Pin / Exit 预制 + PinConfig ──────────────────
|
||||
MapPinConfigSO pinConfig = EnsurePinConfig(report);
|
||||
GameObject cellPrefab = EnsureCellPrefab(report);
|
||||
GameObject pinPrefab = EnsureSimpleImagePrefab("UI_Map_Pin",
|
||||
new Color32(0xF0, 0xC0, 0x40, 0xFF), new Vector2(14, 14), report);
|
||||
GameObject exitPrefab = EnsureSimpleImagePrefab("UI_Map_ExitConnector",
|
||||
new Color32(0xC0, 0xC0, 0xC0, 0xCC), new Vector2(8, 16), report);
|
||||
|
||||
Sprite playerDotSprite = null; // 占位用纯色,留空即可
|
||||
|
||||
// ── 小地图(HUD Canvas 下)────────────────────────────────────────
|
||||
GameObject hudCanvas = FindHudCanvas();
|
||||
if (hudCanvas == null)
|
||||
report.Add("未找到 HUD Canvas,请先执行 BaseGames/Scene/Setup/Scaffold HUD Canvas;本次跳过小地图/区域名搭建。");
|
||||
else
|
||||
{
|
||||
// 按需求不搭建右上角小地图,只保留全屏大地图;保留进入区域时的区域名横幅。
|
||||
BuildRegionBanner(hudCanvas, report);
|
||||
report.Add("已跳过小地图(MinimapHUD)搭建:按需求只保留全屏大地图。");
|
||||
}
|
||||
|
||||
// ── 全屏地图(独立 Map Canvas,PanelStack 管理)───────────────────
|
||||
GameObject mapPanelRoot = BuildFullMap(cellPrefab, exitPrefab, pinPrefab, pinConfig, report);
|
||||
|
||||
// ── 传送确认框 + 控制器 ───────────────────────────────────────────
|
||||
BuildTeleportConfirm(mapPanelRoot, report);
|
||||
|
||||
// ── 登记 UIManager._panels[Map] + 绑定 EVT_MapOpen ────────────────
|
||||
RegisterMapPanelWithUIManager(mapPanelRoot, report);
|
||||
|
||||
Undo.CollapseUndoOperations(undoGroup);
|
||||
AssetDatabase.SaveAssets();
|
||||
MarkDirtyAndLog("Map UI 脚手架", mapPanelRoot != null ? mapPanelRoot : hudCanvas, report);
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// 预制 / 资产
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>创建(或复用)MapRoomCellUI 预制:_bg 为 Raycast Target(可点击传送),其余子图不挡射线。</summary>
|
||||
private static GameObject EnsureCellPrefab(List<string> report)
|
||||
{
|
||||
string path = $"{UiPrefabDir}/UI_Map_RoomCell.prefab";
|
||||
var existing = AssetDatabase.LoadAssetAtPath<GameObject>(path);
|
||||
if (existing != null) return existing;
|
||||
EnsureFolder(UiPrefabDir);
|
||||
|
||||
var root = new GameObject("UI_Map_RoomCell", typeof(RectTransform));
|
||||
((RectTransform)root.transform).sizeDelta = new Vector2(32, 32);
|
||||
|
||||
// 背景(可见 + 可点击)
|
||||
var bg = MakeImage(root.transform, "BG", new Color(1f, 1f, 1f, 1f), new Vector2(32, 32), raycast: true);
|
||||
// 轮廓(RawImage,默认禁用)
|
||||
var outlineGo = MakeChild(root.transform, "Outline", new Vector2(32, 32));
|
||||
var outline = outlineGo.AddComponent<RawImage>();
|
||||
outline.raycastTarget = false; outline.enabled = false;
|
||||
// 图标 / 高亮 / 雾 / 传送标记(均不挡射线,运行时按需启用)
|
||||
var icon = MakeImage(root.transform, "Icon", new Color(1, 1, 1, 1), new Vector2(20, 20), raycast: false); icon.enabled = false;
|
||||
var highlight = MakeImage(root.transform, "Highlight", new Color(1f, 0.9f, 0.2f, 1f),new Vector2(34, 34), raycast: false); highlight.enabled = false;
|
||||
var fog = MakeImage(root.transform, "Fog", new Color(0, 0, 0, 0.85f), new Vector2(32, 32), raycast: false); fog.enabled = false;
|
||||
var teleport = MakeImage(root.transform, "TeleportMarker", new Color(0.3f, 0.8f, 1f, 1f), new Vector2(12, 12), raycast: false); teleport.enabled = false;
|
||||
|
||||
var cell = root.AddComponent<MapRoomCellUI>();
|
||||
AssignRef(cell, "_bg", bg);
|
||||
AssignRef(cell, "_icon", icon);
|
||||
AssignRef(cell, "_outlineImage", outline);
|
||||
AssignRef(cell, "_highlight", highlight);
|
||||
AssignRef(cell, "_fogOverlay", fog);
|
||||
AssignRef(cell, "_teleportMarker",teleport);
|
||||
|
||||
var prefab = PrefabUtility.SaveAsPrefabAsset(root, path);
|
||||
Object.DestroyImmediate(root);
|
||||
report.Add($"已创建 Cell 预制:{path}(占位纯色,美术可替换)。");
|
||||
return prefab;
|
||||
}
|
||||
|
||||
private static GameObject EnsureSimpleImagePrefab(string name, Color color, Vector2 size, List<string> report)
|
||||
{
|
||||
string path = $"{UiPrefabDir}/{name}.prefab";
|
||||
var existing = AssetDatabase.LoadAssetAtPath<GameObject>(path);
|
||||
if (existing != null) return existing;
|
||||
EnsureFolder(UiPrefabDir);
|
||||
|
||||
var go = new GameObject(name, typeof(RectTransform));
|
||||
((RectTransform)go.transform).sizeDelta = size;
|
||||
var img = go.AddComponent<Image>();
|
||||
img.color = color;
|
||||
img.raycastTarget = false;
|
||||
|
||||
var prefab = PrefabUtility.SaveAsPrefabAsset(go, path);
|
||||
Object.DestroyImmediate(go);
|
||||
report.Add($"已创建预制:{path}(占位纯色,美术可替换)。");
|
||||
return prefab;
|
||||
}
|
||||
|
||||
private static MapPinConfigSO EnsurePinConfig(List<string> report)
|
||||
{
|
||||
string path = $"{MapDataDir}/MapPinConfig.asset";
|
||||
var existing = AssetDatabase.LoadAssetAtPath<MapPinConfigSO>(path);
|
||||
if (existing != null) return existing;
|
||||
EnsureFolder(MapDataDir);
|
||||
|
||||
var so = ScriptableObject.CreateInstance<MapPinConfigSO>();
|
||||
AssetDatabase.CreateAsset(so, path);
|
||||
report.Add($"已创建占位 MapPinConfig:{path}(_entries 为空,请配置 PinType→Sprite 映射)。");
|
||||
return so;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// 小地图(HUD)
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
private static void BuildMinimap(GameObject hudCanvas, GameObject cellPrefab,
|
||||
GameObject pinPrefab, MapPinConfigSO pinConfig, List<string> report)
|
||||
{
|
||||
Transform hudRoot = hudCanvas.transform.Find("HUDRoot") ?? hudCanvas.transform;
|
||||
|
||||
var minimapGo = GetOrCreateChild(hudRoot, "Minimap").gameObject;
|
||||
var mmRect = minimapGo.GetComponent<RectTransform>() ?? minimapGo.AddComponent<RectTransform>();
|
||||
AnchorTopRight(mmRect, new Vector2(180, 180), new Vector2(-16, -16));
|
||||
// 边框底图(可见,便于定位;美术可替换/移除)
|
||||
var frame = minimapGo.GetComponent<Image>() ?? minimapGo.AddComponent<Image>();
|
||||
frame.color = new Color(0f, 0f, 0f, 0.35f);
|
||||
frame.raycastTarget = false;
|
||||
|
||||
// 带 RectMask2D 的内容容器(cell 在此平移)
|
||||
var viewportGo = GetOrCreateChild(minimapGo.transform, "Viewport").gameObject;
|
||||
var vpRect = viewportGo.GetComponent<RectTransform>() ?? viewportGo.AddComponent<RectTransform>();
|
||||
StretchFill(vpRect, 6f);
|
||||
if (viewportGo.GetComponent<RectMask2D>() == null) viewportGo.AddComponent<RectMask2D>();
|
||||
|
||||
// 玩家圆点(在容器内)
|
||||
var playerDot = MakeImage(viewportGo.transform, "PlayerDot", new Color(1f, 0.25f, 0.25f, 1f), new Vector2(8, 8), raycast: false);
|
||||
|
||||
var hud = GetOrAddComponent<MinimapHUD>(minimapGo);
|
||||
var input = GetOrAddComponent<MinimapInputHandler>(minimapGo);
|
||||
|
||||
AssignRef(hud, "_cellPrefab", cellPrefab.GetComponent<MapRoomCellUI>());
|
||||
AssignRef(hud, "_cellContainer",vpRect);
|
||||
AssignRef(hud, "_playerDot", playerDot);
|
||||
AssignRef(hud, "_pinPrefab", pinPrefab.GetComponent<Image>());
|
||||
AssignRef(hud, "_pinConfig", pinConfig);
|
||||
AssignMapIcons(hud, report);
|
||||
AssignAsset(input, "_inputReader", report, true, "InputReader");
|
||||
|
||||
report.Add("Minimap 已搭建于 HUDRoot(右上角占位框)。美术可调整位置/尺寸/边框。");
|
||||
}
|
||||
|
||||
/// <summary>进入新区域时屏幕中央渐显区域名横幅(RegionNameDisplay,挂 HUDRoot)。</summary>
|
||||
private static void BuildRegionBanner(GameObject hudCanvas, List<string> report)
|
||||
{
|
||||
Transform hudRoot = hudCanvas.transform.Find("HUDRoot") ?? hudCanvas.transform;
|
||||
var bannerGo = GetOrCreateChild(hudRoot, "RegionNameBanner").gameObject;
|
||||
var br = bannerGo.GetComponent<RectTransform>() ?? bannerGo.AddComponent<RectTransform>();
|
||||
br.anchorMin = br.anchorMax = new Vector2(0.5f, 0.72f);
|
||||
br.pivot = new Vector2(0.5f, 0.5f);
|
||||
br.sizeDelta = new Vector2(640f, 90f);
|
||||
br.anchoredPosition = Vector2.zero;
|
||||
if (bannerGo.GetComponent<CanvasGroup>() == null) bannerGo.AddComponent<CanvasGroup>();
|
||||
|
||||
var txt = MakeText(bannerGo.transform, "RegionText", "区域名");
|
||||
txt.fontSize = 48;
|
||||
StretchFill((RectTransform)txt.transform, 0f);
|
||||
|
||||
var rnd = GetOrAddComponent<RegionNameDisplay>(bannerGo);
|
||||
AssignRef(rnd, "_regionText", txt);
|
||||
AssignAsset(rnd, "_onRegionChanged", report, false, "EVT_RegionChanged");
|
||||
report.Add("RegionNameDisplay(进入区域时渐显区域名横幅)已搭建于 HUDRoot。");
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// 全屏地图(独立 Canvas)
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
private static GameObject BuildFullMap(GameObject cellPrefab, GameObject exitPrefab,
|
||||
GameObject pinPrefab, MapPinConfigSO pinConfig, List<string> report)
|
||||
{
|
||||
GameObject canvasGo = GetOrCreateMapCanvas("Map Canvas", 25);
|
||||
|
||||
var panelGo = GetOrCreateChild(canvasGo.transform, "MapPanel").gameObject;
|
||||
StretchFill(panelGo.GetComponent<RectTransform>() ?? panelGo.AddComponent<RectTransform>(), 0f);
|
||||
// 半透明全屏底
|
||||
var panelBg = panelGo.GetComponent<Image>() ?? panelGo.AddComponent<Image>();
|
||||
panelBg.color = new Color(0.05f, 0.05f, 0.07f, 0.92f);
|
||||
|
||||
// ScrollView → Viewport(RectMask2D) → RoomContainer(content)
|
||||
var scrollGo = GetOrCreateChild(panelGo.transform, "ScrollView").gameObject;
|
||||
StretchFill(scrollGo.GetComponent<RectTransform>() ?? scrollGo.AddComponent<RectTransform>(), 40f);
|
||||
var scrollRect = GetOrAddComponent<ScrollRect>(scrollGo);
|
||||
scrollRect.horizontal = true; scrollRect.vertical = true;
|
||||
scrollRect.movementType = ScrollRect.MovementType.Clamped;
|
||||
scrollRect.scrollSensitivity = 0f; // 缩放/平移由 MapInputHandler 处理
|
||||
|
||||
var viewportGo = GetOrCreateChild(scrollGo.transform, "Viewport").gameObject;
|
||||
var vpRect = viewportGo.GetComponent<RectTransform>() ?? viewportGo.AddComponent<RectTransform>();
|
||||
StretchFill(vpRect, 0f);
|
||||
if (viewportGo.GetComponent<RectMask2D>() == null) viewportGo.AddComponent<RectMask2D>();
|
||||
var vpImg = viewportGo.GetComponent<Image>() ?? viewportGo.AddComponent<Image>();
|
||||
vpImg.color = new Color(0, 0, 0, 0.01f); // 近透明,作为 ScrollRect viewport 的图形
|
||||
|
||||
var contentGo = GetOrCreateChild(viewportGo.transform, "RoomContainer").gameObject;
|
||||
var contentRect = contentGo.GetComponent<RectTransform>() ?? contentGo.AddComponent<RectTransform>();
|
||||
contentRect.anchorMin = contentRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
contentRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
contentRect.sizeDelta = new Vector2(4000, 4000);
|
||||
|
||||
scrollRect.content = contentRect;
|
||||
scrollRect.viewport = vpRect;
|
||||
|
||||
// 玩家图标(content 内)
|
||||
var playerIcon = MakeImage(contentGo.transform, "PlayerIcon", new Color(1f, 0.3f, 0.3f, 1f), new Vector2(16, 16), raycast: false);
|
||||
|
||||
// Tooltip(默认隐藏)
|
||||
var tooltipGo = GetOrCreateChild(panelGo.transform, "Tooltip").gameObject;
|
||||
var ttRect = tooltipGo.GetComponent<RectTransform>() ?? tooltipGo.AddComponent<RectTransform>();
|
||||
AnchorTopRight(ttRect, new Vector2(240, 60), new Vector2(-20, -20));
|
||||
var ttImg = tooltipGo.GetComponent<Image>() ?? tooltipGo.AddComponent<Image>();
|
||||
ttImg.color = new Color(0, 0, 0, 0.8f); ttImg.raycastTarget = false;
|
||||
var ttText = MakeText(tooltipGo.transform, "Text", "房间");
|
||||
tooltipGo.SetActive(false);
|
||||
|
||||
// 组件
|
||||
var mapPanel = GetOrAddComponent<MapPanel>(panelGo);
|
||||
var mapInput = GetOrAddComponent<MapInputHandler>(panelGo);
|
||||
|
||||
AssignRef(mapPanel, "_roomContainer", contentRect);
|
||||
AssignRef(mapPanel, "_cellPrefab", cellPrefab.GetComponent<MapRoomCellUI>());
|
||||
AssignRef(mapPanel, "_exitConnectorPrefab", exitPrefab.GetComponent<Image>());
|
||||
AssignRef(mapPanel, "_scrollRect", scrollRect);
|
||||
AssignRef(mapPanel, "_playerIconImg", playerIcon);
|
||||
AssignRef(mapPanel, "_pinPrefab", pinPrefab.GetComponent<Image>());
|
||||
AssignRef(mapPanel, "_pinConfig", pinConfig);
|
||||
AssignRef(mapPanel, "_tooltipPanel", tooltipGo);
|
||||
AssignRef(mapPanel, "_tooltipText", ttText);
|
||||
AssignMapIcons(mapPanel, report);
|
||||
AssignRefObj(mapPanel, "_iconPlayerPos", null); // 占位玩家图标用纯色,留空
|
||||
|
||||
AssignAsset(mapInput, "_inputReader", report, true, "InputReader");
|
||||
AssignRef(mapInput, "_scrollRect", scrollRect);
|
||||
AssignRef(mapInput, "_zoomTarget", contentRect);
|
||||
|
||||
// ── 探索进度(左上角,全局% + 当前区域%;格式串走本地化 Key)──────
|
||||
var progGo = GetOrCreateChild(panelGo.transform, "ProgressDisplay").gameObject;
|
||||
var progRect = progGo.GetComponent<RectTransform>() ?? progGo.AddComponent<RectTransform>();
|
||||
progRect.anchorMin = progRect.anchorMax = new Vector2(0f, 1f);
|
||||
progRect.pivot = new Vector2(0f, 1f);
|
||||
progRect.anchoredPosition = new Vector2(28f, -24f);
|
||||
progRect.sizeDelta = new Vector2(380f, 96f);
|
||||
var globalTxt = MakeText(progGo.transform, "GlobalProgress", "0%");
|
||||
var gRt = (RectTransform)globalTxt.transform; gRt.anchorMin = gRt.anchorMax = new Vector2(0f, 1f); gRt.pivot = new Vector2(0f, 1f); gRt.anchoredPosition = Vector2.zero;
|
||||
globalTxt.alignment = TextAlignmentOptions.TopLeft;
|
||||
var regionTxt = MakeText(progGo.transform, "RegionProgress", "0%");
|
||||
var rRt = (RectTransform)regionTxt.transform; rRt.anchorMin = rRt.anchorMax = new Vector2(0f, 1f); rRt.pivot = new Vector2(0f, 1f); rRt.anchoredPosition = new Vector2(0f, -44f);
|
||||
regionTxt.alignment = TextAlignmentOptions.TopLeft; regionTxt.fontSize = 24;
|
||||
var prog = GetOrAddComponent<MapProgressDisplay>(progGo);
|
||||
AssignRef(prog, "_globalProgressText", globalTxt);
|
||||
AssignRef(prog, "_regionProgressText", regionTxt);
|
||||
AssignString(prog, "_globalFormat", "MAP_PROGRESS_GLOBAL"); // 本地化 Key,MapProgressDisplay 运行时解析为格式串
|
||||
AssignString(prog, "_regionFormat", "MAP_PROGRESS_REGION");
|
||||
AssignAsset(prog, "_onRegionChanged", report, false, "EVT_RegionChanged");
|
||||
|
||||
// ── 关闭提示(底部居中):输入图标(随设备自适应) + 本地化标签 ──────
|
||||
var hintRow = GetOrCreateChild(panelGo.transform, "CloseHint").gameObject;
|
||||
var hintRowRt = hintRow.GetComponent<RectTransform>() ?? hintRow.AddComponent<RectTransform>();
|
||||
hintRowRt.anchorMin = hintRowRt.anchorMax = new Vector2(0.5f, 0f);
|
||||
hintRowRt.pivot = new Vector2(0.5f, 0f);
|
||||
hintRowRt.anchoredPosition = new Vector2(0f, 24f);
|
||||
hintRowRt.sizeDelta = new Vector2(360f, 40f);
|
||||
var hintLayout = GetOrAddComponent<HorizontalLayoutGroup>(hintRow);
|
||||
hintLayout.childAlignment = TextAnchor.MiddleCenter; hintLayout.spacing = 8f;
|
||||
hintLayout.childForceExpandWidth = false; hintLayout.childForceExpandHeight = false;
|
||||
MakeInputIcon(hintRow.transform, "CloseIcon", "Cancel"); // 复用 InputIconImage:键鼠/手柄自动显示对应按键图标
|
||||
var hintLabel = MakeLocalizedText(hintRow.transform, "CloseLabel", "MAP_CLOSE_HINT");
|
||||
hintLabel.fontSize = 22;
|
||||
report.Add("关闭提示:InputIconImage(动作 'Cancel') + LocalizedText('MAP_CLOSE_HINT')。" +
|
||||
"动作名需与 InputActions 一致;按键图标需在 InputDeviceIconSetSO 中配置(用 Input Icon Studio)。");
|
||||
|
||||
panelGo.SetActive(false); // 由 PanelStack 控制显隐
|
||||
report.Add("MapPanel 已搭建(含探索进度 + 输入图标关闭提示;默认隐藏,由 UIManager PanelStack 管理)。");
|
||||
return panelGo;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// 传送确认框 + 控制器
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
private static void BuildTeleportConfirm(GameObject mapPanelRoot, List<string> report)
|
||||
{
|
||||
if (mapPanelRoot == null) { report.Add("MapPanel 缺失,跳过传送确认框搭建。"); return; }
|
||||
Transform canvas = mapPanelRoot.transform.parent ?? mapPanelRoot.transform;
|
||||
|
||||
// 确认框(自包含,SetActive 显隐)
|
||||
var dialogGo = GetOrCreateChild(canvas, "TeleportConfirmDialog").gameObject;
|
||||
StretchFill(dialogGo.GetComponent<RectTransform>() ?? dialogGo.AddComponent<RectTransform>(), 0f);
|
||||
var dimImg = dialogGo.GetComponent<Image>() ?? dialogGo.AddComponent<Image>();
|
||||
dimImg.color = new Color(0, 0, 0, 0.6f);
|
||||
|
||||
var boxGo = GetOrCreateChild(dialogGo.transform, "Box").gameObject;
|
||||
var boxRect = boxGo.GetComponent<RectTransform>() ?? boxGo.AddComponent<RectTransform>();
|
||||
boxRect.anchorMin = boxRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
boxRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
boxRect.sizeDelta = new Vector2(520, 260);
|
||||
var boxImg = boxGo.GetComponent<Image>() ?? boxGo.AddComponent<Image>();
|
||||
boxImg.color = new Color(0.12f, 0.12f, 0.15f, 1f);
|
||||
|
||||
var title = MakeText(boxGo.transform, "Title", "快速传送");
|
||||
((RectTransform)title.transform).anchoredPosition = new Vector2(0, 90);
|
||||
var body = MakeText(boxGo.transform, "Body", "传送到该地点?");
|
||||
var confirmBtn = MakeButton(boxGo.transform, "ConfirmButton", "确认", new Vector2(-110, -90), out TMP_Text confirmLabel);
|
||||
var cancelBtn = MakeButton(boxGo.transform, "CancelButton", "取消", new Vector2( 110, -90), out TMP_Text cancelLabel);
|
||||
|
||||
var dialog = GetOrAddComponent<ConfirmDialogController>(dialogGo);
|
||||
AssignRef(dialog, "_root", dialogGo);
|
||||
AssignRef(dialog, "_titleText", title);
|
||||
AssignRef(dialog, "_bodyText", body);
|
||||
AssignRef(dialog, "_confirmLabel", confirmLabel);
|
||||
AssignRef(dialog, "_cancelLabel", cancelLabel);
|
||||
AssignRef(dialog, "_btnConfirm", confirmBtn);
|
||||
AssignRef(dialog, "_btnCancel", cancelBtn);
|
||||
dialogGo.SetActive(false);
|
||||
|
||||
// 控制器:接 MapPanel 的 OnTeleportStationSelected
|
||||
var ctrlGo = GetOrCreateChild(canvas, "MapTeleportConfirmController").gameObject;
|
||||
var ctrl = GetOrAddComponent<MapTeleportConfirmController>(ctrlGo);
|
||||
AssignRef(ctrl, "_mapPanel", mapPanelRoot.GetComponent<MapPanel>());
|
||||
AssignRef(ctrl, "_confirmDialog", dialog);
|
||||
|
||||
report.Add("传送确认框 + MapTeleportConfirmController 已搭建并绑定 MapPanel。");
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// UIManager 登记
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
private static void RegisterMapPanelWithUIManager(GameObject mapPanelRoot, List<string> report)
|
||||
{
|
||||
if (mapPanelRoot == null) return;
|
||||
var uiManager = Object.FindFirstObjectByType<UIManager>();
|
||||
if (uiManager == null)
|
||||
{
|
||||
report.Add("场景中无 UIManager,未登记 PanelId.Map;请在 UIManager._panels 手动添加 {Map, MapPanel}。");
|
||||
return;
|
||||
}
|
||||
|
||||
var so = new SerializedObject(uiManager);
|
||||
var panels = so.FindProperty("_panels");
|
||||
if (panels == null || !panels.isArray)
|
||||
{
|
||||
report.Add("UIManager._panels 不可写,请手动登记 PanelId.Map。");
|
||||
}
|
||||
else
|
||||
{
|
||||
// 已登记则跳过
|
||||
bool exists = false;
|
||||
for (int i = 0; i < panels.arraySize; i++)
|
||||
{
|
||||
var el = panels.GetArrayElementAtIndex(i);
|
||||
if (el.FindPropertyRelative("id").enumValueIndex == (int)PanelId.Map)
|
||||
{
|
||||
el.FindPropertyRelative("root").objectReferenceValue = mapPanelRoot;
|
||||
exists = true; break;
|
||||
}
|
||||
}
|
||||
if (!exists)
|
||||
{
|
||||
int idx = panels.arraySize;
|
||||
panels.arraySize = idx + 1;
|
||||
var el = panels.GetArrayElementAtIndex(idx);
|
||||
el.FindPropertyRelative("id").enumValueIndex = (int)PanelId.Map;
|
||||
el.FindPropertyRelative("root").objectReferenceValue = mapPanelRoot;
|
||||
}
|
||||
so.ApplyModifiedPropertiesWithoutUndo();
|
||||
report.Add("已登记 UIManager._panels[Map] → MapPanel。");
|
||||
}
|
||||
|
||||
AssignAsset(uiManager, "_onMapOpen", report, false, "EVT_MapOpen", "EVT_OpenMap");
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
// 通用辅助
|
||||
// ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
private static void AssignMapIcons(Object target, List<string> report)
|
||||
{
|
||||
AssignAsset(target, "_iconSavePoint", report, false, "ICN_Map_SavePoint", "ICN_SavePoint");
|
||||
AssignAsset(target, "_iconBossRoom", report, false, "ICN_Map_Boss", "ICN_Boss");
|
||||
AssignAsset(target, "_iconShop", report, false, "ICN_Map_Shop", "ICN_Shop");
|
||||
AssignAsset(target, "_iconTeleport", report, false, "ICN_Map_Teleport", "ICN_Teleport");
|
||||
}
|
||||
|
||||
private static GameObject FindHudCanvas()
|
||||
{
|
||||
Scene scene = SceneManager.GetActiveScene();
|
||||
foreach (GameObject root in scene.GetRootGameObjects())
|
||||
{
|
||||
if (root.name == "HUD Canvas") return root;
|
||||
foreach (string path in new[] { "[UI]/UIRoot/HUD Canvas", "UIRoot/HUD Canvas", "HUD Canvas" })
|
||||
{
|
||||
var found = root.transform.Find(path);
|
||||
if (found != null) return found.gameObject;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static GameObject GetOrCreateMapCanvas(string name, int sortOrder)
|
||||
{
|
||||
Scene scene = SceneManager.GetActiveScene();
|
||||
Transform uiRoot = null;
|
||||
foreach (GameObject root in scene.GetRootGameObjects())
|
||||
{
|
||||
if (root.name == name) return root;
|
||||
foreach (string path in new[] { $"[UI]/UIRoot/{name}", $"UIRoot/{name}" })
|
||||
{
|
||||
var found = root.transform.Find(path);
|
||||
if (found != null) return found.gameObject;
|
||||
}
|
||||
uiRoot ??= root.transform.Find("[UI]/UIRoot") ?? root.transform.Find("UIRoot");
|
||||
}
|
||||
|
||||
var canvasGo = new GameObject(name);
|
||||
Undo.RegisterCreatedObjectUndo(canvasGo, $"Create {name}");
|
||||
if (uiRoot != null) canvasGo.transform.SetParent(uiRoot, false);
|
||||
|
||||
var canvas = canvasGo.AddComponent<Canvas>();
|
||||
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
|
||||
canvas.sortingOrder = sortOrder;
|
||||
var scaler = canvasGo.AddComponent<CanvasScaler>();
|
||||
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
|
||||
scaler.referenceResolution = new Vector2(1920, 1080);
|
||||
canvasGo.AddComponent<GraphicRaycaster>();
|
||||
return canvasGo;
|
||||
}
|
||||
|
||||
// ── UI 元素构造 ──────────────────────────────────────────────────────
|
||||
|
||||
private static GameObject MakeChild(Transform parent, string name, Vector2 size)
|
||||
{
|
||||
var go = GetOrCreateChild(parent, name).gameObject;
|
||||
var rt = go.GetComponent<RectTransform>() ?? go.AddComponent<RectTransform>();
|
||||
rt.sizeDelta = size;
|
||||
return go;
|
||||
}
|
||||
|
||||
private static Image MakeImage(Transform parent, string name, Color color, Vector2 size, bool raycast)
|
||||
{
|
||||
var go = MakeChild(parent, name, size);
|
||||
var img = go.GetComponent<Image>() ?? go.AddComponent<Image>();
|
||||
img.color = color;
|
||||
img.raycastTarget = raycast;
|
||||
return img;
|
||||
}
|
||||
|
||||
private static TMP_Text MakeText(Transform parent, string name, string text)
|
||||
{
|
||||
var go = MakeChild(parent, name, new Vector2(240, 40));
|
||||
var t = go.GetComponent<TextMeshProUGUI>() ?? go.AddComponent<TextMeshProUGUI>();
|
||||
t.text = text;
|
||||
t.alignment = TextAlignmentOptions.Center;
|
||||
t.fontSize = 28;
|
||||
t.raycastTarget = false;
|
||||
return t;
|
||||
}
|
||||
|
||||
private static Button MakeButton(Transform parent, string name, string label, Vector2 anchoredPos, out TMP_Text labelText)
|
||||
{
|
||||
var go = MakeChild(parent, name, new Vector2(160, 56));
|
||||
((RectTransform)go.transform).anchorMin = ((RectTransform)go.transform).anchorMax = new Vector2(0.5f, 0.5f);
|
||||
((RectTransform)go.transform).anchoredPosition = anchoredPos;
|
||||
var img = go.GetComponent<Image>() ?? go.AddComponent<Image>();
|
||||
img.color = new Color(0.25f, 0.25f, 0.3f, 1f);
|
||||
var btn = GetOrAddComponent<Button>(go);
|
||||
btn.targetGraphic = img;
|
||||
labelText = MakeText(go.transform, "Label", label);
|
||||
return btn;
|
||||
}
|
||||
|
||||
/// <summary>创建带 LocalizedText 的 TMP 文本(文案随语言切换自动刷新)。返回 TMP_Text 供调整字号/对齐。</summary>
|
||||
private static TMP_Text MakeLocalizedText(Transform parent, string name, string locKey)
|
||||
{
|
||||
var go = MakeChild(parent, name, new Vector2(180f, 36f));
|
||||
var t = go.GetComponent<TextMeshProUGUI>() ?? go.AddComponent<TextMeshProUGUI>();
|
||||
t.alignment = TextAlignmentOptions.Center;
|
||||
t.fontSize = 22;
|
||||
t.raycastTarget = false;
|
||||
var lt = GetOrAddComponent<LocalizedText>(go); // RequireComponent<TMP_Text> 已满足
|
||||
AssignString(lt, "_key", locKey);
|
||||
return t;
|
||||
}
|
||||
|
||||
/// <summary>创建带 InputIconImage(ByActionName) 的按键图标 Image(随当前设备自适应显示)。</summary>
|
||||
private static InputIconImage MakeInputIcon(Transform parent, string name, string actionName)
|
||||
{
|
||||
var go = MakeChild(parent, name, new Vector2(36f, 36f));
|
||||
var img = go.GetComponent<Image>() ?? go.AddComponent<Image>();
|
||||
img.raycastTarget = false;
|
||||
var icon = GetOrAddComponent<InputIconImage>(go); // 默认 ByActionName 模式
|
||||
AssignString(icon, "_actionName", actionName);
|
||||
return icon;
|
||||
}
|
||||
|
||||
// ── 布局 ──────────────────────────────────────────────────────────────
|
||||
|
||||
private static void StretchFill(RectTransform rt, float padding)
|
||||
{
|
||||
rt.anchorMin = Vector2.zero; rt.anchorMax = Vector2.one;
|
||||
rt.pivot = new Vector2(0.5f, 0.5f);
|
||||
rt.offsetMin = new Vector2(padding, padding);
|
||||
rt.offsetMax = new Vector2(-padding, -padding);
|
||||
}
|
||||
|
||||
private static void AnchorTopRight(RectTransform rt, Vector2 size, Vector2 offset)
|
||||
{
|
||||
rt.anchorMin = rt.anchorMax = new Vector2(1f, 1f);
|
||||
rt.pivot = new Vector2(1f, 1f);
|
||||
rt.sizeDelta = size;
|
||||
rt.anchoredPosition = offset;
|
||||
}
|
||||
|
||||
// ── 引用 / 资产绑定(对照 HUDScaffoldWizard)─────────────────────────
|
||||
|
||||
private static Transform GetOrCreateChild(Transform parent, string name)
|
||||
{
|
||||
var child = parent.Find(name);
|
||||
if (child != null) return child;
|
||||
var go = new GameObject(name);
|
||||
Undo.RegisterCreatedObjectUndo(go, $"Create {name}");
|
||||
go.transform.SetParent(parent, false);
|
||||
go.AddComponent<RectTransform>();
|
||||
return go.transform;
|
||||
}
|
||||
|
||||
private static T GetOrAddComponent<T>(GameObject go) where T : Component
|
||||
{
|
||||
var c = go.GetComponent<T>();
|
||||
return c != null ? c : Undo.AddComponent<T>(go);
|
||||
}
|
||||
|
||||
private static void AssignRef(Object target, string propertyName, Object value)
|
||||
{
|
||||
var so = new SerializedObject(target);
|
||||
var prop = so.FindProperty(propertyName);
|
||||
if (prop == null)
|
||||
{
|
||||
Debug.LogWarning($"[MapUIScaffold] 未找到属性 {target.GetType().Name}.{propertyName}", target);
|
||||
return;
|
||||
}
|
||||
prop.objectReferenceValue = value;
|
||||
so.ApplyModifiedPropertiesWithoutUndo();
|
||||
}
|
||||
|
||||
private static void AssignRefObj(Object target, string propertyName, Object value) => AssignRef(target, propertyName, value);
|
||||
|
||||
private static void AssignString(Object target, string propertyName, string value)
|
||||
{
|
||||
var so = new SerializedObject(target);
|
||||
var prop = so.FindProperty(propertyName);
|
||||
if (prop == null)
|
||||
{
|
||||
Debug.LogWarning($"[MapUIScaffold] 未找到字符串属性 {target.GetType().Name}.{propertyName}", target);
|
||||
return;
|
||||
}
|
||||
prop.stringValue = value;
|
||||
so.ApplyModifiedPropertiesWithoutUndo();
|
||||
}
|
||||
|
||||
private static void AssignAsset(Object target, string propertyName, List<string> report,
|
||||
bool required, params string[] candidates)
|
||||
{
|
||||
Object asset = FindFirstAsset(candidates);
|
||||
if (asset == null)
|
||||
{
|
||||
if (required)
|
||||
report.Add($"未找到 {target.GetType().Name}.{propertyName} 所需资产: {string.Join(" / ", candidates)}");
|
||||
return;
|
||||
}
|
||||
AssignRef(target, propertyName, asset);
|
||||
}
|
||||
|
||||
private static Object FindFirstAsset(params string[] candidates)
|
||||
{
|
||||
foreach (string candidate in candidates)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(candidate)) continue;
|
||||
foreach (string guid in AssetDatabase.FindAssets(candidate))
|
||||
{
|
||||
string path = AssetDatabase.GUIDToAssetPath(guid);
|
||||
Object asset = AssetDatabase.LoadMainAssetAtPath(path);
|
||||
if (asset != null && asset.name == candidate) return asset;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void EnsureFolder(string folder)
|
||||
{
|
||||
if (string.IsNullOrEmpty(folder) || AssetDatabase.IsValidFolder(folder)) return;
|
||||
var parts = folder.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;
|
||||
}
|
||||
}
|
||||
|
||||
private static void MarkDirtyAndLog(string scaffoldName, GameObject root, List<string> report)
|
||||
{
|
||||
EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
|
||||
if (root != null) Selection.activeGameObject = root;
|
||||
if (report.Count == 0) { Debug.Log($"[MapUIScaffold] {scaffoldName} 完成。", root); return; }
|
||||
Debug.LogWarning($"[MapUIScaffold] {scaffoldName} 完成,以下 {report.Count} 项需手动确认:\n- {string.Join("\n- ", report)}", root);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user