Slider continuation... xp percentage slider

xpSlider

Here are my scripts…hope you like it

Experience.cs
using RPG.Saving;
using UnityEngine;

namespace RPG.Stats
{
    public class Experience : MonoBehaviour, ISaveable
    {
        [SerializeField] float experiencePoints = 0;
        [SerializeField] int currentLevel = 1;
        ExperienceDisplay displayScript;
        BaseStats baseStats;

        private void Start()
        {
            displayScript = FindObjectOfType<ExperienceDisplay>();
            baseStats = GetComponent<BaseStats>();
        }


        public void GainExperience(float xp)
        {
            experiencePoints += xp;
            currentLevel = baseStats.GetLevel();
            displayScript.ChangeXpHud(currentLevel);
        }

        public float GetXp()
        {
            return experiencePoints;
        }


        public object CaptureState()
        {
            return experiencePoints;
        }

        public void RestoreState(object state)
        {
            experiencePoints = (float)state;
        }
    }
}
ExperienceDisplay.cs
using System.Collections;
using UnityEngine;
using UnityEngine.UI;
using TMPro;

namespace RPG.Stats
{
    public class ExperienceDisplay : MonoBehaviour
    {
        [SerializeField] Slider xpPercentSlider;
        [SerializeField] TextMeshProUGUI xpText;
        [SerializeField] TextMeshProUGUI levelText;
        Experience xpScript;
        BaseStats baseStatsScript;
        [SerializeField] ProgressionSO progressionSO;
        int currentLevel = 1;
        float currentXP;

        private void Awake()
        {
            GameObject player = GameObject.FindWithTag("Player");
            xpScript = player.GetComponent<Experience>();
            baseStatsScript = player.GetComponent<BaseStats>();
        }
        
        IEnumerator Start()
        {
            yield return new WaitForSeconds(0.1f);
            currentLevel = baseStatsScript.GetLevel();
            ChangeXpHud(currentLevel);
        }


        public void ChangeXpHud(int currentLevelFromXpScript)
        {
            currentLevel = currentLevelFromXpScript;
            SetXpText();
            SetSliderPercentToNextLevel();
            SetLevelText(currentLevel);
        }


        void SetXpText()
        {
            currentXP = xpScript.GetXp();
            xpText.text = "Total XP = " + currentXP.ToString();
        }

        void SetSliderPercentToNextLevel()
        {
            var xpNeeded = progressionSO.GetStat(Stat.ExperienceNeededToLevelUp, CharacterClass.Player, currentLevel);
            var xpPercent = currentXP / xpNeeded;
            xpPercentSlider.value = xpPercent;
        }

        public void SetLevelText(int level)
        {
            currentLevel = level;
            levelText.text = "Level " + currentLevel.ToString();
        }
    }
}
1 Like

Well done, Heath!

1 Like

I’ve used your logic and created a slider for Player health and Level but i’m facing a problem with enemy health bar , i tried to add the health bar above the enemy and it works but now when i try to setActive(false) the canvas. it disappear in all enemies from the same type prefab. and when i attack. it appears in all enemies again, even the dead ones. how can i make the canvas disappear only for the dead enemy and the same for show canvas.

PS: i use unity assets for Player health bar UI.

1 Like

hmmm… would need to see your code for the script responsible for turning those on and off

1 Like

Hi!

What I higly suggest in this case is to use Animations instead of code, this allows for many cool features that would be otherwise pretty hard to control with code, here’s an example of what I mean:

In this quick thing I did, you can clearly see that the Health Bars react individually and even have a cool fade at the end. This can be easily done with the Animator and one simple line of code which is this.

     GetComponent<Animator>().Play(nameOfTheState, 1, 0);

This implies that if you hit the enemy multiple times the animation will reset, instead of having 20 timers in your code, you can control the time with Animations, meaning that tweaking the timer is extremely more easy since the only thing you’ll have to do is modify the animation by moving a simple slider instead of going into the code or setting each timer individually.

One thing is worth mentioning is that in the video the transitions are overcomplicated, they can work just fine with this setting:

You don’t even need parameters for this to work, just exit transitions.
Remember you’ll have to add a second layer to the Animator with this settings:

This might see a little convoluted at first, but believe me, the less code you have the better, and Unity allows to do many cool things without writing a single line of code or in this case, one line of code.

what an interesting approach… that’s neat…

Not sure what the approach your code is taking to turn the bars on/off… they should be individual in the first place, and it sounds like your script is turning it on/off in the prefab?

Making sure you’re not referencing the prefab, that you’re referencing the slider on the actual enemy, simply Destroy() the slider.

1 Like

yes i have tried to destroy the slider and it destroy it on all enemy prefab, it like you said. maybe i’m referencing all enemy prefab. i’ll share the code in a sec

Yeah, most likely you’re referencing the Prefab, not the instance

1 Like

Billboard.cs

using RPG.Stats;
using UnityEngine;

namespace RPG.Core
{
    public class Billboard : MonoBehaviour
    {
        public Transform cam;
        public GameObject healthBar = null;

        Fighter fighter;
        private void Awake()
        {
            fighter = GameObject.FindWithTag("Player").GetComponent<Fighter>();
        }
        private void LateUpdate()
        {
            transform.LookAt(transform.position + cam.forward);
            if (fighter.GetTarget != null)
            {
                Health health = fighter.GetTarget;
                if (health.IsDead())
                {
                    healthBar.gameObject.SetActive(false);
                }
                else if (!health.IsDead())
                {
                    healthBar.gameObject.SetActive(true);
                }
            }
        }
    }
}

i made a billboard to rotate the health bar if the camera move or the enemy moved so it gives a better look.

EnemyHealthDisplay.cs

using RPG.Stats;
using UnityEngine;
using UnityEngine.UI;

namespace RPG.Combat
{
    public class EnemyHealthDisplay : MonoBehaviour
    {
        public Slider slider;
        public Image fill;
        

        Fighter fighter;
        private void Awake()
        {
            fighter = GameObject.FindWithTag("Player").GetComponent<Fighter>();
        }

        private void Update()
        {
            if (fighter.GetTarget != null)
            {
                Health health = fighter.GetTarget;
                slider.maxValue = health.GetBaseHealth;
                slider.value = health.GetHealthPoints;
            }
        }
    }
}

The odds are, that HealthBar in the second slide is the prefab from the inspector, not the object under the Goblin. (This is a common error, and it has -=all=- the symptoms. Try dragging the HealthBar from the Goblin heirachy to the Billboard script.

2 Likes

yes this worked as expected thank you.
another problem i’m facing is i’m trying to create spawn enemy, to generate enemy i put in an array every 5 minutes.

but first i tried to create the enemies but when i did the patrol path is not working and i think its the same issue you talked about. i need to take the patrol path from the hierarchy , but how i can do this when i instantiate this enemy ??

and i can’t solve the above problem with enemyHealthBar if i went with this approach.

how can i solve this issue ?

bandicam-2020-05-24-05-24-40-526

Ok to work around this problem i created

SpawnEnemy.cs

using System.Collections;
using UnityEngine;

namespace RPG.Combat
{
    public class SpawnEnemy : MonoBehaviour
    {
        [SerializeField] GameObject[] spawnEnemy = null;
        [SerializeField] float timeToWait = 10f;
        [SerializeField] float timeBetweenSpawnEnemy = 1f;
        void Start()
        {
            StartCoroutine(CreateEnemies());
        }

        IEnumerator CreateEnemies()
        {
            while(true)
            {
                yield return new WaitForSeconds(timeToWait);
                foreach (GameObject enemy in spawnEnemy)
                {
                    Instantiate(enemy, transform.position, Quaternion.identity);
                    yield return new WaitForSeconds(timeBetweenSpawnEnemy);
                }
                yield return null;
            }
        }
    }
}

and i added the enemy in the heirarchy, and delayed the spawnEnemy to wait for 10 sec. and took the enemy for the heirarchy and assigned it to spawnEnemy.
and then added in the heirarchy the defaultPatrolPath and assigned it to the enemy.

in the IEnumerator is there a better way of doing this and does this approach cause performance issues.