// -------------------------------------------------------------------------------------------------------------------- // // 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 } }