314 lines
10 KiB
C#
314 lines
10 KiB
C#
using Unity.Netcode;
|
|
using UnityEngine;
|
|
|
|
namespace Northbound
|
|
{
|
|
/// <summary>
|
|
/// 전장의 안개 시각적 렌더링 (클라이언트)
|
|
/// </summary>
|
|
[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 _localPlayerId;
|
|
private bool _isInitialized;
|
|
|
|
private void Start()
|
|
{
|
|
// NetworkManager 준비 대기
|
|
if (NetworkManager.Singleton == null || !NetworkManager.Singleton.IsClient)
|
|
{
|
|
return;
|
|
}
|
|
|
|
Initialize();
|
|
}
|
|
|
|
private void Initialize()
|
|
{
|
|
if (_isInitialized) return;
|
|
|
|
var fogSystem = FogOfWarSystem.Instance;
|
|
if (fogSystem == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// 로컬 플레이어의 실제 ID 가져오기 (없어도 텍스처는 생성)
|
|
_localPlayerId = GetLocalPlayerId();
|
|
|
|
// 텍스처 생성 (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<MeshRenderer>();
|
|
if (fogMaterial != null)
|
|
{
|
|
Material instanceMaterial = new Material(fogMaterial);
|
|
instanceMaterial.mainTexture = _fogTexture;
|
|
_meshRenderer.material = instanceMaterial;
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
if (fogSystem.disableInEditor)
|
|
{
|
|
_meshRenderer.enabled = false;
|
|
}
|
|
#endif
|
|
|
|
// 렌더링 레이어 설정
|
|
gameObject.layer = LayerMask.NameToLayer("UI"); // 또는 별도 레이어 생성
|
|
|
|
// 메쉬 생성
|
|
CreatePlaneMesh(fogSystem);
|
|
|
|
_isInitialized = true;
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
// NetworkManager가 준비되지 않았다면 초기화 시도
|
|
if (!_isInitialized && NetworkManager.Singleton != null && NetworkManager.Singleton.IsClient)
|
|
{
|
|
Initialize();
|
|
}
|
|
|
|
if (!_isInitialized) return;
|
|
|
|
// 로컬 플레이어 ID가 아직 없으면 다시 시도
|
|
if (_localPlayerId == ulong.MaxValue)
|
|
{
|
|
_localPlayerId = GetLocalPlayerId();
|
|
}
|
|
|
|
_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(_localPlayerId);
|
|
if (fogData == null)
|
|
{
|
|
// fogData가 null이면 아직 서버에서 데이터를 받지 못함
|
|
return;
|
|
}
|
|
|
|
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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// 로컬 플레이어의 실제 ID 가져오기
|
|
/// </summary>
|
|
private ulong GetLocalPlayerId()
|
|
{
|
|
if (NetworkManager.Singleton == null) return ulong.MaxValue;
|
|
|
|
// 방법 1: SpawnManager에서 찾기
|
|
NetworkObject localPlayer = null;
|
|
if (NetworkManager.Singleton.SpawnManager != 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<NetworkPlayerController>(FindObjectsSortMode.None);
|
|
foreach (var player in allPlayers)
|
|
{
|
|
if (player.IsLocalPlayer)
|
|
{
|
|
localPlayer = player.GetComponent<NetworkObject>();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (localPlayer == null) return ulong.MaxValue;
|
|
|
|
var playerController = localPlayer.GetComponent<NetworkPlayerController>();
|
|
if (playerController == null) return ulong.MaxValue;
|
|
|
|
return playerController.OwnerPlayerId;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Apply box blur smoothing to create smooth circular vision edges
|
|
/// </summary>
|
|
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<MeshFilter>();
|
|
|
|
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;
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
if (_fogTexture != null)
|
|
{
|
|
Destroy(_fogTexture);
|
|
}
|
|
}
|
|
}
|
|
} |