Initial commit: Client Doc docs Server Tools

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
ud18010
2026-07-10 10:24:29 +08:00
co-authored by Cursor
commit 7e35d8da31
3374 changed files with 680813 additions and 0 deletions
@@ -0,0 +1,51 @@
using System;
using System.Collections;
using System.Text;
using UnityEngine;
using UnityEngine.Networking;
namespace XGame
{
// 账号鉴权 HTTP 客户端:POST /login、/register,拿 {token,pid}。
public static class AuthClient
{
[Serializable] class AuthReq { public string username; public string password; }
[Serializable] class AuthResp { public string token; public int pid; }
public static IEnumerator Login(string user, string pwd, Action<bool, string, int, string> done)
=> Post("/login", user, pwd, done);
public static IEnumerator Register(string user, string pwd, Action<bool, string, int, string> done)
=> Post("/register", user, pwd, done);
static IEnumerator Post(string path, string user, string pwd, Action<bool, string, int, string> done)
{
string url = GlobalData.ProductionAuthBase + path;
string body = JsonUtility.ToJson(new AuthReq { username = user, password = pwd });
using (var req = new UnityWebRequest(url, "POST"))
{
req.uploadHandler = new UploadHandlerRaw(Encoding.UTF8.GetBytes(body));
req.downloadHandler = new DownloadHandlerBuffer();
req.SetRequestHeader("Content-Type", "application/json");
yield return req.SendWebRequest();
if (req.result != UnityWebRequest.Result.Success)
{
string err = req.responseCode == 401 ? "账号或密码错误"
: req.responseCode == 409 ? "用户名已被占用"
: ("网络错误: " + req.error);
done(false, null, 0, err);
yield break;
}
AuthResp resp = null;
try { resp = JsonUtility.FromJson<AuthResp>(req.downloadHandler.text); } catch { }
if (resp == null || string.IsNullOrEmpty(resp.token))
{
done(false, null, 0, "响应解析失败");
yield break;
}
done(true, resp.token, resp.pid, null);
}
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1658466d5399e3241b93b004568852d4
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 42ca20d79634abb42b014dd1d9fcfb0e
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 6f5f6a77b5a84e6fa8d4a3a4bc53c0f2
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,107 @@
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace XGame
{
// UI 层级,对应 UIRoot 下的四个子节点;顺序即渲染顺序(后者在上):HUD < Normal < Dialog < Tips
public enum UILayer
{
HUD = 0, // 抬头信息:血条、状态、摇杆、小地图等常驻 HUD
Normal = 1, // 普通功能界面:背包、设置、商城、登录/大厅等常规面板
Dialog = 2, // 弹出对话框:确认框等模态弹窗
Tips = 3, // 弹出文字信息:飘字、Toast 轻提示
}
/// <summary>
/// UI 挂载管理:维护一个统一的 UI 画布 UIRoot,其下有 HUD/Normal/Dialog/Tips 层级节点,
/// 把 UI 预设按类型挂到对应层。规则见 Doc/Rule/UnityProject.md「UI 挂载规则」。
///
/// UIRoot 本身是 Canvas(ScreenSpaceOverlay + ScaleWithScreenSize 2048x1024)。这很关键:
/// 面板预设自带的 Canvas 挂进来后变成"嵌套子画布",嵌套 Canvas 不会驱动其 RectTransform
/// 面板根节点在预设里设置的 Anchor/尺寸才会生效(否则顶层 Overlay 画布会把根强制拉成全屏、锚点失效)。
/// 参考分辨率与各面板自带 CanvasScaler 一致(2048x1024, match 0.5),故缩放不变。
/// </summary>
public static class UIManager
{
private const string UIRootName = "UIRoot";
private static readonly string[] LayerNames = { "HUD", "Normal", "Dialog", "Tips" };
private static readonly Vector2 DesignResolution = new Vector2(2048f, 1024f);
/// <summary>把 UI 挂到指定层(worldPositionStays=false 保留本地布局/锚点)。</summary>
public static void AttachTo(UILayer layer, GameObject ui, bool worldPositionStays = false)
{
if (ui == null)
{
return;
}
RectTransform layerRt = GetLayer(layer);
if (layerRt != null)
{
ui.transform.SetParent(layerRt, worldPositionStays);
}
}
/// <summary>获取指定层的 RectTransform(确保 UIRoot 画布与该层存在)。</summary>
public static RectTransform GetLayer(UILayer layer)
{
Transform root = EnsureUIRoot();
string name = LayerNames[(int)layer];
Transform t = root.Find(name);
if (t == null)
{
t = CreateFullStretchChild(root, name);
}
return t as RectTransform;
}
private static Transform EnsureUIRoot()
{
EnsureEventSystem();
GameObject go = GameObject.Find(UIRootName);
if (go == null)
{
go = new GameObject(UIRootName, typeof(RectTransform), typeof(Canvas), typeof(CanvasScaler), typeof(GraphicRaycaster));
Canvas canvas = go.GetComponent<Canvas>();
canvas.renderMode = RenderMode.ScreenSpaceOverlay;
canvas.sortingOrder = 100; // 位于主界面之上
CanvasScaler scaler = go.GetComponent<CanvasScaler>();
scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
scaler.referenceResolution = DesignResolution;
scaler.screenMatchMode = CanvasScaler.ScreenMatchMode.MatchWidthOrHeight;
scaler.matchWidthOrHeight = 0.5f;
// 按 HUD→Normal→Dialog→Tips 顺序创建,子节点顺序即渲染层级(后建的在上)
for (int i = 0; i < LayerNames.Length; i++)
{
CreateFullStretchChild(go.transform, LayerNames[i]);
}
}
return go.transform;
}
private static void EnsureEventSystem()
{
if (EventSystem.current != null || Object.FindObjectOfType<EventSystem>() != null)
{
return;
}
new GameObject("EventSystem", typeof(EventSystem), typeof(StandaloneInputModule));
}
private static Transform CreateFullStretchChild(Transform parent, string name)
{
GameObject go = new GameObject(name, typeof(RectTransform));
RectTransform rt = go.GetComponent<RectTransform>();
rt.SetParent(parent, false);
rt.anchorMin = Vector2.zero;
rt.anchorMax = Vector2.one;
rt.offsetMin = Vector2.zero;
rt.offsetMax = Vector2.zero;
rt.pivot = new Vector2(0.5f, 0.5f);
rt.localScale = Vector3.one;
return go.transform;
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: d1c655b3a9179e3479e4066be0525792
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: