Saturday, June 16, 2018

Contents of Scroll View UI won't display

Symptoms:

My exact symptoms were that the Scroll View contents would be displayed if I played from the scene with the Scroll View, but if I started in another scene and went to that scene the Scroll View would be empty.

Solution:

Set the Under the Scroll Rect component of the Scroll View set the visibility to "Permanent".

Friday, June 15, 2018

How to create Linked Text in Unity with Textmesh Pro

What I was trying to achieve was a block of UI text with links that I could click with the mouse to cause something to happen. In Unity this doesn't happen out of the box. However there is a package called TextMesh Pro which with a bit of effort achieved what I was after.

Here is the link: https://assetstore.unity.com/packages/essentials/beta-projects/textmesh-pro-84126

Unfortunately the documentation is a bit sketchy, some out-of-date, and difficult to find for the newer version.

So to get it to work...
  1. Update to Unity 5.6 or later.
  2. Download and install the package.
  3. From GameObject > UI add a TextMeshPro-Text to the canvas.
  4. Make sure the scene has an Event System object.
  5. Add a script to TextMesh Pro Text object based on TMP_Text_Selector_B.cs 
  6. Links in the text need to be tagged like: Some text <link="link_name">this text has a link</link> and this is boring text.
  7. In TMP_TextInfoDebugTool.cs comment out the contents of method OnDrawGizmos() as this causes errors in the editor.
  8. There is a sample scene called: 12 - Link Example with links that work.
  9. If using the color tag (which works a little different to to the Text color tag) make sure that the base text color is white. Color tags are in the form: <#40A0FF>This is a color tag - just specify the color in the tag.</color>
  10. While I could get the mouseovers to work using the new TMP Text Event Handler script I couldn't get it to register mouse clicks so I used the old TMP_Text_Selector_B version instead.
This is my Selector script based on TMP_Text_Selector_B
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using System.Collections;
using System.Collections.Generic;
using TMPro;

// Disabled warning due to SetVertices being deprecated 
// until new release with SetMesh() is available.
#pragma warning disable 0618

public class TextOutputTextSelector : MonoBehaviour, IPointerEnterHandler, IPointerExitHandler, IPointerClickHandler, IPointerUpHandler
{
 public RectTransform TextPopup_Prefab_01;

 private RectTransform m_TextPopup_RectTransform;
 private TextMeshProUGUI m_TextPopup_TMPComponent;

 private const string k_LinkText = "You have selected link <#ffff00>";
 private const string k_WordText = "Word Index: <#ffff00>";


 private TextMeshProUGUI m_TextMeshPro;
 private Canvas m_Canvas;
 private Camera m_Camera;

 // Flags
 private bool isHoveringObject;
 private int m_selectedLink = -1;
 private int m_selectedWord = -1;
 private int m_lastIndex = -1;

 private Matrix4x4 m_matrix;

 private TMP_MeshInfo[] m_cachedMeshInfoVertexData;

 // ----------------------------------------------------------------------------
 public void Awake()
 {
  m_TextMeshPro = gameObject.GetComponent<TextMeshProUGUI>();
  m_Canvas = gameObject.GetComponentInParent<Canvas>();

  // Get a reference to the camera if Canvas Render Mode is not ScreenSpace Overlay.
  if (m_Canvas.renderMode == RenderMode.ScreenSpaceOverlay)
   m_Camera = null;
  else
   m_Camera = m_Canvas.worldCamera; 
 }

 // ----------------------------------------------------------------------------
 public void OnEnable()
 {

  // Subscribe to event fired when text object has been regenerated.
  TMPro_EventManager.TEXT_CHANGED_EVENT.Add(ON_TEXT_CHANGED);
 }
  
 // ----------------------------------------------------------------------------
 public void OnDisable()
 {
  // UnSubscribe to event fired when text object has been regenerated.
  TMPro_EventManager.TEXT_CHANGED_EVENT.Remove(ON_TEXT_CHANGED);
 }

 // ----------------------------------------------------------------------------
 public void ON_TEXT_CHANGED(Object obj)
 {

  if (obj == m_TextMeshPro)
  {
   // Update cached vertex data.
   m_cachedMeshInfoVertexData = m_TextMeshPro.textInfo.CopyMeshInfoVertexData();
  }
 }

 // ----------------------------------------------------------------------------
 public void LateUpdate()
 {

  if (isHoveringObject)
  {
   // LINKS 
   // Check if mouse intersects with any links.
   int linkIndex = TMP_TextUtilities.FindIntersectingLink(m_TextMeshPro, Input.mousePosition, m_Camera);

   // Clear previous link selection if one existed.
   if ((linkIndex == -1 && m_selectedLink != -1) || linkIndex != m_selectedLink)
   {
    m_selectedLink = -1;
   }

   // Handle new Link selection.
   if (linkIndex != -1 && linkIndex != m_selectedLink)
   {
    m_selectedLink = linkIndex;
    TMP_LinkInfo linkInfo = m_TextMeshPro.textInfo.linkInfo[linkIndex];

    Debug.Log("(Late Update) Selected Link Index: " + m_selectedLink);
    Debug.Log("*** (Late Update) Link ID: \"" + linkInfo.GetLinkID() + "\"   Link Text: \"" + linkInfo.GetLinkText() + "\""); 

    Vector3 worldPointInRectangle = Vector3.zero;
    RectTransformUtility.ScreenPointToWorldPointInRectangle(m_TextMeshPro.rectTransform, Input.mousePosition, m_Camera, out worldPointInRectangle);

    // change color of link (Doesn't quite work but leaving here for future use)
    // Iterate through each of the characters of the word.
    /*
    Debug.Log("First Character Index: " + linkInfo.linkTextfirstCharacterIndex);
    Debug.Log("Link Text Length: " + linkInfo.linkTextLength);

    for (int i = 0; i < linkInfo.linkTextLength; i++)
    {
     int characterIndex = linkInfo.linkTextfirstCharacterIndex + i;

     // Get the index of the material / sub text object used by this character.
     int meshIndex = m_TextMeshPro.textInfo.characterInfo[characterIndex].materialReferenceIndex;

     int vertexIndex = m_TextMeshPro.textInfo.characterInfo[characterIndex].vertexIndex;

     // Get a reference to the vertex color
     Color32[] vertexColors = m_TextMeshPro.textInfo.meshInfo[meshIndex].colors32;

     Color32 c = vertexColors[vertexIndex + 0].Tint(0.75f);

     vertexColors[vertexIndex + 0] = c;
     vertexColors[vertexIndex + 1] = c;
     vertexColors[vertexIndex + 2] = c;
     vertexColors[vertexIndex + 3] = c;
    }

    // Update Geometry
    m_TextMeshPro.UpdateVertexData(TMP_VertexDataUpdateFlags.All);
    */
   }

  }
  else
  {
   // Restore any character that may have been modified
   if (m_lastIndex != -1)
   {
    RestoreCachedVertexAttributes(m_lastIndex);
    m_lastIndex = -1;
   }
  }
 }

 // ----------------------------------------------------------------------------
 public void OnPointerEnter(PointerEventData eventData)
 {
  isHoveringObject = true;
 }

 // ----------------------------------------------------------------------------
 public void OnPointerExit(PointerEventData eventData)
 {
  //Debug.Log("OnPointerExit()");
  isHoveringObject = false;
 }

 // ----------------------------------------------------------------------------
 public void OnPointerClick(PointerEventData eventData)
 {
        // Check if Mouse intersects any words and if so assign a random color to that word.
        int linkIndex = TMP_TextUtilities.FindIntersectingLink(m_TextMeshPro, Input.mousePosition, m_Camera);
  if (linkIndex != -1)
  {
   //TMP_LinkInfo linkInfo = m_TextMeshPro.textInfo.linkInfo[linkIndex];
   Debug.Log("(On Pointer Click) - Selected Link ID: " + m_selectedLink);
   Debug.Log("Mouse position - X: " + Input.mousePosition.x + ", Y: " + Input.mousePosition.y);
  }
 }

 // ----------------------------------------------------------------------------
 public void OnPointerUp(PointerEventData eventData)
 {
  //Debug.Log("OnPointerUp()");
 }

 // ----------------------------------------------------------------------------
 void RestoreCachedVertexAttributes(int index)
 {
  if (index == -1 || index > m_TextMeshPro.textInfo.characterCount - 1) return;

  // Get the index of the material / sub text object used by this character.
  int materialIndex = m_TextMeshPro.textInfo.characterInfo[index].materialReferenceIndex;

  // Get the index of the first vertex of the selected character.
  int vertexIndex = m_TextMeshPro.textInfo.characterInfo[index].vertexIndex;

  // Restore Vertices
  // Get a reference to the cached / original vertices.
  Vector3[] src_vertices = m_cachedMeshInfoVertexData[materialIndex].vertices;

  // Get a reference to the vertices that we need to replace.
  Vector3[] dst_vertices = m_TextMeshPro.textInfo.meshInfo[materialIndex].vertices;

  // Restore / Copy vertices from source to destination
  dst_vertices[vertexIndex + 0] = src_vertices[vertexIndex + 0];
  dst_vertices[vertexIndex + 1] = src_vertices[vertexIndex + 1];
  dst_vertices[vertexIndex + 2] = src_vertices[vertexIndex + 2];
  dst_vertices[vertexIndex + 3] = src_vertices[vertexIndex + 3];

  // Restore Vertex Colors
  // Get a reference to the vertex colors we need to replace.
  Color32[] dst_colors = m_TextMeshPro.textInfo.meshInfo[materialIndex].colors32;

  // Get a reference to the cached / original vertex colors.
  Color32[] src_colors = m_cachedMeshInfoVertexData[materialIndex].colors32;

  // Copy the vertex colors from source to destination.
  dst_colors[vertexIndex + 0] = src_colors[vertexIndex + 0];
  dst_colors[vertexIndex + 1] = src_colors[vertexIndex + 1];
  dst_colors[vertexIndex + 2] = src_colors[vertexIndex + 2];
  dst_colors[vertexIndex + 3] = src_colors[vertexIndex + 3];

  // Restore UV0S
  // UVS0
  Vector2[] src_uv0s = m_cachedMeshInfoVertexData[materialIndex].uvs0;
  Vector2[] dst_uv0s = m_TextMeshPro.textInfo.meshInfo[materialIndex].uvs0;
  dst_uv0s[vertexIndex + 0] = src_uv0s[vertexIndex + 0];
  dst_uv0s[vertexIndex + 1] = src_uv0s[vertexIndex + 1];
  dst_uv0s[vertexIndex + 2] = src_uv0s[vertexIndex + 2];
  dst_uv0s[vertexIndex + 3] = src_uv0s[vertexIndex + 3];

  // UVS2
  Vector2[] src_uv2s = m_cachedMeshInfoVertexData[materialIndex].uvs2;
  Vector2[] dst_uv2s = m_TextMeshPro.textInfo.meshInfo[materialIndex].uvs2;
  dst_uv2s[vertexIndex + 0] = src_uv2s[vertexIndex + 0];
  dst_uv2s[vertexIndex + 1] = src_uv2s[vertexIndex + 1];
  dst_uv2s[vertexIndex + 2] = src_uv2s[vertexIndex + 2];
  dst_uv2s[vertexIndex + 3] = src_uv2s[vertexIndex + 3];


  // Restore last vertex attribute as we swapped it as well
  int lastIndex = (src_vertices.Length / 4 - 1) * 4;

  // Vertices
  dst_vertices[lastIndex + 0] = src_vertices[lastIndex + 0];
  dst_vertices[lastIndex + 1] = src_vertices[lastIndex + 1];
  dst_vertices[lastIndex + 2] = src_vertices[lastIndex + 2];
  dst_vertices[lastIndex + 3] = src_vertices[lastIndex + 3];

  // Vertex Colors
  src_colors = m_cachedMeshInfoVertexData[materialIndex].colors32;
  dst_colors = m_TextMeshPro.textInfo.meshInfo[materialIndex].colors32;
  dst_colors[lastIndex + 0] = src_colors[lastIndex + 0];
  dst_colors[lastIndex + 1] = src_colors[lastIndex + 1];
  dst_colors[lastIndex + 2] = src_colors[lastIndex + 2];
  dst_colors[lastIndex + 3] = src_colors[lastIndex + 3];

  // UVS0
  src_uv0s = m_cachedMeshInfoVertexData[materialIndex].uvs0;
  dst_uv0s = m_TextMeshPro.textInfo.meshInfo[materialIndex].uvs0;
  dst_uv0s[lastIndex + 0] = src_uv0s[lastIndex + 0];
  dst_uv0s[lastIndex + 1] = src_uv0s[lastIndex + 1];
  dst_uv0s[lastIndex + 2] = src_uv0s[lastIndex + 2];
  dst_uv0s[lastIndex + 3] = src_uv0s[lastIndex + 3];

  // UVS2
  src_uv2s = m_cachedMeshInfoVertexData[materialIndex].uvs2;
  dst_uv2s = m_TextMeshPro.textInfo.meshInfo[materialIndex].uvs2;
  dst_uv2s[lastIndex + 0] = src_uv2s[lastIndex + 0];
  dst_uv2s[lastIndex + 1] = src_uv2s[lastIndex + 1];
  dst_uv2s[lastIndex + 2] = src_uv2s[lastIndex + 2];
  dst_uv2s[lastIndex + 3] = src_uv2s[lastIndex + 3];

  // Need to update the appropriate 
  m_TextMeshPro.UpdateVertexData(TMP_VertexDataUpdateFlags.All);
 }
}