// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. using System.Collections; using UnityEngine; using UnityEngine.Serialization; namespace Microsoft.MixedReality.Toolkit.UI.PulseShader { /// /// Script for generating pulse shader effect on the surface of a mesh. /// public class PulseShaderHandler : MonoBehaviour { [SerializeField] [Tooltip("Shader parameter name to drive the pulse radius.")] [FormerlySerializedAs("ParamName")] private string paramName = "_Pulse_"; /// /// Shader parameter name to drive the pulse radius. /// public string ParamName { get { return paramName; } set { if (paramName != value) { paramName = value; } } } [SerializeField] [Tooltip("Shader parameter name to set the pulse origin, in local space.")] [FormerlySerializedAs("OriginParamName")] private string originParamName = "_Pulse_Origin_"; /// /// Shader parameter name to set the pulse origin, in local space. /// public string OriginParamName { get { return originParamName; } set { if (originParamName != value) { originParamName = value; } } } [SerializeField] [Tooltip("How long in seconds the pulse should animate")] [FormerlySerializedAs("PulseDuration")] private float pulseAnimationDuration = 5f; /// /// How long in seconds the pulse should animate. /// public float PulseAnimationDuration { get { return pulseAnimationDuration; } set { if (pulseAnimationDuration != value) { pulseAnimationDuration = value; } } } [SerializeField] [Tooltip("Minimum time to wait between each pulse.")] [FormerlySerializedAs("PulseRepeatMinDelay")] private float pulseRepeatMinDelay = 1f; /// /// Minimum time to wait between each pulse. /// public float PulseRepeatMinDelay { get { return pulseRepeatMinDelay; } set { if (pulseRepeatMinDelay != value) { pulseRepeatMinDelay = value; } } } [SerializeField] [Tooltip("Automatically begin repeated pulsing.")] [FormerlySerializedAs("AutoStart")] private bool autoStart = false; /// /// Automatically begin repeated pulsing. /// public bool AutoStart { get { return autoStart; } set { if (autoStart != value) { autoStart = value; } } } [SerializeField] [Tooltip("The material to animate.")] [FormerlySerializedAs("SurfaceMat")] private Material surfaceMat; /// /// The material to animate. /// public Material SurfaceMat { get { return surfaceMat; } set { if (surfaceMat != value) { surfaceMat = value; } } } // Internal state private Coroutine RepeatPulseCoroutine; private Coroutine CurrentCoroutine; private float pulseStartedTime; private bool repeatingPulse; private bool cancelPulse; protected virtual void Start() { if (AutoStart) { StartPulsing(); } } protected virtual void OnDestroy() { ResetPulseMaterial(); } protected virtual void Update() { } #region Material Control public void SetLocalOrigin(Vector3 origin) { SurfaceMat.SetVector(OriginParamName, origin); } protected void ResetPulseMaterial() { ApplyPulseRadiusToMaterial(0); } protected void ApplyPulseRadiusToMaterial(float radius) { surfaceMat.SetFloat(paramName, radius); } #endregion #region Pulse Control public void PulseOnce(bool cancelPreviousPulse = true) { cancelPulse = false; if (CurrentCoroutine != null && cancelPreviousPulse) { // Stop the previous coroutine StopCoroutine(CurrentCoroutine); } CurrentCoroutine = StartCoroutine(CoSinglePulse()); } // Start the repeat animation coroutine protected void StartPulsing() { repeatingPulse = true; cancelPulse = false; if (RepeatPulseCoroutine == null) { RepeatPulseCoroutine = StartCoroutine(CoRepeatPulse()); } } // Auto start stopping protected void StopPulsing(bool finishCurrentPulse = true) { repeatingPulse = false; if (!finishCurrentPulse) { cancelPulse = true; ApplyPulseRadiusToMaterial(0); } } #endregion #region Animation Timing private IEnumerator CoSinglePulse() { yield return CoWaitForRepeatDelay(); if (!cancelPulse) { yield return CoAnimatePulse(); } } // Delay the animation of shader values based on the PulseRepeatMinDelay private IEnumerator CoWaitForRepeatDelay() { // Wait for minimum time between pulses starting if (pulseStartedTime > 0) { float timeSincePulseStarted = Time.time - pulseStartedTime; float delayTime = PulseRepeatMinDelay - timeSincePulseStarted; if (delayTime > 0) { yield return new WaitForSeconds(delayTime); } } } // Animate shader values over time based on the PulseAnimationDuration private IEnumerator CoAnimatePulse() { pulseStartedTime = Time.time; float t = 0; while (t < PulseAnimationDuration && !cancelPulse) { t += Time.deltaTime; ApplyPulseRadiusToMaterial(t / PulseAnimationDuration); yield return null; } } // Repeat the pulse shader animation private IEnumerator CoRepeatPulse() { while (repeatingPulse && !cancelPulse) { yield return CoSinglePulse(); } RepeatPulseCoroutine = null; } #endregion } }