From 93d326e692dc1d7d55945d26aff8d54f268973a7 Mon Sep 17 00:00:00 2001 From: dal4segno Date: Wed, 25 Feb 2026 23:35:18 +0900 Subject: [PATCH] =?UTF-8?q?=ED=81=B4=EB=9D=BC=EC=9D=B4=EC=96=B8=ED=8A=B8?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EB=B0=9D=ED=98=80=EC=A7=80=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EC=9D=80=20=EA=B3=B3=EC=9D=98=20=EC=A0=81=EC=9D=B4=20?= =?UTF-8?q?=EB=B3=B4=EC=9D=B4=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Assets/Scripts/EnemyUnit.cs | 153 +++++++++++++++++++++++++++ Assets/Scripts/FogOfWarRenderer.cs | 41 ++++++- Assets/Scripts/FogOfWarSystem.cs | 33 ++++-- Assets/Scripts/FogOfWarVisibility.cs | 34 +++++- 4 files changed, 243 insertions(+), 18 deletions(-) diff --git a/Assets/Scripts/EnemyUnit.cs b/Assets/Scripts/EnemyUnit.cs index 749d3c2..33dff76 100644 --- a/Assets/Scripts/EnemyUnit.cs +++ b/Assets/Scripts/EnemyUnit.cs @@ -9,6 +9,12 @@ namespace Northbound [RequireComponent(typeof(Collider))] public class EnemyUnit : NetworkBehaviour, IDamageable, ITeamMember, IHealthProvider { + [Header("Fog of War Visibility")] + [Tooltip("전장의 안개에 의한 가시성 제어 활성화")] + public bool enableFogVisibility = true; + + [Tooltip("가시성 체크 주기 (초)")] + public float visibilityCheckInterval = 0.1f; [Header("Team Settings")] [Tooltip("이 유닛의 팀 (Hostile = 적대세력, Monster = 몬스터)")] public TeamType enemyTeam = TeamType.Hostile; @@ -54,10 +60,20 @@ namespace Northbound private UnitHealthBar _healthBar; + // 전장의 안개 가시성 + private Renderer[] _renderers; + private float _visibilityTimer; + private bool _lastVisibleState = true; + private bool _initializedVisibility = false; + private ulong _localPlayerId = ulong.MaxValue; + public override void OnNetworkSpawn() { base.OnNetworkSpawn(); + // 렌더러 캐시 (가시성 제어용) + _renderers = GetComponentsInChildren(); + if (IsServer) { _currentHealth.Value = maxHealth; @@ -72,6 +88,12 @@ namespace Northbound { CreateHealthBar(); } + + // 클라이언트에서는 기본적으로 렌더러 비활성화 + if (enableFogVisibility && IsClient) + { + SetRenderersEnabled(false); + } } public override void OnNetworkDespawn() @@ -80,6 +102,137 @@ namespace Northbound base.OnNetworkDespawn(); } + private void Update() + { + // 클라이언트에서만 가시성 체크 (호스트 포함) + if (enableFogVisibility && IsClient) + { + UpdateFogVisibility(); + } + } + + /// + /// 전장의 안개에 따른 가시성 업데이트 (클라이언트 전용) + /// + private void UpdateFogVisibility() + { + _visibilityTimer += Time.deltaTime; + if (_visibilityTimer < visibilityCheckInterval) return; + _visibilityTimer = 0f; + + // 로컬 플레이어 ID 캐시 + if (_localPlayerId == ulong.MaxValue) + { + _localPlayerId = GetLocalPlayerId(); + if (_localPlayerId == ulong.MaxValue) + { + // 로컬 플레이어를 찾지 못함 - 기본적으로 숨김 + if (_initializedVisibility) return; + _initializedVisibility = true; + SetRenderersEnabled(false); + return; + } + } + + // FogOfWarSystem이 없으면 가시성 체크 안함 + if (FogOfWarSystem.Instance == null) + { + if (_initializedVisibility) return; + _initializedVisibility = true; + SetRenderersEnabled(false); + return; + } + + // 현재 위치의 가시성 확인 + FogOfWarState state = FogOfWarSystem.Instance.GetVisibilityState(_localPlayerId, transform.position); + bool shouldBeVisible = (state == FogOfWarState.Visible); + + // 초기화되지 않은 경우 강제로 설정 + if (!_initializedVisibility) + { + _initializedVisibility = true; + _lastVisibleState = shouldBeVisible; + SetRenderersEnabled(shouldBeVisible); + return; + } + + // 상태가 변경된 경우에만 렌더러 업데이트 + if (shouldBeVisible != _lastVisibleState) + { + _lastVisibleState = shouldBeVisible; + SetRenderersEnabled(shouldBeVisible); + } + } + + /// + /// 로컬 플레이어의 실제 ID 가져오기 + /// + private ulong GetLocalPlayerId() + { + if (NetworkManager.Singleton == null) + { + return ulong.MaxValue; + } + + // 방법 1: SpawnManager에서 찾기 + var 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) + { + if (player.IsLocalPlayer) + { + localPlayer = player.GetComponent(); + break; + } + } + } + + if (localPlayer == null) + { + return ulong.MaxValue; + } + + var playerController = localPlayer.GetComponent(); + if (playerController == null) + { + return ulong.MaxValue; + } + + return playerController.OwnerPlayerId; + } + + /// + /// 모든 렌더러 활성화/비활성화 + /// + private void SetRenderersEnabled(bool enabled) + { + if (_renderers == null) return; + + foreach (var renderer in _renderers) + { + if (renderer != null) + { + renderer.enabled = enabled; + } + } + + // 체력바도 함께 처리 + if (_healthBar != null) + { + _healthBar.gameObject.SetActive(enabled); + } + } + private void OnHealthChanged(int previousValue, int newValue) { if (_healthBar != null) diff --git a/Assets/Scripts/FogOfWarRenderer.cs b/Assets/Scripts/FogOfWarRenderer.cs index 9aa4aa0..f360997 100644 --- a/Assets/Scripts/FogOfWarRenderer.cs +++ b/Assets/Scripts/FogOfWarRenderer.cs @@ -31,7 +31,7 @@ namespace Northbound private Color[] _colors; private MeshRenderer _meshRenderer; private float _updateTimer; - private ulong _localClientId; + private ulong _localPlayerId; private bool _isInitialized; private void Start() @@ -49,14 +49,15 @@ namespace Northbound { if (_isInitialized) return; - _localClientId = NetworkManager.Singleton.LocalClientId; - var fogSystem = FogOfWarSystem.Instance; if (fogSystem == null) { return; } + // 로컬 플레이어의 실제 ID 가져오기 (없어도 텍스처는 생성) + _localPlayerId = GetLocalPlayerId(); + // 텍스처 생성 (Bilinear filtering for smooth edges) _fogTexture = new Texture2D(fogSystem.gridWidth, fogSystem.gridHeight) { @@ -105,11 +106,16 @@ namespace Northbound if (!_isInitialized && NetworkManager.Singleton != null && NetworkManager.Singleton.IsClient) { Initialize(); - return; } if (!_isInitialized) return; + // 로컬 플레이어 ID가 아직 없으면 다시 시도 + if (_localPlayerId == ulong.MaxValue) + { + _localPlayerId = GetLocalPlayerId(); + } + _updateTimer += Time.deltaTime; if (_updateTimer >= updateInterval) { @@ -135,7 +141,12 @@ namespace Northbound if (_meshRenderer != null) _meshRenderer.enabled = true; - var fogData = fogSystem.GetPlayerFogData(_localClientId); + var fogData = fogSystem.GetPlayerFogData(_localPlayerId); + if (fogData == null) + { + // fogData가 null이면 아직 서버에서 데이터를 받지 못함 + return; + } for (int y = 0; y < fogSystem.gridHeight; y++) { @@ -164,6 +175,26 @@ namespace Northbound _fogTexture.Apply(); } + /// + /// 로컬 플레이어의 실제 ID 가져오기 + /// + private ulong GetLocalPlayerId() + { + if (NetworkManager.Singleton == null) return ulong.MaxValue; + + var localPlayer = NetworkManager.Singleton.SpawnManager.GetLocalPlayerObject(); + if (localPlayer != null) + { + var playerController = localPlayer.GetComponent(); + if (playerController != null) + { + return playerController.OwnerPlayerId; + } + } + + return ulong.MaxValue; + } + /// /// Apply box blur smoothing to create smooth circular vision edges /// diff --git a/Assets/Scripts/FogOfWarSystem.cs b/Assets/Scripts/FogOfWarSystem.cs index 37c1c6f..9ecef51 100644 --- a/Assets/Scripts/FogOfWarSystem.cs +++ b/Assets/Scripts/FogOfWarSystem.cs @@ -219,7 +219,7 @@ namespace Northbound [Header("Editor Settings")] [Tooltip("Disable fog of war in Unity Editor for easier development")] - public bool disableInEditor = true; + public bool disableInEditor = false; // 서버: 각 플레이어별 가시성 맵 private Dictionary _serverFogData = new Dictionary(); @@ -250,11 +250,18 @@ namespace Northbound { // Server: Register client connected callback to initialize fog data NetworkManager.Singleton.OnClientConnectedCallback += OnClientConnected; + + // 호스트 자신의 데이터도 초기화 (OnClientConnected가 호스트에게는 호출되지 않을 수 있음) + ulong hostClientId = NetworkManager.Singleton.LocalClientId; + if (!_serverFogData.ContainsKey(hostClientId)) + { + _serverFogData[hostClientId] = new FogOfWarData(gridWidth, gridHeight); + } } - if (IsClient && !IsServer) + // 클라이언트는 로컬 데이터 초기화 (호스트 포함) + if (IsClient) { - // 클라이언트는 로컬 데이터 초기화 _localFogData = new FogOfWarData(gridWidth, gridHeight); } } @@ -262,7 +269,7 @@ namespace Northbound private void OnClientConnected(ulong clientId) { if (!IsServer) return; - + // Ensure fog data exists for this client if (!_serverFogData.ContainsKey(clientId)) { @@ -317,13 +324,18 @@ namespace Northbound /// public FogOfWarData GetPlayerFogData(ulong clientId) { - // 클라이언트는 자신의 로컬 데이터 반환 - if (IsClient && !IsServer) + // 클라이언트(호스트 포함)는 자신의 로컬 데이터 반환 + // 서버에서 계산된 시야 데이터는 ClientRpc로 동기화됨 + if (IsClient) { + if (_localFogData == null) + { + _localFogData = new FogOfWarData(gridWidth, gridHeight); + } return _localFogData; } - // 서버는 해당 클라이언트의 데이터 반환 + // 순수 서버(전용 서버)의 경우 서버 데이터 반환 if (!_serverFogData.ContainsKey(clientId)) { _serverFogData[clientId] = new FogOfWarData(gridWidth, gridHeight); @@ -369,8 +381,11 @@ namespace Northbound FogOfWarData fogData = kvp.Value; // 해당 클라이언트가 여전히 연결되어 있는지 확인 - if (NetworkManager.Singleton == null || - !NetworkManager.Singleton.ConnectedClients.ContainsKey(clientId)) + // 호스트의 경우 ConnectedClients에 없을 수 있으므로 별도 체크 + bool isHost = (clientId == NetworkManager.Singleton.LocalClientId); + bool isConnected = NetworkManager.Singleton.ConnectedClients.ContainsKey(clientId); + + if (!isHost && !isConnected) { continue; } diff --git a/Assets/Scripts/FogOfWarVisibility.cs b/Assets/Scripts/FogOfWarVisibility.cs index e1e8298..ac2ac73 100644 --- a/Assets/Scripts/FogOfWarVisibility.cs +++ b/Assets/Scripts/FogOfWarVisibility.cs @@ -39,7 +39,7 @@ namespace Northbound private Material[] _originalMaterials; private bool _isVisible = false; private float _updateTimer; - private ulong _localClientId; + private ulong _localPlayerId; private bool _isInitialized = false; private float _objectHeight = 0f; @@ -130,7 +130,13 @@ namespace Northbound { if (NetworkManager.Singleton != null && NetworkManager.Singleton.IsClient) { - _localClientId = NetworkManager.Singleton.LocalClientId; + _localPlayerId = GetLocalPlayerId(); + if (_localPlayerId == ulong.MaxValue) + { + // Local player not found yet - stay hidden + SetVisible(false); + return; + } _isInitialized = true; // Force immediate visibility update on initialization @@ -152,6 +158,26 @@ namespace Northbound } } + /// + /// 로컬 플레이어의 실제 ID 가져오기 + /// + private ulong GetLocalPlayerId() + { + if (NetworkManager.Singleton == null) return ulong.MaxValue; + + var localPlayer = NetworkManager.Singleton.SpawnManager.GetLocalPlayerObject(); + if (localPlayer != null) + { + var playerController = localPlayer.GetComponent(); + if (playerController != null) + { + return playerController.OwnerPlayerId; + } + } + + return ulong.MaxValue; + } + private void UpdateVisibility() { var fogSystem = FogOfWarSystem.Instance; @@ -163,7 +189,7 @@ namespace Northbound } // Wait for fog data to be initialized - var fogData = fogSystem.GetPlayerFogData(_localClientId); + var fogData = fogSystem.GetPlayerFogData(_localPlayerId); if (fogData == null) { // Fog data not ready yet - stay hidden @@ -171,7 +197,7 @@ namespace Northbound return; } - FogOfWarState fogState = fogSystem.GetVisibilityState(_localClientId, transform.position); + FogOfWarState fogState = fogSystem.GetVisibilityState(_localPlayerId, transform.position); // Check for distant visibility based on height bool isDistantVisible = false;