Forum begins after the advertisement:
[Part 24] Character select screen not working properly
Home › Forums › Video Game Tutorial Series › Creating a Rogue-like Shoot-em Up in Unity › [Part 24] Character select screen not working properly
- This topic has 5 replies, 2 voices, and was last updated 9 hours, 32 minutes ago by
Jukers.
-
AuthorPosts
-
May 6, 2025 at 12:16 pm #18084::
I just finished part 24 and i noticed some major errors on my project, tried to find the source of it but didnt get lucky. I managed to make the generation of the characters on screen with the button on Character Selector Background, but when i play the game and select (click on) the character, nothing happens (click on the character and nothing happens, even if i play the game, the default character is the one chosen) The default character works, its description and everything works, but i cant change the character ill leave my scripts here if it helps
UIStatsDisplay.cs
using System.Text; using System.Reflection; using UnityEngine; using TMPro; public class UIStatDisplay : MonoBehaviour { public PlayerStats player; // The player that this stat display is rendering stats for. public CharacterData character; public bool displayCurrentHealth = false; public bool updateInEditor = false; TextMeshProUGUI statNames, statValues; // Update this stat display whenever it is set to be active. void OnEnable() { UpdateStatFields(); } void OnDrawGizmosSelected() { if(updateInEditor) UpdateStatFields(); } public CharacterData.Stats GetDisplayedStats() { if(player) return player.Stats; else if(character) return character.stats; return new CharacterData.Stats(); } public void UpdateStatFields() { if (!player && !character) return; // Get a reference to both Text objects to render stat names and stat values. if (!statNames) statNames = transform.GetChild(0).GetComponent<TextMeshProUGUI>(); if (!statValues) statValues = transform.GetChild(1).GetComponent<TextMeshProUGUI>(); // Render all stat names and values. // Use StringBuilders so that the string manipulation runs faster. StringBuilder names = new StringBuilder(); StringBuilder values = new StringBuilder(); 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) { // Add the stat name. names.AppendLine(field.Name); // Get the stat value. object val = field.GetValue(GetDisplayedStats()); float fval = val is int ? (int)val : (float)val; // Print it as a percentage if it has an attribute assigned and is a float. PropertyAttribute attribute = (PropertyAttribute)field.GetCustomAttribute<RangeAttribute>() ?? field.GetCustomAttribute<MinAttribute>(); 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(); } } 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(); } void Reset() { player = FindObjectOfType<PlayerStats>(); } }
CharacterData.cs
using UnityEngine; [CreateAssetMenu(fileName = "Character Data", menuName = "2D Top-down Rogue-like/Character Data")] public class CharacterData : ScriptableObject { [SerializeField] Sprite icon; public RuntimeAnimatorController animator; public Sprite Icon { get => icon; private set => icon = value; } [SerializeField] new string name; public string Name { get => name; private set => name = value; } [SerializeField] string fullName; public string FullName {get => fullName; private set => fullName = value;} [SerializeField] [TextArea] string characterDescription; public string CharacterDescription { get => characterDescription; private set => characterDescription = value; } [SerializeField] WeaponData startingWeapon; public WeaponData StartingWeapon { get => startingWeapon; private set => startingWeapon = value; } [System.Serializable] public struct Stats { public float maxHealth, recovery, armor; [Range(-1, 10)] public float moveSpeed, might, area; [Range(-1, 5)] public float speed, duration; [Range(-1, 10)] public int amount; [Range(-1, 1)] public float cooldown; [Range(-1, 10)] public float expGain; [Min(-1)] public float luck, growth, greed, curse; public float magnet; public int revival; public static Stats operator +(Stats s1, Stats s2) { s1.maxHealth += s2.maxHealth; s1.recovery += s2.recovery; s1.armor += s2.armor; s1.moveSpeed += s2.moveSpeed; s1.might += s2.might; s1.area += s2.area; s1.speed += s2.speed; s1.duration += s2.duration; s1.amount += s2.amount; s1.cooldown += s2.cooldown; s1.expGain += s2.expGain; s1.luck += s2.luck; s1.growth += s2.growth; s1.greed += s2.greed; s1.curse += s2.curse; s1.magnet += s2.magnet; return s1; } public static Stats operator *(Stats s1, Stats s2) { s1.maxHealth *= s2.maxHealth; s1.recovery *= s2.recovery; s1.armor *= s2.armor; s1.moveSpeed *= s2.moveSpeed; s1.might *= s2.might; s1.area *= s2.area; s1.speed *= s2.speed; s1.duration *= s2.duration; s1.amount *= s2.amount; s1.cooldown *= s2.cooldown; s1.luck *= s2.luck; s1.growth *= s2.growth; s1.greed *= s2.greed; s1.curse *= s2.curse; s1.magnet *= s2.magnet; return s1; } } public Stats stats = new Stats { maxHealth = 100, moveSpeed = 1, might = 1, amount = 0, area = 1, speed = 1, duration = 1, cooldown = 1, expGain = 1, luck = 1, greed = 1, growth = 1, curse = 1 }; }
UICharacterSelector.cs
using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using UnityEditor; using TMPro; public class UICharacterSelector : MonoBehaviour { public CharacterData defaultCharacter; public static CharacterData selected; public UIStatDisplay statsUI; [Header("Template")] public Toggle toggleTemplate; public string characterNamePath = "Charater Name"; public string weaponIconPath = "Weapon Icon"; public string characterIconPath = "Character Icon"; public List<Toggle> selectableToggles = new List<Toggle>(); [Header("DescriptionBox")] public TextMeshProUGUI characterFullName; public TextMeshProUGUI characterDescription; public Image selectedCharacterIcon; public Image selectedCharacterWeapon; void Start() { if(defaultCharacter) Select(defaultCharacter); } //public CharacterData characterData; public static CharacterData[] GetAllCharacterDataAssets() { List<CharacterData> characters = new List<CharacterData>(); //ramddomly pick a character if we are playing from the Game Scene #if UNITY_EDITOR string[] allAssetPaths = AssetDatabase.GetAllAssetPaths(); foreach (string assetPath in allAssetPaths) { if (assetPath.EndsWith(".asset")) { CharacterData characterData = AssetDatabase.LoadAssetAtPath<CharacterData>(assetPath); if (characterData != null) { characters.Add(characterData); } } } #else Debug.LogWarning("This function cannot be called on builds."); #endif return characters.ToArray(); } public static CharacterData GetData() { //use the selected chosen in the Select() function if(selected) return selected; else { //randomly pick a character if we are playing from the editor CharacterData[] characters = GetAllCharacterDataAssets(); if(characters.Length > 0) return characters[Random.Range(0, characters.Length)]; } return null; } public void Select(CharacterData character) { Debug.Log($"Character selected: {character.FullName}"); //update stat fields in character select screen if (statsUI == null) { Debug.LogError("statsUI is not assigned in the UICharacterSelector."); return; } if (characterFullName == null) Debug.LogError("characterFullName is not assigned."); if (characterDescription == null) Debug.LogError("characterDescription is not assigned."); if (selectedCharacterIcon == null) Debug.LogError("selectedCharacterIcon is not assigned."); if (selectedCharacterWeapon == null) Debug.LogError("selectedCharacterWeapon is not assigned."); selected = statsUI.character = character; statsUI.UpdateStatFields(); characterFullName.text = character.FullName; characterDescription.text = character.CharacterDescription; selectedCharacterIcon.sprite = character.Icon; selectedCharacterWeapon.sprite = character.StartingWeapon.icon; } }
Maybe i missed something on the inspector? Re-watched the video but got no success
May 6, 2025 at 7:00 pm #18116::Can you add the highlighted portion to your code and see if it produces the highlighted message in the Console when you select a character?
public void Select(CharacterData character) { Debug.Log($"Character selected: {character.FullName}"); //update stat fields in character select screen if (statsUI == null) { Debug.LogError("statsUI is not assigned in the UICharacterSelector."); return; } if (characterFullName == null) Debug.LogError("characterFullName is not assigned."); if (characterDescription == null) Debug.LogError("characterDescription is not assigned."); if (selectedCharacterIcon == null) Debug.LogError("selectedCharacterIcon is not assigned."); if (selectedCharacterWeapon == null) Debug.LogError("selectedCharacterWeapon is not assigned."); selected = statsUI.character = character; statsUI.UpdateStatFields(); characterFullName.text = character.FullName; characterDescription.text = character.CharacterDescription; selectedCharacterIcon.sprite = character.Icon; selectedCharacterWeapon.sprite = character.StartingWeapon.icon; Debug.Log($"Current selected on UICharacterSelector: {selected}"); }
May 6, 2025 at 10:22 pm #18125::Added it and it doenst appear on the console, only the first log that i have, as i highlighted bellow
public void Select(CharacterData character) { Debug.Log($"Character selected: {character.FullName}"); //update stat fields in character select screen if (statsUI == null) { Debug.LogError("statsUI is not assigned in the UICharacterSelector."); return; } if (characterFullName == null) Debug.LogError("characterFullName is not assigned."); if (characterDescription == null) Debug.LogError("characterDescription is not assigned."); if (selectedCharacterIcon == null) Debug.LogError("selectedCharacterIcon is not assigned."); if (selectedCharacterWeapon == null) Debug.LogError("selectedCharacterWeapon is not assigned."); selected = statsUI.character = character; statsUI.UpdateStatFields(); characterFullName.text = character.FullName; characterDescription.text = character.CharacterDescription; selectedCharacterIcon.sprite = character.Icon; selectedCharacterWeapon.sprite = character.StartingWeapon.icon; Debug.Log($"Current selected on UICharacterSelector: {selected}"); }
May 7, 2025 at 9:09 am #18133::That means something is stopping the function from executing entirely, so your
selected
variable is never assigned.Are any of the highlighted lines below appearing in your Console as well?
public void Select(CharacterData character) { Debug.Log($"Character selected: {character.FullName}"); //update stat fields in character select screen if (statsUI == null) { Debug.LogError("statsUI is not assigned in the UICharacterSelector."); return; } if (characterFullName == null) Debug.LogError("characterFullName is not assigned."); if (characterDescription == null) Debug.LogError("characterDescription is not assigned."); if (selectedCharacterIcon == null) Debug.LogError("selectedCharacterIcon is not assigned."); if (selectedCharacterWeapon == null) Debug.LogError("selectedCharacterWeapon is not assigned."); selected = statsUI.character = character; statsUI.UpdateStatFields(); characterFullName.text = character.FullName; characterDescription.text = character.CharacterDescription; selectedCharacterIcon.sprite = character.Icon; selectedCharacterWeapon.sprite = character.StartingWeapon.icon; Debug.Log($"Current selected on UICharacterSelector: {selected}"); }
May 7, 2025 at 11:16 pm #18134::Not really, i only get this error
NullReferenceException: Object reference not set to an instance of an object UIStatDisplay.UpdateStatFields () (at Assets/Scripts/UI/UIStatsDisplay.cs:49) UICharacterSelector.Select (CharacterData character) (at Assets/Scripts/UICharacterSelector.cs:88) UICharacterSelector.Start () (at Assets/Scripts/UICharacterSelector.cs:28)
that points to UIStatsDisplay.cs UpdateStatFields Function
public void UpdateStatFields() { if (!player && !character) return; // Get a reference to both Text objects to render stat names and stat values. if (!statNames) statNames = transform.GetChild(0).GetComponent<TextMeshProUGUI>(); if (!statValues) statValues = transform.GetChild(1).GetComponent<TextMeshProUGUI>(); // Render all stat names and values. // Use StringBuilders so that the string manipulation runs faster. StringBuilder names = new StringBuilder(); StringBuilder values = new StringBuilder(); if(displayCurrentHealth) { names.AppendLine("Health"); values.AppendLine(player.CurrentHealth.ToString()); //This line } FieldInfo[] fields = typeof(CharacterData.Stats).GetFields(BindingFlags.Public | BindingFlags.Instance); foreach (FieldInfo field in fields) { // Add the stat name. names.AppendLine(field.Name); // Get the stat value. object val = field.GetValue(GetDisplayedStats()); float fval = val is int ? (int)val : (float)val; // Print it as a percentage if it has an attribute assigned and is a float. PropertyAttribute attribute = (PropertyAttribute)field.GetCustomAttribute<RangeAttribute>() ?? field.GetCustomAttribute<MinAttribute>(); 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(); } }
Which i don’t think it relates to the malfunctioning i am experiencing
Also, the default character is not setted up as it should be when entering the select character screen (the values are the example values i made when creating the screens) When i created the characters in the editor (UI Character Selector button to generate characters) the template changed to the first character created, this is not shown in the video, so i don’t know if it is the expected behavior
May 8, 2025 at 2:44 am #18135::Update: I forgot to remove the “Display Current Health” on the UIStatsDisplay of the character selector, and the error on the console doesn’t appear anymore.
and this Log appears and updates the description box (set the description box and UIStats to the default character) but when i click a character it doesn’t update the info neither selects the character (when i enter the game, the character seems to be random) Log Below:
public void Select(CharacterData character) { Debug.Log($"Character selected: {character.FullName}"); //update stat fields in character select screen if (statsUI == null) { Debug.LogError("statsUI is not assigned in the UICharacterSelector."); return; } if (characterFullName == null) Debug.LogError("characterFullName is not assigned."); if (characterDescription == null) Debug.LogError("characterDescription is not assigned."); if (selectedCharacterIcon == null) Debug.LogError("selectedCharacterIcon is not assigned."); if (selectedCharacterWeapon == null) Debug.LogError("selectedCharacterWeapon is not assigned."); selected = statsUI.character = character; statsUI.UpdateStatFields(); characterFullName.text = character.FullName; characterDescription.text = character.CharacterDescription; selectedCharacterIcon.sprite = character.Icon; selectedCharacterWeapon.sprite = character.StartingWeapon.icon; Debug.Log($"Current selected on UICharacterSelector: {selected}"); } }
-
AuthorPosts
- You must be logged in to reply to this topic.