Forum begins after the advertisement:


[Part 18] Tooltips Break the UI Stats Display

Home Forums Video Game Tutorial Series Creating a Rogue-like Shoot-em Up in Unity [Part 18] Tooltips Break the UI Stats Display

Viewing 8 posts - 1 through 8 (of 8 total)
  • Author
    Posts
  • #16397
    Sean
    Level 11
    Former Patron
    Helpful?
    Up
    0
    ::

    Hi,

    So that it’d be easier to understand some of the variables in CharacterData.Stats, I added some Tooltips to them, such as stating that it’s a percentage that should be entered as a decimal. For example:

    <code>[Tooltip("0.1 = 10%")]
    [Range(-1,10)] public float moveSpeed;
    [Tooltip("0.1 = 10%")]
    [Range(-1,10)] public float might;
    [Tooltip("0.1 = 10%")]
    [Range(-1,10)] public float area;</code>

    Unfortunately, I later realised this broke the code as it was, I believe, taking the tooltips as fields to enter into the UI Stats Display prefab’s text fields.

    Is there any way to improve the UIStatsDisplay.cs code so that it doesn’t include tooltips?

    Kind Regards, Sean

    #16398
    Sean
    Level 11
    Former Patron
    Helpful?
    Up
    0
    ::

    The error that comes up, for those who’d like to know, is:

    <code>AmbiguousMatchException: Multiple custom attributes of the same type found.
    System.Attribute.GetCustomAttribute (System.Reflection.MemberInfo element, System.Type attributeType, System.Boolean inherit) (at <834b2ded5dad441e8c7a4287897d63c7>:0)
    System.Attribute.GetCustomAttribute (System.Reflection.MemberInfo element, System.Type attributeType) (at <834b2ded5dad441e8c7a4287897d63c7>:0)
    UIStatsDsiplay.UpdateStatsFields () (at Assets/Scripts/UI/UIStatsDisplay.cs:58)
    UIStatsDsiplay.OnEnable () (at Assets/Scripts/UI/UIStatsDisplay.cs:17)
    UnityEngine.GameObject:SetActive(GameObject, Boolean)
    GameManager:PauseGame() (at Assets/Scripts/GameManager.cs:186)
    GameManager:CheckForPauseAndResume() (at Assets/Scripts/GameManager.cs:203)
    GameManager:Update() (at Assets/Scripts/GameManager.cs:98)</code>
    #16400
    Terence
    Level 30
    Keymaster
    Helpful?
    Up
    0
    ::

    Sean, can you share your code for UIStatsDisplay here? The [Tooltip] attribute shouldn’t be causing any error that you are seeing.

    #16408
    Sean
    Level 11
    Former Patron
    Helpful?
    Up
    1
    ::

    Sure thing!

    <code>using System.Collections;
    using System.Collections.Generic;
    using System.Reflection;
    using System.Text;
    using TMPro;
    using UnityEngine;
    
    public class UIStatsDisplay : MonoBehaviour {
    
        public PlayerStats player;
        public bool displayCurrentHealth = false;
        public bool updateInEditor = false;
        TextMeshProUGUI statNames;
        TextMeshProUGUI statValues;
    
        private void OnEnable() {
            UpdateStatsFields();
        }
    
        private void OnDrawGizmosSelected(){
            if (updateInEditor) {
                UpdateStatsFields();
            }
        }
    
        public void UpdateStatsFields() {
            if (!player) {
                return;
            }
    
            if (!statNames) {
                statNames = transform.GetChild(0).GetComponent<TextMeshProUGUI>();
            }
            if (!statValues) {
                statValues = transform.GetChild(1).GetComponent<TextMeshProUGUI>();
            }
    
            //Use StringBuilders so that the string manipulation runs faster.
            StringBuilder names = new StringBuilder();
            StringBuilder values = new StringBuilder();
    
            //Add the current health to the stat box.
            if (displayCurrentHealth) {
                names.AppendLine("Health");
                values.AppendLine(player.CurrentHealth.ToString());
            }
    
            FieldInfo[] fields = typeof(CharacterData.Stats).GetFields(BindingFlags.Public | BindingFlags.Instance);
            foreach (FieldInfo field in fields) {
                //Render stat names.
                names.AppendLine(field.Name);
    
                //Get the stat value.
                object value = field.GetValue(player.Stats);
                float fvalue = value is int ? (int)value : (float)value;
    
                //Print it as a percentage if it has an attribute assigned and is a float.
                PropertyAttribute attribute = (PropertyAttribute)PropertyAttribute.GetCustomAttribute(field, typeof(PropertyAttribute));
                if (attribute != null && field.FieldType == typeof(float)) {
                    float percentage = Mathf.Round(fvalue * 100 - 100);
    
                    //If the stat value is 0, just put a dash.
                    if (Mathf.Approximately(percentage, 0)) {
                        values.Append('-').Append('\n');
                    } else {
                        if (percentage > 0) {
                            values.Append('+');
                        }
                        values.Append(percentage).Append('%').Append('\n');
                    }
                } else {
                    values.Append(fvalue).Append('\n');
                }
    
                //Update the fields with the strings we built.
                statNames.text = PrettifyNames(names);
                statValues.text = values.ToString();
            }
        }
    
        public static string PrettifyNames(StringBuilder input) {
            //Return an empty string if StringBuilder is empty.
            if (input.Length <= 0) {
                return string.Empty;
            }
    
            StringBuilder result = new StringBuilder();
            char last = '\0';
            for (int i = 0; i < input.Length; i++) {
                char c = input[i];
    
                //Check when to uppercase or add spaces to a character.
                if (last == '\0' || char.IsWhiteSpace(last)) {
                    c = char.ToUpper(c);
                } else if (char.IsUpper(c)) {
                    result.Append(' '); //Insert space before capital letter
                }
                result.Append(c);
    
                last = c;
            }
    
            return result.ToString();
        }
    
        private void Reset() {
            player = FindObjectOfType<PlayerStats>();
        }
    
    }
    </code>
    has upvoted this post.
    #16419
    Terence
    Level 30
    Keymaster
    Helpful?
    Up
    0
    ::

    You’re right that there is an issue with the UIStatsDisplay script. The issue is in line 58:

    foreach (FieldInfo field in fields)
    {
        // Render stat names.
        names.AppendLine(field.Name);
    
        // Get the stat value.
        object val = field.GetValue(player.Stats);
        float fval = val is int ? (int)val : (float)val;
    
        // Print it as a percentage if it has a Range or Min attribute assigned and is a float.
        PropertyAttribute attribute = (PropertyAttribute)PropertyAttribute.GetCustomAttribute(field, typeof(PropertyAttribute));
        if (attribute != null && field.FieldType == typeof(float))
        {
            float percentage = Mathf.Round(fval * 100 - 100);
    
            // If the stat value is 0, just put a dash.
            if (Mathf.Approximately(percentage, 0))
            {
                values.Append('-').Append('\n');
            }
            else
            {
                if (percentage > 0)
                    values.Append('+');
                values.Append(percentage).Append('%').Append('\n');
            }
        }
        else
        {
            values.Append(fval).Append('\n');
        }
    
        // Updates the fields with the strings we built.
        statNames.text = PrettifyNames(names);
        statValues.text = values.ToString();
    }

    Specifically, we need to change this line:

    PropertyAttribute attribute = (PropertyAttribute)PropertyAttribute.GetCustomAttribute(field, typeof(PropertyAttribute));

    To this line:

    PropertyAttribute attribute = (PropertyAttribute)field.GetCustomAttribute<RangeAttribute>() ?? field.GetCustomAttribute<MinAttribute>();

    Previously, when you added the [Tooltip] attribute, this caused the PropertyAttribute.GetCustomAttribute() method to break because there were multiple attributes, and this function is coded to throw an error if there are multiple attributes.

    field.GetCustomAttribute() doesn’t cause the same error, because it works like GetComponent() and only picks out relevant attributes, instead of the first attribute like PropertyAttribute.GetCustomAttribute() does.

    Thanks for finding this bug! Here’s a badge for your account:

    Bug Reporter
    #16435
    Sean
    Level 11
    Former Patron
    Helpful?
    Up
    0
    ::Minion - In Awe
    #16438
    Sean
    Level 11
    Former Patron
    Helpful?
    Up
    0
    ::

    Unfortunately, I got all excited, implemented it, and then both functions of “GetCustomAttribute()” threw an error.

    They both show: “Error (active) CS0411 The type arguments for method ‘CustomAttributeExtensions.GetCustomAttribute<T>(Assembly)’ cannot be inferred from the usage. Try specifying the type arguments explicitly. Assembly-CSharp G:\Unity Projects\Vampire Survivor – Space Clone\Assets\Scripts\UI\UIStatsDisplay.cs 58”

    #16439
    Terence
    Level 30
    Keymaster
    Helpful?
    Up
    0
    ::

    You are missing the angle brackets. My bad. I missed them out when copy pasting:

    PropertyAttribute attribute = (PropertyAttribute)field.GetCustomAttribute<RangeAttribute>() ?? field.GetCustomAttribute<MinAttribute>();
Viewing 8 posts - 1 through 8 (of 8 total)
  • You must be logged in to reply to this topic.

Go to Login Page →


Advertisement below: