Add MainHUD gameplay UI, net handshake updates, and server maintenance mode.
Introduce UiManager/MainHUD prefab flow, strengthen GameNet/WsClient, and add AIServer maintenance endpoint support. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace HotUpdate.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// UI_ConfirmDialog:公告 / 维护 / 通用确认。
|
||||
/// </summary>
|
||||
public sealed class ConfirmDialogView : MonoBehaviour
|
||||
{
|
||||
public const string PrefabName = "UI_ConfirmDialog";
|
||||
|
||||
TextMeshProUGUI _title;
|
||||
TextMeshProUGUI _message;
|
||||
Button _ok;
|
||||
Button _cancel;
|
||||
Button _close;
|
||||
Action _onOk;
|
||||
Action _onCancel;
|
||||
|
||||
public void Bind()
|
||||
{
|
||||
_title = FindTmp("Txt_Title");
|
||||
_message = FindTmp("Txt_Message");
|
||||
_ok = FindBtn("Btn_Ok");
|
||||
_cancel = FindBtn("Btn_Cancel");
|
||||
_close = FindBtn("Btn_Close");
|
||||
|
||||
Wire(_ok, () => { _onOk?.Invoke(); Hide(); });
|
||||
Wire(_cancel, () => { _onCancel?.Invoke(); Hide(); });
|
||||
Wire(_close, () => { _onCancel?.Invoke(); Hide(); });
|
||||
}
|
||||
|
||||
public void Show(
|
||||
string title,
|
||||
string message,
|
||||
Action onOk = null,
|
||||
Action onCancel = null,
|
||||
bool showCancel = false)
|
||||
{
|
||||
if (_title != null) _title.text = title ?? string.Empty;
|
||||
if (_message != null) _message.text = message ?? string.Empty;
|
||||
_onOk = onOk;
|
||||
_onCancel = onCancel;
|
||||
if (_cancel != null) _cancel.gameObject.SetActive(showCancel);
|
||||
gameObject.SetActive(true);
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
gameObject.SetActive(false);
|
||||
}
|
||||
|
||||
static void Wire(Button btn, Action action)
|
||||
{
|
||||
if (btn == null) return;
|
||||
btn.onClick.RemoveAllListeners();
|
||||
btn.onClick.AddListener(() => action?.Invoke());
|
||||
}
|
||||
|
||||
TextMeshProUGUI FindTmp(string name)
|
||||
{
|
||||
foreach (var c in GetComponentsInChildren<TextMeshProUGUI>(true))
|
||||
if (c.name == name) return c;
|
||||
return null;
|
||||
}
|
||||
|
||||
Button FindBtn(string name)
|
||||
{
|
||||
foreach (var c in GetComponentsInChildren<Button>(true))
|
||||
if (c.name == name) return c;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0eff42e367d11a7408adc8c9db3697c8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace HotUpdate.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// UI_MainHUD 运行时绑定。
|
||||
/// </summary>
|
||||
public sealed class MainHudView : MonoBehaviour
|
||||
{
|
||||
public const string PrefabName = "UI_MainHUD";
|
||||
|
||||
TextMeshProUGUI _status;
|
||||
TextMeshProUGUI _pid;
|
||||
GameObject _reconnectPanel;
|
||||
TextMeshProUGUI _reconnectText;
|
||||
Button _reconnectBtn;
|
||||
|
||||
public event Action OnReconnectClicked;
|
||||
|
||||
public void Bind()
|
||||
{
|
||||
_status = FindTmp("Txt_Status");
|
||||
_pid = FindTmp("Txt_Pid");
|
||||
var panel = transform.Find("Panel_Reconnect");
|
||||
_reconnectPanel = panel != null ? panel.gameObject : null;
|
||||
_reconnectText = FindTmp("Txt_Reconnect");
|
||||
var btnTf = transform.Find("Panel_Reconnect/Btn_Reconnect");
|
||||
_reconnectBtn = btnTf != null ? btnTf.GetComponent<Button>() : null;
|
||||
if (_reconnectBtn != null)
|
||||
{
|
||||
_reconnectBtn.onClick.RemoveAllListeners();
|
||||
_reconnectBtn.onClick.AddListener(() => OnReconnectClicked?.Invoke());
|
||||
}
|
||||
ShowReconnect(false);
|
||||
}
|
||||
|
||||
public void SetStatus(string text)
|
||||
{
|
||||
if (_status != null) _status.text = text ?? string.Empty;
|
||||
}
|
||||
|
||||
public void SetPid(long pid)
|
||||
{
|
||||
if (_pid != null) _pid.text = pid > 0 ? $"pid={pid}" : "pid=-";
|
||||
}
|
||||
|
||||
public void ShowReconnect(bool visible, string message = null)
|
||||
{
|
||||
if (_reconnectPanel != null) _reconnectPanel.SetActive(visible);
|
||||
if (visible && _reconnectText != null && !string.IsNullOrEmpty(message))
|
||||
_reconnectText.text = message;
|
||||
}
|
||||
|
||||
TextMeshProUGUI FindTmp(string path)
|
||||
{
|
||||
var t = transform.Find(path);
|
||||
if (t == null)
|
||||
{
|
||||
// 深层查找
|
||||
var all = GetComponentsInChildren<TextMeshProUGUI>(true);
|
||||
foreach (var c in all)
|
||||
if (c.name == path) return c;
|
||||
return null;
|
||||
}
|
||||
return t.GetComponent<TextMeshProUGUI>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 85b335bef198c764dac1b0c2792443a8
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace HotUpdate.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// 竖屏 UI 页面栈:加载 Prefab、打开/关闭、按名查找。
|
||||
/// </summary>
|
||||
public sealed class UiManager : MonoBehaviour
|
||||
{
|
||||
public static UiManager Instance { get; private set; }
|
||||
|
||||
readonly Dictionary<string, GameObject> _opened = new Dictionary<string, GameObject>(StringComparer.Ordinal);
|
||||
Transform _root;
|
||||
UiPrefabLoader _loader;
|
||||
|
||||
public void Init(Transform root, UiPrefabLoader loader)
|
||||
{
|
||||
Instance = this;
|
||||
_root = root;
|
||||
_loader = loader ?? new UiPrefabLoader();
|
||||
}
|
||||
|
||||
public GameObject Open(string prefabName, int siblingIndex = -1)
|
||||
{
|
||||
if (string.IsNullOrEmpty(prefabName)) return null;
|
||||
if (_opened.TryGetValue(prefabName, out var existing) && existing != null)
|
||||
{
|
||||
existing.SetActive(true);
|
||||
return existing;
|
||||
}
|
||||
|
||||
var prefab = _loader.Load(prefabName);
|
||||
if (prefab == null)
|
||||
{
|
||||
Debug.LogError($"[UiManager] prefab not found: {prefabName}");
|
||||
return null;
|
||||
}
|
||||
|
||||
var go = Instantiate(prefab, _root, false);
|
||||
go.name = prefabName;
|
||||
if (siblingIndex >= 0)
|
||||
go.transform.SetSiblingIndex(siblingIndex);
|
||||
_opened[prefabName] = go;
|
||||
return go;
|
||||
}
|
||||
|
||||
public T Open<T>(string prefabName) where T : Component
|
||||
{
|
||||
var go = Open(prefabName);
|
||||
return go != null ? go.GetComponentInChildren<T>(true) : null;
|
||||
}
|
||||
|
||||
public void Close(string prefabName, bool destroy = false)
|
||||
{
|
||||
if (!_opened.TryGetValue(prefabName, out var go) || go == null)
|
||||
return;
|
||||
if (destroy)
|
||||
{
|
||||
Destroy(go);
|
||||
_opened.Remove(prefabName);
|
||||
}
|
||||
else go.SetActive(false);
|
||||
}
|
||||
|
||||
public GameObject Get(string prefabName)
|
||||
{
|
||||
_opened.TryGetValue(prefabName, out var go);
|
||||
return go;
|
||||
}
|
||||
|
||||
void OnDestroy()
|
||||
{
|
||||
if (Instance == this) Instance = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f9774ca39700a5042a7f4876e1f4e2cf
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,39 @@
|
||||
using UnityEngine;
|
||||
using YooAsset;
|
||||
|
||||
namespace HotUpdate.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// UI Prefab 加载:编辑器 AssetDatabase;运行时优先 DefaultPackage 寻址。
|
||||
/// </summary>
|
||||
public sealed class UiPrefabLoader
|
||||
{
|
||||
public const string PrefabFolder = "Assets/Bundles/UI/Prefab";
|
||||
|
||||
public GameObject Load(string prefabName)
|
||||
{
|
||||
if (string.IsNullOrEmpty(prefabName)) return null;
|
||||
|
||||
if (YooAssets.IsInitialized)
|
||||
{
|
||||
// DefaultPackage 或任意已初始化包
|
||||
var packages = new[] { "DefaultPackage", "Art" };
|
||||
foreach (var name in packages)
|
||||
{
|
||||
if (!YooAssets.TryGetPackage(name, out var pkg)) continue;
|
||||
if (pkg.InitializeStatus != EOperationStatus.Succeeded) continue;
|
||||
var handle = pkg.LoadAssetSync<GameObject>(prefabName);
|
||||
if (handle.Status == EOperationStatus.Succeeded)
|
||||
return handle.GetAssetObject<GameObject>();
|
||||
}
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
var path = $"{PrefabFolder}/{prefabName}.prefab";
|
||||
return UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(path);
|
||||
#else
|
||||
return null;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4e78d596353200e4fbb315f840e50f6e
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user