using Unity.Netcode; using UnityEngine; namespace Northbound { /// /// 전장의 안개 시각적 렌더링 (클라이언트) /// [RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))] public class FogOfWarRenderer : MonoBehaviour { [Header("Settings")] public Material fogMaterial; public float updateInterval = 0.1f; public float fogHeight = 5f; // 안개 높이 (지형보다 약간 위) [Header("Smoothing")] [Tooltip("Enable smooth circular vision edges")] public bool enableSmoothing = true; [Tooltip("Smoothing strength (higher = smoother but more blurry)")] [Range(1, 5)] public int smoothingPasses = 2; [Header("Colors")] public Color unexploredColor = new Color(0, 0, 0, 1f); // 완전히 어두움 public Color exploredColor = new Color(0, 0, 0, 0.6f); // 반투명 public Color visibleColor = new Color(0, 0, 0, 0f); // 투명 private Texture2D _fogTexture; private Color[] _colors; private MeshRenderer _meshRenderer; private float _updateTimer; private ulong _localClientId; private bool _isInitialized; private void Start() { // NetworkManager 준비 대기 if (NetworkManager.Singleton == null || !NetworkManager.Singleton.IsClient) { Debug.LogWarning("[FogOfWar] NetworkManager가 준비되지 않았습니다. 대기 중..."); return; } Initialize(); } private void Initialize() { if (_isInitialized) return; _localClientId = NetworkManager.Singleton.LocalClientId; var fogSystem = FogOfWarSystem.Instance; if (fogSystem == null) { Debug.LogError("[FogOfWar] FogOfWarSystem을 찾을 수 없습니다! 씬에 FogOfWarSystem GameObject가 있는지 확인하세요."); return; } // 텍스처 생성 (Bilinear filtering for smooth edges) _fogTexture = new Texture2D(fogSystem.gridWidth, fogSystem.gridHeight) { filterMode = FilterMode.Bilinear, wrapMode = TextureWrapMode.Clamp, anisoLevel = 0 // Disable anisotropic filtering for fog overlay }; _colors = new Color[fogSystem.gridWidth * fogSystem.gridHeight]; // 초기 색상을 미탐색 상태로 설정 for (int i = 0; i < _colors.Length; i++) { _colors[i] = unexploredColor; } _fogTexture.SetPixels(_colors); _fogTexture.Apply(); // 머티리얼 설정 _meshRenderer = GetComponent(); if (fogMaterial != null) { Material instanceMaterial = new Material(fogMaterial); instanceMaterial.mainTexture = _fogTexture; _meshRenderer.material = instanceMaterial; } else { Debug.LogWarning("[FogOfWar] Fog Material이 설정되지 않았습니다!"); } #if UNITY_EDITOR if (fogSystem.disableInEditor) { _meshRenderer.enabled = false; } #endif // 렌더링 레이어 설정 gameObject.layer = LayerMask.NameToLayer("UI"); // 또는 별도 레이어 생성 // 메쉬 생성 CreatePlaneMesh(fogSystem); _isInitialized = true; Debug.Log($"[FogOfWar] 렌더러 초기화 완료 (Client {_localClientId})"); } private void Update() { // NetworkManager가 준비되지 않았다면 초기화 시도 if (!_isInitialized && NetworkManager.Singleton != null && NetworkManager.Singleton.IsClient) { Initialize(); return; } if (!_isInitialized) return; _updateTimer += Time.deltaTime; if (_updateTimer >= updateInterval) { _updateTimer = 0f; UpdateFogTexture(); } } private void UpdateFogTexture() { var fogSystem = FogOfWarSystem.Instance; if (fogSystem == null) return; #if UNITY_EDITOR if (fogSystem.disableInEditor) { if (_meshRenderer != null) _meshRenderer.enabled = false; return; } #endif if (_meshRenderer != null) _meshRenderer.enabled = true; var fogData = fogSystem.GetPlayerFogData(_localClientId); for (int y = 0; y < fogSystem.gridHeight; y++) { for (int x = 0; x < fogSystem.gridWidth; x++) { FogOfWarState state = fogData.GetState(x, y); int index = y * fogSystem.gridWidth + x; _colors[index] = state switch { FogOfWarState.Visible => visibleColor, FogOfWarState.Explored => exploredColor, FogOfWarState.Unexplored => unexploredColor, _ => unexploredColor }; } } // Apply smoothing if enabled if (enableSmoothing) { SmoothFogTexture(fogSystem.gridWidth, fogSystem.gridHeight); } _fogTexture.SetPixels(_colors); _fogTexture.Apply(); } /// /// Apply box blur smoothing to create smooth circular vision edges /// private void SmoothFogTexture(int width, int height) { Color[] smoothed = new Color[_colors.Length]; for (int pass = 0; pass < smoothingPasses; pass++) { for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { int index = y * width + x; // Box blur: average with neighbors float r = 0, g = 0, b = 0, a = 0; int samples = 0; // Sample 3x3 kernel for (int dy = -1; dy <= 1; dy++) { for (int dx = -1; dx <= 1; dx++) { int nx = x + dx; int ny = y + dy; if (nx >= 0 && nx < width && ny >= 0 && ny < height) { int nIndex = ny * width + nx; Color c = _colors[nIndex]; r += c.r; g += c.g; b += c.b; a += c.a; samples++; } } } // Average smoothed[index] = new Color(r / samples, g / samples, b / samples, a / samples); } } // Copy smoothed back to colors for next pass System.Array.Copy(smoothed, _colors, _colors.Length); } } private void CreatePlaneMesh(FogOfWarSystem fogSystem) { MeshFilter meshFilter = GetComponent(); float width = fogSystem.gridWidth * fogSystem.cellSize; float height = fogSystem.gridHeight * fogSystem.cellSize; Mesh mesh = new Mesh { name = "FogOfWar Plane" }; mesh.vertices = new Vector3[] { new Vector3(0, fogHeight, 0), new Vector3(width, fogHeight, 0), new Vector3(0, fogHeight, height), new Vector3(width, fogHeight, height) }; mesh.triangles = new int[] { 0, 2, 1, 2, 3, 1 }; mesh.uv = new Vector2[] { new Vector2(0, 0), new Vector2(1, 0), new Vector2(0, 1), new Vector2(1, 1) }; mesh.RecalculateNormals(); meshFilter.mesh = mesh; transform.position = fogSystem.worldOrigin; Debug.Log($"[FogOfWar] Plane Mesh 생성 완료: {width}x{height} at {fogSystem.worldOrigin}"); } private void OnDestroy() { if (_fogTexture != null) { Destroy(_fogTexture); } } } }