feat: 패시브 트리 프로토타입 구현
- 패시브 트리/노드/프리셋 데이터와 카탈로그 참조 구조를 추가하고 Resources 의존을 Data/Passives 자산 구조로 정리 - 플레이어 런타임, 전투 계수, 프리셋 적용, 멀티플레이 동기화 경로에 패시브 적용 로직 연결 - 프리팹 기반 패시브 트리 UI와 노드 아이콘/프리셋/상세 패널 흐름을 추가하고 HUD에 연동 - 패시브 디버그/부트스트랩 메뉴와 UI 프리팹 재생성 경로를 추가
This commit is contained in:
8
Assets/_Game/Data/Passives.meta
Normal file
8
Assets/_Game/Data/Passives.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: aff34933e9f711544a8478e8e2ca8b3d
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,19 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: cf6077238ea8f1649a7cecd929b02c91, type: 3}
|
||||
m_Name: Data_PassivePrototypeCatalog
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Passives.PassivePrototypeCatalogData
|
||||
prototypeTree: {fileID: 11400000, guid: 33ad68732732b6a4589e24da88253f8d, type: 2}
|
||||
nonePreset: {fileID: 11400000, guid: ced52cd35b0d0d147ab2bad8af294331, type: 2}
|
||||
defensePreset: {fileID: 11400000, guid: 086ccaebf9a348642a99fa0e2f626918, type: 2}
|
||||
supportPreset: {fileID: 11400000, guid: f0f4fa2b1a936314da3b5f71c5f8d9b9, type: 2}
|
||||
attackPreset: {fileID: 11400000, guid: 8ca1fb05eab83bd44bcd3fd46bfc439f, type: 2}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 328a12c12954ef649a9ba5892323c345
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,34 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 16b8c249387e8bf4f94e93d1776171a4, type: 3}
|
||||
m_Name: Data_PassiveTree_Player_Prototype
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Passives.PassiveTreeData
|
||||
treeId: player_prototype_tree
|
||||
treeName: "\uD50C\uB808\uC774\uC5B4 \uD328\uC2DC\uBE0C \uD504\uB85C\uD1A0\uD0C0\uC785"
|
||||
description: "\uACF5\uACA9 / \uBC29\uC5B4 / \uC9C0\uC6D0 3\uCD95\uACFC \uBE0C\uB9BF\uC9C0
|
||||
\uB178\uB4DC\uB85C \uAD6C\uC131\uB41C \uB4DC\uB85C\uADF8\uC804 \uBC38\uB7F0\uC2F1
|
||||
\uAC80\uC99D\uC6A9 \uD2B8\uB9AC\uC785\uB2C8\uB2E4."
|
||||
initialPoints: 8
|
||||
nodes:
|
||||
- {fileID: 11400000, guid: a94fab25cea2ef040a684ad03a9515aa, type: 2}
|
||||
- {fileID: 11400000, guid: 608b595113bc12e4dbf618f93ab41aaa, type: 2}
|
||||
- {fileID: 11400000, guid: 8b6865d1ccdfbc24a956e50ced20da87, type: 2}
|
||||
- {fileID: 11400000, guid: 95f84aa662f1c3e4d92c1371d6bfcf17, type: 2}
|
||||
- {fileID: 11400000, guid: 7651af69dcffc174eb8baa9a822d8188, type: 2}
|
||||
- {fileID: 11400000, guid: bb00ad8c4601d3a4fb1e30a153faca38, type: 2}
|
||||
- {fileID: 11400000, guid: 4bde7b3376f2b7d429e5a9dc90d1cf3c, type: 2}
|
||||
- {fileID: 11400000, guid: c5e1fee00b45d7643ae1b51b749d8d57, type: 2}
|
||||
- {fileID: 11400000, guid: 21150da8ec1314242ada62509025a417, type: 2}
|
||||
- {fileID: 11400000, guid: daf27733a031c2f46906ebb801428f34, type: 2}
|
||||
- {fileID: 11400000, guid: 1fe3777d83675364187bfd8134b859d5, type: 2}
|
||||
- {fileID: 11400000, guid: 5535fa758a366ad409df12010f661378, type: 2}
|
||||
- {fileID: 11400000, guid: b654aea2451ac2b4c8feca4baa733c7f, type: 2}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 33ad68732732b6a4589e24da88253f8d
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/_Game/Data/Passives/Nodes.meta
Normal file
8
Assets/_Game/Data/Passives/Nodes.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7413e74519660d6439d1316ccaeb9ef1
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,42 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: c72b2635385ed49498483636164bac87, type: 3}
|
||||
m_Name: Data_PassiveNode_Player_AttackDefense_Bridge
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Passives.PassiveNodeData
|
||||
nodeId: attack_defense_bridge
|
||||
displayName: "\uC555\uBC15 \uBC29\uBCBD"
|
||||
description: "\uACF5\uACA9\uACFC \uBC29\uC5B4\uB97C \uC5F0\uACB0\uD558\uB294 \uBE0C\uB9BF\uC9C0\uB85C,
|
||||
\uC555\uBC15 \uC720\uC9C0\uB825\uACFC \uC804\uD22C \uC548\uC815\uC131\uC744 \uD568\uAED8
|
||||
\uCC59\uAE41\uB2C8\uB2E4."
|
||||
branch: 4
|
||||
nodeKind: 2
|
||||
axisMask: 3
|
||||
tier: 2
|
||||
cost: 1
|
||||
layoutPosition: {x: -0.24, y: 0.14}
|
||||
prerequisiteNodes:
|
||||
- {fileID: 11400000, guid: 608b595113bc12e4dbf618f93ab41aaa, type: 2}
|
||||
- {fileID: 11400000, guid: 7651af69dcffc174eb8baa9a822d8188, type: 2}
|
||||
connectedNodes:
|
||||
- {fileID: 11400000, guid: 608b595113bc12e4dbf618f93ab41aaa, type: 2}
|
||||
- {fileID: 11400000, guid: 7651af69dcffc174eb8baa9a822d8188, type: 2}
|
||||
effects:
|
||||
- effectType: 1
|
||||
statType: 3
|
||||
modType: 0
|
||||
value: 1.06
|
||||
skillRoleMask: 1
|
||||
- effectType: 6
|
||||
statType: 3
|
||||
modType: 0
|
||||
value: 0.95
|
||||
skillRoleMask: 7
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1fe3777d83675364187bfd8134b859d5
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,41 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: c72b2635385ed49498483636164bac87, type: 3}
|
||||
m_Name: Data_PassiveNode_Player_DefenseSupport_Bridge
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Passives.PassiveNodeData
|
||||
nodeId: defense_support_bridge
|
||||
displayName: "\uC218\uD638 \uC21C\uD658"
|
||||
description: "\uBC29\uC5B4\uC640 \uC9C0\uC6D0\uC744 \uC5F0\uACB0\uD558\uB294 \uBE0C\uB9BF\uC9C0\uB85C,
|
||||
\uBCF4\uD638\uB9C9\uACFC \uD68C\uBCF5 \uAE30\uC5EC\uB97C \uD568\uAED8 \uB04C\uC5B4\uC62C\uB9BD\uB2C8\uB2E4."
|
||||
branch: 4
|
||||
nodeKind: 2
|
||||
axisMask: 6
|
||||
tier: 2
|
||||
cost: 1
|
||||
layoutPosition: {x: 0, y: -0.28}
|
||||
prerequisiteNodes:
|
||||
- {fileID: 11400000, guid: 7651af69dcffc174eb8baa9a822d8188, type: 2}
|
||||
- {fileID: 11400000, guid: c5e1fee00b45d7643ae1b51b749d8d57, type: 2}
|
||||
connectedNodes:
|
||||
- {fileID: 11400000, guid: 7651af69dcffc174eb8baa9a822d8188, type: 2}
|
||||
- {fileID: 11400000, guid: c5e1fee00b45d7643ae1b51b749d8d57, type: 2}
|
||||
effects:
|
||||
- effectType: 4
|
||||
statType: 3
|
||||
modType: 0
|
||||
value: 1.1
|
||||
skillRoleMask: 7
|
||||
- effectType: 2
|
||||
statType: 3
|
||||
modType: 0
|
||||
value: 1.08
|
||||
skillRoleMask: 4
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5535fa758a366ad409df12010f661378
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,39 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: c72b2635385ed49498483636164bac87, type: 3}
|
||||
m_Name: Data_PassiveNode_Player_Dps_Capstone
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Passives.PassiveNodeData
|
||||
nodeId: attack_capstone
|
||||
displayName: "\uC9D1\uD589 \uC99D\uD3ED"
|
||||
description: "\uACF5\uACA9 \uCD95 \uC644\uC131 \uB178\uB4DC\uB85C, \uACF5\uACA9
|
||||
\uACC4\uC5F4 \uACE0\uC704\uB825 \uAE30\uC220\uC758 \uAE30\uC5EC\uB3C4\uB97C \uAC15\uD654\uD569\uB2C8\uB2E4."
|
||||
branch: 1
|
||||
nodeKind: 3
|
||||
axisMask: 1
|
||||
tier: 3
|
||||
cost: 2
|
||||
layoutPosition: {x: 0, y: 0.84}
|
||||
prerequisiteNodes:
|
||||
- {fileID: 11400000, guid: 8b6865d1ccdfbc24a956e50ced20da87, type: 2}
|
||||
connectedNodes:
|
||||
- {fileID: 11400000, guid: 8b6865d1ccdfbc24a956e50ced20da87, type: 2}
|
||||
effects:
|
||||
- effectType: 1
|
||||
statType: 3
|
||||
modType: 0
|
||||
value: 1.1
|
||||
skillRoleMask: 1
|
||||
- effectType: 7
|
||||
statType: 3
|
||||
modType: 0
|
||||
value: 0.9
|
||||
skillRoleMask: 1
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 95f84aa662f1c3e4d92c1371d6bfcf17
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,35 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: c72b2635385ed49498483636164bac87, type: 3}
|
||||
m_Name: Data_PassiveNode_Player_Dps_Core
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Passives.PassiveNodeData
|
||||
nodeId: attack_core
|
||||
displayName: "\uC9D1\uC911 \uACF5\uC138"
|
||||
description: "\uACF5\uACA9 \uC2A4\uD0AC \uACC4\uC5F4\uC758 \uD575\uC2EC \uD654\uB825\uC744
|
||||
\uAC15\uD654\uD569\uB2C8\uB2E4."
|
||||
branch: 1
|
||||
nodeKind: 1
|
||||
axisMask: 1
|
||||
tier: 2
|
||||
cost: 1
|
||||
layoutPosition: {x: 0, y: 0.6}
|
||||
prerequisiteNodes:
|
||||
- {fileID: 11400000, guid: 608b595113bc12e4dbf618f93ab41aaa, type: 2}
|
||||
connectedNodes:
|
||||
- {fileID: 11400000, guid: 608b595113bc12e4dbf618f93ab41aaa, type: 2}
|
||||
- {fileID: 11400000, guid: 95f84aa662f1c3e4d92c1371d6bfcf17, type: 2}
|
||||
effects:
|
||||
- effectType: 1
|
||||
statType: 3
|
||||
modType: 0
|
||||
value: 1.12
|
||||
skillRoleMask: 1
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8b6865d1ccdfbc24a956e50ced20da87
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,47 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: c72b2635385ed49498483636164bac87, type: 3}
|
||||
m_Name: Data_PassiveNode_Player_Dps_Entry
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Passives.PassiveNodeData
|
||||
nodeId: attack_entry
|
||||
displayName: "\uACF5\uC138 \uC801\uC751"
|
||||
description: "\uACF5\uACA9 \uCD95\uC758 \uCD9C\uBC1C\uC810\uC73C\uB85C, \uAE30\uBCF8
|
||||
\uD654\uB825 \uACC4\uC5F4 \uC2A4\uD0EF\uC744 \uB04C\uC5B4\uC62C\uB9BD\uB2C8\uB2E4."
|
||||
branch: 1
|
||||
nodeKind: 1
|
||||
axisMask: 1
|
||||
tier: 1
|
||||
cost: 1
|
||||
layoutPosition: {x: 0, y: 0.34}
|
||||
prerequisiteNodes:
|
||||
- {fileID: 11400000, guid: a94fab25cea2ef040a684ad03a9515aa, type: 2}
|
||||
connectedNodes:
|
||||
- {fileID: 11400000, guid: a94fab25cea2ef040a684ad03a9515aa, type: 2}
|
||||
- {fileID: 11400000, guid: 8b6865d1ccdfbc24a956e50ced20da87, type: 2}
|
||||
- {fileID: 11400000, guid: 1fe3777d83675364187bfd8134b859d5, type: 2}
|
||||
- {fileID: 11400000, guid: b654aea2451ac2b4c8feca4baa733c7f, type: 2}
|
||||
effects:
|
||||
- effectType: 0
|
||||
statType: 0
|
||||
modType: 1
|
||||
value: 0.1
|
||||
skillRoleMask: 7
|
||||
- effectType: 0
|
||||
statType: 1
|
||||
modType: 1
|
||||
value: 0.1
|
||||
skillRoleMask: 7
|
||||
- effectType: 0
|
||||
statType: 2
|
||||
modType: 1
|
||||
value: 0.1
|
||||
skillRoleMask: 7
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 608b595113bc12e4dbf618f93ab41aaa
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,29 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: c72b2635385ed49498483636164bac87, type: 3}
|
||||
m_Name: Data_PassiveNode_Player_Hub
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Passives.PassiveNodeData
|
||||
nodeId: hub
|
||||
displayName: "\uC911\uC2EC \uD5C8\uBE0C"
|
||||
description: "\uD328\uC2DC\uBE0C \uD2B8\uB9AC\uC758 \uC2DC\uC791\uC810\uC785\uB2C8\uB2E4."
|
||||
branch: 0
|
||||
nodeKind: 0
|
||||
axisMask: 0
|
||||
tier: 0
|
||||
cost: 0
|
||||
layoutPosition: {x: 0, y: 0}
|
||||
prerequisiteNodes: []
|
||||
connectedNodes:
|
||||
- {fileID: 11400000, guid: 608b595113bc12e4dbf618f93ab41aaa, type: 2}
|
||||
- {fileID: 11400000, guid: 7651af69dcffc174eb8baa9a822d8188, type: 2}
|
||||
- {fileID: 11400000, guid: c5e1fee00b45d7643ae1b51b749d8d57, type: 2}
|
||||
effects: []
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a94fab25cea2ef040a684ad03a9515aa
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,41 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: c72b2635385ed49498483636164bac87, type: 3}
|
||||
m_Name: Data_PassiveNode_Player_SupportAttack_Bridge
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Passives.PassiveNodeData
|
||||
nodeId: support_attack_bridge
|
||||
displayName: "\uC804\uC220 \uC99D\uD3ED"
|
||||
description: "\uC9C0\uC6D0\uACFC \uACF5\uACA9\uC744 \uC5F0\uACB0\uD558\uB294 \uBE0C\uB9BF\uC9C0\uB85C,
|
||||
\uD654\uB825\uACFC \uC720\uC9C0 \uD6A8\uC728\uC744 \uD568\uAED8 \uBCF4\uC870\uD569\uB2C8\uB2E4."
|
||||
branch: 4
|
||||
nodeKind: 2
|
||||
axisMask: 5
|
||||
tier: 2
|
||||
cost: 1
|
||||
layoutPosition: {x: 0.24, y: 0.14}
|
||||
prerequisiteNodes:
|
||||
- {fileID: 11400000, guid: c5e1fee00b45d7643ae1b51b749d8d57, type: 2}
|
||||
- {fileID: 11400000, guid: 608b595113bc12e4dbf618f93ab41aaa, type: 2}
|
||||
connectedNodes:
|
||||
- {fileID: 11400000, guid: c5e1fee00b45d7643ae1b51b749d8d57, type: 2}
|
||||
- {fileID: 11400000, guid: 608b595113bc12e4dbf618f93ab41aaa, type: 2}
|
||||
effects:
|
||||
- effectType: 1
|
||||
statType: 3
|
||||
modType: 0
|
||||
value: 1.06
|
||||
skillRoleMask: 1
|
||||
- effectType: 7
|
||||
statType: 3
|
||||
modType: 0
|
||||
value: 0.95
|
||||
skillRoleMask: 7
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b654aea2451ac2b4c8feca4baa733c7f
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,39 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: c72b2635385ed49498483636164bac87, type: 3}
|
||||
m_Name: Data_PassiveNode_Player_Support_Capstone
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Passives.PassiveNodeData
|
||||
nodeId: support_capstone
|
||||
displayName: "\uB9C8\uB825 \uC21C\uD658"
|
||||
description: "\uCD5C\uB300 \uB9C8\uB098\uC640 \uC720\uC9C0 \uD6A8\uC728\uC744 \uD568\uAED8
|
||||
\uB192\uC785\uB2C8\uB2E4."
|
||||
branch: 3
|
||||
nodeKind: 3
|
||||
axisMask: 4
|
||||
tier: 3
|
||||
cost: 2
|
||||
layoutPosition: {x: 0.82, y: -0.58}
|
||||
prerequisiteNodes:
|
||||
- {fileID: 11400000, guid: 21150da8ec1314242ada62509025a417, type: 2}
|
||||
connectedNodes:
|
||||
- {fileID: 11400000, guid: 21150da8ec1314242ada62509025a417, type: 2}
|
||||
effects:
|
||||
- effectType: 0
|
||||
statType: 5
|
||||
modType: 1
|
||||
value: 0.2
|
||||
skillRoleMask: 7
|
||||
- effectType: 7
|
||||
statType: 3
|
||||
modType: 0
|
||||
value: 0.85
|
||||
skillRoleMask: 4
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: daf27733a031c2f46906ebb801428f34
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,40 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: c72b2635385ed49498483636164bac87, type: 3}
|
||||
m_Name: Data_PassiveNode_Player_Support_Core
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Passives.PassiveNodeData
|
||||
nodeId: support_core
|
||||
displayName: "\uC870\uC728 \uC219\uB828"
|
||||
description: "\uD68C\uBCF5\uACFC \uBCF4\uD638\uB9C9 \uBD80\uC5EC \uD6A8\uC728\uC744
|
||||
\uD568\uAED8 \uAC15\uD654\uD569\uB2C8\uB2E4."
|
||||
branch: 3
|
||||
nodeKind: 1
|
||||
axisMask: 4
|
||||
tier: 2
|
||||
cost: 1
|
||||
layoutPosition: {x: 0.58, y: -0.34}
|
||||
prerequisiteNodes:
|
||||
- {fileID: 11400000, guid: c5e1fee00b45d7643ae1b51b749d8d57, type: 2}
|
||||
connectedNodes:
|
||||
- {fileID: 11400000, guid: c5e1fee00b45d7643ae1b51b749d8d57, type: 2}
|
||||
- {fileID: 11400000, guid: daf27733a031c2f46906ebb801428f34, type: 2}
|
||||
effects:
|
||||
- effectType: 2
|
||||
statType: 3
|
||||
modType: 0
|
||||
value: 1.15
|
||||
skillRoleMask: 4
|
||||
- effectType: 3
|
||||
statType: 3
|
||||
modType: 0
|
||||
value: 1.15
|
||||
skillRoleMask: 4
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 21150da8ec1314242ada62509025a417
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,37 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: c72b2635385ed49498483636164bac87, type: 3}
|
||||
m_Name: Data_PassiveNode_Player_Support_Entry
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Passives.PassiveNodeData
|
||||
nodeId: support_entry
|
||||
displayName: "\uAD6C\uD638 \uC801\uC751"
|
||||
description: "\uC9C0\uC6D0 \uCD95\uC758 \uCD9C\uBC1C\uC810\uC73C\uB85C, \uD68C\uBCF5
|
||||
\uAE30\uBC18 \uB2A5\uB825\uC744 \uB192\uC785\uB2C8\uB2E4."
|
||||
branch: 3
|
||||
nodeKind: 1
|
||||
axisMask: 4
|
||||
tier: 1
|
||||
cost: 1
|
||||
layoutPosition: {x: 0.34, y: -0.1}
|
||||
prerequisiteNodes:
|
||||
- {fileID: 11400000, guid: a94fab25cea2ef040a684ad03a9515aa, type: 2}
|
||||
connectedNodes:
|
||||
- {fileID: 11400000, guid: a94fab25cea2ef040a684ad03a9515aa, type: 2}
|
||||
- {fileID: 11400000, guid: 21150da8ec1314242ada62509025a417, type: 2}
|
||||
- {fileID: 11400000, guid: 5535fa758a366ad409df12010f661378, type: 2}
|
||||
- {fileID: 11400000, guid: b654aea2451ac2b4c8feca4baa733c7f, type: 2}
|
||||
effects:
|
||||
- effectType: 0
|
||||
statType: 4
|
||||
modType: 1
|
||||
value: 0.2
|
||||
skillRoleMask: 7
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c5e1fee00b45d7643ae1b51b749d8d57
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,34 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: c72b2635385ed49498483636164bac87, type: 3}
|
||||
m_Name: Data_PassiveNode_Player_Tank_Capstone
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Passives.PassiveNodeData
|
||||
nodeId: defense_capstone
|
||||
displayName: "\uCCA0\uBCBD \uC720\uC9C0"
|
||||
description: "\uBC1B\uB294 \uD53C\uD574\uB97C \uB0AE\uCDB0 \uC804\uC5F4 \uC720\uC9C0\uB825\uC744
|
||||
\uC644\uC131\uD569\uB2C8\uB2E4."
|
||||
branch: 2
|
||||
nodeKind: 3
|
||||
axisMask: 2
|
||||
tier: 3
|
||||
cost: 2
|
||||
layoutPosition: {x: -0.82, y: -0.58}
|
||||
prerequisiteNodes:
|
||||
- {fileID: 11400000, guid: bb00ad8c4601d3a4fb1e30a153faca38, type: 2}
|
||||
connectedNodes:
|
||||
- {fileID: 11400000, guid: bb00ad8c4601d3a4fb1e30a153faca38, type: 2}
|
||||
effects:
|
||||
- effectType: 6
|
||||
statType: 3
|
||||
modType: 0
|
||||
value: 0.88
|
||||
skillRoleMask: 7
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 4bde7b3376f2b7d429e5a9dc90d1cf3c
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,40 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: c72b2635385ed49498483636164bac87, type: 3}
|
||||
m_Name: Data_PassiveNode_Player_Tank_Core
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Passives.PassiveNodeData
|
||||
nodeId: defense_core
|
||||
displayName: "\uBC29\uD638 \uC219\uB828"
|
||||
description: "\uC704\uD611 \uC720\uC9C0\uC640 \uBCF4\uD638\uB9C9 \uC218\uD61C\uB7C9\uC744
|
||||
\uD568\uAED8 \uAC15\uD654\uD569\uB2C8\uB2E4."
|
||||
branch: 2
|
||||
nodeKind: 1
|
||||
axisMask: 2
|
||||
tier: 2
|
||||
cost: 1
|
||||
layoutPosition: {x: -0.58, y: -0.34}
|
||||
prerequisiteNodes:
|
||||
- {fileID: 11400000, guid: 7651af69dcffc174eb8baa9a822d8188, type: 2}
|
||||
connectedNodes:
|
||||
- {fileID: 11400000, guid: 7651af69dcffc174eb8baa9a822d8188, type: 2}
|
||||
- {fileID: 11400000, guid: 4bde7b3376f2b7d429e5a9dc90d1cf3c, type: 2}
|
||||
effects:
|
||||
- effectType: 5
|
||||
statType: 3
|
||||
modType: 0
|
||||
value: 1.3
|
||||
skillRoleMask: 7
|
||||
- effectType: 4
|
||||
statType: 3
|
||||
modType: 0
|
||||
value: 1.15
|
||||
skillRoleMask: 7
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bb00ad8c4601d3a4fb1e30a153faca38
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,37 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: c72b2635385ed49498483636164bac87, type: 3}
|
||||
m_Name: Data_PassiveNode_Player_Tank_Entry
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Passives.PassiveNodeData
|
||||
nodeId: defense_entry
|
||||
displayName: "\uC804\uC5F4 \uC801\uC751"
|
||||
description: "\uBC29\uC5B4 \uCD95\uC758 \uCD9C\uBC1C\uC810\uC73C\uB85C, \uC804\uC5F4
|
||||
\uC720\uC9C0\uC5D0 \uD544\uC694\uD55C \uC0DD\uC874\uB825\uC744 \uD655\uBCF4\uD569\uB2C8\uB2E4."
|
||||
branch: 2
|
||||
nodeKind: 1
|
||||
axisMask: 2
|
||||
tier: 1
|
||||
cost: 1
|
||||
layoutPosition: {x: -0.34, y: -0.1}
|
||||
prerequisiteNodes:
|
||||
- {fileID: 11400000, guid: a94fab25cea2ef040a684ad03a9515aa, type: 2}
|
||||
connectedNodes:
|
||||
- {fileID: 11400000, guid: a94fab25cea2ef040a684ad03a9515aa, type: 2}
|
||||
- {fileID: 11400000, guid: bb00ad8c4601d3a4fb1e30a153faca38, type: 2}
|
||||
- {fileID: 11400000, guid: 1fe3777d83675364187bfd8134b859d5, type: 2}
|
||||
- {fileID: 11400000, guid: 5535fa758a366ad409df12010f661378, type: 2}
|
||||
effects:
|
||||
- effectType: 0
|
||||
statType: 3
|
||||
modType: 1
|
||||
value: 0.2
|
||||
skillRoleMask: 7
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7651af69dcffc174eb8baa9a822d8188
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
8
Assets/_Game/Data/Passives/Presets.meta
Normal file
8
Assets/_Game/Data/Passives/Presets.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 38a8f9c2ff5a66f41be8fdaae7b8f3e2
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,27 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 2f8d8a9657f495440af71ac932114128, type: 3}
|
||||
m_Name: Data_PassivePreset_Player_Dps
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Passives.PassivePresetData
|
||||
presetName: "\uACF5\uACA9\uD615 \uD328\uC2DC\uBE0C"
|
||||
description: "\uACF5\uACA9 \uCD95 \uC644\uC131\uACFC \uD568\uAED8 \uBC29\uC5B4/\uC9C0\uC6D0
|
||||
\uBE0C\uB9BF\uC9C0\uB97C \uAC00\uBCCD\uAC8C \uC5EC\uB294 \uD504\uB9AC\uC14B\uC785\uB2C8\uB2E4."
|
||||
tree: {fileID: 11400000, guid: 33ad68732732b6a4589e24da88253f8d, type: 2}
|
||||
selectedNodes:
|
||||
- {fileID: 11400000, guid: a94fab25cea2ef040a684ad03a9515aa, type: 2}
|
||||
- {fileID: 11400000, guid: 608b595113bc12e4dbf618f93ab41aaa, type: 2}
|
||||
- {fileID: 11400000, guid: 8b6865d1ccdfbc24a956e50ced20da87, type: 2}
|
||||
- {fileID: 11400000, guid: 95f84aa662f1c3e4d92c1371d6bfcf17, type: 2}
|
||||
- {fileID: 11400000, guid: 7651af69dcffc174eb8baa9a822d8188, type: 2}
|
||||
- {fileID: 11400000, guid: c5e1fee00b45d7643ae1b51b749d8d57, type: 2}
|
||||
- {fileID: 11400000, guid: 1fe3777d83675364187bfd8134b859d5, type: 2}
|
||||
- {fileID: 11400000, guid: b654aea2451ac2b4c8feca4baa733c7f, type: 2}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8ca1fb05eab83bd44bcd3fd46bfc439f
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,19 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 2f8d8a9657f495440af71ac932114128, type: 3}
|
||||
m_Name: Data_PassivePreset_Player_None
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Passives.PassivePresetData
|
||||
presetName: "\uD328\uC2DC\uBE0C \uC5C6\uC74C"
|
||||
description: "\uBE44\uAD50 \uAE30\uC900\uC120 \uD655\uBCF4\uC6A9 \uD504\uB9AC\uC14B\uC785\uB2C8\uB2E4."
|
||||
tree: {fileID: 11400000, guid: 33ad68732732b6a4589e24da88253f8d, type: 2}
|
||||
selectedNodes:
|
||||
- {fileID: 11400000, guid: a94fab25cea2ef040a684ad03a9515aa, type: 2}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ced52cd35b0d0d147ab2bad8af294331
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,27 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 2f8d8a9657f495440af71ac932114128, type: 3}
|
||||
m_Name: Data_PassivePreset_Player_Support
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Passives.PassivePresetData
|
||||
presetName: "\uC9C0\uC6D0\uD615 \uD328\uC2DC\uBE0C"
|
||||
description: "\uC9C0\uC6D0 \uCD95 \uC644\uC131\uACFC \uD568\uAED8 \uACF5\uACA9/\uBC29\uC5B4
|
||||
\uBE0C\uB9BF\uC9C0\uB97C \uAC00\uBCCD\uAC8C \uC5EC\uB294 \uD504\uB9AC\uC14B\uC785\uB2C8\uB2E4."
|
||||
tree: {fileID: 11400000, guid: 33ad68732732b6a4589e24da88253f8d, type: 2}
|
||||
selectedNodes:
|
||||
- {fileID: 11400000, guid: a94fab25cea2ef040a684ad03a9515aa, type: 2}
|
||||
- {fileID: 11400000, guid: c5e1fee00b45d7643ae1b51b749d8d57, type: 2}
|
||||
- {fileID: 11400000, guid: 21150da8ec1314242ada62509025a417, type: 2}
|
||||
- {fileID: 11400000, guid: daf27733a031c2f46906ebb801428f34, type: 2}
|
||||
- {fileID: 11400000, guid: 7651af69dcffc174eb8baa9a822d8188, type: 2}
|
||||
- {fileID: 11400000, guid: 608b595113bc12e4dbf618f93ab41aaa, type: 2}
|
||||
- {fileID: 11400000, guid: 5535fa758a366ad409df12010f661378, type: 2}
|
||||
- {fileID: 11400000, guid: b654aea2451ac2b4c8feca4baa733c7f, type: 2}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: f0f4fa2b1a936314da3b5f71c5f8d9b9
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,27 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!114 &11400000
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 0}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 2f8d8a9657f495440af71ac932114128, type: 3}
|
||||
m_Name: Data_PassivePreset_Player_Tank
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.Passives.PassivePresetData
|
||||
presetName: "\uBC29\uC5B4\uD615 \uD328\uC2DC\uBE0C"
|
||||
description: "\uBC29\uC5B4 \uCD95 \uC644\uC131\uACFC \uD568\uAED8 \uACF5\uACA9/\uC9C0\uC6D0
|
||||
\uBE0C\uB9BF\uC9C0\uB97C \uAC00\uBCCD\uAC8C \uC5EC\uB294 \uD504\uB9AC\uC14B\uC785\uB2C8\uB2E4."
|
||||
tree: {fileID: 11400000, guid: 33ad68732732b6a4589e24da88253f8d, type: 2}
|
||||
selectedNodes:
|
||||
- {fileID: 11400000, guid: a94fab25cea2ef040a684ad03a9515aa, type: 2}
|
||||
- {fileID: 11400000, guid: 7651af69dcffc174eb8baa9a822d8188, type: 2}
|
||||
- {fileID: 11400000, guid: bb00ad8c4601d3a4fb1e30a153faca38, type: 2}
|
||||
- {fileID: 11400000, guid: 4bde7b3376f2b7d429e5a9dc90d1cf3c, type: 2}
|
||||
- {fileID: 11400000, guid: 608b595113bc12e4dbf618f93ab41aaa, type: 2}
|
||||
- {fileID: 11400000, guid: c5e1fee00b45d7643ae1b51b749d8d57, type: 2}
|
||||
- {fileID: 11400000, guid: 1fe3777d83675364187bfd8134b859d5, type: 2}
|
||||
- {fileID: 11400000, guid: 5535fa758a366ad409df12010f661378, type: 2}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 086ccaebf9a348642a99fa0e2f626918
|
||||
NativeFormatImporter:
|
||||
externalObjects: {}
|
||||
mainObjectFileID: 11400000
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/_Game/Icons/Icon_Passive_Normal.png
Normal file
BIN
Assets/_Game/Icons/Icon_Passive_Normal.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
96
Assets/_Game/Icons/Icon_Passive_Normal.png.meta
Normal file
96
Assets/_Game/Icons/Icon_Passive_Normal.png.meta
Normal file
@@ -0,0 +1,96 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1976d5e3c186efb4dbe16a350623ffd1
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: -1
|
||||
aniso: -1
|
||||
mipBias: -100
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
BIN
Assets/_Game/Icons/Icon_Passive_Special.png
Normal file
BIN
Assets/_Game/Icons/Icon_Passive_Special.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 28 KiB |
96
Assets/_Game/Icons/Icon_Passive_Special.png.meta
Normal file
96
Assets/_Game/Icons/Icon_Passive_Special.png.meta
Normal file
@@ -0,0 +1,96 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 088956c0ca61a904ea0d0548ebeb1b53
|
||||
TextureImporter:
|
||||
internalIDToNameTable: []
|
||||
externalObjects: {}
|
||||
serializedVersion: 11
|
||||
mipmaps:
|
||||
mipMapMode: 0
|
||||
enableMipMap: 0
|
||||
sRGBTexture: 1
|
||||
linearTexture: 0
|
||||
fadeOut: 0
|
||||
borderMipMap: 0
|
||||
mipMapsPreserveCoverage: 0
|
||||
alphaTestReferenceValue: 0.5
|
||||
mipMapFadeDistanceStart: 1
|
||||
mipMapFadeDistanceEnd: 3
|
||||
bumpmap:
|
||||
convertToNormalMap: 0
|
||||
externalNormalMap: 0
|
||||
heightScale: 0.25
|
||||
normalMapFilter: 0
|
||||
isReadable: 0
|
||||
streamingMipmaps: 0
|
||||
streamingMipmapsPriority: 0
|
||||
vTOnly: 0
|
||||
grayScaleToAlpha: 0
|
||||
generateCubemap: 6
|
||||
cubemapConvolution: 0
|
||||
seamlessCubemap: 0
|
||||
textureFormat: 1
|
||||
maxTextureSize: 2048
|
||||
textureSettings:
|
||||
serializedVersion: 2
|
||||
filterMode: -1
|
||||
aniso: -1
|
||||
mipBias: -100
|
||||
wrapU: 1
|
||||
wrapV: 1
|
||||
wrapW: 1
|
||||
nPOTScale: 0
|
||||
lightmap: 0
|
||||
compressionQuality: 50
|
||||
spriteMode: 1
|
||||
spriteExtrude: 1
|
||||
spriteMeshType: 1
|
||||
alignment: 0
|
||||
spritePivot: {x: 0.5, y: 0.5}
|
||||
spritePixelsToUnits: 100
|
||||
spriteBorder: {x: 0, y: 0, z: 0, w: 0}
|
||||
spriteGenerateFallbackPhysicsShape: 1
|
||||
alphaUsage: 1
|
||||
alphaIsTransparency: 1
|
||||
spriteTessellationDetail: -1
|
||||
textureType: 8
|
||||
textureShape: 1
|
||||
singleChannelComponent: 0
|
||||
flipbookRows: 1
|
||||
flipbookColumns: 1
|
||||
maxTextureSizeSet: 0
|
||||
compressionQualitySet: 0
|
||||
textureFormatSet: 0
|
||||
ignorePngGamma: 0
|
||||
applyGammaDecoding: 0
|
||||
platformSettings:
|
||||
- serializedVersion: 3
|
||||
buildTarget: DefaultTexturePlatform
|
||||
maxTextureSize: 2048
|
||||
resizeAlgorithm: 0
|
||||
textureFormat: -1
|
||||
textureCompression: 1
|
||||
compressionQuality: 50
|
||||
crunchedCompression: 0
|
||||
allowsAlphaSplitting: 0
|
||||
overridden: 0
|
||||
androidETC2FallbackOverride: 0
|
||||
forceMaximumCompressionQuality_BC6H_BC7: 0
|
||||
spriteSheet:
|
||||
serializedVersion: 2
|
||||
sprites: []
|
||||
outline: []
|
||||
physicsShape: []
|
||||
bones: []
|
||||
spriteID: 5e97eb03825dee720800000000000000
|
||||
internalID: 0
|
||||
vertices: []
|
||||
indices:
|
||||
edges: []
|
||||
weights: []
|
||||
secondaryTextures: []
|
||||
spritePackingTag:
|
||||
pSDRemoveMatte: 0
|
||||
pSDShowRemoveMatteOption: 0
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -204,6 +204,14 @@ MonoBehaviour:
|
||||
ShowTopMostFoldoutHeaderGroup: 1
|
||||
characterStats: {fileID: -5132198055668300151}
|
||||
abnormalityManager: {fileID: 0}
|
||||
shieldStateAbnormality: {fileID: 0}
|
||||
passivePrototypeCatalog: {fileID: 11400000, guid: 328a12c12954ef649a9ba5892323c345, type: 2}
|
||||
passiveTree: {fileID: 11400000, guid: 33ad68732732b6a4589e24da88253f8d, type: 2}
|
||||
applyDefaultPassivePresetOnNetworkSpawn: 0
|
||||
defaultPassivePreset: {fileID: 0}
|
||||
currentPassivePresetName:
|
||||
usedPassivePoints: 0
|
||||
remainingPassivePoints: 0
|
||||
--- !u!114 &8606252901290138286
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
@@ -220,7 +228,14 @@ MonoBehaviour:
|
||||
NetworkAnimatorExpanded: 0
|
||||
AuthorityMode: 1
|
||||
m_Animator: {fileID: 3426985706796420257}
|
||||
TransitionStateInfoList: []
|
||||
TransitionStateInfoList:
|
||||
- IsCrossFadeExit: 0
|
||||
Layer: 0
|
||||
OriginatingState: -1526221077
|
||||
DestinationState: 976603345
|
||||
TransitionDuration: 0.05
|
||||
TriggerNameHash: 976603345
|
||||
TransitionIndex: 0
|
||||
AnimatorParameterEntries:
|
||||
ParameterEntries:
|
||||
- name: Speed
|
||||
@@ -335,7 +350,36 @@ MonoBehaviour:
|
||||
- {fileID: 11400000, guid: a822c7e8c7cee5546ad594b582208e53, type: 2}
|
||||
- {fileID: 11400000, guid: 29e1ce0656471b54f84b18a773032a99, type: 2}
|
||||
- {fileID: 0}
|
||||
- {fileID: 11400000, guid: 2ed15dca92a165046b6df17b28f64874, type: 2}
|
||||
- {fileID: 0}
|
||||
skillLoadoutEntries:
|
||||
- baseSkill: {fileID: 11400000, guid: b7f09e0e899c8fc4bb2cc9204cc6eb4a, type: 2}
|
||||
socketedGems:
|
||||
- {fileID: 0}
|
||||
- {fileID: 0}
|
||||
- baseSkill: {fileID: 11400000, guid: b8c86399865e91144a3d6fcfddc04fd9, type: 2}
|
||||
socketedGems:
|
||||
- {fileID: 0}
|
||||
- {fileID: 0}
|
||||
- baseSkill: {fileID: 11400000, guid: 1020083ab98b8214f918fa2ab7c1a3a1, type: 2}
|
||||
socketedGems:
|
||||
- {fileID: 0}
|
||||
- {fileID: 0}
|
||||
- baseSkill: {fileID: 11400000, guid: a822c7e8c7cee5546ad594b582208e53, type: 2}
|
||||
socketedGems:
|
||||
- {fileID: 0}
|
||||
- {fileID: 0}
|
||||
- baseSkill: {fileID: 11400000, guid: 29e1ce0656471b54f84b18a773032a99, type: 2}
|
||||
socketedGems:
|
||||
- {fileID: 0}
|
||||
- {fileID: 0}
|
||||
- baseSkill: {fileID: 0}
|
||||
socketedGems:
|
||||
- {fileID: 0}
|
||||
- {fileID: 0}
|
||||
- baseSkill: {fileID: 0}
|
||||
socketedGems:
|
||||
- {fileID: 0}
|
||||
- {fileID: 0}
|
||||
skillController: {fileID: 6912018896034183004}
|
||||
networkController: {fileID: 0}
|
||||
weaponEquipment: {fileID: 0}
|
||||
@@ -430,6 +474,7 @@ MonoBehaviour:
|
||||
networkController: {fileID: 0}
|
||||
abnormalityManager: {fileID: 0}
|
||||
skillController: {fileID: 0}
|
||||
hitReactionController: {fileID: 0}
|
||||
spectator: {fileID: 0}
|
||||
--- !u!114 &2540460367028266762
|
||||
MonoBehaviour:
|
||||
@@ -467,6 +512,8 @@ MonoBehaviour:
|
||||
networkController: {fileID: 0}
|
||||
skillInput: {fileID: 0}
|
||||
skillController: {fileID: 0}
|
||||
playerMovement: {fileID: 0}
|
||||
hitReactionController: {fileID: 0}
|
||||
stunData: {fileID: 0}
|
||||
silenceData: {fileID: 0}
|
||||
runOnStartInEditor: 0
|
||||
|
||||
8
Assets/_Game/Prefabs/UI/PassiveTree.meta
Normal file
8
Assets/_Game/Prefabs/UI/PassiveTree.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: ffdc40617e3fa8b42b458aa8ef82e70c
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
232
Assets/_Game/Prefabs/UI/PassiveTree/UI_PassiveTreeNode.prefab
Normal file
232
Assets/_Game/Prefabs/UI/PassiveTree/UI_PassiveTreeNode.prefab
Normal file
@@ -0,0 +1,232 @@
|
||||
%YAML 1.1
|
||||
%TAG !u! tag:unity3d.com,2011:
|
||||
--- !u!1 &581345866983187496
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 6261231552513207993}
|
||||
- component: {fileID: 8268640799320570731}
|
||||
- component: {fileID: 448894188463328570}
|
||||
- component: {fileID: 2832153398621926819}
|
||||
- component: {fileID: 7672961496650835067}
|
||||
- component: {fileID: 3908938516274206423}
|
||||
m_Layer: 0
|
||||
m_Name: UI_PassiveTreeNode
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &6261231552513207993
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 581345866983187496}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children:
|
||||
- {fileID: 3875000767313047181}
|
||||
m_Father: {fileID: 0}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0.5, y: 0.5}
|
||||
m_AnchorMax: {x: 0.5, y: 0.5}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: 70, y: 70}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &8268640799320570731
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 581345866983187496}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &448894188463328570
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 581345866983187496}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 0.16, g: 0.16, b: 0.18, a: 0.98}
|
||||
m_RaycastTarget: 1
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 21300000, guid: 1976d5e3c186efb4dbe16a350623ffd1, type: 3}
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 1
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
--- !u!114 &2832153398621926819
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 581345866983187496}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 4e29b1a8efbd4b44bb3f3716e73f07ff, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Button
|
||||
m_Navigation:
|
||||
m_Mode: 3
|
||||
m_WrapAround: 0
|
||||
m_SelectOnUp: {fileID: 0}
|
||||
m_SelectOnDown: {fileID: 0}
|
||||
m_SelectOnLeft: {fileID: 0}
|
||||
m_SelectOnRight: {fileID: 0}
|
||||
m_Transition: 1
|
||||
m_Colors:
|
||||
m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
|
||||
m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
|
||||
m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
|
||||
m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
|
||||
m_ColorMultiplier: 1
|
||||
m_FadeDuration: 0.1
|
||||
m_SpriteState:
|
||||
m_HighlightedSprite: {fileID: 0}
|
||||
m_PressedSprite: {fileID: 0}
|
||||
m_SelectedSprite: {fileID: 0}
|
||||
m_DisabledSprite: {fileID: 0}
|
||||
m_AnimationTriggers:
|
||||
m_NormalTrigger: Normal
|
||||
m_HighlightedTrigger: Highlighted
|
||||
m_PressedTrigger: Pressed
|
||||
m_SelectedTrigger: Selected
|
||||
m_DisabledTrigger: Disabled
|
||||
m_Interactable: 1
|
||||
m_TargetGraphic: {fileID: 448894188463328570}
|
||||
m_OnClick:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
--- !u!114 &7672961496650835067
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 581345866983187496}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: e19747de3f5aca642ab2be37e372fb86, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Outline
|
||||
m_EffectColor: {r: 0, g: 0, b: 0, a: 0.45}
|
||||
m_EffectDistance: {x: 2, y: 2}
|
||||
m_UseGraphicAlpha: 1
|
||||
--- !u!114 &3908938516274206423
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 581345866983187496}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 68ca5a2201ac9a446ba14501b44a6222, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.UI.PassiveTreeNodeView
|
||||
rootRect: {fileID: 6261231552513207993}
|
||||
backgroundImage: {fileID: 448894188463328570}
|
||||
innerImage: {fileID: 3476538439727275718}
|
||||
button: {fileID: 2832153398621926819}
|
||||
outline: {fileID: 7672961496650835067}
|
||||
--- !u!1 &2257208977927085773
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
serializedVersion: 6
|
||||
m_Component:
|
||||
- component: {fileID: 3875000767313047181}
|
||||
- component: {fileID: 6261820943415329823}
|
||||
- component: {fileID: 3476538439727275718}
|
||||
m_Layer: 0
|
||||
m_Name: InnerIcon
|
||||
m_TagString: Untagged
|
||||
m_Icon: {fileID: 0}
|
||||
m_NavMeshLayer: 0
|
||||
m_StaticEditorFlags: 0
|
||||
m_IsActive: 1
|
||||
--- !u!224 &3875000767313047181
|
||||
RectTransform:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2257208977927085773}
|
||||
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
|
||||
m_LocalPosition: {x: 0, y: 0, z: 0}
|
||||
m_LocalScale: {x: 1, y: 1, z: 1}
|
||||
m_ConstrainProportionsScale: 0
|
||||
m_Children: []
|
||||
m_Father: {fileID: 6261231552513207993}
|
||||
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
|
||||
m_AnchorMin: {x: 0, y: 0}
|
||||
m_AnchorMax: {x: 1, y: 1}
|
||||
m_AnchoredPosition: {x: 0, y: 0}
|
||||
m_SizeDelta: {x: -20, y: -20}
|
||||
m_Pivot: {x: 0.5, y: 0.5}
|
||||
--- !u!222 &6261820943415329823
|
||||
CanvasRenderer:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2257208977927085773}
|
||||
m_CullTransparentMesh: 1
|
||||
--- !u!114 &3476538439727275718
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 2257208977927085773}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: UnityEngine.UI::UnityEngine.UI.Image
|
||||
m_Material: {fileID: 0}
|
||||
m_Color: {r: 0.32, g: 0.32, b: 0.34, a: 0.36}
|
||||
m_RaycastTarget: 0
|
||||
m_RaycastPadding: {x: 0, y: 0, z: 0, w: 0}
|
||||
m_Maskable: 1
|
||||
m_OnCullStateChanged:
|
||||
m_PersistentCalls:
|
||||
m_Calls: []
|
||||
m_Sprite: {fileID: 21300000, guid: 1976d5e3c186efb4dbe16a350623ffd1, type: 3}
|
||||
m_Type: 0
|
||||
m_PreserveAspect: 1
|
||||
m_FillCenter: 1
|
||||
m_FillMethod: 4
|
||||
m_FillAmount: 1
|
||||
m_FillClockwise: 1
|
||||
m_FillOrigin: 0
|
||||
m_UseSpriteMesh: 0
|
||||
m_PixelsPerUnitMultiplier: 1
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1d3b43f2c97adda4fbfd2673dc9742c5
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
4915
Assets/_Game/Prefabs/UI/PassiveTree/UI_PassiveTreeView.prefab
Normal file
4915
Assets/_Game/Prefabs/UI/PassiveTree/UI_PassiveTreeView.prefab
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 84f4ef5d87e891e4d85dfd6c2159b45d
|
||||
PrefabImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -1259,6 +1259,7 @@ GameObject:
|
||||
m_Component:
|
||||
- component: {fileID: 8128918834687467434}
|
||||
- component: {fileID: 5228383019470402710}
|
||||
- component: {fileID: 7013584622031137981}
|
||||
m_Layer: 5
|
||||
m_Name: UI_PlayerResources
|
||||
m_TagString: Untagged
|
||||
@@ -1301,7 +1302,47 @@ MonoBehaviour:
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.UI.PlayerHUD
|
||||
healthBar: {fileID: 8880571246735491684}
|
||||
manaBar: {fileID: 382917834290158363}
|
||||
abnormalitySummaryText: {fileID: 0}
|
||||
autoCreateAbnormalitySummary: 1
|
||||
autoCreatePassiveTreeUi: 1
|
||||
autoFindPlayer: 1
|
||||
--- !u!114 &7013584622031137981
|
||||
MonoBehaviour:
|
||||
m_ObjectHideFlags: 0
|
||||
m_CorrespondingSourceObject: {fileID: 0}
|
||||
m_PrefabInstance: {fileID: 0}
|
||||
m_PrefabAsset: {fileID: 0}
|
||||
m_GameObject: {fileID: 6416932529810011951}
|
||||
m_Enabled: 1
|
||||
m_EditorHideFlags: 0
|
||||
m_Script: {fileID: 11500000, guid: 7cbd4a7a9310669419c8eb607903f8b1, type: 3}
|
||||
m_Name:
|
||||
m_EditorClassIdentifier: Colosseum.Game::Colosseum.UI.PassiveTreeUI
|
||||
toggleKey: 30
|
||||
toggleButtonLabel: "\uD328\uC2DC\uBE0C"
|
||||
toggleButtonAnchoredPosition: {x: -132, y: 48}
|
||||
openOnStartInPlayMode: 0
|
||||
viewPrefab: {fileID: 4566021111625159030, guid: 84f4ef5d87e891e4d85dfd6c2159b45d, type: 3}
|
||||
nodePrefab: {fileID: 3908938516274206423, guid: 1d3b43f2c97adda4fbfd2673dc9742c5, type: 3}
|
||||
passivePrototypeCatalog: {fileID: 11400000, guid: 328a12c12954ef649a9ba5892323c345, type: 2}
|
||||
normalNodeSprite: {fileID: 21300000, guid: 1976d5e3c186efb4dbe16a350623ffd1, type: 3}
|
||||
specialNodeSprite: {fileID: 21300000, guid: 088956c0ca61a904ea0d0548ebeb1b53, type: 3}
|
||||
innerNodeSprite: {fileID: 21300000, guid: 1976d5e3c186efb4dbe16a350623ffd1, type: 3}
|
||||
panelBackgroundColor: {r: 0.07, g: 0.07, b: 0.1, a: 0.98}
|
||||
sectionBackgroundColor: {r: 0.12, g: 0.12, b: 0.16, a: 0.96}
|
||||
sectionOverlayColor: {r: 0, g: 0, b: 0, a: 0.12}
|
||||
attackColor: {r: 0.78, g: 0.3, b: 0.22, a: 1}
|
||||
defenseColor: {r: 0.2, g: 0.56, b: 0.6, a: 1}
|
||||
supportColor: {r: 0.56, g: 0.67, b: 0.24, a: 1}
|
||||
bridgeColor: {r: 0.58, g: 0.47, b: 0.23, a: 1}
|
||||
lockedNodeColor: {r: 0.15, g: 0.15, b: 0.17, a: 0.96}
|
||||
selectedNodeTint: {r: 1, g: 0.92, b: 0.68, a: 1}
|
||||
focusedOutlineColor: {r: 0.95, g: 0.92, b: 0.82, a: 1}
|
||||
normalTextColor: {r: 0.9, g: 0.88, b: 0.82, a: 1}
|
||||
mutedTextColor: {r: 0.72, g: 0.71, b: 0.66, a: 0.95}
|
||||
statusErrorColor: {r: 1, g: 0.52, b: 0.45, a: 1}
|
||||
lineColor: {r: 0.72, g: 0.67, b: 0.53, a: 0.4}
|
||||
activeLineColor: {r: 0.98, g: 0.9, b: 0.7, a: 0.92}
|
||||
--- !u!1 &7094235100059026060
|
||||
GameObject:
|
||||
m_ObjectHideFlags: 0
|
||||
|
||||
@@ -5,6 +5,7 @@ using Unity.Netcode;
|
||||
using UnityEngine;
|
||||
|
||||
using Colosseum.Enemy;
|
||||
using Colosseum.Passives;
|
||||
using Colosseum.Skills;
|
||||
|
||||
namespace Colosseum.Combat
|
||||
@@ -18,6 +19,7 @@ namespace Colosseum.Combat
|
||||
private sealed class ActorMetrics
|
||||
{
|
||||
public string label;
|
||||
public string passivePresetName;
|
||||
public float totalDamageDealt;
|
||||
public float bossDamageDealt;
|
||||
public float damageTaken;
|
||||
@@ -193,6 +195,12 @@ namespace Colosseum.Combat
|
||||
builder.AppendLine();
|
||||
builder.Append("- ");
|
||||
builder.Append(metrics.label);
|
||||
if (!string.IsNullOrWhiteSpace(metrics.passivePresetName))
|
||||
{
|
||||
builder.Append(" [Passive=");
|
||||
builder.Append(metrics.passivePresetName);
|
||||
builder.Append(']');
|
||||
}
|
||||
builder.Append(" | BossDmg=");
|
||||
builder.Append(metrics.bossDamageDealt.ToString("0.##"));
|
||||
builder.Append(" | TotalDmg=");
|
||||
@@ -244,6 +252,8 @@ namespace Colosseum.Combat
|
||||
actorMetrics.Add(actorLabel, metrics);
|
||||
}
|
||||
|
||||
metrics.passivePresetName = PassiveRuntimeModifierUtility.GetCurrentPresetName(actor);
|
||||
|
||||
return metrics;
|
||||
}
|
||||
|
||||
|
||||
630
Assets/_Game/Scripts/Editor/PassiveTreeUiPrefabBuilder.cs
Normal file
630
Assets/_Game/Scripts/Editor/PassiveTreeUiPrefabBuilder.cs
Normal file
@@ -0,0 +1,630 @@
|
||||
using System.IO;
|
||||
|
||||
using TMPro;
|
||||
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
using Colosseum.Passives;
|
||||
using Colosseum.UI;
|
||||
|
||||
namespace Colosseum.Editor
|
||||
{
|
||||
/// <summary>
|
||||
/// 패시브 트리 uGUI 프리팹을 재생성하는 에디터 유틸리티입니다.
|
||||
/// </summary>
|
||||
public static class PassiveTreeUiPrefabBuilder
|
||||
{
|
||||
private const string PrefabFolder = "Assets/_Game/Prefabs/UI/PassiveTree";
|
||||
private const string ViewPrefabPath = PrefabFolder + "/UI_PassiveTreeView.prefab";
|
||||
private const string NodePrefabPath = PrefabFolder + "/UI_PassiveTreeNode.prefab";
|
||||
private const string PlayerHudPrefabPath = "Assets/_Game/Prefabs/UI/UI_PlayerResources.prefab";
|
||||
private const string PreferredFontAssetPath = "Assets/_Game/Fonts/TMP/TMP_MaruBuri.asset";
|
||||
private const string NormalIconSpritePath = "Assets/_Game/Icons/Icon_Passive_Normal.png";
|
||||
private const string SpecialIconSpritePath = "Assets/_Game/Icons/Icon_Passive_Special.png";
|
||||
private const string PassiveCatalogAssetPath = "Assets/_Game/Data/Passives/Data_PassivePrototypeCatalog.asset";
|
||||
|
||||
private static readonly Color PanelBackgroundColor = new(0.07f, 0.07f, 0.10f, 0.98f);
|
||||
private static readonly Color SectionBackgroundColor = new(0.12f, 0.12f, 0.16f, 0.96f);
|
||||
private static readonly Color SectionOverlayColor = new(0f, 0f, 0f, 0.12f);
|
||||
private static readonly Color ToggleButtonColor = new(0.18f, 0.21f, 0.16f, 0.96f);
|
||||
private static readonly Color CloseButtonColor = new(0.34f, 0.20f, 0.20f, 0.96f);
|
||||
private static readonly Color ButtonColor = new(0.20f, 0.20f, 0.24f, 0.96f);
|
||||
private static readonly Color TextColor = new(0.90f, 0.88f, 0.82f, 1f);
|
||||
private static readonly Color NodeColor = new(0.16f, 0.16f, 0.18f, 0.98f);
|
||||
private static readonly Color NodeOutlineColor = new(0f, 0f, 0f, 0.45f);
|
||||
|
||||
[MenuItem("Tools/Colosseum/Passives/Rebuild Passive UI Prefabs")]
|
||||
public static void RebuildPassiveUiPrefabs()
|
||||
{
|
||||
EnsureFolders();
|
||||
|
||||
PassiveTreeNodeView nodePrefab = BuildNodePrefab();
|
||||
PassiveTreeViewReferences viewPrefab = BuildViewPrefab();
|
||||
BindPlayerHudPrefab(viewPrefab, nodePrefab);
|
||||
|
||||
AssetDatabase.SaveAssets();
|
||||
AssetDatabase.Refresh();
|
||||
|
||||
Debug.Log("[PassiveTreeUiPrefabBuilder] 패시브 UI 프리팹 재생성을 완료했습니다.");
|
||||
}
|
||||
|
||||
private static void EnsureFolders()
|
||||
{
|
||||
if (!AssetDatabase.IsValidFolder("Assets/_Game/Prefabs/UI"))
|
||||
{
|
||||
AssetDatabase.CreateFolder("Assets/_Game/Prefabs", "UI");
|
||||
}
|
||||
|
||||
if (!AssetDatabase.IsValidFolder(PrefabFolder))
|
||||
{
|
||||
AssetDatabase.CreateFolder("Assets/_Game/Prefabs/UI", "PassiveTree");
|
||||
}
|
||||
}
|
||||
|
||||
private static PassiveTreeNodeView BuildNodePrefab()
|
||||
{
|
||||
GameObject root = new("UI_PassiveTreeNode", typeof(RectTransform), typeof(CanvasRenderer), typeof(Image), typeof(Button), typeof(Outline), typeof(PassiveTreeNodeView));
|
||||
try
|
||||
{
|
||||
RectTransform rect = root.GetComponent<RectTransform>();
|
||||
rect.sizeDelta = new Vector2(70f, 70f);
|
||||
|
||||
Image background = root.GetComponent<Image>();
|
||||
background.sprite = LoadPassiveNodeSprite(false);
|
||||
background.type = Image.Type.Simple;
|
||||
background.preserveAspect = true;
|
||||
background.color = NodeColor;
|
||||
|
||||
Outline outline = root.GetComponent<Outline>();
|
||||
outline.effectColor = NodeOutlineColor;
|
||||
outline.effectDistance = new Vector2(2f, 2f);
|
||||
|
||||
Button button = root.GetComponent<Button>();
|
||||
button.targetGraphic = background;
|
||||
|
||||
GameObject innerObject = CreateUiObject("InnerIcon", root.transform, typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
|
||||
RectTransform innerRect = innerObject.GetComponent<RectTransform>();
|
||||
StretchRect(innerRect);
|
||||
innerRect.offsetMin = new Vector2(10f, 10f);
|
||||
innerRect.offsetMax = new Vector2(-10f, -10f);
|
||||
|
||||
Image innerImage = innerObject.GetComponent<Image>();
|
||||
innerImage.sprite = LoadPassiveNodeSprite(false);
|
||||
innerImage.type = Image.Type.Simple;
|
||||
innerImage.preserveAspect = true;
|
||||
innerImage.color = new Color(0.32f, 0.32f, 0.34f, 0.36f);
|
||||
innerImage.raycastTarget = false;
|
||||
|
||||
PassiveTreeNodeView references = root.GetComponent<PassiveTreeNodeView>();
|
||||
SetSerializedReference(references, "rootRect", rect);
|
||||
SetSerializedReference(references, "backgroundImage", background);
|
||||
SetSerializedReference(references, "innerImage", innerImage);
|
||||
SetSerializedReference(references, "button", button);
|
||||
SetSerializedReference(references, "outline", outline);
|
||||
|
||||
GameObject saved = SavePrefab(root, NodePrefabPath);
|
||||
return saved.GetComponent<PassiveTreeNodeView>();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Object.DestroyImmediate(root);
|
||||
}
|
||||
}
|
||||
|
||||
private static PassiveTreeViewReferences BuildViewPrefab()
|
||||
{
|
||||
GameObject root = new("UI_PassiveTreeView", typeof(RectTransform), typeof(PassiveTreeViewReferences));
|
||||
try
|
||||
{
|
||||
RectTransform rootRect = root.GetComponent<RectTransform>();
|
||||
StretchRect(rootRect);
|
||||
|
||||
Button toggleButton = CreateButton(root.transform, "Button_PassiveTree", "패시브", new Vector2(110f, 40f), 20f, FontStyles.Bold, ToggleButtonColor, TextAnchor.MiddleCenter);
|
||||
RectTransform toggleRect = toggleButton.GetComponent<RectTransform>();
|
||||
toggleRect.anchorMin = new Vector2(1f, 0f);
|
||||
toggleRect.anchorMax = new Vector2(1f, 0f);
|
||||
toggleRect.pivot = new Vector2(1f, 0f);
|
||||
toggleRect.anchoredPosition = new Vector2(-132f, 48f);
|
||||
|
||||
GameObject overlayRootObject = CreateUiObject("OverlayRoot", root.transform, typeof(RectTransform));
|
||||
RectTransform overlayRootRect = overlayRootObject.GetComponent<RectTransform>();
|
||||
StretchRect(overlayRootRect);
|
||||
overlayRootObject.SetActive(false);
|
||||
|
||||
Image backdrop = AddImage(overlayRootObject, "Backdrop", new Color(0f, 0f, 0f, 0.52f));
|
||||
StretchRect(backdrop.rectTransform);
|
||||
|
||||
GameObject panelObject = CreateUiObject("Panel", overlayRootObject.transform, typeof(RectTransform), typeof(Image), typeof(VerticalLayoutGroup));
|
||||
RectTransform panelRect = panelObject.GetComponent<RectTransform>();
|
||||
panelRect.anchorMin = new Vector2(0.5f, 0.5f);
|
||||
panelRect.anchorMax = new Vector2(0.5f, 0.5f);
|
||||
panelRect.pivot = new Vector2(0.5f, 0.5f);
|
||||
panelRect.sizeDelta = new Vector2(1380f, 820f);
|
||||
|
||||
Image panelImage = panelObject.GetComponent<Image>();
|
||||
panelImage.sprite = GetBuiltinSprite();
|
||||
panelImage.type = Image.Type.Sliced;
|
||||
panelImage.color = PanelBackgroundColor;
|
||||
|
||||
VerticalLayoutGroup panelLayout = panelObject.GetComponent<VerticalLayoutGroup>();
|
||||
panelLayout.padding = new RectOffset(22, 22, 22, 22);
|
||||
panelLayout.spacing = 16f;
|
||||
panelLayout.childAlignment = TextAnchor.UpperLeft;
|
||||
panelLayout.childControlWidth = true;
|
||||
panelLayout.childControlHeight = true;
|
||||
panelLayout.childForceExpandWidth = true;
|
||||
panelLayout.childForceExpandHeight = false;
|
||||
|
||||
BuildHeader(panelObject.transform, out TextMeshProUGUI pointsSummaryText, out Button closeButton);
|
||||
BuildBody(panelObject.transform,
|
||||
out TextMeshProUGUI selectionSummaryText,
|
||||
out Button nonePresetButton,
|
||||
out Button defensePresetButton,
|
||||
out Button supportPresetButton,
|
||||
out Button attackPresetButton,
|
||||
out Button clearButton,
|
||||
out RectTransform graphRect,
|
||||
out RectTransform connectionLayer,
|
||||
out RectTransform nodeLayer,
|
||||
out RectTransform detailContent,
|
||||
out TextMeshProUGUI detailText,
|
||||
out Button selectNodeButton,
|
||||
out TextMeshProUGUI selectNodeButtonLabel);
|
||||
BuildFooter(panelObject.transform, out TextMeshProUGUI statusText);
|
||||
|
||||
PassiveTreeViewReferences references = root.GetComponent<PassiveTreeViewReferences>();
|
||||
SetSerializedReference(references, "rootRect", rootRect);
|
||||
SetSerializedReference(references, "overlayRoot", overlayRootObject);
|
||||
SetSerializedReference(references, "panelRect", panelRect);
|
||||
SetSerializedReference(references, "toggleButton", toggleButton);
|
||||
SetSerializedReference(references, "toggleButtonLabel", toggleButton.GetComponentInChildren<TextMeshProUGUI>(true));
|
||||
SetSerializedReference(references, "pointsSummaryText", pointsSummaryText);
|
||||
SetSerializedReference(references, "closeButton", closeButton);
|
||||
SetSerializedReference(references, "selectionSummaryText", selectionSummaryText);
|
||||
SetSerializedReference(references, "nonePresetButton", nonePresetButton);
|
||||
SetSerializedReference(references, "defensePresetButton", defensePresetButton);
|
||||
SetSerializedReference(references, "supportPresetButton", supportPresetButton);
|
||||
SetSerializedReference(references, "attackPresetButton", attackPresetButton);
|
||||
SetSerializedReference(references, "clearButton", clearButton);
|
||||
SetSerializedReference(references, "graphRect", graphRect);
|
||||
SetSerializedReference(references, "connectionLayer", connectionLayer);
|
||||
SetSerializedReference(references, "nodeLayer", nodeLayer);
|
||||
SetSerializedReference(references, "detailContent", detailContent);
|
||||
SetSerializedReference(references, "detailText", detailText);
|
||||
SetSerializedReference(references, "selectNodeButton", selectNodeButton);
|
||||
SetSerializedReference(references, "selectNodeButtonLabel", selectNodeButtonLabel);
|
||||
SetSerializedReference(references, "statusText", statusText);
|
||||
|
||||
GameObject saved = SavePrefab(root, ViewPrefabPath);
|
||||
return saved.GetComponent<PassiveTreeViewReferences>();
|
||||
}
|
||||
finally
|
||||
{
|
||||
Object.DestroyImmediate(root);
|
||||
}
|
||||
}
|
||||
|
||||
private static void BindPlayerHudPrefab(PassiveTreeViewReferences viewPrefab, PassiveTreeNodeView nodePrefab)
|
||||
{
|
||||
GameObject root = PrefabUtility.LoadPrefabContents(PlayerHudPrefabPath);
|
||||
try
|
||||
{
|
||||
PassiveTreeUI passiveTreeUi = root.GetComponent<PassiveTreeUI>();
|
||||
if (passiveTreeUi == null)
|
||||
{
|
||||
passiveTreeUi = root.AddComponent<PassiveTreeUI>();
|
||||
}
|
||||
|
||||
SerializedObject passiveTreeUiObject = new(passiveTreeUi);
|
||||
passiveTreeUiObject.FindProperty("viewPrefab").objectReferenceValue = viewPrefab;
|
||||
passiveTreeUiObject.FindProperty("nodePrefab").objectReferenceValue = nodePrefab;
|
||||
passiveTreeUiObject.FindProperty("passivePrototypeCatalog").objectReferenceValue = LoadPassivePrototypeCatalog();
|
||||
passiveTreeUiObject.FindProperty("normalNodeSprite").objectReferenceValue = LoadPassiveNodeSprite(false);
|
||||
passiveTreeUiObject.FindProperty("specialNodeSprite").objectReferenceValue = LoadPassiveNodeSprite(true);
|
||||
passiveTreeUiObject.FindProperty("innerNodeSprite").objectReferenceValue = LoadPassiveNodeSprite(false);
|
||||
passiveTreeUiObject.ApplyModifiedPropertiesWithoutUndo();
|
||||
|
||||
PrefabUtility.SaveAsPrefabAsset(root, PlayerHudPrefabPath);
|
||||
}
|
||||
finally
|
||||
{
|
||||
PrefabUtility.UnloadPrefabContents(root);
|
||||
}
|
||||
}
|
||||
|
||||
private static void BuildHeader(Transform parent, out TextMeshProUGUI pointsSummaryText, out Button closeButton)
|
||||
{
|
||||
GameObject headerObject = CreateUiObject("Header", parent, typeof(RectTransform), typeof(Image), typeof(LayoutElement), typeof(HorizontalLayoutGroup));
|
||||
RectTransform headerRect = headerObject.GetComponent<RectTransform>();
|
||||
headerObject.GetComponent<LayoutElement>().minHeight = 54f;
|
||||
|
||||
Image headerImage = headerObject.GetComponent<Image>();
|
||||
headerImage.sprite = GetBuiltinSprite();
|
||||
headerImage.type = Image.Type.Sliced;
|
||||
headerImage.color = SectionBackgroundColor;
|
||||
|
||||
HorizontalLayoutGroup headerLayout = headerObject.GetComponent<HorizontalLayoutGroup>();
|
||||
headerLayout.padding = new RectOffset(14, 14, 8, 8);
|
||||
headerLayout.spacing = 10f;
|
||||
headerLayout.childAlignment = TextAnchor.MiddleLeft;
|
||||
headerLayout.childControlWidth = true;
|
||||
headerLayout.childControlHeight = true;
|
||||
headerLayout.childForceExpandWidth = false;
|
||||
headerLayout.childForceExpandHeight = false;
|
||||
|
||||
GameObject titleGroup = CreateUiObject("TitleGroup", headerObject.transform, typeof(RectTransform), typeof(LayoutElement), typeof(VerticalLayoutGroup));
|
||||
titleGroup.GetComponent<LayoutElement>().flexibleWidth = 1f;
|
||||
VerticalLayoutGroup titleLayout = titleGroup.GetComponent<VerticalLayoutGroup>();
|
||||
titleLayout.spacing = 0f;
|
||||
titleLayout.childAlignment = TextAnchor.MiddleLeft;
|
||||
titleLayout.childControlWidth = true;
|
||||
titleLayout.childControlHeight = true;
|
||||
titleLayout.childForceExpandWidth = true;
|
||||
titleLayout.childForceExpandHeight = false;
|
||||
|
||||
TextMeshProUGUI title = CreateText(titleGroup.transform, "Label_Title", 24f, TextAlignmentOptions.Left, FontStyles.Bold);
|
||||
title.text = "패시브 트리";
|
||||
title.textWrappingMode = TextWrappingModes.NoWrap;
|
||||
|
||||
pointsSummaryText = CreateText(headerObject.transform, "Label_Points", 17f, TextAlignmentOptions.Right, FontStyles.Normal);
|
||||
pointsSummaryText.text = string.Empty;
|
||||
pointsSummaryText.textWrappingMode = TextWrappingModes.NoWrap;
|
||||
pointsSummaryText.gameObject.AddComponent<LayoutElement>().preferredWidth = 420f;
|
||||
|
||||
closeButton = CreateButton(headerObject.transform, "Button_Close", "닫기", new Vector2(84f, 34f), 18f, FontStyles.Bold, CloseButtonColor, TextAnchor.MiddleCenter);
|
||||
}
|
||||
|
||||
private static void BuildBody(
|
||||
Transform parent,
|
||||
out TextMeshProUGUI selectionSummaryText,
|
||||
out Button nonePresetButton,
|
||||
out Button defensePresetButton,
|
||||
out Button supportPresetButton,
|
||||
out Button attackPresetButton,
|
||||
out Button clearButton,
|
||||
out RectTransform graphRect,
|
||||
out RectTransform connectionLayer,
|
||||
out RectTransform nodeLayer,
|
||||
out RectTransform detailContent,
|
||||
out TextMeshProUGUI detailText,
|
||||
out Button selectNodeButton,
|
||||
out TextMeshProUGUI selectNodeButtonLabel)
|
||||
{
|
||||
GameObject bodyObject = CreateUiObject("Body", parent, typeof(RectTransform), typeof(LayoutElement), typeof(HorizontalLayoutGroup));
|
||||
LayoutElement bodyLayoutElement = bodyObject.GetComponent<LayoutElement>();
|
||||
bodyLayoutElement.flexibleHeight = 1f;
|
||||
|
||||
HorizontalLayoutGroup bodyLayout = bodyObject.GetComponent<HorizontalLayoutGroup>();
|
||||
bodyLayout.spacing = 16f;
|
||||
bodyLayout.childAlignment = TextAnchor.UpperLeft;
|
||||
bodyLayout.childControlWidth = true;
|
||||
bodyLayout.childControlHeight = true;
|
||||
bodyLayout.childForceExpandWidth = true;
|
||||
bodyLayout.childForceExpandHeight = true;
|
||||
|
||||
RectTransform graphSection = CreateSectionRoot(bodyObject.transform, "Section_Graph", 0f);
|
||||
graphSection.GetComponent<LayoutElement>().flexibleWidth = 1f;
|
||||
graphSection.GetComponent<LayoutElement>().minWidth = 760f;
|
||||
CreateSectionTitle(graphSection, "트리 그래프");
|
||||
|
||||
GameObject graphSurface = CreateUiObject("GraphSurface", graphSection, typeof(RectTransform), typeof(LayoutElement), typeof(Image));
|
||||
graphRect = graphSurface.GetComponent<RectTransform>();
|
||||
LayoutElement graphLayout = graphSurface.GetComponent<LayoutElement>();
|
||||
graphLayout.flexibleHeight = 1f;
|
||||
graphLayout.minHeight = 560f;
|
||||
|
||||
Image graphImage = graphSurface.GetComponent<Image>();
|
||||
graphImage.sprite = GetBuiltinSprite();
|
||||
graphImage.type = Image.Type.Sliced;
|
||||
graphImage.color = SectionOverlayColor;
|
||||
|
||||
connectionLayer = CreateUiObject("Connections", graphSurface.transform, typeof(RectTransform)).GetComponent<RectTransform>();
|
||||
StretchRect(connectionLayer);
|
||||
nodeLayer = CreateUiObject("Nodes", graphSurface.transform, typeof(RectTransform)).GetComponent<RectTransform>();
|
||||
StretchRect(nodeLayer);
|
||||
|
||||
RectTransform rightSection = CreateSectionRoot(bodyObject.transform, "Section_Right", 360f);
|
||||
CreateSectionTitle(rightSection, "노드 상세");
|
||||
RectTransform detailScroll = CreateScrollView(rightSection, "Scroll_Detail", out detailContent);
|
||||
LayoutElement detailScrollLayout = detailScroll.gameObject.AddComponent<LayoutElement>();
|
||||
detailScrollLayout.flexibleHeight = 1f;
|
||||
detailScrollLayout.minHeight = 300f;
|
||||
|
||||
detailText = CreateText(detailContent, "Label_Detail", 19f, TextAlignmentOptions.TopLeft, FontStyles.Normal);
|
||||
detailText.textWrappingMode = TextWrappingModes.Normal;
|
||||
detailText.overflowMode = TextOverflowModes.Overflow;
|
||||
detailText.margin = new Vector4(18f, 8f, 18f, 14f);
|
||||
detailText.extraPadding = true;
|
||||
detailText.gameObject.AddComponent<LayoutElement>().minHeight = 300f;
|
||||
|
||||
selectNodeButton = CreateButton(rightSection, "Button_SelectNode", "노드 선택", new Vector2(0f, 58f), 21f, FontStyles.Bold, ButtonColor, TextAnchor.MiddleCenter);
|
||||
selectNodeButtonLabel = selectNodeButton.GetComponentInChildren<TextMeshProUGUI>(true);
|
||||
|
||||
CreateDivider(rightSection, "Divider_Actions");
|
||||
|
||||
selectionSummaryText = CreateInfoText(rightSection, "Label_SelectionSummary", 132f);
|
||||
|
||||
CreateSectionTitle(rightSection, "프리셋");
|
||||
GameObject presetGrid = CreateUiObject("PresetGrid", rightSection, typeof(RectTransform), typeof(GridLayoutGroup));
|
||||
GridLayoutGroup presetLayout = presetGrid.GetComponent<GridLayoutGroup>();
|
||||
presetLayout.cellSize = new Vector2(154f, 56f);
|
||||
presetLayout.spacing = new Vector2(8f, 8f);
|
||||
presetLayout.constraint = GridLayoutGroup.Constraint.FixedColumnCount;
|
||||
presetLayout.constraintCount = 2;
|
||||
|
||||
nonePresetButton = CreateButton(presetGrid.transform, "Button_Preset_None", "패시브 없음", Vector2.zero, 18f, FontStyles.Normal, ButtonColor, TextAnchor.MiddleCenter);
|
||||
defensePresetButton = CreateButton(presetGrid.transform, "Button_Preset_Defense", "방어형 패시브", Vector2.zero, 18f, FontStyles.Normal, ButtonColor, TextAnchor.MiddleCenter);
|
||||
supportPresetButton = CreateButton(presetGrid.transform, "Button_Preset_Support", "지원형 패시브", Vector2.zero, 18f, FontStyles.Normal, ButtonColor, TextAnchor.MiddleCenter);
|
||||
attackPresetButton = CreateButton(presetGrid.transform, "Button_Preset_Attack", "공격형 패시브", Vector2.zero, 18f, FontStyles.Normal, ButtonColor, TextAnchor.MiddleCenter);
|
||||
clearButton = CreateButton(rightSection, "Button_Clear", "전체 초기화", new Vector2(0f, 52f), 18f, FontStyles.Bold, ButtonColor, TextAnchor.MiddleCenter);
|
||||
}
|
||||
|
||||
private static void BuildFooter(Transform parent, out TextMeshProUGUI statusText)
|
||||
{
|
||||
GameObject footerObject = CreateUiObject("Footer", parent, typeof(RectTransform), typeof(Image), typeof(LayoutElement), typeof(HorizontalLayoutGroup));
|
||||
footerObject.GetComponent<LayoutElement>().minHeight = 42f;
|
||||
|
||||
Image footerImage = footerObject.GetComponent<Image>();
|
||||
footerImage.sprite = GetBuiltinSprite();
|
||||
footerImage.type = Image.Type.Sliced;
|
||||
footerImage.color = SectionBackgroundColor;
|
||||
|
||||
HorizontalLayoutGroup footerLayout = footerObject.GetComponent<HorizontalLayoutGroup>();
|
||||
footerLayout.padding = new RectOffset(16, 16, 8, 8);
|
||||
footerLayout.childAlignment = TextAnchor.MiddleLeft;
|
||||
footerLayout.childControlWidth = true;
|
||||
footerLayout.childControlHeight = true;
|
||||
footerLayout.childForceExpandWidth = true;
|
||||
footerLayout.childForceExpandHeight = true;
|
||||
|
||||
statusText = CreateText(footerObject.transform, "Label_Status", 17f, TextAlignmentOptions.MidlineLeft, FontStyles.Normal);
|
||||
statusText.text = string.Empty;
|
||||
statusText.textWrappingMode = TextWrappingModes.NoWrap;
|
||||
statusText.overflowMode = TextOverflowModes.Ellipsis;
|
||||
footerObject.SetActive(false);
|
||||
}
|
||||
|
||||
private static RectTransform CreateSectionRoot(Transform parent, string name, float preferredWidth)
|
||||
{
|
||||
GameObject sectionObject = CreateUiObject(name, parent, typeof(RectTransform), typeof(Image), typeof(LayoutElement), typeof(VerticalLayoutGroup));
|
||||
RectTransform rect = sectionObject.GetComponent<RectTransform>();
|
||||
LayoutElement layoutElement = sectionObject.GetComponent<LayoutElement>();
|
||||
layoutElement.flexibleHeight = 1f;
|
||||
layoutElement.preferredWidth = preferredWidth;
|
||||
if (preferredWidth > 0f)
|
||||
{
|
||||
layoutElement.minWidth = preferredWidth;
|
||||
layoutElement.flexibleWidth = 0f;
|
||||
}
|
||||
|
||||
Image sectionImage = sectionObject.GetComponent<Image>();
|
||||
sectionImage.sprite = GetBuiltinSprite();
|
||||
sectionImage.type = Image.Type.Sliced;
|
||||
sectionImage.color = SectionBackgroundColor;
|
||||
|
||||
VerticalLayoutGroup layout = sectionObject.GetComponent<VerticalLayoutGroup>();
|
||||
layout.padding = new RectOffset(16, 16, 16, 16);
|
||||
layout.spacing = 12f;
|
||||
layout.childAlignment = TextAnchor.UpperLeft;
|
||||
layout.childControlWidth = true;
|
||||
layout.childControlHeight = true;
|
||||
layout.childForceExpandWidth = true;
|
||||
layout.childForceExpandHeight = false;
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
private static TextMeshProUGUI CreateSectionTitle(RectTransform parent, string text)
|
||||
{
|
||||
TextMeshProUGUI label = CreateText(parent, "Label_Title", 28f, TextAlignmentOptions.Left, FontStyles.Bold);
|
||||
label.text = text;
|
||||
return label;
|
||||
}
|
||||
|
||||
private static TextMeshProUGUI CreateInfoText(RectTransform parent, string name, float minHeight)
|
||||
{
|
||||
GameObject containerObject = CreateUiObject($"{name}_Container", parent, typeof(RectTransform), typeof(Image), typeof(LayoutElement), typeof(VerticalLayoutGroup));
|
||||
RectTransform containerRect = containerObject.GetComponent<RectTransform>();
|
||||
containerObject.GetComponent<LayoutElement>().minHeight = minHeight;
|
||||
|
||||
Image containerImage = containerObject.GetComponent<Image>();
|
||||
containerImage.sprite = GetBuiltinSprite();
|
||||
containerImage.type = Image.Type.Sliced;
|
||||
containerImage.color = SectionOverlayColor;
|
||||
|
||||
VerticalLayoutGroup layout = containerObject.GetComponent<VerticalLayoutGroup>();
|
||||
layout.padding = new RectOffset(16, 16, 12, 12);
|
||||
layout.childAlignment = TextAnchor.UpperLeft;
|
||||
layout.childControlWidth = true;
|
||||
layout.childControlHeight = true;
|
||||
layout.childForceExpandWidth = true;
|
||||
layout.childForceExpandHeight = false;
|
||||
|
||||
TextMeshProUGUI label = CreateText(containerRect, name, 18f, TextAlignmentOptions.TopLeft, FontStyles.Normal);
|
||||
label.textWrappingMode = TextWrappingModes.Normal;
|
||||
label.overflowMode = TextOverflowModes.Overflow;
|
||||
label.margin = new Vector4(14f, 6f, 14f, 10f);
|
||||
label.extraPadding = true;
|
||||
return label;
|
||||
}
|
||||
|
||||
private static void CreateDivider(Transform parent, string name)
|
||||
{
|
||||
GameObject dividerObject = CreateUiObject(name, parent, typeof(RectTransform), typeof(Image), typeof(LayoutElement));
|
||||
dividerObject.GetComponent<Image>().color = new Color(0f, 0f, 0f, 0.22f);
|
||||
LayoutElement layout = dividerObject.GetComponent<LayoutElement>();
|
||||
layout.minHeight = 2f;
|
||||
layout.preferredHeight = 2f;
|
||||
}
|
||||
|
||||
private static RectTransform CreateScrollView(Transform parent, string name, out RectTransform content)
|
||||
{
|
||||
GameObject root = CreateUiObject(name, parent, typeof(RectTransform), typeof(Image), typeof(ScrollRect));
|
||||
RectTransform rootRect = root.GetComponent<RectTransform>();
|
||||
|
||||
Image rootImage = root.GetComponent<Image>();
|
||||
rootImage.sprite = GetBuiltinSprite();
|
||||
rootImage.type = Image.Type.Sliced;
|
||||
rootImage.color = SectionOverlayColor;
|
||||
|
||||
ScrollRect scrollRect = root.GetComponent<ScrollRect>();
|
||||
scrollRect.horizontal = false;
|
||||
scrollRect.vertical = true;
|
||||
scrollRect.scrollSensitivity = 30f;
|
||||
|
||||
GameObject viewportObject = CreateUiObject("Viewport", root.transform, typeof(RectTransform), typeof(Image), typeof(Mask));
|
||||
RectTransform viewport = viewportObject.GetComponent<RectTransform>();
|
||||
StretchRect(viewport);
|
||||
viewportObject.GetComponent<Image>().color = new Color(1f, 1f, 1f, 0.01f);
|
||||
viewportObject.GetComponent<Mask>().showMaskGraphic = false;
|
||||
|
||||
GameObject contentObject = CreateUiObject("Content", viewportObject.transform, typeof(RectTransform), typeof(VerticalLayoutGroup), typeof(ContentSizeFitter));
|
||||
content = contentObject.GetComponent<RectTransform>();
|
||||
content.anchorMin = new Vector2(0f, 1f);
|
||||
content.anchorMax = new Vector2(1f, 1f);
|
||||
content.pivot = new Vector2(0f, 1f);
|
||||
|
||||
VerticalLayoutGroup contentLayout = contentObject.GetComponent<VerticalLayoutGroup>();
|
||||
contentLayout.padding = new RectOffset(16, 16, 14, 14);
|
||||
contentLayout.spacing = 8f;
|
||||
contentLayout.childAlignment = TextAnchor.UpperLeft;
|
||||
contentLayout.childControlWidth = true;
|
||||
contentLayout.childControlHeight = true;
|
||||
contentLayout.childForceExpandWidth = true;
|
||||
contentLayout.childForceExpandHeight = false;
|
||||
|
||||
ContentSizeFitter fitter = contentObject.GetComponent<ContentSizeFitter>();
|
||||
fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
fitter.horizontalFit = ContentSizeFitter.FitMode.Unconstrained;
|
||||
|
||||
scrollRect.viewport = viewport;
|
||||
scrollRect.content = content;
|
||||
return rootRect;
|
||||
}
|
||||
|
||||
private static Button CreateButton(Transform parent, string name, string labelText, Vector2 size, float fontSize, FontStyles fontStyle, Color color, TextAnchor anchor)
|
||||
{
|
||||
GameObject buttonObject = CreateUiObject(name, parent, typeof(RectTransform), typeof(CanvasRenderer), typeof(Image), typeof(Button));
|
||||
RectTransform rect = buttonObject.GetComponent<RectTransform>();
|
||||
if (size.sqrMagnitude > 0f)
|
||||
{
|
||||
rect.sizeDelta = size;
|
||||
}
|
||||
|
||||
Image image = buttonObject.GetComponent<Image>();
|
||||
image.sprite = GetBuiltinSprite();
|
||||
image.type = Image.Type.Sliced;
|
||||
image.color = color;
|
||||
|
||||
Button button = buttonObject.GetComponent<Button>();
|
||||
button.targetGraphic = image;
|
||||
|
||||
TextMeshProUGUI label = CreateText(buttonObject.transform, "Label", fontSize, ToTmpAlignment(anchor), fontStyle);
|
||||
label.text = labelText;
|
||||
StretchRect(label.rectTransform);
|
||||
label.color = Color.white;
|
||||
label.textWrappingMode = TextWrappingModes.Normal;
|
||||
|
||||
return button;
|
||||
}
|
||||
|
||||
private static TextMeshProUGUI CreateText(Transform parent, string name, float fontSize, TextAlignmentOptions alignment, FontStyles fontStyle)
|
||||
{
|
||||
GameObject textObject = CreateUiObject(name, parent, typeof(RectTransform), typeof(CanvasRenderer), typeof(TextMeshProUGUI));
|
||||
RectTransform rect = textObject.GetComponent<RectTransform>();
|
||||
rect.anchorMin = new Vector2(0f, 1f);
|
||||
rect.anchorMax = new Vector2(1f, 1f);
|
||||
rect.pivot = new Vector2(0f, 1f);
|
||||
|
||||
TextMeshProUGUI text = textObject.GetComponent<TextMeshProUGUI>();
|
||||
text.font = LoadPreferredFontAsset();
|
||||
text.fontSize = fontSize;
|
||||
text.fontStyle = fontStyle;
|
||||
text.color = TextColor;
|
||||
text.alignment = alignment;
|
||||
text.textWrappingMode = TextWrappingModes.Normal;
|
||||
text.extraPadding = true;
|
||||
text.raycastTarget = false;
|
||||
return text;
|
||||
}
|
||||
|
||||
private static TMP_FontAsset LoadPreferredFontAsset()
|
||||
{
|
||||
TMP_FontAsset preferredFont = AssetDatabase.LoadAssetAtPath<TMP_FontAsset>(PreferredFontAssetPath);
|
||||
if (preferredFont != null)
|
||||
return preferredFont;
|
||||
|
||||
return TMP_Settings.defaultFontAsset;
|
||||
}
|
||||
|
||||
private static Sprite LoadPassiveNodeSprite(bool special)
|
||||
{
|
||||
string assetPath = special ? SpecialIconSpritePath : NormalIconSpritePath;
|
||||
return AssetDatabase.LoadAssetAtPath<Sprite>(assetPath);
|
||||
}
|
||||
|
||||
private static PassivePrototypeCatalogData LoadPassivePrototypeCatalog()
|
||||
{
|
||||
return AssetDatabase.LoadAssetAtPath<PassivePrototypeCatalogData>(PassiveCatalogAssetPath);
|
||||
}
|
||||
|
||||
private static Image AddImage(GameObject gameObject, string childName, Color color)
|
||||
{
|
||||
GameObject imageObject = CreateUiObject(childName, gameObject.transform, typeof(RectTransform), typeof(CanvasRenderer), typeof(Image));
|
||||
Image image = imageObject.GetComponent<Image>();
|
||||
image.sprite = GetBuiltinSprite();
|
||||
image.type = Image.Type.Sliced;
|
||||
image.color = color;
|
||||
return image;
|
||||
}
|
||||
|
||||
private static GameObject CreateUiObject(string name, Transform parent, params System.Type[] componentTypes)
|
||||
{
|
||||
GameObject gameObject = new(name, componentTypes);
|
||||
gameObject.transform.SetParent(parent, false);
|
||||
return gameObject;
|
||||
}
|
||||
|
||||
private static void StretchRect(RectTransform rectTransform)
|
||||
{
|
||||
rectTransform.anchorMin = Vector2.zero;
|
||||
rectTransform.anchorMax = Vector2.one;
|
||||
rectTransform.offsetMin = Vector2.zero;
|
||||
rectTransform.offsetMax = Vector2.zero;
|
||||
rectTransform.anchoredPosition = Vector2.zero;
|
||||
rectTransform.sizeDelta = Vector2.zero;
|
||||
}
|
||||
|
||||
private static void SetSerializedReference(Object target, string propertyName, Object value)
|
||||
{
|
||||
SerializedObject serializedObject = new(target);
|
||||
serializedObject.FindProperty(propertyName).objectReferenceValue = value;
|
||||
serializedObject.ApplyModifiedPropertiesWithoutUndo();
|
||||
}
|
||||
|
||||
private static GameObject SavePrefab(GameObject root, string path)
|
||||
{
|
||||
PrefabUtility.SaveAsPrefabAsset(root, path);
|
||||
return AssetDatabase.LoadAssetAtPath<GameObject>(path);
|
||||
}
|
||||
|
||||
private static Sprite GetBuiltinSprite()
|
||||
{
|
||||
return AssetDatabase.GetBuiltinExtraResource<Sprite>("UI/Skin/UISprite.psd");
|
||||
}
|
||||
|
||||
private static TextAlignmentOptions ToTmpAlignment(TextAnchor anchor)
|
||||
{
|
||||
return anchor switch
|
||||
{
|
||||
TextAnchor.MiddleCenter => TextAlignmentOptions.Center,
|
||||
TextAnchor.MiddleLeft => TextAlignmentOptions.MidlineLeft,
|
||||
_ => TextAlignmentOptions.Center,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 99f2c00c8ea8e7947978c046303348b4
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 9934975f568477d4191a9bcbcfd01f0a
|
||||
@@ -6,6 +6,7 @@ using UnityEngine;
|
||||
using Unity.Netcode;
|
||||
|
||||
using Colosseum.Abnormalities;
|
||||
using Colosseum.Passives;
|
||||
using Colosseum.Stats;
|
||||
using Colosseum.Combat;
|
||||
using Colosseum.Skills;
|
||||
@@ -746,7 +747,11 @@ namespace Colosseum.Enemy
|
||||
return 1f;
|
||||
|
||||
ThreatController threatController = sourceObject.GetComponent<ThreatController>();
|
||||
return threatController != null ? Mathf.Max(0f, threatController.CurrentThreatMultiplier) : 1f;
|
||||
float runtimeThreatMultiplier = threatController != null
|
||||
? Mathf.Max(0f, threatController.CurrentThreatMultiplier)
|
||||
: 1f;
|
||||
float passiveThreatMultiplier = PassiveRuntimeModifierUtility.GetThreatGeneratedMultiplier(sourceObject);
|
||||
return runtimeThreatMultiplier * passiveThreatMultiplier;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
8
Assets/_Game/Scripts/Passives.meta
Normal file
8
Assets/_Game/Scripts/Passives.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 11df31e847a1eee40a91c23141d91bb4
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
51
Assets/_Game/Scripts/Passives/PassiveEffectEntry.cs
Normal file
51
Assets/_Game/Scripts/Passives/PassiveEffectEntry.cs
Normal file
@@ -0,0 +1,51 @@
|
||||
using System;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
using Colosseum.Skills;
|
||||
using Colosseum.Stats;
|
||||
|
||||
namespace Colosseum.Passives
|
||||
{
|
||||
/// <summary>
|
||||
/// 패시브 노드의 개별 효과 정의입니다.
|
||||
/// </summary>
|
||||
[Serializable]
|
||||
public class PassiveEffectEntry
|
||||
{
|
||||
[Tooltip("패시브 효과 종류")]
|
||||
[SerializeField] private PassiveEffectType effectType = PassiveEffectType.StatModifier;
|
||||
|
||||
[Tooltip("스탯 수정자일 때 대상 스탯")]
|
||||
[SerializeField] private StatType statType = StatType.Vitality;
|
||||
|
||||
[Tooltip("스탯 수정자일 때 적용 방식")]
|
||||
[SerializeField] private StatModType modType = StatModType.PercentAdd;
|
||||
|
||||
[Tooltip("효과 값")]
|
||||
[SerializeField] private float value = 1f;
|
||||
|
||||
[Tooltip("스킬 역할에 따른 제한이 필요한 경우 사용")]
|
||||
[SerializeField] private SkillRoleType skillRoleMask = SkillRoleType.All;
|
||||
|
||||
public PassiveEffectType EffectType => effectType;
|
||||
public StatType StatType => statType;
|
||||
public StatModType ModType => modType;
|
||||
public float Value => value;
|
||||
public SkillRoleType SkillRoleMask => skillRoleMask;
|
||||
|
||||
/// <summary>
|
||||
/// 이 효과가 현재 스킬에 적용 가능한지 확인합니다.
|
||||
/// </summary>
|
||||
public bool AppliesToSkill(SkillData skill)
|
||||
{
|
||||
if (skillRoleMask == SkillRoleType.None || skillRoleMask == SkillRoleType.All)
|
||||
return true;
|
||||
|
||||
if (skill == null)
|
||||
return false;
|
||||
|
||||
return (skillRoleMask & skill.SkillRole) != 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/_Game/Scripts/Passives/PassiveEffectEntry.cs.meta
Normal file
2
Assets/_Game/Scripts/Passives/PassiveEffectEntry.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: bcf6d3de9c557e5418acd39a75266509
|
||||
66
Assets/_Game/Scripts/Passives/PassiveEnums.cs
Normal file
66
Assets/_Game/Scripts/Passives/PassiveEnums.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
|
||||
namespace Colosseum.Passives
|
||||
{
|
||||
/// <summary>
|
||||
/// 패시브 노드가 속한 갈래입니다.
|
||||
/// </summary>
|
||||
public enum PassiveNodeBranch
|
||||
{
|
||||
Common,
|
||||
Attack,
|
||||
Defense,
|
||||
Support,
|
||||
Bridge,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 패시브 노드의 시각적/구조적 분류입니다.
|
||||
/// </summary>
|
||||
public enum PassiveNodeKind
|
||||
{
|
||||
Hub,
|
||||
Axis,
|
||||
Bridge,
|
||||
Capstone,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 패시브 노드가 기여하는 축 마스크입니다.
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum PassiveAxisMask
|
||||
{
|
||||
None = 0,
|
||||
Attack = 1 << 0,
|
||||
Defense = 1 << 1,
|
||||
Support = 1 << 2,
|
||||
All = Attack | Defense | Support,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 패시브 노드가 제공하는 효과 종류입니다.
|
||||
/// </summary>
|
||||
public enum PassiveEffectType
|
||||
{
|
||||
StatModifier,
|
||||
DamageMultiplier,
|
||||
HealMultiplier,
|
||||
ShieldDoneMultiplier,
|
||||
ShieldReceivedMultiplier,
|
||||
ThreatGeneratedMultiplier,
|
||||
IncomingDamageMultiplier,
|
||||
ManaCostMultiplier,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 프로토타입 패시브 프리셋 분류입니다.
|
||||
/// </summary>
|
||||
public enum PassivePrototypePresetKind
|
||||
{
|
||||
None,
|
||||
Defense,
|
||||
Support,
|
||||
Attack,
|
||||
}
|
||||
}
|
||||
2
Assets/_Game/Scripts/Passives/PassiveEnums.cs.meta
Normal file
2
Assets/_Game/Scripts/Passives/PassiveEnums.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: a72f5239fa8913747ae5dd7fbd7f66eb
|
||||
67
Assets/_Game/Scripts/Passives/PassiveNodeData.cs
Normal file
67
Assets/_Game/Scripts/Passives/PassiveNodeData.cs
Normal file
@@ -0,0 +1,67 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Colosseum.Passives
|
||||
{
|
||||
/// <summary>
|
||||
/// 패시브 트리의 단일 노드 데이터입니다.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "PassiveNode", menuName = "Colosseum/Passives/Passive Node")]
|
||||
public class PassiveNodeData : ScriptableObject
|
||||
{
|
||||
[Header("기본 정보")]
|
||||
[SerializeField] private string nodeId;
|
||||
[SerializeField] private string displayName;
|
||||
[TextArea(2, 4)]
|
||||
[SerializeField] private string description;
|
||||
[SerializeField] private PassiveNodeBranch branch = PassiveNodeBranch.Common;
|
||||
[SerializeField] private PassiveNodeKind nodeKind = PassiveNodeKind.Axis;
|
||||
[SerializeField] private PassiveAxisMask axisMask = PassiveAxisMask.None;
|
||||
[Min(0)] [SerializeField] private int tier = 0;
|
||||
[Min(0)] [SerializeField] private int cost = 1;
|
||||
|
||||
[Header("트리 레이아웃")]
|
||||
[Tooltip("트리 그래프 안에서 사용할 정규화 좌표 (-1 ~ 1 권장)")]
|
||||
[SerializeField] private Vector2 layoutPosition = Vector2.zero;
|
||||
|
||||
[Header("연결 정보")]
|
||||
[SerializeField] private List<PassiveNodeData> prerequisiteNodes = new();
|
||||
[SerializeField] private List<PassiveNodeData> connectedNodes = new();
|
||||
|
||||
[Header("효과")]
|
||||
[SerializeField] private List<PassiveEffectEntry> effects = new();
|
||||
|
||||
public string NodeId => nodeId;
|
||||
public string DisplayName => displayName;
|
||||
public string Description => description;
|
||||
public PassiveNodeBranch Branch => branch;
|
||||
public PassiveNodeKind NodeKind => nodeKind;
|
||||
public PassiveAxisMask AxisMask => axisMask;
|
||||
public int Tier => tier;
|
||||
public int Cost => cost;
|
||||
public Vector2 LayoutPosition => layoutPosition;
|
||||
public IReadOnlyList<PassiveNodeData> PrerequisiteNodes => prerequisiteNodes;
|
||||
public IReadOnlyList<PassiveNodeData> ConnectedNodes => connectedNodes;
|
||||
public IReadOnlyList<PassiveEffectEntry> Effects => effects;
|
||||
|
||||
private void OnValidate()
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(nodeId))
|
||||
{
|
||||
nodeId = name;
|
||||
}
|
||||
|
||||
if (axisMask == PassiveAxisMask.None)
|
||||
{
|
||||
axisMask = branch switch
|
||||
{
|
||||
PassiveNodeBranch.Attack => PassiveAxisMask.Attack,
|
||||
PassiveNodeBranch.Defense => PassiveAxisMask.Defense,
|
||||
PassiveNodeBranch.Support => PassiveAxisMask.Support,
|
||||
_ => PassiveAxisMask.None,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/_Game/Scripts/Passives/PassiveNodeData.cs.meta
Normal file
2
Assets/_Game/Scripts/Passives/PassiveNodeData.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c72b2635385ed49498483636164bac87
|
||||
225
Assets/_Game/Scripts/Passives/PassivePresentationUtility.cs
Normal file
225
Assets/_Game/Scripts/Passives/PassivePresentationUtility.cs
Normal file
@@ -0,0 +1,225 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
using Colosseum.Skills;
|
||||
using Colosseum.Stats;
|
||||
|
||||
namespace Colosseum.Passives
|
||||
{
|
||||
/// <summary>
|
||||
/// 패시브 노드와 효과를 UI 친화적인 문자열로 변환합니다.
|
||||
/// </summary>
|
||||
public static class PassivePresentationUtility
|
||||
{
|
||||
public static string GetBranchLabel(PassiveNodeBranch branch)
|
||||
{
|
||||
return branch switch
|
||||
{
|
||||
PassiveNodeBranch.Common => "공통",
|
||||
PassiveNodeBranch.Attack => "공격",
|
||||
PassiveNodeBranch.Defense => "방어",
|
||||
PassiveNodeBranch.Support => "지원",
|
||||
PassiveNodeBranch.Bridge => "연결",
|
||||
_ => "미분류",
|
||||
};
|
||||
}
|
||||
|
||||
public static string GetNodeKindLabel(PassiveNodeKind nodeKind)
|
||||
{
|
||||
return nodeKind switch
|
||||
{
|
||||
PassiveNodeKind.Hub => "허브",
|
||||
PassiveNodeKind.Axis => "축 노드",
|
||||
PassiveNodeKind.Bridge => "브릿지",
|
||||
PassiveNodeKind.Capstone => "완성 노드",
|
||||
_ => "노드",
|
||||
};
|
||||
}
|
||||
|
||||
public static string GetAxisSummary(PassiveAxisMask axisMask)
|
||||
{
|
||||
if (axisMask == PassiveAxisMask.None)
|
||||
return "중립";
|
||||
|
||||
if (axisMask == PassiveAxisMask.All)
|
||||
return "공격 / 방어 / 지원";
|
||||
|
||||
List<string> labels = new();
|
||||
if ((axisMask & PassiveAxisMask.Attack) != 0)
|
||||
labels.Add("공격");
|
||||
if ((axisMask & PassiveAxisMask.Defense) != 0)
|
||||
labels.Add("방어");
|
||||
if ((axisMask & PassiveAxisMask.Support) != 0)
|
||||
labels.Add("지원");
|
||||
|
||||
return labels.Count > 0 ? string.Join(" / ", labels) : "중립";
|
||||
}
|
||||
|
||||
public static string GetStatLabel(StatType statType)
|
||||
{
|
||||
return statType switch
|
||||
{
|
||||
StatType.Strength => "근력 (STR)",
|
||||
StatType.Dexterity => "민첩 (DEX)",
|
||||
StatType.Intelligence => "지능 (INT)",
|
||||
StatType.Vitality => "활력 (VIT)",
|
||||
StatType.Wisdom => "지혜 (WIS)",
|
||||
StatType.Spirit => "정신 (SPI)",
|
||||
_ => "알 수 없는 스탯",
|
||||
};
|
||||
}
|
||||
|
||||
public static string BuildNodeSummary(PassiveNodeData node)
|
||||
{
|
||||
if (node == null)
|
||||
return string.Empty;
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.Append(node.DisplayName);
|
||||
builder.Append('\n');
|
||||
builder.Append(GetAxisSummary(node.AxisMask));
|
||||
builder.Append(" | 비용 ");
|
||||
builder.Append(node.Cost);
|
||||
|
||||
string effectSummary = BuildEffectSummary(node);
|
||||
if (!string.IsNullOrWhiteSpace(effectSummary))
|
||||
{
|
||||
builder.Append('\n');
|
||||
builder.Append(effectSummary);
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
public static string BuildNodeDetail(PassiveNodeData node)
|
||||
{
|
||||
if (node == null)
|
||||
return "노드를 선택하면 설명을 표시합니다.";
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.AppendLine($"{node.DisplayName} [{GetBranchLabel(node.Branch)}]");
|
||||
builder.AppendLine($"{GetNodeKindLabel(node.NodeKind)} | 축: {GetAxisSummary(node.AxisMask)}");
|
||||
builder.AppendLine($"티어 {node.Tier} | 비용 {node.Cost}");
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(node.Description))
|
||||
{
|
||||
builder.AppendLine();
|
||||
builder.AppendLine(node.Description.Trim());
|
||||
}
|
||||
|
||||
if (node.Effects != null && node.Effects.Count > 0)
|
||||
{
|
||||
builder.AppendLine();
|
||||
builder.AppendLine("효과");
|
||||
for (int i = 0; i < node.Effects.Count; i++)
|
||||
{
|
||||
PassiveEffectEntry effect = node.Effects[i];
|
||||
if (effect == null)
|
||||
continue;
|
||||
|
||||
builder.Append("• ");
|
||||
builder.AppendLine(GetEffectLabel(effect));
|
||||
}
|
||||
}
|
||||
|
||||
if (node.PrerequisiteNodes != null && node.PrerequisiteNodes.Count > 0)
|
||||
{
|
||||
builder.AppendLine();
|
||||
builder.AppendLine("선행 노드");
|
||||
for (int i = 0; i < node.PrerequisiteNodes.Count; i++)
|
||||
{
|
||||
PassiveNodeData prerequisiteNode = node.PrerequisiteNodes[i];
|
||||
if (prerequisiteNode == null)
|
||||
continue;
|
||||
|
||||
builder.Append("• ");
|
||||
builder.AppendLine(prerequisiteNode.DisplayName);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.ToString().TrimEnd();
|
||||
}
|
||||
|
||||
public static string BuildEffectSummary(PassiveNodeData node)
|
||||
{
|
||||
if (node?.Effects == null || node.Effects.Count <= 0)
|
||||
return "효과 없음";
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
int writtenCount = 0;
|
||||
|
||||
for (int i = 0; i < node.Effects.Count; i++)
|
||||
{
|
||||
PassiveEffectEntry effect = node.Effects[i];
|
||||
if (effect == null)
|
||||
continue;
|
||||
|
||||
if (writtenCount > 0)
|
||||
builder.Append(" / ");
|
||||
|
||||
builder.Append(GetEffectLabel(effect));
|
||||
writtenCount++;
|
||||
}
|
||||
|
||||
return writtenCount > 0 ? builder.ToString() : "효과 없음";
|
||||
}
|
||||
|
||||
public static string GetEffectLabel(PassiveEffectEntry effect)
|
||||
{
|
||||
if (effect == null)
|
||||
return string.Empty;
|
||||
|
||||
string label = effect.EffectType switch
|
||||
{
|
||||
PassiveEffectType.StatModifier => BuildStatModifierLabel(effect),
|
||||
PassiveEffectType.DamageMultiplier => $"공격 피해 {FormatMultiplierDelta(effect.Value)}",
|
||||
PassiveEffectType.HealMultiplier => $"회복량 {FormatMultiplierDelta(effect.Value)}",
|
||||
PassiveEffectType.ShieldDoneMultiplier => $"보호막 부여량 {FormatMultiplierDelta(effect.Value)}",
|
||||
PassiveEffectType.ShieldReceivedMultiplier => $"보호막 수혜량 {FormatMultiplierDelta(effect.Value)}",
|
||||
PassiveEffectType.ThreatGeneratedMultiplier => $"위협 생성 {FormatMultiplierDelta(effect.Value)}",
|
||||
PassiveEffectType.IncomingDamageMultiplier => $"받는 피해 {FormatMultiplierDelta(effect.Value)}",
|
||||
PassiveEffectType.ManaCostMultiplier => $"마나 비용 {FormatMultiplierDelta(effect.Value)}",
|
||||
_ => $"알 수 없는 효과 ({effect.EffectType})",
|
||||
};
|
||||
|
||||
if (effect.SkillRoleMask != SkillRoleType.None && effect.SkillRoleMask != SkillRoleType.All)
|
||||
{
|
||||
label += $" ({SkillClassificationUtility.GetAllowedRoleSummary(effect.SkillRoleMask)} 스킬 한정)";
|
||||
}
|
||||
|
||||
return label;
|
||||
}
|
||||
|
||||
private static string BuildStatModifierLabel(PassiveEffectEntry effect)
|
||||
{
|
||||
string statLabel = GetStatLabel(effect.StatType);
|
||||
|
||||
return effect.ModType switch
|
||||
{
|
||||
StatModType.Flat => $"{statLabel} {FormatFlatValue(effect.Value)}",
|
||||
StatModType.PercentAdd => $"{statLabel} {FormatSignedPercent(effect.Value * 100f)}",
|
||||
StatModType.PercentMult => $"{statLabel} {FormatSignedPercent((effect.Value - 1f) * 100f)}",
|
||||
_ => $"{statLabel} {effect.Value:0.##}",
|
||||
};
|
||||
}
|
||||
|
||||
private static string FormatMultiplierDelta(float multiplier)
|
||||
{
|
||||
return FormatSignedPercent((multiplier - 1f) * 100f);
|
||||
}
|
||||
|
||||
private static string FormatSignedPercent(float percentValue)
|
||||
{
|
||||
string sign = percentValue >= 0f ? "+" : string.Empty;
|
||||
return $"{sign}{percentValue:0.##}%";
|
||||
}
|
||||
|
||||
private static string FormatFlatValue(float value)
|
||||
{
|
||||
string sign = value >= 0f ? "+" : string.Empty;
|
||||
return $"{sign}{value:0.##}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 91dfed6432de76b43b187ce333faaa54
|
||||
44
Assets/_Game/Scripts/Passives/PassivePresetData.cs
Normal file
44
Assets/_Game/Scripts/Passives/PassivePresetData.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Colosseum.Passives
|
||||
{
|
||||
/// <summary>
|
||||
/// 패시브 선택 상태를 빠르게 적용하기 위한 프리셋입니다.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "PassivePreset", menuName = "Colosseum/Passives/Passive Preset")]
|
||||
public class PassivePresetData : ScriptableObject
|
||||
{
|
||||
[Header("기본 정보")]
|
||||
[SerializeField] private string presetName;
|
||||
[TextArea(2, 4)]
|
||||
[SerializeField] private string description;
|
||||
|
||||
[Header("트리")]
|
||||
[SerializeField] private PassiveTreeData tree;
|
||||
|
||||
[Header("선택 노드")]
|
||||
[SerializeField] private List<PassiveNodeData> selectedNodes = new List<PassiveNodeData>();
|
||||
|
||||
public string PresetName => presetName;
|
||||
public string Description => description;
|
||||
public PassiveTreeData Tree => tree;
|
||||
public IReadOnlyList<PassiveNodeData> SelectedNodes => selectedNodes;
|
||||
|
||||
public List<string> BuildSelectedNodeIdList()
|
||||
{
|
||||
List<string> nodeIds = new List<string>(selectedNodes.Count);
|
||||
for (int i = 0; i < selectedNodes.Count; i++)
|
||||
{
|
||||
PassiveNodeData node = selectedNodes[i];
|
||||
if (node == null || string.IsNullOrWhiteSpace(node.NodeId))
|
||||
continue;
|
||||
|
||||
nodeIds.Add(node.NodeId);
|
||||
}
|
||||
|
||||
return nodeIds;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/_Game/Scripts/Passives/PassivePresetData.cs.meta
Normal file
2
Assets/_Game/Scripts/Passives/PassivePresetData.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2f8d8a9657f495440af71ac932114128
|
||||
28
Assets/_Game/Scripts/Passives/PassivePrototypeCatalog.cs
Normal file
28
Assets/_Game/Scripts/Passives/PassivePrototypeCatalog.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
namespace Colosseum.Passives
|
||||
{
|
||||
/// <summary>
|
||||
/// 프로토타입 패시브 카탈로그 접근을 돕는 정적 유틸리티입니다.
|
||||
/// </summary>
|
||||
public static class PassivePrototypeCatalog
|
||||
{
|
||||
public static PassiveTreeData LoadPrototypeTree(PassivePrototypeCatalogData catalog)
|
||||
{
|
||||
return catalog != null ? catalog.PrototypeTree : null;
|
||||
}
|
||||
|
||||
public static PassivePresetData LoadPreset(PassivePrototypeCatalogData catalog, PassivePrototypePresetKind kind)
|
||||
{
|
||||
return catalog != null ? catalog.GetPreset(kind) : null;
|
||||
}
|
||||
|
||||
public static PassivePrototypePresetKind ResolveOwnerPresetKind(ulong ownerClientId)
|
||||
{
|
||||
return ownerClientId switch
|
||||
{
|
||||
0 => PassivePrototypePresetKind.Defense,
|
||||
1 => PassivePrototypePresetKind.Support,
|
||||
_ => PassivePrototypePresetKind.Attack,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 44a4706642cb88547be17c970370eb5a
|
||||
37
Assets/_Game/Scripts/Passives/PassivePrototypeCatalogData.cs
Normal file
37
Assets/_Game/Scripts/Passives/PassivePrototypeCatalogData.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Colosseum.Passives
|
||||
{
|
||||
/// <summary>
|
||||
/// 플레이어 패시브 프로토타입 트리와 기본 프리셋 참조를 묶는 카탈로그입니다.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "PassivePrototypeCatalog", menuName = "Colosseum/Passives/Passive Prototype Catalog")]
|
||||
public class PassivePrototypeCatalogData : ScriptableObject
|
||||
{
|
||||
[Header("트리")]
|
||||
[SerializeField] private PassiveTreeData prototypeTree;
|
||||
|
||||
[Header("프리셋")]
|
||||
[SerializeField] private PassivePresetData nonePreset;
|
||||
[SerializeField] private PassivePresetData defensePreset;
|
||||
[SerializeField] private PassivePresetData supportPreset;
|
||||
[SerializeField] private PassivePresetData attackPreset;
|
||||
|
||||
public PassiveTreeData PrototypeTree => prototypeTree;
|
||||
public PassivePresetData NonePreset => nonePreset;
|
||||
public PassivePresetData DefensePreset => defensePreset;
|
||||
public PassivePresetData SupportPreset => supportPreset;
|
||||
public PassivePresetData AttackPreset => attackPreset;
|
||||
|
||||
public PassivePresetData GetPreset(PassivePrototypePresetKind kind)
|
||||
{
|
||||
return kind switch
|
||||
{
|
||||
PassivePrototypePresetKind.Defense => defensePreset,
|
||||
PassivePrototypePresetKind.Support => supportPreset,
|
||||
PassivePrototypePresetKind.Attack => attackPreset,
|
||||
_ => nonePreset,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: cf6077238ea8f1649a7cecd929b02c91
|
||||
328
Assets/_Game/Scripts/Passives/PassiveRuntimeController.cs
Normal file
328
Assets/_Game/Scripts/Passives/PassiveRuntimeController.cs
Normal file
@@ -0,0 +1,328 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
using Colosseum.Skills;
|
||||
using Colosseum.Stats;
|
||||
|
||||
namespace Colosseum.Passives
|
||||
{
|
||||
/// <summary>
|
||||
/// 선택된 패시브 노드를 실제 전투 수치에 적용합니다.
|
||||
/// </summary>
|
||||
[DisallowMultipleComponent]
|
||||
public class PassiveRuntimeController : MonoBehaviour
|
||||
{
|
||||
[Header("References")]
|
||||
[Tooltip("CharacterStats 컴포넌트 (없으면 자동 검색)")]
|
||||
[SerializeField] private CharacterStats characterStats;
|
||||
|
||||
[Header("Debug")]
|
||||
[Tooltip("현재 적용된 패시브 프리셋 이름")]
|
||||
[SerializeField] private string currentPresetName = string.Empty;
|
||||
[Tooltip("현재 적용된 노드 ID 목록")]
|
||||
[SerializeField] private string currentSelectionSummary = string.Empty;
|
||||
[Tooltip("현재 사용한 패시브 포인트")]
|
||||
[Min(0)] [SerializeField] private int usedPoints = 0;
|
||||
|
||||
private readonly List<PassiveEffectEntry> activeEffects = new List<PassiveEffectEntry>();
|
||||
private readonly List<string> selectedNodeIds = new List<string>();
|
||||
private PassiveTreeData currentTree;
|
||||
|
||||
public string CurrentPresetName => currentPresetName;
|
||||
public int UsedPoints => usedPoints;
|
||||
public int RemainingPoints => currentTree != null ? Mathf.Max(0, currentTree.InitialPoints - usedPoints) : 0;
|
||||
public IReadOnlyList<string> SelectedNodeIds => selectedNodeIds;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (characterStats == null)
|
||||
{
|
||||
characterStats = GetComponent<CharacterStats>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 외부에서 참조를 보정합니다.
|
||||
/// </summary>
|
||||
public void Initialize(CharacterStats stats)
|
||||
{
|
||||
if (stats != null)
|
||||
{
|
||||
characterStats = stats;
|
||||
}
|
||||
else if (characterStats == null)
|
||||
{
|
||||
characterStats = GetComponent<CharacterStats>();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 선택된 노드 구성을 적용합니다.
|
||||
/// </summary>
|
||||
public bool TryApplySelection(PassiveTreeData tree, IReadOnlyList<string> nodeIds, string presetName, out string reason)
|
||||
{
|
||||
Initialize(characterStats);
|
||||
ClearAppliedState();
|
||||
|
||||
currentTree = tree;
|
||||
currentPresetName = presetName ?? string.Empty;
|
||||
|
||||
if (currentTree == null)
|
||||
{
|
||||
reason = "패시브 트리 데이터가 없습니다.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!currentTree.TryResolveSelection(nodeIds, out List<PassiveNodeData> resolvedNodes, out reason))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
usedPoints = currentTree.CalculateUsedPoints(resolvedNodes);
|
||||
|
||||
for (int i = 0; i < resolvedNodes.Count; i++)
|
||||
{
|
||||
PassiveNodeData node = resolvedNodes[i];
|
||||
if (node == null)
|
||||
continue;
|
||||
|
||||
selectedNodeIds.Add(node.NodeId);
|
||||
|
||||
IReadOnlyList<PassiveEffectEntry> effects = node.Effects;
|
||||
if (effects == null)
|
||||
continue;
|
||||
|
||||
for (int j = 0; j < effects.Count; j++)
|
||||
{
|
||||
PassiveEffectEntry effect = effects[j];
|
||||
if (effect == null)
|
||||
continue;
|
||||
|
||||
ApplyEffect(effect);
|
||||
}
|
||||
}
|
||||
|
||||
currentSelectionSummary = BuildSelectionSummary();
|
||||
reason = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 선택 상태를 해제합니다.
|
||||
/// </summary>
|
||||
public void ClearSelection()
|
||||
{
|
||||
ClearAppliedState();
|
||||
currentTree = null;
|
||||
currentPresetName = string.Empty;
|
||||
currentSelectionSummary = string.Empty;
|
||||
}
|
||||
|
||||
public float GetDamageMultiplier(SkillData skill = null)
|
||||
{
|
||||
return GetScalarMultiplier(PassiveEffectType.DamageMultiplier, skill);
|
||||
}
|
||||
|
||||
public float GetHealMultiplier(SkillData skill = null)
|
||||
{
|
||||
return GetScalarMultiplier(PassiveEffectType.HealMultiplier, skill);
|
||||
}
|
||||
|
||||
public float GetShieldDoneMultiplier(SkillData skill = null)
|
||||
{
|
||||
return GetScalarMultiplier(PassiveEffectType.ShieldDoneMultiplier, skill);
|
||||
}
|
||||
|
||||
public float GetShieldReceivedMultiplier()
|
||||
{
|
||||
return GetScalarMultiplier(PassiveEffectType.ShieldReceivedMultiplier, null);
|
||||
}
|
||||
|
||||
public float GetThreatGeneratedMultiplier()
|
||||
{
|
||||
return GetScalarMultiplier(PassiveEffectType.ThreatGeneratedMultiplier, null);
|
||||
}
|
||||
|
||||
public float GetIncomingDamageMultiplier()
|
||||
{
|
||||
return GetScalarMultiplier(PassiveEffectType.IncomingDamageMultiplier, null);
|
||||
}
|
||||
|
||||
public float GetManaCostMultiplier(SkillData skill = null)
|
||||
{
|
||||
return GetScalarMultiplier(PassiveEffectType.ManaCostMultiplier, skill);
|
||||
}
|
||||
|
||||
public string BuildSummary()
|
||||
{
|
||||
StringBuilder builder = new StringBuilder();
|
||||
builder.Append("[Passive] ");
|
||||
builder.Append(string.IsNullOrWhiteSpace(currentPresetName) ? "미적용" : currentPresetName);
|
||||
builder.Append(" | Used=");
|
||||
builder.Append(usedPoints);
|
||||
|
||||
if (currentTree != null)
|
||||
{
|
||||
builder.Append('/');
|
||||
builder.Append(currentTree.InitialPoints);
|
||||
}
|
||||
|
||||
if (selectedNodeIds.Count > 0)
|
||||
{
|
||||
builder.Append(" | Nodes=");
|
||||
builder.Append(currentSelectionSummary);
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
private void ApplyEffect(PassiveEffectEntry effect)
|
||||
{
|
||||
if (effect.EffectType == PassiveEffectType.StatModifier)
|
||||
{
|
||||
ApplyStatModifier(effect);
|
||||
return;
|
||||
}
|
||||
|
||||
activeEffects.Add(effect);
|
||||
}
|
||||
|
||||
private void ApplyStatModifier(PassiveEffectEntry effect)
|
||||
{
|
||||
if (characterStats == null || effect == null)
|
||||
return;
|
||||
|
||||
CharacterStat stat = characterStats.GetStat(effect.StatType);
|
||||
if (stat == null)
|
||||
return;
|
||||
|
||||
stat.AddModifier(new StatModifier(effect.Value, effect.ModType, this));
|
||||
}
|
||||
|
||||
private float GetScalarMultiplier(PassiveEffectType effectType, SkillData skill)
|
||||
{
|
||||
float result = 1f;
|
||||
for (int i = 0; i < activeEffects.Count; i++)
|
||||
{
|
||||
PassiveEffectEntry effect = activeEffects[i];
|
||||
if (effect == null || effect.EffectType != effectType)
|
||||
continue;
|
||||
|
||||
if (!effect.AppliesToSkill(skill))
|
||||
continue;
|
||||
|
||||
result *= Mathf.Max(0f, effect.Value);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private void ClearAppliedState()
|
||||
{
|
||||
RemoveAllPassiveStatModifiers();
|
||||
activeEffects.Clear();
|
||||
selectedNodeIds.Clear();
|
||||
usedPoints = 0;
|
||||
}
|
||||
|
||||
private void RemoveAllPassiveStatModifiers()
|
||||
{
|
||||
if (characterStats == null)
|
||||
return;
|
||||
|
||||
foreach (StatType statType in Enum.GetValues(typeof(StatType)))
|
||||
{
|
||||
CharacterStat stat = characterStats.GetStat(statType);
|
||||
stat?.RemoveAllModifiersFromSource(this);
|
||||
}
|
||||
}
|
||||
|
||||
private string BuildSelectionSummary()
|
||||
{
|
||||
if (selectedNodeIds.Count <= 0)
|
||||
return string.Empty;
|
||||
|
||||
return string.Join(", ", selectedNodeIds);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 패시브 전투 배율을 안전하게 조회하는 유틸리티입니다.
|
||||
/// </summary>
|
||||
public static class PassiveRuntimeModifierUtility
|
||||
{
|
||||
public static float GetDamageMultiplier(GameObject actor)
|
||||
{
|
||||
PassiveRuntimeController controller = GetController(actor);
|
||||
return controller != null ? controller.GetDamageMultiplier(GetCurrentSkill(actor)) : 1f;
|
||||
}
|
||||
|
||||
public static float GetHealMultiplier(GameObject actor)
|
||||
{
|
||||
PassiveRuntimeController controller = GetController(actor);
|
||||
return controller != null ? controller.GetHealMultiplier(GetCurrentSkill(actor)) : 1f;
|
||||
}
|
||||
|
||||
public static float GetShieldDoneMultiplier(GameObject actor)
|
||||
{
|
||||
PassiveRuntimeController controller = GetController(actor);
|
||||
return controller != null ? controller.GetShieldDoneMultiplier(GetCurrentSkill(actor)) : 1f;
|
||||
}
|
||||
|
||||
public static float GetShieldReceivedMultiplier(GameObject actor)
|
||||
{
|
||||
PassiveRuntimeController controller = GetController(actor);
|
||||
return controller != null ? controller.GetShieldReceivedMultiplier() : 1f;
|
||||
}
|
||||
|
||||
public static float GetThreatGeneratedMultiplier(GameObject actor)
|
||||
{
|
||||
PassiveRuntimeController controller = GetController(actor);
|
||||
return controller != null ? controller.GetThreatGeneratedMultiplier() : 1f;
|
||||
}
|
||||
|
||||
public static float GetIncomingDamageMultiplier(GameObject actor)
|
||||
{
|
||||
PassiveRuntimeController controller = GetController(actor);
|
||||
return controller != null ? controller.GetIncomingDamageMultiplier() : 1f;
|
||||
}
|
||||
|
||||
public static float GetManaCostMultiplier(GameObject actor, SkillData skill)
|
||||
{
|
||||
PassiveRuntimeController controller = GetController(actor);
|
||||
return controller != null ? controller.GetManaCostMultiplier(skill) : 1f;
|
||||
}
|
||||
|
||||
public static string GetCurrentPresetName(GameObject actor)
|
||||
{
|
||||
PassiveRuntimeController controller = GetController(actor);
|
||||
return controller != null ? controller.CurrentPresetName : string.Empty;
|
||||
}
|
||||
|
||||
public static string BuildSummary(GameObject actor)
|
||||
{
|
||||
PassiveRuntimeController controller = GetController(actor);
|
||||
return controller != null ? controller.BuildSummary() : "[Passive] 미적용";
|
||||
}
|
||||
|
||||
private static PassiveRuntimeController GetController(GameObject actor)
|
||||
{
|
||||
if (actor == null)
|
||||
return null;
|
||||
|
||||
return actor.GetComponent<PassiveRuntimeController>() ?? actor.GetComponentInParent<PassiveRuntimeController>();
|
||||
}
|
||||
|
||||
private static SkillData GetCurrentSkill(GameObject actor)
|
||||
{
|
||||
if (actor == null)
|
||||
return null;
|
||||
|
||||
SkillController skillController = actor.GetComponent<SkillController>() ?? actor.GetComponentInParent<SkillController>();
|
||||
return skillController != null ? skillController.CurrentSkill : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 1e050120f06c9e447900d65e3629482e
|
||||
129
Assets/_Game/Scripts/Passives/PassiveTreeData.cs
Normal file
129
Assets/_Game/Scripts/Passives/PassiveTreeData.cs
Normal file
@@ -0,0 +1,129 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
namespace Colosseum.Passives
|
||||
{
|
||||
/// <summary>
|
||||
/// 패시브 트리 전체 데이터를 정의합니다.
|
||||
/// </summary>
|
||||
[CreateAssetMenu(fileName = "PassiveTree", menuName = "Colosseum/Passives/Passive Tree")]
|
||||
public class PassiveTreeData : ScriptableObject
|
||||
{
|
||||
[Header("기본 정보")]
|
||||
[SerializeField] private string treeId;
|
||||
[SerializeField] private string treeName;
|
||||
[TextArea(2, 4)]
|
||||
[SerializeField] private string description;
|
||||
[Min(0)] [SerializeField] private int initialPoints = 0;
|
||||
|
||||
[Header("노드")]
|
||||
[SerializeField] private List<PassiveNodeData> nodes = new List<PassiveNodeData>();
|
||||
|
||||
public string TreeId => treeId;
|
||||
public string TreeName => treeName;
|
||||
public string Description => description;
|
||||
public int InitialPoints => initialPoints;
|
||||
public IReadOnlyList<PassiveNodeData> Nodes => nodes;
|
||||
|
||||
public PassiveNodeData GetNodeById(string nodeId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(nodeId))
|
||||
return null;
|
||||
|
||||
for (int i = 0; i < nodes.Count; i++)
|
||||
{
|
||||
PassiveNodeData node = nodes[i];
|
||||
if (node != null && string.Equals(node.NodeId, nodeId, System.StringComparison.Ordinal))
|
||||
return node;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 선택된 노드 구성이 유효한지 검사합니다.
|
||||
/// </summary>
|
||||
public bool TryResolveSelection(IReadOnlyList<string> selectedNodeIds, out List<PassiveNodeData> resolvedNodes, out string reason)
|
||||
{
|
||||
resolvedNodes = new List<PassiveNodeData>();
|
||||
reason = string.Empty;
|
||||
|
||||
HashSet<string> uniqueIds = new HashSet<string>();
|
||||
int totalCost = 0;
|
||||
|
||||
if (selectedNodeIds == null)
|
||||
return true;
|
||||
|
||||
for (int i = 0; i < selectedNodeIds.Count; i++)
|
||||
{
|
||||
string nodeId = selectedNodeIds[i];
|
||||
if (string.IsNullOrWhiteSpace(nodeId))
|
||||
continue;
|
||||
|
||||
if (!uniqueIds.Add(nodeId))
|
||||
{
|
||||
reason = $"중복 노드가 포함되어 있습니다: {nodeId}";
|
||||
return false;
|
||||
}
|
||||
|
||||
PassiveNodeData node = GetNodeById(nodeId);
|
||||
if (node == null)
|
||||
{
|
||||
reason = $"트리에 없는 노드입니다: {nodeId}";
|
||||
return false;
|
||||
}
|
||||
|
||||
resolvedNodes.Add(node);
|
||||
totalCost += node.Cost;
|
||||
}
|
||||
|
||||
for (int i = 0; i < resolvedNodes.Count; i++)
|
||||
{
|
||||
PassiveNodeData node = resolvedNodes[i];
|
||||
IReadOnlyList<PassiveNodeData> prerequisiteNodes = node.PrerequisiteNodes;
|
||||
if (prerequisiteNodes == null)
|
||||
continue;
|
||||
|
||||
for (int j = 0; j < prerequisiteNodes.Count; j++)
|
||||
{
|
||||
PassiveNodeData prerequisiteNode = prerequisiteNodes[j];
|
||||
if (prerequisiteNode == null)
|
||||
continue;
|
||||
|
||||
if (!uniqueIds.Contains(prerequisiteNode.NodeId))
|
||||
{
|
||||
reason = $"{node.DisplayName} 선택에는 선행 노드 {prerequisiteNode.DisplayName} 이(가) 필요합니다.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (totalCost > initialPoints)
|
||||
{
|
||||
reason = $"선택한 노드 비용이 보유 포인트를 초과합니다. Used={totalCost}, Max={initialPoints}";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public int CalculateUsedPoints(IReadOnlyList<PassiveNodeData> selectedNodes)
|
||||
{
|
||||
if (selectedNodes == null)
|
||||
return 0;
|
||||
|
||||
int totalCost = 0;
|
||||
for (int i = 0; i < selectedNodes.Count; i++)
|
||||
{
|
||||
PassiveNodeData node = selectedNodes[i];
|
||||
if (node == null)
|
||||
continue;
|
||||
|
||||
totalCost += Mathf.Max(0, node.Cost);
|
||||
}
|
||||
|
||||
return totalCost;
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/_Game/Scripts/Passives/PassiveTreeData.cs.meta
Normal file
2
Assets/_Game/Scripts/Passives/PassiveTreeData.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 16b8c249387e8bf4f94e93d1776171a4
|
||||
@@ -1,16 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using UnityEngine;
|
||||
|
||||
using Unity.Collections;
|
||||
using Unity.Netcode;
|
||||
|
||||
using Colosseum.Abnormalities;
|
||||
using Colosseum.Stats;
|
||||
using Colosseum.Combat;
|
||||
using Colosseum.Passives;
|
||||
using Colosseum.Skills;
|
||||
using Colosseum.Stats;
|
||||
|
||||
namespace Colosseum.Player
|
||||
{
|
||||
/// <summary>
|
||||
/// 플레이어 네트워크 상태 관리 (HP, MP 등)
|
||||
/// 플레이어 네트워크 상태 관리 (HP, MP, 패시브 상태 등)
|
||||
/// </summary>
|
||||
public class PlayerNetworkController : NetworkBehaviour, IDamageable
|
||||
{
|
||||
@@ -25,12 +30,39 @@ namespace Colosseum.Player
|
||||
[Tooltip("보호막 타입이 지정되지 않았을 때 사용할 기본 보호막 이상상태 데이터")]
|
||||
[SerializeField] private AbnormalityData shieldStateAbnormality;
|
||||
|
||||
// 네트워크 동기화 변수
|
||||
private NetworkVariable<float> currentHealth = new NetworkVariable<float>(100f);
|
||||
private NetworkVariable<float> currentMana = new NetworkVariable<float>(50f);
|
||||
private NetworkVariable<bool> isDead = new NetworkVariable<bool>(false);
|
||||
private NetworkVariable<float> currentShield = new NetworkVariable<float>(0f);
|
||||
[Header("Passive Prototype")]
|
||||
[Tooltip("프로토타입 패시브 카탈로그")]
|
||||
[SerializeField] private PassivePrototypeCatalogData passivePrototypeCatalog;
|
||||
|
||||
[Tooltip("기본 패시브 트리 (비어 있으면 카탈로그의 트리를 사용)")]
|
||||
[SerializeField] private PassiveTreeData passiveTree;
|
||||
|
||||
[Tooltip("네트워크 스폰 시 기본 패시브 프리셋 자동 적용 여부")]
|
||||
[SerializeField] private bool applyDefaultPassivePresetOnNetworkSpawn = false;
|
||||
|
||||
[Tooltip("자동 적용할 기본 패시브 프리셋 (없으면 패시브 미적용으로 시작)")]
|
||||
[SerializeField] private PassivePresetData defaultPassivePreset;
|
||||
|
||||
[Header("Passive Debug")]
|
||||
[Tooltip("현재 적용 중인 패시브 프리셋 이름")]
|
||||
[SerializeField] private string currentPassivePresetName = string.Empty;
|
||||
|
||||
[Tooltip("현재 사용한 패시브 포인트")]
|
||||
[Min(0)] [SerializeField] private int usedPassivePoints = 0;
|
||||
|
||||
[Tooltip("현재 남은 패시브 포인트")]
|
||||
[Min(0)] [SerializeField] private int remainingPassivePoints = 0;
|
||||
|
||||
private readonly NetworkVariable<float> currentHealth = new NetworkVariable<float>(100f);
|
||||
private readonly NetworkVariable<float> currentMana = new NetworkVariable<float>(50f);
|
||||
private readonly NetworkVariable<bool> isDead = new NetworkVariable<bool>(false);
|
||||
private readonly NetworkVariable<float> currentShield = new NetworkVariable<float>(0f);
|
||||
private readonly NetworkVariable<FixedString512Bytes> selectedPassiveNodeIds = new NetworkVariable<FixedString512Bytes>();
|
||||
private readonly NetworkVariable<FixedString64Bytes> selectedPassivePresetName = new NetworkVariable<FixedString64Bytes>();
|
||||
private readonly ShieldCollection shieldCollection = new ShieldCollection();
|
||||
private readonly List<string> passiveNodeIdBuffer = new List<string>();
|
||||
|
||||
private PassiveRuntimeController passiveRuntimeController;
|
||||
|
||||
public float Health => currentHealth.Value;
|
||||
public float Mana => currentMana.Value;
|
||||
@@ -38,24 +70,26 @@ namespace Colosseum.Player
|
||||
public float MaxHealth => characterStats != null ? characterStats.MaxHealth : 100f;
|
||||
public float MaxMana => characterStats != null ? characterStats.MaxMana : 50f;
|
||||
public CharacterStats Stats => characterStats;
|
||||
public PassivePrototypeCatalogData PassivePrototypeCatalogData => passivePrototypeCatalog;
|
||||
public PassiveTreeData PassiveTree => passiveTree;
|
||||
public string CurrentPassivePresetName => currentPassivePresetName;
|
||||
public int UsedPassivePoints => usedPassivePoints;
|
||||
public int RemainingPassivePoints => remainingPassivePoints;
|
||||
public IReadOnlyList<string> SelectedPassiveNodeIds => passiveRuntimeController != null ? passiveRuntimeController.SelectedNodeIds : Array.Empty<string>();
|
||||
|
||||
// 체력/마나 변경 이벤트
|
||||
public event Action<float, float> OnHealthChanged; // (oldValue, newValue)
|
||||
public event Action<float, float> OnManaChanged; // (oldValue, newValue)
|
||||
public event Action<float, float> OnShieldChanged; // (oldValue, newValue)
|
||||
|
||||
// 사망 이벤트
|
||||
public event Action<float, float> OnHealthChanged;
|
||||
public event Action<float, float> OnManaChanged;
|
||||
public event Action<float, float> OnShieldChanged;
|
||||
public event Action<PlayerNetworkController> OnDeath;
|
||||
public event Action<bool> OnDeathStateChanged; // (isDead)
|
||||
public event Action<bool> OnDeathStateChanged;
|
||||
public event Action<PlayerNetworkController> OnRespawned;
|
||||
public event Action OnPassiveSelectionChanged;
|
||||
|
||||
// IDamageable 구현
|
||||
public float CurrentHealth => currentHealth.Value;
|
||||
public bool IsDead => isDead.Value;
|
||||
|
||||
public override void OnNetworkSpawn()
|
||||
{
|
||||
// CharacterStats 참조 확인
|
||||
if (characterStats == null)
|
||||
{
|
||||
characterStats = GetComponent<CharacterStats>();
|
||||
@@ -66,18 +100,35 @@ namespace Colosseum.Player
|
||||
abnormalityManager = GetComponent<AbnormalityManager>();
|
||||
}
|
||||
|
||||
// 네트워크 변수 변경 콜백 등록
|
||||
EnsurePassiveRuntimeReferences();
|
||||
|
||||
currentHealth.OnValueChanged += HandleHealthChanged;
|
||||
currentMana.OnValueChanged += HandleManaChanged;
|
||||
currentShield.OnValueChanged += HandleShieldChanged;
|
||||
isDead.OnValueChanged += HandleDeathStateChanged;
|
||||
selectedPassiveNodeIds.OnValueChanged += HandleSelectedPassiveNodeIdsChanged;
|
||||
selectedPassivePresetName.OnValueChanged += HandleSelectedPassivePresetNameChanged;
|
||||
|
||||
ApplyPassiveSelectionFromNetworkState(false);
|
||||
|
||||
// 초기화
|
||||
if (IsServer)
|
||||
{
|
||||
currentHealth.Value = MaxHealth;
|
||||
currentMana.Value = MaxMana;
|
||||
isDead.Value = false;
|
||||
|
||||
if (applyDefaultPassivePresetOnNetworkSpawn && IsPassiveSelectionEmpty())
|
||||
{
|
||||
PassivePresetData initialPreset = defaultPassivePreset != null
|
||||
? defaultPassivePreset
|
||||
: ResolvePrototypePreset(PassivePrototypePresetKind.None);
|
||||
DebugApplyPassivePreset(initialPreset, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
RefreshVitalsAfterPassiveChange(true);
|
||||
}
|
||||
|
||||
RefreshShieldState();
|
||||
}
|
||||
}
|
||||
@@ -95,11 +146,12 @@ namespace Colosseum.Player
|
||||
|
||||
public override void OnNetworkDespawn()
|
||||
{
|
||||
// 콜백 해제
|
||||
currentHealth.OnValueChanged -= HandleHealthChanged;
|
||||
currentMana.OnValueChanged -= HandleManaChanged;
|
||||
currentShield.OnValueChanged -= HandleShieldChanged;
|
||||
isDead.OnValueChanged -= HandleDeathStateChanged;
|
||||
selectedPassiveNodeIds.OnValueChanged -= HandleSelectedPassiveNodeIdsChanged;
|
||||
selectedPassivePresetName.OnValueChanged -= HandleSelectedPassivePresetNameChanged;
|
||||
}
|
||||
|
||||
private void HandleHealthChanged(float oldValue, float newValue)
|
||||
@@ -122,6 +174,22 @@ namespace Colosseum.Player
|
||||
OnDeathStateChanged?.Invoke(newValue);
|
||||
}
|
||||
|
||||
private void HandleSelectedPassiveNodeIdsChanged(FixedString512Bytes oldValue, FixedString512Bytes newValue)
|
||||
{
|
||||
if (oldValue.Equals(newValue))
|
||||
return;
|
||||
|
||||
ApplyPassiveSelectionFromNetworkState(false);
|
||||
}
|
||||
|
||||
private void HandleSelectedPassivePresetNameChanged(FixedString64Bytes oldValue, FixedString64Bytes newValue)
|
||||
{
|
||||
if (oldValue.Equals(newValue))
|
||||
return;
|
||||
|
||||
ApplyPassiveSelectionFromNetworkState(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 대미지 적용 (서버에서만 실행)
|
||||
/// </summary>
|
||||
@@ -137,7 +205,8 @@ namespace Colosseum.Player
|
||||
[Rpc(SendTo.Server)]
|
||||
public void UseManaRpc(float amount)
|
||||
{
|
||||
if (isDead.Value) return;
|
||||
if (isDead.Value)
|
||||
return;
|
||||
|
||||
currentMana.Value = Mathf.Max(0f, currentMana.Value - amount);
|
||||
}
|
||||
@@ -148,7 +217,8 @@ namespace Colosseum.Player
|
||||
[Rpc(SendTo.Server)]
|
||||
public void RestoreHealthRpc(float amount)
|
||||
{
|
||||
if (isDead.Value) return;
|
||||
if (isDead.Value)
|
||||
return;
|
||||
|
||||
currentHealth.Value = Mathf.Min(MaxHealth, currentHealth.Value + amount);
|
||||
}
|
||||
@@ -159,7 +229,8 @@ namespace Colosseum.Player
|
||||
[Rpc(SendTo.Server)]
|
||||
public void RestoreManaRpc(float amount)
|
||||
{
|
||||
if (isDead.Value) return;
|
||||
if (isDead.Value)
|
||||
return;
|
||||
|
||||
currentMana.Value = Mathf.Min(MaxMana, currentMana.Value + amount);
|
||||
}
|
||||
@@ -170,7 +241,7 @@ namespace Colosseum.Player
|
||||
[Rpc(SendTo.Everyone)]
|
||||
private void PlayDeathAnimationRpc()
|
||||
{
|
||||
var animator = GetComponentInChildren<Animator>();
|
||||
Animator animator = GetComponentInChildren<Animator>();
|
||||
if (animator != null)
|
||||
{
|
||||
animator.SetTrigger("Die");
|
||||
@@ -182,56 +253,50 @@ namespace Colosseum.Player
|
||||
/// </summary>
|
||||
private void HandleDeath()
|
||||
{
|
||||
if (isDead.Value) return;
|
||||
if (isDead.Value)
|
||||
return;
|
||||
|
||||
isDead.Value = true;
|
||||
shieldCollection.Clear();
|
||||
RefreshShieldState();
|
||||
|
||||
// 사망 시 활성 이상 상태를 정리해 리스폰 시 잔존하지 않게 합니다.
|
||||
if (abnormalityManager != null)
|
||||
{
|
||||
abnormalityManager.RemoveAllAbnormalities();
|
||||
}
|
||||
|
||||
// 이동 비활성화
|
||||
var movement = GetComponent<PlayerMovement>();
|
||||
PlayerMovement movement = GetComponent<PlayerMovement>();
|
||||
if (movement != null)
|
||||
{
|
||||
movement.ClearForcedMovement();
|
||||
movement.enabled = false;
|
||||
}
|
||||
|
||||
var hitReactionController = GetComponent<HitReactionController>();
|
||||
HitReactionController hitReactionController = GetComponent<HitReactionController>();
|
||||
if (hitReactionController != null)
|
||||
{
|
||||
hitReactionController.ClearHitReactionState();
|
||||
}
|
||||
|
||||
var threatController = GetComponent<ThreatController>();
|
||||
ThreatController threatController = GetComponent<ThreatController>();
|
||||
if (threatController != null)
|
||||
{
|
||||
threatController.ClearThreatModifiers();
|
||||
}
|
||||
|
||||
// 스킬 입력 비활성화
|
||||
var skillInput = GetComponent<PlayerSkillInput>();
|
||||
PlayerSkillInput skillInput = GetComponent<PlayerSkillInput>();
|
||||
if (skillInput != null)
|
||||
{
|
||||
skillInput.enabled = false;
|
||||
}
|
||||
|
||||
// 실행 중인 스킬 즉시 취소
|
||||
var skillController = GetComponent<SkillController>();
|
||||
SkillController skillController = GetComponent<SkillController>();
|
||||
if (skillController != null)
|
||||
{
|
||||
skillController.CancelSkill(SkillCancelReason.Death);
|
||||
}
|
||||
|
||||
// 모든 클라이언트에서 사망 애니메이션 재생
|
||||
PlayDeathAnimationRpc();
|
||||
|
||||
// 사망 이벤트 발생
|
||||
OnDeath?.Invoke(this);
|
||||
|
||||
Debug.Log($"[Player] Player {OwnerClientId} died!");
|
||||
@@ -242,7 +307,8 @@ namespace Colosseum.Player
|
||||
/// </summary>
|
||||
public void Respawn()
|
||||
{
|
||||
if (!IsServer) return;
|
||||
if (!IsServer)
|
||||
return;
|
||||
|
||||
if (abnormalityManager != null)
|
||||
{
|
||||
@@ -250,46 +316,42 @@ namespace Colosseum.Player
|
||||
}
|
||||
|
||||
isDead.Value = false;
|
||||
currentHealth.Value = MaxHealth;
|
||||
currentMana.Value = MaxMana;
|
||||
RefreshVitalsAfterPassiveChange(true);
|
||||
shieldCollection.Clear();
|
||||
RefreshShieldState();
|
||||
|
||||
// 이동 재활성화
|
||||
var movement = GetComponent<PlayerMovement>();
|
||||
PlayerMovement movement = GetComponent<PlayerMovement>();
|
||||
if (movement != null)
|
||||
{
|
||||
movement.ClearForcedMovement();
|
||||
movement.enabled = true;
|
||||
}
|
||||
|
||||
var hitReactionController = GetComponent<HitReactionController>();
|
||||
HitReactionController hitReactionController = GetComponent<HitReactionController>();
|
||||
if (hitReactionController != null)
|
||||
{
|
||||
hitReactionController.ClearHitReactionState();
|
||||
}
|
||||
|
||||
var threatController = GetComponent<ThreatController>();
|
||||
ThreatController threatController = GetComponent<ThreatController>();
|
||||
if (threatController != null)
|
||||
{
|
||||
threatController.ClearThreatModifiers();
|
||||
}
|
||||
|
||||
// 스킬 입력 재활성화
|
||||
var skillInput = GetComponent<PlayerSkillInput>();
|
||||
PlayerSkillInput skillInput = GetComponent<PlayerSkillInput>();
|
||||
if (skillInput != null)
|
||||
{
|
||||
skillInput.enabled = true;
|
||||
}
|
||||
|
||||
// 애니메이션 리셋
|
||||
var animator = GetComponentInChildren<Animator>();
|
||||
Animator animator = GetComponentInChildren<Animator>();
|
||||
if (animator != null)
|
||||
{
|
||||
animator.Rebind();
|
||||
}
|
||||
|
||||
var skillController = GetComponent<SkillController>();
|
||||
SkillController skillController = GetComponent<SkillController>();
|
||||
if (skillController != null)
|
||||
{
|
||||
skillController.CancelSkill(SkillCancelReason.Respawn);
|
||||
@@ -300,6 +362,271 @@ namespace Colosseum.Player
|
||||
Debug.Log($"[Player] Player {OwnerClientId} respawned!");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 서버 기준으로 패시브 프리셋을 적용합니다.
|
||||
/// </summary>
|
||||
public bool DebugApplyPassivePreset(PassivePresetData preset, bool fillResourcesToMax = true)
|
||||
{
|
||||
if (!IsServer)
|
||||
return false;
|
||||
|
||||
EnsurePassiveRuntimeReferences();
|
||||
|
||||
if (preset == null)
|
||||
{
|
||||
return DebugClearPassiveSelection(fillResourcesToMax);
|
||||
}
|
||||
|
||||
PassiveTreeData targetTree = preset.Tree != null ? preset.Tree : passiveTree;
|
||||
if (targetTree == null)
|
||||
{
|
||||
targetTree = ResolvePrototypeTree();
|
||||
}
|
||||
|
||||
if (targetTree == null)
|
||||
{
|
||||
Debug.LogWarning("[Passive] 패시브 트리를 찾지 못해 프리셋을 적용할 수 없습니다.");
|
||||
return false;
|
||||
}
|
||||
|
||||
List<string> nodeIds = preset.BuildSelectedNodeIdList();
|
||||
if (!targetTree.TryResolveSelection(nodeIds, out _, out string reason))
|
||||
{
|
||||
Debug.LogWarning($"[Passive] 프리셋 적용 실패 | Preset={preset.PresetName} | Reason={reason}");
|
||||
return false;
|
||||
}
|
||||
|
||||
passiveTree = targetTree;
|
||||
selectedPassivePresetName.Value = new FixedString64Bytes(string.IsNullOrWhiteSpace(preset.PresetName) ? "패시브 프리셋" : preset.PresetName);
|
||||
selectedPassiveNodeIds.Value = new FixedString512Bytes(BuildPassiveSelectionCsv(nodeIds));
|
||||
|
||||
ApplyPassiveSelectionFromNetworkState(fillResourcesToMax);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 서버 기준으로 패시브 선택을 모두 해제합니다.
|
||||
/// </summary>
|
||||
public bool DebugClearPassiveSelection(bool fillResourcesToMax = false)
|
||||
{
|
||||
if (!IsServer)
|
||||
return false;
|
||||
|
||||
EnsurePassiveRuntimeReferences();
|
||||
|
||||
if (passiveTree == null)
|
||||
{
|
||||
passiveTree = ResolvePrototypeTree();
|
||||
}
|
||||
|
||||
selectedPassivePresetName.Value = new FixedString64Bytes("패시브 없음");
|
||||
selectedPassiveNodeIds.Value = default;
|
||||
|
||||
ApplyPassiveSelectionFromNetworkState(fillResourcesToMax);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// MPP 역할 분배 기준 패시브 프리셋을 자동 적용합니다.
|
||||
/// </summary>
|
||||
public bool TryApplyPrototypePassivePresetForOwner()
|
||||
{
|
||||
if (!IsServer)
|
||||
return false;
|
||||
|
||||
PassivePrototypePresetKind presetKind = PassivePrototypeCatalog.ResolveOwnerPresetKind(OwnerClientId);
|
||||
PassivePresetData preset = ResolvePrototypePreset(presetKind);
|
||||
return DebugApplyPassivePreset(preset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 지정한 노드가 이미 선택되어 있는지 확인합니다.
|
||||
/// </summary>
|
||||
public bool IsPassiveNodeSelected(string nodeId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(nodeId))
|
||||
return false;
|
||||
|
||||
IReadOnlyList<string> selectedNodeIds = SelectedPassiveNodeIds;
|
||||
for (int i = 0; i < selectedNodeIds.Count; i++)
|
||||
{
|
||||
if (string.Equals(selectedNodeIds[i], nodeId, StringComparison.Ordinal))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 지정한 패시브 노드를 현재 상태에서 선택할 수 있는지 확인합니다.
|
||||
/// </summary>
|
||||
public bool CanSelectPassiveNode(PassiveNodeData node, out string reason, out int nextUsedPoints)
|
||||
{
|
||||
nextUsedPoints = usedPassivePoints;
|
||||
|
||||
if (node == null)
|
||||
{
|
||||
reason = "선택할 패시브 노드가 없습니다.";
|
||||
return false;
|
||||
}
|
||||
|
||||
EnsurePassiveRuntimeReferences();
|
||||
if (passiveTree == null)
|
||||
{
|
||||
reason = "패시브 트리를 찾지 못했습니다.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IsPassiveNodeSelected(node.NodeId))
|
||||
{
|
||||
reason = "이미 선택한 노드입니다.";
|
||||
return false;
|
||||
}
|
||||
|
||||
List<string> previewSelection = new List<string>(SelectedPassiveNodeIds.Count + 1);
|
||||
IReadOnlyList<string> selectedNodeIds = SelectedPassiveNodeIds;
|
||||
for (int i = 0; i < selectedNodeIds.Count; i++)
|
||||
{
|
||||
previewSelection.Add(selectedNodeIds[i]);
|
||||
}
|
||||
|
||||
previewSelection.Add(node.NodeId);
|
||||
|
||||
if (!passiveTree.TryResolveSelection(previewSelection, out List<PassiveNodeData> resolvedNodes, out reason))
|
||||
return false;
|
||||
|
||||
nextUsedPoints = passiveTree.CalculateUsedPoints(resolvedNodes);
|
||||
reason = string.Empty;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 오너 클라이언트 또는 서버에서 패시브 노드 선택을 요청합니다.
|
||||
/// </summary>
|
||||
public bool RequestSelectPassiveNode(string nodeId, out string reason)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(nodeId))
|
||||
{
|
||||
reason = "선택할 노드 ID가 비어 있습니다.";
|
||||
return false;
|
||||
}
|
||||
|
||||
EnsurePassiveRuntimeReferences();
|
||||
PassiveNodeData node = passiveTree != null ? passiveTree.GetNodeById(nodeId) : null;
|
||||
if (!CanSelectPassiveNode(node, out reason, out _))
|
||||
return false;
|
||||
|
||||
if (IsServer)
|
||||
{
|
||||
return TrySelectPassiveNodeServer(nodeId, false, out reason);
|
||||
}
|
||||
|
||||
if (!IsOwner)
|
||||
{
|
||||
reason = "오너 플레이어만 패시브를 변경할 수 있습니다.";
|
||||
return false;
|
||||
}
|
||||
|
||||
RequestSelectPassiveNodeRpc(new FixedString64Bytes(nodeId));
|
||||
reason = $"'{node.DisplayName}' 선택 요청을 전송했습니다.";
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 오너 클라이언트 또는 서버에서 패시브 전체 초기화를 요청합니다.
|
||||
/// </summary>
|
||||
public bool RequestClearPassiveSelection(out string reason)
|
||||
{
|
||||
if (IsServer)
|
||||
{
|
||||
bool cleared = DebugClearPassiveSelection(false);
|
||||
reason = cleared ? "패시브 선택을 초기화했습니다." : "패시브 선택 초기화에 실패했습니다.";
|
||||
return cleared;
|
||||
}
|
||||
|
||||
if (!IsOwner)
|
||||
{
|
||||
reason = "오너 플레이어만 패시브를 변경할 수 있습니다.";
|
||||
return false;
|
||||
}
|
||||
|
||||
RequestClearPassiveSelectionRpc();
|
||||
reason = "패시브 초기화 요청을 전송했습니다.";
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 오너 클라이언트 또는 서버에서 프로토타입 프리셋 적용을 요청합니다.
|
||||
/// </summary>
|
||||
public bool RequestApplyPrototypePassivePreset(PassivePrototypePresetKind presetKind, out string reason)
|
||||
{
|
||||
PassivePresetData preset = ResolvePrototypePreset(presetKind);
|
||||
if (preset == null)
|
||||
{
|
||||
reason = "패시브 프리셋을 찾지 못했습니다.";
|
||||
return false;
|
||||
}
|
||||
|
||||
PassiveTreeData targetTree = preset.Tree != null ? preset.Tree : passiveTree;
|
||||
if (targetTree == null)
|
||||
{
|
||||
targetTree = ResolvePrototypeTree();
|
||||
}
|
||||
|
||||
if (targetTree == null)
|
||||
{
|
||||
reason = "패시브 트리를 찾지 못했습니다.";
|
||||
return false;
|
||||
}
|
||||
|
||||
List<string> nodeIds = preset.BuildSelectedNodeIdList();
|
||||
if (!targetTree.TryResolveSelection(nodeIds, out _, out reason))
|
||||
return false;
|
||||
|
||||
if (IsServer)
|
||||
{
|
||||
bool applied = DebugApplyPassivePreset(preset, false);
|
||||
reason = applied ? $"'{preset.PresetName}' 프리셋을 적용했습니다." : $"'{preset.PresetName}' 프리셋 적용에 실패했습니다.";
|
||||
return applied;
|
||||
}
|
||||
|
||||
if (!IsOwner)
|
||||
{
|
||||
reason = "오너 플레이어만 패시브를 변경할 수 있습니다.";
|
||||
return false;
|
||||
}
|
||||
|
||||
RequestApplyPrototypePassivePresetRpc((int)presetKind);
|
||||
reason = $"'{preset.PresetName}' 프리셋 적용 요청을 전송했습니다.";
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 현재 패시브 적용 상태를 문자열로 반환합니다.
|
||||
/// </summary>
|
||||
public string BuildPassiveSummary()
|
||||
{
|
||||
EnsurePassiveRuntimeReferences();
|
||||
return passiveRuntimeController != null
|
||||
? passiveRuntimeController.BuildSummary()
|
||||
: "[Passive] 미적용";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 패시브 변경 이후 현재 자원 수치를 재정렬합니다.
|
||||
/// </summary>
|
||||
public void RefreshVitalsAfterPassiveChange(bool fillToMax)
|
||||
{
|
||||
if (!IsServer)
|
||||
return;
|
||||
|
||||
float nextMaxHealth = MaxHealth;
|
||||
float nextMaxMana = MaxMana;
|
||||
|
||||
currentHealth.Value = fillToMax ? nextMaxHealth : Mathf.Min(currentHealth.Value, nextMaxHealth);
|
||||
currentMana.Value = fillToMax ? nextMaxMana : Mathf.Min(currentMana.Value, nextMaxMana);
|
||||
}
|
||||
|
||||
#region IDamageable
|
||||
/// <summary>
|
||||
/// 대미지 적용 (서버에서만 호출)
|
||||
@@ -314,7 +641,8 @@ namespace Colosseum.Player
|
||||
/// </summary>
|
||||
public float Heal(float amount)
|
||||
{
|
||||
if (!IsServer || isDead.Value) return 0f;
|
||||
if (!IsServer || isDead.Value)
|
||||
return 0f;
|
||||
|
||||
float actualHeal = Mathf.Min(amount, MaxHealth - currentHealth.Value);
|
||||
currentHealth.Value = Mathf.Min(MaxHealth, currentHealth.Value + amount);
|
||||
@@ -330,8 +658,12 @@ namespace Colosseum.Player
|
||||
if (!IsServer || isDead.Value || amount <= 0f)
|
||||
return 0f;
|
||||
|
||||
float resolvedAmount = amount * PassiveRuntimeModifierUtility.GetShieldReceivedMultiplier(gameObject);
|
||||
if (resolvedAmount <= 0f)
|
||||
return 0f;
|
||||
|
||||
AbnormalityData shieldType = shieldAbnormality != null ? shieldAbnormality : shieldStateAbnormality;
|
||||
float actualAppliedShield = shieldCollection.ApplyShield(shieldType, amount, duration, source);
|
||||
float actualAppliedShield = shieldCollection.ApplyShield(shieldType, resolvedAmount, duration, source);
|
||||
RefreshShieldState();
|
||||
return actualAppliedShield;
|
||||
}
|
||||
@@ -343,10 +675,11 @@ namespace Colosseum.Player
|
||||
|
||||
private float GetIncomingDamageMultiplier()
|
||||
{
|
||||
if (abnormalityManager == null)
|
||||
return 1f;
|
||||
|
||||
return Mathf.Max(0f, abnormalityManager.IncomingDamageMultiplier);
|
||||
float abnormalityMultiplier = abnormalityManager != null
|
||||
? Mathf.Max(0f, abnormalityManager.IncomingDamageMultiplier)
|
||||
: 1f;
|
||||
float passiveMultiplier = PassiveRuntimeModifierUtility.GetIncomingDamageMultiplier(gameObject);
|
||||
return abnormalityMultiplier * passiveMultiplier;
|
||||
}
|
||||
|
||||
private float ConsumeShield(float incomingDamage)
|
||||
@@ -389,5 +722,189 @@ namespace Colosseum.Player
|
||||
gameObject);
|
||||
}
|
||||
#endregion
|
||||
|
||||
private void EnsurePassiveRuntimeReferences()
|
||||
{
|
||||
if (characterStats == null)
|
||||
{
|
||||
characterStats = GetComponent<CharacterStats>();
|
||||
}
|
||||
|
||||
if (passiveRuntimeController == null)
|
||||
{
|
||||
passiveRuntimeController = GetComponent<PassiveRuntimeController>();
|
||||
}
|
||||
|
||||
if (passiveRuntimeController == null)
|
||||
{
|
||||
passiveRuntimeController = gameObject.AddComponent<PassiveRuntimeController>();
|
||||
}
|
||||
|
||||
passiveRuntimeController.Initialize(characterStats);
|
||||
|
||||
if (passiveTree == null)
|
||||
{
|
||||
passiveTree = ResolvePrototypeTree();
|
||||
}
|
||||
}
|
||||
|
||||
private PassiveTreeData ResolvePrototypeTree()
|
||||
{
|
||||
PassiveTreeData catalogTree = PassivePrototypeCatalog.LoadPrototypeTree(passivePrototypeCatalog);
|
||||
return catalogTree != null ? catalogTree : passiveTree;
|
||||
}
|
||||
|
||||
private PassivePresetData ResolvePrototypePreset(PassivePrototypePresetKind presetKind)
|
||||
{
|
||||
PassivePresetData preset = PassivePrototypeCatalog.LoadPreset(passivePrototypeCatalog, presetKind);
|
||||
if (preset != null)
|
||||
return preset;
|
||||
|
||||
return presetKind == PassivePrototypePresetKind.None ? defaultPassivePreset : null;
|
||||
}
|
||||
|
||||
[Rpc(SendTo.Server)]
|
||||
private void RequestSelectPassiveNodeRpc(FixedString64Bytes nodeId)
|
||||
{
|
||||
TrySelectPassiveNodeServer(nodeId.ToString(), false, out _);
|
||||
}
|
||||
|
||||
[Rpc(SendTo.Server)]
|
||||
private void RequestClearPassiveSelectionRpc()
|
||||
{
|
||||
DebugClearPassiveSelection(false);
|
||||
}
|
||||
|
||||
[Rpc(SendTo.Server)]
|
||||
private void RequestApplyPrototypePassivePresetRpc(int presetKindValue)
|
||||
{
|
||||
PassivePrototypePresetKind presetKind = Enum.IsDefined(typeof(PassivePrototypePresetKind), presetKindValue)
|
||||
? (PassivePrototypePresetKind)presetKindValue
|
||||
: PassivePrototypePresetKind.None;
|
||||
|
||||
PassivePresetData preset = ResolvePrototypePreset(presetKind);
|
||||
DebugApplyPassivePreset(preset, false);
|
||||
}
|
||||
|
||||
private void ApplyPassiveSelectionFromNetworkState(bool fillResourcesToMax)
|
||||
{
|
||||
EnsurePassiveRuntimeReferences();
|
||||
|
||||
string presetName = selectedPassivePresetName.Value.ToString();
|
||||
string selectionCsv = selectedPassiveNodeIds.Value.ToString();
|
||||
ParsePassiveSelectionCsv(selectionCsv, passiveNodeIdBuffer);
|
||||
|
||||
if (passiveRuntimeController == null)
|
||||
return;
|
||||
|
||||
if (passiveTree == null)
|
||||
{
|
||||
passiveRuntimeController.ClearSelection();
|
||||
currentPassivePresetName = string.IsNullOrWhiteSpace(presetName) ? "미적용" : presetName;
|
||||
usedPassivePoints = 0;
|
||||
remainingPassivePoints = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!passiveRuntimeController.TryApplySelection(passiveTree, passiveNodeIdBuffer, presetName, out string reason))
|
||||
{
|
||||
passiveRuntimeController.ClearSelection();
|
||||
currentPassivePresetName = string.IsNullOrWhiteSpace(presetName) ? "미적용" : presetName;
|
||||
usedPassivePoints = 0;
|
||||
remainingPassivePoints = passiveTree.InitialPoints;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(reason))
|
||||
{
|
||||
Debug.LogWarning($"[Passive] 패시브 적용 실패 | Player={gameObject.name} | Reason={reason}");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
currentPassivePresetName = string.IsNullOrWhiteSpace(presetName) ? "패시브 없음" : presetName;
|
||||
usedPassivePoints = passiveRuntimeController.UsedPoints;
|
||||
remainingPassivePoints = passiveRuntimeController.RemainingPoints;
|
||||
}
|
||||
|
||||
if (IsServer)
|
||||
{
|
||||
RefreshVitalsAfterPassiveChange(fillResourcesToMax);
|
||||
}
|
||||
|
||||
OnPassiveSelectionChanged?.Invoke();
|
||||
}
|
||||
|
||||
private bool IsPassiveSelectionEmpty()
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(selectedPassiveNodeIds.Value.ToString());
|
||||
}
|
||||
|
||||
private static string BuildPassiveSelectionCsv(IReadOnlyList<string> nodeIds)
|
||||
{
|
||||
if (nodeIds == null || nodeIds.Count <= 0)
|
||||
return string.Empty;
|
||||
|
||||
return string.Join(",", nodeIds);
|
||||
}
|
||||
|
||||
private static void ParsePassiveSelectionCsv(string csv, List<string> destination)
|
||||
{
|
||||
destination.Clear();
|
||||
if (string.IsNullOrWhiteSpace(csv))
|
||||
return;
|
||||
|
||||
string[] segments = csv.Split(',');
|
||||
for (int i = 0; i < segments.Length; i++)
|
||||
{
|
||||
string nodeId = segments[i]?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(nodeId))
|
||||
continue;
|
||||
|
||||
destination.Add(nodeId);
|
||||
}
|
||||
}
|
||||
|
||||
private bool TrySelectPassiveNodeServer(string nodeId, bool fillResourcesToMax, out string reason)
|
||||
{
|
||||
reason = string.Empty;
|
||||
|
||||
if (!IsServer)
|
||||
{
|
||||
reason = "서버에서만 패시브 노드를 확정할 수 있습니다.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(nodeId))
|
||||
{
|
||||
reason = "선택할 노드 ID가 비어 있습니다.";
|
||||
return false;
|
||||
}
|
||||
|
||||
EnsurePassiveRuntimeReferences();
|
||||
if (passiveTree == null)
|
||||
{
|
||||
reason = "패시브 트리를 찾지 못했습니다.";
|
||||
return false;
|
||||
}
|
||||
|
||||
ParsePassiveSelectionCsv(selectedPassiveNodeIds.Value.ToString(), passiveNodeIdBuffer);
|
||||
for (int i = 0; i < passiveNodeIdBuffer.Count; i++)
|
||||
{
|
||||
if (string.Equals(passiveNodeIdBuffer[i], nodeId, StringComparison.Ordinal))
|
||||
{
|
||||
reason = "이미 선택한 노드입니다.";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
passiveNodeIdBuffer.Add(nodeId);
|
||||
|
||||
if (!passiveTree.TryResolveSelection(passiveNodeIdBuffer, out _, out reason))
|
||||
return false;
|
||||
|
||||
selectedPassivePresetName.Value = new FixedString64Bytes("커스텀 패시브");
|
||||
selectedPassiveNodeIds.Value = new FixedString512Bytes(BuildPassiveSelectionCsv(passiveNodeIdBuffer));
|
||||
ApplyPassiveSelectionFromNetworkState(fillResourcesToMax);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Colosseum.Skills;
|
||||
using Colosseum.Passives;
|
||||
using Colosseum.Weapons;
|
||||
|
||||
#if UNITY_EDITOR
|
||||
@@ -368,8 +369,9 @@ namespace Colosseum.Player
|
||||
|
||||
float baseCost = loadoutEntry.GetResolvedManaCost();
|
||||
float multiplier = weaponEquipment != null ? weaponEquipment.ManaCostMultiplier : 1f;
|
||||
float passiveMultiplier = PassiveRuntimeModifierUtility.GetManaCostMultiplier(gameObject, loadoutEntry.BaseSkill);
|
||||
|
||||
return baseCost * multiplier;
|
||||
return baseCost * multiplier * passiveMultiplier;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -660,6 +662,11 @@ namespace Colosseum.Player
|
||||
return;
|
||||
|
||||
SetSkills(loadout);
|
||||
EnsureRuntimeReferences();
|
||||
if (networkController != null)
|
||||
{
|
||||
networkController.TryApplyPrototypePassivePresetForOwner();
|
||||
}
|
||||
Debug.Log($"[MPP] 자동 프리셋 적용: {GetMppmLoadoutLabel()} (OwnerClientId={OwnerClientId})");
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ using UnityEngine;
|
||||
|
||||
using Colosseum.Stats;
|
||||
using Colosseum.Combat;
|
||||
using Colosseum.Passives;
|
||||
using Colosseum.Skills;
|
||||
using Colosseum.Weapons;
|
||||
|
||||
@@ -75,7 +76,8 @@ namespace Colosseum.Skills.Effects
|
||||
// 무기 데미지 배율 적용
|
||||
float damageMultiplier = GetDamageMultiplier(caster);
|
||||
float gemMultiplier = SkillRuntimeModifierUtility.GetDamageMultiplier(caster);
|
||||
return baseTotal * damageMultiplier * gemMultiplier;
|
||||
float passiveMultiplier = PassiveRuntimeModifierUtility.GetDamageMultiplier(caster);
|
||||
return baseTotal * damageMultiplier * gemMultiplier * passiveMultiplier;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -2,6 +2,7 @@ using UnityEngine;
|
||||
|
||||
using Colosseum.Stats;
|
||||
using Colosseum.Combat;
|
||||
using Colosseum.Passives;
|
||||
using Colosseum.Skills;
|
||||
|
||||
namespace Colosseum.Skills.Effects
|
||||
@@ -42,11 +43,15 @@ namespace Colosseum.Skills.Effects
|
||||
var stats = caster.GetComponent<CharacterStats>();
|
||||
if (stats == null)
|
||||
{
|
||||
return baseHeal * SkillRuntimeModifierUtility.GetHealMultiplier(caster);
|
||||
return baseHeal *
|
||||
SkillRuntimeModifierUtility.GetHealMultiplier(caster) *
|
||||
PassiveRuntimeModifierUtility.GetHealMultiplier(caster);
|
||||
}
|
||||
|
||||
float resolvedHeal = baseHeal + (stats.HealPower * healScaling);
|
||||
return resolvedHeal * SkillRuntimeModifierUtility.GetHealMultiplier(caster);
|
||||
return resolvedHeal *
|
||||
SkillRuntimeModifierUtility.GetHealMultiplier(caster) *
|
||||
PassiveRuntimeModifierUtility.GetHealMultiplier(caster);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ using Colosseum.Enemy;
|
||||
using Colosseum.Player;
|
||||
using Colosseum.Stats;
|
||||
using Colosseum.Combat;
|
||||
using Colosseum.Passives;
|
||||
using Colosseum.Skills;
|
||||
|
||||
namespace Colosseum.Skills.Effects
|
||||
@@ -56,10 +57,16 @@ namespace Colosseum.Skills.Effects
|
||||
{
|
||||
CharacterStats stats = caster != null ? caster.GetComponent<CharacterStats>() : null;
|
||||
if (stats == null)
|
||||
return baseShield * SkillRuntimeModifierUtility.GetShieldMultiplier(caster);
|
||||
{
|
||||
return baseShield *
|
||||
SkillRuntimeModifierUtility.GetShieldMultiplier(caster) *
|
||||
PassiveRuntimeModifierUtility.GetShieldDoneMultiplier(caster);
|
||||
}
|
||||
|
||||
float resolvedShield = baseShield + (stats.HealPower * shieldScaling);
|
||||
return resolvedShield * SkillRuntimeModifierUtility.GetShieldMultiplier(caster);
|
||||
return resolvedShield *
|
||||
SkillRuntimeModifierUtility.GetShieldMultiplier(caster) *
|
||||
PassiveRuntimeModifierUtility.GetShieldDoneMultiplier(caster);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
23
Assets/_Game/Scripts/UI/PassiveTreeNodeView.cs
Normal file
23
Assets/_Game/Scripts/UI/PassiveTreeNodeView.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Colosseum.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// 패시브 트리 노드 프리팹의 참조 모음입니다.
|
||||
/// </summary>
|
||||
public class PassiveTreeNodeView : MonoBehaviour
|
||||
{
|
||||
[SerializeField] private RectTransform rootRect;
|
||||
[SerializeField] private Image backgroundImage;
|
||||
[SerializeField] private Image innerImage;
|
||||
[SerializeField] private Button button;
|
||||
[SerializeField] private Outline outline;
|
||||
|
||||
public RectTransform RootRect => rootRect;
|
||||
public Image BackgroundImage => backgroundImage;
|
||||
public Image InnerImage => innerImage;
|
||||
public Button Button => button;
|
||||
public Outline Outline => outline;
|
||||
}
|
||||
}
|
||||
2
Assets/_Game/Scripts/UI/PassiveTreeNodeView.cs.meta
Normal file
2
Assets/_Game/Scripts/UI/PassiveTreeNodeView.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 68ca5a2201ac9a446ba14501b44a6222
|
||||
1416
Assets/_Game/Scripts/UI/PassiveTreeUI.cs
Normal file
1416
Assets/_Game/Scripts/UI/PassiveTreeUI.cs
Normal file
File diff suppressed because it is too large
Load Diff
2
Assets/_Game/Scripts/UI/PassiveTreeUI.cs.meta
Normal file
2
Assets/_Game/Scripts/UI/PassiveTreeUI.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 7cbd4a7a9310669419c8eb607903f8b1
|
||||
64
Assets/_Game/Scripts/UI/PassiveTreeViewReferences.cs
Normal file
64
Assets/_Game/Scripts/UI/PassiveTreeViewReferences.cs
Normal file
@@ -0,0 +1,64 @@
|
||||
using TMPro;
|
||||
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Colosseum.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// 패시브 트리 메인 뷰 프리팹의 참조 모음입니다.
|
||||
/// </summary>
|
||||
public class PassiveTreeViewReferences : MonoBehaviour
|
||||
{
|
||||
[Header("Root")]
|
||||
[SerializeField] private RectTransform rootRect;
|
||||
[SerializeField] private GameObject overlayRoot;
|
||||
[SerializeField] private RectTransform panelRect;
|
||||
|
||||
[Header("Header")]
|
||||
[SerializeField] private Button toggleButton;
|
||||
[SerializeField] private TextMeshProUGUI toggleButtonLabel;
|
||||
[SerializeField] private TextMeshProUGUI pointsSummaryText;
|
||||
[SerializeField] private Button closeButton;
|
||||
|
||||
[Header("Body")]
|
||||
[SerializeField] private TextMeshProUGUI selectionSummaryText;
|
||||
[SerializeField] private Button nonePresetButton;
|
||||
[SerializeField] private Button defensePresetButton;
|
||||
[SerializeField] private Button supportPresetButton;
|
||||
[SerializeField] private Button attackPresetButton;
|
||||
[SerializeField] private Button clearButton;
|
||||
[SerializeField] private RectTransform graphRect;
|
||||
[SerializeField] private RectTransform connectionLayer;
|
||||
[SerializeField] private RectTransform nodeLayer;
|
||||
[SerializeField] private RectTransform detailContent;
|
||||
[SerializeField] private TextMeshProUGUI detailText;
|
||||
[SerializeField] private Button selectNodeButton;
|
||||
[SerializeField] private TextMeshProUGUI selectNodeButtonLabel;
|
||||
|
||||
[Header("Footer")]
|
||||
[SerializeField] private TextMeshProUGUI statusText;
|
||||
|
||||
public RectTransform RootRect => rootRect;
|
||||
public GameObject OverlayRoot => overlayRoot;
|
||||
public RectTransform PanelRect => panelRect;
|
||||
public Button ToggleButton => toggleButton;
|
||||
public TextMeshProUGUI ToggleButtonLabel => toggleButtonLabel;
|
||||
public TextMeshProUGUI PointsSummaryText => pointsSummaryText;
|
||||
public Button CloseButton => closeButton;
|
||||
public TextMeshProUGUI SelectionSummaryText => selectionSummaryText;
|
||||
public Button NonePresetButton => nonePresetButton;
|
||||
public Button DefensePresetButton => defensePresetButton;
|
||||
public Button SupportPresetButton => supportPresetButton;
|
||||
public Button AttackPresetButton => attackPresetButton;
|
||||
public Button ClearButton => clearButton;
|
||||
public RectTransform GraphRect => graphRect;
|
||||
public RectTransform ConnectionLayer => connectionLayer;
|
||||
public RectTransform NodeLayer => nodeLayer;
|
||||
public RectTransform DetailContent => detailContent;
|
||||
public TextMeshProUGUI DetailText => detailText;
|
||||
public Button SelectNodeButton => selectNodeButton;
|
||||
public TextMeshProUGUI SelectNodeButtonLabel => selectNodeButtonLabel;
|
||||
public TextMeshProUGUI StatusText => statusText;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 110020793eb86f044b14fe49801bd83e
|
||||
@@ -26,6 +26,10 @@ namespace Colosseum.UI
|
||||
[Tooltip("이상상태 요약 텍스트를 자동 생성할지 여부")]
|
||||
[SerializeField] private bool autoCreateAbnormalitySummary = true;
|
||||
|
||||
[Header("Passive UI")]
|
||||
[Tooltip("런타임 패시브 UI 컴포넌트를 자동으로 보정할지 여부")]
|
||||
[SerializeField] private bool autoCreatePassiveTreeUi = true;
|
||||
|
||||
[Header("Target")]
|
||||
[Tooltip("자동으로 로컬 플레이어 찾기")]
|
||||
[SerializeField] private bool autoFindPlayer = true;
|
||||
@@ -41,6 +45,11 @@ namespace Colosseum.UI
|
||||
/// </summary>
|
||||
public string CurrentAbnormalitySummary => abnormalitySummaryText != null ? abnormalitySummaryText.text : string.Empty;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
EnsurePassiveTreeUi();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (autoFindPlayer)
|
||||
@@ -235,6 +244,14 @@ namespace Colosseum.UI
|
||||
abnormalitySummaryText = summaryText;
|
||||
}
|
||||
|
||||
private void EnsurePassiveTreeUi()
|
||||
{
|
||||
if (!autoCreatePassiveTreeUi || GetComponent<PassiveTreeUI>() != null)
|
||||
return;
|
||||
|
||||
gameObject.AddComponent<PassiveTreeUI>();
|
||||
}
|
||||
|
||||
private void UpdateAbnormalitySummary()
|
||||
{
|
||||
if (abnormalitySummaryText == null)
|
||||
|
||||
Reference in New Issue
Block a user