Skip to content

Instantly share code, notes, and snippets.

@thsbrown
Last active March 1, 2022 22:15
Show Gist options
  • Save thsbrown/cbc8f429dc01c06bae417295e53ebfbe to your computer and use it in GitHub Desktop.
Save thsbrown/cbc8f429dc01c06bae417295e53ebfbe to your computer and use it in GitHub Desktop.
Display a emanating ring from a given position in Unity
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Cysharp.Threading.Tasks;
using DG.Tweening;
using Lean.Touch;
using Shapes;
using Sirenix.OdinInspector;
using StudioName.Runtime.ExtensionAndHelpers;
using StudioName.Runtime.Utility;
using UnityEngine;
using UnityEngine.Rendering;
public class InteractionRing : ImmediateModeShapeDrawer
{
/// <summary>
/// The color of our ring
/// </summary>
[Tooltip("The color of our ring")]
[Title("Configure Ring")]
public Color color;
/// <summary>
/// The thickness of our ring
/// </summary>
[Tooltip("The thickness of our ring")]
public float thickness;
/// <summary>
/// The starting and ending range we will use for our radius when animating
/// </summary>
[Title("Configure Animation")]
[Tooltip("The starting and ending range we will use for our radius when animating")]
[MinMaxSlider(0, 20,true)]
public Vector2 radiusRange;
/// <summary>
/// The speed of our ring animation
/// </summary>
[Tooltip("The speed of our ring animation")]
public float animationSpeed;
/// <summary>
/// The percent completion time that the new ring will be created and begin animating at
/// </summary>
[Tooltip("The percent completion time that the new ring will be created and begin animating at")]
[Range(0,1)]
public float secondStartsAt;
/// <summary>
/// The easing curve we will use when animating our ring
/// </summary>
[Tooltip("The easing curve we will use when animating our ring")]
public Ease ease;
/// <summary>
/// The rings we are currently animating
/// </summary>
private List<AnimatedRing> animatedRings;
private void Start()
{
StartRingAnimation();
}
private void OnDestroy()
{
StopRingAnimation();
}
public override void DrawShapes(Camera camera)
{
//only render if we are either main camera or scene view camera (otherwise we won't see shape in scene view)
if (camera != Camera.main && camera.cameraType != CameraType.SceneView)
{
return;
}
using(Draw.Command(camera,CameraEvent.BeforeForwardAlpha))
{
foreach (var shapesRing in animatedRings)
{
shapesRing.Draw();
}
}
}
/// <summary>
/// Restarts our ring animation
/// </summary>
[TitleGroup("Utility")]
[ButtonGroup("Utility/Methods")]
public async UniTask StartRingAnimation()
{
IsRingsAnimating = true;
animatedRings = new List<AnimatedRing>();
while (IsRingsAnimating)
{
var shapeRing = new AnimatedRing();
shapeRing.ConfigureRing(transform,thickness,color);
shapeRing.ConfigureAnimation(radiusRange,animationSpeed,ease);
animatedRings.Add(shapeRing);
var tween = shapeRing.Play();
tween.OnComplete(() =>
{
animatedRings.Remove(shapeRing);
});
var distance = 0f;
while (tween.IsActive() && distance < secondStartsAt)
{
await UniTask.Yield();
distance = GenUtil.MapRange(shapeRing.Radius, radiusRange, new Vector2(0, 1));
}
//uncomment this for second to spawn utilizing animation duration instead of distance
//await tween.AsyncWaitForPosition(tween.Duration() * secondStartsAt).AsUniTask();
}
}
/// <summary>
/// Stops our ring animation
/// </summary>
[ButtonGroup("Utility/Methods")]
public void StopRingAnimation()
{
IsRingsAnimating = false;
var tweens = animatedRings.Select(x => x.LastAnimation).ToList();
foreach (var tween in tweens)
{
tween.Kill(true);
}
}
/// <summary>
/// Stop our ring animation and restarts it
/// </summary>
[ButtonGroup("Utility/Methods")]
public async void RestartRingAnimation()
{
StopRingAnimation();
//wait period of time for our AsyncWaitForPosition in PlayRingAnimation to complete so while loop will exit
await Task.Delay(TimeSpan.FromSeconds(0.01f));
StartRingAnimation();
}
/// <summary>
/// Is our ring animation currently playing
/// </summary>
public bool IsRingsAnimating { get; private set; }
private void OnDrawGizmos()
{
Draw.Ring(transform.position,Quaternion.Euler(new Vector3(-90,0,0)),radiusRange.x,thickness,DiscColors.Flat(Color.green));
Draw.Ring(transform.position,Quaternion.Euler(new Vector3(-90,0,0)),radiusRange.y,thickness,DiscColors.Flat(Color.red));
var secondStartRadius = GenUtil.MapRange(secondStartsAt,new Vector2(0, 1),radiusRange);
Draw.Ring(transform.position,Quaternion.Euler(new Vector3(-90,0,0)),secondStartRadius,thickness,DiscColors.Flat(Color.blue));
}
/// <summary>
/// The ring that we will animate outward using <see cref="DOTween"/> and <see cref="Shapes"/>
/// </summary>
private class AnimatedRing
{
private Transform transform;
private float radius;
private float endingRadius;
private float thickness;
private Color color;
private float animationSpeed;
private Ease ease;
/// <summary>
/// Configures the properties of our ring that we will be animating
/// </summary>
/// <param name="transform">The transform position we will start our ring from</param>
/// <param name="thickness">The thickness of our ring</param>
/// <param name="color">The color of our ring</param>
public void ConfigureRing(Transform transform, float thickness, Color color)
{
this.transform = transform;
this.thickness = thickness;
this.color = color;
this.animationSpeed = 1;
}
/// <summary>
/// Configures the properties of our ring animation
/// </summary>
/// <param name="radiusRange">The starting and ending radius for our animation (x = starting, y = ending)</param>
/// <param name="animationSpeed">The speed our animation</param>
/// <param name="ease">The type of ease we will use for animation</param>
public void ConfigureAnimation(Vector2 radiusRange, float animationSpeed, Ease ease)
{
this.radius = radiusRange.x;
this.endingRadius = radiusRange.y;
this.animationSpeed = animationSpeed;
this.ease = ease;
}
/// <summary>
/// Draws our ring utilizing <see cref="Shapes.Draw.Ring(UnityEngine.Vector3)"/>
/// </summary>
public void Draw()
{
Shapes.Draw.Ring(transform.position,Quaternion.Euler(new Vector3(-90,0,0)),radius,thickness,DiscColors.Flat(color));
}
/// <summary>
/// Starts the animation sequence
/// </summary>
/// <returns>The tween we are using to play our animation</returns>
public Tween Play()
{
var sequence = DOTween.Sequence();
var radiusTween = DOTween.To(
() => radius,
(x) => radius = x,
endingRadius,
animationSpeed);
var colorTween = DOTween.ToAlpha(
() => color,
(x) => color = x,
0,
animationSpeed);
sequence.Append(radiusTween);
sequence.Join(colorTween);
sequence.SetEase(ease);
LastAnimation = sequence;
return sequence;
}
/// <summary>
/// The last animation / tween that was played using <see cref="Play"/>
/// </summary>
public Tween LastAnimation { get; private set; }
/// <summary>
/// The current radius of the animated ring (this will vary based on the animation elapsed time)
/// <remarks>Equal to starting radius when animation begins, and ending radius when animation completes</remarks>
/// </summary>
public float Radius => radius;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment