fix:1、多语言优化。2、添加死局检查脚本

This commit is contained in:
2026-07-20 10:48:50 +08:00
parent c2ad44d755
commit ad3b1d24f2
10 changed files with 697 additions and 181 deletions
+79 -79
View File
@@ -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<string>();
using (var version = new AndroidJavaClass("android.os.Build$VERSION"))
{
if (version.GetStatic<int>("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<string>();
// using (var version = new AndroidJavaClass("android.os.Build$VERSION"))
// {
// if (version.GetStatic<int>("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
@@ -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<string>();
using (var version = new AndroidJavaClass("android.os.Build$VERSION"))
{
if (version.GetStatic<int>("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<string>();
// using (var version = new AndroidJavaClass("android.os.Build$VERSION"))
// {
// if (version.GetStatic<int>("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
+507
View File
@@ -0,0 +1,507 @@
#if UNITY_EDITOR
using System.Collections.Generic;
using System.IO;
using System.Linq;
using ChillConnect;
using UnityEditor;
using UnityEngine;
/// <summary>
/// 关卡死局检测工具(Unity编辑器下使用)
/// </summary>
public class ArrowGameValidator
{
// ========== 方向转换工具 ==========
/// <summary>
/// 方向字符串转行列偏移量
/// </summary>
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)
};
}
/// <summary>
/// pointId 转 (row, col)
/// </summary>
private static (int row, int col) PointIdToRowCol(int pointId, int totalCols)
{
int row = pointId / totalCols;
int col = pointId % totalCols;
return (row, col);
}
/// <summary>
/// (row, col) 转 pointId
/// </summary>
private static int RowColToPointId(int row, int col, int totalCols)
{
return row * totalCols + col;
}
// ========== 核心检测逻辑 ==========
/// <summary>
/// 构建所有箭头占用的网格点集合
/// 返回: Dictionary<pointId, List<arrowId>> —— 每个网格点被哪些箭头占用
/// </summary>
private static Dictionary<int, List<int>> BuildGridOccupation(LevelConfig config)
{
var gridOccupation = new Dictionary<int, List<int>>();
int totalCols = config.gridCols;
foreach (var arrow in config.arrows)
{
int curPointId = arrow.startPoint;
// 起点也加入占用
if (!gridOccupation.ContainsKey(curPointId))
gridOccupation[curPointId] = new List<int>();
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<int>();
gridOccupation[curPointId].Add(arrow.id);
}
}
return gridOccupation;
}
/// <summary>
/// 获取箭头的最终方向(头部朝向)
/// </summary>
private static string GetFinalDirection(ArrowConfig arrow)
{
if (arrow.path != null && arrow.path.Count > 0)
return arrow.path[arrow.path.Count - 1];
return "up"; // 默认向上
}
/// <summary>
/// 获取箭头头部的网格位置 (pointId)
/// </summary>
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;
}
/// <summary>
/// 构建依赖图:检测每个箭头前方被哪些其他箭头阻挡
/// 返回: 邻接表 —— blockedBy[arrowId] = List of arrowIds that block it
/// 即:要消除 arrowId,必须先消除 blockedBy[arrowId] 中的箭头
/// </summary>
private static Dictionary<int, HashSet<int>> BuildDependencyGraph(
LevelConfig config,
Dictionary<int, List<int>> gridOccupation)
{
var blockedBy = new Dictionary<int, HashSet<int>>();
int totalCols = config.gridCols;
int totalRows = config.gridRows;
// 初始化
foreach (var arrow in config.arrows)
{
blockedBy[arrow.id] = new HashSet<int>();
}
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;
}
/// <summary>
/// 使用拓扑排序检测是否有环(死局)
/// 返回: (isSolvable, unsolvableArrowIds)
/// </summary>
private static (bool isSolvable, List<int> unsolvableArrowIds) DetectDeadlock(
LevelConfig config,
Dictionary<int, HashSet<int>> blockedBy)
{
// 计算每个箭头的入度(被多少箭头阻挡)
var inDegree = new Dictionary<int, int>();
foreach (var arrow in config.arrows)
{
inDegree[arrow.id] = 0;
}
// 构建反向邻接表:谁依赖于我
var dependents = new Dictionary<int, List<int>>();
foreach (var arrow in config.arrows)
{
dependents[arrow.id] = new List<int>();
}
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<int>();
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<int>());
}
// 找出无法移除的箭头(入度 > 0 的)
var unsolvableArrows = new List<int>();
foreach (var arrow in config.arrows)
{
if (inDegree[arrow.id] > 0)
{
unsolvableArrows.Add(arrow.id);
}
}
return (false, unsolvableArrows);
}
/// <summary>
/// 从环路中提取所有涉及的箭头ID
/// </summary>
private static List<int> ExtractCycleArrowIds(
LevelConfig config,
Dictionary<int, HashSet<int>> blockedBy,
List<int> unsolvableArrows)
{
// 从不可解的箭头出发,DFS找出所有在环路中的箭头
var allCycleNodes = new HashSet<int>();
foreach (int startId in unsolvableArrows)
{
if (allCycleNodes.Contains(startId))
continue;
// 尝试从 startId 出发找环
var visited = new HashSet<int>();
var inStack = new HashSet<int>();
var path = new List<int>();
if (FindCycle(startId, blockedBy, visited, inStack, path, out var cycleNodes))
{
foreach (int node in cycleNodes)
{
allCycleNodes.Add(node);
}
}
}
return allCycleNodes.ToList();
}
/// <summary>
/// DFS寻找环路
/// </summary>
private static bool FindCycle(
int current,
Dictionary<int, HashSet<int>> blockedBy,
HashSet<int> visited,
HashSet<int> inStack,
List<int> path,
out List<int> 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;
}
// ========== 公共接口 ==========
/// <summary>
/// 检测单个关卡是否可解
/// </summary>
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;
}
/// <summary>
/// 批量检测关卡目录下所有JSON文件
/// </summary>
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<ValidationResult>();
foreach (string filePath in jsonFiles)
{
try
{
string json = File.ReadAllText(filePath);
LevelConfig config = JsonUtility.FromJson<LevelConfig>(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<LevelConfig>(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输出结果", "确定");
}
// ========== 数据结构 ==========
/// <summary>
/// 检测结果
/// </summary>
public class ValidationResult
{
public int levelId;
public string levelName;
public bool isSolvable;
public string message;
public List<int> unsolvableArrowIds = new List<int>();
public Dictionary<int, HashSet<int>> blockedBy;
}
}
#endif
+3
View File
@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: da19b18902544191a3062014594d3b22
timeCreated: 1784305307
@@ -4,6 +4,12 @@
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.unity3d.player"
xmlns:tools="http://schemas.android.com/tools">
<!-- 强制移除webview aar自带的媒体权限,解决Play未声明报错 -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" tools:node="remove"/>
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" tools:node="remove"/>
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" tools:node="remove"/>
<application>
<activity android:name="com.unity3d.player.UnityPlayerActivity"
android:theme="@style/UnityThemeSelector">
@@ -1 +1 @@
<?xml version="1.0" encoding="utf-8"?><resources><string name="com.crashlytics.android.build_id" translatable="false">a3b8638f-c8ff-4e13-89ae-85cb2b148835</string></resources>
<?xml version="1.0" encoding="utf-8"?><resources><string name="com.crashlytics.android.build_id" translatable="false">d4a07f28-7baa-4f99-aa00-e8f80b7c45a1</string></resources>
@@ -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}",
@@ -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;