// --------------------------------------------------------------------------------------------------------------------
//
// Copyright (c) VRMADA, All rights reserved.
//
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core;
using UltimateXR.Core.Components;
using UltimateXR.Extensions.Unity;
using UnityEngine;
namespace UltimateXR.Animation.GameObjects
{
///
/// Component that allows to make objects blink using their material's emission channel.
///
public class UxrObjectBlink : UxrComponent
{
#region Inspector Properties/Serialized Fields
[SerializeField] private MeshRenderer _renderer;
[SerializeField] private int _materialSlot = -1;
[SerializeField] private Color _colorNormal = Color.black;
[SerializeField] private Color _colorHighlight = Color.white;
[SerializeField] private float _blinksPerSec = 4.0f;
[SerializeField] private float _durationSeconds = -1.0f;
[SerializeField] private bool _useUnscaledTime;
#endregion
#region Public Types & Data
///
/// Gets whether the object is currently blinking.
///
public bool IsBlinking { get; private set; } = true;
#endregion
#region Public Methods
///
/// Starts a blinking animation using the emission material of an object.
///
/// The GameObject to blink
/// The emission color
/// The blink frequency
/// Total duration of the blinking animation
///
/// -1 to target all renderer materials if there is more than one. An index between [0, materialCount
/// - 1] to target a specific material only.
///
///
/// Whether to use unscaled time () or not (
/// ).
///
/// Animation component
public static UxrObjectBlink StartBlinking(GameObject gameObject, Color emissionColor, float blinksPerSec, float durationSeconds, int materialSlot = -1, bool useUnscaledTime = false)
{
if (gameObject == null)
{
return null;
}
UxrObjectBlink blinkComponent = gameObject.GetOrAddComponent();
blinkComponent.CheckInitialize();
blinkComponent.StartBlinkingInternal(emissionColor, blinksPerSec, durationSeconds, materialSlot, useUnscaledTime);
return blinkComponent;
}
///
/// Stops a blinking animation on an object if it has any.
///
/// GameObject to stop the animation from
public static void StopBlinking(GameObject gameObject)
{
if (gameObject == null)
{
return;
}
if (gameObject.TryGetComponent(out var blinkComponent))
{
blinkComponent.enabled = false;
}
}
///
/// Checks whether the given GameObject has any blinking animation running.
///
/// GameObject to check
/// Whether the given GameObject has any blinking animation running
public static bool CheckBlinking(GameObject gameObject)
{
if (gameObject == null)
{
return false;
}
UxrObjectBlink blinkComponent = gameObject.GetComponent();
return blinkComponent != null && blinkComponent.IsBlinking;
}
///
/// Sets up the blinking animation parameters.
///
/// Renderer whose material will be animated
/// The emission color when it is not blinking
/// The fully blinking color
/// The blinking frequency
/// The total duration of the animation in seconds
///
/// -1 to target all renderer materials if there is more than one. An index between [0, materialCount
/// - 1] to target a specific material only.
///
///
/// Whether to use unscaled time () or not (
/// ).
///
public void Setup(MeshRenderer renderer, Color colorNormal, Color colorHighlight, float blinksPerSec = 4.0f, float durationSeconds = -1.0f, int materialSlot = -1, bool useUnscaledTime = false)
{
_renderer = renderer;
_colorNormal = colorNormal;
_colorHighlight = colorHighlight;
_blinksPerSec = blinksPerSec;
_durationSeconds = durationSeconds;
_materialSlot = materialSlot;
_useUnscaledTime = useUnscaledTime;
IsBlinking = false;
IsInitialized = false;
CheckInitialize();
}
///
/// Starts or restarts the blinking animation using the current parameters.
///
public void StartBlinkingWithCurrentParameters()
{
CheckInitialize();
StartBlinkingInternal(_colorHighlight, _blinksPerSec, _durationSeconds, _materialSlot, _useUnscaledTime);
}
#endregion
#region Unity
///
/// Initializes the component.
///
protected override void Awake()
{
base.Awake();
CheckInitialize();
}
///
/// When re-enabled, starts blinking again with the current parameters.
///
protected override void OnEnable()
{
base.OnEnable();
StartBlinkingWithCurrentParameters();
}
///
/// Stops blinking.
///
protected override void OnDisable()
{
base.OnDisable();
StopBlinkingInternal();
}
///
/// Updates the blinking animation if active.
///
private void Update()
{
if (IsBlinking && _renderer != null)
{
float timer = CurrentTime - _blinkStartTime;
if (_durationSeconds >= 0.0f && timer >= _durationSeconds)
{
StopBlinkingInternal();
}
else
{
float blend = (Mathf.Sin(timer * Mathf.PI * _blinksPerSec * 2.0f) + 1.0f) * 0.5f;
Material[] materials = _renderer.materials;
for (int i = 0; i < materials.Length; i++)
{
if (i == _materialSlot || _materialSlot < 0)
{
materials[i].SetColor(UxrConstants.Shaders.EmissionColorVarName, Color.Lerp(_colorNormal, _colorHighlight, blend));
}
}
_renderer.materials = materials;
}
}
}
#endregion
#region Private Methods
///
/// Initializes the component if necessary.
///
private void CheckInitialize()
{
if (!IsInitialized)
{
if (_renderer == null)
{
_renderer = GetComponent();
}
if (_renderer != null)
{
_originalMaterials = _renderer.sharedMaterials;
}
IsInitialized = true;
}
}
///
/// Starts blinking.
///
/// Emission color
/// Blinking frequency
/// Total duration in seconds
/// The material(s) to target
/// Whether to use unscaled time or not
private void StartBlinkingInternal(Color emissionColor, float blinksPerSec, float durationSeconds, int materialSlot, bool useUnscaledTime)
{
if (_renderer == null)
{
return;
}
_useUnscaledTime = useUnscaledTime;
_blinkStartTime = CurrentTime;
_colorHighlight = emissionColor;
_blinksPerSec = blinksPerSec;
_durationSeconds = durationSeconds;
_materialSlot = materialSlot;
Material[] materials = _renderer.materials;
for (int i = 0; i < materials.Length; i++)
{
if (i == _materialSlot || _materialSlot < 0)
{
materials[i].EnableKeyword(UxrConstants.Shaders.EmissionKeyword);
}
}
_renderer.materials = materials;
IsBlinking = true;
enabled = true;
}
///
/// Stops blinking.
///
private void StopBlinkingInternal()
{
if (_renderer == null)
{
return;
}
IsBlinking = false;
RestoreOriginalSharedMaterial();
}
///
/// Restores the original (shared) material.
///
private void RestoreOriginalSharedMaterial()
{
if (_renderer)
{
_renderer.sharedMaterials = _originalMaterials;
}
}
#endregion
#region Private Types & Data
private float CurrentTime => _useUnscaledTime ? Time.unscaledTime : Time.time;
private bool IsInitialized { get; set; }
private float _blinkStartTime;
private Material[] _originalMaterials;
#endregion
}
}