191 lines
8.1 KiB
C#
191 lines
8.1 KiB
C#
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
|
|
{
|
|
/// <summary>
|
|
/// Visualizes <see cref="ProjectileObject"/> by displaying <see cref="AbstractTrajectory"/> and <see cref="AbstractAreaOfEffect"/>.<br/>
|
|
/// TODO: So far only first step of projectile is supported!
|
|
/// </summary>
|
|
[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;
|
|
|
|
/// <summary>
|
|
/// Initializes and starts projectile visualizing.
|
|
/// </summary>
|
|
/// <param name="caster">UnitObject who is casting this projectile</param>
|
|
/// <param name="projectileSteps">Definition how projectile will move and hit</param>
|
|
/// <param name="trajectoryMaterial">Material to draw Trajectory</param>
|
|
/// <param name="areaOfEffectMaterial">Material to draw AreaOfEffect</param>
|
|
/// <param name="transformEnd">Target position for projectile</param>
|
|
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>();
|
|
_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>();
|
|
_meshFilter.mesh = _projectileStep.areaOfEffect.GetMesh();
|
|
_meshRenderer = _visualizerGameObject.AddComponent<MeshRenderer>();
|
|
_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();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates whole <see cref="AbstractTrajectory"/>
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// If <see cref="ProjectileCollisionType"/> specified then try to detect collision on trajectory.
|
|
/// </summary>
|
|
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<UnitObject>() != _caster));
|
|
if (collisionIndex == - 1) collisionIndex = points.Length - 1;
|
|
|
|
_collisionOnPosition = points[collisionIndex];
|
|
return points.Take(collisionIndex + 1).ToArray();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates whole <see cref="AbstractAreaOfEffect"/>
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns start position for current step by defined <see cref="ProjectileStepStartFrom"/>.
|
|
/// </summary>
|
|
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)
|
|
};
|
|
}
|
|
}
|
|
} |