Files
Northbound/Assets/Scripts/FogOfWarVisibility.cs
dal4segno a9a744589d 전장의 안개 기능 개선
미탐험 구역의 모든 오브젝트는 보이지 않음
적이 시야를 제공하는 문제 수정
높은 장애물은 더 먼 거리에서부터 보일 수 있음
2026-01-30 16:04:22 +09:00

350 lines
13 KiB
C#

using Unity.Netcode;
using UnityEngine;
namespace Northbound
{
/// <summary>
/// Controls object visibility based on fog of war state
/// Attach to buildings, obstacles, enemies, or any object that should be hidden in fog
/// </summary>
public class FogOfWarVisibility : MonoBehaviour
{
[Header("Visibility Settings")]
[Tooltip("Show this object in explored areas (greyed out) or only when visible")]
public bool showInExploredAreas = false;
[Tooltip("Update frequency for checking fog state (seconds)")]
public float updateInterval = 0.2f;
[Tooltip("Renderers to show/hide (auto-detected if empty)")]
public Renderer[] renderers;
[Tooltip("Enable debug logging for this object")]
public bool debugLogging = false;
[Header("Height-Based Distant Visibility")]
[Tooltip("Enable visibility from farther away based on object height")]
public bool enableDistantVisibility = true;
[Tooltip("Visibility range multiplier per unit of height (default: 2x vision per 1m height)")]
public float heightVisibilityMultiplier = 2.0f;
[Tooltip("Minimum height to get extended visibility (meters)")]
public float minHeightForDistantVisibility = 3.0f;
[Header("Explored State Visual (Optional)")]
[Tooltip("Apply grey/desaturated material when in explored state")]
public bool useExploredMaterial = false;
[Tooltip("Material to use in explored state (optional)")]
public Material exploredMaterial;
private Material[] _originalMaterials;
private bool _isVisible = false;
private float _updateTimer;
private ulong _localClientId;
private bool _isInitialized = false;
private float _objectHeight = 0f;
private void Start()
{
// Auto-detect renderers if not set
if (renderers == null || renderers.Length == 0)
{
renderers = GetComponentsInChildren<Renderer>();
if (debugLogging)
{
Debug.Log($"[FogOfWarVisibility] {gameObject.name}: Auto-detected {renderers.Length} renderers");
}
}
if (renderers == null || renderers.Length == 0)
{
Debug.LogWarning($"[FogOfWarVisibility] {gameObject.name}: No renderers found! Component will not work.");
return;
}
// Store original materials for explored state
if (useExploredMaterial && renderers != null && renderers.Length > 0)
{
_originalMaterials = new Material[renderers.Length];
for (int i = 0; i < renderers.Length; i++)
{
if (renderers[i] != null)
{
_originalMaterials[i] = renderers[i].sharedMaterial;
}
}
}
// Calculate object height for distant visibility
_objectHeight = CalculateObjectHeight();
if (debugLogging)
{
Debug.Log($"[FogOfWarVisibility] {gameObject.name}: Object height = {_objectHeight}m");
}
// CRITICAL: Start hidden and stay hidden until fog system confirms visibility
if (debugLogging)
{
Debug.Log($"[FogOfWarVisibility] {gameObject.name}: START - Setting all {renderers.Length} renderers to HIDDEN");
}
// 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);
}
/// <summary>
/// Calculate the height of this object for distant visibility
/// </summary>
private float CalculateObjectHeight()
{
// Try to get height from Building component
var building = GetComponent<Building>();
if (building != null && building.buildingData != null)
{
return building.buildingData.height;
}
// Fallback: Use renderer bounds
if (renderers != null && renderers.Length > 0)
{
float maxHeight = 0f;
foreach (var renderer in renderers)
{
if (renderer != null)
{
maxHeight = Mathf.Max(maxHeight, renderer.bounds.size.y);
}
}
return maxHeight;
}
// Last resort: Use collider bounds
var collider = GetComponent<Collider>();
if (collider != null)
{
return collider.bounds.size.y;
}
return 0f;
}
private void Update()
{
// Only run on clients, not on dedicated server
if (NetworkManager.Singleton != null && NetworkManager.Singleton.IsServer && !NetworkManager.Singleton.IsClient)
{
// Dedicated server - don't process visibility
return;
}
// Initialize when network is ready
if (!_isInitialized)
{
if (NetworkManager.Singleton != null && NetworkManager.Singleton.IsClient)
{
_localClientId = NetworkManager.Singleton.LocalClientId;
_isInitialized = true;
// Force immediate visibility update on initialization
UpdateVisibility();
}
else
{
// Network not ready - stay hidden
SetVisible(false);
return;
}
}
_updateTimer += Time.deltaTime;
if (_updateTimer >= updateInterval)
{
_updateTimer = 0f;
UpdateVisibility();
}
}
private void UpdateVisibility()
{
var fogSystem = FogOfWarSystem.Instance;
if (fogSystem == null)
{
// No fog system - stay hidden for safety
SetVisible(false);
return;
}
// Wait for fog data to be initialized
var fogData = fogSystem.GetPlayerFogData(_localClientId);
if (fogData == null)
{
// Fog data not ready yet - stay hidden
SetVisible(false);
return;
}
FogOfWarState fogState = fogSystem.GetVisibilityState(_localClientId, transform.position);
// Check for distant visibility based on height
bool isDistantVisible = false;
if (enableDistantVisibility && _objectHeight >= minHeightForDistantVisibility)
{
isDistantVisible = CheckDistantVisibility(fogSystem);
}
if (debugLogging)
{
Debug.Log($"[FogOfWarVisibility] {gameObject.name} at {transform.position}: State={fogState}, DistantVisible={isDistantVisible}, Height={_objectHeight}m");
}
switch (fogState)
{
case FogOfWarState.Visible:
// Currently visible - show with original materials
if (debugLogging) Debug.Log($"[FogOfWarVisibility] {gameObject.name}: Setting VISIBLE");
SetVisible(true);
SetExploredVisual(false);
break;
case FogOfWarState.Explored:
// Previously seen but not currently visible
// BUT: Tall objects can be seen from farther away
if (showInExploredAreas || isDistantVisible)
{
if (debugLogging) Debug.Log($"[FogOfWarVisibility] {gameObject.name}: Setting EXPLORED (visible) - distantVisible={isDistantVisible}");
SetVisible(true);
SetExploredVisual(true);
}
else
{
if (debugLogging) Debug.Log($"[FogOfWarVisibility] {gameObject.name}: Setting EXPLORED (hidden)");
SetVisible(false);
}
break;
case FogOfWarState.Unexplored:
// Never seen - hide unless tall enough to see from distance
if (isDistantVisible)
{
if (debugLogging) Debug.Log($"[FogOfWarVisibility] {gameObject.name}: Setting UNEXPLORED but DISTANT VISIBLE");
SetVisible(true);
SetExploredVisual(true); // Show as explored/distant
}
else
{
if (debugLogging) Debug.Log($"[FogOfWarVisibility] {gameObject.name}: Setting UNEXPLORED (hidden)");
SetVisible(false);
}
break;
default:
// Unknown state - hide completely
SetVisible(false);
break;
}
}
/// <summary>
/// Check if this object should be visible from a distance based on its height
/// </summary>
private bool CheckDistantVisibility(FogOfWarSystem fogSystem)
{
// Find the closest vision provider (player)
if (NetworkManager.Singleton == null || !NetworkManager.Singleton.IsClient)
return false;
// Get local player object
var localPlayer = NetworkManager.Singleton.LocalClient?.PlayerObject;
if (localPlayer == null)
return false;
// Calculate distance to player
float distanceToPlayer = Vector3.Distance(transform.position, localPlayer.transform.position);
// 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;
// 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;
if (debugLogging)
{
Debug.Log($"[FogOfWarVisibility] {gameObject.name}: Distance={distanceToPlayer:F1}m, ExtendedRange={totalRange:F1}m (height bonus: +{extendedRange:F1}m)");
}
return distanceToPlayer <= totalRange;
}
private void SetVisible(bool visible)
{
if (_isVisible == visible) return;
_isVisible = visible;
if (renderers == null || renderers.Length == 0)
{
if (debugLogging)
{
Debug.LogWarning($"[FogOfWarVisibility] {gameObject.name}: SetVisible({visible}) called but no renderers!");
}
return;
}
if (debugLogging)
{
Debug.Log($"[FogOfWarVisibility] {gameObject.name}: SetVisible({visible}) - updating {renderers.Length} renderers");
}
foreach (var renderer in renderers)
{
if (renderer != null)
{
renderer.enabled = visible;
if (debugLogging)
{
Debug.Log($"[FogOfWarVisibility] {gameObject.name}: Renderer '{renderer.name}' enabled = {visible}");
}
}
}
}
private void SetExploredVisual(bool explored)
{
if (!useExploredMaterial || renderers == null || exploredMaterial == null)
return;
for (int i = 0; i < renderers.Length; i++)
{
if (renderers[i] != null)
{
if (explored)
{
renderers[i].material = exploredMaterial;
}
else if (_originalMaterials != null && i < _originalMaterials.Length)
{
renderers[i].material = _originalMaterials[i];
}
}
}
}
private void OnDrawGizmosSelected()
{
// Visualize fog state check position
Gizmos.color = _isVisible ? Color.green : Color.red;
Gizmos.DrawWireSphere(transform.position, 0.5f);
}
}
}