using UnityEngine; using UnityEngine.UI; using TMPro; using System.Collections.Generic; using System.Collections; namespace BulletHellTemplate { /// /// Manages the user interface during gameplay, including player stats, skills, and input handling for both mobile and PC platforms. /// public class UIGameplay : MonoBehaviour { #region Mobile Controls [Header("Mobile Controls")] [Tooltip("Reference to the Joystick for mobile movement")] public Joystick joystick; // Reference to the Joystick for mobile movement [Header("Mobile Skill Joysticks")] [Tooltip("Array to hold skill joysticks for mobile")] public SkillJoystick[] skillJoysticks; // Array to hold skill joysticks for mobile #endregion #region PC Controls [Header("PC Controls")] [Tooltip("Reference to the PC Input Controller")] public PCInputController pcInputController; // Reference to the PC Input Controller [Tooltip("Reference to the PC Skill Controller")] public PCSkillController pcSkillController; // Reference to the PC Skill Controller #endregion [Header("Skill Icons")] [Tooltip("Array to hold skill images")] public SkillImage[] skillImages; [Header("Final Boss")] [Tooltip("Final boss message GameObject")] public GameObject finalBossMessage; private MonsterEntity finalBossEntity; private bool showBossHP; [Tooltip("Time to close the final boss message")] public float timeToCloseMessage = 3f; [Header("Gameplay Rules Info")] [Tooltip("Timer text")] public TextMeshProUGUI timer; [Tooltip("Monsters killed text")] public TextMeshProUGUI monstersKilled; [Tooltip("Gold gain text")] public TextMeshProUGUI goldGain; [Tooltip("Insufficient MP text")] public TextMeshProUGUI insufficientMPText; [Tooltip("Pause menu GameObject")] public GameObject pauseMenu; public Image minimapImage; [Header("Upgrade Infos")] [Tooltip("Perk entry prefab")] public PerkEntry perkEntryPrefab; [Tooltip("Perk container transform")] public Transform perkContainer; public Button openUpgradesButton; public GameObject updateAvailable; [Header("Skill Perk Images")] [Tooltip("Array to hold skill perk images")] public SkillsPerkImage[] skillPerkImages; // Array to hold skill perk images [Header("Stat Perk Images")] [Tooltip("Array to hold stat perk images")] public StatPerkImage[] statPerkImages; // Array to hold stat perk images [Header("Boss HP Bar")] [Tooltip("Boss HP bar Image")] public Image bossHpBar; [Tooltip("Boss HP container GameObject")] public GameObject bossHpContainer; [Tooltip("Timer container GameObject")] public GameObject timerContainer; [Header("End Game Screen")] [Tooltip("End game screen GameObject")] public GameObject endGameScreen; [Tooltip("End game message text")] public TextMeshProUGUI endGameMessage; [Tooltip("Victory objects to activate")] public GameObject[] VictoryObjects; [Tooltip("Defeat objects to activate")] public GameObject[] DefeatObjects; [Tooltip("Victory audio clip")] public AudioClip VictoryAudio; [Tooltip("Defeat audio clip")] public AudioClip DefeatAudio; [Header("Locked Perk Sprites")] [Tooltip("Sprite for locked skills")] public Sprite lockedSkillSprite; [Tooltip("Sprite for locked stats")] public Sprite lockedStatSprite; [Tooltip("Sprite for unlocked slots")] public Sprite unlockedSprite; [Header("UI Character Info")] public Image characterIcon; [Tooltip("Health bar Image")] public Image hpBar; [Tooltip("Mana bar Image")] public Image mpBar; [Tooltip("Experience bar Image")] public Image xpBar; [Tooltip("Current text")] public TextMeshProUGUI hpCurrent; public TextMeshProUGUI energyCurrent; public TextMeshProUGUI xpCurrent; [Tooltip("Level text")] public TextMeshProUGUI level; [Tooltip("Health text")] public TextMeshProUGUI hpText; [Tooltip("Health regeneration text")] public TextMeshProUGUI hpRegenText; [Tooltip("Health leech text")] public TextMeshProUGUI hpLeechText; [Tooltip("Mana text")] public TextMeshProUGUI mpText; [Tooltip("Mana regeneration text")] public TextMeshProUGUI mpRegenText; [Tooltip("Damage text")] public TextMeshProUGUI damageText; [Tooltip("Attack speed text")] public TextMeshProUGUI attackSpeedText; [Tooltip("Cooldown reduction text")] public TextMeshProUGUI cooldownReductionText; [Tooltip("Critical rate text")] public TextMeshProUGUI criticalRateText; [Tooltip("Critical damage multiplier text")] public TextMeshProUGUI criticalDamageMultiplierText; [Tooltip("Defense text")] public TextMeshProUGUI defenseText; [Tooltip("Shield text")] public TextMeshProUGUI shieldText; [Tooltip("Move speed text")] public TextMeshProUGUI moveSpeedText; [Tooltip("Collect range text")] public TextMeshProUGUI collectRangeText; private CharacterEntity characterEntity; private float currentHp; private bool isUpdatingHpBar = false; private int upgradeAmount = 0; public static UIGameplay Singleton { get; private set; } // Singleton instance private void Awake() { // Implement singleton pattern if (Singleton == null) { Singleton = this; } else { Destroy(gameObject); } } private void Start() { StartCoroutine(InitializeUpgradeButton()); StartCoroutine(BlinkGameObjectWhileUpgrading(updateAvailable)); } /// /// Attempts to initialize the upgrade button when GameplayManager is ready. /// private IEnumerator InitializeUpgradeButton() { // Keep trying until GameplayManager is available while (GameplayManager.Singleton == null) { yield return null; // Wait for the next frame } // Check the upgrade mode and set up the button accordingly if (openUpgradesButton != null && GameplayManager.Singleton.upgradeMode == GameplayManager.UpgradeMode.UpgradeOnButtonClick) { openUpgradesButton.interactable = false; openUpgradesButton.onClick.AddListener(OnClickToChoicePowerUp); } else if (openUpgradesButton != null) { openUpgradesButton.gameObject.SetActive(false); } } void Update() { if (characterEntity != null) { // Mobile movement using joystick if (joystick != null) { Vector3 direction = new Vector3(joystick.Horizontal, 0, joystick.Vertical); characterEntity.Move(direction); // Pass the joystick direction } UpdateUI(); // Update the UI with the latest character stats } // Update timer and other UI elements timer.text = GameplayManager.Singleton.GetFormattedTime(); monstersKilled.text = GameplayManager.Singleton.GetMonstersKilled().ToString(); goldGain.text = GameplayManager.Singleton.GetGainGold().ToString(); // Update skill cooldowns UpdateSkillCooldowns(); if (characterEntity != null) { // Mobile aiming with skill joysticks if (skillJoysticks != null) { foreach (var skillJoystick in skillJoysticks) { if (skillJoystick != null) { Vector3 direction = new Vector3(skillJoystick.Horizontal, 0, skillJoystick.Vertical); characterEntity.UpdateDirectionalAim(new Vector2(direction.x, direction.z),skillJoystick.skillIndex); } } } } if (showBossHP) { if (finalBossEntity != null) { bossHpBar.fillAmount = finalBossEntity.GetCurrentHP() / finalBossEntity.GetMaxHP(); } else { bossHpBar.fillAmount = 0; } } } /// /// Shows the final boss message and triggers camera shake. /// public void ShowFinalBossMessage() { if (finalBossMessage != null) { finalBossMessage.SetActive(true); StartCoroutine(HideFinalBossMessageAfterDelay(timeToCloseMessage)); if (TopDownCameraController.Singleton != null) { TopDownCameraController.Singleton.TriggerCameraShake(); } } } private IEnumerator HideFinalBossMessageAfterDelay(float delay) { yield return new WaitForSeconds(delay); finalBossMessage.SetActive(false); timerContainer.SetActive(false); showBossHP = true; bossHpContainer.SetActive(true); } /// /// Displays the power-up choices when leveling up. /// public void OnLevelUpChoicePowerUp() { foreach (Transform child in perkContainer) { Destroy(child.gameObject); } List perks = GameplayManager.Singleton.GetRandomPerks(); foreach (var perk in perks) { PerkEntry perkEntry = Instantiate(perkEntryPrefab, perkContainer); if (perk is SkillPerkData skillPerk) { perkEntry.SetupEntry(skillPerk); } else if (perk is StatPerkData statPerk) { perkEntry.SetupEntry(statPerk); } else if (perk is SkillData baseSkill) { perkEntry.SetupEntry(baseSkill); } } pauseMenu.SetActive(true); } public void AddUpgradePoints() { upgradeAmount++; openUpgradesButton.interactable = true; } public void OnClickToChoicePowerUp() { if (upgradeAmount > 0) { GameplayManager.Singleton.TogglePause(); OnLevelUpChoicePowerUp(); upgradeAmount--; } else { Debug.Log("No upgrades available to choose."); CloseChoicePowerUp(); } } /// /// Continuously checks upgradeAmount and makes a GameObject blink when upgradeAmount is greater than 0. /// /// The GameObject to blink. public IEnumerator BlinkGameObjectWhileUpgrading(GameObject targetObject) { if (targetObject == null) { Debug.LogWarning("Target GameObject is null. Cannot blink."); yield break; } bool initialActiveState = targetObject.activeSelf; while (true) // Loop for the duration of the game { if (upgradeAmount > 0) { targetObject.SetActive(!targetObject.activeSelf); yield return new WaitForSeconds(0.5f); } else { if (!targetObject.activeSelf != initialActiveState) { targetObject.SetActive(initialActiveState); } yield return null; } } } /// /// Automatically selects a random power-up for the player. /// public void OnRandomChoicePowerUp() { // Get shuffled perks List perks = GameplayManager.Singleton.GetRandomPerks(); if (perks.Count > 0) { // Choose a random perk object randomPerk = perks[Random.Range(0, perks.Count)]; // Apply the chosen perk if (randomPerk is SkillPerkData skillPerk) { int currentLevel = GameplayManager.Singleton.GetSkillLevel(skillPerk); if (currentLevel < skillPerk.maxLevel) { GameplayManager.Singleton.SetPerkLevel(skillPerk); // Set the new level characterEntity.ApplySkillPerk(skillPerk); // Apply the perk } else { Debug.LogWarning($"SkillPerk {skillPerk.name} is already at max level."); } } else if (randomPerk is StatPerkData statPerk) { int currentLevel = GameplayManager.Singleton.GetPerkLevel(statPerk); if (currentLevel < GameplayManager.Singleton.maxLevelStatPerks) { GameplayManager.Singleton.SetPerkLevel(statPerk); // Set the new level characterEntity.ApplyStatPerk(statPerk, currentLevel + 1); // Apply the stat perk } else { Debug.LogWarning($"StatPerk {statPerk.statType} is already at max level."); } } else if (randomPerk is SkillData baseSkill) { int currentLevel = GameplayManager.Singleton.GetBaseSkillLevel(baseSkill); if (currentLevel < baseSkill.skillLevels.Count - 1) { GameplayManager.Singleton.LevelUpBaseSkill(baseSkill); // Level up the base skill } else { Debug.LogWarning($"BaseSkill {baseSkill.skillName} is already at max level."); } } // Update UI UpdateSkillPerkUI(); UpdateStatPerkUI(); } else { Debug.LogWarning("No perks available to choose from."); } CloseChoicePowerUp(); } /// /// Closes the power-up choice menu and re-enables the button if there are remaining upgrades. /// public void CloseChoicePowerUp() { // Clear existing perks in the UI foreach (Transform child in perkContainer) { Destroy(child.gameObject); } // Handle button interaction for UpgradeOnButtonClick mode if (GameplayManager.Singleton.upgradeMode == GameplayManager.UpgradeMode.UpgradeOnButtonClick) { openUpgradesButton.interactable = upgradeAmount > 0; // Enable if upgrades remain, disable otherwise } else { openUpgradesButton.interactable = false; // Always disable in other modes } // Handle pause logic for UpgradeOnLevelUp mode if (GameplayManager.Singleton.upgradeMode == GameplayManager.UpgradeMode.UpgradeOnLevelUp || GameplayManager.Singleton.upgradeMode == GameplayManager.UpgradeMode.UpgradeOnButtonClick) { GameplayManager.Singleton.TogglePause(); } // Hide the pause menu pauseMenu.SetActive(false); // Update the UI UpdateSkillPerkUI(); UpdateSkillIcons(); } /// /// Sets the final boss entity for HP tracking. /// /// The final boss monster entity. public void SetFinalBoss(MonsterEntity boss) { finalBossEntity = boss; } /// /// Updates the skill perk UI elements. /// public void UpdateSkillPerkUI() { if (characterEntity != null && skillPerkImages != null) { for (int i = 0; i < skillPerkImages.Length; i++) { SkillsPerkImage perkImage = skillPerkImages[i]; if (i < characterEntity.GetSkillsPerkData().Count) { // Display active skill perks SkillPerkData skillPerk = characterEntity.GetSkillsPerkData()[i]; int perkLevel = GameplayManager.Singleton.GetSkillLevel(skillPerk); // Set the perk icon perkImage.perkIcon.sprite = skillPerk.icon; perkImage.perkLevel.text = perkLevel.ToString(); // Activate max level icon if the skill perk is evolved bool isEvolved = (perkLevel >= skillPerk.maxLevel) && skillPerk.hasEvolution && characterEntity.HasStatPerk(skillPerk.perkRequireToEvolveSkill); perkImage.maxLevelPerkIcon.gameObject.SetActive(isEvolved); } else if (i < GameplayManager.Singleton.maxSkills) { // Available slot but no skill assigned perkImage.perkIcon.sprite = unlockedSprite; perkImage.perkLevel.text = ""; perkImage.maxLevelPerkIcon.gameObject.SetActive(false); } else { // Locked slots perkImage.perkIcon.sprite = lockedSkillSprite; perkImage.perkLevel.text = ""; perkImage.maxLevelPerkIcon.gameObject.SetActive(false); } } } } /// /// Updates the stat perk UI elements. /// public void UpdateStatPerkUI() { if (characterEntity != null && statPerkImages != null) { for (int i = 0; i < statPerkImages.Length; i++) { StatPerkImage statPerkImage = statPerkImages[i]; if (i < characterEntity.GetStatsPerkData().Count) { // Display active stat perks StatPerkData statPerk = characterEntity.GetStatsPerkData()[i]; int perkLevel = GameplayManager.Singleton.GetPerkLevel(statPerk); // Set the stat perk icon statPerkImage.perkIcon.sprite = statPerk.icon; statPerkImage.perkLevel.text = perkLevel.ToString(); // Activate max level icon if the stat perk reaches max level bool isMaxLevel = perkLevel >= GameplayManager.Singleton.maxLevelStatPerks; statPerkImage.maxLevelStatIcon.gameObject.SetActive(isMaxLevel); } else if (i < GameplayManager.Singleton.maxStats) { // Available slot but no stat assigned statPerkImage.perkIcon.sprite = unlockedSprite; statPerkImage.perkLevel.text = ""; statPerkImage.maxLevelStatIcon.gameObject.SetActive(false); } else { // Locked slots statPerkImage.perkIcon.sprite = lockedStatSprite; statPerkImage.perkLevel.text = ""; statPerkImage.maxLevelStatIcon.gameObject.SetActive(false); } } } } /// /// Sets the character entity and initializes related components. /// /// The character entity to set. public void SetCharacterEntity(CharacterEntity entity) { characterEntity = entity; if (skillJoysticks != null) { foreach (SkillJoystick skillJoystick in skillJoysticks) { skillJoystick.Setup(characterEntity); } } if (pcSkillController != null) { pcSkillController.Setup(characterEntity); } // Initialize current HP currentHp = characterEntity.GetCurrentHP(); characterIcon.sprite = characterEntity.GetCharacterData().icon; // Update skill icons UpdateSkillIcons(); } /// /// Toggles the pause menu and game state. /// public void OnClickTogglePauseGame() { if (GameplayManager.Singleton == null) { Debug.LogError("GameplayManager Singleton is not available."); return; } GameplayManager.Singleton.TogglePause(); if (pauseMenu == null) { Debug.LogError("Pause menu GameObject is not assigned."); return; } pauseMenu.SetActive(!pauseMenu.activeSelf); } /// /// Updates the user interface elements with the latest character stats. /// private void UpdateUI() { if (characterEntity != null) { float newHp = characterEntity.GetCurrentHP(); if (newHp != currentHp && !isUpdatingHpBar) { StartCoroutine(UpdateHpBar(currentHp, newHp)); currentHp = newHp; } hpText.text = $"HP: {Mathf.CeilToInt(characterEntity.GetCurrentHP())} / {Mathf.CeilToInt(characterEntity.GetMaxHP())}"; hpRegenText.text = $"HP Regen: {Mathf.CeilToInt(characterEntity.GetCurrentHPRegen())}"; hpLeechText.text = $"HP Leech: {characterEntity.GetCurrentHPLeech():F2}%"; mpText.text = $"MP: {Mathf.CeilToInt(characterEntity.GetCurrentMP())} / {Mathf.CeilToInt(characterEntity.GetMaxMP())}"; mpRegenText.text = $"MP Regen: {Mathf.CeilToInt(characterEntity.GetCurrentMPRegen())}"; damageText.text = $"Damage: {Mathf.CeilToInt(characterEntity.GetCurrentDamage())}"; attackSpeedText.text = $"Attack Speed: {characterEntity.GetCurrentAttackSpeed():F2}"; cooldownReductionText.text = $"Cooldown Reduction: {characterEntity.GetCurrentCooldownReduction():F2}%"; criticalRateText.text = $"Critical Rate: {characterEntity.GetCurrentCriticalRate():F2}%"; criticalDamageMultiplierText.text = $"Critical Damage Multiplier: x{Mathf.CeilToInt(characterEntity.GetCurrentCriticalDamageMultiplier())}"; defenseText.text = $"Defense: {Mathf.CeilToInt(characterEntity.GetCurrentDefense())}"; shieldText.text = $"Shield: {Mathf.CeilToInt(characterEntity.GetCurrentShield())}"; moveSpeedText.text = $"Move Speed: {characterEntity.GetCurrentMoveSpeed():F2}"; collectRangeText.text = $"Collect Range: {Mathf.CeilToInt(characterEntity.GetCurrentCollectRange())}"; float maxMP = characterEntity.GetMaxMP(); float currentMP = characterEntity.GetCurrentMP(); mpBar.fillAmount = (float)currentMP / maxMP; int currentXP = characterEntity.GetCurrentXP(); int xpToNextLevel = characterEntity.GetXPToNextLevel(); int currentLevel = characterEntity.GetCurrentLevel(); xpBar.fillAmount = (float)currentXP / xpToNextLevel; hpCurrent.text = $"{Mathf.CeilToInt(characterEntity.GetCurrentHP())} / {Mathf.CeilToInt(characterEntity.GetMaxHP())}"; energyCurrent.text = $"{Mathf.CeilToInt(characterEntity.GetCurrentMP())} / {Mathf.CeilToInt(characterEntity.GetMaxMP())}"; xpCurrent.text = $"{Mathf.CeilToInt(currentXP)} / {Mathf.CeilToInt(xpToNextLevel)}"; level.text = $"Level: {currentLevel}"; } } private IEnumerator UpdateHpBar(float startHp, float endHp) { isUpdatingHpBar = true; float startFillAmount = startHp / characterEntity.GetMaxHP(); float endFillAmount = endHp / characterEntity.GetMaxHP(); float elapsedTime = 0f; float duration = 0.5f; // Duration for the flash effect while (elapsedTime < duration) { float t = elapsedTime / duration; hpBar.fillAmount = Mathf.Lerp(startFillAmount, endFillAmount, t); elapsedTime += Time.deltaTime; yield return null; } hpBar.fillAmount = endFillAmount; isUpdatingHpBar = false; } /// /// Updates the skill icons in the UI. /// private void UpdateSkillIcons() { if (skillImages != null && characterEntity != null) { for (int i = 0; i < skillImages.Length; i++) { SkillImage skillImage = skillImages[i]; if (skillImage.ImageComponent != null) { try { Sprite skillIcon = characterEntity.GetSkillIcon(skillImage.Index); skillImage.ImageComponent.sprite = skillIcon; // Get skill data SkillData skillData = characterEntity.GetSkillData(skillImage.Index); int currentLevel = GameplayManager.Singleton.GetBaseSkillLevel(skillData); int maxLevelIndex = skillData.skillLevels.Count - 1; bool isEvolved = skillData.skillLevels[currentLevel].isEvolved; // Activate max level icon if the skill is evolved skillImage.maxLevelSkillIcon.gameObject.SetActive(isEvolved); skillImage.CooldownImage.fillAmount = 0; skillImage.CooldownText.text = ""; } catch (System.Exception ex) { Debug.Log($"Error fetching skill icon for index {skillImage.Index}: {ex.Message}"); } } else { Debug.LogError($"ImageComponent of SkillImage at index {i} is null."); } } } } private void UpdateSkillCooldowns() { if (characterEntity != null) { PlatformType currentPlatform = GameInstance.Singleton.GetCurrentPlatform(); if (currentPlatform == PlatformType.PC && pcSkillController != null) { for (int i = 0; i < skillImages.Length; i++) { SkillImage skillImage = skillImages[i]; if (skillImage.ImageComponent != null) { float cooldown = pcSkillController.GetCurrentCooldown(skillImage.Index); float maxCooldown = pcSkillController.GetCooldownTime(skillImage.Index); if (cooldown > 0 && maxCooldown > 0) { skillImage.CooldownImage.fillAmount = cooldown / maxCooldown; skillImage.CooldownText.text = Mathf.Ceil(cooldown).ToString(); } else { skillImage.CooldownImage.fillAmount = 0; skillImage.CooldownText.text = ""; } } else { Debug.LogWarning($"SkillImage at index {i} has no ImageComponent."); } } } else if (currentPlatform == PlatformType.Mobile && skillJoysticks != null && skillJoysticks.Length > 0) { for (int i = 0; i < skillImages.Length; i++) { SkillImage skillImage = skillImages[i]; if (skillImage.ImageComponent != null) { SkillJoystick skillJoystick = null; if (i < skillJoysticks.Length) skillJoystick = skillJoysticks[i]; if (skillJoystick != null) { float cooldown = skillJoystick.GetCurrentCooldown(); float maxCooldown = skillJoystick.GetCooldownTime(); if (cooldown > 0 && maxCooldown > 0) { skillImage.CooldownImage.fillAmount = cooldown / maxCooldown; skillImage.CooldownText.text = Mathf.Ceil(cooldown).ToString(); } else { skillImage.CooldownImage.fillAmount = 0; skillImage.CooldownText.text = ""; } } } else { Debug.LogWarning($"SkillImage at index {i} has no ImageComponent."); } } } } } public void Revive() { endGameScreen.SetActive( false ); GameplayManager.Singleton.ResumeGame(); characterEntity.Revive(); } /// /// Displays the end game screen, showing victory or defeat based on the result. /// /// Set to true if the player won the game, false if they lost. public void DisplayEndGameScreen(bool won) { // Show the end game screen endGameScreen.SetActive(true); // Set the end game message text and color based on whether the player won or lost endGameMessage.text = won ? "You Win!" : "You Lose!"; endGameMessage.color = won ? Color.green : Color.red; // Activate objects and play audio based on victory or defeat if (won) { // Activate Victory objects ActivateObjects(VictoryObjects); // Play Victory audio AudioManager.Singleton.PlayAudio(VictoryAudio, "master"); } else { // Activate Defeat objects ActivateObjects(DefeatObjects); // Play Defeat audio AudioManager.Singleton.PlayAudio(DefeatAudio, "master"); } } /// /// Activates all the objects in the given array. /// /// Array of GameObjects to activate. private void ActivateObjects(GameObject[] objects) { foreach (GameObject obj in objects) { if (obj != null) { obj.SetActive(true); } } } /// /// Returns to the main menu. /// public void ReturnToMainMenu() { GameManager.Singleton.ReturnToMainMenu(); } public CharacterEntity GetCharacterEntity() { return characterEntity; } /// /// Displays a status message with the specified color. /// /// The message to display. /// The color of the message text. public void DisplayMPInsufficientMessage(string message) { insufficientMPText.text = message; StartCoroutine(HideStatusMessageAfterDelay(insufficientMPText, 2f)); // Hide message after delay } /// /// Coroutine to hide the status message after a delay. /// /// The TextMeshProUGUI component displaying the status message. /// The delay in seconds before hiding the message. private IEnumerator HideStatusMessageAfterDelay(TextMeshProUGUI _insufficientMPText, float delay) { yield return new WaitForSeconds(delay); _insufficientMPText.text = string.Empty; } } /// /// Struct to hold skill image components for the UI. /// [System.Serializable] public struct SkillImage { [Tooltip("Image component to show the skill icon")] public Image ImageComponent; // Image component to show the skill icon [Tooltip("Image to show cooldown progress")] public Image CooldownImage; // Image to show cooldown progress [Tooltip("Text to show remaining cooldown time")] public TextMeshProUGUI CooldownText; // Text to show remaining cooldown time public Image maxLevelSkillIcon; // Image to activate when skill is evolved [Tooltip("Index of the skill in the character's skill list")] public int Index; // Index of the skill in the character's skill list } /// /// Struct to hold skill perk images for the UI. /// [System.Serializable] public struct SkillsPerkImage { [Tooltip("Image component to show the skill perk icon")] public Image perkIcon; public Image maxLevelPerkIcon; [Tooltip("Text to show the current level of the skill perk")] public TextMeshProUGUI perkLevel; } /// /// Struct to hold stat perk images for the UI. /// [System.Serializable] public struct StatPerkImage { [Tooltip("Image component to show the stat perk icon")] public Image perkIcon; // Image component to show the stat perk icon [Tooltip("Text to show the current level of the stat perk")] public TextMeshProUGUI perkLevel; // Text to show the current level of the stat perk public Image maxLevelStatIcon; } }