Files
dungeons/Assets/UltimateXR/Runtime/Scripts/Animation/GameObjects/UxrObjectBlink.cs
2024-08-06 21:58:35 +02:00

313 lines
11 KiB
C#

// --------------------------------------------------------------------------------------------------------------------
// <copyright file="UxrObjectBlink.cs" company="VRMADA">
// Copyright (c) VRMADA, All rights reserved.
// </copyright>
// --------------------------------------------------------------------------------------------------------------------
using UltimateXR.Core;
using UltimateXR.Core.Components;
using UltimateXR.Extensions.Unity;
using UnityEngine;
namespace UltimateXR.Animation.GameObjects
{
/// <summary>
/// Component that allows to make objects blink using their material's emission channel.
/// </summary>
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
/// <summary>
/// Gets whether the object is currently blinking.
/// </summary>
public bool IsBlinking { get; private set; } = true;
#endregion
#region Public Methods
/// <summary>
/// Starts a blinking animation using the emission material of an object.
/// </summary>
/// <param name="gameObject">The GameObject to blink</param>
/// <param name="emissionColor">The emission color</param>
/// <param name="blinksPerSec">The blink frequency</param>
/// <param name="durationSeconds">Total duration of the blinking animation</param>
/// <param name="materialSlot">
/// -1 to target all renderer materials if there is more than one. An index between [0, materialCount
/// - 1] to target a specific material only.
/// </param>
/// <param name="useUnscaledTime">
/// Whether to use unscaled time (<see cref="Time.unscaledTime" />) or not (
/// <see cref="Time.time" />).
/// </param>
/// <returns>Animation component</returns>
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<UxrObjectBlink>();
blinkComponent.CheckInitialize();
blinkComponent.StartBlinkingInternal(emissionColor, blinksPerSec, durationSeconds, materialSlot, useUnscaledTime);
return blinkComponent;
}
/// <summary>
/// Stops a blinking animation on an object if it has any.
/// </summary>
/// <param name="gameObject">GameObject to stop the animation from</param>
public static void StopBlinking(GameObject gameObject)
{
if (gameObject == null)
{
return;
}
if (gameObject.TryGetComponent<UxrObjectBlink>(out var blinkComponent))
{
blinkComponent.enabled = false;
}
}
/// <summary>
/// Checks whether the given GameObject has any blinking animation running.
/// </summary>
/// <param name="gameObject">GameObject to check</param>
/// <returns>Whether the given GameObject has any blinking animation running</returns>
public static bool CheckBlinking(GameObject gameObject)
{
if (gameObject == null)
{
return false;
}
UxrObjectBlink blinkComponent = gameObject.GetComponent<UxrObjectBlink>();
return blinkComponent != null && blinkComponent.IsBlinking;
}
/// <summary>
/// Sets up the blinking animation parameters.
/// </summary>
/// <param name="renderer">Renderer whose material will be animated</param>
/// <param name="colorNormal">The emission color when it is not blinking</param>
/// <param name="colorHighlight">The fully blinking color</param>
/// <param name="blinksPerSec">The blinking frequency</param>
/// <param name="durationSeconds">The total duration of the animation in seconds</param>
/// <param name="materialSlot">
/// -1 to target all renderer materials if there is more than one. An index between [0, materialCount
/// - 1] to target a specific material only.
/// </param>
/// <param name="useUnscaledTime">
/// Whether to use unscaled time (<see cref="Time.unscaledTime" />) or not (
/// <see cref="Time.time" />).
/// </param>
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();
}
/// <summary>
/// Starts or restarts the blinking animation using the current parameters.
/// </summary>
public void StartBlinkingWithCurrentParameters()
{
CheckInitialize();
StartBlinkingInternal(_colorHighlight, _blinksPerSec, _durationSeconds, _materialSlot, _useUnscaledTime);
}
#endregion
#region Unity
/// <summary>
/// Initializes the component.
/// </summary>
protected override void Awake()
{
base.Awake();
CheckInitialize();
}
/// <summary>
/// When re-enabled, starts blinking again with the current parameters.
/// </summary>
protected override void OnEnable()
{
base.OnEnable();
StartBlinkingWithCurrentParameters();
}
/// <summary>
/// Stops blinking.
/// </summary>
protected override void OnDisable()
{
base.OnDisable();
StopBlinkingInternal();
}
/// <summary>
/// Updates the blinking animation if active.
/// </summary>
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
/// <summary>
/// Initializes the component if necessary.
/// </summary>
private void CheckInitialize()
{
if (!IsInitialized)
{
if (_renderer == null)
{
_renderer = GetComponent<MeshRenderer>();
}
if (_renderer != null)
{
_originalMaterials = _renderer.sharedMaterials;
}
IsInitialized = true;
}
}
/// <summary>
/// Starts blinking.
/// </summary>
/// <param name="emissionColor">Emission color</param>
/// <param name="blinksPerSec">Blinking frequency</param>
/// <param name="durationSeconds">Total duration in seconds</param>
/// <param name="materialSlot">The material(s) to target</param>
/// <param name="useUnscaledTime">Whether to use unscaled time or not</param>
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;
}
/// <summary>
/// Stops blinking.
/// </summary>
private void StopBlinkingInternal()
{
if (_renderer == null)
{
return;
}
IsBlinking = false;
RestoreOriginalSharedMaterial();
}
/// <summary>
/// Restores the original (shared) material.
/// </summary>
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
}
}