feat: 패시브 트리 프로토타입 구현
- 패시브 트리/노드/프리셋 데이터와 카탈로그 참조 구조를 추가하고 Resources 의존을 Data/Passives 자산 구조로 정리 - 플레이어 런타임, 전투 계수, 프리셋 적용, 멀티플레이 동기화 경로에 패시브 적용 로직 연결 - 프리팹 기반 패시브 트리 UI와 노드 아이콘/프리셋/상세 패널 흐름을 추가하고 HUD에 연동 - 패시브 디버그/부트스트랩 메뉴와 UI 프리팹 재생성 경로를 추가
This commit is contained in:
787
Assets/_Game/Scripts/Editor/PlayerPassiveDebugMenu.cs
Normal file
787
Assets/_Game/Scripts/Editor/PlayerPassiveDebugMenu.cs
Normal file
@@ -0,0 +1,787 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
using Colosseum.Passives;
|
||||
using Colosseum.Player;
|
||||
using Colosseum.Skills;
|
||||
using Colosseum.Stats;
|
||||
using Colosseum.UI;
|
||||
|
||||
namespace Colosseum.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// 패시브 트리 프로토타입 에셋 생성 및 디버그 적용 메뉴입니다.
|
||||
/// </summary>
|
||||
public static class PlayerPassiveDebugMenu
|
||||
{
|
||||
private const string DataFolderPath = "Assets/_Game/Data";
|
||||
private const string PassiveFolderPath = "Assets/_Game/Data/Passives";
|
||||
private const string PassiveNodeFolderPath = "Assets/_Game/Data/Passives/Nodes";
|
||||
private const string PassivePresetFolderPath = "Assets/_Game/Data/Passives/Presets";
|
||||
private const string PassiveCatalogAssetPath = PassiveFolderPath + "/Data_PassivePrototypeCatalog.asset";
|
||||
private const string PlayerPrefabPath = "Assets/_Game/Prefabs/Player/Prefab_Player_Default.prefab";
|
||||
private const string PlayerResourcesPrefabPath = "Assets/_Game/Prefabs/UI/UI_PlayerResources.prefab";
|
||||
|
||||
private const string PassiveTreeAssetPath = PassiveFolderPath + "/Data_PassiveTree_Player_Prototype.asset";
|
||||
private const string NonePresetAssetPath = PassivePresetFolderPath + "/Data_PassivePreset_Player_None.asset";
|
||||
private const string DefensePresetAssetPath = PassivePresetFolderPath + "/Data_PassivePreset_Player_Tank.asset";
|
||||
private const string SupportPresetAssetPath = PassivePresetFolderPath + "/Data_PassivePreset_Player_Support.asset";
|
||||
private const string AttackPresetAssetPath = PassivePresetFolderPath + "/Data_PassivePreset_Player_Dps.asset";
|
||||
|
||||
private const string HubNodeAssetPath = PassiveNodeFolderPath + "/Data_PassiveNode_Player_Hub.asset";
|
||||
private const string DefenseEntryNodeAssetPath = PassiveNodeFolderPath + "/Data_PassiveNode_Player_Tank_Entry.asset";
|
||||
private const string DefenseCoreNodeAssetPath = PassiveNodeFolderPath + "/Data_PassiveNode_Player_Tank_Core.asset";
|
||||
private const string DefenseCapstoneNodeAssetPath = PassiveNodeFolderPath + "/Data_PassiveNode_Player_Tank_Capstone.asset";
|
||||
private const string SupportEntryNodeAssetPath = PassiveNodeFolderPath + "/Data_PassiveNode_Player_Support_Entry.asset";
|
||||
private const string SupportCoreNodeAssetPath = PassiveNodeFolderPath + "/Data_PassiveNode_Player_Support_Core.asset";
|
||||
private const string SupportCapstoneNodeAssetPath = PassiveNodeFolderPath + "/Data_PassiveNode_Player_Support_Capstone.asset";
|
||||
private const string AttackEntryNodeAssetPath = PassiveNodeFolderPath + "/Data_PassiveNode_Player_Dps_Entry.asset";
|
||||
private const string AttackCoreNodeAssetPath = PassiveNodeFolderPath + "/Data_PassiveNode_Player_Dps_Core.asset";
|
||||
private const string AttackCapstoneNodeAssetPath = PassiveNodeFolderPath + "/Data_PassiveNode_Player_Dps_Capstone.asset";
|
||||
private const string AttackDefenseBridgeNodeAssetPath = PassiveNodeFolderPath + "/Data_PassiveNode_Player_AttackDefense_Bridge.asset";
|
||||
private const string DefenseSupportBridgeNodeAssetPath = PassiveNodeFolderPath + "/Data_PassiveNode_Player_DefenseSupport_Bridge.asset";
|
||||
private const string SupportAttackBridgeNodeAssetPath = PassiveNodeFolderPath + "/Data_PassiveNode_Player_SupportAttack_Bridge.asset";
|
||||
|
||||
[MenuItem("Tools/Colosseum/Debug/Passive/Bootstrap Prototype Assets")]
|
||||
private static void BootstrapPrototypeAssets()
|
||||
{
|
||||
EnsureFolder("Assets/_Game", "Data");
|
||||
EnsureFolder(DataFolderPath, "Passives");
|
||||
EnsureFolder(PassiveFolderPath, "Nodes");
|
||||
EnsureFolder(PassiveFolderPath, "Presets");
|
||||
|
||||
PassiveNodeData hubNode = CreateOrLoadNode(HubNodeAssetPath);
|
||||
PassiveNodeData defenseEntryNode = CreateOrLoadNode(DefenseEntryNodeAssetPath);
|
||||
PassiveNodeData defenseCoreNode = CreateOrLoadNode(DefenseCoreNodeAssetPath);
|
||||
PassiveNodeData defenseCapstoneNode = CreateOrLoadNode(DefenseCapstoneNodeAssetPath);
|
||||
PassiveNodeData supportEntryNode = CreateOrLoadNode(SupportEntryNodeAssetPath);
|
||||
PassiveNodeData supportCoreNode = CreateOrLoadNode(SupportCoreNodeAssetPath);
|
||||
PassiveNodeData supportCapstoneNode = CreateOrLoadNode(SupportCapstoneNodeAssetPath);
|
||||
PassiveNodeData attackEntryNode = CreateOrLoadNode(AttackEntryNodeAssetPath);
|
||||
PassiveNodeData attackCoreNode = CreateOrLoadNode(AttackCoreNodeAssetPath);
|
||||
PassiveNodeData attackCapstoneNode = CreateOrLoadNode(AttackCapstoneNodeAssetPath);
|
||||
PassiveNodeData attackDefenseBridgeNode = CreateOrLoadNode(AttackDefenseBridgeNodeAssetPath);
|
||||
PassiveNodeData defenseSupportBridgeNode = CreateOrLoadNode(DefenseSupportBridgeNodeAssetPath);
|
||||
PassiveNodeData supportAttackBridgeNode = CreateOrLoadNode(SupportAttackBridgeNodeAssetPath);
|
||||
|
||||
ConfigureNode(
|
||||
hubNode,
|
||||
"hub",
|
||||
"중심 허브",
|
||||
"패시브 트리의 시작점입니다.",
|
||||
PassiveNodeBranch.Common,
|
||||
PassiveNodeKind.Hub,
|
||||
PassiveAxisMask.None,
|
||||
0,
|
||||
0,
|
||||
new Vector2(0f, 0f),
|
||||
new PassiveNodeData[0],
|
||||
new[] { attackEntryNode, defenseEntryNode, supportEntryNode },
|
||||
new PassiveEffectConfig[0]);
|
||||
|
||||
ConfigureNode(
|
||||
attackEntryNode,
|
||||
"attack_entry",
|
||||
"공세 적응",
|
||||
"공격 축의 출발점으로, 기본 화력 계열 스탯을 끌어올립니다.",
|
||||
PassiveNodeBranch.Attack,
|
||||
PassiveNodeKind.Axis,
|
||||
PassiveAxisMask.Attack,
|
||||
1,
|
||||
1,
|
||||
new Vector2(0f, 0.34f),
|
||||
new[] { hubNode },
|
||||
new[] { hubNode, attackCoreNode, attackDefenseBridgeNode, supportAttackBridgeNode },
|
||||
new[]
|
||||
{
|
||||
PassiveEffectConfig.CreateStat(StatType.Strength, StatModType.PercentAdd, 0.1f),
|
||||
PassiveEffectConfig.CreateStat(StatType.Dexterity, StatModType.PercentAdd, 0.1f),
|
||||
PassiveEffectConfig.CreateStat(StatType.Intelligence, StatModType.PercentAdd, 0.1f),
|
||||
});
|
||||
|
||||
ConfigureNode(
|
||||
attackCoreNode,
|
||||
"attack_core",
|
||||
"집중 공세",
|
||||
"공격 스킬 계열의 핵심 화력을 강화합니다.",
|
||||
PassiveNodeBranch.Attack,
|
||||
PassiveNodeKind.Axis,
|
||||
PassiveAxisMask.Attack,
|
||||
2,
|
||||
1,
|
||||
new Vector2(0f, 0.6f),
|
||||
new[] { attackEntryNode },
|
||||
new[] { attackEntryNode, attackCapstoneNode },
|
||||
new[]
|
||||
{
|
||||
PassiveEffectConfig.CreateScalar(PassiveEffectType.DamageMultiplier, 1.12f, SkillRoleType.Attack),
|
||||
});
|
||||
|
||||
ConfigureNode(
|
||||
attackCapstoneNode,
|
||||
"attack_capstone",
|
||||
"집행 증폭",
|
||||
"공격 축 완성 노드로, 공격 계열 고위력 기술의 기여도를 강화합니다.",
|
||||
PassiveNodeBranch.Attack,
|
||||
PassiveNodeKind.Capstone,
|
||||
PassiveAxisMask.Attack,
|
||||
3,
|
||||
2,
|
||||
new Vector2(0f, 0.84f),
|
||||
new[] { attackCoreNode },
|
||||
new[] { attackCoreNode },
|
||||
new[]
|
||||
{
|
||||
PassiveEffectConfig.CreateScalar(PassiveEffectType.DamageMultiplier, 1.10f, SkillRoleType.Attack),
|
||||
PassiveEffectConfig.CreateScalar(PassiveEffectType.ManaCostMultiplier, 0.90f, SkillRoleType.Attack),
|
||||
});
|
||||
|
||||
ConfigureNode(
|
||||
defenseEntryNode,
|
||||
"defense_entry",
|
||||
"전열 적응",
|
||||
"방어 축의 출발점으로, 전열 유지에 필요한 생존력을 확보합니다.",
|
||||
PassiveNodeBranch.Defense,
|
||||
PassiveNodeKind.Axis,
|
||||
PassiveAxisMask.Defense,
|
||||
1,
|
||||
1,
|
||||
new Vector2(-0.34f, -0.1f),
|
||||
new[] { hubNode },
|
||||
new[] { hubNode, defenseCoreNode, attackDefenseBridgeNode, defenseSupportBridgeNode },
|
||||
new[]
|
||||
{
|
||||
PassiveEffectConfig.CreateStat(StatType.Vitality, StatModType.PercentAdd, 0.2f),
|
||||
});
|
||||
|
||||
ConfigureNode(
|
||||
defenseCoreNode,
|
||||
"defense_core",
|
||||
"방호 숙련",
|
||||
"위협 유지와 보호막 수혜량을 함께 강화합니다.",
|
||||
PassiveNodeBranch.Defense,
|
||||
PassiveNodeKind.Axis,
|
||||
PassiveAxisMask.Defense,
|
||||
2,
|
||||
1,
|
||||
new Vector2(-0.58f, -0.34f),
|
||||
new[] { defenseEntryNode },
|
||||
new[] { defenseEntryNode, defenseCapstoneNode },
|
||||
new[]
|
||||
{
|
||||
PassiveEffectConfig.CreateScalar(PassiveEffectType.ThreatGeneratedMultiplier, 1.30f),
|
||||
PassiveEffectConfig.CreateScalar(PassiveEffectType.ShieldReceivedMultiplier, 1.15f),
|
||||
});
|
||||
|
||||
ConfigureNode(
|
||||
defenseCapstoneNode,
|
||||
"defense_capstone",
|
||||
"철벽 유지",
|
||||
"받는 피해를 낮춰 전열 유지력을 완성합니다.",
|
||||
PassiveNodeBranch.Defense,
|
||||
PassiveNodeKind.Capstone,
|
||||
PassiveAxisMask.Defense,
|
||||
3,
|
||||
2,
|
||||
new Vector2(-0.82f, -0.58f),
|
||||
new[] { defenseCoreNode },
|
||||
new[] { defenseCoreNode },
|
||||
new[]
|
||||
{
|
||||
PassiveEffectConfig.CreateScalar(PassiveEffectType.IncomingDamageMultiplier, 0.88f),
|
||||
});
|
||||
|
||||
ConfigureNode(
|
||||
supportEntryNode,
|
||||
"support_entry",
|
||||
"구호 적응",
|
||||
"지원 축의 출발점으로, 회복 기반 능력을 높입니다.",
|
||||
PassiveNodeBranch.Support,
|
||||
PassiveNodeKind.Axis,
|
||||
PassiveAxisMask.Support,
|
||||
1,
|
||||
1,
|
||||
new Vector2(0.34f, -0.1f),
|
||||
new[] { hubNode },
|
||||
new[] { hubNode, supportCoreNode, defenseSupportBridgeNode, supportAttackBridgeNode },
|
||||
new[]
|
||||
{
|
||||
PassiveEffectConfig.CreateStat(StatType.Wisdom, StatModType.PercentAdd, 0.2f),
|
||||
});
|
||||
|
||||
ConfigureNode(
|
||||
supportCoreNode,
|
||||
"support_core",
|
||||
"조율 숙련",
|
||||
"회복과 보호막 부여 효율을 함께 강화합니다.",
|
||||
PassiveNodeBranch.Support,
|
||||
PassiveNodeKind.Axis,
|
||||
PassiveAxisMask.Support,
|
||||
2,
|
||||
1,
|
||||
new Vector2(0.58f, -0.34f),
|
||||
new[] { supportEntryNode },
|
||||
new[] { supportEntryNode, supportCapstoneNode },
|
||||
new[]
|
||||
{
|
||||
PassiveEffectConfig.CreateScalar(PassiveEffectType.HealMultiplier, 1.15f, SkillRoleType.Support),
|
||||
PassiveEffectConfig.CreateScalar(PassiveEffectType.ShieldDoneMultiplier, 1.15f, SkillRoleType.Support),
|
||||
});
|
||||
|
||||
ConfigureNode(
|
||||
supportCapstoneNode,
|
||||
"support_capstone",
|
||||
"마력 순환",
|
||||
"최대 마나와 유지 효율을 함께 높입니다.",
|
||||
PassiveNodeBranch.Support,
|
||||
PassiveNodeKind.Capstone,
|
||||
PassiveAxisMask.Support,
|
||||
3,
|
||||
2,
|
||||
new Vector2(0.82f, -0.58f),
|
||||
new[] { supportCoreNode },
|
||||
new[] { supportCoreNode },
|
||||
new[]
|
||||
{
|
||||
PassiveEffectConfig.CreateStat(StatType.Spirit, StatModType.PercentAdd, 0.2f),
|
||||
PassiveEffectConfig.CreateScalar(PassiveEffectType.ManaCostMultiplier, 0.85f, SkillRoleType.Support),
|
||||
});
|
||||
|
||||
ConfigureNode(
|
||||
attackDefenseBridgeNode,
|
||||
"attack_defense_bridge",
|
||||
"압박 방벽",
|
||||
"공격과 방어를 연결하는 브릿지로, 압박 유지력과 전투 안정성을 함께 챙깁니다.",
|
||||
PassiveNodeBranch.Bridge,
|
||||
PassiveNodeKind.Bridge,
|
||||
PassiveAxisMask.Attack | PassiveAxisMask.Defense,
|
||||
2,
|
||||
1,
|
||||
new Vector2(-0.24f, 0.14f),
|
||||
new[] { attackEntryNode, defenseEntryNode },
|
||||
new[] { attackEntryNode, defenseEntryNode },
|
||||
new[]
|
||||
{
|
||||
PassiveEffectConfig.CreateScalar(PassiveEffectType.DamageMultiplier, 1.06f, SkillRoleType.Attack),
|
||||
PassiveEffectConfig.CreateScalar(PassiveEffectType.IncomingDamageMultiplier, 0.95f),
|
||||
});
|
||||
|
||||
ConfigureNode(
|
||||
defenseSupportBridgeNode,
|
||||
"defense_support_bridge",
|
||||
"수호 순환",
|
||||
"방어와 지원을 연결하는 브릿지로, 보호막과 회복 기여를 함께 끌어올립니다.",
|
||||
PassiveNodeBranch.Bridge,
|
||||
PassiveNodeKind.Bridge,
|
||||
PassiveAxisMask.Defense | PassiveAxisMask.Support,
|
||||
2,
|
||||
1,
|
||||
new Vector2(0f, -0.28f),
|
||||
new[] { defenseEntryNode, supportEntryNode },
|
||||
new[] { defenseEntryNode, supportEntryNode },
|
||||
new[]
|
||||
{
|
||||
PassiveEffectConfig.CreateScalar(PassiveEffectType.ShieldReceivedMultiplier, 1.10f),
|
||||
PassiveEffectConfig.CreateScalar(PassiveEffectType.HealMultiplier, 1.08f, SkillRoleType.Support),
|
||||
});
|
||||
|
||||
ConfigureNode(
|
||||
supportAttackBridgeNode,
|
||||
"support_attack_bridge",
|
||||
"전술 증폭",
|
||||
"지원과 공격을 연결하는 브릿지로, 화력과 유지 효율을 함께 보조합니다.",
|
||||
PassiveNodeBranch.Bridge,
|
||||
PassiveNodeKind.Bridge,
|
||||
PassiveAxisMask.Support | PassiveAxisMask.Attack,
|
||||
2,
|
||||
1,
|
||||
new Vector2(0.24f, 0.14f),
|
||||
new[] { supportEntryNode, attackEntryNode },
|
||||
new[] { supportEntryNode, attackEntryNode },
|
||||
new[]
|
||||
{
|
||||
PassiveEffectConfig.CreateScalar(PassiveEffectType.DamageMultiplier, 1.06f, SkillRoleType.Attack),
|
||||
PassiveEffectConfig.CreateScalar(PassiveEffectType.ManaCostMultiplier, 0.95f),
|
||||
});
|
||||
|
||||
PassiveTreeData tree = CreateOrLoadTree();
|
||||
ConfigureTree(
|
||||
tree,
|
||||
"player_prototype_tree",
|
||||
"플레이어 패시브 프로토타입",
|
||||
"공격 / 방어 / 지원 3축과 브릿지 노드로 구성된 드로그전 밸런싱 검증용 트리입니다.",
|
||||
8,
|
||||
new[]
|
||||
{
|
||||
hubNode,
|
||||
attackEntryNode,
|
||||
attackCoreNode,
|
||||
attackCapstoneNode,
|
||||
defenseEntryNode,
|
||||
defenseCoreNode,
|
||||
defenseCapstoneNode,
|
||||
supportEntryNode,
|
||||
supportCoreNode,
|
||||
supportCapstoneNode,
|
||||
attackDefenseBridgeNode,
|
||||
defenseSupportBridgeNode,
|
||||
supportAttackBridgeNode,
|
||||
});
|
||||
|
||||
CreateOrUpdatePreset(
|
||||
NonePresetAssetPath,
|
||||
"패시브 없음",
|
||||
"비교 기준선 확보용 프리셋입니다.",
|
||||
tree,
|
||||
new[] { hubNode });
|
||||
|
||||
CreateOrUpdatePreset(
|
||||
DefensePresetAssetPath,
|
||||
"방어형 패시브",
|
||||
"방어 축 완성과 함께 공격/지원 브릿지를 가볍게 여는 프리셋입니다.",
|
||||
tree,
|
||||
new[]
|
||||
{
|
||||
hubNode,
|
||||
defenseEntryNode,
|
||||
defenseCoreNode,
|
||||
defenseCapstoneNode,
|
||||
attackEntryNode,
|
||||
supportEntryNode,
|
||||
attackDefenseBridgeNode,
|
||||
defenseSupportBridgeNode,
|
||||
});
|
||||
|
||||
CreateOrUpdatePreset(
|
||||
SupportPresetAssetPath,
|
||||
"지원형 패시브",
|
||||
"지원 축 완성과 함께 공격/방어 브릿지를 가볍게 여는 프리셋입니다.",
|
||||
tree,
|
||||
new[]
|
||||
{
|
||||
hubNode,
|
||||
supportEntryNode,
|
||||
supportCoreNode,
|
||||
supportCapstoneNode,
|
||||
defenseEntryNode,
|
||||
attackEntryNode,
|
||||
defenseSupportBridgeNode,
|
||||
supportAttackBridgeNode,
|
||||
});
|
||||
|
||||
CreateOrUpdatePreset(
|
||||
AttackPresetAssetPath,
|
||||
"공격형 패시브",
|
||||
"공격 축 완성과 함께 방어/지원 브릿지를 가볍게 여는 프리셋입니다.",
|
||||
tree,
|
||||
new[]
|
||||
{
|
||||
hubNode,
|
||||
attackEntryNode,
|
||||
attackCoreNode,
|
||||
attackCapstoneNode,
|
||||
defenseEntryNode,
|
||||
supportEntryNode,
|
||||
attackDefenseBridgeNode,
|
||||
supportAttackBridgeNode,
|
||||
});
|
||||
|
||||
PassivePrototypeCatalogData catalog = CreateOrLoadCatalog();
|
||||
ConfigureCatalog(
|
||||
catalog,
|
||||
tree,
|
||||
AssetDatabase.LoadAssetAtPath<PassivePresetData>(NonePresetAssetPath),
|
||||
AssetDatabase.LoadAssetAtPath<PassivePresetData>(DefensePresetAssetPath),
|
||||
AssetDatabase.LoadAssetAtPath<PassivePresetData>(SupportPresetAssetPath),
|
||||
AssetDatabase.LoadAssetAtPath<PassivePresetData>(AttackPresetAssetPath));
|
||||
BindPrototypeCatalogToPrefabs(catalog);
|
||||
|
||||
AssetDatabase.SaveAssets();
|
||||
AssetDatabase.Refresh();
|
||||
Debug.Log("[Passive] 프로토타입 패시브 에셋 생성을 완료했습니다.");
|
||||
}
|
||||
|
||||
[MenuItem("Tools/Colosseum/Debug/Passive/Apply Local None")]
|
||||
private static void ApplyLocalNone()
|
||||
{
|
||||
ApplyLocalPreset(NonePresetAssetPath, "패시브 없음");
|
||||
}
|
||||
|
||||
[MenuItem("Tools/Colosseum/Debug/Passive/Apply Local Defense")]
|
||||
private static void ApplyLocalDefense()
|
||||
{
|
||||
ApplyLocalPreset(DefensePresetAssetPath, "방어형 패시브");
|
||||
}
|
||||
|
||||
[MenuItem("Tools/Colosseum/Debug/Passive/Apply Local Support")]
|
||||
private static void ApplyLocalSupport()
|
||||
{
|
||||
ApplyLocalPreset(SupportPresetAssetPath, "지원형 패시브");
|
||||
}
|
||||
|
||||
[MenuItem("Tools/Colosseum/Debug/Passive/Apply Local Attack")]
|
||||
private static void ApplyLocalAttack()
|
||||
{
|
||||
ApplyLocalPreset(AttackPresetAssetPath, "공격형 패시브");
|
||||
}
|
||||
|
||||
[MenuItem("Tools/Colosseum/Debug/Passive/Apply Owner Presets To All Players")]
|
||||
private static void ApplyOwnerPresetsToAllPlayers()
|
||||
{
|
||||
if (!EditorApplication.isPlaying)
|
||||
{
|
||||
Debug.LogWarning("[Passive] 플레이 모드에서만 사용할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerNetworkController[] players = Object.FindObjectsByType<PlayerNetworkController>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
|
||||
if (players == null || players.Length == 0)
|
||||
{
|
||||
Debug.LogWarning("[Passive] PlayerNetworkController를 찾지 못했습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
int appliedCount = 0;
|
||||
for (int i = 0; i < players.Length; i++)
|
||||
{
|
||||
PlayerNetworkController player = players[i];
|
||||
if (player != null && player.TryApplyPrototypePassivePresetForOwner())
|
||||
{
|
||||
appliedCount++;
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Log($"[Passive] 역할별 패시브 프리셋 적용 완료 | Applied={appliedCount}");
|
||||
}
|
||||
|
||||
[MenuItem("Tools/Colosseum/Debug/Passive/Log Local Passive Summary")]
|
||||
private static void LogLocalPassiveSummary()
|
||||
{
|
||||
if (!EditorApplication.isPlaying)
|
||||
{
|
||||
Debug.LogWarning("[Passive] 플레이 모드에서만 사용할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerNetworkController localNetworkController = FindLocalNetworkController();
|
||||
if (localNetworkController == null)
|
||||
{
|
||||
Debug.LogWarning("[Passive] 로컬 PlayerNetworkController를 찾지 못했습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log(localNetworkController.BuildPassiveSummary());
|
||||
}
|
||||
|
||||
private static void ApplyLocalPreset(string presetPath, string label)
|
||||
{
|
||||
if (!EditorApplication.isPlaying)
|
||||
{
|
||||
Debug.LogWarning("[Passive] 플레이 모드에서만 사용할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerNetworkController localNetworkController = FindLocalNetworkController();
|
||||
if (localNetworkController == null)
|
||||
{
|
||||
Debug.LogWarning("[Passive] 로컬 PlayerNetworkController를 찾지 못했습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
PassivePresetData preset = AssetDatabase.LoadAssetAtPath<PassivePresetData>(presetPath);
|
||||
if (preset == null)
|
||||
{
|
||||
Debug.LogWarning($"[Passive] 패시브 프리셋을 찾지 못했습니다: {presetPath}");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!localNetworkController.DebugApplyPassivePreset(preset))
|
||||
{
|
||||
Debug.LogWarning($"[Passive] {label} 적용에 실패했습니다. 호스트/서버 플레이 모드인지 확인하세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
Debug.Log($"[Passive] {label} 적용 완료");
|
||||
}
|
||||
|
||||
private static PlayerNetworkController FindLocalNetworkController()
|
||||
{
|
||||
PlayerNetworkController[] players = Object.FindObjectsByType<PlayerNetworkController>(FindObjectsInactive.Exclude, FindObjectsSortMode.None);
|
||||
for (int i = 0; i < players.Length; i++)
|
||||
{
|
||||
PlayerNetworkController player = players[i];
|
||||
if (player != null && player.IsOwner)
|
||||
return player;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void EnsureFolder(string parentFolder, string childFolderName)
|
||||
{
|
||||
string fullPath = $"{parentFolder}/{childFolderName}";
|
||||
if (AssetDatabase.IsValidFolder(fullPath))
|
||||
return;
|
||||
|
||||
AssetDatabase.CreateFolder(parentFolder, childFolderName);
|
||||
}
|
||||
|
||||
private static PassiveTreeData CreateOrLoadTree()
|
||||
{
|
||||
PassiveTreeData tree = AssetDatabase.LoadAssetAtPath<PassiveTreeData>(PassiveTreeAssetPath);
|
||||
if (tree != null)
|
||||
return tree;
|
||||
|
||||
tree = ScriptableObject.CreateInstance<PassiveTreeData>();
|
||||
AssetDatabase.CreateAsset(tree, PassiveTreeAssetPath);
|
||||
return tree;
|
||||
}
|
||||
|
||||
private static PassivePrototypeCatalogData CreateOrLoadCatalog()
|
||||
{
|
||||
PassivePrototypeCatalogData catalog = AssetDatabase.LoadAssetAtPath<PassivePrototypeCatalogData>(PassiveCatalogAssetPath);
|
||||
if (catalog != null)
|
||||
return catalog;
|
||||
|
||||
catalog = ScriptableObject.CreateInstance<PassivePrototypeCatalogData>();
|
||||
AssetDatabase.CreateAsset(catalog, PassiveCatalogAssetPath);
|
||||
return catalog;
|
||||
}
|
||||
|
||||
private static PassiveNodeData CreateOrLoadNode(string assetPath)
|
||||
{
|
||||
PassiveNodeData node = AssetDatabase.LoadAssetAtPath<PassiveNodeData>(assetPath);
|
||||
if (node != null)
|
||||
return node;
|
||||
|
||||
node = ScriptableObject.CreateInstance<PassiveNodeData>();
|
||||
AssetDatabase.CreateAsset(node, assetPath);
|
||||
return node;
|
||||
}
|
||||
|
||||
private static void ConfigureTree(
|
||||
PassiveTreeData tree,
|
||||
string treeId,
|
||||
string treeName,
|
||||
string description,
|
||||
int initialPoints,
|
||||
IReadOnlyList<PassiveNodeData> nodes)
|
||||
{
|
||||
SerializedObject serializedTree = new SerializedObject(tree);
|
||||
serializedTree.FindProperty("treeId").stringValue = treeId;
|
||||
serializedTree.FindProperty("treeName").stringValue = treeName;
|
||||
serializedTree.FindProperty("description").stringValue = description;
|
||||
serializedTree.FindProperty("initialPoints").intValue = initialPoints;
|
||||
|
||||
SerializedProperty nodeProperty = serializedTree.FindProperty("nodes");
|
||||
nodeProperty.arraySize = nodes != null ? nodes.Count : 0;
|
||||
for (int i = 0; i < nodeProperty.arraySize; i++)
|
||||
{
|
||||
nodeProperty.GetArrayElementAtIndex(i).objectReferenceValue = nodes[i];
|
||||
}
|
||||
|
||||
serializedTree.ApplyModifiedPropertiesWithoutUndo();
|
||||
EditorUtility.SetDirty(tree);
|
||||
}
|
||||
|
||||
private static void ConfigureNode(
|
||||
PassiveNodeData node,
|
||||
string nodeId,
|
||||
string displayName,
|
||||
string description,
|
||||
PassiveNodeBranch branch,
|
||||
PassiveNodeKind nodeKind,
|
||||
PassiveAxisMask axisMask,
|
||||
int tier,
|
||||
int cost,
|
||||
Vector2 layoutPosition,
|
||||
IReadOnlyList<PassiveNodeData> prerequisiteNodes,
|
||||
IReadOnlyList<PassiveNodeData> connectedNodes,
|
||||
IReadOnlyList<PassiveEffectConfig> effects)
|
||||
{
|
||||
SerializedObject serializedNode = new SerializedObject(node);
|
||||
serializedNode.FindProperty("nodeId").stringValue = nodeId;
|
||||
serializedNode.FindProperty("displayName").stringValue = displayName;
|
||||
serializedNode.FindProperty("description").stringValue = description;
|
||||
serializedNode.FindProperty("branch").enumValueIndex = (int)branch;
|
||||
serializedNode.FindProperty("nodeKind").enumValueIndex = (int)nodeKind;
|
||||
serializedNode.FindProperty("axisMask").intValue = (int)axisMask;
|
||||
serializedNode.FindProperty("tier").intValue = tier;
|
||||
serializedNode.FindProperty("cost").intValue = cost;
|
||||
serializedNode.FindProperty("layoutPosition").vector2Value = layoutPosition;
|
||||
|
||||
SerializedProperty prerequisiteProperty = serializedNode.FindProperty("prerequisiteNodes");
|
||||
prerequisiteProperty.arraySize = prerequisiteNodes != null ? prerequisiteNodes.Count : 0;
|
||||
for (int i = 0; i < prerequisiteProperty.arraySize; i++)
|
||||
{
|
||||
prerequisiteProperty.GetArrayElementAtIndex(i).objectReferenceValue = prerequisiteNodes[i];
|
||||
}
|
||||
|
||||
SerializedProperty connectedProperty = serializedNode.FindProperty("connectedNodes");
|
||||
connectedProperty.arraySize = connectedNodes != null ? connectedNodes.Count : 0;
|
||||
for (int i = 0; i < connectedProperty.arraySize; i++)
|
||||
{
|
||||
connectedProperty.GetArrayElementAtIndex(i).objectReferenceValue = connectedNodes[i];
|
||||
}
|
||||
|
||||
SerializedProperty effectProperty = serializedNode.FindProperty("effects");
|
||||
effectProperty.arraySize = effects != null ? effects.Count : 0;
|
||||
for (int i = 0; i < effectProperty.arraySize; i++)
|
||||
{
|
||||
SerializedProperty effectEntry = effectProperty.GetArrayElementAtIndex(i);
|
||||
PassiveEffectConfig effectConfig = effects[i];
|
||||
effectEntry.FindPropertyRelative("effectType").enumValueIndex = (int)effectConfig.EffectType;
|
||||
effectEntry.FindPropertyRelative("statType").enumValueIndex = (int)effectConfig.StatType;
|
||||
effectEntry.FindPropertyRelative("modType").enumValueIndex = (int)effectConfig.ModType;
|
||||
effectEntry.FindPropertyRelative("value").floatValue = effectConfig.Value;
|
||||
effectEntry.FindPropertyRelative("skillRoleMask").intValue = (int)effectConfig.SkillRoleMask;
|
||||
}
|
||||
|
||||
serializedNode.ApplyModifiedPropertiesWithoutUndo();
|
||||
EditorUtility.SetDirty(node);
|
||||
}
|
||||
|
||||
private static void CreateOrUpdatePreset(
|
||||
string assetPath,
|
||||
string presetName,
|
||||
string description,
|
||||
PassiveTreeData tree,
|
||||
IReadOnlyList<PassiveNodeData> selectedNodes)
|
||||
{
|
||||
PassivePresetData preset = AssetDatabase.LoadAssetAtPath<PassivePresetData>(assetPath);
|
||||
if (preset == null)
|
||||
{
|
||||
preset = ScriptableObject.CreateInstance<PassivePresetData>();
|
||||
AssetDatabase.CreateAsset(preset, assetPath);
|
||||
}
|
||||
|
||||
SerializedObject serializedPreset = new SerializedObject(preset);
|
||||
serializedPreset.FindProperty("presetName").stringValue = presetName;
|
||||
serializedPreset.FindProperty("description").stringValue = description;
|
||||
serializedPreset.FindProperty("tree").objectReferenceValue = tree;
|
||||
|
||||
SerializedProperty selectedNodesProperty = serializedPreset.FindProperty("selectedNodes");
|
||||
selectedNodesProperty.arraySize = selectedNodes != null ? selectedNodes.Count : 0;
|
||||
for (int i = 0; i < selectedNodesProperty.arraySize; i++)
|
||||
{
|
||||
selectedNodesProperty.GetArrayElementAtIndex(i).objectReferenceValue = selectedNodes[i];
|
||||
}
|
||||
|
||||
serializedPreset.ApplyModifiedPropertiesWithoutUndo();
|
||||
EditorUtility.SetDirty(preset);
|
||||
}
|
||||
|
||||
private static void ConfigureCatalog(
|
||||
PassivePrototypeCatalogData catalog,
|
||||
PassiveTreeData tree,
|
||||
PassivePresetData nonePreset,
|
||||
PassivePresetData defensePreset,
|
||||
PassivePresetData supportPreset,
|
||||
PassivePresetData attackPreset)
|
||||
{
|
||||
if (catalog == null)
|
||||
return;
|
||||
|
||||
SerializedObject serializedCatalog = new SerializedObject(catalog);
|
||||
serializedCatalog.FindProperty("prototypeTree").objectReferenceValue = tree;
|
||||
serializedCatalog.FindProperty("nonePreset").objectReferenceValue = nonePreset;
|
||||
serializedCatalog.FindProperty("defensePreset").objectReferenceValue = defensePreset;
|
||||
serializedCatalog.FindProperty("supportPreset").objectReferenceValue = supportPreset;
|
||||
serializedCatalog.FindProperty("attackPreset").objectReferenceValue = attackPreset;
|
||||
serializedCatalog.ApplyModifiedPropertiesWithoutUndo();
|
||||
EditorUtility.SetDirty(catalog);
|
||||
}
|
||||
|
||||
private static void BindPrototypeCatalogToPrefabs(PassivePrototypeCatalogData catalog)
|
||||
{
|
||||
BindCatalogToPlayerPrefab(catalog);
|
||||
BindCatalogToPlayerResourcesPrefab(catalog);
|
||||
}
|
||||
|
||||
private static void BindCatalogToPlayerPrefab(PassivePrototypeCatalogData catalog)
|
||||
{
|
||||
GameObject root = PrefabUtility.LoadPrefabContents(PlayerPrefabPath);
|
||||
try
|
||||
{
|
||||
PlayerNetworkController controller = root.GetComponent<PlayerNetworkController>();
|
||||
if (controller == null)
|
||||
return;
|
||||
|
||||
SerializedObject serializedController = new SerializedObject(controller);
|
||||
serializedController.FindProperty("passivePrototypeCatalog").objectReferenceValue = catalog;
|
||||
if (serializedController.FindProperty("passiveTree").objectReferenceValue == null)
|
||||
{
|
||||
serializedController.FindProperty("passiveTree").objectReferenceValue = catalog != null ? catalog.PrototypeTree : null;
|
||||
}
|
||||
|
||||
serializedController.ApplyModifiedPropertiesWithoutUndo();
|
||||
PrefabUtility.SaveAsPrefabAsset(root, PlayerPrefabPath);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrefabUtility.UnloadPrefabContents(root);
|
||||
}
|
||||
}
|
||||
|
||||
private static void BindCatalogToPlayerResourcesPrefab(PassivePrototypeCatalogData catalog)
|
||||
{
|
||||
GameObject root = PrefabUtility.LoadPrefabContents(PlayerResourcesPrefabPath);
|
||||
try
|
||||
{
|
||||
PassiveTreeUI passiveTreeUi = root.GetComponent<PassiveTreeUI>();
|
||||
if (passiveTreeUi == null)
|
||||
return;
|
||||
|
||||
SerializedObject serializedPassiveTreeUi = new SerializedObject(passiveTreeUi);
|
||||
serializedPassiveTreeUi.FindProperty("passivePrototypeCatalog").objectReferenceValue = catalog;
|
||||
serializedPassiveTreeUi.ApplyModifiedPropertiesWithoutUndo();
|
||||
PrefabUtility.SaveAsPrefabAsset(root, PlayerResourcesPrefabPath);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrefabUtility.UnloadPrefabContents(root);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly struct PassiveEffectConfig
|
||||
{
|
||||
public PassiveEffectConfig(
|
||||
PassiveEffectType effectType,
|
||||
StatType statType,
|
||||
StatModType modType,
|
||||
float value,
|
||||
SkillRoleType skillRoleMask)
|
||||
{
|
||||
EffectType = effectType;
|
||||
StatType = statType;
|
||||
ModType = modType;
|
||||
Value = value;
|
||||
SkillRoleMask = skillRoleMask;
|
||||
}
|
||||
|
||||
public PassiveEffectType EffectType { get; }
|
||||
public StatType StatType { get; }
|
||||
public StatModType ModType { get; }
|
||||
public float Value { get; }
|
||||
public SkillRoleType SkillRoleMask { get; }
|
||||
|
||||
public static PassiveEffectConfig CreateScalar(
|
||||
PassiveEffectType effectType,
|
||||
float value,
|
||||
SkillRoleType skillRoleMask = SkillRoleType.All)
|
||||
{
|
||||
return new PassiveEffectConfig(effectType, StatType.Vitality, StatModType.Flat, value, skillRoleMask);
|
||||
}
|
||||
|
||||
public static PassiveEffectConfig CreateStat(
|
||||
StatType statType,
|
||||
StatModType modType,
|
||||
float value)
|
||||
{
|
||||
return new PassiveEffectConfig(PassiveEffectType.StatModifier, statType, modType, value, SkillRoleType.All);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user