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 { /// /// 패시브 트리 프로토타입 에셋 생성 및 디버그 적용 메뉴입니다. /// 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(NonePresetAssetPath), AssetDatabase.LoadAssetAtPath(DefensePresetAssetPath), AssetDatabase.LoadAssetAtPath(SupportPresetAssetPath), AssetDatabase.LoadAssetAtPath(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(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(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(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(PassiveTreeAssetPath); if (tree != null) return tree; tree = ScriptableObject.CreateInstance(); AssetDatabase.CreateAsset(tree, PassiveTreeAssetPath); return tree; } private static PassivePrototypeCatalogData CreateOrLoadCatalog() { PassivePrototypeCatalogData catalog = AssetDatabase.LoadAssetAtPath(PassiveCatalogAssetPath); if (catalog != null) return catalog; catalog = ScriptableObject.CreateInstance(); AssetDatabase.CreateAsset(catalog, PassiveCatalogAssetPath); return catalog; } private static PassiveNodeData CreateOrLoadNode(string assetPath) { PassiveNodeData node = AssetDatabase.LoadAssetAtPath(assetPath); if (node != null) return node; node = ScriptableObject.CreateInstance(); AssetDatabase.CreateAsset(node, assetPath); return node; } private static void ConfigureTree( PassiveTreeData tree, string treeId, string treeName, string description, int initialPoints, IReadOnlyList 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 prerequisiteNodes, IReadOnlyList connectedNodes, IReadOnlyList 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 selectedNodes) { PassivePresetData preset = AssetDatabase.LoadAssetAtPath(assetPath); if (preset == null) { preset = ScriptableObject.CreateInstance(); 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(); 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(); 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); } } } }