Label.cs 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528
  1. using System.Collections;
  2. using System.Collections.Generic;
  3. using UnityEngine;
  4. using UnityEngine.UI;
  5. namespace Google.Maps.Examples.Shared {
  6. /// <summary>
  7. /// Class for showing an in-scene, <see cref="Camera.main"/> aligned Label.
  8. /// </summary>
  9. [RequireComponent(typeof(CanvasGroup))]
  10. public class Label : MonoBehaviour {
  11. [Tooltip("Text element to show this Label's text in.")]
  12. public Text Text;
  13. [Tooltip(
  14. "Should this Label keep its starting x-rotation (i.e. should it stay at its current " +
  15. "up/down tilt amount, and just turn in y-axis to face Camera)?")]
  16. public bool TurnOnly;
  17. [Tooltip(
  18. "Should the Label which is the most closely aligned to the Camera be the most visible? " +
  19. "This helps reduce visual clutter by allowing all Labels not been directly looked at to " +
  20. "be faded out.")]
  21. public bool FadeWithView;
  22. [Tooltip("Total time this Label should take to fade in or out?")]
  23. public float FadeTime = 1f;
  24. [Tooltip("Should this Label start hidden?")]
  25. public bool StartFadedOut;
  26. [Tooltip("Should this Label automatically start fading in as soon as SetText is called?")]
  27. public bool AutoFadeIn;
  28. /// <summary>
  29. /// Is this <see cref="Label"/> currently tracking <see cref="Camera.main"/>, i.e. is this
  30. /// <see cref="Label"/> continually making sure it is aligned to <see cref="Camera.main"/>?
  31. /// </summary>
  32. /// <remarks>
  33. /// This flag is used to ensure that the coroutine for <see cref="Camera.main"/>-tracking is not
  34. /// redundantly started when it is already running.
  35. /// </remarks>
  36. private bool IsTrackingCamera;
  37. /// <summary>Is this <see cref="Label"/> currently fading in or out?</summary>
  38. /// <remarks>
  39. /// This flag is used to ensure that the fading-facing coroutine is not redundantly started when
  40. /// when it is already running.
  41. /// </remarks>
  42. private bool IsFading;
  43. /// <summary>Has a <see cref="Text"/> component been defined?</summary>
  44. /// <remarks>
  45. /// This is set to null before this check is performed, allowing this check to only be performed
  46. /// once, with the result being stored for future re-use.
  47. /// </remarks>
  48. private bool? HasText;
  49. /// <summary>Is this <see cref="Label"/> currently fading in (true) or out (false)?</summary>
  50. private bool FadingIn;
  51. /// <summary>Time into current fade.</summary>
  52. private float FadeTimer;
  53. /// <summary>
  54. /// Value to multiply alpha by to achieve desired alpha level? This is used when alpha is
  55. /// controlled by view angle, to allow manual changes in alpha or fading animations to be
  56. /// combined with this view-dependant alpha logic.
  57. /// </summary>
  58. /// <remarks>
  59. /// This value starts at 1f to ensure no change in alpha until a new value is set.
  60. /// </remarks>
  61. private float AlphaMultiplier = 1f;
  62. /// <summary>
  63. /// Required <see cref="CanvasGroup"/>, used for optionally fading all parts of this
  64. /// <see cref="Label"/> in or out as <see cref="Camera.main"/> looks towards or away from it.
  65. /// </summary>
  66. /// <remarks>
  67. /// This variable is auto-found on first access, allowing this <see cref="CanvasGroup"/> to be
  68. /// accessed at any time without having to check if it is null.
  69. /// </remarks>
  70. private CanvasGroup CanvasGroup {
  71. get {
  72. if (_CanvasGroup == null) {
  73. _CanvasGroup = GetComponent<CanvasGroup>();
  74. }
  75. return _CanvasGroup;
  76. }
  77. }
  78. /// <summary>
  79. /// Actual stored <see cref="CanvasGroup"/>. This is stored in this way to allow
  80. /// <see cref="Label.CanvasGroup"/> to be used at any time without having to check if it is
  81. /// null.
  82. /// </summary>
  83. private CanvasGroup _CanvasGroup;
  84. /// <summary>Has this <see cref="Label"/> been setup yet?</summary>
  85. /// <remarks>Allows setup to be intelligently called when needed?</remarks>
  86. private bool IsSetup;
  87. /// <summary>
  88. /// All <see cref="Label"/>s in the current scene. Used to perform group actions, like starting
  89. /// all <see cref="Label"/>s fading in or out together, or hiding the <see cref="Text"/> part of
  90. /// all <see cref="Label"/>s.
  91. /// </summary>
  92. private static readonly List<Label> AllLabels = new List<Label>();
  93. /// <summary>
  94. /// Setup this <see cref="Label"/> if (and only if) have not already done so.
  95. /// </summary>
  96. private void Start() {
  97. TrySetup();
  98. }
  99. /// <summary>
  100. /// Setup this <see cref="Label"/> if (and only if) have not already done so.
  101. /// </summary>
  102. protected void TrySetup() {
  103. // Skip if have already setup this Label.
  104. if (IsSetup) {
  105. return;
  106. }
  107. // Add to list of all Labels, so can fade all Labels in/out together.
  108. AllLabels.Add(this);
  109. // If this Label is meant to start faded out, make invisible now.
  110. if (StartFadedOut) {
  111. AlphaMultiplier = 0f;
  112. CanvasGroup.alpha = 0f;
  113. }
  114. // Flag this label as now set up.
  115. IsSetup = true;
  116. }
  117. /// <summary>Set the specific text to display on this <see cref="Label"/>.</summary>
  118. /// <param name="text">Text to show on this <see cref="Label"/>.</param>
  119. internal void SetText(string text) {
  120. // Make sure this label is setup.
  121. TrySetup();
  122. // Print an error if no Text element has been given.
  123. if (!CanFindText()) {
  124. // Note: 'name' and 'GetType()' just give the name of the GameObject this script is on, and
  125. // the name of this script respectively.
  126. Debug.LogErrorFormat(
  127. "No Text element set for {0}.{1}, which requires a UI.Text element to " +
  128. "show given text \"{2}\".",
  129. name,
  130. GetType(),
  131. text);
  132. return;
  133. }
  134. // Name this GameObject based on given text (to make debugging easier) and display the text.
  135. gameObject.name = string.Concat("Label: ", text);
  136. Text.text = text;
  137. // Start this Label tracking the Camera (unless already doing so).
  138. if (!IsTrackingCamera) {
  139. StartCoroutine(TrackCamera());
  140. }
  141. // Optionally start fading in.
  142. if (AutoFadeIn) {
  143. StartFadingIn();
  144. }
  145. }
  146. /// <summary>Set a new alpha value for this <see cref="Label"/>.</summary>
  147. /// <param name="newAlpha">
  148. /// New alpha value to set, assumed to be a valid value (within the range of 0f to 1f).
  149. /// </param>
  150. internal void SetAlpha(float newAlpha) {
  151. // Make sure this label is setup.
  152. TrySetup();
  153. // Print an error if no Text element has been given.
  154. if (!CanFindText()) {
  155. Debug.LogErrorFormat(
  156. "No Text element set for {0}.{1}, which requires a UI.Text element to " +
  157. "apply new alpha value of {2:N2} to.",
  158. name,
  159. GetType(),
  160. newAlpha);
  161. return;
  162. }
  163. // Set desired alpha level.
  164. ApplyAlpha(newAlpha);
  165. // Start this Label tracking the Camera (unless already doing so).
  166. if (!IsTrackingCamera) {
  167. StartCoroutine(TrackCamera());
  168. }
  169. }
  170. /// <summary>Start this <see cref="Label"/> fading in smoothly over time.</summary>
  171. internal void StartFadingIn() {
  172. StartFading(true);
  173. }
  174. /// <summary>Start this <see cref="Label"/> fading in smoothly over time.</summary>
  175. internal void StartFadingOut() {
  176. StartFading(false);
  177. }
  178. /// <summary>Start all <see cref="Label"/>s fading in smoothly over time.</summary>
  179. internal static void StartFadingAllIn() {
  180. StartFadingAll(true);
  181. }
  182. /// <summary>Start all <see cref="Label"/>s fading in smoothly over time.</summary>
  183. internal static void StartFadingAllOut() {
  184. StartFadingAll(false);
  185. }
  186. /// <summary>Shrink/grow all <see cref="Label"/>s by a given multiplier.</summary>
  187. /// <remarks>
  188. /// This is a class member. Calling <see cref="Label.ScaleAll"/> iterates over all in-scene
  189. /// <see cref="Label"/>s, scaling each.
  190. /// </remarks>
  191. /// <param name="multiplier">
  192. /// Value to multiply current scale by for all <see cref="Label"/>s.
  193. /// </param>
  194. internal static void ScaleAll(float multiplier) {
  195. foreach (Label label in AllLabels) {
  196. label.Scale(multiplier);
  197. }
  198. }
  199. /// <summary>Hide all <see cref="Label"/>s.</summary>
  200. /// <remarks>
  201. /// This is a class member. Calling <see cref="Label.HideAll"/> iterates over all in-scene
  202. /// <see cref="Label"/>s, hiding each.
  203. /// </remarks>
  204. /// <param name="hide">Optionally set to true to hide, false to show.</param>
  205. internal static void HideAll(bool hide = true) {
  206. HideOrShowAll(hide);
  207. }
  208. /// <summary>Hide all <see cref="Label"/>s.</summary>
  209. /// <remarks>
  210. /// This is a class member. Calling the static method <see cref="Label.ShowAll"/> iterates over
  211. /// all in-scene <see cref="Label"/>s, showing each.
  212. /// </remarks>
  213. /// <param name="show">Optionally set to true to show, false to hide.</param>
  214. internal static void ShowAll(bool show = true) {
  215. HideOrShowAll(!show);
  216. }
  217. /// <summary>Hide the <see cref="Text"/> part of all <see cref="Label"/>s.</summary>
  218. /// <remarks>
  219. /// This is a class member. Calling the static method <see cref="Label.HideAllText"/> iterates
  220. /// over all in-scene <see cref="Label"/>s, hiding the <see cref="Text"/> part of each.
  221. /// </remarks>
  222. /// <param name="hide">Optionally set to true to hide, false to show.</param>
  223. internal static void HideAllText(bool hide = true) {
  224. HideOrShowAllText(hide);
  225. }
  226. /// <summary>Hide the <see cref="Text"/> part of all <see cref="Label"/>s.</summary>
  227. /// <remarks>
  228. /// This is a class member. Calling the static method <see cref="Label.ShowAllText"/> iterates
  229. /// over all in-scene <see cref="Label"/>s, showing the <see cref="Text"/> part of each.
  230. /// </remarks>
  231. /// <param name="show">Optionally set to true to hide, false to show.</param>
  232. internal static void ShowAllText(bool show = true) {
  233. HideOrShowAllText(!show);
  234. }
  235. /// <summary>Turn to face the <see cref="Camera.main"/> every frame.</summary>
  236. /// <remarks>
  237. /// If <see cref="FadeWithView"/> is enabled, this <see cref="Label"/> will also fade in or out
  238. /// as <see cref="Camera.main"/> looks towards or away from it.
  239. /// </remarks>
  240. private IEnumerator TrackCamera() {
  241. // Flag that this coroutine has started (so it will not be redundantly restarted later).
  242. IsTrackingCamera = true;
  243. // Start facing Camera.
  244. while (true) {
  245. // Get the current rotation of the Camera. If Labels are meant to turn only (y-rotation
  246. // only, ignoring x-rotation) then remove the x-component of this rotation.
  247. Quaternion cameraRotation;
  248. if (TurnOnly) {
  249. Vector3 cameraEuler = Camera.main.transform.eulerAngles;
  250. cameraRotation = Quaternion.Euler(0f, cameraEuler.y, 0f);
  251. } else {
  252. cameraRotation = Camera.main.transform.rotation;
  253. }
  254. // Match Label's rotation to that of the Camera. This is so all Labels will be parallel to
  255. // each other, yet all be readable to the Camera. Also note that, for any other kind of
  256. // GameObject, the inverse of the Camera's rotation should be used, so that this Label is
  257. // facing the opposite direction of the Camera (i.e. towards the Camera). But Unity creates
  258. // all UI elements and quads with their textured side facing backwards. So to make any UI
  259. // element look at the Camera, we have to make it face the same direction as the Camera.
  260. transform.rotation = cameraRotation;
  261. // Optionally fade in/out based on view angle, so that the Label closest to the view center
  262. // is the clearest, and all other Labels are semi-transparent.
  263. if (FadeWithView) {
  264. FadeWithViewAngle();
  265. }
  266. // Wait for next frame.
  267. yield return null;
  268. }
  269. }
  270. /// <summary>Fade this <see cref="Label"/> in or out over time.</summary>
  271. private IEnumerator Fade() {
  272. // Flag that this coroutine has started (so it will not be redundantly restarted later).
  273. IsFading = true;
  274. // Start fading in/out.
  275. while (true) {
  276. // Count up until end of fading animation.
  277. FadeTimer += Time.smoothDeltaTime;
  278. // If have reached end, apply final alpha value (fully faded in or out).
  279. if (FadeTimer >= FadeTime) {
  280. ApplyAlpha(FadingIn ? 1f : 0f);
  281. IsFading = false;
  282. break;
  283. }
  284. // If still fading, determine current fade value.
  285. float fadePercent = FadeTimer / FadeTime;
  286. // Convert to alpha based on whether fading in or out, smoothing result towards 1f so will
  287. // smoothly approach/leave fully opaque and apply.
  288. float alpha = FadingIn ? fadePercent : 1f - fadePercent;
  289. alpha = Smooth(alpha);
  290. ApplyAlpha(alpha);
  291. // Wait for next frame.
  292. yield return null;
  293. }
  294. }
  295. /// <summary>Start this <see cref="Label"/> fading in or out smoothly over time.</summary>
  296. /// <params name="fadeIn">True to start fading in, false to start fading out.</params>
  297. private void StartFading(bool fadeIn) {
  298. // Make sure this label is setup.
  299. TrySetup();
  300. // Print an error if no Text element has been given.
  301. if (!CanFindText()) {
  302. Debug.LogErrorFormat(
  303. "No Text element set for {0}.{1}, which requires a UI.Text element to " +
  304. "start fading {2}.",
  305. name,
  306. GetType(),
  307. fadeIn ? "in" : "out");
  308. return;
  309. }
  310. // Setup start of fade.
  311. AlphaMultiplier = 0f;
  312. FadeTimer = 0f;
  313. FadingIn = fadeIn;
  314. // Start this Label fading (unless already fading). Note that if already fading, then the
  315. // change in variables above will set up the new fade with the existing coroutine.
  316. if (!IsFading) {
  317. StartCoroutine(Fade());
  318. }
  319. // Make this Label always face the Camera (unless already facing the Camera).
  320. if (!IsTrackingCamera) {
  321. StartCoroutine(TrackCamera());
  322. }
  323. }
  324. /// <summary>Start all <see cref="Label"/>s fading in or out smoothly over time.</summary>
  325. /// <remarks>
  326. /// This is a class member. Calling the static method <see cref="Label.StartFadingAll"/>
  327. /// iterates over all in-scene <see cref="Label"/>s, calling <see cref="StartFading"/> on each.
  328. /// </remarks>
  329. /// <params name="fadeIn">True to start fading in, false to start fading out.</params>
  330. private static void StartFadingAll(bool fadeIn) {
  331. // Print an error if there are no Labels to start fading.
  332. if (AllLabels.Count == 0) {
  333. Debug.LogErrorFormat(
  334. "No {0} found in the current scene, so cannot start fading them all " + "{1}.",
  335. typeof(Label),
  336. fadeIn ? "in" : "out");
  337. return;
  338. }
  339. // Traverse list of all Labels in the scene, starting fading them all in.out. Labels are
  340. // traversed in reverse order so that any null/deleted Labels can be removed without affecting
  341. // future list-indices.
  342. for (int i = AllLabels.Count - 1; i >= 0; i--) {
  343. if (AllLabels[i] == null) {
  344. AllLabels.RemoveAt(i);
  345. } else {
  346. AllLabels[i].StartFading(fadeIn);
  347. }
  348. }
  349. }
  350. ///< summary>
  351. /// Set desired alpha level. If alpha is controlled by view angle, apply alpha through the use
  352. /// of a multiplier, otherwise apply alpha directly to <see cref="CanvasGroup"/> now.
  353. /// </summary>
  354. private void ApplyAlpha(float newAlpha) {
  355. if (FadeWithView) {
  356. AlphaMultiplier = newAlpha;
  357. } else {
  358. CanvasGroup.alpha = newAlpha;
  359. }
  360. }
  361. /// <summary>
  362. /// Fade all parts of this <see cref="Label"/> in or out as <see cref="Camera.main"/> faces
  363. /// towards or away from this <see cref="Label"/>.
  364. /// </summary>
  365. /// <remarks>
  366. /// This is to avoid the screen becoming too visually cluttered with fully-opaque
  367. /// <see cref="Label"/>s, naturally highlighting the <see cref="Label"/>s
  368. /// <see cref="Camera.main"/> is looking at and fading out the rest.
  369. /// </remarks>
  370. private void FadeWithViewAngle() {
  371. // Determine how close the Camera is to directly looking at this Label.
  372. Vector3 direction = (Text.transform.position - Camera.main.transform.position).normalized;
  373. float dotProduct = Vector3.Dot(Camera.main.transform.forward, direction);
  374. float angle = Mathf.Acos(dotProduct) * Mathf.Rad2Deg;
  375. // Convert angle to an alpha value of 1f when looking directly at this Label, and nearly
  376. // transparent (0.1f) when looking 45 degrees or more away from this Label.
  377. float alpha;
  378. if (angle > 45f) {
  379. alpha = 0.1f;
  380. } else {
  381. alpha = (1f - angle / 45f) * 0.9f + 0.1f;
  382. // Smooth alpha towards 1f, so Label will stay at nearly 100% alpha for longer when looking
  383. // roughly near Label.
  384. alpha = Smooth(alpha);
  385. }
  386. // Apply alpha to Canvas Group to fade Label in/out. Multiply by alpha multiplier to allow
  387. // alpha to be influenced by manual changes or by fading in/out.
  388. CanvasGroup.alpha = alpha * AlphaMultiplier;
  389. }
  390. /// <summary>Shrink/grow this <see cref="Label"/> by a given multiplier.</summary>
  391. /// <param name="multiplier">Value to multiply current scale by.</param>
  392. private void Scale(float multiplier) {
  393. // Make sure this label is setup before adjusting its scale
  394. TrySetup();
  395. gameObject.transform.localScale *= multiplier;
  396. }
  397. /// <summary>Hide/show all <see cref="Label"/>s.</summary>
  398. /// This is a class member. Calling the static method <see cref="Label.HideOrShowAll"/> iterates
  399. /// over all in-scene <see cref="Label"/>s, calling <see cref="HideOrShow"/> on each.
  400. /// <param name="hide">True to hide, false to show if hidden.</param>
  401. private static void HideOrShowAll(bool hide) {
  402. foreach (Label label in AllLabels) {
  403. label.HideOrShow(hide);
  404. }
  405. }
  406. /// <summary>Hide/show this <see cref="Label"/>.</summary>
  407. /// <param name="hide">True to hide, false to show if hidden.</param>
  408. private void HideOrShow(bool hide) {
  409. // Make sure this label is setup, then activate/deactivate it's GameObject to show/hide it.
  410. TrySetup();
  411. gameObject.SetActive(!hide);
  412. }
  413. /// <summary>Hide/show the <see cref="Text"/> part of all <see cref="Label"/>s.</summary>
  414. /// <remarks>
  415. /// This is a class member. Calling the static method <see cref="Label.HideOrShowAllText"/>
  416. /// iterates over all in-scene <see cref="Label"/>s, calling <see cref="HideOrShowText"/> on
  417. /// each.
  418. /// </remarks>
  419. /// <param name="hide">True to hide text, false to show if hidden.</param>
  420. private static void HideOrShowAllText(bool hide) {
  421. foreach (Label label in AllLabels) {
  422. label.HideOrShowText(hide);
  423. }
  424. }
  425. /// <summary>Hide/show the <see cref="Text"/> part of this <see cref="Label"/>.</summary>
  426. /// <param name="hide">True to hide text, false to show if hidden.</param>
  427. private void HideOrShowText(bool hide) {
  428. // Make sure this label is setup, then if a text element can be found, hide/show it.
  429. TrySetup();
  430. if (CanFindText()) {
  431. Text.gameObject.SetActive(!hide);
  432. }
  433. }
  434. /// <summary>See if a <see cref="Text"/> element has been given.</summary>
  435. private bool CanFindText() {
  436. // If have not already done so, check for Text element, storing result for later re-use if
  437. // required.
  438. if (!HasText.HasValue) {
  439. HasText = Text != null;
  440. }
  441. return HasText.Value;
  442. }
  443. /// <summary>Return a given value smoothed towards 1f.</summary>
  444. private static float Smooth(float value) {
  445. return Mathf.Sin(value * Mathf.PI / 2f);
  446. }
  447. }
  448. }