diff --git a/Assets/Data/Scripts/DataClasses/TowerDataExtensions.cs b/Assets/Data/Scripts/DataClasses/TowerDataExtensions.cs index 9508822..e174e81 100644 --- a/Assets/Data/Scripts/DataClasses/TowerDataExtensions.cs +++ b/Assets/Data/Scripts/DataClasses/TowerDataExtensions.cs @@ -38,7 +38,7 @@ namespace Northbound.Data public int length => sizeZ; // Z축 (깊이) public float height => sizeY; // Y축 (높이) public int maxHealth => maxHp; - public float visionRange => atkRange; + public float visionRange => sight; public float requiredWorkAmount => manpower; public Vector3 GetSize(int rotation) diff --git a/Assets/Prefabs/Core.asset b/Assets/Prefabs/Core.asset new file mode 100644 index 0000000..dc40c81 --- /dev/null +++ b/Assets/Prefabs/Core.asset @@ -0,0 +1,47 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 8c40fef5ebc37b743a3f225c1ca57c32, type: 3} + m_Name: Core + m_EditorClassIdentifier: Assembly-CSharp::Northbound.Data.TowerData + id: 0 + memo: + buildingName: + level: 0 + upgradeTo: 0 + towerType: + mana: 0 + manpower: 0 + sizeX: 0 + sizeY: 0 + sizeZ: 0 + maxHp: 0 + sight: 100 + atkRange: 0 + atkDamage: 0 + atkIntervalSec: 0 + modelPath: + prefab: {fileID: 0} + icon: {fileID: 0} + placementOffset: {x: 0, y: 0, z: 0} + allowRotation: 1 + isIndestructible: 0 + autoRegenerate: 0 + regenPerSecond: 1 + providesVision: 1 + constructionEquipment: + socketName: RightHand + equipmentPrefab: {fileID: 0} + attachOnStart: 1 + detachOnEnd: 1 + keepEquipped: 0 + attachDelay: 0 + detachDelay: 0 diff --git a/Assets/Prefabs/Core.asset.meta b/Assets/Prefabs/Core.asset.meta new file mode 100644 index 0000000..cab2ec9 --- /dev/null +++ b/Assets/Prefabs/Core.asset.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 8e9cb7f0c2209b543b171709534789aa +NativeFormatImporter: + externalObjects: {} + mainObjectFileID: 11400000 + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Prefabs/Core.prefab b/Assets/Prefabs/Core.prefab index cfa7314..35716e9 100644 --- a/Assets/Prefabs/Core.prefab +++ b/Assets/Prefabs/Core.prefab @@ -49,7 +49,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} m_Name: m_EditorClassIdentifier: Unity.Netcode.Runtime::Unity.Netcode.NetworkObject - GlobalObjectIdHash: 3998537868 + GlobalObjectIdHash: 615747208 InScenePlacedSourceGlobalObjectIdHash: 615747208 DeferredDespawnTick: 0 Ownership: 0 @@ -106,7 +106,7 @@ MonoBehaviour: m_Name: m_EditorClassIdentifier: Assembly-CSharp::Northbound.Building ShowTopMostFoldoutHeaderGroup: 1 - buildingData: {fileID: 0} + buildingData: {fileID: 11400000, guid: 8e9cb7f0c2209b543b171709534789aa, type: 2} gridPosition: {x: 0, y: 0, z: 0} rotation: 0 initialTeam: 1 @@ -152,10 +152,12 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 760137a2fd0da7f458ac4b0ee7f485d6, type: 3} m_Name: m_EditorClassIdentifier: Assembly-CSharp::Northbound.FogOfWarVisibility + alwaysVisible: 1 showInExploredAreas: 1 updateInterval: 0.2 renderers: [] enableDistantVisibility: 1 + baseVisibilityRange: 50 heightVisibilityMultiplier: 2 minHeightForDistantVisibility: 3 useExploredMaterial: 0 diff --git a/Assets/Scripts/Buildings/Blacksmith.cs b/Assets/Scripts/Buildings/Blacksmith.cs index 333eaeb..1b08f04 100644 --- a/Assets/Scripts/Buildings/Blacksmith.cs +++ b/Assets/Scripts/Buildings/Blacksmith.cs @@ -6,8 +6,16 @@ namespace Northbound /// /// 블랙스미스 건물 - 업그레이드 구매 가능 /// - public class Blacksmith : MonoBehaviour, IInteractable + public class Blacksmith : NetworkBehaviour, IInteractable, IVisionProvider, ITeamMember { + [Header("Vision Settings")] + [Tooltip("블랙스미스가 제공하는 시야 범위")] + public float visionRange = 10f; + + [Header("Team")] + [Tooltip("건물의 팀")] + public TeamType initialTeam = TeamType.Player; + [Header("UI Reference")] [SerializeField] private GameObject _upgradePopupPrefab; @@ -55,9 +63,6 @@ namespace Northbound return transform; } - - - #endregion private void OpenUpgradePopup(ulong playerId) @@ -115,12 +120,63 @@ namespace Northbound return null; } - private void OnDestroy() + public override void OnDestroy() { if (_popupInstance != null) { Destroy(_popupInstance); } + base.OnDestroy(); } + + #region NetworkBehaviour Overrides + + public override void OnNetworkSpawn() + { + if (IsServer) + { + // 시야 제공자로 등록 + FogOfWarSystem.Instance?.RegisterVisionProvider(this); + } + } + + public override void OnNetworkDespawn() + { + if (IsServer) + { + // 시야 제공자 등록 해제 + FogOfWarSystem.Instance?.UnregisterVisionProvider(this); + } + } + + #endregion + + #region IVisionProvider Implementation + + public ulong GetOwnerId() => 0; // 블랙스미스는 모든 플레이어에게 시야 제공 + + public float GetVisionRange() => visionRange; + + Transform IVisionProvider.GetTransform() => transform; + + public bool IsActive() => IsSpawned; + + TeamType IVisionProvider.GetTeam() => initialTeam; + + #endregion + + #region ITeamMember Implementation + + public TeamType GetTeam() => initialTeam; + + public bool IsDead() => false; // 블랙스미스는 파괴되지 않음 + + public void SetTeam(TeamType team) + { + // 블랙스미스의 팀은 변경할 수 없음 + Debug.LogWarning("[Blacksmith] 블랙스미스의 팀은 변경할 수 없습니다."); + } + + #endregion } } diff --git a/Assets/Scripts/Core.cs b/Assets/Scripts/Core.cs index b21d0c4..5e4fbe6 100644 --- a/Assets/Scripts/Core.cs +++ b/Assets/Scripts/Core.cs @@ -7,12 +7,15 @@ namespace Northbound /// /// 플레이어가 자원을 건내받아 게임의 전역 자원으로 관리하는 중앙 허브 /// - public class Core : NetworkBehaviour, IInteractable, IDamageable, ITeamMember + public class Core : NetworkBehaviour, IInteractable, IDamageable, ITeamMember, IVisionProvider { [Header("Core Settings")] public int maxStorageCapacity = 1000; // 코어의 최대 저장 용량 public bool unlimitedStorage = false; // 무제한 저장소 + [Header("Vision")] + public float visionRange = 15f; // 코어가 제공하는 시야 범위 + [Header("Health")] public int maxHealth = 1000; public GameObject damageEffectPrefab; @@ -55,6 +58,9 @@ namespace Northbound { _totalResources.Value = 0; _currentHealth.Value = maxHealth; + + // 시야 제공자로 등록 + FogOfWarSystem.Instance?.RegisterVisionProvider(this); } _currentHealth.OnValueChanged += OnHealthChanged; @@ -62,6 +68,12 @@ namespace Northbound public override void OnNetworkDespawn() { + if (IsServer) + { + // 시야 제공자 등록 해제 + FogOfWarSystem.Instance?.UnregisterVisionProvider(this); + } + _currentHealth.OnValueChanged -= OnHealthChanged; } @@ -372,5 +384,19 @@ namespace Northbound } #endregion + + #region IVisionProvider Implementation + + public ulong GetOwnerId() => 0; // 코어는 모든 플레이어에게 시야 제공 + + public float GetVisionRange() => visionRange; + + Transform IVisionProvider.GetTransform() => transform; + + public bool IsActive() => IsSpawned && !IsDead(); + + TeamType IVisionProvider.GetTeam() => TeamType.Player; + + #endregion } } \ No newline at end of file diff --git a/Assets/Scripts/FogOfWarRenderer.cs b/Assets/Scripts/FogOfWarRenderer.cs index f360997..9a62841 100644 --- a/Assets/Scripts/FogOfWarRenderer.cs +++ b/Assets/Scripts/FogOfWarRenderer.cs @@ -182,17 +182,39 @@ namespace Northbound { if (NetworkManager.Singleton == null) return ulong.MaxValue; - var localPlayer = NetworkManager.Singleton.SpawnManager.GetLocalPlayerObject(); - if (localPlayer != null) + // 방법 1: SpawnManager에서 찾기 + NetworkObject localPlayer = null; + if (NetworkManager.Singleton.SpawnManager != null) { - var playerController = localPlayer.GetComponent(); - if (playerController != null) + localPlayer = NetworkManager.Singleton.SpawnManager.GetLocalPlayerObject(); + } + + // 방법 2: LocalClient에서 찾기 + if (localPlayer == null && NetworkManager.Singleton.LocalClient != null) + { + localPlayer = NetworkManager.Singleton.LocalClient.PlayerObject; + } + + // 방법 3: 직접 검색 (IsLocalPlayer인 플레이어 찾기) + if (localPlayer == null) + { + var allPlayers = FindObjectsByType(FindObjectsSortMode.None); + foreach (var player in allPlayers) { - return playerController.OwnerPlayerId; + if (player.IsLocalPlayer) + { + localPlayer = player.GetComponent(); + break; + } } } - return ulong.MaxValue; + if (localPlayer == null) return ulong.MaxValue; + + var playerController = localPlayer.GetComponent(); + if (playerController == null) return ulong.MaxValue; + + return playerController.OwnerPlayerId; } /// diff --git a/Assets/Scripts/FogOfWarSystem.cs b/Assets/Scripts/FogOfWarSystem.cs index 9ecef51..92fc709 100644 --- a/Assets/Scripts/FogOfWarSystem.cs +++ b/Assets/Scripts/FogOfWarSystem.cs @@ -307,7 +307,8 @@ namespace Northbound if (!_visionProviders.Contains(provider)) { _visionProviders.Add(provider); - // Debug.Log($"[FogOfWar] Vision Provider 등록: {provider.GetTransform().name} (Owner: {provider.GetOwnerId()})"); + // 즉시 시야 업데이트 트리거 + _updateTimer = updateInterval; } } diff --git a/Assets/Scripts/FogOfWarVisibility.cs b/Assets/Scripts/FogOfWarVisibility.cs index ac2ac73..20a3e8c 100644 --- a/Assets/Scripts/FogOfWarVisibility.cs +++ b/Assets/Scripts/FogOfWarVisibility.cs @@ -10,6 +10,9 @@ namespace Northbound public class FogOfWarVisibility : MonoBehaviour { [Header("Visibility Settings")] + [Tooltip("Always show this object regardless of fog state (for core, important structures)")] + public bool alwaysVisible = false; + [Tooltip("Show this object in explored areas (greyed out) or only when visible")] public bool showInExploredAreas = false; @@ -19,10 +22,13 @@ namespace Northbound [Tooltip("Renderers to show/hide (auto-detected if empty)")] public Renderer[] renderers; - [Header("Height-Based Distant Visibility")] + [Header("Extended Visibility")] [Tooltip("Enable visibility from farther away based on object height")] public bool enableDistantVisibility = true; + [Tooltip("Base visibility range (additional range beyond player vision)")] + public float baseVisibilityRange = 10f; + [Tooltip("Visibility range multiplier per unit of height (default: 2x vision per 1m height)")] public float heightVisibilityMultiplier = 2.0f; @@ -76,8 +82,17 @@ namespace Northbound // CRITICAL: Start hidden and stay hidden until fog system confirms visibility // Force initial hide - don't use SetVisible because _isVisible defaults to false // which would cause early return - _isVisible = true; // Set to true first so SetVisible(false) actually runs - SetVisible(false); + // 단, alwaysVisible이면 항상 보임 + if (alwaysVisible) + { + _isVisible = false; + SetVisible(true); + } + else + { + _isVisible = true; // Set to true first so SetVisible(false) actually runs + SetVisible(false); + } } /// @@ -165,21 +180,50 @@ namespace Northbound { if (NetworkManager.Singleton == null) return ulong.MaxValue; - var localPlayer = NetworkManager.Singleton.SpawnManager.GetLocalPlayerObject(); - if (localPlayer != null) + // 방법 1: SpawnManager에서 찾기 + NetworkObject localPlayer = null; + if (NetworkManager.Singleton.SpawnManager != null) { - var playerController = localPlayer.GetComponent(); - if (playerController != null) + localPlayer = NetworkManager.Singleton.SpawnManager.GetLocalPlayerObject(); + } + + // 방법 2: LocalClient에서 찾기 + if (localPlayer == null && NetworkManager.Singleton.LocalClient != null) + { + localPlayer = NetworkManager.Singleton.LocalClient.PlayerObject; + } + + // 방법 3: 직접 검색 (IsLocalPlayer인 플레이어 찾기) + if (localPlayer == null) + { + var allPlayers = FindObjectsByType(FindObjectsSortMode.None); + foreach (var player in allPlayers) { - return playerController.OwnerPlayerId; + if (player.IsLocalPlayer) + { + localPlayer = player.GetComponent(); + break; + } } } - return ulong.MaxValue; + if (localPlayer == null) return ulong.MaxValue; + + var playerController = localPlayer.GetComponent(); + if (playerController == null) return ulong.MaxValue; + + return playerController.OwnerPlayerId; } private void UpdateVisibility() { + // 항상 보이는 객체는 fog 체크 안함 + if (alwaysVisible) + { + SetVisible(true); + return; + } + var fogSystem = FogOfWarSystem.Instance; if (fogSystem == null) { @@ -268,11 +312,14 @@ namespace Northbound // Calculate extended visibility range based on height // Taller objects can be seen from farther away // Formula: Base range + (height - minHeight) * multiplier - float extendedRange = (_objectHeight - minHeightForDistantVisibility) * heightVisibilityMultiplier; + float extendedRange = 0f; + if (_objectHeight >= minHeightForDistantVisibility) + { + extendedRange = (_objectHeight - minHeightForDistantVisibility) * heightVisibilityMultiplier; + } - // Get player's vision range (assume average vision provider has ~15 unit range) - float baseVisionRange = 15f; // You can make this configurable - float totalRange = baseVisionRange + extendedRange; + // Total range = player vision + base visibility + height bonus + float totalRange = baseVisibilityRange + extendedRange; return distanceToPlayer <= totalRange; } diff --git a/Assets/Scripts/WorkerSpawner.cs b/Assets/Scripts/WorkerSpawner.cs index c33e1c9..732f4b2 100644 --- a/Assets/Scripts/WorkerSpawner.cs +++ b/Assets/Scripts/WorkerSpawner.cs @@ -3,8 +3,16 @@ using UnityEngine; namespace Northbound { - public class WorkerSpawner : NetworkBehaviour, IInteractable + public class WorkerSpawner : NetworkBehaviour, IInteractable, IVisionProvider, ITeamMember { + [Header("Vision Settings")] + [Tooltip("워커 홀이 제공하는 시야 범위")] + public float visionRange = 10f; + + [Header("Team")] + [Tooltip("건물의 팀")] + public TeamType initialTeam = TeamType.Player; + [Header("Spawner Settings")] public GameObject workerPrefab; public Transform spawnPoint; @@ -34,11 +42,24 @@ namespace Northbound { base.OnNetworkSpawn(); _workerCount.OnValueChanged += OnWorkerCountChanged; + + if (IsServer) + { + // 시야 제공자로 등록 + FogOfWarSystem.Instance?.RegisterVisionProvider(this); + } } public override void OnNetworkDespawn() { _workerCount.OnValueChanged -= OnWorkerCountChanged; + + if (IsServer) + { + // 시야 제공자 등록 해제 + FogOfWarSystem.Instance?.UnregisterVisionProvider(this); + } + base.OnNetworkDespawn(); } @@ -240,9 +261,37 @@ namespace Northbound Vector3 spawnCenter = spawnPoint != null ? spawnPoint.position : transform.position; Gizmos.DrawWireSphere(spawnCenter, spawnRadius); - UnityEditor.Handles.Label(spawnCenter + Vector3.up * 2f, + UnityEditor.Handles.Label(spawnCenter + Vector3.up * 2f, $"Worker Spawner\nWorkers: {_workerCount.Value}/{maxWorkers}"); #endif } + + #region IVisionProvider Implementation + + public ulong GetOwnerId() => 0; // 워커 홀은 모든 플레이어에게 시야 제공 + + public float GetVisionRange() => visionRange; + + Transform IVisionProvider.GetTransform() => transform; + + public bool IsActive() => IsSpawned; + + TeamType IVisionProvider.GetTeam() => initialTeam; + + #endregion + + #region ITeamMember Implementation + + public TeamType GetTeam() => initialTeam; + + public bool IsDead() => false; // 워커 홀은 파괴되지 않음 + + public void SetTeam(TeamType team) + { + // 워커 홀의 팀은 변경할 수 없음 + Debug.LogWarning("[WorkerSpawner] 워커 홀의 팀은 변경할 수 없습니다."); + } + + #endregion } }