Created
September 8, 2022 12:21
-
-
Save aleverdes/9f685d05d49528d7bcc4ce10ab602841 to your computer and use it in GitHub Desktop.
Script for drawing an arc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using UnityEngine; | |
namespace UnityOpenXR | |
{ | |
public class Arc : MonoBehaviour | |
{ | |
public int SegmentCount = 60; | |
public float Thickness = 0.01f; | |
[Tooltip("The amount of time in seconds to predict the motion of the projectile.")] | |
public float ArcDuration = 3.0f; | |
[Tooltip("The amount of time in seconds between each segment of the projectile.")] | |
public float SegmentBreak = 0.025f; | |
[Tooltip("The speed at which the line segments of the arc move.")] | |
public float ArcSpeed = 0.2f; | |
public Material Material; | |
public LayerMask TraceLayerMask; | |
public bool NonTeleportAreaUnderArc { get; private set; } | |
public Vector3 PlayerTeleportPositionCandidate { get; private set; } | |
public Vector3 PlayerTeleportNormalCandidate { get; private set; } | |
//Private data | |
private Material _materialRay; | |
private LineRenderer[] lineRenderers; | |
private float arcTimeOffset = 0.0f; | |
private float prevThickness = 0.0f; | |
private int prevSegmentCount = 0; | |
private bool showArc = true; | |
private Vector3 startPos; | |
private Vector3 projectileVelocity; | |
private bool useGravity = true; | |
private Transform arcObjectsTransfrom; | |
private bool drawOnlyFirstSegmentOfArc = false; | |
private bool arcInvalid = false; | |
private float scale = 1.5f; | |
private static readonly int ColorShader = Shader.PropertyToID("_Color"); | |
private Color _defaultColor = Color.cyan; | |
private Color _validColor = new Color(0f, 1f, 0.34f, 0.45f); | |
private Color _invalidColor = new Color(1f, 0f, 0.36f, 0.45f); | |
//------------------------------------------------- | |
void Start() | |
{ | |
arcTimeOffset = Time.time; | |
_materialRay = new Material(Material); | |
_materialRay.SetColor(ColorShader, _defaultColor); | |
_materialRay.renderQueue = 3001; | |
} | |
//------------------------------------------------- | |
void Update() | |
{ | |
//scale arc to match player scale | |
//scale = transform.lossyScale.x; | |
if (Math.Abs(Thickness - prevThickness) > Mathf.Epsilon || SegmentCount != prevSegmentCount) | |
{ | |
CreateLineRendererObjects(); | |
prevThickness = Thickness; | |
prevSegmentCount = SegmentCount; | |
} | |
} | |
//------------------------------------------------- | |
private void CreateLineRendererObjects() | |
{ | |
//Destroy any existing line renderer objects | |
if (arcObjectsTransfrom != null) | |
{ | |
Destroy(arcObjectsTransfrom.gameObject); | |
} | |
GameObject arcObjectsParent = new GameObject("ArcObjects"); | |
arcObjectsTransfrom = arcObjectsParent.transform; | |
arcObjectsTransfrom.SetParent(this.transform); | |
//Create new line renderer objects | |
lineRenderers = new LineRenderer[SegmentCount]; | |
for (int i = 0; i < SegmentCount; ++i) | |
{ | |
GameObject newObject = new GameObject("LineRenderer_" + i); | |
newObject.transform.SetParent(arcObjectsTransfrom); | |
lineRenderers[i] = newObject.AddComponent<LineRenderer>(); | |
lineRenderers[i].receiveShadows = false; | |
lineRenderers[i].reflectionProbeUsage = UnityEngine.Rendering.ReflectionProbeUsage.Off; | |
lineRenderers[i].lightProbeUsage = UnityEngine.Rendering.LightProbeUsage.Off; | |
lineRenderers[i].shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; | |
lineRenderers[i].material = _materialRay; | |
#if (UNITY_5_4) | |
lineRenderers[i].SetWidth(thickness, thickness); | |
#else | |
lineRenderers[i].startWidth = Thickness * scale; | |
lineRenderers[i].endWidth = Thickness * scale; | |
#endif | |
lineRenderers[i].enabled = false; | |
} | |
} | |
//------------------------------------------------- | |
public void SetArcData( | |
Vector3 position, | |
Vector3 velocity, | |
bool gravity, | |
bool pointerAtBadAngle, | |
bool badTeleport) | |
{ | |
startPos = position; | |
projectileVelocity = velocity; | |
useGravity = gravity; | |
if (drawOnlyFirstSegmentOfArc && !pointerAtBadAngle) | |
{ | |
arcTimeOffset = Time.time; | |
} | |
drawOnlyFirstSegmentOfArc = pointerAtBadAngle; | |
arcInvalid = pointerAtBadAngle || badTeleport; | |
} | |
//------------------------------------------------- | |
public void Show() | |
{ | |
showArc = true; | |
if (lineRenderers == null) | |
{ | |
CreateLineRendererObjects(); | |
} | |
} | |
//------------------------------------------------- | |
public void Hide() | |
{ | |
//Hide the line segments if they were previously being shown | |
if (showArc) | |
{ | |
HideLineSegments(0, SegmentCount); | |
} | |
showArc = false; | |
} | |
//------------------------------------------------- | |
// Draws each segment of the arc individually | |
//------------------------------------------------- | |
public bool DrawArc(out RaycastHit hitInfo) | |
{ | |
float timeStep = ArcDuration / SegmentCount; | |
float currentTimeOffset = (Time.time - arcTimeOffset) * ArcSpeed; | |
//Reset the arc time offset when it has gone beyond a segment length | |
if (currentTimeOffset > (timeStep + SegmentBreak)) | |
{ | |
arcTimeOffset = Time.time; | |
currentTimeOffset = 0.0f; | |
} | |
float segmentStartTime = currentTimeOffset; | |
float arcHitTime = FindProjectileCollision(out hitInfo); | |
var raycastNothing = arcHitTime == float.MaxValue; | |
if (raycastNothing) | |
{ | |
_materialRay.SetColor(ColorShader, _invalidColor); | |
NonTeleportAreaUnderArc = true; | |
} | |
if (drawOnlyFirstSegmentOfArc) | |
{ | |
//Only draw first segment | |
lineRenderers[0].enabled = true; | |
lineRenderers[0].SetPosition(0, GetArcPositionAtTime(0.0f)); | |
lineRenderers[0].SetPosition(1, GetArcPositionAtTime(arcHitTime < timeStep ? arcHitTime : timeStep)); | |
HideLineSegments(1, SegmentCount); | |
} | |
else | |
{ | |
//Draw the first segment outside the loop if needed | |
int loopStartSegment = 0; | |
if (segmentStartTime > SegmentBreak) | |
{ | |
float firstSegmentEndTime = currentTimeOffset - SegmentBreak; | |
if (arcHitTime < firstSegmentEndTime) | |
{ | |
firstSegmentEndTime = arcHitTime; | |
} | |
DrawArcSegment(0, 0.0f, firstSegmentEndTime); | |
loopStartSegment = 1; | |
} | |
bool stopArc = false; | |
int currentSegment = 0; | |
if (segmentStartTime < arcHitTime) | |
{ | |
for (currentSegment = loopStartSegment; currentSegment < SegmentCount; ++currentSegment) | |
{ | |
//Clamp the segment end time to the arc duration | |
float segmentEndTime = segmentStartTime + timeStep; | |
if (segmentEndTime >= ArcDuration) | |
{ | |
segmentEndTime = ArcDuration; | |
stopArc = true; | |
} | |
if (segmentEndTime >= arcHitTime) | |
{ | |
segmentEndTime = arcHitTime; | |
stopArc = true; | |
} | |
DrawArcSegment(currentSegment, segmentStartTime, segmentEndTime); | |
segmentStartTime += timeStep + SegmentBreak; | |
//If the previous end time or the next start time is beyond the duration then stop the arc | |
if (stopArc || segmentStartTime >= ArcDuration || segmentStartTime >= arcHitTime) | |
{ | |
break; | |
} | |
} | |
} | |
else | |
{ | |
currentSegment--; | |
} | |
//Hide the rest of the line segments | |
HideLineSegments(currentSegment + 1, SegmentCount); | |
} | |
return !raycastNothing; | |
} | |
//------------------------------------------------- | |
private void DrawArcSegment(int index, float startTime, float endTime) | |
{ | |
lineRenderers[index].enabled = true; | |
lineRenderers[index].SetPosition(0, GetArcPositionAtTime(startTime)); | |
lineRenderers[index].SetPosition(1, GetArcPositionAtTime(endTime)); | |
} | |
//------------------------------------------------- | |
public void SetColor(Color color) | |
{ | |
for (int i = 0; i < SegmentCount; ++i) | |
{ | |
#if (UNITY_5_4) | |
lineRenderers[i].SetColors(color, color); | |
#else | |
lineRenderers[i].startColor = color; | |
lineRenderers[i].endColor = color; | |
#endif | |
} | |
} | |
private const int MaxHits = 500; //It'll fail sometime but eh not my fault | |
RaycastHit[] _hits = new RaycastHit[MaxHits]; | |
//------------------------------------------------- | |
private float FindProjectileCollision(out RaycastHit hitInfo) | |
{ | |
float timeStep = ArcDuration / SegmentCount; | |
float segmentStartTime = 0.0f; | |
hitInfo = new RaycastHit(); | |
Vector3 segmentStartPos = GetArcPositionAtTime(segmentStartTime); | |
for (int i = 0; i < SegmentCount; ++i) | |
{ | |
float segmentEndTime = segmentStartTime + timeStep; | |
Vector3 segmentEndPos = GetArcPositionAtTime(segmentEndTime); | |
var size = Physics.RaycastNonAlloc(segmentStartPos, | |
segmentEndPos - segmentStartPos, | |
_hits, | |
(segmentEndPos - segmentStartPos).magnitude, | |
TraceLayerMask, | |
QueryTriggerInteraction.Ignore); | |
if (size > 0) | |
{ | |
Array.Sort(_hits, 0, size, new HeightComparerDesc()); | |
for (int j = 0; j < size; j++) | |
{ | |
hitInfo = _hits[j]; | |
if (!hitInfo.transform.CompareTag("TeleportArea") || arcInvalid) | |
{ | |
_materialRay.SetColor(ColorShader, _invalidColor); | |
DrawCross(hitInfo.point, _invalidColor, 0.5f); | |
NonTeleportAreaUnderArc = true; | |
} | |
else | |
{ | |
_materialRay.SetColor(ColorShader, _validColor); | |
NonTeleportAreaUnderArc = false; | |
} | |
PlayerTeleportPositionCandidate = hitInfo.point; | |
PlayerTeleportNormalCandidate = hitInfo.normal; | |
float segmentDistance = Vector3.Distance(segmentStartPos, segmentEndPos); | |
float hitTime = segmentStartTime + (timeStep * (hitInfo.distance / segmentDistance)); | |
return hitTime; | |
} | |
} | |
segmentStartTime = segmentEndTime; | |
segmentStartPos = segmentEndPos; | |
} | |
return float.MaxValue; | |
} | |
public class HeightComparerDesc : IComparer<RaycastHit> | |
{ | |
public int Compare(RaycastHit a, RaycastHit b) | |
{ | |
if (a.point.y > b.point.y) | |
{ | |
return -1; | |
} | |
if (a.point.y < b.point.y) | |
{ | |
return 1; | |
} | |
return 0; | |
} | |
} | |
private static void DrawCross(Vector3 origin, Color crossColor, float size) | |
{ | |
Vector3 line1Start = origin + (Vector3.right * size); | |
Vector3 line1End = origin - (Vector3.right * size); | |
Debug.DrawLine(line1Start, line1End, crossColor); | |
Vector3 line2Start = origin + (Vector3.up * size); | |
Vector3 line2End = origin - (Vector3.up * size); | |
Debug.DrawLine(line2Start, line2End, crossColor); | |
Vector3 line3Start = origin + (Vector3.forward * size); | |
Vector3 line3End = origin - (Vector3.forward * size); | |
Debug.DrawLine(line3Start, line3End, crossColor); | |
} | |
//------------------------------------------------- | |
public Vector3 GetArcPositionAtTime(float time) | |
{ | |
Vector3 gravity = useGravity ? Physics.gravity : Vector3.zero; | |
Vector3 arcPos = startPos + ((projectileVelocity * time) + (0.5f * time * time) * gravity) * scale; | |
return arcPos; | |
} | |
//------------------------------------------------- | |
private void HideLineSegments(int startSegment, int endSegment) | |
{ | |
if (lineRenderers != null) | |
{ | |
for (int i = startSegment; i < endSegment; ++i) | |
{ | |
if (lineRenderers.Length < i) | |
{ | |
continue; | |
} | |
lineRenderers[i].enabled = false; | |
} | |
} | |
} | |
public bool IsArcValid() => !arcInvalid && !NonTeleportAreaUnderArc; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment