How to Make a Clickable URL in Unity using TextMesh Pro

Whether you are using Unity to develop an app or a game, you might come across a point where you have to add a URL to your UI somewhere. However, there is no out of the box support to make a clickable URL in Unity and you will have to implement it yourself. Luckily this isn’t so hard to do, especially when leveraging some neat features offered by TextMesh Pro. If you want to get straight to the code, we recommend you to scroll down.


How to Visualize a URL in Unity?

In our approach, we will use the Rich Text formatting options offered by TextMesh Pro. If you have worked with UnityEngine.UI.Text components in the past then you might already be familiar with using Rich Text Formatting. To correctly visualize a URL we can use the color and underline tags. If you want a complete overview of the available Richt Text options in TextMesh Pro you can read more about them here.

Stylize a clickable url in Unity
Styling a Clickable URL in Unity

In our case, we will be using the underline and color formatting tags to visualize our URL. If you add the text below to a TextMesh Pro Text component, you should see a similar result as the one in the image above. Later we will also show how you can customize the color of your URL from inside the Unity Editor rather than hardcoding it.

Please, visit <color=#aaaaff><u>Google</u></color>

How to Detect if a User Clicks a URL in Unity?

Now that we know how to visualize URLs in Unity, we have to figure out how to detect if a user clicks on a URL. Luckily, TexhMesh Pro offers us a way to do this pretty easily. First, we have to let TextMesh Pro know which portion of a text is clickable using the <link> tag. Secondly, we have to manually check if a URL is clicked and respond to it.


Creating a link in TextMesh Pro

Simply put, a link in TextMesh Pro is a portion of text that is clickable. Again, we will use Rich Formatting to tell TextMeshPro what portion of our text is clickable. As can be seen below, the code for this is pretty straightforward. We will simply wrap the name of the website by the <link> tag and also give the link the value of the URL. If you have every built a website in HTML then this will look extremely similar to that of an anchor tag.

<color=#aaaaff><u><link="https://www.google.com//">Google</link></u></color>

Check if a link has been clicked in TextMesh Pro

Next, to check if a link has actually been clicked or tapped, we have to use the TMP_TextUtilities.FindIntersectingLink() utility method. To use this method, we have to pass in the Text component that displays the text containing the link, the current input position (Either mouse or finger position) of the user and we have to specify a camera. If you are working with a 2D UI you can simply pass a null as the camera argument here. The code below is an excerpt of our solution that we will be discussing shortly.

int linkIndex = TMP_TextUtilities.FindIntersectingLink(m_TextComponent, inputPosition, null);

Looking at the code, you will see that the utility method is returning an integer that we assign to a variable named linkIndex. This value will hold the index of the link that was clicked in the text component. Any text component can have multiple links, so using the linkIndex we can determine which link has been clicked. More information about the links inside a TextMesh Pro text component can be found by accessing its textInfo.linkInfo property.


The Solution

Since we now know how to visualize a URL and how to check if a URL has been clicked in Unity, we can show our final solution.

In our solution, we created a TextMeshPro URL Encoder component that can be added to any GameObject with a TexhMeshPro Text component. Using our new encoder component, a developer can specify which text component they would like to target for the URL encoding and also with which color to display the URLs.

Also, in our solution we made it so that URLs can easily be entered by using a custom <a> tag. This tag works similar to the <link> tag but it also allows us to automatically add styling options In fact, our anchor tag is converted internally into a color, underline and link tag.

Finally, we have made sure that the system can handle multiple URLs and it works on both standalone as well as mobile platforms. Please, scroll down if you want to read more about the implmentation details.

TextMeshProURLEncoder.cs
  1. using System.Text.RegularExpressions;
  2. using UnityEngine;
  3. using TMPro;
  4.  
  5. public class TextMeshProURLEncoder : MonoBehaviour
  6. {
  7. [SerializeField]
  8. private TMP_Text m_TextComponent = null;
  9. [SerializeField]
  10. private Color m_URLColor = Color.white;
  11.  
  12. private TextMeshProURLInvoker m_Invoker = null;
  13.  
  14. private void Awake()
  15. {
  16. m_Invoker = gameObject.AddComponent<TextMeshProURLInvoker>();
  17. m_Invoker.Setup(m_TextComponent);
  18.  
  19. SetText(m_TextComponent.text);
  20. }
  21.  
  22. public void SetText(string source)
  23. {
  24. m_TextComponent.text = ProcessSource(source);
  25. }
  26.  
  27. private string ProcessSource(string source)
  28. {
  29. m_Invoker.Clear();
  30.  
  31. MatchEvaluator evaluator = new MatchEvaluator((match) =>
  32. {
  33. string url = match.Groups[1].Value;
  34. string name = match.Groups[2].Value;
  35. string key = m_Invoker.CreateKeyForURL(url);
  36.  
  37. return StylizeSource(string.Format("<link=\"{0}\">{1}</link>", key, name), m_URLColor);
  38. });
  39.  
  40. // Regular Expression: <a="(.+?)">(.+?)<\/a>
  41. return Regex.Replace(source, "<a=\"(.+?)\">(.+?)</a>", evaluator);
  42. }
  43.  
  44. private static string StylizeSource(string source, Color color)
  45. {
  46. return string.Format("<color=#{0}><u>{1}</u></color>", ColorUtility.ToHtmlStringRGB(color), source);
  47. }
  48. }
  49.  

TextMeshProURLInvoker.cs
  1. using System.Collections.Generic;
  2. using UnityEngine;
  3. using TMPro;
  4.  
  5. public class TextMeshProURLInvoker : MonoBehaviour
  6. {
  7. private Dictionary<string, string> m_URLMapping = new Dictionary<string, string>();
  8. private TMP_Text m_TextComponent = null;
  9.  
  10. public void Setup(TMP_Text textComponent)
  11. {
  12. m_TextComponent = textComponent;
  13. }
  14.  
  15. public void Clear()
  16. {
  17. m_URLMapping.Clear();
  18. }
  19.  
  20. public string CreateKeyForURL(string url)
  21. {
  22. string key = GenerateKey();
  23. m_URLMapping.Add(key, url);
  24.  
  25. return key;
  26. }
  27.  
  28. private void LateUpdate()
  29. {
  30. Vector2 inputPosition = Vector2.zero;
  31. if(!CheckForInteraction(out inputPosition)) return;
  32.  
  33. int linkIndex = TMP_TextUtilities.FindIntersectingLink(m_TextComponent, inputPosition, null);
  34. if(linkIndex < 0) return;
  35.  
  36. if(!TryOpenURL(linkIndex))
  37. {
  38. Debug.LogError("An error occured when opening URL with linkIndex: " + linkIndex);
  39. }
  40. }
  41.  
  42. private bool CheckForInteraction(out Vector2 inputPosition)
  43. {
  44. inputPosition = Vector2.zero;
  45.  
  46. #if UNITY_EDITOR || UNITY_STANDALONE
  47. if(!Input.GetMouseButtonUp(0)) return false;
  48. inputPosition = Input.mousePosition;
  49. #else
  50. if(Input.touchCount == 0) return false;
  51. if(Input.GetTouch(0).phase != TouchPhase.Ended) return false;
  52. inputPosition = Input.GetTouch(0).position;
  53. #endif
  54.  
  55. return TMP_TextUtilities.IsIntersectingRectTransform(m_TextComponent.rectTransform, inputPosition, null);
  56. }
  57.  
  58. private string GenerateKey()
  59. {
  60. return string.Format("url-{0}", m_URLMapping.Count);
  61. }
  62.  
  63. private bool TryOpenURL(int linkIndex)
  64. {
  65. TMP_LinkInfo[] linkInfos = m_TextComponent.textInfo.linkInfo;
  66.  
  67. if(linkIndex >= linkInfos.Length) return false;
  68.  
  69. TMP_LinkInfo linkInfo = linkInfos[linkIndex];
  70. string key = linkInfo.GetLinkID();
  71.  
  72. if(!m_URLMapping.ContainsKey(key)) return false;
  73.  
  74. Application.OpenURL(m_URLMapping[key]);
  75. return true;
  76. }
  77. }

Implementation Details

  • Our solution is split up into two components. One component encodes the URLs into the text component and the other responds to user input and acts on it. We tried making sure that each component adheres to the Single Object Responsibility rule as much as possible without going into too much depth.
  • For better usability, our solution adds a custom <a> tag to automatically add extra styling features using rich text. This is done by converting the text using Regular Expression Replace.
  • By default, a <link> tag cannot hold values larger than 120 characters. This is the main reason why we added a mapping to our TextMeshProURLInvoker class that maps links to URLs. By doing this, we are able to circumvent the 120 character limit.

Conclusion

To conclude, you now know how to create a clickable URL in Unity by leveraging both the Rich Text formatting options and utility methods offered through TextMesh Pro. Using the code above, it should be easy to add any URL into your Unity app or game. As a result, it will improve the user experience as well as make it easier for developers to add or remove URLs.