【关卡】 关卡怪物导出工具
parent
1276dec6db
commit
01b9b43978
|
|
@ -0,0 +1,314 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Framework;
|
||||
using Gameplay.Level;
|
||||
using PhxhSDK.Phxh;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
public static class LevelMonsterCountExporter
|
||||
{
|
||||
private const string ExportFolderName = "LevelMonsterCountExports";
|
||||
private const string MenuRoot = "Tools/*关卡数据工具/";
|
||||
private const string FactionName = "敌方";
|
||||
private const int UngroupedId = -1;
|
||||
|
||||
private static string ExportFolderPath =>
|
||||
Path.GetFullPath(Path.Combine(Application.dataPath, "..", ExportFolderName));
|
||||
|
||||
[MenuItem(MenuRoot + "关卡配置导出")]
|
||||
private static void ExportLevelMonsterCounts()
|
||||
{
|
||||
var levelTable = EditorTableManager.instance.tables.Level;
|
||||
var monsterTable = EditorTableManager.instance.tables.Monster;
|
||||
var monsterClassTable = EditorTableManager.instance.tables.MonsterClass;
|
||||
|
||||
if (levelTable == null || monsterTable == null || monsterClassTable == null)
|
||||
{
|
||||
EditorUtility.DisplayDialog("错误", "无法获取关卡/怪物/怪物职业配置表", "确定");
|
||||
return;
|
||||
}
|
||||
|
||||
var levelList = levelTable.DataList;
|
||||
if (levelList == null || levelList.Count == 0)
|
||||
{
|
||||
EditorUtility.DisplayDialog("错误", "关卡配置表为空", "确定");
|
||||
return;
|
||||
}
|
||||
|
||||
var results = new Dictionary<RowKey, JobCount>();
|
||||
|
||||
try
|
||||
{
|
||||
for (var index = 0; index < levelList.Count; index++)
|
||||
{
|
||||
var levelCfg = levelList[index];
|
||||
var progress = (index + 1f) / levelList.Count;
|
||||
EditorUtility.DisplayProgressBar("导出关卡配置", $"处理关卡: {levelCfg.Id}", progress);
|
||||
|
||||
var levelJsonName = levelCfg.LevelData;
|
||||
if (string.IsNullOrEmpty(levelJsonName))
|
||||
{
|
||||
Debug.LogWarning($"关卡配置 LevelData 为空: {levelCfg.Id}");
|
||||
continue;
|
||||
}
|
||||
|
||||
var levelPath = string.Format(Constants.LEVEL_CONFIG_FORMAT_PATH, levelJsonName);
|
||||
if (!File.Exists(levelPath))
|
||||
{
|
||||
Debug.LogWarning($"关卡数据不存在: {levelPath}");
|
||||
continue;
|
||||
}
|
||||
|
||||
LevelData levelData;
|
||||
try
|
||||
{
|
||||
levelData = JsonHelper.LoadJson<LevelData>(levelPath);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError($"解析关卡数据失败: {levelPath}\n{ex}");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (levelData?.npcInfos == null || levelData.npcInfos.Count == 0)
|
||||
continue;
|
||||
|
||||
foreach (var npc in levelData.npcInfos)
|
||||
{
|
||||
if (npc == null)
|
||||
continue;
|
||||
|
||||
var monster = monsterTable.GetOrDefault(npc.id);
|
||||
if (monster == null)
|
||||
{
|
||||
Debug.LogWarning($"怪物表未找到 MonsterID: {npc.id} (关卡 {levelCfg.Id})");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!TryResolveJobCategory(monster, monsterClassTable, out var category))
|
||||
continue;
|
||||
|
||||
var groupIds = GetGroupIds(npc.groups);
|
||||
foreach (var groupId in groupIds)
|
||||
{
|
||||
var key = new RowKey(levelCfg.Id, groupId);
|
||||
if (!results.TryGetValue(key, out var count))
|
||||
{
|
||||
count = new JobCount();
|
||||
results[key] = count;
|
||||
}
|
||||
|
||||
count.Add(category);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
EditorUtility.ClearProgressBar();
|
||||
}
|
||||
|
||||
WriteCsv(results);
|
||||
}
|
||||
|
||||
[MenuItem(MenuRoot + "打开关卡配置导出目录")]
|
||||
private static void OpenExportFolder()
|
||||
{
|
||||
Directory.CreateDirectory(ExportFolderPath);
|
||||
EditorUtility.RevealInFinder(ExportFolderPath);
|
||||
}
|
||||
|
||||
private static void WriteCsv(Dictionary<RowKey, JobCount> results)
|
||||
{
|
||||
Directory.CreateDirectory(ExportFolderPath);
|
||||
|
||||
var baseFileName = $"LevelMonsterCount_{DateTime.Now:yyyyMMdd}.csv";
|
||||
var savePath = GetUniqueFilePath(baseFileName);
|
||||
|
||||
var sb = new StringBuilder();
|
||||
sb.AppendLine("关卡ID,阵营,怪物组,指挥总数,支援总数,特射总数,侦察总数,压制总数,载具总数,建筑总数");
|
||||
|
||||
foreach (var pair in results.OrderBy(r => r.Key.LevelId).ThenBy(r => r.Key.GroupId))
|
||||
{
|
||||
var key = pair.Key;
|
||||
var count = pair.Value;
|
||||
sb.AppendLine(string.Join(",",
|
||||
Csv(key.LevelId.ToString()),
|
||||
Csv(FactionName),
|
||||
Csv(key.GroupId.ToString()),
|
||||
Csv(count.Commander.ToString()),
|
||||
Csv(count.Support.ToString()),
|
||||
Csv(count.Shoot.ToString()),
|
||||
Csv(count.Scout.ToString()),
|
||||
Csv(count.Suppress.ToString()),
|
||||
Csv(count.Vehicle.ToString()),
|
||||
Csv(count.Building.ToString())));
|
||||
}
|
||||
|
||||
File.WriteAllText(savePath, sb.ToString(), Encoding.UTF8);
|
||||
Debug.Log($"关卡怪物职业统计已导出: {savePath}");
|
||||
EditorUtility.RevealInFinder(savePath);
|
||||
}
|
||||
|
||||
private static string GetUniqueFilePath(string fileName)
|
||||
{
|
||||
var savePath = Path.Combine(ExportFolderPath, fileName);
|
||||
if (!File.Exists(savePath))
|
||||
return savePath;
|
||||
|
||||
var nameWithoutExt = Path.GetFileNameWithoutExtension(fileName);
|
||||
var extension = Path.GetExtension(fileName);
|
||||
var index = 1;
|
||||
do
|
||||
{
|
||||
savePath = Path.Combine(ExportFolderPath, $"{nameWithoutExt}_{index}{extension}");
|
||||
index++;
|
||||
} while (File.Exists(savePath));
|
||||
|
||||
return savePath;
|
||||
}
|
||||
|
||||
private static IEnumerable<int> GetGroupIds(List<int> groups)
|
||||
{
|
||||
if (groups == null || groups.Count == 0)
|
||||
return new[] { UngroupedId };
|
||||
|
||||
return groups.Distinct().ToArray();
|
||||
}
|
||||
|
||||
private static bool TryResolveJobCategory(cfg.MonsterCfg.DataMonster monster,
|
||||
cfg.MonsterCfg.MonsterClass monsterClassTable,
|
||||
out JobCategory category)
|
||||
{
|
||||
category = JobCategory.Unknown;
|
||||
|
||||
switch ((int)monster.NpcType)
|
||||
{
|
||||
case 0:
|
||||
{
|
||||
var classData = monsterClassTable.GetOrDefault(monster.MonsterClassID);
|
||||
if (classData == null)
|
||||
{
|
||||
Debug.LogWarning($"怪物职业表未找到 MonsterClassID: {monster.MonsterClassID} (MonsterID {monster.MonsterID})");
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (classData.Job)
|
||||
{
|
||||
case cfg.CharacterCfg.Ejob.Job_Commander:
|
||||
category = JobCategory.Commander;
|
||||
return true;
|
||||
case cfg.CharacterCfg.Ejob.Job_Support:
|
||||
category = JobCategory.Support;
|
||||
return true;
|
||||
case cfg.CharacterCfg.Ejob.Job_Shoot:
|
||||
category = JobCategory.Shoot;
|
||||
return true;
|
||||
case cfg.CharacterCfg.Ejob.Job_Scout:
|
||||
category = JobCategory.Scout;
|
||||
return true;
|
||||
case cfg.CharacterCfg.Ejob.Job_Suppress:
|
||||
category = JobCategory.Suppress;
|
||||
return true;
|
||||
case cfg.CharacterCfg.Ejob.Job_Vehicle:
|
||||
category = JobCategory.Vehicle;
|
||||
return true;
|
||||
default:
|
||||
Debug.LogWarning($"未知职业: {classData.Job} (MonsterID {monster.MonsterID})");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
category = JobCategory.Vehicle;
|
||||
return true;
|
||||
case 3:
|
||||
category = JobCategory.Building;
|
||||
return true;
|
||||
default:
|
||||
// Debug.LogWarning($"未知NpcType: {monster.NpcType} (MonsterID {monster.MonsterID})");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static string Csv(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
return string.Empty;
|
||||
|
||||
if (value.Contains(",") || value.Contains("\"") || value.Contains("\n") || value.Contains("\r"))
|
||||
return "\"" + value.Replace("\"", "\"\"") + "\"";
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private readonly struct RowKey : IEquatable<RowKey>
|
||||
{
|
||||
public readonly int LevelId;
|
||||
public readonly int GroupId;
|
||||
|
||||
public RowKey(int levelId, int groupId)
|
||||
{
|
||||
LevelId = levelId;
|
||||
GroupId = groupId;
|
||||
}
|
||||
|
||||
public bool Equals(RowKey other) => LevelId == other.LevelId && GroupId == other.GroupId;
|
||||
public override bool Equals(object obj) => obj is RowKey other && Equals(other);
|
||||
public override int GetHashCode() => (LevelId * 397) ^ GroupId;
|
||||
}
|
||||
|
||||
private sealed class JobCount
|
||||
{
|
||||
public int Commander;
|
||||
public int Support;
|
||||
public int Shoot;
|
||||
public int Scout;
|
||||
public int Suppress;
|
||||
public int Vehicle;
|
||||
public int Building;
|
||||
|
||||
public void Add(JobCategory category)
|
||||
{
|
||||
switch (category)
|
||||
{
|
||||
case JobCategory.Commander:
|
||||
Commander++;
|
||||
break;
|
||||
case JobCategory.Support:
|
||||
Support++;
|
||||
break;
|
||||
case JobCategory.Shoot:
|
||||
Shoot++;
|
||||
break;
|
||||
case JobCategory.Scout:
|
||||
Scout++;
|
||||
break;
|
||||
case JobCategory.Suppress:
|
||||
Suppress++;
|
||||
break;
|
||||
case JobCategory.Vehicle:
|
||||
Vehicle++;
|
||||
break;
|
||||
case JobCategory.Building:
|
||||
Building++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum JobCategory
|
||||
{
|
||||
Unknown,
|
||||
Commander,
|
||||
Support,
|
||||
Shoot,
|
||||
Scout,
|
||||
Suppress,
|
||||
Vehicle,
|
||||
Building
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
fileFormatVersion: 2
|
||||
guid: a8dac2b2263604bf19db0c9ffc6c22d1
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Loading…
Reference in New Issue