Last active
August 11, 2022 09:44
-
-
Save codorizzi/e3349f1276ec65502f7b76e1587a11ba to your computer and use it in GitHub Desktop.
Unity - Radial Timer Bar
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
/* | |
* 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