- 패시브 트리 노드 배치를 삼각형 기반 자동 배치 구조로 전환하고 축 및 브릿지 반경을 재정리\n- 패시브 UI 프리팹과 런타임 렌더링을 수정해 노드 겹침, 링크 관통, 상태별 간격 변화, 하단 여백 문제를 정리\n- 프로토타입 패시브 노드, 트리, 프리셋 자산을 재생성해 최신 레이아웃과 확장 노드 구성을 반영
1049 lines
47 KiB
C#
1049 lines
47 KiB
C#
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 DefenseFrameNodeAssetPath = PassiveNodeFolderPath + "/Data_PassiveNode_Player_Tank_Frame.asset";
|
|
private const string DefenseGuardNodeAssetPath = PassiveNodeFolderPath + "/Data_PassiveNode_Player_Tank_Guard.asset";
|
|
private const string DefenseResolveNodeAssetPath = PassiveNodeFolderPath + "/Data_PassiveNode_Player_Tank_Resolve.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 SupportInsightNodeAssetPath = PassiveNodeFolderPath + "/Data_PassiveNode_Player_Support_Insight.asset";
|
|
private const string SupportWellNodeAssetPath = PassiveNodeFolderPath + "/Data_PassiveNode_Player_Support_Well.asset";
|
|
private const string SupportFocusNodeAssetPath = PassiveNodeFolderPath + "/Data_PassiveNode_Player_Support_Focus.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 AttackStrengthNodeAssetPath = PassiveNodeFolderPath + "/Data_PassiveNode_Player_Dps_Strength.asset";
|
|
private const string AttackPrecisionNodeAssetPath = PassiveNodeFolderPath + "/Data_PassiveNode_Player_Dps_Precision.asset";
|
|
private const string AttackArcanaNodeAssetPath = PassiveNodeFolderPath + "/Data_PassiveNode_Player_Dps_Arcana.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";
|
|
|
|
private const float AxisEntryRadius = 0.24f;
|
|
private const float AxisCoreRadius = 0.56f;
|
|
private const float AxisOuterStatRadius = 0.88f;
|
|
private const float AxisCenterStatRadius = 0.78f;
|
|
private const float AxisCapstoneRadius = 1.0f;
|
|
private const float AxisStatSideOffset = 0.32f;
|
|
private const float BridgeRadius = 0.70f;
|
|
|
|
[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 defenseFrameNode = CreateOrLoadNode(DefenseFrameNodeAssetPath);
|
|
PassiveNodeData defenseGuardNode = CreateOrLoadNode(DefenseGuardNodeAssetPath);
|
|
PassiveNodeData defenseResolveNode = CreateOrLoadNode(DefenseResolveNodeAssetPath);
|
|
PassiveNodeData defenseCoreNode = CreateOrLoadNode(DefenseCoreNodeAssetPath);
|
|
PassiveNodeData defenseCapstoneNode = CreateOrLoadNode(DefenseCapstoneNodeAssetPath);
|
|
PassiveNodeData supportEntryNode = CreateOrLoadNode(SupportEntryNodeAssetPath);
|
|
PassiveNodeData supportInsightNode = CreateOrLoadNode(SupportInsightNodeAssetPath);
|
|
PassiveNodeData supportWellNode = CreateOrLoadNode(SupportWellNodeAssetPath);
|
|
PassiveNodeData supportFocusNode = CreateOrLoadNode(SupportFocusNodeAssetPath);
|
|
PassiveNodeData supportCoreNode = CreateOrLoadNode(SupportCoreNodeAssetPath);
|
|
PassiveNodeData supportCapstoneNode = CreateOrLoadNode(SupportCapstoneNodeAssetPath);
|
|
PassiveNodeData attackEntryNode = CreateOrLoadNode(AttackEntryNodeAssetPath);
|
|
PassiveNodeData attackStrengthNode = CreateOrLoadNode(AttackStrengthNodeAssetPath);
|
|
PassiveNodeData attackPrecisionNode = CreateOrLoadNode(AttackPrecisionNodeAssetPath);
|
|
PassiveNodeData attackArcanaNode = CreateOrLoadNode(AttackArcanaNodeAssetPath);
|
|
PassiveNodeData attackCoreNode = CreateOrLoadNode(AttackCoreNodeAssetPath);
|
|
PassiveNodeData attackCapstoneNode = CreateOrLoadNode(AttackCapstoneNodeAssetPath);
|
|
PassiveNodeData attackDefenseBridgeNode = CreateOrLoadNode(AttackDefenseBridgeNodeAssetPath);
|
|
PassiveNodeData defenseSupportBridgeNode = CreateOrLoadNode(DefenseSupportBridgeNodeAssetPath);
|
|
PassiveNodeData supportAttackBridgeNode = CreateOrLoadNode(SupportAttackBridgeNodeAssetPath);
|
|
|
|
Vector2 attackDirection = Vector2.up;
|
|
Vector2 defenseDirection = RotateLayout(Vector2.up, 120f);
|
|
Vector2 supportDirection = RotateLayout(Vector2.up, -120f);
|
|
|
|
Vector2 attackEntryPosition = BuildAxisLayoutPosition(attackDirection, AxisEntryRadius);
|
|
Vector2 attackCorePosition = BuildAxisLayoutPosition(attackDirection, AxisCoreRadius);
|
|
Vector2 attackStrengthPosition = BuildAxisLayoutPosition(attackDirection, AxisOuterStatRadius, AxisStatSideOffset);
|
|
Vector2 attackPrecisionPosition = BuildAxisLayoutPosition(attackDirection, AxisCenterStatRadius);
|
|
Vector2 attackArcanaPosition = BuildAxisLayoutPosition(attackDirection, AxisOuterStatRadius, -AxisStatSideOffset);
|
|
Vector2 attackCapstonePosition = BuildAxisLayoutPosition(attackDirection, AxisCapstoneRadius);
|
|
|
|
Vector2 defenseEntryPosition = BuildAxisLayoutPosition(defenseDirection, AxisEntryRadius);
|
|
Vector2 defenseCorePosition = BuildAxisLayoutPosition(defenseDirection, AxisCoreRadius);
|
|
Vector2 defenseFramePosition = BuildAxisLayoutPosition(defenseDirection, AxisOuterStatRadius, AxisStatSideOffset);
|
|
Vector2 defenseResolvePosition = BuildAxisLayoutPosition(defenseDirection, AxisCenterStatRadius);
|
|
Vector2 defenseGuardPosition = BuildAxisLayoutPosition(defenseDirection, AxisOuterStatRadius, -AxisStatSideOffset);
|
|
Vector2 defenseCapstonePosition = BuildAxisLayoutPosition(defenseDirection, AxisCapstoneRadius);
|
|
|
|
Vector2 supportEntryPosition = BuildAxisLayoutPosition(supportDirection, AxisEntryRadius);
|
|
Vector2 supportCorePosition = BuildAxisLayoutPosition(supportDirection, AxisCoreRadius);
|
|
Vector2 supportInsightPosition = BuildAxisLayoutPosition(supportDirection, AxisOuterStatRadius, AxisStatSideOffset);
|
|
Vector2 supportWellPosition = BuildAxisLayoutPosition(supportDirection, AxisCenterStatRadius);
|
|
Vector2 supportFocusPosition = BuildAxisLayoutPosition(supportDirection, AxisOuterStatRadius, -AxisStatSideOffset);
|
|
Vector2 supportCapstonePosition = BuildAxisLayoutPosition(supportDirection, AxisCapstoneRadius);
|
|
|
|
Vector2 attackDefenseBridgePosition = RotateLayout(Vector2.up * BridgeRadius, 60f);
|
|
Vector2 defenseSupportBridgePosition = RotateLayout(Vector2.up * BridgeRadius, 180f);
|
|
Vector2 supportAttackBridgePosition = RotateLayout(Vector2.up * BridgeRadius, -60f);
|
|
|
|
ConfigureNode(
|
|
hubNode,
|
|
"hub",
|
|
string.Empty,
|
|
string.Empty,
|
|
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,
|
|
attackEntryPosition,
|
|
new[] { hubNode },
|
|
new[] { hubNode, attackCoreNode },
|
|
new[]
|
|
{
|
|
PassiveEffectConfig.CreateStat(StatType.Strength, StatModType.PercentAdd, 0.05f),
|
|
PassiveEffectConfig.CreateStat(StatType.Dexterity, StatModType.PercentAdd, 0.05f),
|
|
PassiveEffectConfig.CreateStat(StatType.Intelligence, StatModType.PercentAdd, 0.05f),
|
|
});
|
|
|
|
ConfigureNode(
|
|
attackStrengthNode,
|
|
"attack_strength",
|
|
"근력 단련",
|
|
"직접적인 무기 화력을 높이기 위한 힘의 기반을 다집니다.",
|
|
PassiveNodeBranch.Attack,
|
|
PassiveNodeKind.Axis,
|
|
PassiveAxisMask.Attack,
|
|
2,
|
|
1,
|
|
attackStrengthPosition,
|
|
new[] { attackCoreNode },
|
|
new[] { attackCoreNode },
|
|
new[]
|
|
{
|
|
PassiveEffectConfig.CreateStat(StatType.Strength, StatModType.Flat, 4f),
|
|
});
|
|
|
|
ConfigureNode(
|
|
attackPrecisionNode,
|
|
"attack_precision",
|
|
"민첩 연마",
|
|
"정확한 움직임과 타격 빈도를 위해 민첩을 끌어올립니다.",
|
|
PassiveNodeBranch.Attack,
|
|
PassiveNodeKind.Axis,
|
|
PassiveAxisMask.Attack,
|
|
2,
|
|
1,
|
|
attackPrecisionPosition,
|
|
new[] { attackCoreNode },
|
|
new[] { attackCoreNode },
|
|
new[]
|
|
{
|
|
PassiveEffectConfig.CreateStat(StatType.Dexterity, StatModType.Flat, 4f),
|
|
});
|
|
|
|
ConfigureNode(
|
|
attackArcanaNode,
|
|
"attack_arcana",
|
|
"지능 예열",
|
|
"공세 축에서도 주문 계열 화력을 포기하지 않도록 지능 기반을 보강합니다.",
|
|
PassiveNodeBranch.Attack,
|
|
PassiveNodeKind.Axis,
|
|
PassiveAxisMask.Attack,
|
|
2,
|
|
1,
|
|
attackArcanaPosition,
|
|
new[] { attackCoreNode },
|
|
new[] { attackCoreNode },
|
|
new[]
|
|
{
|
|
PassiveEffectConfig.CreateStat(StatType.Intelligence, StatModType.Flat, 4f),
|
|
});
|
|
|
|
ConfigureNode(
|
|
attackCoreNode,
|
|
"attack_core",
|
|
"집중 공세",
|
|
"공격 스킬 계열의 핵심 화력을 강화합니다.",
|
|
PassiveNodeBranch.Attack,
|
|
PassiveNodeKind.Axis,
|
|
PassiveAxisMask.Attack,
|
|
3,
|
|
1,
|
|
attackCorePosition,
|
|
new[] { attackEntryNode },
|
|
new[] { attackEntryNode, attackStrengthNode, attackPrecisionNode, attackArcanaNode, attackCapstoneNode, attackDefenseBridgeNode, supportAttackBridgeNode },
|
|
new[]
|
|
{
|
|
PassiveEffectConfig.CreateScalar(PassiveEffectType.DamageMultiplier, 1.10f, SkillRoleType.Attack),
|
|
});
|
|
|
|
ConfigureNode(
|
|
attackCapstoneNode,
|
|
"attack_capstone",
|
|
"집행 증폭",
|
|
"공격 축 완성 노드로, 공격 계열 고위력 기술의 기여도를 강화합니다.",
|
|
PassiveNodeBranch.Attack,
|
|
PassiveNodeKind.Capstone,
|
|
PassiveAxisMask.Attack,
|
|
4,
|
|
2,
|
|
attackCapstonePosition,
|
|
new[] { attackCoreNode },
|
|
new[] { attackCoreNode },
|
|
new[]
|
|
{
|
|
PassiveEffectConfig.CreateScalar(PassiveEffectType.DamageMultiplier, 1.08f, SkillRoleType.Attack),
|
|
PassiveEffectConfig.CreateScalar(PassiveEffectType.ManaCostMultiplier, 0.92f, SkillRoleType.Attack),
|
|
});
|
|
|
|
ConfigureNode(
|
|
defenseEntryNode,
|
|
"defense_entry",
|
|
"전열 적응",
|
|
"방어 축의 출발점으로, 전열 유지에 필요한 생존력을 확보합니다.",
|
|
PassiveNodeBranch.Defense,
|
|
PassiveNodeKind.Axis,
|
|
PassiveAxisMask.Defense,
|
|
1,
|
|
1,
|
|
defenseEntryPosition,
|
|
new[] { hubNode },
|
|
new[] { hubNode, defenseCoreNode },
|
|
new[]
|
|
{
|
|
PassiveEffectConfig.CreateStat(StatType.Vitality, StatModType.PercentAdd, 0.10f),
|
|
});
|
|
|
|
ConfigureNode(
|
|
defenseFrameNode,
|
|
"defense_frame",
|
|
"활력 축적",
|
|
"방어 축의 기본 체력을 두텁게 확보합니다.",
|
|
PassiveNodeBranch.Defense,
|
|
PassiveNodeKind.Axis,
|
|
PassiveAxisMask.Defense,
|
|
2,
|
|
1,
|
|
defenseFramePosition,
|
|
new[] { defenseCoreNode },
|
|
new[] { defenseCoreNode },
|
|
new[]
|
|
{
|
|
PassiveEffectConfig.CreateStat(StatType.Vitality, StatModType.Flat, 6f),
|
|
});
|
|
|
|
ConfigureNode(
|
|
defenseGuardNode,
|
|
"defense_guard",
|
|
"굳센 육체",
|
|
"기본 활력을 비율로 증폭해 전투 지속력을 높입니다.",
|
|
PassiveNodeBranch.Defense,
|
|
PassiveNodeKind.Axis,
|
|
PassiveAxisMask.Defense,
|
|
2,
|
|
1,
|
|
defenseGuardPosition,
|
|
new[] { defenseCoreNode },
|
|
new[] { defenseCoreNode },
|
|
new[]
|
|
{
|
|
PassiveEffectConfig.CreateStat(StatType.Vitality, StatModType.PercentAdd, 0.12f),
|
|
});
|
|
|
|
ConfigureNode(
|
|
defenseResolveNode,
|
|
"defense_resolve",
|
|
"전투 호흡",
|
|
"전열에서 스킬을 굴릴 여유를 위해 정신을 함께 보강합니다.",
|
|
PassiveNodeBranch.Defense,
|
|
PassiveNodeKind.Axis,
|
|
PassiveAxisMask.Defense,
|
|
2,
|
|
1,
|
|
defenseResolvePosition,
|
|
new[] { defenseCoreNode },
|
|
new[] { defenseCoreNode },
|
|
new[]
|
|
{
|
|
PassiveEffectConfig.CreateStat(StatType.Spirit, StatModType.Flat, 4f),
|
|
});
|
|
|
|
ConfigureNode(
|
|
defenseCoreNode,
|
|
"defense_core",
|
|
"방호 숙련",
|
|
"위협 유지와 보호막 수혜량을 함께 강화합니다.",
|
|
PassiveNodeBranch.Defense,
|
|
PassiveNodeKind.Axis,
|
|
PassiveAxisMask.Defense,
|
|
3,
|
|
1,
|
|
defenseCorePosition,
|
|
new[] { defenseEntryNode },
|
|
new[] { defenseEntryNode, defenseFrameNode, defenseGuardNode, defenseResolveNode, defenseCapstoneNode, attackDefenseBridgeNode, defenseSupportBridgeNode },
|
|
new[]
|
|
{
|
|
PassiveEffectConfig.CreateScalar(PassiveEffectType.ThreatGeneratedMultiplier, 1.25f),
|
|
PassiveEffectConfig.CreateScalar(PassiveEffectType.ShieldReceivedMultiplier, 1.10f),
|
|
});
|
|
|
|
ConfigureNode(
|
|
defenseCapstoneNode,
|
|
"defense_capstone",
|
|
"철벽 유지",
|
|
"받는 피해를 낮춰 전열 유지력을 완성합니다.",
|
|
PassiveNodeBranch.Defense,
|
|
PassiveNodeKind.Capstone,
|
|
PassiveAxisMask.Defense,
|
|
4,
|
|
2,
|
|
defenseCapstonePosition,
|
|
new[] { defenseCoreNode },
|
|
new[] { defenseCoreNode },
|
|
new[]
|
|
{
|
|
PassiveEffectConfig.CreateScalar(PassiveEffectType.IncomingDamageMultiplier, 0.90f),
|
|
});
|
|
|
|
ConfigureNode(
|
|
supportEntryNode,
|
|
"support_entry",
|
|
"구호 적응",
|
|
"지원 축의 출발점으로, 회복 기반 능력을 높입니다.",
|
|
PassiveNodeBranch.Support,
|
|
PassiveNodeKind.Axis,
|
|
PassiveAxisMask.Support,
|
|
1,
|
|
1,
|
|
supportEntryPosition,
|
|
new[] { hubNode },
|
|
new[] { hubNode, supportCoreNode },
|
|
new[]
|
|
{
|
|
PassiveEffectConfig.CreateStat(StatType.Wisdom, StatModType.PercentAdd, 0.10f),
|
|
});
|
|
|
|
ConfigureNode(
|
|
supportInsightNode,
|
|
"support_insight",
|
|
"지혜 응축",
|
|
"회복량의 기초가 되는 지혜를 직접 보강합니다.",
|
|
PassiveNodeBranch.Support,
|
|
PassiveNodeKind.Axis,
|
|
PassiveAxisMask.Support,
|
|
2,
|
|
1,
|
|
supportInsightPosition,
|
|
new[] { supportCoreNode },
|
|
new[] { supportCoreNode },
|
|
new[]
|
|
{
|
|
PassiveEffectConfig.CreateStat(StatType.Wisdom, StatModType.Flat, 4f),
|
|
});
|
|
|
|
ConfigureNode(
|
|
supportWellNode,
|
|
"support_well",
|
|
"정신 샘",
|
|
"지원 축 운용을 위해 마나 기반을 단단히 합니다.",
|
|
PassiveNodeBranch.Support,
|
|
PassiveNodeKind.Axis,
|
|
PassiveAxisMask.Support,
|
|
2,
|
|
1,
|
|
supportWellPosition,
|
|
new[] { supportCoreNode },
|
|
new[] { supportCoreNode },
|
|
new[]
|
|
{
|
|
PassiveEffectConfig.CreateStat(StatType.Spirit, StatModType.Flat, 6f),
|
|
});
|
|
|
|
ConfigureNode(
|
|
supportFocusNode,
|
|
"support_focus",
|
|
"집중 순환",
|
|
"지속적인 보조를 위해 정신을 비율로 확장합니다.",
|
|
PassiveNodeBranch.Support,
|
|
PassiveNodeKind.Axis,
|
|
PassiveAxisMask.Support,
|
|
2,
|
|
1,
|
|
supportFocusPosition,
|
|
new[] { supportCoreNode },
|
|
new[] { supportCoreNode },
|
|
new[]
|
|
{
|
|
PassiveEffectConfig.CreateStat(StatType.Spirit, StatModType.PercentAdd, 0.12f),
|
|
});
|
|
|
|
ConfigureNode(
|
|
supportCoreNode,
|
|
"support_core",
|
|
"조율 숙련",
|
|
"회복과 보호막 부여 효율을 함께 강화합니다.",
|
|
PassiveNodeBranch.Support,
|
|
PassiveNodeKind.Axis,
|
|
PassiveAxisMask.Support,
|
|
3,
|
|
1,
|
|
supportCorePosition,
|
|
new[] { supportEntryNode },
|
|
new[] { supportEntryNode, supportInsightNode, supportWellNode, supportFocusNode, supportCapstoneNode, defenseSupportBridgeNode, supportAttackBridgeNode },
|
|
new[]
|
|
{
|
|
PassiveEffectConfig.CreateScalar(PassiveEffectType.HealMultiplier, 1.12f, SkillRoleType.Support),
|
|
PassiveEffectConfig.CreateScalar(PassiveEffectType.ShieldDoneMultiplier, 1.12f, SkillRoleType.Support),
|
|
});
|
|
|
|
ConfigureNode(
|
|
supportCapstoneNode,
|
|
"support_capstone",
|
|
"마력 순환",
|
|
"최대 마나와 유지 효율을 함께 높입니다.",
|
|
PassiveNodeBranch.Support,
|
|
PassiveNodeKind.Capstone,
|
|
PassiveAxisMask.Support,
|
|
4,
|
|
2,
|
|
supportCapstonePosition,
|
|
new[] { supportCoreNode },
|
|
new[] { supportCoreNode },
|
|
new[]
|
|
{
|
|
PassiveEffectConfig.CreateScalar(PassiveEffectType.ManaCostMultiplier, 0.88f, SkillRoleType.Support),
|
|
PassiveEffectConfig.CreateScalar(PassiveEffectType.ShieldDoneMultiplier, 1.08f, SkillRoleType.Support),
|
|
});
|
|
|
|
ConfigureNode(
|
|
attackDefenseBridgeNode,
|
|
"attack_defense_bridge",
|
|
"압박 방벽",
|
|
"공격과 방어를 연결하는 브릿지로, 힘과 활력을 함께 보강합니다.",
|
|
PassiveNodeBranch.Bridge,
|
|
PassiveNodeKind.Bridge,
|
|
PassiveAxisMask.Attack | PassiveAxisMask.Defense,
|
|
2,
|
|
1,
|
|
attackDefenseBridgePosition,
|
|
new[] { attackCoreNode },
|
|
new[] { attackCoreNode, defenseCoreNode },
|
|
new[]
|
|
{
|
|
PassiveEffectConfig.CreateStat(StatType.Strength, StatModType.Flat, 3f),
|
|
PassiveEffectConfig.CreateStat(StatType.Vitality, StatModType.Flat, 3f),
|
|
});
|
|
|
|
ConfigureNode(
|
|
defenseSupportBridgeNode,
|
|
"defense_support_bridge",
|
|
"수호 순환",
|
|
"방어와 지원을 연결하는 브릿지로, 활력과 지혜를 함께 끌어올립니다.",
|
|
PassiveNodeBranch.Bridge,
|
|
PassiveNodeKind.Bridge,
|
|
PassiveAxisMask.Defense | PassiveAxisMask.Support,
|
|
2,
|
|
1,
|
|
defenseSupportBridgePosition,
|
|
new[] { defenseCoreNode },
|
|
new[] { defenseCoreNode, supportCoreNode },
|
|
new[]
|
|
{
|
|
PassiveEffectConfig.CreateStat(StatType.Vitality, StatModType.Flat, 3f),
|
|
PassiveEffectConfig.CreateStat(StatType.Wisdom, StatModType.Flat, 3f),
|
|
});
|
|
|
|
ConfigureNode(
|
|
supportAttackBridgeNode,
|
|
"support_attack_bridge",
|
|
"전술 증폭",
|
|
"지원과 공격을 연결하는 브릿지로, 지혜와 지능 기반을 함께 높입니다.",
|
|
PassiveNodeBranch.Bridge,
|
|
PassiveNodeKind.Bridge,
|
|
PassiveAxisMask.Support | PassiveAxisMask.Attack,
|
|
2,
|
|
1,
|
|
supportAttackBridgePosition,
|
|
new[] { supportCoreNode },
|
|
new[] { supportCoreNode, attackCoreNode },
|
|
new[]
|
|
{
|
|
PassiveEffectConfig.CreateStat(StatType.Wisdom, StatModType.Flat, 3f),
|
|
PassiveEffectConfig.CreateStat(StatType.Intelligence, StatModType.Flat, 3f),
|
|
});
|
|
|
|
PassiveTreeData tree = CreateOrLoadTree();
|
|
ConfigureTree(
|
|
tree,
|
|
"player_prototype_tree",
|
|
"플레이어 패시브 프로토타입",
|
|
"공격 / 방어 / 지원 3축에 다수의 스탯 노드와 연결 노드를 배치한 드로그전 밸런싱 검증용 트리입니다.",
|
|
12,
|
|
new[]
|
|
{
|
|
hubNode,
|
|
attackEntryNode,
|
|
attackStrengthNode,
|
|
attackPrecisionNode,
|
|
attackArcanaNode,
|
|
attackCoreNode,
|
|
attackCapstoneNode,
|
|
defenseEntryNode,
|
|
defenseFrameNode,
|
|
defenseGuardNode,
|
|
defenseResolveNode,
|
|
defenseCoreNode,
|
|
defenseCapstoneNode,
|
|
supportEntryNode,
|
|
supportInsightNode,
|
|
supportWellNode,
|
|
supportFocusNode,
|
|
supportCoreNode,
|
|
supportCapstoneNode,
|
|
attackDefenseBridgeNode,
|
|
defenseSupportBridgeNode,
|
|
supportAttackBridgeNode,
|
|
});
|
|
|
|
CreateOrUpdatePreset(
|
|
NonePresetAssetPath,
|
|
"패시브 없음",
|
|
"비교 기준선 확보용 프리셋입니다.",
|
|
tree,
|
|
new[] { hubNode });
|
|
|
|
CreateOrUpdatePreset(
|
|
DefensePresetAssetPath,
|
|
"방어형 패시브",
|
|
"방어 축의 스탯 노드와 핵심 유지 노드를 우선 확보하는 프리셋입니다.",
|
|
tree,
|
|
new[]
|
|
{
|
|
hubNode,
|
|
defenseEntryNode,
|
|
defenseCoreNode,
|
|
defenseFrameNode,
|
|
defenseGuardNode,
|
|
defenseResolveNode,
|
|
defenseCapstoneNode,
|
|
attackEntryNode,
|
|
attackCoreNode,
|
|
supportEntryNode,
|
|
attackDefenseBridgeNode,
|
|
supportCoreNode,
|
|
});
|
|
|
|
CreateOrUpdatePreset(
|
|
SupportPresetAssetPath,
|
|
"지원형 패시브",
|
|
"지원 축의 스탯 노드와 유지 효율 노드를 우선 확보하는 프리셋입니다.",
|
|
tree,
|
|
new[]
|
|
{
|
|
hubNode,
|
|
supportEntryNode,
|
|
supportCoreNode,
|
|
supportInsightNode,
|
|
supportWellNode,
|
|
supportFocusNode,
|
|
supportCapstoneNode,
|
|
defenseEntryNode,
|
|
defenseCoreNode,
|
|
attackEntryNode,
|
|
defenseSupportBridgeNode,
|
|
attackCoreNode,
|
|
});
|
|
|
|
CreateOrUpdatePreset(
|
|
AttackPresetAssetPath,
|
|
"공격형 패시브",
|
|
"공격 축의 스탯 노드와 화력 노드를 우선 확보하는 프리셋입니다.",
|
|
tree,
|
|
new[]
|
|
{
|
|
hubNode,
|
|
attackEntryNode,
|
|
attackCoreNode,
|
|
attackStrengthNode,
|
|
attackPrecisionNode,
|
|
attackArcanaNode,
|
|
attackCapstoneNode,
|
|
defenseEntryNode,
|
|
defenseCoreNode,
|
|
supportEntryNode,
|
|
attackDefenseBridgeNode,
|
|
supportCoreNode,
|
|
});
|
|
|
|
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);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 기준 축 템플릿 좌표를 실제 축 방향으로 회전합니다.
|
|
/// </summary>
|
|
private static Vector2 BuildAxisLayoutPosition(Vector2 axisDirection, float radialDistance, float lateralOffset = 0f)
|
|
{
|
|
Vector2 normalizedDirection = axisDirection.normalized;
|
|
Vector2 perpendicularDirection = new Vector2(-normalizedDirection.y, normalizedDirection.x);
|
|
return normalizedDirection * radialDistance + perpendicularDirection * lateralOffset;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 2D 평면 좌표를 각도 기준으로 회전합니다.
|
|
/// </summary>
|
|
private static Vector2 RotateLayout(Vector2 value, float degrees)
|
|
{
|
|
float radians = degrees * Mathf.Deg2Rad;
|
|
float cosine = Mathf.Cos(radians);
|
|
float sine = Mathf.Sin(radians);
|
|
return new Vector2(
|
|
value.x * cosine - value.y * sine,
|
|
value.x * sine + value.y * cosine);
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
}
|