301 lines
10 KiB
C#
301 lines
10 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;
|
|
|
|
[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 (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();
|
|
|
|
// 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);
|
|
}
|
|
|
|
/// <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);
|
|
}
|
|
|
|
switch (fogState)
|
|
{
|
|
case FogOfWarState.Visible:
|
|
// Currently visible - show with original materials
|
|
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)
|
|
{
|
|
SetVisible(true);
|
|
SetExploredVisual(true);
|
|
}
|
|
else
|
|
{
|
|
SetVisible(false);
|
|
}
|
|
break;
|
|
|
|
case FogOfWarState.Unexplored:
|
|
// Never seen - hide unless tall enough to see from distance
|
|
if (isDistantVisible)
|
|
{
|
|
SetVisible(true);
|
|
SetExploredVisual(true); // Show as explored/distant
|
|
}
|
|
else
|
|
{
|
|
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;
|
|
|
|
return distanceToPlayer <= totalRange;
|
|
}
|
|
|
|
private void SetVisible(bool visible)
|
|
{
|
|
if (_isVisible == visible) return;
|
|
|
|
_isVisible = visible;
|
|
|
|
if (renderers == null || renderers.Length == 0)
|
|
return;
|
|
|
|
foreach (var renderer in renderers)
|
|
{
|
|
if (renderer != null)
|
|
{
|
|
renderer.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);
|
|
}
|
|
}
|
|
}
|