Skip to content

Instantly share code, notes, and snippets.

@codorizzi
Last active August 11, 2022 09:44
Show Gist options
  • Save codorizzi/e3349f1276ec65502f7b76e1587a11ba to your computer and use it in GitHub Desktop.
Save codorizzi/e3349f1276ec65502f7b76e1587a11ba to your computer and use it in GitHub Desktop.
Unity - Radial Timer Bar
/*
* Author - https://twitter.com/JSqearle
* License - CC BY-SA
* Asset Dependencies:
* - https://assetstore.unity.com/packages/tools/sprite-management/shapes2d-procedural-sprites-and-ui-62586 (Free)
* - https://assetstore.unity.com/packages/tools/utilities/odin-inspector-and-serializer-89041 (optional - can be removed)
* - https://assetstore.unity.com/packages/tools/animation/dotween-hotween-v2-27676 (Free)
*/
using DG.Tweening;
using Shapes2D;
using Sirenix.OdinInspector;
using TMPro;
using UnityEngine;
using UnityEngine.UI;
namespace Radial_Timer {
public class RadialTimer : MonoBehaviour {
[RequireComponent(typeof(Image))]
[RequireComponent(typeof(Shape))]
public class RadialSegment : MonoBehaviour {
public enum SegmentState {
On,
Off,
Pulse,
}
private Shape _shape;
private SegmentState _state;
private Image _image;
private Color _onColor;
private Color _offColor;
private float _transitionTime;
private float _pulseTime;
private GameObject _gameObject;
[ShowInInspector]
public SegmentState State {
get => _state;
set {
if (_state == value)
return;
_state = value;
_image.DOKill();
if (_state != SegmentState.Pulse)
_image.DOColor(_state == SegmentState.Off ? _offColor : _onColor, _transitionTime);
else {
_image
.DOColor(_offColor, _pulseTime).OnComplete(() => {
_image.DOColor(_onColor, _pulseTime).SetLoops(-1, LoopType.Yoyo);
});
}
}
}
public static RadialSegment Create(Transform parent, float startAngle, float endAngle, float cutout, Color onColor, Color offColor, float transitionTime, float pulseTime) {
GameObject go = new GameObject("Segment");
go.transform.SetParent(parent);
go.transform.localPosition = Vector3.zero;
go.transform.localScale = Vector3.one;
RadialSegment segment = go.AddComponent<RadialSegment>();
segment._image = segment.GetComponent<Image>();
segment._shape = segment.GetComponent<Shape>();
segment._onColor = onColor;
segment._offColor = offColor;
segment._image.color = offColor;
segment._shape.settings.shapeType = ShapeType.Ellipse;
segment._shape.settings.innerCutout = Vector2.one * cutout;
segment._shape.settings.startAngle = startAngle;
segment._shape.settings.endAngle = endAngle;
segment._transitionTime = transitionTime;
segment._pulseTime = pulseTime;
segment.State = SegmentState.Off;
return segment;
}
public void OnDestroy() {
_image.DOKill();
}
}
#region Events
#endregion
#region Public Fields
[InfoBox("Changing these properties forces rebuild")]
[FoldoutGroup("Settings")] [OnValueChanged("Initialize")] public Color onColor = Color.white;
[FoldoutGroup("Settings")] [OnValueChanged("Initialize")] public Color offColor = Color.grey;
[PropertySpace(5)]
[FoldoutGroup("Settings")] [OnValueChanged("Initialize")] public bool pulseNext = true;
[PropertySpace(5)]
[FoldoutGroup("Settings")] [OnValueChanged("Initialize")] public float transitionTime = 0.25f;
[FoldoutGroup("Settings")] [OnValueChanged("Initialize")] public float pulseTime = 0.75f;
[PropertySpace(5)]
[FoldoutGroup("Settings")] [OnValueChanged("Initialize")] public int size = 5;
[FoldoutGroup("Settings")] [OnValueChanged("Initialize")] public float padding = 10f;
[FoldoutGroup("Settings")] [OnValueChanged("Initialize"), Range(0,1)] public float width = 0.8f;
[ShowInInspector] public int Current {
get => _current;
set {
_current = Mathf.Clamp(value, 0, _segmentContainer.childCount);
if(counter != null)
counter.SetText(_current.ToString());
Render();
}
}
public TMP_Text counter;
#endregion
#region Private Fields
private int _current;
private Transform _segmentContainer;
#endregion
private void Awake() {
_segmentContainer = transform.Find("Segments");
}
void Start() {
Initialize();
}
private void Initialize() {
if (!Application.isPlaying)
return;
_segmentContainer.Clear();
float spacing = 360 / (float)size;
RadialSegment first = null;
float angle = 0;
for(int i = 0; i < size; i++) {
RadialSegment segment = RadialSegment.Create(_segmentContainer, angle + padding/2, angle + spacing - padding/2, 1 - width, onColor, offColor, transitionTime, pulseTime);
first ??= segment;
angle += spacing;
segment.transform.SetAsFirstSibling();
}
// ReSharper disable once PossibleNullReferenceException
first.transform.SetAsFirstSibling();
Render();
}
private void Render() {
foreach (Transform t in _segmentContainer) {
RadialSegment r = t.GetComponent<RadialSegment>();
r.State = t.GetSiblingIndex() < Current ? RadialSegment.SegmentState.On : RadialSegment.SegmentState.Off;
if (t.GetSiblingIndex() == Current && pulseNext)
r.State = RadialSegment.SegmentState.Pulse;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment