Unity 6.5 업그레이드와 신규 프로토타입 기반 구축
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Northbound.Data;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Northbound.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// ScriptableObject를 단일 원본으로 사용하는 게임 데이터의 참조와 값 범위를 검사합니다.
|
||||
/// </summary>
|
||||
public static class ScriptableObjectDataValidator
|
||||
{
|
||||
private const string DataRoot = "Assets/Data/ScriptableObjects";
|
||||
|
||||
[MenuItem("Tools/Northbound/Data/Validate All ScriptableObjects")]
|
||||
public static void ValidateAll()
|
||||
{
|
||||
int errors = 0;
|
||||
int warnings = 0;
|
||||
|
||||
List<TowerData> towers = LoadAll<TowerData>();
|
||||
List<MonsterData> monsters = LoadAll<MonsterData>();
|
||||
List<CreepData> creeps = LoadAll<CreepData>();
|
||||
List<PlayerData> players = LoadAll<PlayerData>();
|
||||
List<UpgradeData> upgrades = LoadAll<UpgradeData>();
|
||||
List<DefaultSettingsData> settings = LoadAll<DefaultSettingsData>();
|
||||
|
||||
ValidateUniqueIds(towers, data => data.id, "Tower", ref errors);
|
||||
ValidateUniqueIds(monsters, data => data.id, "Monster", ref errors);
|
||||
ValidateUniqueIds(creeps, data => data.id, "Creep", ref errors);
|
||||
ValidateUniqueIds(players, data => data.id, "Player", ref errors);
|
||||
ValidateUniqueIds(upgrades, data => data.id, "Upgrade", ref errors);
|
||||
ValidateUniqueIds(settings, data => data.id, "DefaultSettings", ref errors);
|
||||
|
||||
ValidateTowers(towers, ref errors, ref warnings);
|
||||
ValidateUnits(monsters, data => data.id, data => data.maxHp, data => data.moveSpeed,
|
||||
data => data.cost, data => data.weight, "Monster", ref errors);
|
||||
ValidateUnits(creeps, data => data.id, data => data.maxHp, data => data.moveSpeed,
|
||||
data => data.cost, data => data.weight, "Creep", ref errors);
|
||||
ValidatePlayers(players, ref errors);
|
||||
ValidateUpgrades(upgrades, ref errors, ref warnings);
|
||||
ValidateSettings(settings, ref errors, ref warnings);
|
||||
|
||||
string summary = $"[SO Data Validation] {towers.Count} towers, {monsters.Count} monsters, " +
|
||||
$"{creeps.Count} creeps, {players.Count} players, {upgrades.Count} upgrades, " +
|
||||
$"{settings.Count} settings — {errors} errors, {warnings} warnings";
|
||||
|
||||
if (errors > 0)
|
||||
Debug.LogError(summary);
|
||||
else if (warnings > 0)
|
||||
Debug.LogWarning(summary);
|
||||
else
|
||||
Debug.Log(summary);
|
||||
}
|
||||
|
||||
private static List<T> LoadAll<T>() where T : ScriptableObject
|
||||
{
|
||||
return AssetDatabase.FindAssets($"t:{typeof(T).Name}", new[] { DataRoot })
|
||||
.Select(AssetDatabase.GUIDToAssetPath)
|
||||
.Select(AssetDatabase.LoadAssetAtPath<T>)
|
||||
.Where(asset => asset != null)
|
||||
.OrderBy(asset => asset.name)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
private static void ValidateUniqueIds<T>(IEnumerable<T> assets, Func<T, int> getId,
|
||||
string dataType, ref int errors) where T : UnityEngine.Object
|
||||
{
|
||||
var ids = new Dictionary<int, T>();
|
||||
foreach (T asset in assets)
|
||||
{
|
||||
int id = getId(asset);
|
||||
if (id <= 0)
|
||||
{
|
||||
Error($"{dataType} ID는 1 이상이어야 합니다: {asset.name}", asset, ref errors);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ids.TryGetValue(id, out T existing))
|
||||
Error($"{dataType} ID {id}가 중복됩니다: {existing.name}, {asset.name}", asset, ref errors);
|
||||
else
|
||||
ids.Add(id, asset);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateTowers(List<TowerData> towers, ref int errors, ref int warnings)
|
||||
{
|
||||
var towerIds = towers.Select(data => data.id).ToHashSet();
|
||||
foreach (TowerData data in towers)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(data.buildingName))
|
||||
Error($"Tower {data.id}의 표시 이름이 비어 있습니다.", data, ref errors);
|
||||
if (data.prefab == null)
|
||||
Error($"Tower {data.id}의 프리팹이 없습니다.", data, ref errors);
|
||||
if (data.mana < 0 || data.manpower < 0)
|
||||
Error($"Tower {data.id}의 비용은 음수일 수 없습니다.", data, ref errors);
|
||||
if (data.maxHp <= 0)
|
||||
Error($"Tower {data.id}의 체력은 1 이상이어야 합니다.", data, ref errors);
|
||||
if (data.sizeX <= 0 || data.sizeY <= 0 || data.sizeZ <= 0)
|
||||
Error($"Tower {data.id}의 배치 크기는 모두 1 이상이어야 합니다.", data, ref errors);
|
||||
if (data.upgradeTo > 0 && !towerIds.Contains(data.upgradeTo))
|
||||
Error($"Tower {data.id}의 업그레이드 대상 {data.upgradeTo}가 없습니다.", data, ref errors);
|
||||
if (data.atkDamage > 0 && data.atkRange <= 0)
|
||||
Warn($"Tower {data.id}는 공격력이 있지만 사거리가 없습니다.", data, ref warnings);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateUnits<T>(IEnumerable<T> units, Func<T, int> getId,
|
||||
Func<T, int> getHealth, Func<T, float> getSpeed, Func<T, int> getCost,
|
||||
Func<T, float> getWeight, string dataType, ref int errors) where T : UnityEngine.Object
|
||||
{
|
||||
foreach (T data in units)
|
||||
{
|
||||
int id = getId(data);
|
||||
if (getHealth(data) <= 0)
|
||||
Error($"{dataType} {id}의 체력은 1 이상이어야 합니다.", data, ref errors);
|
||||
if (getSpeed(data) < 0)
|
||||
Error($"{dataType} {id}의 이동 속도는 음수일 수 없습니다.", data, ref errors);
|
||||
if (getCost(data) <= 0 || getWeight(data) <= 0)
|
||||
Error($"{dataType} {id}의 생성 비용과 가중치는 0보다 커야 합니다.", data, ref errors);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidatePlayers(IEnumerable<PlayerData> players, ref int errors)
|
||||
{
|
||||
foreach (PlayerData data in players)
|
||||
{
|
||||
if (data.maxHp <= 0 || data.moveSpeed <= 0)
|
||||
Error($"Player {data.id}의 체력과 이동 속도는 0보다 커야 합니다.", data, ref errors);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateUpgrades(List<UpgradeData> upgrades, ref int errors, ref int warnings)
|
||||
{
|
||||
var ids = upgrades.Select(data => data.id).ToHashSet();
|
||||
foreach (UpgradeData data in upgrades)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(data.memo))
|
||||
Warn($"Upgrade {data.id}의 표시용 메모가 비어 있습니다.", data, ref warnings);
|
||||
if (data.mana < 0)
|
||||
Error($"Upgrade {data.id}의 비용은 음수일 수 없습니다.", data, ref errors);
|
||||
if (data.effectStatList.Count != data.effectOpList.Count ||
|
||||
data.effectStatList.Count != data.effectValueList.Count)
|
||||
Error($"Upgrade {data.id}의 효과 목록 길이가 서로 다릅니다.", data, ref errors);
|
||||
|
||||
foreach (int prerequisiteId in data.requireUpgradeId)
|
||||
{
|
||||
if (prerequisiteId == data.id)
|
||||
Error($"Upgrade {data.id}가 자기 자신을 선행 조건으로 참조합니다.", data, ref errors);
|
||||
else if (!ids.Contains(prerequisiteId))
|
||||
Error($"Upgrade {data.id}의 선행 조건 {prerequisiteId}가 없습니다.", data, ref errors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ValidateSettings(IEnumerable<DefaultSettingsData> settings,
|
||||
ref int errors, ref int warnings)
|
||||
{
|
||||
var keys = new HashSet<string>(StringComparer.Ordinal);
|
||||
foreach (DefaultSettingsData data in settings)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(data.key))
|
||||
{
|
||||
Warn($"DefaultSettings {data.id}의 키가 비어 있습니다.", data, ref warnings);
|
||||
continue;
|
||||
}
|
||||
|
||||
string scopedKey = $"{data.scene}:{data.key}";
|
||||
if (!keys.Add(scopedKey))
|
||||
Error($"DefaultSettings 키가 중복됩니다: {scopedKey}", data, ref errors);
|
||||
}
|
||||
}
|
||||
|
||||
private static void Error(string message, UnityEngine.Object context, ref int count)
|
||||
{
|
||||
count++;
|
||||
Debug.LogError($"[SO Data Validation] {message}", context);
|
||||
}
|
||||
|
||||
private static void Warn(string message, UnityEngine.Object context, ref int count)
|
||||
{
|
||||
count++;
|
||||
Debug.LogWarning($"[SO Data Validation] {message}", context);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user