업그레이드 데이터 입력 로직 및 기능 추가

캐릭터 스탯을 PlayerStats 컴포넌트에서 모아서 관리하도록 변경
코드에서도 마찬가지
This commit is contained in:
2026-02-23 00:21:44 +09:00
parent b34254137f
commit cc475bce3e
54 changed files with 1402 additions and 98 deletions

View File

@@ -70,6 +70,7 @@
<Compile Include="Assets\Scripts\Editor\NetworkConnectionHelperEditor.cs" />
<Compile Include="Assets\Scripts\Editor\FogOfWarVisibilitySetup.cs" />
<Compile Include="Assets\FlatKit\Shaders\Editor\MaterialPropertyExtensions.cs" />
<Compile Include="Assets\Scripts\Editor\UpgradePrefabSetup.cs" />
<Compile Include="Assets\FlatKit\Shaders\Editor\TerrainEditor.cs" />
</ItemGroup>
<ItemGroup>

View File

@@ -79,9 +79,11 @@
<Compile Include="Assets\Scripts\WorkerSpawner.cs" />
<Compile Include="Assets\Scripts\Resource.cs" />
<Compile Include="Assets\Data\Scripts\DataClasses\UpgradeData.cs" />
<Compile Include="Assets\Scripts\UpgradeDatabase.cs" />
<Compile Include="Assets\Scripts\AutoTargetSystem.cs" />
<Compile Include="Assets\Scripts\CreepDataComponent.cs" />
<Compile Include="Assets\Scripts\BuildingSlotButton.cs" />
<Compile Include="Assets\Scripts\PlayerStats.cs" />
<Compile Include="Assets\FlatKit\[Render Pipeline] URP\Water\Scripts\Buoyancy.cs" />
<Compile Include="Assets\Scripts\ObstacleSpawner.cs" />
<Compile Include="Assets\Scripts\BuildingFoundation.cs" />
@@ -125,6 +127,7 @@
<Compile Include="Assets\Scripts\EnemyAIState.cs" />
<Compile Include="Assets\Scripts\ShortcutNetworkStarter.cs" />
<Compile Include="Assets\Scripts\EquipmentData.cs" />
<Compile Include="Assets\Scripts\PlayerUpgradeManager.cs" />
<Compile Include="Assets\Scripts\NetworkConnectionHelper.cs" />
<Compile Include="Assets\Scripts\NetworkConnectionHandler.cs" />
</ItemGroup>

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: dc09279707549e041ae0219468b0a4a2
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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: 4f134815569ad014e9ccc81ddc443c4c, type: 3}
m_Name: Upgrade1
m_EditorClassIdentifier: Assembly-CSharp::Northbound.Data.UpgradeData
id: 1
memo: "\uCCB4\uB825 \uC99D\uAC00 Lv.1"
upgradeCategory: combat
upgradeTarget: person
mana: 200
requireUpgradeId:
effectStatList:
- player_max_hp
effectOpList:
- add
effectValueList:
- 50
sortOrder: 1

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b9e46f00da341b24ab07b314c89b7fd9
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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: 4f134815569ad014e9ccc81ddc443c4c, type: 3}
m_Name: Upgrade10
m_EditorClassIdentifier: Assembly-CSharp::Northbound.Data.UpgradeData
id: 10
memo: "\uB178\uB3D9\uB825 \uC99D\uAC00 Lv.1"
upgradeCategory: harvest
upgradeTarget: person
mana: 200
requireUpgradeId:
effectStatList:
- player_manpower
effectOpList:
- add
effectValueList:
- 5
sortOrder: 10

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c665e2cbdd230c04fb6d3e9fac091a65
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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: 4f134815569ad014e9ccc81ddc443c4c, type: 3}
m_Name: Upgrade11
m_EditorClassIdentifier: Assembly-CSharp::Northbound.Data.UpgradeData
id: 11
memo: "\uB178\uB3D9\uB825 \uC99D\uAC00 Lv.2"
upgradeCategory: harvest
upgradeTarget: person
mana: 300
requireUpgradeId: 0a000000
effectStatList:
- player_manpower
effectOpList:
- add
effectValueList:
- 10
sortOrder: 11

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: f5ad1c8a4ecf84a43b7c609c58592295
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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: 4f134815569ad014e9ccc81ddc443c4c, type: 3}
m_Name: Upgrade12
m_EditorClassIdentifier: Assembly-CSharp::Northbound.Data.UpgradeData
id: 12
memo: "\uB178\uB3D9\uB825 \uC99D\uAC00 Lv.3"
upgradeCategory: harvest
upgradeTarget: person
mana: 400
requireUpgradeId: 070000000a000000
effectStatList:
- player_manpower
effectOpList:
- add
effectValueList:
- 20
sortOrder: 12

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 555ef1e6ff58ee442953d075e417c091
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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: 4f134815569ad014e9ccc81ddc443c4c, type: 3}
m_Name: Upgrade13
m_EditorClassIdentifier: Assembly-CSharp::Northbound.Data.UpgradeData
id: 13
memo: "\uC774\uB3D9\uC18D\uB3C4 \uC99D\uAC00 Lv.1"
upgradeCategory: scout
upgradeTarget: person
mana: 200
requireUpgradeId:
effectStatList:
- player_move_speed
effectOpList:
- mul
effectValueList:
- 1.1
sortOrder: 13

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: aa2a6eecb804c3948a4fa7e75bacfca6
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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: 4f134815569ad014e9ccc81ddc443c4c, type: 3}
m_Name: Upgrade14
m_EditorClassIdentifier: Assembly-CSharp::Northbound.Data.UpgradeData
id: 14
memo: "\uC774\uB3D9\uC18D\uB3C4 \uC99D\uAC00 Lv.2"
upgradeCategory: scout
upgradeTarget: person
mana: 300
requireUpgradeId: 0d000000
effectStatList:
- player_move_speed
effectOpList:
- mul
effectValueList:
- 1.2
sortOrder: 14

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 3216444533dfdc14891741f0b1c5a30a
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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: 4f134815569ad014e9ccc81ddc443c4c, type: 3}
m_Name: Upgrade15
m_EditorClassIdentifier: Assembly-CSharp::Northbound.Data.UpgradeData
id: 15
memo: "\uC774\uB3D9\uC18D\uB3C4 \uC99D\uAC00 Lv.3"
upgradeCategory: scout
upgradeTarget: person
mana: 400
requireUpgradeId: 0e000000
effectStatList:
- player_move_speed
effectOpList:
- mul
effectValueList:
- 1.3
sortOrder: 15

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 227e825691ca7c9498e4af4cbb592faa
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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: 4f134815569ad014e9ccc81ddc443c4c, type: 3}
m_Name: Upgrade2
m_EditorClassIdentifier: Assembly-CSharp::Northbound.Data.UpgradeData
id: 2
memo: "\uCCB4\uB825 \uC99D\uAC00 Lv.2"
upgradeCategory: combat
upgradeTarget: person
mana: 300
requireUpgradeId: 01000000
effectStatList:
- player_max_hp
effectOpList:
- add
effectValueList:
- 100
sortOrder: 2

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d989011e0aeec9a4b9ed3d9e49488c8a
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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: 4f134815569ad014e9ccc81ddc443c4c, type: 3}
m_Name: Upgrade3
m_EditorClassIdentifier: Assembly-CSharp::Northbound.Data.UpgradeData
id: 3
memo: "\uCCB4\uB825 \uC99D\uAC00 Lv.3"
upgradeCategory: combat
upgradeTarget: person
mana: 400
requireUpgradeId: 0200000005000000
effectStatList:
- player_max_hp
effectOpList:
- add
effectValueList:
- 150
sortOrder: 3

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 29b2206b90bc89c4183c6fb0a1bb699e
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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: 4f134815569ad014e9ccc81ddc443c4c, type: 3}
m_Name: Upgrade4
m_EditorClassIdentifier: Assembly-CSharp::Northbound.Data.UpgradeData
id: 4
memo: "\uACF5\uACA9\uB825 \uC99D\uAC00 Lv.1"
upgradeCategory: combat
upgradeTarget: person
mana: 200
requireUpgradeId:
effectStatList:
- player_atk_damage
effectOpList:
- add
effectValueList:
- 5
sortOrder: 4

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 4c370fa5d360b034b977fa923d14f60b
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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: 4f134815569ad014e9ccc81ddc443c4c, type: 3}
m_Name: Upgrade5
m_EditorClassIdentifier: Assembly-CSharp::Northbound.Data.UpgradeData
id: 5
memo: "\uACF5\uACA9\uB825 \uC99D\uAC00 Lv.2"
upgradeCategory: combat
upgradeTarget: person
mana: 300
requireUpgradeId: 04000000
effectStatList:
- player_atk_damage
effectOpList:
- add
effectValueList:
- 10
sortOrder: 5

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 49e9bbb379a103741bca937c322db0cb
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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: 4f134815569ad014e9ccc81ddc443c4c, type: 3}
m_Name: Upgrade6
m_EditorClassIdentifier: Assembly-CSharp::Northbound.Data.UpgradeData
id: 6
memo: "\uACF5\uACA9\uB825 \uC99D\uAC00 Lv.3"
upgradeCategory: combat
upgradeTarget: person
mana: 400
requireUpgradeId: 0200000005000000
effectStatList:
- player_atk_damage
effectOpList:
- add
effectValueList:
- 15
sortOrder: 6

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c6178f649bd6ad141888fb39c653bda9
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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: 4f134815569ad014e9ccc81ddc443c4c, type: 3}
m_Name: Upgrade7
m_EditorClassIdentifier: Assembly-CSharp::Northbound.Data.UpgradeData
id: 7
memo: "\uC790\uC6D0 \uCD5C\uB300 \uC6A9\uB7C9 \uC99D\uAC00 Lv.1"
upgradeCategory: harvest
upgradeTarget: person
mana: 200
requireUpgradeId:
effectStatList:
- player_capacity
effectOpList:
- add
effectValueList:
- 20
sortOrder: 7

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 36cdb899e627bd54092fa9e4458ff6d2
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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: 4f134815569ad014e9ccc81ddc443c4c, type: 3}
m_Name: Upgrade8
m_EditorClassIdentifier: Assembly-CSharp::Northbound.Data.UpgradeData
id: 8
memo: "\uC790\uC6D0 \uCD5C\uB300 \uC6A9\uB7C9 \uC99D\uAC00 Lv.2"
upgradeCategory: harvest
upgradeTarget: person
mana: 300
requireUpgradeId: 07000000
effectStatList:
- player_capacity
effectOpList:
- add
effectValueList:
- 50
sortOrder: 8

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 9e8418449515bb142b06d88d7a0ac4e1
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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: 4f134815569ad014e9ccc81ddc443c4c, type: 3}
m_Name: Upgrade9
m_EditorClassIdentifier: Assembly-CSharp::Northbound.Data.UpgradeData
id: 9
memo: "\uC790\uC6D0 \uCD5C\uB300 \uC6A9\uB7C9 \uC99D\uAC00 Lv.3"
upgradeCategory: harvest
upgradeTarget: person
mana: 400
requireUpgradeId: 070000000a000000
effectStatList:
- player_capacity
effectOpList:
- add
effectValueList:
- 100
sortOrder: 9

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: eca8a6e3f11a84f44aa061493293e791
NativeFormatImporter:
externalObjects: {}
mainObjectFileID: 11400000
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -23,6 +23,8 @@ GameObject:
- component: {fileID: -4348726977448206869}
- component: {fileID: 7148704114816793672}
- component: {fileID: 6581787771557727003}
- component: {fileID: 8704851935123448388}
- component: {fileID: 2146720647481692606}
m_Layer: 9
m_Name: Player
m_TagString: Untagged
@@ -85,10 +87,8 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::NetworkPlayerController
ShowTopMostFoldoutHeaderGroup: 1
moveSpeed: 5
rotationSpeed: 10
initialTeam: 1
maxHealth: 100
damageEffectPrefab: {fileID: 0}
deathEffectPrefab: {fileID: 0}
resourcePickupPrefab: {fileID: 1627676033990080135, guid: 8c45964a69bf8fa4ba461ed217bc052f, type: 3}
@@ -158,7 +158,6 @@ MonoBehaviour:
interactableLayer:
serializedVersion: 2
m_Bits: 128
workPower: 1
rayOrigin: {fileID: 0}
useForwardDirection: 1
playAnimations: 1
@@ -196,8 +195,6 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::Northbound.AttackAction
ShowTopMostFoldoutHeaderGroup: 1
attackRange: 3
attackDamage: 100
attackCooldown: 1
attackableLayer:
serializedVersion: 2
@@ -249,7 +246,6 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::Northbound.PlayerResourceInventory
ShowTopMostFoldoutHeaderGroup: 1
maxResourceCapacity: 50
--- !u!114 &2148255267416253297
MonoBehaviour:
m_ObjectHideFlags: 0
@@ -263,7 +259,6 @@ MonoBehaviour:
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::Northbound.PlayerVisionProvider
ShowTopMostFoldoutHeaderGroup: 1
visionRange: 10
--- !u!95 &1698609800605343773
Animator:
serializedVersion: 7
@@ -393,6 +388,38 @@ MonoBehaviour:
m_EditorClassIdentifier: Assembly-CSharp::Northbound.InteractableModalManager
interactableModal: {fileID: 7317980967521758771}
playerInteraction: {fileID: 8729870597719024730}
--- !u!114 &8704851935123448388
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1314983689436087486}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: c2399ad44b71131439123eaec84abb08, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::Northbound.PlayerUpgradeManager
ShowTopMostFoldoutHeaderGroup: 1
--- !u!114 &2146720647481692606
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1314983689436087486}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 6aa26fe48ebdaae438d465df7c6a3bef, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::Northbound.PlayerStats
baseMaxHp: 100
baseDamage: 10
baseCapacity: 100
baseManpower: 10
baseMoveSpeed: 5
baseSight: 10
baseAttackRange: 2
--- !u!1 &1862223349553492570
GameObject:
m_ObjectHideFlags: 0

View File

@@ -920,6 +920,7 @@ Transform:
- {fileID: 946527919}
- {fileID: 1701756768}
- {fileID: 1166878644}
- {fileID: 1247940084}
m_Father: {fileID: 0}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &672563220
@@ -2024,6 +2025,51 @@ Transform:
m_Children: []
m_Father: {fileID: 640318137}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!1 &1247940083
GameObject:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
serializedVersion: 6
m_Component:
- component: {fileID: 1247940084}
- component: {fileID: 1247940085}
m_Layer: 0
m_Name: UpgradeDatabase
m_TagString: Untagged
m_Icon: {fileID: 0}
m_NavMeshLayer: 0
m_StaticEditorFlags: 0
m_IsActive: 1
--- !u!4 &1247940084
Transform:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1247940083}
serializedVersion: 2
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: 640318137}
m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
--- !u!114 &1247940085
MonoBehaviour:
m_ObjectHideFlags: 0
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1247940083}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: 12780dc9ee390c742b6cbd405e59a916, type: 3}
m_Name:
m_EditorClassIdentifier: Assembly-CSharp::Northbound.UpgradeDatabase
_upgradeDataList: []
--- !u!224 &1282800775 stripped
RectTransform:
m_CorrespondingSourceObject: {fileID: 8873176760615364646, guid: a470599cc0481164ab487ecf39b1ebd0, type: 3}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: ba53e8dca24fd4346a54d32e8358a2bc
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -9,8 +9,6 @@ namespace Northbound
public class AttackAction : NetworkBehaviour, IAction
{
[Header("Attack Settings")]
public float attackRange = 2f;
public int attackDamage = 10;
public float attackCooldown = 0.5f;
public LayerMask attackableLayer = ~0;
@@ -33,12 +31,14 @@ namespace Northbound
private EquipmentSocket _equipmentSocket;
private bool _isAttacking = false;
private bool _isWeaponEquipped = false;
private PlayerStats _playerStats;
private void Awake()
{
_animator = GetComponent<Animator>();
_teamMember = GetComponent<ITeamMember>();
_equipmentSocket = GetComponent<EquipmentSocket>();
_playerStats = GetComponent<PlayerStats>();
}
public bool CanExecute(ulong playerId)
@@ -80,7 +80,7 @@ namespace Northbound
private void PerformAttack()
{
Vector3 attackOrigin = attackPoint != null ? attackPoint.position : transform.position;
Collider[] hits = Physics.OverlapSphere(attackOrigin, attackRange, attackableLayer);
Collider[] hits = Physics.OverlapSphere(attackOrigin, GetAttackRange(), attackableLayer);
foreach (Collider hit in hits)
{
@@ -115,7 +115,7 @@ namespace Northbound
if (NetworkManager.Singleton.SpawnManager.SpawnedObjects.TryGetValue(targetNetworkId, out NetworkObject targetObj))
{
var damageable = targetObj.GetComponent<IDamageable>();
damageable?.TakeDamage(attackDamage, attackerNetworkId);
damageable?.TakeDamage(_playerStats?.GetDamage() ?? 10, attackerNetworkId);
}
}
@@ -248,7 +248,7 @@ namespace Northbound
{
Vector3 attackOrigin = attackPoint != null ? attackPoint.position : transform.position;
Gizmos.color = Color.red;
Gizmos.DrawWireSphere(attackOrigin, attackRange);
Gizmos.DrawWireSphere(attackOrigin, GetAttackRange());
}
public override void OnDestroy()
@@ -263,5 +263,10 @@ namespace Northbound
}
public bool IsAttacking => _isAttacking;
/// <summary>
/// 공격 범위 반환 (PlayerStats 우선)
/// </summary>
public float GetAttackRange() => _playerStats?.GetAttackRange() ?? 2f;
}
}
}

View File

@@ -1,3 +1,4 @@
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
@@ -45,12 +46,12 @@ namespace Northbound.Editor
}
Debug.Log($"<color=green>[CSVToSOImporter] Import complete: {successCount} succeeded, {failCount} failed</color>");
if (towerImported)
{
AutoConfigureBuildingManager();
}
AssetDatabase.Refresh();
}
@@ -61,7 +62,8 @@ namespace Northbound.Editor
{ "Monster", new MonsterPrefabSetup() },
{ "Creep", new CreepPrefabSetup() },
{ "Tower", new TowerPrefabSetup() },
{ "Player", new PlayerPrefabSetup() }
{ "Player", new PlayerPrefabSetup() },
{ "Upgrade", new UpgradePrefabSetup() }
};
}
@@ -82,14 +84,19 @@ namespace Northbound.Editor
}
IPrefabSetup prefabSetup = prefabSetups[typeName];
string templateName = prefabSetup.GetTemplateName();
string templatePath = $"Assets/Data/Templates/{templateName}.prefab";
GameObject template = AssetDatabase.LoadAssetAtPath<GameObject>(templatePath);
if (template == null)
// Upgrade는 템플릿이 필요 없음
GameObject template = null;
if (typeName != "Upgrade")
{
Debug.LogError($"[CSVToSOImporter] Template not found: {templatePath}");
return false;
string templateName = prefabSetup.GetTemplateName();
string templatePath = $"Assets/Data/Templates/{templateName}.prefab";
template = AssetDatabase.LoadAssetAtPath<GameObject>(templatePath);
if (template == null)
{
Debug.LogError($"[CSVToSOImporter] Template not found: {templatePath}");
return false;
}
}
string[] csvLines = File.ReadAllLines(csvPath);
@@ -123,7 +130,6 @@ namespace Northbound.Editor
Debug.Log($"[CSVToSOImporter] {typeName}: {successCount} prefabs created/updated");
}
// If towers were imported, auto-configure BuildingManager
if (typeName == "Tower")
{
Debug.Log($"<color=cyan>[CSVToSOImporter] Tower import complete!</color>");
@@ -134,8 +140,19 @@ namespace Northbound.Editor
private static bool CreatePrefabFromRow(string typeName, string[] headers, string[] values, GameObject template, IPrefabSetup prefabSetup)
{
string soPath = $"Assets/Data/ScriptableObjects/{typeName}";
Directory.CreateDirectory(Path.Combine(Application.dataPath, $"ScriptableObjects/{typeName}"));
string soPath;
if (typeName == "Upgrade")
{
// Upgrade는 Resources 폴더에 저장 (런타임 자동 로드용)
soPath = "Assets/Resources/Data/ScriptableObjects/Upgrade";
Directory.CreateDirectory(Path.Combine(Application.dataPath, "Resources/Data/ScriptableObjects/Upgrade"));
}
else
{
soPath = $"Assets/Data/ScriptableObjects/{typeName}";
Directory.CreateDirectory(Path.Combine(Application.dataPath, $"ScriptableObjects/{typeName}"));
}
int id = 0;
for (int i = 0; i < headers.Length && i < values.Length; i++)
@@ -188,6 +205,11 @@ namespace Northbound.Editor
PlayerPrefabSetup.UpdatePlayerPrefab((PlayerData)data);
prefabObj = null;
}
else if (typeName == "Upgrade")
{
// Upgrade는 프리팹이 필요 없음
prefabObj = null;
}
else
{
string prefabPath = $"Assets/Prefabs/{typeName}/{typeName}{id}.prefab";
@@ -219,7 +241,6 @@ namespace Northbound.Editor
EditorUtility.SetDirty(data);
}
// Force save assets to disk
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
@@ -236,7 +257,6 @@ namespace Northbound.Editor
return;
}
// Load TowerData
string[] towerDataGuids = AssetDatabase.FindAssets("t:TowerData", new[] { "Assets/Data/ScriptableObjects" });
List<TowerData> allTowers = new List<TowerData>();
@@ -302,6 +322,26 @@ namespace Northbound.Editor
return type.IsValueType ? System.Activator.CreateInstance(type) : null;
}
// List<T> 타입 처리 (세미콜론 구분)
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>))
{
System.Type elementType = type.GetGenericArguments()[0];
var elements = value.Split(';').Select(s => s.Trim()).Where(s => !string.IsNullOrEmpty(s)).ToList();
IList list = (IList)System.Activator.CreateInstance(type);
foreach (string element in elements)
{
object elementValue = ParseValue(element, elementType);
if (elementValue != null)
{
list.Add(elementValue);
}
}
return list;
}
if (type == typeof(int))
{
int result;

View File

@@ -45,52 +45,38 @@ namespace Northbound.Editor
private static void SetupPrefabComponents(GameObject prefab, PlayerData playerData)
{
var networkController = prefab.GetComponent<NetworkPlayerController>();
if (networkController != null)
// PlayerStats에 모든 스탯 설정
var playerStats = prefab.GetComponent<PlayerStats>();
if (playerStats != null)
{
SerializedObject so = new SerializedObject(networkController);
so.FindProperty("moveSpeed").floatValue = playerData.moveSpeed;
so.FindProperty("maxHealth").intValue = playerData.maxHp;
SerializedObject so = new SerializedObject(playerStats);
so.FindProperty("baseMaxHp").intValue = playerData.maxHp;
so.FindProperty("baseDamage").intValue = playerData.atkDamage;
so.FindProperty("baseCapacity").intValue = playerData.capacity;
so.FindProperty("baseManpower").floatValue = playerData.manpower;
so.FindProperty("baseMoveSpeed").floatValue = playerData.moveSpeed;
so.FindProperty("baseSight").floatValue = playerData.sight;
so.FindProperty("baseAttackRange").floatValue = playerData.atkRange;
so.ApplyModifiedProperties();
Debug.Log($"[PlayerPrefabSetup] Updated NetworkPlayerController: moveSpeed={playerData.moveSpeed}, maxHealth={playerData.maxHp}");
Debug.Log($"[PlayerPrefabSetup] Updated PlayerStats: " +
$"maxHp={playerData.maxHp}, damage={playerData.atkDamage}, " +
$"capacity={playerData.capacity}, manpower={playerData.manpower}, " +
$"moveSpeed={playerData.moveSpeed}, sight={playerData.sight}, " +
$"attackRange={playerData.atkRange}");
}
else
{
Debug.LogWarning($"[PlayerPrefabSetup] PlayerStats component not found on prefab!");
}
// AttackAction의 attackCooldown은 별도 설정 (스탯이 아님)
var attackAction = prefab.GetComponent<AttackAction>();
if (attackAction != null)
{
SerializedObject so = new SerializedObject(attackAction);
so.FindProperty("attackRange").intValue = playerData.atkRange;
so.FindProperty("attackDamage").intValue = playerData.atkDamage;
so.FindProperty("attackCooldown").floatValue = playerData.atkIntervalSec;
so.ApplyModifiedProperties();
Debug.Log($"[PlayerPrefabSetup] Updated AttackAction: attackRange={playerData.atkRange}, attackDamage={playerData.atkDamage}, attackCooldown={playerData.atkIntervalSec}");
}
var visionProvider = prefab.GetComponent<PlayerVisionProvider>();
if (visionProvider != null)
{
SerializedObject so = new SerializedObject(visionProvider);
so.FindProperty("visionRange").floatValue = playerData.sight;
so.ApplyModifiedProperties();
Debug.Log($"[PlayerPrefabSetup] Updated PlayerVisionProvider: visionRange={playerData.sight}");
}
var resourceInventory = prefab.GetComponent<PlayerResourceInventory>();
if (resourceInventory != null)
{
SerializedObject so = new SerializedObject(resourceInventory);
so.FindProperty("maxResourceCapacity").intValue = playerData.capacity;
so.ApplyModifiedProperties();
Debug.Log($"[PlayerPrefabSetup] Updated PlayerResourceInventory: maxResourceCapacity={playerData.capacity}");
}
var playerInteraction = prefab.GetComponent<PlayerInteraction>();
if (playerInteraction != null)
{
SerializedObject so = new SerializedObject(playerInteraction);
so.FindProperty("workPower").floatValue = playerData.manpower;
so.ApplyModifiedProperties();
Debug.Log($"[PlayerPrefabSetup] Updated PlayerInteraction: workPower={playerData.manpower}");
Debug.Log($"[PlayerPrefabSetup] Updated AttackAction: attackCooldown={playerData.atkIntervalSec}");
}
EditorUtility.SetDirty(prefab);

View File

@@ -0,0 +1,23 @@
using Northbound.Data;
using UnityEditor;
using UnityEngine;
namespace Northbound.Editor
{
/// <summary>
/// Upgrade 타입은 프리팹이 필요 없으므로 빈 구현
/// </summary>
public class UpgradePrefabSetup : IPrefabSetup
{
public string GetTemplateName()
{
return ""; // 템플릿 필요 없음
}
public void SetupPrefab(GameObject prefab, ScriptableObject data)
{
// Upgrade는 프리팹이 필요 없음
Debug.LogWarning($"[UpgradePrefabSetup] Upgrade 타입은 프리팹이 필요하지 않습니다.");
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f33f085cffff92c489c74fc11a89fa49

View File

@@ -9,15 +9,11 @@ using Northbound;
public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageable
{
[Header("Movement Settings")]
public float moveSpeed = 5f;
public float rotationSpeed = 10f;
[Header("Team Settings")]
[SerializeField] private TeamType initialTeam = TeamType.Player;
[Header("Health Settings")]
[SerializeField] private int maxHealth = 100;
[Header("Visual Effects")]
[SerializeField] private GameObject damageEffectPrefab;
[SerializeField] private GameObject deathEffectPrefab;
@@ -50,8 +46,11 @@ public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageabl
private PlayerInputActions _inputActions;
private Animator _animator;
private NetworkAnimator _networkAnimator;
private PlayerStats _playerStats;
// 이 플레이어가 로컬 플레이어인지 확인
public bool IsLocalPlayer => _ownerPlayerId.Value == NetworkManager.Singleton.LocalClientId;
public ulong OwnerPlayerId => _ownerPlayerId.Value;
@@ -64,6 +63,7 @@ public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageabl
_controller = GetComponent<CharacterController>();
_animator = GetComponent<Animator>();
_networkAnimator = GetComponent<NetworkAnimator>();
_playerStats = GetComponent<PlayerStats>();
}
public override void OnNetworkSpawn()
@@ -80,7 +80,7 @@ public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageabl
if (_currentHealth.Value == 0)
{
_currentHealth.Value = maxHealth;
_currentHealth.Value = GetMaxHealth();
}
}
@@ -189,7 +189,7 @@ public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageabl
if (_controller != null)
{
_controller.Move(move * moveSpeed * Time.deltaTime);
_controller.Move(move * (_playerStats?.GetMoveSpeed() ?? 5f) * Time.deltaTime);
}
}
}
@@ -376,7 +376,7 @@ public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageabl
if (!IsServer) return;
// 체력 회복
_currentHealth.Value = maxHealth;
_currentHealth.Value = GetMaxHealth();
// 스폰 포인트로 이동
var spawnPoints = FindObjectsByType<PlayerSpawnPoint>(FindObjectsSortMode.None);
@@ -423,11 +423,12 @@ public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageabl
public int GetCurrentHealth() => _currentHealth.Value;
public int GetMaxHealth() => maxHealth;
public int GetMaxHealth() => _playerStats?.GetMaxHp() ?? 100;
public float GetHealthPercentage()
{
return maxHealth > 0 ? (float)_currentHealth.Value / maxHealth : 0f;
int max = GetMaxHealth();
return max > 0 ? (float)_currentHealth.Value / max : 0f;
}
public bool IsDead() => _currentHealth.Value <= 0;
@@ -436,7 +437,7 @@ public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageabl
{
if (!IsServer) return;
int healAmount = Mathf.Min(amount, maxHealth - _currentHealth.Value);
int healAmount = Mathf.Min(amount, GetMaxHealth() - _currentHealth.Value);
_currentHealth.Value += healAmount;
}
@@ -459,13 +460,13 @@ public class NetworkPlayerController : NetworkBehaviour, ITeamMember, IDamageabl
{
string teamName = TeamManager.GetTeamName(_team.Value);
UnityEditor.Handles.Label(transform.position + Vector3.up * 3f,
$"Player: {gameObject.name}\nTeam: {teamName}\nHP: {_currentHealth.Value}/{maxHealth}");
$"Player: {gameObject.name}\nTeam: {teamName}\nHP: {_currentHealth.Value}/{GetMaxHealth()}");
}
else
{
string teamName = TeamManager.GetTeamName(initialTeam);
UnityEditor.Handles.Label(transform.position + Vector3.up * 3f,
$"Player: {gameObject.name}\nTeam: {teamName}\nHP: {maxHealth}/{maxHealth}");
$"Player: {gameObject.name}\nTeam: {teamName}");
}
#endif
}

View File

@@ -13,7 +13,7 @@ namespace Northbound
[Header("Interaction Settings")]
public float interactionRange = 3f;
public LayerMask interactableLayer = ~0;
public float workPower = 10f;
[Header("Detection")]
public Transform rayOrigin;
@@ -47,9 +47,10 @@ namespace Northbound
private Coroutine _interactionTimeoutCoroutine;
private NetworkPlayerController _networkPlayerController;
private PlayerStats _playerStats;
public bool IsInteracting => _isInteracting;
public float WorkPower => workPower;
public float WorkPower => _playerStats?.GetManpower() ?? 10f;
public IInteractable CurrentUnavailableInteractable => _unavailableInteractable;
// 로컬 플레이어인지 확인
@@ -60,6 +61,8 @@ namespace Northbound
{
_networkPlayerController = GetComponent<NetworkPlayerController>();
_networkAnimator = GetComponent<NetworkAnimator>();
_playerStats = GetComponent<PlayerStats>();
}
public override void OnNetworkSpawn()

View File

@@ -5,13 +5,13 @@ namespace Northbound
{
public class PlayerResourceInventory : NetworkBehaviour
{
public int maxResourceCapacity = 100;
private int _displayAmount = 0;
public int CurrentResourceAmount => _displayAmount;
public int MaxResourceCapacity => maxResourceCapacity;
public int MaxResourceCapacity => _playerStats?.GetCapacity() ?? 100;
private NetworkPlayerController _networkPlayerController;
private PlayerStats _playerStats;
private bool IsLocalPlayer => _networkPlayerController != null && _networkPlayerController.IsLocalPlayer;
private ulong LocalPlayerId => _networkPlayerController != null ? _networkPlayerController.OwnerPlayerId : OwnerClientId;
@@ -19,6 +19,7 @@ namespace Northbound
private void Awake()
{
_networkPlayerController = GetComponent<NetworkPlayerController>();
_playerStats = GetComponent<PlayerStats>();
}
[Rpc(SendTo.Server)]
@@ -33,7 +34,6 @@ namespace Northbound
public override void OnNetworkSpawn()
{
// _ownerPlayerId 변경 이벤트 구독
if (_networkPlayerController != null)
{
_networkPlayerController.OnOwnerChanged += OnOwnerPlayerIdChanged;
@@ -50,8 +50,8 @@ namespace Northbound
private void TryInitialize()
{
if (!IsLocalPlayer) return;
SetMaxCapacityServerRpc(maxResourceCapacity, LocalPlayerId);
SetMaxCapacityServerRpc(MaxResourceCapacity, LocalPlayerId);
RequestResourceUpdateServerRpc(LocalPlayerId);
}

View File

@@ -0,0 +1,144 @@
using System.Collections.Generic;
using UnityEngine;
using Northbound.Data;
namespace Northbound
{
/// <summary>
/// 플레이어 스탯 계산 컴포넌트
/// 기본 스탯 + 업그레이드 보너스를 계산하여 반환
/// </summary>
public class PlayerStats : MonoBehaviour
{
[Header("Base Stats")]
[SerializeField] private int baseMaxHp = 100;
[SerializeField] private int baseDamage = 10;
[SerializeField] private int baseCapacity = 100;
[SerializeField] private float baseManpower = 10f;
[SerializeField] private float baseMoveSpeed = 5f;
[SerializeField] private float baseSight = 10f;
[SerializeField] private float baseAttackRange = 2f;
private PlayerUpgradeManager _upgradeManager;
private void Awake()
{
_upgradeManager = GetComponent<PlayerUpgradeManager>();
}
/// <summary>
/// 특정 스탯에 대한 업그레이드 보너스 계산 (ADD만 지원)
/// </summary>
private float CalculateStatBonus(string statName)
{
float bonus = 0f;
if (_upgradeManager == null)
return bonus;
var ownedUpgrades = _upgradeManager.GetOwnedUpgradeData();
foreach (var upgrade in ownedUpgrades)
{
if (upgrade == null) continue;
// effectStatList에서 해당 스탯 찾기
for (int i = 0; i < upgrade.effectStatList.Count; i++)
{
if (i >= upgrade.effectOpList.Count || i >= upgrade.effectValueList.Count)
continue;
if (upgrade.effectStatList[i] == statName)
{
string op = upgrade.effectOpList[i];
float value = upgrade.effectValueList[i];
// ADD 연산만 지원
if (op == "add")
{
bonus += value;
}
}
}
}
return bonus;
}
/// <summary>
/// 최대 체력 반환
/// </summary>
public int GetMaxHp()
{
float bonus = CalculateStatBonus("player_max_hp");
return baseMaxHp + (int)bonus;
}
/// <summary>
/// 공격력 반환
/// </summary>
public int GetDamage()
{
float bonus = CalculateStatBonus("player_atk_damage");
return baseDamage + (int)bonus;
}
/// <summary>
/// 자원 용량 반환
/// </summary>
public int GetCapacity()
{
float bonus = CalculateStatBonus("player_capacity");
return baseCapacity + (int)bonus;
}
/// <summary>
/// 노동력(건설/채굴 작업량) 반환
/// </summary>
public float GetManpower()
{
float bonus = CalculateStatBonus("player_manpower");
return baseManpower + bonus;
}
/// <summary>
/// 이동 속도 반환
/// </summary>
public float GetMoveSpeed()
{
float bonus = CalculateStatBonus("player_move_speed");
// move_speed는 mul 연산이지만, 우선 ADD만 지원하므로 add로 처리
return baseMoveSpeed + bonus;
}
/// <summary>
/// 시야 범위 반환
/// </summary>
public float GetSight()
{
float bonus = CalculateStatBonus("player_sight");
return baseSight + bonus;
}
/// <summary>
/// 공격 범위 반환
/// </summary>
public float GetAttackRange()
{
float bonus = CalculateStatBonus("player_atk_range");
return baseAttackRange + bonus;
}
#region Base Stat Setters (/)
public void SetBaseMaxHp(int value) => baseMaxHp = value;
public void SetBaseDamage(int value) => baseDamage = value;
public void SetBaseCapacity(int value) => baseCapacity = value;
public void SetBaseManpower(float value) => baseManpower = value;
public void SetBaseMoveSpeed(float value) => baseMoveSpeed = value;
public void SetBaseSight(float value) => baseSight = value;
public void SetBaseAttackRange(float value) => baseAttackRange = value;
#endregion
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 6aa26fe48ebdaae438d465df7c6a3bef

View File

@@ -0,0 +1,269 @@
using System;
using System.Collections.Generic;
using Unity.Netcode;
using UnityEngine;
using Northbound.Data;
namespace Northbound
{
/// <summary>
/// 플레이어별 업그레이드 보유 상태를 관리하는 컴포넌트
/// </summary>
public class PlayerUpgradeManager : NetworkBehaviour
{
// 보유한 업그레이드 ID 목록 (네트워크 동기화)
private NetworkList<int> _ownedUpgradeIds;
// 이벤트
public event Action<int> OnUpgradePurchased; // upgradeId
private NetworkPlayerController _networkPlayerController;
public new bool IsLocalPlayer => _networkPlayerController != null && _networkPlayerController.IsLocalPlayer;
public ulong OwnerPlayerId => _networkPlayerController != null ? _networkPlayerController.OwnerPlayerId : OwnerClientId;
private void Awake()
{
_ownedUpgradeIds = new NetworkList<int>();
_networkPlayerController = GetComponent<NetworkPlayerController>();
}
public override void OnNetworkSpawn()
{
base.OnNetworkSpawn();
if (_networkPlayerController != null)
{
_networkPlayerController.OnOwnerChanged += OnOwnerChanged;
}
}
public override void OnNetworkDespawn()
{
base.OnNetworkDespawn();
if (_networkPlayerController != null)
{
_networkPlayerController.OnOwnerChanged -= OnOwnerChanged;
}
_ownedUpgradeIds.Dispose();
}
private void OnOwnerChanged(ulong newOwnerId)
{
// 소유자 변경 시 필요한 처리
}
/// <summary>
/// 보유한 업그레이드 ID 목록 (복사본 반환)
/// </summary>
public List<int> GetOwnedUpgradeIdList()
{
var list = new List<int>();
foreach (int id in _ownedUpgradeIds)
{
list.Add(id);
}
return list;
}
/// <summary>
/// 특정 업그레이드를 보유하고 있는지 확인
/// </summary>
public bool HasUpgrade(int upgradeId)
{
foreach (int id in _ownedUpgradeIds)
{
if (id == upgradeId)
return true;
}
return false;
}
/// <summary>
/// 보유한 업그레이드 ID를 HashSet으로 반환
/// </summary>
public HashSet<int> GetOwnedUpgradeIdSet()
{
var set = new HashSet<int>();
foreach (int id in _ownedUpgradeIds)
{
set.Add(id);
}
return set;
}
/// <summary>
/// 업그레이드 구매 요청 (클라이언트에서 호출)
/// </summary>
public void RequestPurchaseUpgrade(int upgradeId)
{
PurchaseUpgradeServerRpc(upgradeId);
}
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
private void PurchaseUpgradeServerRpc(int upgradeId)
{
if (!CanPurchaseUpgrade(upgradeId, out string failReason))
{
Debug.LogWarning($"[PlayerUpgradeManager] 업그레이드 구매 실패: {failReason}");
return;
}
// 업그레이드 데이터 가져오기
var upgrade = UpgradeDatabase.Instance?.GetUpgradeById(upgradeId);
if (upgrade == null)
{
Debug.LogError($"[PlayerUpgradeManager] 업그레이드 ID {upgradeId}를 찾을 수 없습니다.");
return;
}
// 비용 차감
var core = CoreResourceManager.Instance?.mainCore;
if (core == null)
{
Debug.LogError("[PlayerUpgradeManager] 코어를 찾을 수 없습니다.");
return;
}
if (!core.CanConsumeResource(upgrade.mana))
{
Debug.LogWarning($"[PlayerUpgradeManager] 자원이 부족합니다. 필요: {upgrade.mana}");
return;
}
core.ConsumeResourceServerRpc(upgrade.mana);
// 업그레이드 추가
_ownedUpgradeIds.Add(upgradeId);
Debug.Log($"<color=green>[PlayerUpgradeManager] 업그레이드 '{upgrade.memo}' 구매 완료! (ID: {upgradeId})</color>");
// 이벤트 발생
OnUpgradePurchased?.Invoke(upgradeId);
}
/// <summary>
/// 업그레이드 구매 가능 여부 확인
/// </summary>
private bool CanPurchaseUpgrade(int upgradeId, out string failReason)
{
failReason = string.Empty;
var upgradeDatabase = UpgradeDatabase.Instance;
if (upgradeDatabase == null)
{
failReason = "UpgradeDatabase가 초기화되지 않았습니다.";
return false;
}
var upgrade = upgradeDatabase.GetUpgradeById(upgradeId);
if (upgrade == null)
{
failReason = $"업그레이드 ID {upgradeId}를 찾을 수 없습니다.";
return false;
}
// 이미 보유 중인지 확인
if (HasUpgrade(upgradeId))
{
failReason = "이미 보유한 업그레이드입니다.";
return false;
}
// 선행 조건 확인
if (!upgradeDatabase.ArePrerequisitesMet(upgradeId, GetOwnedUpgradeIdSet()))
{
failReason = "선행 업그레이드 조건을 충족하지 않았습니다.";
return false;
}
// 비용 확인
var core = CoreResourceManager.Instance?.mainCore;
if (core == null)
{
failReason = "코어를 찾을 수 없습니다.";
return false;
}
if (!core.CanConsumeResource(upgrade.mana))
{
failReason = $"자원이 부족합니다. 필요: {upgrade.mana}, 보유: {core.TotalResources}";
return false;
}
// 개인 업그레이드인지 확인
if (upgrade.upgradeTarget != "person")
{
failReason = "팀 업그레이드는 개인이 구매할 수 없습니다.";
return false;
}
return true;
}
/// <summary>
/// 업그레이드 구매 가능 여부 (외부용)
/// </summary>
public bool CanPurchase(int upgradeId)
{
return CanPurchaseUpgrade(upgradeId, out _);
}
/// <summary>
/// 구매 가능한 업그레이드 목록 반환
/// </summary>
public List<UpgradeData> GetPurchasableUpgrades()
{
var result = new List<UpgradeData>();
var upgradeDatabase = UpgradeDatabase.Instance;
if (upgradeDatabase == null) return result;
var personalUpgrades = upgradeDatabase.GetPersonalUpgrades();
var ownedIds = GetOwnedUpgradeIdSet();
foreach (var upgrade in personalUpgrades)
{
if (upgrade == null) continue;
// 이미 보유하면 제외
if (ownedIds.Contains(upgrade.id)) continue;
// 선행 조건 충족 시 구매 가능
if (upgradeDatabase.ArePrerequisitesMet(upgrade.id, ownedIds))
{
result.Add(upgrade);
}
}
return result;
}
/// <summary>
/// 보유한 업그레이드 데이터 목록 반환
/// </summary>
public List<UpgradeData> GetOwnedUpgradeData()
{
var result = new List<UpgradeData>();
var upgradeDatabase = UpgradeDatabase.Instance;
if (upgradeDatabase == null) return result;
foreach (int id in _ownedUpgradeIds)
{
var upgrade = upgradeDatabase.GetUpgradeById(id);
if (upgrade != null)
{
result.Add(upgrade);
}
}
return result;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c2399ad44b71131439123eaec84abb08

View File

@@ -8,11 +8,11 @@ namespace Northbound
/// </summary>
public class PlayerVisionProvider : NetworkBehaviour, IVisionProvider
{
[Header("Vision Settings")]
public float visionRange = 10f;
private PlayerStats _playerStats;
public override void OnNetworkSpawn()
{
_playerStats = GetComponent<PlayerStats>();
if (IsServer)
{
FogOfWarSystem.Instance?.RegisterVisionProvider(this);
@@ -28,14 +28,14 @@ namespace Northbound
}
public ulong GetOwnerId() => OwnerClientId;
public float GetVisionRange() => visionRange;
public float GetVisionRange() => _playerStats?.GetSight() ?? 10f;
public Transform GetTransform() => transform;
public bool IsActive() => IsSpawned;
private void OnDrawGizmosSelected()
{
Gizmos.color = Color.yellow;
Gizmos.DrawWireSphere(transform.position, visionRange);
Gizmos.DrawWireSphere(transform.position, GetVisionRange());
}
}
}
}

View File

@@ -334,13 +334,21 @@ namespace Northbound
return;
int playerAvailableSpace = resourceManager.GetAvailableSpace(playerId);
// 플레이어의 작업량 가져오기
float playerWorkPower = GetPlayerWorkPower(playerId);
int gatheredAmount = Mathf.Min(
resourcesPerGathering,
(int)playerWorkPower,
_currentResources.Value,
playerAvailableSpace
);
if (gatheredAmount <= 0)
{
return;
@@ -411,8 +419,30 @@ namespace Northbound
{
return transform;
}
/// <summary>
/// 플레이어의 작업량 가져오기 (PlayerInteraction.WorkPower)
/// </summary>
private float GetPlayerWorkPower(ulong playerId)
{
// PlayerInteraction 컴포넌트에서 workPower 가져오기
if (NetworkManager.Singleton != null && NetworkManager.Singleton.ConnectedClients.TryGetValue(playerId, out var client))
{
if (client.PlayerObject != null)
{
var playerInteraction = client.PlayerObject.GetComponent<PlayerInteraction>();
if (playerInteraction != null)
{
return playerInteraction.WorkPower;
}
}
}
// 기본값: 10
return 10f;
}
private Worker FindWorkerForPlayer(ulong playerId)
{
if (NetworkManager.Singleton == null || NetworkManager.Singleton.SpawnManager == null)
{

View File

@@ -0,0 +1,177 @@
using System.Collections.Generic;
using UnityEngine;
using Northbound.Data;
namespace Northbound
{
/// <summary>
/// 업그레이드 데이터를 자동으로 로드하고 관리하는 싱글톤
/// Resources 폴더에서 모든 UpgradeData를 자동으로 로드
/// </summary>
public class UpgradeDatabase : MonoBehaviour
{
private static UpgradeDatabase _instance;
public static UpgradeDatabase Instance => _instance;
// Resources에서 자동 로드된 데이터
private List<UpgradeData> _upgradeDataList = new List<UpgradeData>();
// 조회용 딕셔너리
private Dictionary<int, UpgradeData> _upgradeById = new Dictionary<int, UpgradeData>();
private Dictionary<string, List<UpgradeData>> _upgradesByCategory = new Dictionary<string, List<UpgradeData>>();
// Resources 경로
private const string UPGRADE_RESOURCES_PATH = "Data/ScriptableObjects/Upgrade";
private void Awake()
{
if (_instance != null && _instance != this)
{
Destroy(gameObject);
return;
}
_instance = this;
LoadUpgradeData();
BuildLookupTables();
}
/// <summary>
/// Resources에서 모든 UpgradeData 자동 로드
/// </summary>
private void LoadUpgradeData()
{
_upgradeDataList.Clear();
var upgrades = Resources.LoadAll<UpgradeData>(UPGRADE_RESOURCES_PATH);
_upgradeDataList.AddRange(upgrades);
Debug.Log($"[UpgradeDatabase] {_upgradeDataList.Count}개의 업그레이드 데이터를 로드했습니다.");
}
/// <summary>
/// 조회용 테이블 구축
/// </summary>
private void BuildLookupTables()
{
_upgradeById.Clear();
_upgradesByCategory.Clear();
foreach (var upgrade in _upgradeDataList)
{
if (upgrade == null) continue;
// ID로 조회
if (!_upgradeById.ContainsKey(upgrade.id))
{
_upgradeById[upgrade.id] = upgrade;
}
// 카테고리로 조회
string category = upgrade.upgradeCategory;
if (!_upgradesByCategory.ContainsKey(category))
{
_upgradesByCategory[category] = new List<UpgradeData>();
}
_upgradesByCategory[category].Add(upgrade);
}
}
/// <summary>
/// 데이터 다시 로드 (에디터에서 데이터 변경 시 호출)
/// </summary>
public void ReloadData()
{
LoadUpgradeData();
BuildLookupTables();
}
#region Getters
/// <summary>
/// ID로 업그레이드 데이터 조회
/// </summary>
public UpgradeData GetUpgradeById(int id)
{
if (_upgradeById.TryGetValue(id, out var upgrade))
{
return upgrade;
}
Debug.LogWarning($"[UpgradeDatabase] ID {id}에 해당하는 업그레이드를 찾을 수 없습니다.");
return null;
}
/// <summary>
/// 카테고리로 업그레이드 목록 조회
/// </summary>
public List<UpgradeData> GetUpgradesByCategory(string category)
{
if (_upgradesByCategory.TryGetValue(category, out var upgrades))
{
return new List<UpgradeData>(upgrades);
}
return new List<UpgradeData>();
}
/// <summary>
/// 개인 업그레이드 목록 조회
/// </summary>
public List<UpgradeData> GetPersonalUpgrades()
{
var result = new List<UpgradeData>();
foreach (var upgrade in _upgradeDataList)
{
if (upgrade != null && upgrade.upgradeTarget == "person")
{
result.Add(upgrade);
}
}
return result;
}
/// <summary>
/// 팀 업그레이드 목록 조회
/// </summary>
public List<UpgradeData> GetTeamUpgrades()
{
var result = new List<UpgradeData>();
foreach (var upgrade in _upgradeDataList)
{
if (upgrade != null && upgrade.upgradeTarget == "share")
{
result.Add(upgrade);
}
}
return result;
}
/// <summary>
/// 모든 업그레이드 데이터 반환
/// </summary>
public List<UpgradeData> GetAllUpgrades()
{
return new List<UpgradeData>(_upgradeDataList);
}
/// <summary>
/// 선행 업그레이드 조건 충족 여부 확인
/// </summary>
public bool ArePrerequisitesMet(int upgradeId, HashSet<int> ownedUpgradeIds)
{
var upgrade = GetUpgradeById(upgradeId);
if (upgrade == null) return false;
foreach (int requiredId in upgrade.requireUpgradeId)
{
if (!ownedUpgradeIds.Contains(requiredId))
{
return false;
}
}
return true;
}
#endregion
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 12780dc9ee390c742b6cbd405e59a916

View File

@@ -1,6 +1,6 @@
id,memo,move_speed,max_hp,sight,atk_range,atk_damage,atk_interval_sec,cost,weight,model_path,animation_controller_path,wave_min,wave_max
101,Grunt(기본),6.7,75,6,1,3,1.2,3,1,Assets/Models/Skeleton_Minion.fbx,Assets/Animations/MonsterAnimationController.controller,1,10
101,Grunt(기본),6.7,75,6,1,3,1.2,3,1.0,Assets/Models/Skeleton_Minion.fbx,Assets/Animations/MonsterAnimationController.controller,1,10
102,Tank(느림/단단),3.3,125,6,1,4,1.5,5,0.5,Assets/Models/Druid.fbx,Assets/Animations/MonsterAnimationController.controller,3,15
103,Ranged(원거리/약함),5.3,65,6,5,2,1.4,3,0.2,Assets/Models/Skeleton_Warrior.fbx,Assets/Animations/MonsterAnimationController.controller,3,20
104,Fast(빠름/약함),8.6,45,6,1,2,1,3,0.333,Assets/Models/Skeleton_Rogue.fbx,Assets/Animations/MonsterAnimationController.controller,3,20
104,Fast(빠름/약함),8.6,45,6,1,2,1.0,3,0.333,Assets/Models/Skeleton_Rogue.fbx,Assets/Animations/MonsterAnimationController.controller,3,20
105,Elite(소수 정예),6.6,100,6,1,7,1.3,6,0.1,Assets/Models/Barbarian_Large.fbx,Assets/Animations/MonsterAnimationController.controller,5,20
1 id memo move_speed max_hp sight atk_range atk_damage atk_interval_sec cost weight model_path animation_controller_path wave_min wave_max
2 101 Grunt(기본) 6.7 75 6 1 3 1.2 3 1 1.0 Assets/Models/Skeleton_Minion.fbx Assets/Animations/MonsterAnimationController.controller 1 10
3 102 Tank(느림/단단) 3.3 125 6 1 4 1.5 5 0.5 Assets/Models/Druid.fbx Assets/Animations/MonsterAnimationController.controller 3 15
4 103 Ranged(원거리/약함) 5.3 65 6 5 2 1.4 3 0.2 Assets/Models/Skeleton_Warrior.fbx Assets/Animations/MonsterAnimationController.controller 3 20
5 104 Fast(빠름/약함) 8.6 45 6 1 2 1 1.0 3 0.333 Assets/Models/Skeleton_Rogue.fbx Assets/Animations/MonsterAnimationController.controller 3 20
6 105 Elite(소수 정예) 6.6 100 6 1 7 1.3 6 0.1 Assets/Models/Barbarian_Large.fbx Assets/Animations/MonsterAnimationController.controller 5 20

View File

@@ -1,12 +1,12 @@
id,memo,building_name,level,upgrade_to,tower_type,mana,manpower,size_x,size_y,size_z,max_hp,sight,atk_range,atk_damage,atk_interval_sec,model_path
1,타워,Arrow Tower Lv.1,1,2,attack,100,10,4,10,4,50,10,5,3,2,Assets/Models/building_tower_B_blue.fbx
2,타워,Arrow Tower Lv.2,2,3,attack,200,20,4,10,4,75,10,10,6,2,Assets/Models/building_tower_B_blue.fbx
3,타워,Arrow Tower Lv.3,3,4,attack,300,30,4,10,4,100,15,10,9,2,Assets/Models/building_tower_B_blue.fbx
4,타워,Arrow Tower Lv.4,4,5,attack,400,40,4,10,4,150,15,15,12,2,Assets/Models/building_tower_B_blue.fbx
1,타워,Arrow Tower Lv.1,1,2.0,attack,100,10,4,10,4,50,10,5,3,2,Assets/Models/building_tower_B_blue.fbx
2,타워,Arrow Tower Lv.2,2,3.0,attack,200,20,4,10,4,75,10,10,6,2,Assets/Models/building_tower_B_blue.fbx
3,타워,Arrow Tower Lv.3,3,4.0,attack,300,30,4,10,4,100,15,10,9,2,Assets/Models/building_tower_B_blue.fbx
4,타워,Arrow Tower Lv.4,4,5.0,attack,400,40,4,10,4,150,15,15,12,2,Assets/Models/building_tower_B_blue.fbx
5,타워,Arrow Tower Lv.5,5,,attack,500,50,4,10,4,200,20,15,15,2,Assets/Models/building_tower_B_blue.fbx
6,,Wall Lv.1,1,7,defense,10,10,8,4,3,30,1,0,0,0,Assets/Models/wall_straight.fbx
7,,Wall Lv.2,2,8,defense,30,10,8,4,3,30,1,0,0,0,Assets/Models/wall_straight.fbx
8,,Wall Lv.3,3,9,defense,50,20,8,4,3,30,1,0,0,0,Assets/Models/wall_straight.fbx
9,,Wall Lv.4,4,10,defense,100,20,8,4,3,30,1,0,0,0,Assets/Models/wall_straight.fbx
6,,Wall Lv.1,1,7.0,defense,10,10,8,4,3,30,1,0,0,0,Assets/Models/wall_straight.fbx
7,,Wall Lv.2,2,8.0,defense,30,10,8,4,3,30,1,0,0,0,Assets/Models/wall_straight.fbx
8,,Wall Lv.3,3,9.0,defense,50,20,8,4,3,30,1,0,0,0,Assets/Models/wall_straight.fbx
9,,Wall Lv.4,4,10.0,defense,100,20,8,4,3,30,1,0,0,0,Assets/Models/wall_straight.fbx
10,,Wall Lv.5,5,,defense,150,20,8,4,3,30,1,0,0,0,Assets/Models/wall_straight.fbx
11,와드,Ward Lv.1,1,,sight,10,10,1,1,1,10,5,0,0,0,Assets/Models/torch.fbx
1 id memo building_name level upgrade_to tower_type mana manpower size_x size_y size_z max_hp sight atk_range atk_damage atk_interval_sec model_path
2 1 타워 Arrow Tower Lv.1 1 2 2.0 attack 100 10 4 10 4 50 10 5 3 2 Assets/Models/building_tower_B_blue.fbx
3 2 타워 Arrow Tower Lv.2 2 3 3.0 attack 200 20 4 10 4 75 10 10 6 2 Assets/Models/building_tower_B_blue.fbx
4 3 타워 Arrow Tower Lv.3 3 4 4.0 attack 300 30 4 10 4 100 15 10 9 2 Assets/Models/building_tower_B_blue.fbx
5 4 타워 Arrow Tower Lv.4 4 5 5.0 attack 400 40 4 10 4 150 15 15 12 2 Assets/Models/building_tower_B_blue.fbx
6 5 타워 Arrow Tower Lv.5 5 attack 500 50 4 10 4 200 20 15 15 2 Assets/Models/building_tower_B_blue.fbx
7 6 Wall Lv.1 1 7 7.0 defense 10 10 8 4 3 30 1 0 0 0 Assets/Models/wall_straight.fbx
8 7 Wall Lv.2 2 8 8.0 defense 30 10 8 4 3 30 1 0 0 0 Assets/Models/wall_straight.fbx
9 8 Wall Lv.3 3 9 9.0 defense 50 20 8 4 3 30 1 0 0 0 Assets/Models/wall_straight.fbx
10 9 Wall Lv.4 4 10 10.0 defense 100 20 8 4 3 30 1 0 0 0 Assets/Models/wall_straight.fbx
11 10 Wall Lv.5 5 defense 150 20 8 4 3 30 1 0 0 0 Assets/Models/wall_straight.fbx
12 11 와드 Ward Lv.1 1 sight 10 10 1 1 1 10 5 0 0 0 Assets/Models/torch.fbx