ProximityLight.cs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. // Copyright (c) Microsoft Corporation. All rights reserved.
  2. // Licensed under the MIT License.
  3. using System;
  4. using System.Collections;
  5. using System.Collections.Generic;
  6. using UnityEngine;
  7. /// <summary>
  8. /// Utility component to animate and visualize a light that can be used with
  9. /// the "SDK/Standard" shader "_ProximityLight" feature.
  10. /// </summary>
  11. [ExecuteInEditMode]
  12. public class ProximityLight : MonoBehaviour
  13. {
  14. // Two proximity lights are supported at this time.
  15. private const int proximityLightCount = 2;
  16. private const int proximityLightDataSize = 6;
  17. private static List<ProximityLight> activeProximityLights = new List<ProximityLight>(proximityLightCount);
  18. private static Vector4[] proximityLightData = new Vector4[proximityLightCount * proximityLightDataSize];
  19. private static int proximityLightDataID;
  20. private static int lastProximityLightUpdate = -1;
  21. [Serializable]
  22. public class LightSettings
  23. {
  24. /// <summary>
  25. /// Specifies the radius of the ProximityLight effect when near to a surface.
  26. /// </summary>
  27. public float NearRadius
  28. {
  29. get { return nearRadius; }
  30. set { nearRadius = value; }
  31. }
  32. [Header("Proximity Settings")]
  33. [Tooltip("Specifies the radius of the ProximityLight effect when near to a surface.")]
  34. [SerializeField]
  35. [Range(0.0f, 1.0f)]
  36. private float nearRadius = 0.05f;
  37. /// <summary>
  38. /// Specifies the radius of the ProximityLight effect when far from a surface.
  39. /// </summary>
  40. public float FarRadius
  41. {
  42. get { return farRadius; }
  43. set { farRadius = value; }
  44. }
  45. [Tooltip("Specifies the radius of the ProximityLight effect when far from a surface.")]
  46. [SerializeField]
  47. [Range(0.0f, 1.0f)]
  48. private float farRadius = 0.2f;
  49. /// <summary>
  50. /// Specifies the distance a ProximityLight must be from a surface to be considered near.
  51. /// </summary>
  52. public float NearDistance
  53. {
  54. get { return nearDistance; }
  55. set { nearDistance = value; }
  56. }
  57. [Tooltip("Specifies the distance a ProximityLight must be from a surface to be considered near.")]
  58. [SerializeField]
  59. [Range(0.0f, 1.0f)]
  60. private float nearDistance = 0.02f;
  61. /// <summary>
  62. /// When a ProximityLight is near, the smallest size percentage from the far size it can shrink to.
  63. /// </summary>
  64. public float MinNearSizePercentage
  65. {
  66. get { return minNearSizePercentage; }
  67. set { minNearSizePercentage = value; }
  68. }
  69. [Tooltip("When a ProximityLight is near, the smallest size percentage from the far size it can shrink to.")]
  70. [SerializeField]
  71. [Range(0.0f, 1.0f)]
  72. private float minNearSizePercentage = 0.35f;
  73. /// <summary>
  74. /// The color of the ProximityLight gradient at the center (RGB) and (A) is gradient extent.
  75. /// </summary>
  76. public Color CenterColor
  77. {
  78. get { return centerColor; }
  79. set { centerColor = value; }
  80. }
  81. [Header("Color Settings")]
  82. [Tooltip("The color of the ProximityLight gradient at the center (RGB) and (A) is gradient extent.")]
  83. [ColorUsageAttribute(true, true)]
  84. [SerializeField]
  85. private Color centerColor = new Color(54.0f / 255.0f, 142.0f / 255.0f, 250.0f / 255.0f, 0.0f / 255.0f);
  86. /// <summary>
  87. /// The color of the ProximityLight gradient at the center (RGB) and (A) is gradient extent.
  88. /// </summary>
  89. public Color MiddleColor
  90. {
  91. get { return middleColor; }
  92. set { middleColor = value; }
  93. }
  94. [Tooltip("The color of the ProximityLight gradient at the middle (RGB) and (A) is gradient extent.")]
  95. [SerializeField]
  96. [ColorUsageAttribute(true, true)]
  97. private Color middleColor = new Color(47.0f / 255.0f, 132.0f / 255.0f, 255.0f / 255.0f, 51.0f / 255.0f);
  98. /// <summary>
  99. /// The color of the ProximityLight gradient at the center (RGB) and (A) is gradient extent.
  100. /// </summary>
  101. public Color OuterColor
  102. {
  103. get { return outerColor; }
  104. set { outerColor = value; }
  105. }
  106. [Tooltip("The color of the ProximityLight gradient at the outer (RGB) and (A) is gradient extent.")]
  107. [SerializeField]
  108. [ColorUsageAttribute(true, true)]
  109. private Color outerColor = new Color((82.0f * 3.0f) / 255.0f, (31.0f * 3.0f) / 255.0f, (191.0f * 3.0f) / 255.0f, 255.0f / 255.0f);
  110. }
  111. public LightSettings Settings
  112. {
  113. get { return settings; }
  114. set { settings = value; }
  115. }
  116. [SerializeField]
  117. private LightSettings settings = new LightSettings();
  118. private float pulseTime;
  119. private float pulseFade;
  120. /// <summary>
  121. /// Initiates a pulse, if one is not already occurring, which simulates a user touching a surface.
  122. /// </summary>
  123. /// <param name="pulseDuration">How long in seconds should the pulse animate over.</param>
  124. /// <param name="fadeBegin">At what point during the pulseDuration should the pulse begin to fade out as a percentage. Range should be [0, 1].</param>
  125. /// <param name="fadeSpeed">The speed to fade in and out.</param>
  126. public void Pulse(float pulseDuration = 0.2f, float fadeBegin = 0.8f, float fadeSpeed = 10.0f)
  127. {
  128. if (pulseTime <= 0.0f)
  129. {
  130. StartCoroutine(PulseRoutine(pulseDuration, fadeBegin, fadeSpeed));
  131. }
  132. }
  133. private void OnEnable()
  134. {
  135. AddProximityLight(this);
  136. }
  137. private void OnDisable()
  138. {
  139. pulseTime = pulseFade = 0;
  140. RemoveProximityLight(this);
  141. UpdateProximityLights(true);
  142. }
  143. #if UNITY_EDITOR
  144. private void Update()
  145. {
  146. if (Application.isPlaying)
  147. {
  148. return;
  149. }
  150. Initialize();
  151. UpdateProximityLights();
  152. }
  153. #endif // UNITY_EDITOR
  154. private void LateUpdate()
  155. {
  156. UpdateProximityLights();
  157. }
  158. private void OnDrawGizmosSelected()
  159. {
  160. if (!enabled)
  161. {
  162. return;
  163. }
  164. Vector3[] directions = new Vector3[] { Vector3.right, Vector3.left, Vector3.up, Vector3.down, Vector3.forward, Vector3.back };
  165. Gizmos.color = new Color(Settings.CenterColor.r, Settings.CenterColor.g, Settings.CenterColor.b);
  166. Gizmos.DrawWireSphere(transform.position, Settings.NearRadius);
  167. foreach (Vector3 direction in directions)
  168. {
  169. Gizmos.DrawIcon(transform.position + direction * Settings.NearRadius, string.Empty, false);
  170. }
  171. Gizmos.color = new Color(Settings.OuterColor.r, Settings.OuterColor.g, Settings.OuterColor.b);
  172. Gizmos.DrawWireSphere(transform.position, Settings.FarRadius);
  173. foreach (Vector3 direction in directions)
  174. {
  175. Gizmos.DrawIcon(transform.position + direction * Settings.FarRadius, string.Empty, false);
  176. }
  177. }
  178. private static void AddProximityLight(ProximityLight light)
  179. {
  180. if (activeProximityLights.Count >= proximityLightCount)
  181. {
  182. Debug.LogWarningFormat("Max proximity light count ({0}) exceeded.", proximityLightCount);
  183. }
  184. activeProximityLights.Add(light);
  185. }
  186. private static void RemoveProximityLight(ProximityLight light)
  187. {
  188. activeProximityLights.Remove(light);
  189. }
  190. private static void Initialize()
  191. {
  192. proximityLightDataID = Shader.PropertyToID("_ProximityLightData");
  193. }
  194. private static void UpdateProximityLights(bool forceUpdate = false)
  195. {
  196. if (lastProximityLightUpdate == -1)
  197. {
  198. Initialize();
  199. }
  200. if (!forceUpdate && (Time.frameCount == lastProximityLightUpdate))
  201. {
  202. return;
  203. }
  204. for (int i = 0; i < proximityLightCount; ++i)
  205. {
  206. ProximityLight light = (i >= activeProximityLights.Count) ? null : activeProximityLights[i];
  207. int dataIndex = i * proximityLightDataSize;
  208. if (light)
  209. {
  210. proximityLightData[dataIndex] = new Vector4(light.transform.position.x,
  211. light.transform.position.y,
  212. light.transform.position.z,
  213. 1.0f);
  214. float pulseScaler = 1.0f + light.pulseTime;
  215. proximityLightData[dataIndex + 1] = new Vector4(light.Settings.NearRadius * pulseScaler,
  216. 1.0f / Mathf.Clamp(light.Settings.FarRadius * pulseScaler, 0.001f, 1.0f),
  217. 1.0f / Mathf.Clamp(light.Settings.NearDistance * pulseScaler, 0.001f, 1.0f),
  218. Mathf.Clamp01(light.Settings.MinNearSizePercentage));
  219. proximityLightData[dataIndex + 2] = new Vector4(light.Settings.NearDistance * light.pulseTime,
  220. Mathf.Clamp01(1.0f - light.pulseFade),
  221. 0.0f,
  222. 0.0f);
  223. proximityLightData[dataIndex + 3] = light.Settings.CenterColor;
  224. proximityLightData[dataIndex + 4] = light.Settings.MiddleColor;
  225. proximityLightData[dataIndex + 5] = light.Settings.OuterColor;
  226. }
  227. else
  228. {
  229. proximityLightData[dataIndex] = Vector4.zero;
  230. }
  231. }
  232. Shader.SetGlobalVectorArray(proximityLightDataID, proximityLightData);
  233. lastProximityLightUpdate = Time.frameCount;
  234. }
  235. private IEnumerator PulseRoutine(float pulseDuration, float fadeBegin, float fadeSpeed)
  236. {
  237. float pulseTimer = 0.0f;
  238. while (pulseTimer < pulseDuration)
  239. {
  240. pulseTimer += Time.deltaTime;
  241. pulseTime = pulseTimer / pulseDuration;
  242. if (pulseTime > fadeBegin)
  243. {
  244. pulseFade += Time.deltaTime;
  245. }
  246. yield return null;
  247. }
  248. while (pulseFade < 1.0f)
  249. {
  250. pulseFade += Time.deltaTime * fadeSpeed;
  251. yield return null;
  252. }
  253. pulseTime = 0.0f;
  254. while (pulseFade > 0.0f)
  255. {
  256. pulseFade -= Time.deltaTime * fadeSpeed;
  257. yield return null;
  258. }
  259. pulseFade = 0.0f;
  260. }
  261. }