Files
VOIDRPG/Assets/90 - Scripts/Generic/Player/PathVisualizer.cs
T
2026-07-09 21:33:33 +02:00

251 lines
8.9 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using Sirenix.OdinInspector;
using UnityEngine;
using UnityEngine.Rendering;
using VOID.Generic.Dict;
using VOID.Generic.Objects.Abstract.Events;
using VOID.Generic.Objects.Unit;
using VOID.Generic.Objects.Unit.Events;
using VOID.Generic.Player.Input;
using VOID.Generic.Player.Util;
using VOID.UserInterface;
using VOID.Generic.Util;
namespace VOID.Generic.Player
{
[DefaultExecutionOrder(-1)]
public class PathVisualizer : MonoBehaviour
{
public static PathVisualizer current { get; private set; }
// Configuration
[SerializeField] private float redrawTimer = 0.1f;
[SerializeField] private float lineSize = 0.05f;
[SerializeField] private float verticalOffset = 0.1f;
[SerializeField, Required] private Material reachableMaterial;
[SerializeField, Required] private Material unreachableMaterial;
[SerializeField, Required] private Material dangerousMaterial;
[SerializeField, Required] private Material neutralMaterial;
// References
private UnitObject _activeUnit;
private readonly List<LineRenderer> _pathLineRenderers = new();
private bool _autoRedraw;
private float _lastRedrawTimer;
private void Awake()
{
current = this;
}
private void Start()
{
PlayerController.current.OnActiveUnitChange += OnActiveUnitChange;
PlayerInputManager.current.OnForceMoveStart += ShouldAutoRedraw;
PlayerInputManager.current.OnForceMoveEnd += ShouldAutoRedraw;
}
private void FixedUpdate()
{
if (_autoRedraw == false) return;
if (GameUI.current.isCursorOverUI)
{
HidePath();
return;
}
_lastRedrawTimer += Time.fixedDeltaTime;
if (_lastRedrawTimer < redrawTimer) return;
_lastRedrawTimer = 0f;
_activeUnit.unitNavAgent.CalculatePathTo(SelectUtil.SelectPosition());
UpdatePath(_activeUnit.unitNavAgent.GetCorrectedPath().ToArray());
}
private void ShouldAutoRedraw()
{
// No player's active unit!
if (!_activeUnit)
{
_autoRedraw = false;
return;
}
// Unit is moving on drawn path - don't draw another one
if (_activeUnit.unitState.isMoving)
{
_autoRedraw = false;
return;
}
// Is something forcing path drawing?
var isForcedMove = PlayerInputManager.current.isForceMove;
var isActiveUnitTurn = _activeUnit.basicState.turnManager && _activeUnit.basicState.turnManager.currentUnitTurn == _activeUnit;
if (!isForcedMove && !isActiveUnitTurn)
{
_autoRedraw = false;
HidePath();
return;
}
// Is unit casting ability?
if (_activeUnit.unitState.isCasting) {
_autoRedraw = false;
HidePath();
return;
}
_autoRedraw = true;
}
private void AddLineRenderers(int needed)
{
// Add missing LineRenderers to current poll
for (var i = 0; i < needed - _pathLineRenderers.Count; i++)
{
var lineRendererGameObject = new GameObject($"Path Renderer - part {_pathLineRenderers.Count+1}");
lineRendererGameObject.transform.parent = gameObject.transform;
var lineRenderer = lineRendererGameObject.AddComponent<LineRenderer>();
lineRenderer.enabled = false;
lineRenderer.startWidth = lineSize;
lineRenderer.endWidth = lineSize;
lineRenderer.shadowCastingMode = ShadowCastingMode.Off;
lineRenderer.numCapVertices = 3;
lineRenderer.numCornerVertices = 3;
_pathLineRenderers.Add(lineRenderer);
}
}
private void UpdateLineRenderers(List<PathType> pathTypes, List<Vector3[]> paths)
{
AddLineRenderers(paths.Count);
for (var i = 0; i < paths.Count; i++)
{
var lineRenderer = _pathLineRenderers[i];
var pathType = pathTypes[i];
var path = paths[i];
var material = pathType switch
{
PathType.Reachable => reachableMaterial,
PathType.UnReachable => unreachableMaterial,
PathType.Dangerous => dangerousMaterial,
PathType.Neutral => neutralMaterial,
_ => throw new ArgumentOutOfRangeException(nameof(pathType), pathType, null)
};
var offsetPath = path.Select(point => point + new Vector3(0, verticalOffset, 0)).ToArray();
lineRenderer.positionCount = path.Length;
lineRenderer.SetPositions(offsetPath);
lineRenderer.sharedMaterial = material;
lineRenderer.enabled = true;
}
}
private void OnActiveUnitChange(UnitObject unitObject)
{
if (_activeUnit)
{
_activeUnit.unitEvents.onMoveStart.RemoveListener(OnUnitMoveStart);
_activeUnit.unitEvents.onMoveEnd.RemoveListener(OnUnitMoveEnd);
_activeUnit.basicEvents.onTurnSelfStart.RemoveListener(OnTurnSelfStart);
_activeUnit.basicEvents.onTurnSelfEnd.RemoveListener(OnTurnSelfEnd);
_activeUnit.basicEvents.onTurnQueueEnter.RemoveListener(OnTurnQueueEnter);
_activeUnit.basicEvents.onTurnQueueLeave.RemoveListener(OnTurnQueueLeave);
_activeUnit.unitEvents.onStartCasting.RemoveListener(OnStartCasting);
_activeUnit.unitEvents.onEndCasting.RemoveListener(OnEndCasting);
}
HidePath();
ShouldAutoRedraw();
_activeUnit = unitObject;
_activeUnit.unitEvents.onMoveStart.AddListener(OnUnitMoveStart);
_activeUnit.unitEvents.onMoveEnd.AddListener(OnUnitMoveEnd);
_activeUnit.basicEvents.onTurnSelfStart.AddListener(OnTurnSelfStart);
_activeUnit.basicEvents.onTurnSelfEnd.AddListener(OnTurnSelfEnd);
_activeUnit.basicEvents.onTurnQueueEnter.AddListener(OnTurnQueueEnter);
_activeUnit.basicEvents.onTurnQueueLeave.AddListener(OnTurnQueueLeave);
_activeUnit.unitEvents.onStartCasting.AddListener(OnStartCasting);
_activeUnit.unitEvents.onEndCasting.AddListener(OnEndCasting);
}
private void OnUnitMoveStart(MoveEvent moveEvent)
{
_autoRedraw = false;
UpdatePath(moveEvent.pathCorrected.path.ToArray());
}
private void OnUnitMoveEnd(MoveEvent moveEvent)
{
HidePath();
ShouldAutoRedraw();
}
private void OnTurnSelfStart(TurnEvent turnEvent)
{
ShouldAutoRedraw();
}
private void OnTurnSelfEnd(TurnEvent turnEvent)
{
ShouldAutoRedraw();
}
private void OnTurnQueueEnter(TurnEvent turnEvent)
{
ShouldAutoRedraw();
}
private void OnTurnQueueLeave(TurnEvent turnEvent)
{
ShouldAutoRedraw();
}
private void OnStartCasting(CastingEvent castingEvent) {
ShouldAutoRedraw();
}
private void OnEndCasting(CastingEvent castingEvent) {
ShouldAutoRedraw();
}
private void UpdatePath(Vector3[] path)
{
HidePath();
Vector3[] reachablePath;
Vector3[] unreachablePath;
if (_activeUnit.basicState.turnManager)
{
// Inside combat - some part of path can be unreachable (not enough move points)
var movePoints = _activeUnit.unitData.currentResources.movePoints;
NavMeshPathUtil.SplitPath(path, movePoints, out reachablePath, out unreachablePath);
}
else
{
// Outside combat - whole path is reachable
reachablePath = path;
unreachablePath = null;
}
NavMeshPathUtil.SplitReachablePath(reachablePath, out var pathTypes, out var paths);
if (unreachablePath is not null)
{
pathTypes.Add(PathType.UnReachable);
paths.Add(unreachablePath);
}
UpdateLineRenderers(pathTypes, paths);
}
private void HidePath()
{
_pathLineRenderers.ForEach(lineRenderer => lineRenderer.enabled = false);
}
}
}