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