Search Unity

Help with my script to change text alpha color via RegEx

Discussion in 'Scripting' started by M0rrigan, Feb 14, 2016.

  1. M0rrigan

    M0rrigan

    Joined:
    May 29, 2015
    Posts:
    29
    Hello,
    I'm finding a way to script a mechanic that allows player to change alpha color of specific letter contained in a text gameobject. I was using the code below, but it was not the right choice, because it caused some issues in-game (more here: http://answers.unity3d.com/questions/1140931/text-ui-broken-after-chaning-alpha-color-of-single.html )

    Code (CSharp):
    1.  using UnityEngine;
    2. using System.Collections;
    3. using UnityEngine.UI;
    4. public class ColorText : MonoBehaviour
    5. {
    6.      public string alphabetLetter;
    7.      public GameObject letterText;
    8.      void Start ()
    9.      {
    10.          letterText = GameObject.FindWithTag ("LetterText");
    11.      }
    12.      void OnTriggerEnter(Collider other)
    13.      {
    14.          Text myText = letterText.GetComponent<Text> ();
    15.              myText.text = myText.text.Replace(alphabetLetter, "<color=#3D3D3DFF>"+alphabetLetter+"</color>");
    16.            }
    17. }
    That's why I decided to switch to Regex, at JoshuaMcKenie's suggestion. His code, differently from what I was using before, replaces the string to the whole text, and I don't want that. I just want that the string replace every string with the same value in the text. For example, if string = B, it needs to replace every B inside the text with the new B. So, I was trying to change this:
    Code (CSharp):
    1.  myText.text = regex.Replace (baseString, m => characterMap [m.Value]);
    into this:
    Code (CSharp):
    1. myText.text = myText.text.regex.Replace (baseString, m => characterMap [m.Value]);
    But it doesn't work.
    Any suggestions?
     
  2. M0rrigan

    M0rrigan

    Joined:
    May 29, 2015
    Posts:
    29
    Hi again, I'm taking a look at this: https://msdn.microsoft.com/en-US/library/system.text.regularexpressions.regex.ismatch(v=vs.110).aspx
    I understand that I need to use the IsMatch method, before, and replace then. However no success at my first attempt :
    Code (CSharp):
    1.    if(regex.IsMatch(myText.text,alphabetLetter));
    2. {
    3. Debug.Log("MatchFound");
    4.  
    5. }
    I get this error: error CS0176: Static member `System.Text.RegularExpressions.Regex.IsMatch(string, string)' cannot be accessed with an instance reference, qualify it with a type name instead
    I don't know what really means, but maybe it deals with the variable I'm using , that in my case is

    publicGameObjectletterText;
    TextmyText=letterText.GetComponent<Text>();

    Anybody can help?
     
  3. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    916
    first off for statics, you can't access static methods via an instance, you want to directly call the class itself. "regex" is a created instance, but "Regex" is the class
    Code (CSharp):
    1.  
    2. regex.IsMatch();  //wrong
    3. Regex.IsMatch();   //right
    4.  
    however you don't need that.

    second it seems the inital assumptions I had for the colorText was correct, when you activate a letter it activates all instances for that specific letter (which is why I suggested regex for this). the example I gave you was with the characterMap fully populated, listing all the letters immediately (which is why the entire text was highlighted). you can still use my code, just make the dictionary start out empty and add in letters when needed.

    However, if there is only one gameobject in the scene that will ever use ColorText, then we can make it a singleton and adding letters should be even easier.
    Code (CSharp):
    1.  
    2. using System;
    3. using System.Collections.Generic;
    4. using System.Linq;
    5. using System.Text.RegularExpressions;
    6. using UnityEngine;
    7. using UnityEngine.UI;
    8.  
    9.  
    10. public class ColorText: MonoBehaviour
    11. {
    12.  
    13.     static ColorText instance;
    14.  
    15.     Text myText;
    16.     string baseString;
    17.     IDictionary<string,string> characterMap = new Dictionary<string,string>();
    18.  
    19.     void Awake()
    20.     {
    21.         instance = this;
    22.         myText = GetComponent<Text>();
    23.         ChangeText(myText.text);
    24.     }
    25.  
    26.     public static void AddColor(string characters, Color color)
    27.     {
    28.         if(instance==null)
    29.         {
    30.             return;
    31.         }
    32.         characters = Regex.Escape(characters);
    33.  
    34.         instance.characterMap[characters] = "<color=#"+ColorToHex(color)+">"+characters+"</color>";
    35.  
    36.  
    37.         instance.Format();
    38.     }
    39.  
    40.     public static void RemoveColor(string characters)
    41.     {
    42.         if(instance==null)
    43.         {
    44.             return;
    45.         }
    46.    
    47.         characters = Regex.Escape(characters);
    48.  
    49.         if(instance.characterMap.ContainsKey(characters))
    50.         {
    51.             instance.characterMap.Remove(characters);
    52.         }
    53.  
    54.         instance.Format();
    55.     }
    56.  
    57.    public static void ChangeText(string newString)
    58.    {
    59.         if(instance==null)
    60.         {
    61.             return;
    62.         }
    63.  
    64.       instance.baseString = newString;
    65.       instance.Format();
    66.    }
    67.  
    68.     static string ColorToHex (Color color)
    69.     {
    70.         string hex  = ((int)(color.r * 255)).ToString("X2")
    71.                     + ((int)(color.g * 255)).ToString("X2")
    72.                     + ((int)(color.b * 255)).ToString("X2")
    73.                     + ((int)(color.a * 255)).ToString("X2");
    74.    
    75.         return hex;
    76.     }
    77.  
    78.     void Format()
    79.     {
    80.         if(characterMap.Count==0)
    81.         {
    82.             myText.text = baseString;
    83.             return;
    84.         }
    85.  
    86.         Regex regex
    87.             = new Regex (String.Join ("|", characterMap.Keys.ToArray()));
    88.  
    89.         myText.text = regex.Replace (baseString, m => characterMap [m.Value]);
    90.     }
    91.  
    92. }
    93.  
    here you add this to the UI text gameobject and then you can simply have another class statically add in the letters and color. Then when the player or ally runs into the pickup, the selected letters are changed to the specified color

    Code (CSharp):
    1. using UnityEngine;
    2. public class LetterPickup: MonoBehaviour
    3. {
    4.     public string letter;
    5.     public Color color;
    6.  
    7.     void OnTriggerEnter(Collider other)
    8.     {
    9.         ColorText.AddColor(letter,color);
    10.     }
    11. }
     
    Last edited: Feb 19, 2016
    M0rrigan likes this.
  4. M0rrigan

    M0rrigan

    Joined:
    May 29, 2015
    Posts:
    29
    Hey there, thank you for being here. I'm having problem using
    Code (CSharp):
    1. color.ToHexStringRGBA();
    I've read this http://forum.unity3d.com/threads/fixed-unity-5-2-03f-new-bug-or-function-disapeared.353908/ and replaced that with
    Code (CSharp):
    1. ColorUtility.ToHtmlStringRGB(color)
    The only issue that still doesn't allow me to test the script is that I get this error:

    And yes, there will only one text for each scene.
     
  5. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    916
    sorry, that was depreciated code. initially I was going to use 'color.ToHexStringRGBA();' to convert to Hex (since my dev team is using Unity 5.1.1, the Color class still has that method, and it showed up while I was writing the class). but before posting I recalled that one of our designers was having issues due to him using version 5.2.x (where the method was moved to a different class) so I went back and just made my own ColorToHex method (as you can see is at the bottom of the class) and simply forgot to delete that line.

    Notice that I'm not even using that string hex anywhere. you can safely delete that line.

    as for that last error it just means I'm using an instance method "ColorToHex" in a static contex. If it was using instance data, then I would have used 'instance.ColorToHex(color)'. But since the method doesn't touch any instance data it can be made static
    Code (CSharp):
    1. static string ColorToHex (Color color)
    2. {
    3. //stuff
    4. }
     
    M0rrigan likes this.
  6. M0rrigan

    M0rrigan

    Joined:
    May 29, 2015
    Posts:
    29
    Thank you, no errors, now. I've also replaced color with textColor here:

    Code (CSharp):
    1.     using UnityEngine;
    2.     public class LetterPickup: MonoBehaviour
    3.     {
    4.         public string letter;
    5.         public Color textColor;
    6.    
    7.         void OnTriggerEnter(Collider other)
    8.         {
    9.             ColorText.AddColor(letter,color);
    10.         }
    11.     }
    12.  
    I've attached the ColorText.cs to the UI.Text and the LetterPickup script to the Pickup, filled the string and Color variable, but the script doesn't work. I have a couple of questions:
    1. baseString variable is supposed to replace the string of my Text.UI? If yes, what if my text is formatted?
    2. As I've said, the scripts are not working. You've said that I need to add letters. I've only copied/pasted your last code, where do I need to define letters?
    3. I suppose that the LetterPickups variable needs to match with those used in the ColorText's function "AddColor". Am I right?
     
    Last edited: Feb 17, 2016
  7. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    916
    if you're changing color to textColor, make sure you are also updating the other use of the variable as well, you left the old color name unchanged in the OnTriggerEnter

    1. baseString is the unformatted version of the string. it tracks what the text looks like before any regex formatting. Tracking the unformatted version simply makes it easier when new letters are added and its especially easier for removing letters.

    2. I assume your letters are gameobjects that the player walks over in the world to pick them up this means that the Letter pickup gameobject will need this LetterPickup Class as well as a Box or sphere Collider set as a trigger.

    you will likely have multiples types of letter pickups, and on each of these pickups you set the unique values for the letter and color on its attached LetterPickup Component.

    there are still a few nuances that remain undone, like the pickup itself will trigger against anything with a trigger collider (including the ground) and the letter itself doesn't destroy itself on pickup. exactly how this is handled depends on how you've build this game and your own preference.

    take a look at the Roll a ball tutorial, specifically for Collecting Pickup Objects on the basics of making a pickup item. in that video the OnTriggerEnter code is placed on the Player's Object, while I wrote the class for the Pickup to have. I also prefer to usually filter collisions via modifying the collision layers than by checking gameObject tags, but that is slightly more complex.
     
  8. M0rrigan

    M0rrigan

    Joined:
    May 29, 2015
    Posts:
    29
    Yeah, I have no problem with the part regarding the collision with Pickup's trigger. The problem is after updating the variables it still doesn't work. This what I'm using right now on the Pickups:
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class LetterPickup: MonoBehaviour
    4. {
    5.     public string letterAlphabet;
    6.     public Color textColor;
    7.  
    8.     void OnTriggerEnter(Collider other)
    9.     {
    10.         if (other.tag == "Player")
    11.         {
    12.             ColorText.AddColor (letterAlphabet, textColor);
    13.         }
    14.     }
    15. }
    16.  

    And this is the script I've attached to the text UI:

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using System.Linq;
    3. using System.Text.RegularExpressions;
    4. using UnityEngine;
    5. using UnityEngine.UI;
    6.  
    7.  
    8. public class ColorText: MonoBehaviour
    9. {
    10.  
    11.     static ColorText instance;
    12.  
    13. Text myText;
    14. string baseString;
    15.     IDictionary<string,string> characterMap = new Dictionary<string,string> ();
    16.  
    17.  
    18.     void Awake()
    19.     {
    20.         instance = this;
    21.         myText = GetComponent<Text>();
    22.         ChangeText(myText.text);
    23.     }
    24.  
    25.     public static void AddColor(string letterAlphabet, Color textColor)
    26.     {
    27.         if(instance!=null)
    28.         {
    29.             return;
    30.         }
    31.         letterAlphabet = Regex.Escape(letterAlphabet);
    32.  
    33.         //string hex = ColorUtility.ToHtmlStringRGB(color);
    34.         instance.characterMap[letterAlphabet] = "<color=#"+ColorToHex(textColor)+">"+letterAlphabet+"</color>";
    35.  
    36.  
    37.         instance.Format();
    38.     }
    39.  
    40.     public static void RemoveColor(string letterAlphabet)
    41.     {
    42.         if(instance!=null)
    43.         {
    44.             return;
    45.         }
    46.  
    47.         letterAlphabet = Regex.Escape(letterAlphabet);
    48.  
    49.         if(instance.characterMap.ContainsKey(letterAlphabet))
    50.         {
    51.             instance.characterMap.Remove(letterAlphabet);
    52.         }
    53.  
    54.         instance.Format();
    55.     }
    56.  
    57.     public static void ChangeText(string newString)
    58.     {
    59.         if(instance!=null)
    60.         {
    61.             return;
    62.         }
    63.  
    64.         instance.baseString = newString;
    65.         instance.Format();
    66.     }
    67.  
    68.     static    string ColorToHex (Color color)
    69.     {
    70.         string hex  = ((int)(color.r * 255)).ToString("X2")
    71.             + ((int)(color.g * 255)).ToString("X2")
    72.             + ((int)(color.b * 255)).ToString("X2")
    73.             + ((int)(color.a * 255)).ToString("X2");
    74.  
    75.         return hex;
    76.     }
    77.  
    78.     void Format()
    79.     {
    80.         if(characterMap.Count==0)
    81.         {
    82.             myText.text = baseString;
    83.             return;
    84.         }
    85.  
    86.         Regex regex = new Regex (String.Join ("|", characterMap.Keys.ToArray()));
    87.  
    88.         myText.text = regex.Replace (baseString, m => characterMap [m.Value]);
    89.     }
    90.  
    91. }
    It just don't happen anything.I've noticed that baseString is never filled.. Is it normal?
     
  9. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    916
    you should check and make sure that OnTriggerEnter is actually getting called. a simple Debug.log("pickup triggered"); inside the OnTriggerEnter helps with debugging that as it'll print to the console panel when that code is executed

    make sure that the player has a collider set as a trigger for when it walks into the pickup

    baseString is set in the ChangeText(); line 64, which is called in the Awake or optionally by another class (since i made it static)
     
    M0rrigan likes this.
  10. M0rrigan

    M0rrigan

    Joined:
    May 29, 2015
    Posts:
    29
    Thank you Joshua, I've already tried with Debug.log, and it print flawlessly. Also, I've checked the console and I don't get any error. I've tried to make baseString public, only to see if something was happening there, but as I said the box always remain unfilled. I did expect to find the string of the text.UI. The player is okay too: its collider is set as a trigger. Aside from the Debuf.log, I can't see anything happening to my Text.UI. Really, I can't understand why it is not working.
     
  11. M0rrigan

    M0rrigan

    Joined:
    May 29, 2015
    Posts:
    29
    Is there anything else I can try to make it work?
     
  12. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    916
    The likely reason is how you have the game setup and it can be of some small nuance that hasn't been presented to me. Without me physically being there and seeing how its used myself, I can only guess as to why its not working. There's also the possibility is that I'm too incompetent to have missed another error in my code. A better use of your time is that you should set up a testing heuristic, a quick test plan really, on how to best locate the issue quickly.

    You can start with the easy stuff first, check and make sure that the UI Text actually has text, and that the richtext checkbox is active. Typically any way you can check as to why you're not getting the expected output without actually looking at the code (ala Black-Box testing).

    if that doesn't work next step is to do some grey-box testing where you drop a couple Debug.logs() at strategic points in the code and see if they fire correctly. its a little slower but its an efficient method to see if the logic is playing in the right order without actually going step-by-step in the code.

    This last one is White-box testing, is a near sure fire way of finding the problem but also the slowest. Start using break points to follow the code as it executes line-by-line(refer to VisualStudio, MonoDevelop, or what ever you use for how to set breakpoints, link to Unity, and run the code). you can place a break point in the Format function and make sure the base string is set and it setting the Text field properly.

    You typically test in this order because of how fast each one could help you find the issue. Having a test plan is an important skillset if you want to continue as a programmer. I'm only scratching the surface here and not giving test plans the proper justice they deserve so when you have the chance and the insanity, dive into the world of testing for a little bit as using them can teach you so much
     
  13. M0rrigan

    M0rrigan

    Joined:
    May 29, 2015
    Posts:
    29
    Hello, thank you again Joshua. I always use Debug.Log for testing purpose, but your post really ispired me to keep going on. So, I've noticed that the function Format was actually never fired. It is called by AddColor and ChangeColor: the first one when the player enters the trigger, and the second one during Awake. In Format I've put two Debug.log, and none of them was printed.

    Code (CSharp):
    1.     void Format()
    2.     {
    3.         if(characterMap.Count==0)
    4.         {
    5.             Debug.Log ("ColorText.Format.CharacterMapCountisZero");
    6.             baseString = myText.text;
    7.             return;
    8.         }
    9.         Debug.Log ("Format?");
    10.  
    11.  
    12.         Regex regex = new Regex (String.Join ("|", characterMap.Keys.ToArray()));
    13.  
    14.         myText.text = regex.Replace (baseString, m => characterMap [m.Value]);
    15.     }
    So, I've put some Debug.Logs also on AddColor and ChangeColor, particularly in this part:

    Code (CSharp):
    1.  
    2.         if(instance!=null)
    3.         {
    4.             return;
    5.         }
    It was always printed. This means that the next part of the function was never executed.
    So, I've tried to comment that part on AddColor and ChangeColor.... and whoa it worked..!!
    This is because it returned the same istance of the Awake function, that's to say the text with alpha = 0. Is that correct?!
     
  14. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    916
    so I was incompetent after all, or rather blind. Since the methods are static there's a possibility that they can be called when the text doesn't currently exist. so the functions would test to see if the instance was null and if it was then it should do nothing. however I wrote if the instance was NOT null instead of if it was (as I intended to) and I never noticed that i was using "not equals" instead of "equals"

    you don't want to keep them commented out as its important so that you don't get some errors during specific cases, its just that you want them to say 'if(instance==null)' instead of 'if(instance!=null)'
     
    M0rrigan likes this.
  15. M0rrigan

    M0rrigan

    Joined:
    May 29, 2015
    Posts:
    29
    Thank you so much Joshua, you've helped me and gave me the opportunity to learn something new :)