using System; using System.Collections; using System.Collections.Generic; using System.Reflection; using System.Text; using System.Threading.Tasks; using UnityEngine; public sealed class BriskQuickStartSample : MonoBehaviour { [Header("初始化")] public string BaseUrl = "https://brisk.lightyears.ltd"; public string GameKey = "demo-game"; public string ClientVersion = "1.0.0"; public string DeviceId = "editor-device"; public bool ValidateSessionOnInitialize = true; [Header("登录")] public string LoginProvider = "tap"; public string LoginUserId = "tap_user_10001"; public string LoginCode = string.Empty; public string Nickname = "Unity示例玩家"; public string AvatarUrl = string.Empty; [Header("排行榜")] public string RankKey = "season-score"; public string SubmitScoreValue = "128"; public string LeaderboardLimit = "10"; public string AroundMeRange = "5"; public string SeasonId = string.Empty; public string SeasonHistoryLimit = "20"; [Header("云存档")] public string ArchiveSlotNo = "1"; public string ArchiveBaseVersion = string.Empty; [TextArea(3, 6)] public string ArchiveContent = "{\n \"save\": 1,\n \"coins\": 128,\n \"hero\": \"mage\",\n \"title\": \"中文测试存档\"\n}"; [Header("公告")] public string AnnouncementId = string.Empty; [Header("玩家空间")] public string SpacePlayerId = string.Empty; public string SpaceLoginProvider = "tap"; public string SpaceLoginUserId = "tap_user_10001"; [TextArea(3, 6)] public string SpacePayloadText = "{\n \"mood\": \"ready\",\n \"title\": \"你好 Brisk\",\n \"desc\": \"这是中文测试空间数据\"\n}"; [Header("演示")] public bool AutoRunOnStart; private readonly List _eventLogs = new List(); private Vector2 _pageScroll; private Vector2 _resultScroll; private Vector2 _logScroll; private bool _isBusy; private string _busyAction = string.Empty; private string _statusText = "就绪"; private string _resultText = "尚未执行请求。"; private string _lastErrorText = string.Empty; private IReadOnlyList _announcementCache = Array.Empty(); private IReadOnlyList _seasonHistoryCache = Array.Empty(); private void OnEnable() { Brisk.OnInitialized += HandleInitialized; Brisk.OnLoggedIn += HandleLoggedIn; Brisk.OnLoggedOut += HandleLoggedOut; Brisk.OnAuthExpired += HandleAuthExpired; Brisk.OnBlockingError += HandleBlockingError; } private void OnDisable() { Brisk.OnInitialized -= HandleInitialized; Brisk.OnLoggedIn -= HandleLoggedIn; Brisk.OnLoggedOut -= HandleLoggedOut; Brisk.OnAuthExpired -= HandleAuthExpired; Brisk.OnBlockingError -= HandleBlockingError; } private void Start() { if (AutoRunOnStart) { RunAction("自动冒烟流程", RunSmokeFlowAsync); } } [ContextMenu("运行 Brisk 示例")] public void RunFromContextMenu() { RunAction("右键菜单冒烟流程", RunSmokeFlowAsync); } private void OnGUI() { var area = new Rect(12f, 12f, Screen.width - 24f, Screen.height - 24f); GUILayout.BeginArea(area, GUI.skin.box); _pageScroll = GUILayout.BeginScrollView(_pageScroll); DrawHeader(); DrawStatusPanel(); DrawInitSection(); DrawLoginSection(); DrawPlayerAndConfigSection(); DrawAnnouncementsSection(); DrawLeaderboardSection(); DrawArchiveSection(); DrawSpaceSection(); DrawOutputSection(); GUILayout.EndScrollView(); GUILayout.EndArea(); } private void DrawHeader() { GUILayout.Label("Brisk IMGUI 测试面板", GUI.skin.box); GUILayout.Label("这个场景用于在一个页面内测试 SDK 的完整流程。", GUI.skin.label); } private void DrawStatusPanel() { BeginSection("运行状态"); DrawReadOnlyRow("已初始化", Brisk.IsInitialized ? "是" : "否"); DrawReadOnlyRow("已登录", Brisk.IsLoggedIn ? "是" : "否"); DrawReadOnlyRow("PlayerId", Brisk.PlayerId); DrawReadOnlyRow("当前身份", Brisk.Identity == null ? string.Empty : Brisk.Identity.LoginProvider + " / " + Brisk.Identity.LoginUserId); DrawReadOnlyRow("当前动作", _isBusy ? _busyAction : "空闲"); DrawReadOnlyRow("状态", _statusText); GUILayout.BeginHorizontal(); DrawButton("执行冒烟流程", RunSmokeFlowAsync); DrawButton("查看 Bootstrap 缓存", () => { SetResult("Bootstrap 缓存", Brisk.Bootstrap); return Task.CompletedTask; }, Brisk.IsInitialized); DrawButton("关闭 SDK", () => { Brisk.Shutdown(); Log("SDK 已关闭。"); _statusText = "SDK 已关闭"; return Task.CompletedTask; }); DrawButton("清空输出", () => { _resultText = "输出已清空。"; _lastErrorText = string.Empty; _eventLogs.Clear(); _statusText = "输出已清空"; return Task.CompletedTask; }); GUILayout.EndHorizontal(); EndSection(); } private void DrawInitSection() { BeginSection("初始化"); BaseUrl = DrawEditableRow("服务地址", BaseUrl); GameKey = DrawEditableRow("游戏 Key", GameKey); ClientVersion = DrawEditableRow("客户端版本", ClientVersion); DeviceId = DrawEditableRow("设备标识", DeviceId); ValidateSessionOnInitialize = DrawToggleRow("初始化时校验旧会话", ValidateSessionOnInitialize); GUILayout.BeginHorizontal(); DrawButton("初始化", InitializeAsync); DrawButton("重新初始化", ReinitializeAsync); GUILayout.EndHorizontal(); EndSection(); } private void DrawLoginSection() { BeginSection("登录"); LoginProvider = DrawEditableRow("登录提供方", LoginProvider); LoginUserId = DrawEditableRow("登录用户 ID", LoginUserId); LoginCode = DrawEditableRow("登录 Code", LoginCode); Nickname = DrawEditableRow("昵称", Nickname); AvatarUrl = DrawEditableRow("头像地址", AvatarUrl); GUILayout.BeginHorizontal(); DrawButton("按用户 ID 登录", LoginWithUserIdAsync, Brisk.IsInitialized); DrawButton("按 Code 登录", LoginWithCodeAsync, Brisk.IsInitialized); DrawButton("登出", LogoutAsync, Brisk.IsInitialized); GUILayout.EndHorizontal(); EndSection(); } private void DrawPlayerAndConfigSection() { BeginSection("玩家与配置"); GUILayout.BeginHorizontal(); DrawButton("获取当前玩家", GetMeAsync, Brisk.IsLoggedIn); DrawButton("获取当前配置", GetConfigAsync, Brisk.IsInitialized); DrawButton("同步当前身份到空间查询", () => { ApplyCurrentIdentityToSpace(); SetResult("空间查询身份", new Dictionary { { "space_player_id", SpacePlayerId }, { "space_login_provider", SpaceLoginProvider }, { "space_login_user_id", SpaceLoginUserId } }); return Task.CompletedTask; }, Brisk.IsInitialized); GUILayout.EndHorizontal(); EndSection(); } private void DrawAnnouncementsSection() { BeginSection("公告"); AnnouncementId = DrawEditableRow("公告 ID", AnnouncementId); GUILayout.BeginHorizontal(); DrawButton("获取公告列表", GetAnnouncementsAsync, Brisk.IsLoggedIn); DrawButton("标记已读", MarkAnnouncementAsync, Brisk.IsLoggedIn); DrawButton("标记首条缓存公告已读", MarkFirstCachedAnnouncementAsync, Brisk.IsLoggedIn && _announcementCache.Count > 0); GUILayout.EndHorizontal(); EndSection(); } private void DrawLeaderboardSection() { BeginSection("排行榜"); RankKey = DrawEditableRow("排行榜 Key", RankKey); SubmitScoreValue = DrawEditableRow("提交分数", SubmitScoreValue); LeaderboardLimit = DrawEditableRow("Top 数量", LeaderboardLimit); AroundMeRange = DrawEditableRow("附近范围", AroundMeRange); SeasonId = DrawEditableRow("赛季 ID", SeasonId); SeasonHistoryLimit = DrawEditableRow("历史条数", SeasonHistoryLimit); GUILayout.BeginHorizontal(); DrawButton("获取 Top", GetTopAsync, Brisk.IsLoggedIn); DrawButton("获取我的排名", GetMyRankAsync, Brisk.IsLoggedIn); DrawButton("获取我附近的排名", GetAroundMeAsync, Brisk.IsLoggedIn); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); DrawButton("提交分数", SubmitScoreAsync, Brisk.IsLoggedIn); DrawButton("获取当前赛季", GetCurrentSeasonAsync, Brisk.IsLoggedIn); DrawButton("获取赛季历史", GetSeasonHistoryAsync, Brisk.IsLoggedIn); DrawButton("获取赛季详情", GetSeasonDetailAsync, Brisk.IsLoggedIn); GUILayout.EndHorizontal(); EndSection(); } private void DrawArchiveSection() { BeginSection("云存档"); ArchiveSlotNo = DrawEditableRow("槽位号", ArchiveSlotNo); ArchiveBaseVersion = DrawEditableRow("基准版本", ArchiveBaseVersion); ArchiveContent = DrawTextAreaRow("存档内容", ArchiveContent, 90f); GUILayout.BeginHorizontal(); DrawButton("获取槽位列表", GetArchiveSlotsAsync, Brisk.IsLoggedIn); DrawButton("获取存档元信息", GetArchiveMetaAsync, Brisk.IsLoggedIn); DrawButton("上传文本存档", UploadArchiveAsync, Brisk.IsLoggedIn); DrawButton("下载存档", DownloadArchiveAsync, Brisk.IsLoggedIn); GUILayout.EndHorizontal(); EndSection(); } private void DrawSpaceSection() { BeginSection("玩家空间"); SpacePlayerId = DrawEditableRow("空间 PlayerId", SpacePlayerId); SpaceLoginProvider = DrawEditableRow("空间登录提供方", SpaceLoginProvider); SpaceLoginUserId = DrawEditableRow("空间登录用户 ID", SpaceLoginUserId); SpacePayloadText = DrawTextAreaRow("空间 Payload 文本", SpacePayloadText, 90f); GUILayout.BeginHorizontal(); DrawButton("按 PlayerId 获取空间", GetSpaceByPlayerIdAsync, Brisk.IsLoggedIn); DrawButton("按登录身份获取空间", GetSpaceByLoginAsync, Brisk.IsLoggedIn); DrawButton("按 PlayerId 获取统计", GetSpaceStatsByPlayerIdAsync, Brisk.IsLoggedIn); DrawButton("按登录身份获取统计", GetSpaceStatsByLoginAsync, Brisk.IsLoggedIn); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); DrawButton("按 PlayerId 点赞", LikeByPlayerIdAsync, Brisk.IsLoggedIn); DrawButton("按 PlayerId 取消点赞", UnlikeByPlayerIdAsync, Brisk.IsLoggedIn); DrawButton("按登录身份点赞", LikeByLoginAsync, Brisk.IsLoggedIn); DrawButton("按登录身份取消点赞", UnlikeByLoginAsync, Brisk.IsLoggedIn); GUILayout.EndHorizontal(); GUILayout.BeginHorizontal(); DrawButton("更新我的空间", UpdateMySpaceAsync, Brisk.IsLoggedIn); DrawButton("获取我的访客", GetMyVisitsAsync, Brisk.IsLoggedIn); GUILayout.EndHorizontal(); EndSection(); } private void DrawOutputSection() { BeginSection("输出"); GUILayout.Label("最近一次结果", GUI.skin.label); _resultScroll = GUILayout.BeginScrollView(_resultScroll, GUILayout.Height(240f)); GUILayout.TextArea(_resultText, GUILayout.ExpandHeight(true)); GUILayout.EndScrollView(); GUILayout.Space(8f); GUILayout.Label("最近一次错误", GUI.skin.label); GUILayout.TextArea(string.IsNullOrWhiteSpace(_lastErrorText) ? "暂无错误。" : _lastErrorText, GUILayout.Height(90f)); GUILayout.Space(8f); GUILayout.Label("事件日志", GUI.skin.label); _logScroll = GUILayout.BeginScrollView(_logScroll, GUILayout.Height(220f)); GUILayout.TextArea(_eventLogs.Count == 0 ? "暂无事件。" : string.Join("\n", _eventLogs.ToArray()), GUILayout.ExpandHeight(true)); GUILayout.EndScrollView(); EndSection(); } private async Task InitializeAsync() { await Brisk.InitializeAsync(new BriskOptions { BaseUrl = BaseUrl, GameKey = GameKey, ClientVersion = ClientVersion, DeviceId = DeviceId, ValidateSessionOnInitialize = ValidateSessionOnInitialize, ExitHandler = HandleExitRequested }); SetResult("初始化结果", Brisk.Bootstrap); } private async Task ReinitializeAsync() { if (Brisk.IsInitialized) { Brisk.Shutdown(); Log("重新初始化前已先关闭 SDK。"); } await InitializeAsync(); } private async Task LoginWithUserIdAsync() { var result = await Brisk.Auth.LoginWithUserIdAsync(LoginProvider, LoginUserId, CreateProfile()); ApplyIdentity(result.PlayerId, result.LoginProvider, result.LoginUserId); SetResult("按用户 ID 登录结果", result); } private async Task LoginWithCodeAsync() { var result = await Brisk.Auth.LoginWithCodeAsync(LoginProvider, LoginCode, CreateProfile()); ApplyIdentity(result.PlayerId, result.LoginProvider, result.LoginUserId); SetResult("按 Code 登录结果", result); } private async Task LogoutAsync() { await Brisk.Auth.LogoutAsync(); SetResult("登出结果", new Dictionary { { "logged_in", Brisk.IsLoggedIn }, { "player_id", Brisk.PlayerId } }); } private async Task GetMeAsync() { var me = await Brisk.Player.GetMeAsync(); ApplyIdentity(me.PlayerId, me.LoginProvider, me.LoginUserId); SetResult("当前玩家信息", me); } private async Task GetConfigAsync() { var config = await Brisk.Config.GetCurrentAsync(); SetResult("当前配置", config); } private async Task GetAnnouncementsAsync() { _announcementCache = await Brisk.Announcements.GetListAsync(); if (_announcementCache.Count > 0 && string.IsNullOrWhiteSpace(AnnouncementId)) { AnnouncementId = _announcementCache[0].Id.ToString(); } SetResult("公告列表", _announcementCache); } private async Task MarkAnnouncementAsync() { var id = ParseRequiredLong(AnnouncementId, nameof(AnnouncementId)); await Brisk.Announcements.MarkReadAsync(id); SetResult("公告已标记已读", new Dictionary { { "announcement_id", id } }); } private async Task MarkFirstCachedAnnouncementAsync() { if (_announcementCache.Count == 0) { throw new InvalidOperationException("公告缓存为空,请先执行“获取公告列表”。"); } var id = _announcementCache[0].Id; AnnouncementId = id.ToString(); await Brisk.Announcements.MarkReadAsync(id); SetResult("首条缓存公告已标记已读", _announcementCache[0]); } private async Task GetTopAsync() { var result = await Brisk.Leaderboard.GetTopAsync(RankKey, ParseOptionalInt(LeaderboardLimit, 10)); SetResult("排行榜 Top", result); } private async Task GetMyRankAsync() { var result = await Brisk.Leaderboard.GetMeAsync(RankKey); SetResult("我的排行榜信息", result); } private async Task GetAroundMeAsync() { var result = await Brisk.Leaderboard.GetAroundMeAsync(RankKey, ParseOptionalInt(AroundMeRange, 5)); SetResult("我附近的排行榜", result); } private async Task SubmitScoreAsync() { var score = ParseRequiredLong(SubmitScoreValue, nameof(SubmitScoreValue)); await Brisk.Leaderboard.SubmitScoreAsync(RankKey, score); SetResult("分数提交结果", new Dictionary { { "rank_key", RankKey }, { "score", score } }); } private async Task GetCurrentSeasonAsync() { var result = await Brisk.Leaderboard.GetCurrentSeasonAsync(RankKey); SeasonId = result == null ? SeasonId : result.SeasonId; SetResult("当前赛季", result); } private async Task GetSeasonHistoryAsync() { _seasonHistoryCache = await Brisk.Leaderboard.GetSeasonHistoryAsync(RankKey, ParseOptionalInt(SeasonHistoryLimit, 20)); if (_seasonHistoryCache.Count > 0 && string.IsNullOrWhiteSpace(SeasonId)) { SeasonId = _seasonHistoryCache[0].SeasonId; } SetResult("赛季历史", _seasonHistoryCache); } private async Task GetSeasonDetailAsync() { var result = await Brisk.Leaderboard.GetSeasonHistoryDetailAsync(RankKey, SeasonId, ParseOptionalInt(SeasonHistoryLimit, 20)); SetResult("赛季详情", result); } private async Task GetArchiveSlotsAsync() { var result = await Brisk.Archive.GetSlotsAsync(); SetResult("存档槽位列表", result); } private async Task GetArchiveMetaAsync() { var result = await Brisk.Archive.GetMetaAsync(ParseRequiredInt(ArchiveSlotNo, nameof(ArchiveSlotNo))); ArchiveBaseVersion = result == null ? ArchiveBaseVersion : result.Version.ToString(); SetResult("存档元信息", result); } private async Task UploadArchiveAsync() { var slotNo = ParseRequiredInt(ArchiveSlotNo, nameof(ArchiveSlotNo)); var baseVersion = ParseNullableInt(ArchiveBaseVersion); var bytes = Encoding.UTF8.GetBytes(ArchiveContent ?? string.Empty); var result = await Brisk.Archive.UploadAsync(slotNo, bytes, baseVersion); ArchiveBaseVersion = result == null ? ArchiveBaseVersion : result.Version.ToString(); SetResult("存档上传结果", result); } private async Task DownloadArchiveAsync() { var result = await Brisk.Archive.DownloadAsync(ParseRequiredInt(ArchiveSlotNo, nameof(ArchiveSlotNo))); ArchiveBaseVersion = result == null ? ArchiveBaseVersion : result.Version.ToString(); var output = new Dictionary { { "version", result.Version }, { "checksum", result.Checksum }, { "byte_length", result.Bytes == null ? 0 : result.Bytes.Length }, { "text_preview", result.Bytes == null ? string.Empty : Encoding.UTF8.GetString(result.Bytes) } }; SetResult("存档下载结果", output); } private async Task GetSpaceByPlayerIdAsync() { var result = await Brisk.Space.GetByPlayerIdAsync(SpacePlayerId); SetResult("按 PlayerId 获取空间", result); } private async Task GetSpaceByLoginAsync() { var result = await Brisk.Space.GetByLoginIdentityAsync(SpaceLoginProvider, SpaceLoginUserId); SetResult("按登录身份获取空间", result); } private async Task GetSpaceStatsByPlayerIdAsync() { var result = await Brisk.Space.GetStatsByPlayerIdAsync(SpacePlayerId); SetResult("按 PlayerId 获取空间统计", result); } private async Task GetSpaceStatsByLoginAsync() { var result = await Brisk.Space.GetStatsByLoginIdentityAsync(SpaceLoginProvider, SpaceLoginUserId); SetResult("按登录身份获取空间统计", result); } private async Task LikeByPlayerIdAsync() { await Brisk.Space.LikeByPlayerIdAsync(SpacePlayerId); SetResult("按 PlayerId 点赞结果", new Dictionary { { "player_id", SpacePlayerId } }); } private async Task UnlikeByPlayerIdAsync() { await Brisk.Space.UnlikeByPlayerIdAsync(SpacePlayerId); SetResult("按 PlayerId 取消点赞结果", new Dictionary { { "player_id", SpacePlayerId } }); } private async Task LikeByLoginAsync() { await Brisk.Space.LikeByLoginIdentityAsync(SpaceLoginProvider, SpaceLoginUserId); SetResult("按登录身份点赞结果", new Dictionary { { "login_provider", SpaceLoginProvider }, { "login_user_id", SpaceLoginUserId } }); } private async Task UnlikeByLoginAsync() { await Brisk.Space.UnlikeByLoginIdentityAsync(SpaceLoginProvider, SpaceLoginUserId); SetResult("按登录身份取消点赞结果", new Dictionary { { "login_provider", SpaceLoginProvider }, { "login_user_id", SpaceLoginUserId } }); } private async Task UpdateMySpaceAsync() { var payload = new Dictionary { { "sample_text", SpacePayloadText }, { "updated_at", DateTimeOffset.UtcNow.ToString("O") }, { "player_id", Brisk.PlayerId }, { "rank_key", RankKey } }; await Brisk.Space.UpdateMyAsync(payload); SetResult("更新我的空间结果", payload); } private async Task GetMyVisitsAsync() { var result = await Brisk.Space.GetMyVisitsAsync(); SetResult("我的访客列表", result); } private async Task RunSmokeFlowAsync() { await InitializeAsync(); if (!Brisk.IsLoggedIn) { await LoginWithUserIdAsync(); } await GetMeAsync(); await GetConfigAsync(); await GetTopAsync(); await GetAnnouncementsAsync(); await GetArchiveSlotsAsync(); await GetMyVisitsAsync(); } private void DrawButton(string label, Func action, bool enabled = true) { var previous = GUI.enabled; GUI.enabled = previous && !_isBusy && enabled; if (GUILayout.Button(label, GUILayout.Height(30f))) { RunAction(label, action); } GUI.enabled = previous; } private void RunAction(string actionName, Func action) { if (_isBusy) { return; } RunActionAsync(actionName, action); } private async void RunActionAsync(string actionName, Func action) { _isBusy = true; _busyAction = actionName; _statusText = "执行中: " + actionName; _lastErrorText = string.Empty; Log("开始执行: " + actionName); try { await action(); _statusText = "执行成功: " + actionName; Log("执行成功: " + actionName); } catch (Exception exception) { _statusText = "执行失败: " + actionName; _lastErrorText = FormatException(exception); Log("执行失败: " + actionName + " | " + exception.GetType().Name + " | " + exception.Message); Debug.LogException(exception, this); } finally { _busyAction = string.Empty; _isBusy = false; } } private void SetResult(string title, object value) { _resultText = title + "\n" + new string('=', title.Length) + "\n" + FormatValue(value); } private void ApplyIdentity(string playerId, string loginProvider, string loginUserId) { if (!string.IsNullOrWhiteSpace(playerId)) { SpacePlayerId = playerId; } if (!string.IsNullOrWhiteSpace(loginProvider)) { SpaceLoginProvider = loginProvider; LoginProvider = loginProvider; } if (!string.IsNullOrWhiteSpace(loginUserId)) { SpaceLoginUserId = loginUserId; LoginUserId = loginUserId; } } private void ApplyCurrentIdentityToSpace() { if (Brisk.Identity != null) { ApplyIdentity(Brisk.Identity.PlayerId, Brisk.Identity.LoginProvider, Brisk.Identity.LoginUserId); return; } if (Brisk.IsInitialized) { ApplyIdentity(Brisk.PlayerId, LoginProvider, LoginUserId); } } private BriskProfile CreateProfile() { return new BriskProfile { Nickname = Nickname, AvatarUrl = AvatarUrl }; } private void HandleInitialized() { Log("事件: 初始化完成"); } private void HandleLoggedIn() { Log("事件: 登录完成"); ApplyCurrentIdentityToSpace(); } private void HandleLoggedOut() { Log("事件: 登出完成"); } private void HandleAuthExpired(BriskAuthExpiredException exception) { Log("事件: 登录态失效 | " + exception.Message); } private void HandleBlockingError(BriskBlockingException exception) { Log("事件: 严重阻断错误 | " + exception.Message); } private void HandleExitRequested() { Log("Brisk 阻断流程请求退出。"); } private void Log(string message) { var line = "[" + DateTime.Now.ToString("HH:mm:ss") + "] " + message; _eventLogs.Add(line); if (_eventLogs.Count > 120) { _eventLogs.RemoveAt(0); } _logScroll.y = float.MaxValue; } private static int ParseRequiredInt(string value, string fieldName) { if (!int.TryParse(value, out var result)) { throw new InvalidOperationException(fieldName + " 必须是合法整数。"); } return result; } private static long ParseRequiredLong(string value, string fieldName) { if (!long.TryParse(value, out var result)) { throw new InvalidOperationException(fieldName + " 必须是合法整数。"); } return result; } private static int ParseOptionalInt(string value, int fallback) { return int.TryParse(value, out var result) ? result : fallback; } private static int? ParseNullableInt(string value) { if (string.IsNullOrWhiteSpace(value)) { return null; } return int.TryParse(value, out var result) ? result : (int?)null; } private static string FormatException(Exception exception) { if (exception == null) { return "未知错误。"; } var builder = new StringBuilder(); var current = exception; while (current != null) { builder.AppendLine(current.GetType().Name + ": " + current.Message); current = current.InnerException; } return builder.ToString().TrimEnd(); } private static string FormatValue(object value) { var builder = new StringBuilder(); AppendValue(builder, value, 0, 0); return builder.ToString().TrimEnd(); } private static void AppendValue(StringBuilder builder, object value, int indent, int depth) { if (depth > 5) { builder.AppendLine(Indent(indent) + "..."); return; } if (value == null) { builder.AppendLine(Indent(indent) + "null"); return; } if (value is string stringValue) { builder.AppendLine(Indent(indent) + stringValue); return; } if (value is byte[] bytes) { builder.AppendLine(Indent(indent) + "byte[" + bytes.Length + "]"); return; } var type = value.GetType(); if (type.IsPrimitive || value is decimal) { builder.AppendLine(Indent(indent) + value); return; } if (value is IDictionary dictionary) { foreach (DictionaryEntry entry in dictionary) { builder.AppendLine(Indent(indent) + entry.Key + ":"); AppendValue(builder, entry.Value, indent + 2, depth + 1); } return; } if (value is IEnumerable enumerable) { var index = 0; foreach (var item in enumerable) { builder.AppendLine(Indent(indent) + "[" + index + "]"); AppendValue(builder, item, indent + 2, depth + 1); index++; } if (index == 0) { builder.AppendLine(Indent(indent) + "(empty)"); } return; } var fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public); if (fields.Length == 0) { builder.AppendLine(Indent(indent) + value); return; } foreach (var field in fields) { builder.AppendLine(Indent(indent) + field.Name + ":"); AppendValue(builder, field.GetValue(value), indent + 2, depth + 1); } } private static string Indent(int indent) { return new string(' ', indent); } private static void BeginSection(string title) { GUILayout.Space(10f); GUILayout.BeginVertical(GUI.skin.box); GUILayout.Label(title, GUI.skin.label); GUILayout.Space(4f); } private static void EndSection() { GUILayout.EndVertical(); } private static string DrawEditableRow(string label, string value) { GUILayout.BeginHorizontal(); GUILayout.Label(label, GUILayout.Width(180f)); var next = GUILayout.TextField(value ?? string.Empty); GUILayout.EndHorizontal(); return next; } private static string DrawTextAreaRow(string label, string value, float height) { GUILayout.Label(label, GUILayout.Width(180f)); return GUILayout.TextArea(value ?? string.Empty, GUILayout.Height(height)); } private static bool DrawToggleRow(string label, bool value) { GUILayout.BeginHorizontal(); GUILayout.Label(label, GUILayout.Width(180f)); var next = GUILayout.Toggle(value, value ? "Enabled" : "Disabled"); GUILayout.EndHorizontal(); return next; } private static void DrawReadOnlyRow(string label, string value) { GUILayout.BeginHorizontal(); GUILayout.Label(label, GUILayout.Width(180f)); GUILayout.Label(string.IsNullOrWhiteSpace(value) ? "-" : value, GUI.skin.box); GUILayout.EndHorizontal(); } }