using System; using System.Linq; using UnityEngine; using UnityEngine.Rendering; using VOID.Generic.AreaOfEffect; using VOID.Generic.Objects.Unit; using VOID.Generic.Projectile; using VOID.Generic.Trajectory; namespace VOID.Generic.Player { /// /// Visualizes by displaying and .
/// TODO: So far only first step of projectile is supported! ///
[DefaultExecutionOrder(-1)] public class ProjectileVisualizer : MonoBehaviour { private UnitObject _caster; private ProjectileStep _projectileStep; private AbstractTrajectory _trajectory; private Transform _transformStart; private Transform _transformEnd; private Vector3 _positionStart; private Vector3 _positionEnd; private Vector3 _lastPositionStart; private Vector3 _lastPositionEnd; private Vector3 _collisionOnPosition; private LineRenderer _lineRenderer; private MeshFilter _meshFilter; private MeshRenderer _meshRenderer; private GameObject _visualizerGameObject; private void Awake() => enabled = false; /// /// Initializes and starts projectile visualizing. /// /// UnitObject who is casting this projectile /// Definition how projectile will move and hit /// Material to draw Trajectory /// Material to draw AreaOfEffect /// Target position for projectile public void Init( UnitObject caster, ProjectileStep[] projectileSteps, Material trajectoryMaterial, Material areaOfEffectMaterial, Transform transformEnd ) { // TODO: So far only first step of projectile is supported! _projectileStep = projectileSteps.FirstOrDefault(); if (_projectileStep == default) return; _caster = caster; _trajectory = _projectileStep.trajectory.Clone(); _transformStart = FindStartTransform(_projectileStep); _transformEnd = transformEnd; // GameObject containing meshes of Trajectory and AreaOfEffect _visualizerGameObject = new GameObject("Projectile Visualizer"); _visualizerGameObject.transform.parent = gameObject.transform; _visualizerGameObject.transform.rotation = _projectileStep.areaOfEffect.GetRotation(_transformStart.position, _transformEnd.position); // Trajectory visualizing if (_trajectory is not InstantTrajectory) { _lineRenderer = _visualizerGameObject.AddComponent(); _lineRenderer.enabled = false; _lineRenderer.startWidth = _projectileStep.radius; _lineRenderer.endWidth = _projectileStep.radius; _lineRenderer.shadowCastingMode = ShadowCastingMode.Off; _lineRenderer.sharedMaterial = trajectoryMaterial; } // AreaOfEffect visualizing if (_projectileStep.areaOfEffect is not NoneAreaOfEffect) { _meshFilter = _visualizerGameObject.AddComponent(); _meshFilter.mesh = _projectileStep.areaOfEffect.GetMesh(); _meshRenderer = _visualizerGameObject.AddComponent(); _meshRenderer.enabled = false; _meshRenderer.shadowCastingMode = ShadowCastingMode.Off; _meshRenderer.sharedMaterial = areaOfEffectMaterial; } enabled = true; } private void FixedUpdate() { _positionStart = _transformStart.position; _positionEnd = _transformEnd.position; // If positions didn't change then there is no need to recalculate whole Trajectory and AreaOfEffect if (_positionStart == _lastPositionStart && _positionEnd == _lastPositionEnd) return; _lastPositionStart = _positionStart; _lastPositionEnd = _positionEnd; UpdateTrajectory(); UpdateAreaOfEffect(); } /// /// Updates whole /// private void UpdateTrajectory() { // Its instant, that means there is no trajectory if (_trajectory is InstantTrajectory) return; _trajectory.Init(_positionStart, _positionEnd); var pointsNeeded = Mathf.FloorToInt(_trajectory.trajectoryDistance / _projectileStep.speed / Time.fixedDeltaTime); var points = _trajectory.GetPositions(pointsNeeded); // Predict collision on trajectory points = GetPointsToFirstCollision(points); _lineRenderer.positionCount = points.Length; _lineRenderer.SetPositions(points); _lineRenderer.enabled = true; } /// /// If specified then try to detect collision on trajectory. /// private Vector3[] GetPointsToFirstCollision(Vector3[] points) { int layerMask; switch (_projectileStep.collisionType) { case ProjectileCollisionType.StaticOrDynamicCollision: layerMask = LayerManager.Static | LayerManager.StaticHideable | LayerManager.Dynamic; break; case ProjectileCollisionType.StaticCollision: layerMask = LayerManager.Static | LayerManager.StaticHideable; break; default: return points; } // To prevent hitting caster and ground under caster ignore everything in caster's radius var skip = 0; if (_projectileStep.startFrom is ProjectileStepStartFrom.FromCaster) skip = Array.FindIndex(points, point => Vector3.Distance(point, _positionStart) >= _caster.basicData.radius); if (skip == - 1) skip = 0; // Check if projectile will hit anything on its trajeactory (also prevent hitting caster) var collisionIndex = Array.FindIndex(points, skip, point => Physics.OverlapSphere(point, _projectileStep.radius, layerMask) .Any(collider => collider.GetComponentInParent() != _caster)); if (collisionIndex == - 1) collisionIndex = points.Length - 1; _collisionOnPosition = points[collisionIndex]; return points.Take(collisionIndex + 1).ToArray(); } /// /// Updates whole /// private void UpdateAreaOfEffect() { if (_projectileStep.areaOfEffect is NoneAreaOfEffect) return; _visualizerGameObject.transform.position = _collisionOnPosition != default ? _collisionOnPosition : _positionEnd; _visualizerGameObject.transform.rotation = _projectileStep.areaOfEffect.GetRotation(_positionStart, _positionEnd); _meshRenderer.enabled = true; } /// /// Returns start position for current step by defined . /// private Transform FindStartTransform(ProjectileStep step) { return step.startFrom switch { ProjectileStepStartFrom.FromStart => _caster.unitData.castPosition, ProjectileStepStartFrom.FromCaster => _caster.transform, ProjectileStepStartFrom.FromHit => _caster.unitData.castPosition, _ => throw new ArgumentOutOfRangeException( nameof(step.startFrom), step.startFrom, null) }; } } }