using System.Collections;
using System.Collections.Generic;
using PlatypusIdeas.AirPath.Runtime.Configuration;
using PlatypusIdeas.AirPath.Runtime.Core.Terrain;
using PlatypusIdeas.AirPath.Runtime.Events;
using UnityEngine;
namespace PlatypusIdeas.AirPath.Runtime.Visualization {
/// <summary>
/// Manages all path visualization including line renderer and grid cells
/// </summary>
public class PathVisualizationManager : MonoBehaviour {
[Header("Configuration")]
[SerializeField] private VisualizationConfiguration visualizationConfig;
[Header("Visualization Components")]
[SerializeField] private TerrainController terrainController;
[Header("Runtime Settings")]
[SerializeField] private bool debugMode = false;
// Visualization components
private LineRenderer _pathLineRenderer;
private List<Vector2Int> _coloredCells = new();
private Coroutine _pathAnimationCoroutine;
private bool _isValidated = false;
private void OnValidate() {
ValidateConfiguration();
}
private void Start() {
if (!ValidateConfiguration()) {
enabled = false;
return;
}
InitializeComponents();
SubscribeToEvents();
}
/// <summary>
/// Validates all required configurations and dependencies
/// </summary>
/// <returns>True if all validations pass, false otherwise</returns>
private bool ValidateConfiguration() {
bool isValid = true;
string errorPrefix = $"[{gameObject.name}] PathVisualizationManager:";
// Check required configuration
if (visualizationConfig == null) {
Debug.LogError($"{errorPrefix} VisualizationConfiguration must be assigned!", this);
isValid = false;
_isValidated = false;
return false; // Early return as nothing else can be validated
}
// Check terrain reference (warning only, some features will be limited)
if (terrainController == null) {
Debug.LogWarning($"{errorPrefix} TerrainController is not assigned. Cell coloring features will be disabled.", this);
}
// Validate visualization dependencies based on configuration settings
if (visualizationConfig.ShowPathLine) {
if (visualizationConfig.PathLineRendererPrefab == null) {
Debug.LogError($"{errorPrefix} PathLineRendererPrefab must be assigned in VisualizationConfiguration when ShowPathLine is enabled!", this);
isValid = false;
} else {
// Check if prefab has LineRenderer component
var lineRenderer = visualizationConfig.PathLineRendererPrefab.GetComponent<LineRenderer>();
if (lineRenderer == null) {
Debug.LogError($"{errorPrefix} PathLineRendererPrefab must have a LineRenderer component!", this);
isValid = false;
}
}
if (visualizationConfig.PathMaterial == null) {
Debug.LogWarning($"{errorPrefix} PathMaterial is not assigned. Line renderer will use default material.", this);
}
}
if (visualizationConfig.ShowPathCellColors && terrainController == null) {
Debug.LogWarning($"{errorPrefix} ShowPathCellColors is enabled but TerrainController is not assigned. This feature will be disabled.", this);
}
_isValidated = isValid;
return isValid;
}
/// <summary>
/// Checks if the component is properly configured and ready to use
/// </summary>
public bool IsConfigured => _isValidated && visualizationConfig != null;
/// <summary>
/// Checks if cell coloring features are available
/// </summary>
private bool CanUseTerrainFeatures => IsConfigured && terrainController != null;
private void OnDestroy() {
UnsubscribeFromEvents();
CleanupVisualization();
}
private void InitializeComponents() {
if (!IsConfigured) return;
if (visualizationConfig.ShowPathLine) {
SetupLineRenderer();
}
}
private void SubscribeToEvents() {
this.Subscribe<PathCalculatedEvent>(OnPathCalculated);
this.Subscribe<PathRequestedEvent>(OnPathRequested);
this.Subscribe<ConfigurationChangedEvent>(OnConfigurationChanged);
this.Subscribe<PathfindingModeChangedEvent>(OnModeChanged);
this.Subscribe<VisualizationUpdateEvent>(OnVisualizationUpdate);
}
private void UnsubscribeFromEvents() {
EventBusExtensions.Unsubscribe<PathCalculatedEvent>(OnPathCalculated);
EventBusExtensions.Unsubscribe<PathRequestedEvent>(OnPathRequested);
EventBusExtensions.Unsubscribe<ConfigurationChangedEvent>(OnConfigurationChanged);
EventBusExtensions.Unsubscribe<PathfindingModeChangedEvent>(OnModeChanged);
EventBusExtensions.Unsubscribe<VisualizationUpdateEvent>(OnVisualizationUpdate);
}
private void OnConfigurationChanged(ConfigurationChangedEvent evt) {
if (evt.Configuration is VisualizationConfiguration visConfig) {
// Only update if it's the same configuration instance we're using
if (visConfig == visualizationConfig) {
// Re-validate with new configuration
if (ValidateConfiguration()) {
UpdateVisualizationSettings();
}
}
}
}
private void OnModeChanged(PathfindingModeChangedEvent evt) {
// Clear previous visualization when mode changes
ClearPathVisualization();
}
private void OnPathRequested(PathRequestedEvent evt) {
if (!IsConfigured) return;
// Clear previous visualization
ClearPathVisualization();
// Visualize start/end cells if enabled and terrain is available
if (visualizationConfig.ShowPathCellColors && CanUseTerrainFeatures) {
SetCellColor(evt.Request.StartGridPos, visualizationConfig.StartCellColor);
SetCellColor(evt.Request.EndGridPos, visualizationConfig.EndCellColor);
}
}
private void OnPathCalculated(PathCalculatedEvent evt) {
if (!IsConfigured) return;
if (!evt.Success) {
Debug.Log("[PathVisualization] Path calculation failed, no visualization needed");
return;
}
// Visualize path line
if (visualizationConfig.ShowPathLine && evt.WorldPath != null) {
VisualizePath(evt.WorldPath, visualizationConfig.PathLineHeight);
}
// Animate path cells if enabled and terrain is available
if (visualizationConfig.ShowPathCellColors && CanUseTerrainFeatures && evt.WorldPath != null) {
if (_pathAnimationCoroutine != null) {
StopCoroutine(_pathAnimationCoroutine);
}
_pathAnimationCoroutine = StartCoroutine(AnimatePathCells(evt.WorldPath));
}
// Publish visualization complete event
EventBusExtensions.Publish(new VisualizationUpdateEvent(
VisualizationUpdateEvent.UpdateType.PathLineUpdated,
evt.WorldPath,
this
));
}
private void OnVisualizationUpdate(VisualizationUpdateEvent evt) {
switch (evt.Type) {
case VisualizationUpdateEvent.UpdateType.VisualizationCleared:
ClearPathVisualization();
break;
case VisualizationUpdateEvent.UpdateType.DebugVisualizationToggled:
debugMode = (bool)evt.Data;
break;
}
}
private void SetupLineRenderer() {
if (!IsConfigured || visualizationConfig.PathLineRendererPrefab == null) {
return; // Already validated, just safety check
}
var lineObj = Instantiate(visualizationConfig.PathLineRendererPrefab, transform);
lineObj.name = "PathLineRenderer";
_pathLineRenderer = lineObj.GetComponent<LineRenderer>();
ConfigureLineRenderer();
}
private void ConfigureLineRenderer() {
if (_pathLineRenderer == null || !IsConfigured) return;
if (visualizationConfig.PathMaterial != null) {
_pathLineRenderer.material = visualizationConfig.PathMaterial;
}
_pathLineRenderer.startColor = visualizationConfig.PathColor;
_pathLineRenderer.endColor = visualizationConfig.PathColor;
if (visualizationConfig.PathLineWidthCurve != null && visualizationConfig.PathLineWidthCurve.keys.Length > 0) {
_pathLineRenderer.widthCurve = visualizationConfig.PathLineWidthCurve;
} else {
_pathLineRenderer.startWidth = visualizationConfig.PathLineWidth;
_pathLineRenderer.endWidth = visualizationConfig.PathLineWidth * 0.25f;
}
_pathLineRenderer.positionCount = 0;
_pathLineRenderer.gameObject.SetActive(visualizationConfig.ShowPathLine);
}
private void VisualizePath(List<Vector3> worldPath, float heightOffset) {
if (_pathLineRenderer == null || worldPath == null || !IsConfigured || !visualizationConfig.ShowPathLine) return;
_pathLineRenderer.positionCount = worldPath.Count;
for (int i = 0; i < worldPath.Count; i++) {
var linePos = worldPath[i];
linePos.y += heightOffset;
_pathLineRenderer.SetPosition(i, linePos);
}
_pathLineRenderer.gameObject.SetActive(true);
}
private IEnumerator AnimatePathCells(List<Vector3> worldPath) {
if (!CanUseTerrainFeatures) yield break;
foreach (var worldPos in worldPath) {
var gridPos = WorldToGridPosition(worldPos);
SetCellColor(gridPos, visualizationConfig.PathColor);
yield return new WaitForSeconds(0.02f);
}
}
private void SetCellColor(Vector2Int gridPos, Color color) {
if (!CanUseTerrainFeatures) return;
terrainController.SetColor(gridPos, color);
if (!_coloredCells.Contains(gridPos)) {
_coloredCells.Add(gridPos);
}
}
private void ClearPathVisualization() {
// Clear colored cells
if (CanUseTerrainFeatures && Application.isPlaying) {
foreach (var cell in _coloredCells) {
try {
terrainController.ResetCellColor(cell);
} catch (MissingReferenceException) {
// Handle destroyed objects gracefully
}
}
}
_coloredCells.Clear();
// Clear path line
if (_pathLineRenderer != null) {
_pathLineRenderer.positionCount = 0;
_pathLineRenderer.gameObject.SetActive(false);
}
// Stop animation if running
if (_pathAnimationCoroutine != null) {
StopCoroutine(_pathAnimationCoroutine);
_pathAnimationCoroutine = null;
}
}
private void UpdateVisualizationSettings() {
if (!IsConfigured) return;
// Update line renderer
if (_pathLineRenderer != null) {
_pathLineRenderer.gameObject.SetActive(visualizationConfig.ShowPathLine);
ConfigureLineRenderer();
} else if (visualizationConfig.ShowPathLine && !_pathLineRenderer) {
SetupLineRenderer();
}
// Clear colors if disabled
if (!visualizationConfig.ShowPathCellColors && _coloredCells.Count > 0) {
ClearPathVisualization();
}
}
private Vector2Int WorldToGridPosition(Vector3 worldPos) {
if (!CanUseTerrainFeatures) return Vector2Int.zero;
var localPos = worldPos - terrainController.transform.position;
return new Vector2Int(
Mathf.FloorToInt(localPos.x / terrainController.CellSize),
Mathf.FloorToInt(localPos.z / terrainController.CellSize)
);
}
private Vector3 GridToWorldPosition(Vector2Int gridPos, float yOffset = 0) {
if (!CanUseTerrainFeatures) return Vector3.zero;
return terrainController.transform.position + new Vector3(
gridPos.x * terrainController.CellSize + terrainController.CellSize / 2f,
yOffset,
gridPos.y * terrainController.CellSize + terrainController.CellSize / 2f
);
}
private void CleanupVisualization() {
ClearPathVisualization();
if (_pathLineRenderer != null) {
if (Application.isPlaying) {
Destroy(_pathLineRenderer.gameObject);
} else {
DestroyImmediate(_pathLineRenderer.gameObject);
}
}
}
private void OnDrawGizmos() {
if (!debugMode || !IsConfigured || !visualizationConfig.ShowDebugGizmos) return;
// Draw debug visualization if needed
if (_pathLineRenderer != null && _pathLineRenderer.positionCount > 0) {
Gizmos.color = visualizationConfig.DebugPathColor;
for (int i = 0; i < _pathLineRenderer.positionCount - 1; i++) {
Gizmos.DrawLine(_pathLineRenderer.GetPosition(i), _pathLineRenderer.GetPosition(i + 1));
}
}
}
}
}