diff --git a/Assembly-CSharp.csproj b/Assembly-CSharp.csproj
index 90b7114..f9fbe0c 100644
--- a/Assembly-CSharp.csproj
+++ b/Assembly-CSharp.csproj
@@ -72,8 +72,6 @@
-
-
@@ -87,7 +85,6 @@
-
diff --git a/Assets/Data/ScriptableObjects/Tower/Tower1.asset b/Assets/Data/ScriptableObjects/Tower/Tower1.asset
index abf2768..ae158c9 100644
--- a/Assets/Data/ScriptableObjects/Tower/Tower1.asset
+++ b/Assets/Data/ScriptableObjects/Tower/Tower1.asset
@@ -15,38 +15,21 @@ MonoBehaviour:
buildingName: "\uD0C0\uC6CC"
prefab: {fileID: 8512676738329978770, guid: 3f7838db2c2fc424d9bd9a0d243b43be, type: 3}
icon: {fileID: 0}
- width: 3
- length: 3
- height: 3
- placementOffset: {x: 0, y: 0, z: 0}
- allowRotation: 1
- requiredWorkAmount: 10
- workPerInteraction: 10
- interactionCooldown: 1
- constructionAnimationTrigger: Build
- constructionEquipment:
- socketName: RightHand
- equipmentPrefab: {fileID: 0}
- attachOnStart: 1
- detachOnEnd: 1
- keepEquipped: 0
- attachDelay: 0
- detachDelay: 0
- maxHealth: 50
- isIndestructible: 0
- autoRegenerate: 0
- regenPerSecond: 1
- providesVision: 1
- visionRange: 10
id: 1
memo: "\uD0C0\uC6CC"
mana: 25
- manpower: 10
sizeX: 3
sizeY: 3
sizeZ: 3
+ placementOffset: {x: 0, y: 0, z: 0}
+ allowRotation: 1
+ manpower: 10
maxHp: 50
+ isIndestructible: 0
+ autoRegenerate: 0
+ regenPerSecond: 1
atkRange: 10
+ providesVision: 1
atkDamage: 5
atkIntervalSec: 2
modelPath: Assets/Models/building_tower_B_blue.fbx
diff --git a/Assets/Data/ScriptableObjects/Tower/Tower2.asset b/Assets/Data/ScriptableObjects/Tower/Tower2.asset
index c3ae5a2..78d375e 100644
--- a/Assets/Data/ScriptableObjects/Tower/Tower2.asset
+++ b/Assets/Data/ScriptableObjects/Tower/Tower2.asset
@@ -15,38 +15,21 @@ MonoBehaviour:
buildingName: "\uBCBD"
prefab: {fileID: 3671057791414486316, guid: ae9a9b515e1792a45887f0d967b943d6, type: 3}
icon: {fileID: 0}
- width: 2
- length: 2
- height: 1
- placementOffset: {x: 0, y: 0, z: 0}
- allowRotation: 1
- requiredWorkAmount: 5
- workPerInteraction: 10
- interactionCooldown: 1
- constructionAnimationTrigger: Build
- constructionEquipment:
- socketName: RightHand
- equipmentPrefab: {fileID: 0}
- attachOnStart: 1
- detachOnEnd: 1
- keepEquipped: 0
- attachDelay: 0
- detachDelay: 0
- maxHealth: 30
- isIndestructible: 0
- autoRegenerate: 0
- regenPerSecond: 1
- providesVision: 1
- visionRange: 0
id: 2
memo: "\uBCBD"
mana: 5
+ sizeX: 4
+ sizeY: 3
+ sizeZ: 2
+ placementOffset: {x: 0, y: 0, z: 0}
+ allowRotation: 1
manpower: 5
- sizeX: 2
- sizeY: 2
- sizeZ: 1
maxHp: 30
+ isIndestructible: 0
+ autoRegenerate: 0
+ regenPerSecond: 1
atkRange: 0
+ providesVision: 1
atkDamage: 0
atkIntervalSec: 0
modelPath: Assets/Models/wall_straight.fbx
diff --git a/Assets/Data/Scripts/DataClasses/TowerData.cs b/Assets/Data/Scripts/DataClasses/TowerData.cs
index 138d7fb..f56a827 100644
--- a/Assets/Data/Scripts/DataClasses/TowerData.cs
+++ b/Assets/Data/Scripts/DataClasses/TowerData.cs
@@ -1,72 +1,83 @@
-// 이 파일은 자동 생성되었습니다. 직접 수정하지 마세요!
-// 생성 스크립트: DataTools/generate_csharp_classes.py
-
using UnityEngine;
-using System.Collections.Generic; // 리스트 지원을 위해 추가
-using Northbound;
namespace Northbound.Data
{
[CreateAssetMenu(fileName = "TowerData", menuName = "Northbound/Tower Data")]
- public class TowerData : BuildingData
+ public class TowerData : ScriptableObject
{
- [Header("기본 정보")]
- /// 고유 ID
+ [Header("Building Info")]
+ public string buildingName;
+ public GameObject prefab;
+ [Tooltip("UI에 표시될 건물 아이콘")]
+ public Sprite icon;
+
+ [Header("Basic Info")]
+ [Tooltip("고유 ID")]
public int id;
- /// 기획 메모
+ [Tooltip("기획 메모")]
public string memo;
- /// 건설 비용 (mana=20) (mana=50; iron=10)
+ [Tooltip("건설 비용")]
public int mana;
- /// 건설 노동량
- public float manpower;
- /// X 그리드 차지 공간
- public int sizeX;
- /// Y 그리드 차지 공간
- public int sizeY;
- /// Z 차지 공간
- public int sizeZ;
- /// 체력
- public int maxHp;
- /// 사정거리
- public int atkRange;
- /// 데미지
- public int atkDamage;
- /// 공격 주기
- public float atkIntervalSec;
- /// 모델 경로
+
+ [Header("Grid Size")]
+ [Tooltip("X 그리드 차지 공간")]
+ public int sizeX = 1;
+ [Tooltip("Y 그리드 차지 공간")]
+ public int sizeY = 1;
+ [Tooltip("Z 차지 공간")]
+ public int sizeZ = 2;
+
+ [Header("Placement Settings")]
+ [Tooltip("Offset from grid position")]
+ public Vector3 placementOffset = Vector3.zero;
+ [Tooltip("Can rotate this building?")]
+ public bool allowRotation = true;
+
+ [Header("Construction Settings")]
+ [Tooltip("건설 완료에 필요한 총 작업량")]
+ public float manpower = 100f;
+
+ [Header("Health Settings")]
+ [Tooltip("체력")]
+ public int maxHp = 100;
+ [Tooltip("Can this building be damaged?")]
+ public bool isIndestructible = false;
+ [Tooltip("Auto-regenerate health over time")]
+ public bool autoRegenerate = false;
+ [Tooltip("Health regeneration per second")]
+ public int regenPerSecond = 1;
+
+ [Header("Vision Settings")]
+ [Tooltip("사정거리")]
+ public int atkRange = 15;
+ [Tooltip("Does this building provide vision?")]
+ public bool providesVision = true;
+
+ [Header("Attack Settings")]
+ [Tooltip("데미지")]
+ public int atkDamage = 10;
+ [Tooltip("공격 주기")]
+ public float atkIntervalSec = 1f;
+
+ [Header("Model Settings")]
+ [Tooltip("모델 경로")]
public string modelPath;
- private bool fieldsSynced = false;
+ [Header("Properties for convenience")]
+ public int width => sizeX;
+ public int length => sizeY;
+ public float height => sizeZ;
+ public int maxHealth => maxHp;
+ public float visionRange => atkRange;
+ public float requiredWorkAmount => manpower;
- private void Awake()
+ public Vector3 GetSize(int rotation)
{
- SyncFields();
- }
-
- private void SyncFields()
- {
- if (fieldsSynced) return;
- fieldsSynced = true;
-
- // Map TowerData fields to BuildingData fields
- if (string.IsNullOrEmpty(base.buildingName))
- {
- base.buildingName = memo;
- }
- base.width = sizeX;
- base.length = sizeY;
- base.height = sizeZ;
- base.maxHealth = maxHp;
- base.visionRange = atkRange;
- base.requiredWorkAmount = manpower;
- base.workPerInteraction = 10f;
- base.interactionCooldown =1f;
- base.providesVision = true;
- }
-
- public void EnsureSynced()
- {
- SyncFields();
+ // Rotation 0,180 = normal, 90,270 = swap width/length
+ bool isRotated = (rotation == 1 || rotation == 3);
+ float w = isRotated ? length : width;
+ float l = isRotated ? width : length;
+ return new Vector3(w, height, l);
}
}
-}
\ No newline at end of file
+}
diff --git a/Assets/Prefabs/BuildingFoundation.prefab b/Assets/Prefabs/BuildingFoundation.prefab
index 4e6ea6c..77d54d0 100644
--- a/Assets/Prefabs/BuildingFoundation.prefab
+++ b/Assets/Prefabs/BuildingFoundation.prefab
@@ -46,7 +46,7 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.NetworkObject
- GlobalObjectIdHash: 1879467101
+ GlobalObjectIdHash: 4292538740
InScenePlacedSourceGlobalObjectIdHash: 4292538740
DeferredDespawnTick: 0
Ownership: 1
@@ -75,6 +75,15 @@ MonoBehaviour:
buildingData: {fileID: 11400000, guid: 23c12a82ea534b34299700b86fffd524, type: 2}
gridPosition: {x: 0, y: 0, z: 0}
rotation: 0
+ constructionAnimationTrigger: Mining
+ constructionEquipment:
+ socketName: handslot.r
+ equipmentPrefab: {fileID: 919132149155446097, guid: 804d477fc7f114c498aa6f95452be893, type: 3}
+ attachOnStart: 1
+ detachOnEnd: 1
+ keepEquipped: 0
+ attachDelay: 0
+ detachDelay: 0
foundationVisual: {fileID: 2851644658348875061}
progressBarPrefab: {fileID: 0}
--- !u!1001 &3121692064363536484
diff --git a/Assets/Prefabs/Tower/Tower1.prefab b/Assets/Prefabs/Tower/Tower1.prefab
index 4275411..0f4256d 100644
--- a/Assets/Prefabs/Tower/Tower1.prefab
+++ b/Assets/Prefabs/Tower/Tower1.prefab
@@ -195,7 +195,6 @@ GameObject:
- component: {fileID: 100877884298911200}
- component: {fileID: 8485093670801034058}
- component: {fileID: 7262612124217315611}
- - component: {fileID: 3089566480349729}
m_Layer: 0
m_Name: Tower1
m_TagString: Untagged
@@ -327,16 +326,3 @@ MonoBehaviour:
minHeightForDistantVisibility: 3
useExploredMaterial: 0
exploredMaterial: {fileID: 0}
---- !u!114 &3089566480349729
-MonoBehaviour:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 8512676738329978770}
- m_Enabled: 1
- m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: 56c4536effc49fe47af593bf9d17e979, type: 3}
- m_Name:
- m_EditorClassIdentifier: Assembly-CSharp::Northbound.TowerDataComponent
- towerData: {fileID: 11400000, guid: 3e2e145df85a3ee4eb615f87efba4554, type: 2}
diff --git a/Assets/Prefabs/Tower/Tower2.prefab b/Assets/Prefabs/Tower/Tower2.prefab
index 965619e..ecf88e8 100644
--- a/Assets/Prefabs/Tower/Tower2.prefab
+++ b/Assets/Prefabs/Tower/Tower2.prefab
@@ -14,7 +14,6 @@ GameObject:
- component: {fileID: 2615519446934682856}
- component: {fileID: 3203720634638459019}
- component: {fileID: 3906797260079127802}
- - component: {fileID: 3692219876976097854}
m_Layer: 0
m_Name: Tower2
m_TagString: Untagged
@@ -82,8 +81,8 @@ BoxCollider:
m_ProvidesContacts: 0
m_Enabled: 1
serializedVersion: 3
- m_Size: {x: 2, y: 1, z: 2}
- m_Center: {x: 0, y: 0.5, z: 0}
+ m_Size: {x: 4, y: 2, z: 3}
+ m_Center: {x: 0, y: 1, z: 0}
--- !u!208 &2615519446934682856
NavMeshObstacle:
m_ObjectHideFlags: 0
@@ -94,11 +93,11 @@ NavMeshObstacle:
m_Enabled: 1
serializedVersion: 3
m_Shape: 1
- m_Extents: {x: 1, y: 0.5, z: 1}
+ m_Extents: {x: 2, y: 1, z: 1.5}
m_MoveThreshold: 0.1
m_Carve: 0
m_CarveOnlyStationary: 1
- m_Center: {x: 0, y: 0.5, z: 0}
+ m_Center: {x: 0, y: 1, z: 0}
m_TimeToStationary: 0.5
--- !u!114 &3203720634638459019
MonoBehaviour:
@@ -146,19 +145,6 @@ MonoBehaviour:
minHeightForDistantVisibility: 3
useExploredMaterial: 0
exploredMaterial: {fileID: 0}
---- !u!114 &3692219876976097854
-MonoBehaviour:
- m_ObjectHideFlags: 0
- m_CorrespondingSourceObject: {fileID: 0}
- m_PrefabInstance: {fileID: 0}
- m_PrefabAsset: {fileID: 0}
- m_GameObject: {fileID: 3671057791414486316}
- m_Enabled: 1
- m_EditorHideFlags: 0
- m_Script: {fileID: 11500000, guid: 56c4536effc49fe47af593bf9d17e979, type: 3}
- m_Name:
- m_EditorClassIdentifier: Assembly-CSharp::Northbound.TowerDataComponent
- towerData: {fileID: 11400000, guid: 03a521eb1160745439ba2d0efeb12f3c, type: 2}
--- !u!1 &8947776510381915047
GameObject:
m_ObjectHideFlags: 0
@@ -187,7 +173,7 @@ Transform:
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: 0}
- m_LocalScale: {x: 2, y: 1, z: 2}
+ m_LocalScale: {x: 4, y: 2, z: 3}
m_ConstrainProportionsScale: 0
m_Children: []
m_Father: {fileID: 1657799771882240}
diff --git a/Assets/Scripts/Building.cs b/Assets/Scripts/Building.cs
index 279f776..a0d7114 100644
--- a/Assets/Scripts/Building.cs
+++ b/Assets/Scripts/Building.cs
@@ -1,13 +1,14 @@
using System;
using Unity.Netcode;
using UnityEngine;
+using Northbound.Data;
namespace Northbound
{
public class Building : NetworkBehaviour, IDamageable, IVisionProvider, ITeamMember
{
[Header("References")]
- public BuildingData buildingData;
+ public TowerData buildingData;
[Header("Runtime Info")]
public Vector3Int gridPosition;
@@ -152,7 +153,7 @@ namespace Northbound
///
/// 건물 초기화 (BuildingManager가 동적 생성 시 호출)
///
- public void Initialize(BuildingData data, Vector3Int gridPos, int rot, ulong ownerId, TeamType team = TeamType.Player)
+ public void Initialize(TowerData data, Vector3Int gridPos, int rot, ulong ownerId, TeamType team = TeamType.Player)
{
buildingData = data;
gridPosition = gridPos;
@@ -462,7 +463,7 @@ namespace Northbound
#region Grid Bounds
///
- /// Gets the grid-based bounds (from BuildingData width/length/height)
+ /// Gets the grid-based bounds (from TowerData width/length/height)
/// This is used for placement validation, NOT the actual collider bounds
/// Bounds are slightly shrunk to allow adjacent buildings to touch
///
diff --git a/Assets/Scripts/BuildingData.cs b/Assets/Scripts/BuildingData.cs
deleted file mode 100644
index 06228b3..0000000
--- a/Assets/Scripts/BuildingData.cs
+++ /dev/null
@@ -1,65 +0,0 @@
-using UnityEngine;
-
-namespace Northbound
-{
- [CreateAssetMenu(fileName = "NewBuilding", menuName = "Northbound/Building Data")]
- public class BuildingData : ScriptableObject
- {
- [Header("Building Info")]
- public string buildingName;
- public GameObject prefab;
- [Tooltip("UI에 표시될 건물 아이콘")]
- public Sprite icon;
-
- [Header("Grid Size")]
- [Tooltip("Width in grid units")]
- public int width = 1;
- [Tooltip("Length in grid units")]
- public int length = 1;
- [Tooltip("Height for placement validation")]
- public float height = 2f;
-
- [Header("Placement Settings")]
- [Tooltip("Offset from grid position")]
- public Vector3 placementOffset = Vector3.zero;
- [Tooltip("Can rotate this building?")]
- public bool allowRotation = true;
-
- [Header("Construction Settings")]
- [Tooltip("건설 완료에 필요한 총 작업량")]
- public float requiredWorkAmount = 100f;
- [Tooltip("1회 상호작용당 작업량")]
- public float workPerInteraction = 10f;
- [Tooltip("상호작용 쿨다운 (초)")]
- public float interactionCooldown = 1f;
- [Tooltip("건설 시 플레이어가 재생할 애니메이션 트리거 (예: Build, Hammer, Construct)")]
- public string constructionAnimationTrigger = "Build";
- [Tooltip("건설 시 사용할 도구 (선택사항)")]
- public EquipmentData constructionEquipment;
-
- [Header("Health Settings")]
- [Tooltip("Maximum health of the building")]
- public int maxHealth = 100;
- [Tooltip("Can this building be damaged?")]
- public bool isIndestructible = false;
- [Tooltip("Auto-regenerate health over time")]
- public bool autoRegenerate = false;
- [Tooltip("Health regeneration per second")]
- public int regenPerSecond = 1;
-
- [Header("Vision Settings")]
- [Tooltip("Does this building provide vision?")]
- public bool providesVision = true;
- [Tooltip("Vision range in world units")]
- public float visionRange = 15f;
-
- public Vector3 GetSize(int rotation)
- {
- // Rotation 0,180 = normal, 90,270 = swap width/length
- bool isRotated = (rotation == 1 || rotation == 3);
- float w = isRotated ? length : width;
- float l = isRotated ? width : length;
- return new Vector3(w, height, l);
- }
- }
-}
diff --git a/Assets/Scripts/BuildingData.cs.meta b/Assets/Scripts/BuildingData.cs.meta
deleted file mode 100644
index 629788c..0000000
--- a/Assets/Scripts/BuildingData.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 937e64980d44d6b46acb35b8046adf34
\ No newline at end of file
diff --git a/Assets/Scripts/BuildingFoundation.cs b/Assets/Scripts/BuildingFoundation.cs
index 6b3ca48..b3924a3 100644
--- a/Assets/Scripts/BuildingFoundation.cs
+++ b/Assets/Scripts/BuildingFoundation.cs
@@ -1,6 +1,7 @@
using System;
using Unity.Netcode;
using UnityEngine;
+using Northbound.Data;
namespace Northbound
{
@@ -9,11 +10,19 @@ namespace Northbound
///
public class BuildingFoundation : NetworkBehaviour, IInteractable, ITeamMember
{
- [Header("Building Info")]
- public BuildingData buildingData;
+ [Header("Target Building")]
+ public TowerData buildingData;
public Vector3Int gridPosition;
public int rotation;
+ [Header("Construction Settings")]
+ [Tooltip("상호작용 쿨다운 (초)")]
+ public float interactionCooldown = 1f;
+ [Tooltip("건설 시 플레이어가 재생할 애니메이션 트리거")]
+ public string constructionAnimationTrigger = "Build";
+ [Tooltip("건설 시 사용할 도구 (선택사항)")]
+ public EquipmentData constructionEquipment;
+
[Header("Visual")]
public GameObject foundationVisual;
public GameObject progressBarPrefab;
@@ -73,7 +82,7 @@ namespace Northbound
///
/// 토대 초기화
///
- public void Initialize(BuildingData data, Vector3Int pos, int rot, ulong ownerId, TeamType team)
+ public void Initialize(TowerData data, Vector3Int pos, int rot, ulong ownerId, TeamType team)
{
if (!IsServer) return;
@@ -84,7 +93,7 @@ namespace Northbound
_team.Value = team;
_currentProgress.Value = 0f;
- // BuildingData의 크기를 기반으로 스케일 설정
+ // TowerData의 크기를 기반으로 스케일 설정
Vector3 size = data.GetSize(rot);
// foundationVisual의 스케일만 조정 (토대 자체의 pivot은 중앙에 유지)
@@ -127,19 +136,19 @@ namespace Northbound
public bool CanInteract(ulong playerId)
{
- if (buildingData == null)
- {
- Debug.LogWarning($"[BuildingFoundation] buildingData is null");
- return false;
- }
-
// 이미 완성됨
if (_currentProgress.Value >= buildingData.requiredWorkAmount)
{
Debug.Log($"[BuildingFoundation] Already completed");
return false;
}
-
+
+ // 쿨다운 확인
+ if (Time.time - _lastInteractionTime < interactionCooldown)
+ {
+ return false;
+ }
+
// 같은 팀만 건설 가능 - 플레이어의 팀을 가져와서 비교
TeamType playerTeam = GetPlayerTeam(playerId);
if (playerTeam != _team.Value)
@@ -153,17 +162,20 @@ namespace Northbound
public void Interact(ulong playerId)
{
- if (!IsServer || buildingData == null) return;
+ if (!IsServer) return;
if (!CanInteract(playerId))
return;
_lastInteractionTime = Time.time;
- // 건설 진행
- _currentProgress.Value += buildingData.workPerInteraction;
+ // 플레이어의 작업량 가져오기
+ float playerWorkPower = GetPlayerWorkPower(playerId);
- Debug.Log($"[BuildingFoundation] 건설 진행: {_currentProgress.Value}/{buildingData.requiredWorkAmount} ({(_currentProgress.Value / buildingData.requiredWorkAmount * 100f):F1}%)");
+ // 건설 진행
+ _currentProgress.Value += playerWorkPower;
+
+ Debug.Log($"[BuildingFoundation] 건설 진행: {_currentProgress.Value}/{buildingData.requiredWorkAmount} ({(_currentProgress.Value / buildingData.requiredWorkAmount * 100f):F1}%) - 작업량: {playerWorkPower}");
// 완성 체크
if (_currentProgress.Value >= buildingData.requiredWorkAmount)
@@ -174,34 +186,19 @@ namespace Northbound
public string GetInteractionPrompt()
{
- if (buildingData == null)
- return "[E] 건설하기";
-
+ string buildingName = buildingData != null ? buildingData.buildingName : "건물";
float percentage = (_currentProgress.Value / buildingData.requiredWorkAmount) * 100f;
- return $"[E] {buildingData.buildingName} 건설 ({percentage:F0}%)";
+ return $"[E] {buildingName} 건설 ({percentage:F0}%)";
}
public string GetInteractionAnimation()
{
- // BuildingData에서 애니메이션 트리거 가져오기
- if (buildingData != null && !string.IsNullOrEmpty(buildingData.constructionAnimationTrigger))
- {
- return buildingData.constructionAnimationTrigger;
- }
-
- // 기본값: 빈 문자열 (애니메이션 없음)
- return "";
+ return constructionAnimationTrigger;
}
public EquipmentData GetEquipmentData()
{
- // BuildingData에 건설 도구가 정의되어 있으면 반환
- if (buildingData != null && buildingData.constructionEquipment != null)
- {
- return buildingData.constructionEquipment;
- }
-
- return null; // 특별한 도구 불필요
+ return constructionEquipment;
}
public Transform GetTransform()
@@ -251,6 +248,32 @@ namespace Northbound
return TeamType.Player;
}
+ ///
+ /// 플레이어의 작업량 가져오기 (PlayerData.manpower)
+ ///
+ private float GetPlayerWorkPower(ulong playerId)
+ {
+ // PlayerInteraction 컴포넌트에서 workPower 가져오기
+ if (NetworkManager.Singleton != null && NetworkManager.Singleton.SpawnManager != null)
+ {
+ if (NetworkManager.Singleton.ConnectedClients.TryGetValue(playerId, out var client))
+ {
+ if (client.PlayerObject != null)
+ {
+ var playerInteraction = client.PlayerObject.GetComponent();
+ if (playerInteraction != null)
+ {
+ return playerInteraction.WorkPower;
+ }
+ }
+ }
+ }
+
+ // 기본값: 10
+ Debug.LogWarning($"[BuildingFoundation] 플레이어 {playerId}의 workPower를 찾을 수 없어 기본값 10을 사용합니다.");
+ return 10f;
+ }
+
private void CompleteConstruction()
{
if (!IsServer) return;
@@ -301,16 +324,13 @@ namespace Northbound
private void OnProgressValueChanged(float oldValue, float newValue)
{
- if (buildingData != null)
- {
- OnProgressChanged?.Invoke(newValue, buildingData.requiredWorkAmount);
- }
+ OnProgressChanged?.Invoke(newValue, buildingData.requiredWorkAmount);
UpdateProgressBar();
}
private void UpdateProgressBar()
{
- if (_progressBarInstance == null || buildingData == null) return;
+ if (_progressBarInstance == null) return;
// 진행바 UI 업데이트 (BuildingHealthBar와 유사한 구조 사용 가능)
var progressBar = _progressBarInstance.GetComponent();
diff --git a/Assets/Scripts/BuildingManager.cs b/Assets/Scripts/BuildingManager.cs
index 27d0072..a774be4 100644
--- a/Assets/Scripts/BuildingManager.cs
+++ b/Assets/Scripts/BuildingManager.cs
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using Unity.Netcode;
using UnityEngine;
+using Northbound.Data;
namespace Northbound
{
@@ -13,7 +14,7 @@ namespace Northbound
public LayerMask groundLayer;
[Header("Building Database")]
- public List availableBuildings = new List();
+ public List availableBuildings = new List();
[Header("Foundation Settings")]
public GameObject foundationPrefab; // 토대 프리팹 (Inspector에서 할당)
@@ -58,7 +59,7 @@ namespace Northbound
);
}
- public bool IsValidPlacement(BuildingData data, Vector3 position, int rotation, out Vector3 groundPosition)
+ public bool IsValidPlacement(TowerData data, Vector3 position, int rotation, out Vector3 groundPosition)
{
groundPosition = position;
@@ -71,7 +72,7 @@ namespace Northbound
Vector3 snappedPosition = SnapToGrid(groundPosition);
groundPosition = snappedPosition; // Update groundPosition to snapped value
- // Overlap check using GRID SIZE from BuildingData (not actual colliders)
+ // Overlap check using GRID SIZE from TowerData (not actual colliders)
Vector3 gridSize = data.GetSize(rotation);
// 프리팹의 실제 배치 위치 계산 (placementOffset 포함)
@@ -130,7 +131,7 @@ namespace Northbound
/// Get the grid bounds for a building at a given position
/// Useful for preview visualization
///
- public Bounds GetPlacementBounds(BuildingData data, Vector3 position, int rotation)
+ public Bounds GetPlacementBounds(TowerData data, Vector3 position, int rotation)
{
Vector3 gridSize = data.GetSize(rotation);
// position은 이미 placementOffset이 적용된 위치
@@ -183,7 +184,7 @@ namespace Northbound
return;
}
- BuildingData data = availableBuildings[buildingIndex];
+ TowerData data = availableBuildings[buildingIndex];
// 보안 검증 3: 건물 데이터 유효성 확인
if (data == null || data.prefab == null)
@@ -335,7 +336,7 @@ namespace Northbound
return;
}
- BuildingData data = availableBuildings[buildingIndex];
+ TowerData data = availableBuildings[buildingIndex];
// 보안 검증 3: 건물 데이터 유효성 확인
if (data == null)
@@ -427,8 +428,8 @@ namespace Northbound
[Rpc(SendTo.Server, InvokePermission = RpcInvokePermission.Everyone)]
public void SpawnCompletedBuildingServerRpc(string buildingDataName, Vector3Int gridPosition, int rotation, ulong ownerId, TeamType team)
{
- // BuildingData 찾기
- BuildingData data = availableBuildings.Find(b => b.name == buildingDataName);
+ // TowerData 찾기
+ TowerData data = availableBuildings.Find(b => b.name == buildingDataName);
if (data == null || data.prefab == null)
{
Debug.LogError($"[BuildingManager] 건물 데이터를 찾을 수 없습니다: {buildingDataName}");
diff --git a/Assets/Scripts/BuildingPlacement.cs b/Assets/Scripts/BuildingPlacement.cs
index 7d6ba65..6d1b78f 100644
--- a/Assets/Scripts/BuildingPlacement.cs
+++ b/Assets/Scripts/BuildingPlacement.cs
@@ -3,6 +3,7 @@ using Unity.Netcode;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.EventSystems;
+using Northbound.Data;
namespace Northbound
{
@@ -248,20 +249,20 @@ namespace Northbound
return;
}
- BuildingData data = BuildingManager.Instance.availableBuildings[selectedBuildingIndex];
+ TowerData data = BuildingManager.Instance.availableBuildings[selectedBuildingIndex];
if (data == null)
{
- Debug.LogError($"[BuildingPlacement] BuildingData is NULL at index {selectedBuildingIndex}");
+ Debug.LogError($"[BuildingPlacement] TowerData is NULL at index {selectedBuildingIndex}");
return;
}
if (data.prefab == null)
{
- Debug.LogError($"[BuildingPlacement] BuildingData.prefab is NULL at index {selectedBuildingIndex}. Run 'Northbound > Diagnose Tower System'");
+ Debug.LogError($"[BuildingPlacement] TowerData.prefab is NULL at index {selectedBuildingIndex}. Run 'Northbound > Diagnose Tower System'");
return;
}
- Debug.Log($"[BuildingPlacement] BuildingData: {data.buildingName}, Prefab: {data.prefab.name}");
+ Debug.Log($"[BuildingPlacement] TowerData: {data.buildingName}, Prefab: {data.prefab.name}");
Debug.Log($"[BuildingPlacement] Prefab scale: {data.prefab.transform.localScale}");
// 완성 건물 프리팹으로 프리뷰 생성 (사용자가 완성 모습을 볼 수 있도록)
@@ -319,7 +320,7 @@ namespace Northbound
if (selectedBuildingIndex < 0 || selectedBuildingIndex >= BuildingManager.Instance.availableBuildings.Count)
return;
- BuildingData data = BuildingManager.Instance.availableBuildings[selectedBuildingIndex];
+ TowerData data = BuildingManager.Instance.availableBuildings[selectedBuildingIndex];
if (data == null || data.prefab == null)
return;
@@ -444,10 +445,10 @@ namespace Northbound
return;
}
- BuildingData data = BuildingManager.Instance.availableBuildings[selectedBuildingIndex];
+ TowerData data = BuildingManager.Instance.availableBuildings[selectedBuildingIndex];
if (data == null || data.prefab == null)
{
- Debug.LogError($"[BuildingPlacement] BuildingData or prefab is null at index {selectedBuildingIndex}. Please run 'Northbound > Populate Towers from Prefabs' and update BuildingManager.");
+ Debug.LogError($"[BuildingPlacement] TowerData or prefab is null at index {selectedBuildingIndex}. Please run 'Northbound > Populate Towers from Prefabs' and update BuildingManager.");
return;
}
@@ -516,7 +517,7 @@ namespace Northbound
}
}
- private List CalculateDragBuildingPositions(Vector3 start, Vector3 end, BuildingData data)
+ private List CalculateDragBuildingPositions(Vector3 start, Vector3 end, TowerData data)
{
List positions = new List();
@@ -566,7 +567,7 @@ namespace Northbound
return;
}
- BuildingData selectedData = BuildingManager.Instance.availableBuildings[selectedBuildingIndex];
+ TowerData selectedData = BuildingManager.Instance.availableBuildings[selectedBuildingIndex];
int successCount = 0;
foreach (var position in dragBuildingPositions)
@@ -606,7 +607,7 @@ namespace Northbound
Ray ray = Camera.main.ScreenPointToRay(Mouse.current.position.ReadValue());
if (Physics.Raycast(ray, out RaycastHit hit, maxPlacementDistance, groundLayer))
{
- BuildingData selectedData = BuildingManager.Instance.availableBuildings[selectedBuildingIndex];
+ TowerData selectedData = BuildingManager.Instance.availableBuildings[selectedBuildingIndex];
if (BuildingManager.Instance.IsValidPlacement(selectedData, hit.point, currentRotation, out Vector3 groundPosition))
{
diff --git a/Assets/Scripts/BuildingQuickslotUI.cs b/Assets/Scripts/BuildingQuickslotUI.cs
index 7658d39..eb2eaa6 100644
--- a/Assets/Scripts/BuildingQuickslotUI.cs
+++ b/Assets/Scripts/BuildingQuickslotUI.cs
@@ -3,6 +3,7 @@ using Unity.Netcode;
using UnityEngine;
using UnityEngine.InputSystem;
using TMPro;
+using Northbound.Data;
namespace Northbound
{
@@ -196,7 +197,7 @@ namespace Northbound
///
/// 개별 슬롯 버튼 생성
///
- private void CreateSlot(BuildingData buildingData, int index)
+ private void CreateSlot(TowerData buildingData, int index)
{
GameObject slotObj = Instantiate(slotButtonPrefab, slotContainer);
BuildingSlotButton slotButton = slotObj.GetComponent();
diff --git a/Assets/Scripts/BuildingSlotButton.cs b/Assets/Scripts/BuildingSlotButton.cs
index e5d57e0..afb55e3 100644
--- a/Assets/Scripts/BuildingSlotButton.cs
+++ b/Assets/Scripts/BuildingSlotButton.cs
@@ -2,6 +2,7 @@ using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;
using TMPro;
+using Northbound.Data;
namespace Northbound
{
@@ -23,7 +24,7 @@ namespace Northbound
[SerializeField] private Color selectedColor = new Color(0.3f, 0.6f, 0.3f, 1f);
[SerializeField] private Color hoverColor = new Color(0.3f, 0.3f, 0.3f, 1f);
- private BuildingData buildingData;
+ private TowerData buildingData;
private int slotIndex;
private BuildingQuickslotUI quickslotUI;
private bool isSelected = false;
@@ -42,7 +43,7 @@ namespace Northbound
///
/// 슬롯 초기화
///
- public void Initialize(BuildingData data, int index, BuildingQuickslotUI ui)
+ public void Initialize(TowerData data, int index, BuildingQuickslotUI ui)
{
buildingData = data;
slotIndex = index;
diff --git a/Assets/Scripts/Editor/CSVToSOImporter.cs b/Assets/Scripts/Editor/CSVToSOImporter.cs
index 8955c3f..d206f56 100644
--- a/Assets/Scripts/Editor/CSVToSOImporter.cs
+++ b/Assets/Scripts/Editor/CSVToSOImporter.cs
@@ -123,10 +123,9 @@ namespace Northbound.Editor
}
// If towers were imported, auto-configure BuildingManager
- // TowerData now extends BuildingData, so it can be used directly!
if (typeName == "Tower")
{
- Debug.Log($"[CSVToSOImporter] Tower import complete, TowerData extends BuildingData now!");
+ Debug.Log($"[CSVToSOImporter] Tower import complete!");
}
return true;
@@ -203,10 +202,10 @@ namespace Northbound.Editor
}
// Now set the prefab reference on data
- if (data is BuildingData buildingData)
+ if (data is TowerData towerData)
{
- buildingData.prefab = prefabObj;
- Debug.Log($"[CSVToSOImporter] Set prefab reference: {buildingData.name} -> {prefabObj.name}");
+ towerData.prefab = prefabObj;
+ Debug.Log($"[CSVToSOImporter] Set prefab reference: {towerData.name} -> {prefabObj.name}");
}
// Save data asset
@@ -236,9 +235,9 @@ namespace Northbound.Editor
return;
}
- // Load TowerData (which extends BuildingData)
+ // Load TowerData
string[] towerDataGuids = AssetDatabase.FindAssets("t:TowerData", new[] { "Assets/Data/ScriptableObjects" });
- List allTowers = new List();
+ List allTowers = new List();
Debug.Log($"[CSVToSOImporter] Found {towerDataGuids.Length} TowerData assets");
diff --git a/Assets/Scripts/Editor/PlayerPrefabSetup.cs b/Assets/Scripts/Editor/PlayerPrefabSetup.cs
index bb66647..eb4064b 100644
--- a/Assets/Scripts/Editor/PlayerPrefabSetup.cs
+++ b/Assets/Scripts/Editor/PlayerPrefabSetup.cs
@@ -84,6 +84,15 @@ namespace Northbound.Editor
Debug.Log($"[PlayerPrefabSetup] Updated PlayerResourceInventory: maxResourceCapacity={playerData.capacity}");
}
+ var playerInteraction = prefab.GetComponent();
+ if (playerInteraction != null)
+ {
+ SerializedObject so = new SerializedObject(playerInteraction);
+ so.FindProperty("workPower").floatValue = playerData.manpower;
+ so.ApplyModifiedProperties();
+ Debug.Log($"[PlayerPrefabSetup] Updated PlayerInteraction: workPower={playerData.manpower}");
+ }
+
EditorUtility.SetDirty(prefab);
PrefabUtility.SavePrefabAsset(prefab);
Debug.Log($"[PlayerPrefabSetup] Player prefab updated successfully from {playerData.name}");
diff --git a/Assets/Scripts/Editor/TemplateCreator.cs b/Assets/Scripts/Editor/TemplateCreator.cs
index 0b66088..f7df4b0 100644
--- a/Assets/Scripts/Editor/TemplateCreator.cs
+++ b/Assets/Scripts/Editor/TemplateCreator.cs
@@ -159,9 +159,6 @@ namespace Northbound.Editor
if (go.GetComponent() == null)
go.AddComponent();
- if (go.GetComponent() == null)
- go.AddComponent();
-
if (go.GetComponent() == null)
{
BoxCollider collider = go.AddComponent();
diff --git a/Assets/Scripts/Editor/TowerPopulator.cs b/Assets/Scripts/Editor/TowerPopulator.cs
index d4772b1..8dde083 100644
--- a/Assets/Scripts/Editor/TowerPopulator.cs
+++ b/Assets/Scripts/Editor/TowerPopulator.cs
@@ -30,10 +30,10 @@ namespace Northbound.Editor
{
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
GameObject prefab = AssetDatabase.LoadAssetAtPath(assetPath);
- TowerDataComponent tower = prefab?.GetComponent();
- string towerStatus = tower != null && tower.towerData != null ? "✓" : "✗";
- string towerDataName = tower?.towerData?.name ?? "MISSING";
- Debug.Log($" {towerStatus} {prefab.name} - TowerDataComponent: {tower != null}, TowerData: {towerDataName}");
+ Building building = prefab?.GetComponent();
+ string towerStatus = building != null && building.buildingData != null ? "✓" : "✗";
+ string towerDataName = building?.buildingData?.name ?? "MISSING";
+ Debug.Log($" {towerStatus} {prefab.name} - Building: {building != null}, TowerData: {towerDataName}");
}
}
diff --git a/Assets/Scripts/Editor/TowerPrefabSetup.cs b/Assets/Scripts/Editor/TowerPrefabSetup.cs
index 005a462..ab0f743 100644
--- a/Assets/Scripts/Editor/TowerPrefabSetup.cs
+++ b/Assets/Scripts/Editor/TowerPrefabSetup.cs
@@ -20,24 +20,9 @@ namespace Northbound.Editor
return;
}
- var towerDataComponent = prefab.GetComponent();
- if (towerDataComponent == null)
- {
- towerDataComponent = prefab.AddComponent();
- Debug.Log($"[TowerPrefabSetup] Added TowerDataComponent component");
- }
-
- if (towerDataComponent != null)
- {
- towerDataComponent.towerData = towerData;
- }
-
- // TowerData now extends BuildingData, so set prefab reference
+ // Set prefab reference
towerData.prefab = prefab;
- // Ensure TowerData fields are synced to BuildingData
- towerData.EnsureSynced();
-
Transform modelTransform = null;
if (!string.IsNullOrEmpty(towerData.modelPath))
diff --git a/Assets/Scripts/PlayerInteraction.cs b/Assets/Scripts/PlayerInteraction.cs
index ad7d711..650e0e3 100644
--- a/Assets/Scripts/PlayerInteraction.cs
+++ b/Assets/Scripts/PlayerInteraction.cs
@@ -12,19 +12,20 @@ namespace Northbound
[Header("Interaction Settings")]
public float interactionRange = 3f;
public LayerMask interactableLayer = ~0;
-
+ public float workPower = 10f;
+
[Header("Detection")]
public Transform rayOrigin;
public bool useForwardDirection = true;
-
+
[Header("Animation")]
public bool playAnimations = true;
public bool useAnimationEvents = true;
public bool blockDuringAnimation = true;
-
+
[Header("Equipment")]
public bool useEquipment = true;
-
+
[Header("Debug")]
public bool showDebugRay = true;
@@ -41,6 +42,7 @@ namespace Northbound
// 다른 컴포넌트가 이동 차단 여부를 확인할 수 있도록 public 프로퍼티 제공
public bool IsInteracting => _isInteracting;
+ public float WorkPower => workPower;
public override void OnNetworkSpawn()
{
@@ -71,6 +73,17 @@ namespace Northbound
private void Update()
{
if (!IsOwner) return;
+
+ // Check if current interactable is no longer valid (e.g., building completed)
+ if (_isInteracting && _currentInteractable != null)
+ {
+ if (!_currentInteractable.CanInteract(OwnerClientId))
+ {
+ Debug.Log("[PlayerInteraction] Interactable no longer valid - ending interaction");
+ EndInteraction();
+ }
+ }
+
DetectInteractable();
}
@@ -243,6 +256,16 @@ namespace Northbound
}
}
+ private void EndInteraction()
+ {
+ _isInteracting = false;
+ if (_interactionTimeoutCoroutine != null)
+ {
+ StopCoroutine(_interactionTimeoutCoroutine);
+ _interactionTimeoutCoroutine = null;
+ }
+ }
+
private void OnGUI()
{
if (!IsOwner || _currentInteractable == null) return;
diff --git a/Assets/Scripts/TowerDataComponent.cs b/Assets/Scripts/TowerDataComponent.cs
deleted file mode 100644
index 35525e8..0000000
--- a/Assets/Scripts/TowerDataComponent.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using Northbound.Data;
-using Unity.Netcode;
-using UnityEngine;
-
-namespace Northbound
-{
- [RequireComponent(typeof(Building))]
- [RequireComponent(typeof(NetworkObject))]
- public class TowerDataComponent : MonoBehaviour
- {
- [Header("Data Reference")]
- [Tooltip("ScriptableObject containing tower data")]
- public TowerData towerData;
-
- private void Awake()
- {
- // TowerData now extends BuildingData, so just pass it directly
- Building building = GetComponent();
- if (building != null && towerData != null)
- {
- building.buildingData = towerData;
- building.initialTeam = TeamType.Player;
- }
- }
- }
-}
diff --git a/Assets/Scripts/TowerDataComponent.cs.meta b/Assets/Scripts/TowerDataComponent.cs.meta
deleted file mode 100644
index f7f5fa3..0000000
--- a/Assets/Scripts/TowerDataComponent.cs.meta
+++ /dev/null
@@ -1,2 +0,0 @@
-fileFormatVersion: 2
-guid: 56c4536effc49fe47af593bf9d17e979
\ No newline at end of file
diff --git a/GameData/Tower.csv b/GameData/Tower.csv
index 6dd9fb9..64baf32 100644
--- a/GameData/Tower.csv
+++ b/GameData/Tower.csv
@@ -1,3 +1,3 @@
id,memo,mana,manpower,size_x,size_y,size_z,max_hp,atk_range,atk_damage,atk_interval_sec,model_path
1,타워,25,10,3,3,3,50,10,5,2,Assets/Models/building_tower_B_blue.fbx
-2,벽,5,5,2,2,1,30,0,0,0,Assets/Models/wall_straight.fbx
+2,벽,5,5,4,3,2,30,0,0,0,Assets/Models/wall_straight.fbx