From ad3b1d24f2022dc10c8d8d0060673fa3e2f9ff6a Mon Sep 17 00:00:00 2001 From: barry Date: Mon, 20 Jul 2026 10:48:50 +0800 Subject: [PATCH] =?UTF-8?q?fix=EF=BC=9A1=E3=80=81=E5=A4=9A=E8=AF=AD?= =?UTF-8?q?=E8=A8=80=E4=BC=98=E5=8C=96=E3=80=822=E3=80=81=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E6=AD=BB=E5=B1=80=E6=A3=80=E6=9F=A5=E8=84=9A=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Android/addressables_content_state.bin | Bin 1454 -> 1454 bytes .../ThirdParty/WebView/WebViewObject.cs | 158 +++--- .../ThirdParty/WebView/WebViewObject1.cs | 174 +++--- Assets/Editor/ArrowGameValidator.cs | 507 ++++++++++++++++++ Assets/Editor/ArrowGameValidator.cs.meta | 3 + Assets/Plugins/Android/AndroidManifest.xml | 6 + .../res/values/crashlytics_build_id.xml | 2 +- .../TextAsset/localization.json | 20 +- .../ModuleUI/GameResult/GameResultUI.cs | 2 +- ProjectSettings/ProjectSettings.asset | 6 +- 10 files changed, 697 insertions(+), 181 deletions(-) create mode 100644 Assets/Editor/ArrowGameValidator.cs create mode 100644 Assets/Editor/ArrowGameValidator.cs.meta diff --git a/Assets/AddressableAssetsData/Android/addressables_content_state.bin b/Assets/AddressableAssetsData/Android/addressables_content_state.bin index f16099f200e287e1e704c960164933ffecb529d6..7cc977df27a4df24c768b31fbb49694bf19e8258 100644 GIT binary patch delta 14 VcmZ3-y^ecBGb5wn<`zaR762t&1U~=( delta 14 VcmZ3-y^ecBGb5wH<`zaR762ty1U>)& diff --git a/Assets/DFFFQTVUv39wKIBSDK/ThirdParty/WebView/WebViewObject.cs b/Assets/DFFFQTVUv39wKIBSDK/ThirdParty/WebView/WebViewObject.cs index caa40f5..1779e40 100644 --- a/Assets/DFFFQTVUv39wKIBSDK/ThirdParty/WebView/WebViewObject.cs +++ b/Assets/DFFFQTVUv39wKIBSDK/ThirdParty/WebView/WebViewObject.cs @@ -195,9 +195,9 @@ public class WebViewObject : MonoBehaviour case "SetKeyboardVisible": SetKeyboardVisible(s.Substring(i + 1)); break; - case "RequestFileChooserPermissions": - RequestFileChooserPermissions(); - break; + // case "RequestFileChooserPermissions": + // // RequestFileChooserPermissions(); + // break; } } } @@ -219,82 +219,82 @@ public class WebViewObject : MonoBehaviour } /// Called from Java native plugin to request permissions for the file chooser. - public void RequestFileChooserPermissions() - { - var permissions = new List(); - using (var version = new AndroidJavaClass("android.os.Build$VERSION")) - { - if (version.GetStatic("SDK_INT") >= 33) - { - if (!Permission.HasUserAuthorizedPermission("android.permission.READ_MEDIA_IMAGES")) - { - permissions.Add("android.permission.READ_MEDIA_IMAGES"); - } - if (!Permission.HasUserAuthorizedPermission("android.permission.READ_MEDIA_VIDEO")) - { - permissions.Add("android.permission.READ_MEDIA_VIDEO"); - } - if (!Permission.HasUserAuthorizedPermission("android.permission.READ_MEDIA_AUDIO")) - { - permissions.Add("android.permission.READ_MEDIA_AUDIO"); - } - } - else - { - if (!Permission.HasUserAuthorizedPermission(Permission.ExternalStorageRead)) - { - permissions.Add(Permission.ExternalStorageRead); - } - if (!Permission.HasUserAuthorizedPermission(Permission.ExternalStorageWrite)) - { - permissions.Add(Permission.ExternalStorageWrite); - } - } - } - if (!Permission.HasUserAuthorizedPermission(Permission.Camera)) - { - permissions.Add(Permission.Camera); - } - if (permissions.Count > 0) - { -#if UNITY_2020_2_OR_NEWER - var grantedCount = 0; - var deniedCount = 0; - var callbacks = new PermissionCallbacks(); - callbacks.PermissionGranted += (permission) => - { - grantedCount++; - if (grantedCount + deniedCount == permissions.Count) - { - StartCoroutine(CallOnRequestFileChooserPermissionsResult(grantedCount == permissions.Count)); - } - }; - callbacks.PermissionDenied += (permission) => - { - deniedCount++; - if (grantedCount + deniedCount == permissions.Count) - { - StartCoroutine(CallOnRequestFileChooserPermissionsResult(grantedCount == permissions.Count)); - } - }; - callbacks.PermissionDeniedAndDontAskAgain += (permission) => - { - deniedCount++; - if (grantedCount + deniedCount == permissions.Count) - { - StartCoroutine(CallOnRequestFileChooserPermissionsResult(grantedCount == permissions.Count)); - } - }; - Permission.RequestUserPermissions(permissions.ToArray(), callbacks); -#else - StartCoroutine(RequestFileChooserPermissionsCoroutine(permissions.ToArray())); -#endif - } - else - { - StartCoroutine(CallOnRequestFileChooserPermissionsResult(true)); - } - } +// public void RequestFileChooserPermissions() +// { +// var permissions = new List(); +// using (var version = new AndroidJavaClass("android.os.Build$VERSION")) +// { +// if (version.GetStatic("SDK_INT") >= 33) +// { +// if (!Permission.HasUserAuthorizedPermission("android.permission.READ_MEDIA_IMAGES")) +// { +// permissions.Add("android.permission.READ_MEDIA_IMAGES"); +// } +// if (!Permission.HasUserAuthorizedPermission("android.permission.READ_MEDIA_VIDEO")) +// { +// permissions.Add("android.permission.READ_MEDIA_VIDEO"); +// } +// if (!Permission.HasUserAuthorizedPermission("android.permission.READ_MEDIA_AUDIO")) +// { +// permissions.Add("android.permission.READ_MEDIA_AUDIO"); +// } +// } +// else +// { +// if (!Permission.HasUserAuthorizedPermission(Permission.ExternalStorageRead)) +// { +// permissions.Add(Permission.ExternalStorageRead); +// } +// if (!Permission.HasUserAuthorizedPermission(Permission.ExternalStorageWrite)) +// { +// permissions.Add(Permission.ExternalStorageWrite); +// } +// } +// } +// if (!Permission.HasUserAuthorizedPermission(Permission.Camera)) +// { +// permissions.Add(Permission.Camera); +// } +// if (permissions.Count > 0) +// { +// #if UNITY_2020_2_OR_NEWER +// var grantedCount = 0; +// var deniedCount = 0; +// var callbacks = new PermissionCallbacks(); +// callbacks.PermissionGranted += (permission) => +// { +// grantedCount++; +// if (grantedCount + deniedCount == permissions.Count) +// { +// StartCoroutine(CallOnRequestFileChooserPermissionsResult(grantedCount == permissions.Count)); +// } +// }; +// callbacks.PermissionDenied += (permission) => +// { +// deniedCount++; +// if (grantedCount + deniedCount == permissions.Count) +// { +// StartCoroutine(CallOnRequestFileChooserPermissionsResult(grantedCount == permissions.Count)); +// } +// }; +// callbacks.PermissionDeniedAndDontAskAgain += (permission) => +// { +// deniedCount++; +// if (grantedCount + deniedCount == permissions.Count) +// { +// StartCoroutine(CallOnRequestFileChooserPermissionsResult(grantedCount == permissions.Count)); +// } +// }; +// Permission.RequestUserPermissions(permissions.ToArray(), callbacks); +// #else +// StartCoroutine(RequestFileChooserPermissionsCoroutine(permissions.ToArray())); +// #endif +// } +// else +// { +// StartCoroutine(CallOnRequestFileChooserPermissionsResult(true)); +// } +// } #if UNITY_2020_2_OR_NEWER #else diff --git a/Assets/DFFFQTVUv39wKIBSDK/ThirdParty/WebView/WebViewObject1.cs b/Assets/DFFFQTVUv39wKIBSDK/ThirdParty/WebView/WebViewObject1.cs index 712587f..dc196c9 100644 --- a/Assets/DFFFQTVUv39wKIBSDK/ThirdParty/WebView/WebViewObject1.cs +++ b/Assets/DFFFQTVUv39wKIBSDK/ThirdParty/WebView/WebViewObject1.cs @@ -214,9 +214,9 @@ namespace Gree.UnityWebView case "SetKeyboardVisible": SetKeyboardVisible(s.Substring(i + 1)); break; - case "RequestFileChooserPermissions": - RequestFileChooserPermissions(); - break; + // case "RequestFileChooserPermissions": + // RequestFileChooserPermissions(); + // break; } } } @@ -239,90 +239,90 @@ namespace Gree.UnityWebView } /// Called from Java native plugin to request permissions for the file chooser. - public void RequestFileChooserPermissions() - { - var permissions = new List(); - using (var version = new AndroidJavaClass("android.os.Build$VERSION")) - { - if (version.GetStatic("SDK_INT") >= 33) - { - if (!Permission.HasUserAuthorizedPermission("android.permission.READ_MEDIA_IMAGES")) - { - permissions.Add("android.permission.READ_MEDIA_IMAGES"); - } - if (!Permission.HasUserAuthorizedPermission("android.permission.READ_MEDIA_VIDEO")) - { - permissions.Add("android.permission.READ_MEDIA_VIDEO"); - } - if (!Permission.HasUserAuthorizedPermission("android.permission.READ_MEDIA_AUDIO")) - { - permissions.Add("android.permission.READ_MEDIA_AUDIO"); - } - } - else - { - if (!Permission.HasUserAuthorizedPermission(Permission.ExternalStorageRead)) - { - permissions.Add(Permission.ExternalStorageRead); - } - if (!Permission.HasUserAuthorizedPermission(Permission.ExternalStorageWrite)) - { - permissions.Add(Permission.ExternalStorageWrite); - } - } - } -#if UNITYWEBVIEW_ANDROID_ENABLE_CAMERA - if (!Permission.HasUserAuthorizedPermission(Permission.Camera) && mAllowVideoCapture) - { - permissions.Add(Permission.Camera); - } -#endif -#if UNITYWEBVIEW_ANDROID_ENABLE_MICROPHONE - if (!Permission.HasUserAuthorizedPermission(Permission.Microphone) && mAllowAudioCapture) - { - permissions.Add(Permission.Microphone); - } -#endif - if (permissions.Count > 0) - { -#if UNITY_2020_2_OR_NEWER - var grantedCount = 0; - var deniedCount = 0; - var callbacks = new PermissionCallbacks(); - callbacks.PermissionGranted += (permission) => - { - grantedCount++; - if (grantedCount + deniedCount == permissions.Count) - { - StartCoroutine(CallOnRequestFileChooserPermissionsResult(grantedCount == permissions.Count)); - } - }; - callbacks.PermissionDenied += (permission) => - { - deniedCount++; - if (grantedCount + deniedCount == permissions.Count) - { - StartCoroutine(CallOnRequestFileChooserPermissionsResult(grantedCount == permissions.Count)); - } - }; - callbacks.PermissionDeniedAndDontAskAgain += (permission) => - { - deniedCount++; - if (grantedCount + deniedCount == permissions.Count) - { - StartCoroutine(CallOnRequestFileChooserPermissionsResult(grantedCount == permissions.Count)); - } - }; - Permission.RequestUserPermissions(permissions.ToArray(), callbacks); -#else - StartCoroutine(RequestFileChooserPermissionsCoroutine(permissions.ToArray())); -#endif - } - else - { - StartCoroutine(CallOnRequestFileChooserPermissionsResult(true)); - } - } +// public void RequestFileChooserPermissions() +// { +// var permissions = new List(); +// using (var version = new AndroidJavaClass("android.os.Build$VERSION")) +// { +// if (version.GetStatic("SDK_INT") >= 33) +// { +// if (!Permission.HasUserAuthorizedPermission("android.permission.READ_MEDIA_IMAGES")) +// { +// permissions.Add("android.permission.READ_MEDIA_IMAGES"); +// } +// if (!Permission.HasUserAuthorizedPermission("android.permission.READ_MEDIA_VIDEO")) +// { +// permissions.Add("android.permission.READ_MEDIA_VIDEO"); +// } +// if (!Permission.HasUserAuthorizedPermission("android.permission.READ_MEDIA_AUDIO")) +// { +// permissions.Add("android.permission.READ_MEDIA_AUDIO"); +// } +// } +// else +// { +// if (!Permission.HasUserAuthorizedPermission(Permission.ExternalStorageRead)) +// { +// permissions.Add(Permission.ExternalStorageRead); +// } +// if (!Permission.HasUserAuthorizedPermission(Permission.ExternalStorageWrite)) +// { +// permissions.Add(Permission.ExternalStorageWrite); +// } +// } +// } +// #if UNITYWEBVIEW_ANDROID_ENABLE_CAMERA +// if (!Permission.HasUserAuthorizedPermission(Permission.Camera) && mAllowVideoCapture) +// { +// permissions.Add(Permission.Camera); +// } +// #endif +// #if UNITYWEBVIEW_ANDROID_ENABLE_MICROPHONE +// if (!Permission.HasUserAuthorizedPermission(Permission.Microphone) && mAllowAudioCapture) +// { +// permissions.Add(Permission.Microphone); +// } +// #endif +// if (permissions.Count > 0) +// { +// #if UNITY_2020_2_OR_NEWER +// var grantedCount = 0; +// var deniedCount = 0; +// var callbacks = new PermissionCallbacks(); +// callbacks.PermissionGranted += (permission) => +// { +// grantedCount++; +// if (grantedCount + deniedCount == permissions.Count) +// { +// StartCoroutine(CallOnRequestFileChooserPermissionsResult(grantedCount == permissions.Count)); +// } +// }; +// callbacks.PermissionDenied += (permission) => +// { +// deniedCount++; +// if (grantedCount + deniedCount == permissions.Count) +// { +// StartCoroutine(CallOnRequestFileChooserPermissionsResult(grantedCount == permissions.Count)); +// } +// }; +// callbacks.PermissionDeniedAndDontAskAgain += (permission) => +// { +// deniedCount++; +// if (grantedCount + deniedCount == permissions.Count) +// { +// StartCoroutine(CallOnRequestFileChooserPermissionsResult(grantedCount == permissions.Count)); +// } +// }; +// Permission.RequestUserPermissions(permissions.ToArray(), callbacks); +// #else +// StartCoroutine(RequestFileChooserPermissionsCoroutine(permissions.ToArray())); +// #endif +// } +// else +// { +// StartCoroutine(CallOnRequestFileChooserPermissionsResult(true)); +// } +// } #if UNITY_2020_2_OR_NEWER #else diff --git a/Assets/Editor/ArrowGameValidator.cs b/Assets/Editor/ArrowGameValidator.cs new file mode 100644 index 0000000..9d81044 --- /dev/null +++ b/Assets/Editor/ArrowGameValidator.cs @@ -0,0 +1,507 @@ +#if UNITY_EDITOR +using System.Collections.Generic; +using System.IO; +using System.Linq; +using ChillConnect; +using UnityEditor; +using UnityEngine; + +/// +/// 关卡死局检测工具(Unity编辑器下使用) +/// +public class ArrowGameValidator +{ + // ========== 方向转换工具 ========== + + /// + /// 方向字符串转行列偏移量 + /// + private static (int dRow, int dCol) DirToOffset(string dir) + { + return dir switch + { + "up" => (-1, 0), + "down" => (1, 0), + "left" => (0, -1), + "right" => (0, 1), + _ => (0, 0) + }; + } + + /// + /// pointId 转 (row, col) + /// + private static (int row, int col) PointIdToRowCol(int pointId, int totalCols) + { + int row = pointId / totalCols; + int col = pointId % totalCols; + return (row, col); + } + + /// + /// (row, col) 转 pointId + /// + private static int RowColToPointId(int row, int col, int totalCols) + { + return row * totalCols + col; + } + + // ========== 核心检测逻辑 ========== + + /// + /// 构建所有箭头占用的网格点集合 + /// 返回: Dictionary> —— 每个网格点被哪些箭头占用 + /// + private static Dictionary> BuildGridOccupation(LevelConfig config) + { + var gridOccupation = new Dictionary>(); + int totalCols = config.gridCols; + + foreach (var arrow in config.arrows) + { + int curPointId = arrow.startPoint; + + // 起点也加入占用 + if (!gridOccupation.ContainsKey(curPointId)) + gridOccupation[curPointId] = new List(); + gridOccupation[curPointId].Add(arrow.id); + + // 沿路径逐步添加占用 + foreach (string dir in arrow.path) + { + var (row, col) = PointIdToRowCol(curPointId, totalCols); + var (dRow, dCol) = DirToOffset(dir); + row += dRow; + col += dCol; + + // 边界检查 + if (row < 0 || row >= config.gridRows || col < 0 || col >= config.gridCols) + { + Debug.LogWarning($"箭头 {arrow.id} 路径超出网格边界"); + break; + } + + curPointId = RowColToPointId(row, col, totalCols); + + if (!gridOccupation.ContainsKey(curPointId)) + gridOccupation[curPointId] = new List(); + gridOccupation[curPointId].Add(arrow.id); + } + } + + return gridOccupation; + } + + /// + /// 获取箭头的最终方向(头部朝向) + /// + private static string GetFinalDirection(ArrowConfig arrow) + { + if (arrow.path != null && arrow.path.Count > 0) + return arrow.path[arrow.path.Count - 1]; + return "up"; // 默认向上 + } + + /// + /// 获取箭头头部的网格位置 (pointId) + /// + private static int GetArrowHeadPointId(ArrowConfig arrow, int totalCols) + { + int curPointId = arrow.startPoint; + foreach (string dir in arrow.path) + { + var (row, col) = PointIdToRowCol(curPointId, totalCols); + var (dRow, dCol) = DirToOffset(dir); + curPointId = RowColToPointId(row + dRow, col + dCol, totalCols); + } + return curPointId; + } + + /// + /// 构建依赖图:检测每个箭头前方被哪些其他箭头阻挡 + /// 返回: 邻接表 —— blockedBy[arrowId] = List of arrowIds that block it + /// 即:要消除 arrowId,必须先消除 blockedBy[arrowId] 中的箭头 + /// + private static Dictionary> BuildDependencyGraph( + LevelConfig config, + Dictionary> gridOccupation) + { + var blockedBy = new Dictionary>(); + int totalCols = config.gridCols; + int totalRows = config.gridRows; + + // 初始化 + foreach (var arrow in config.arrows) + { + blockedBy[arrow.id] = new HashSet(); + } + + foreach (var arrow in config.arrows) + { + string finalDir = GetFinalDirection(arrow); + int headPointId = GetArrowHeadPointId(arrow, totalCols); + var (headRow, headCol) = PointIdToRowCol(headPointId, totalCols); + var (dRow, dCol) = DirToOffset(finalDir); + + // 沿最终方向逐格扫描 + int curRow = headRow + dRow; + int curCol = headCol + dCol; + + while (curRow >= 0 && curRow < totalRows && curCol >= 0 && curCol < totalCols) + { + int curPointId = RowColToPointId(curRow, curCol, totalCols); + + // 检查该网格点是否被其他箭头占用 + if (gridOccupation.TryGetValue(curPointId, out var occupyingArrows)) + { + foreach (int occupyingArrowId in occupyingArrows) + { + // 不能阻挡自己 + if (occupyingArrowId != arrow.id) + { + // occupyingArrow 阻挡了当前箭头 + // 要消除当前箭头,必须先消除 occupyingArrow + blockedBy[arrow.id].Add(occupyingArrowId); + } + } + } + + curRow += dRow; + curCol += dCol; + } + } + + return blockedBy; + } + + /// + /// 使用拓扑排序检测是否有环(死局) + /// 返回: (isSolvable, unsolvableArrowIds) + /// + private static (bool isSolvable, List unsolvableArrowIds) DetectDeadlock( + LevelConfig config, + Dictionary> blockedBy) + { + // 计算每个箭头的入度(被多少箭头阻挡) + var inDegree = new Dictionary(); + foreach (var arrow in config.arrows) + { + inDegree[arrow.id] = 0; + } + + // 构建反向邻接表:谁依赖于我 + var dependents = new Dictionary>(); + foreach (var arrow in config.arrows) + { + dependents[arrow.id] = new List(); + } + + foreach (var kvp in blockedBy) + { + int arrowId = kvp.Key; + foreach (int blockerId in kvp.Value) + { + // arrowId 依赖 blockerId + // blockerId -> arrowId + dependents[blockerId].Add(arrowId); + inDegree[arrowId]++; + } + } + + // Kahn 拓扑排序 + var queue = new Queue(); + foreach (var arrow in config.arrows) + { + if (inDegree[arrow.id] == 0) + { + queue.Enqueue(arrow.id); + } + } + + int removedCount = 0; + while (queue.Count > 0) + { + int current = queue.Dequeue(); + removedCount++; + + foreach (int dependent in dependents[current]) + { + inDegree[dependent]--; + if (inDegree[dependent] == 0) + { + queue.Enqueue(dependent); + } + } + } + + // 如果所有箭头都能被移除,则可解 + if (removedCount == config.arrows.Count) + { + return (true, new List()); + } + + // 找出无法移除的箭头(入度 > 0 的) + var unsolvableArrows = new List(); + foreach (var arrow in config.arrows) + { + if (inDegree[arrow.id] > 0) + { + unsolvableArrows.Add(arrow.id); + } + } + + return (false, unsolvableArrows); + } + + /// + /// 从环路中提取所有涉及的箭头ID + /// + private static List ExtractCycleArrowIds( + LevelConfig config, + Dictionary> blockedBy, + List unsolvableArrows) + { + // 从不可解的箭头出发,DFS找出所有在环路中的箭头 + var allCycleNodes = new HashSet(); + + foreach (int startId in unsolvableArrows) + { + if (allCycleNodes.Contains(startId)) + continue; + + // 尝试从 startId 出发找环 + var visited = new HashSet(); + var inStack = new HashSet(); + var path = new List(); + + if (FindCycle(startId, blockedBy, visited, inStack, path, out var cycleNodes)) + { + foreach (int node in cycleNodes) + { + allCycleNodes.Add(node); + } + } + } + + return allCycleNodes.ToList(); + } + + /// + /// DFS寻找环路 + /// + private static bool FindCycle( + int current, + Dictionary> blockedBy, + HashSet visited, + HashSet inStack, + List path, + out List cycleNodes) + { + visited.Add(current); + inStack.Add(current); + path.Add(current); + + if (blockedBy.TryGetValue(current, out var blockers)) + { + foreach (int blocker in blockers) + { + if (!visited.Contains(blocker)) + { + if (FindCycle(blocker, blockedBy, visited, inStack, path, out cycleNodes)) + { + inStack.Remove(current); + path.RemoveAt(path.Count - 1); + return true; + } + } + else if (inStack.Contains(blocker)) + { + // 找到环 + int cycleStartIndex = path.IndexOf(blocker); + cycleNodes = path.GetRange(cycleStartIndex, path.Count - cycleStartIndex); + inStack.Remove(current); + path.RemoveAt(path.Count - 1); + return true; + } + } + } + + inStack.Remove(current); + path.RemoveAt(path.Count - 1); + cycleNodes = null; + return false; + } + + // ========== 公共接口 ========== + + /// + /// 检测单个关卡是否可解 + /// + public static ValidationResult ValidateLevel(LevelConfig config) + { + var result = new ValidationResult + { + levelId = config.levelId, + levelName = config.levelName + }; + + if (config.arrows == null || config.arrows.Count == 0) + { + result.isSolvable = true; + result.message = "关卡无箭头"; + return result; + } + + // 1. 构建网格占用 + var gridOccupation = BuildGridOccupation(config); + + // 2. 构建依赖图 + var blockedBy = BuildDependencyGraph(config, gridOccupation); + + // 3. 拓扑排序检测 + var (isSolvable, unsolvableArrows) = DetectDeadlock(config, blockedBy); + + result.isSolvable = isSolvable; + result.blockedBy = blockedBy; + + if (isSolvable) + { + result.message = "✅ 可解"; + } + else + { + // 找出环路中的具体箭头 + var cycleArrows = ExtractCycleArrowIds(config, blockedBy, unsolvableArrows); + result.unsolvableArrowIds = cycleArrows.OrderBy(x => x).ToList(); + result.message = $"❌ 死局,无法消除的箭头: [{string.Join(", ", result.unsolvableArrowIds)}]"; + } + + return result; + } + + /// + /// 批量检测关卡目录下所有JSON文件 + /// + public static void ValidateAllLevels(string folderPath) + { + if (!Directory.Exists(folderPath)) + { + Debug.LogError($"目录不存在: {folderPath}"); + return; + } + + string[] jsonFiles = Directory.GetFiles(folderPath, "*.json"); + int totalCount = jsonFiles.Length; + int solvableCount = 0; + int unsolvableCount = 0; + var unsolvableResults = new List(); + + foreach (string filePath in jsonFiles) + { + try + { + string json = File.ReadAllText(filePath); + LevelConfig config = JsonUtility.FromJson(json); + + var result = ValidateLevel(config); + + if (result.isSolvable) + solvableCount++; + else + { + unsolvableCount++; + unsolvableResults.Add(result); + } + + Debug.Log($"[{filePath}] {result.message}"); + } + catch (System.Exception e) + { + Debug.LogError($"解析文件失败: {filePath}, 错误: {e.Message}"); + } + } + + Debug.Log("========== 检测汇总 =========="); + Debug.Log($"总关卡数: {totalCount}"); + Debug.Log($"可解关卡: {solvableCount}"); + Debug.Log($"死局关卡: {unsolvableCount}"); + + if (unsolvableResults.Count > 0) + { + Debug.Log("========== 死局关卡详情 =========="); + foreach (var result in unsolvableResults) + { + Debug.Log($"关卡 {result.levelId} ({result.levelName}): {result.message}"); + } + } + } + + // ========== 编辑器菜单 ========== + + [MenuItem("Tools/箭头游戏/检测单个关卡(选择JSON文件)")] + public static void ValidateSelectedLevel() + { + string path = EditorUtility.OpenFilePanel("选择关卡JSON", Application.streamingAssetsPath, "json"); + if (string.IsNullOrEmpty(path)) return; + + try + { + string json = File.ReadAllText(path); + LevelConfig config = JsonUtility.FromJson(json); + + var result = ValidateLevel(config); + + Debug.Log($"========== 关卡 {result.levelId} ({result.levelName}) 检测结果 =========="); + Debug.Log(result.message); + + if (!result.isSolvable) + { + Debug.Log($"环路箭头: [{string.Join(", ", result.unsolvableArrowIds)}]"); + + // 打印依赖详情 + Debug.Log("========== 依赖关系 =========="); + foreach (int id in result.unsolvableArrowIds) + { + if (result.blockedBy.TryGetValue(id, out var blockers)) + { + Debug.Log($"箭头 {id} 被阻挡于: [{string.Join(", ", blockers)}]"); + } + } + } + + EditorUtility.DisplayDialog("检测完成", result.message, "确定"); + } + catch (System.Exception e) + { + Debug.LogError($"检测失败: {e.Message}"); + EditorUtility.DisplayDialog("错误", $"检测失败: {e.Message}", "确定"); + } + } + + [MenuItem("Tools/箭头游戏/批量检测所有关卡")] + public static void ValidateAllLevelsMenu() + { + string folderPath = EditorUtility.OpenFolderPanel("选择关卡目录", Application.streamingAssetsPath, ""); + if (string.IsNullOrEmpty(folderPath)) return; + + ValidateAllLevels(folderPath); + EditorUtility.DisplayDialog("批量检测完成", "请查看Console输出结果", "确定"); + } + + // ========== 数据结构 ========== + + /// + /// 检测结果 + /// + public class ValidationResult + { + public int levelId; + public string levelName; + public bool isSolvable; + public string message; + public List unsolvableArrowIds = new List(); + public Dictionary> blockedBy; + } +} + +#endif \ No newline at end of file diff --git a/Assets/Editor/ArrowGameValidator.cs.meta b/Assets/Editor/ArrowGameValidator.cs.meta new file mode 100644 index 0000000..96a503a --- /dev/null +++ b/Assets/Editor/ArrowGameValidator.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: da19b18902544191a3062014594d3b22 +timeCreated: 1784305307 \ No newline at end of file diff --git a/Assets/Plugins/Android/AndroidManifest.xml b/Assets/Plugins/Android/AndroidManifest.xml index 8e9002b..6b07b56 100644 --- a/Assets/Plugins/Android/AndroidManifest.xml +++ b/Assets/Plugins/Android/AndroidManifest.xml @@ -4,6 +4,12 @@ xmlns:android="http://schemas.android.com/apk/res/android" package="com.unity3d.player" xmlns:tools="http://schemas.android.com/tools"> + + + + + + diff --git a/Assets/Plugins/Android/FirebaseCrashlytics.androidlib/res/values/crashlytics_build_id.xml b/Assets/Plugins/Android/FirebaseCrashlytics.androidlib/res/values/crashlytics_build_id.xml index f8eea68..4f4e422 100644 --- a/Assets/Plugins/Android/FirebaseCrashlytics.androidlib/res/values/crashlytics_build_id.xml +++ b/Assets/Plugins/Android/FirebaseCrashlytics.androidlib/res/values/crashlytics_build_id.xml @@ -1 +1 @@ -a3b8638f-c8ff-4e13-89ae-85cb2b148835 +d4a07f28-7baa-4f99-aa00-e8f80b7c45a1 diff --git a/Assets/Resources/ChillConnectAssets/TextAsset/localization.json b/Assets/Resources/ChillConnectAssets/TextAsset/localization.json index 1a3e866..820b4ce 100644 --- a/Assets/Resources/ChillConnectAssets/TextAsset/localization.json +++ b/Assets/Resources/ChillConnectAssets/TextAsset/localization.json @@ -12,7 +12,7 @@ "failed_save": "Failed to save information., please try again!", "un_lock": "To be unlocked", "exchange_succ": "Claim({0})", - "exchange_success": "Claim X{0}", + "redeem_success": "Claim X{0}", "free_revive": "Free Revive", "revive": "Revive", "need_lv_text":"Level {0}", @@ -40,7 +40,7 @@ "failed_save": "情報の保存に失敗しました。再度お試しください!", "un_lock": "解放待ち", "exchange_succ": "受け取る({0})", - "exchange_success": "X{0}を受け取る", + "redeem_success": "X{0}を受け取る", "free_revive": "無料復活", "revive": "復活", "need_lv_text": "レベル{0}", @@ -69,7 +69,7 @@ "failed_save": "정보 저장에 실패했습니다. 다시 시도해 주세요!", "un_lock": "잠금 해제 예정", "exchange_succ": "수령({0})", - "exchange_success": "X{0} 수령", + "redeem_success": "X{0} 수령", "free_revive": "무료 부활", "revive": "부활", "need_lv_text": "레벨 {0}", @@ -97,7 +97,7 @@ "failed_save": "Не удалось сохранить данные, попробуйте снова!", "un_lock": "Будет разблокировано", "exchange_succ": "Забрать({0})", - "exchange_success": "Забрать X{0}", + "redeem_success": "Забрать X{0}", "free_revive": "Бесплатное воскрешение", "revive": "Воскреснуть", "need_lv_text": "Уровень {0}", @@ -125,7 +125,7 @@ "failed_save": "जानकारी सहेजने में विफल, कृपया पुनः प्रयास करें!", "un_lock": "जल्द अनलॉक होगा", "exchange_succ": "दावा करें({0})", - "exchange_success": "X{0} दावा करें", + "redeem_success": "X{0} दावा करें", "free_revive": "मुफ्त पुनर्जीवन", "revive": "पुनर्जीवित करें", "need_lv_text": "स्तर {0}", @@ -141,7 +141,7 @@ "exchange_success": "विनिमय सफल" }, "pt": { - "copy_succeed": "Cópia realizada com sucesso", + "copy_succeed": " Cópia realizada com sucesso", "not_ads": "Os anúncios ainda não estão prontos!", "try_send": "Enviando e-mail…", "no_network": "Sua conexão com a internet está instável!", @@ -153,7 +153,7 @@ "failed_save": "Falha ao salvar as informações, tente novamente!", "un_lock": "A ser desbloqueado", "exchange_succ": "Resgatar({0})", - "exchange_success": "Resgatar X{0}", + "redeem_success": "Resgatar X{0}", "free_revive": "Reviver grátis", "revive": "Reviver", "need_lv_text": "Nível {0}", @@ -181,7 +181,7 @@ "failed_save": "Speichern fehlgeschlagen, bitte erneut versuchen!", "un_lock": "Wird freigeschaltet", "exchange_succ": "Einlösen({0})", - "exchange_success": "X{0} einlösen", + "redeem_success": "X{0} einlösen", "free_revive": "Kostenlose Wiederbelebung", "revive": "Wiederbeleben", "need_lv_text": "Level {0}", @@ -209,7 +209,7 @@ "failed_save": "Échec de l'enregistrement, veuillez réessayer !", "un_lock": "À débloquer", "exchange_succ": "Réclamer({0})", - "exchange_success": "Réclamer X{0}", + "redeem_success": "Réclamer X{0}", "free_revive": "Réanimation gratuite", "revive": "Réanimer", "need_lv_text": "Niveau {0}", @@ -237,7 +237,7 @@ "failed_save": "Error al guardar la información, inténtalo de nuevo!", "un_lock": "Pendiente de desbloquear", "exchange_succ": "Reclamar({0})", - "exchange_success": "Reclamar X{0}", + "redeem_success": "Reclamar X{0}", "free_revive": "Revivir gratis", "revive": "Revivir", "need_lv_text": "Nivel {0}", diff --git a/Assets/Scripts/ModuleUI/GameResult/GameResultUI.cs b/Assets/Scripts/ModuleUI/GameResult/GameResultUI.cs index 48bd334..43d85f0 100644 --- a/Assets/Scripts/ModuleUI/GameResult/GameResultUI.cs +++ b/Assets/Scripts/ModuleUI/GameResult/GameResultUI.cs @@ -189,7 +189,7 @@ namespace ChillConnect }); - ui.btn_mult.GetChild("title").text = Language.GetContentParams("exchange_success", AwardRate); + ui.btn_mult.GetChild("title").text = Language.GetContentParams(" redeem_success", AwardRate); if (SuccessDatas.IsH5Reward) ui.com_ch.visible = false; diff --git a/ProjectSettings/ProjectSettings.asset b/ProjectSettings/ProjectSettings.asset index 97e6f82..fd5a0d1 100644 --- a/ProjectSettings/ProjectSettings.asset +++ b/ProjectSettings/ProjectSettings.asset @@ -141,7 +141,7 @@ PlayerSettings: loadStoreDebugModeEnabled: 0 visionOSBundleVersion: 1.0 tvOSBundleVersion: 1.0 - bundleVersion: 1.0.0 + bundleVersion: 1.0.1 preloadedAssets: [] metroInputSource: 0 wsaTransparentSwapchain: 0 @@ -171,7 +171,7 @@ PlayerSettings: iPhone: 2 tvOS: 0 overrideDefaultApplicationIdentifier: 1 - AndroidBundleVersionCode: 1 + AndroidBundleVersionCode: 2 AndroidMinSdkVersion: 24 AndroidTargetSdkVersion: 36 AndroidPreferredInstallLocation: 1 @@ -849,7 +849,7 @@ PlayerSettings: webGLMemoryGeometricGrowthCap: 96 webGLPowerPreference: 2 scriptingDefineSymbols: - Android: UNITY_VISUAL_SCRIPTING;ES3_TMPRO;ES3_UGUI;USE_ADDRESSABLES;GAME_RELEASE + Android: UNITY_VISUAL_SCRIPTING;ES3_TMPRO;ES3_UGUI;USE_ADDRESSABLES;GAME_RELEASE1 EmbeddedLinux: UNITY_VISUAL_SCRIPTING;ES3_TMPRO;ES3_UGUI LinuxHeadlessSimulation: UNITY_VISUAL_SCRIPTING;ES3_TMPRO;ES3_UGUI Nintendo Switch: UNITY_VISUAL_SCRIPTING;ES3_TMPRO;ES3_UGUI