Change value while time

Hello guys, I’m newbie at Unity and C#, so if some my question is stupid, just excuse me. Also my english can be bad.

I need to change some value, for example Player MP over time. As I understand Coroutines are best way for this, but seems I can’t figure out right logic.

I have Player script that contain this function:

    void Pee()
    {
#if UNITY_STANDALONE
        if (Input.GetMouseButtonDown(0) && IsGrounded() == true && inShop == false)
#elif UNITY_EDITOR
        if (Input.GetMouseButtonDown(0) && IsGrounded() == true && inShop == false)
#elif UNITY_ANDROID
        if (CrossPlatformInputManager.GetButtonDown("Pee") && IsGrounded() == true && inShop == false)
#endif
        {
            if (curMP > 0)
            {
                _playerAnim.Pee();
                SetMP(1,0);
                StartCoroutine(RestoreMP());
            }
        }
    }

As you can see my Player can pee, don’t mind, player is a dog, so he should… But I use RPG-like vars names.
When Player pee he lost 1 MP point from 5 at all. I want he could restore 1 point each 5 seconds.
I write this

IEnumerator RestoreMP()
    {
        if (curMP < defMP)
        {
            canRestoreMP = false;
            yield return new WaitForSeconds(5);
            SetMP(1, 1);
            Debug.Log("Timer set player MP to: " + curMP);
            canRestoreMP = true;
        }
    }

It work fine, but also timer start after pee function, so if Player pee five times in second, MP restores five times in second. And in log I saw max value 4, not 5, but actually player has 5 MP.

When I tried put StartCoroutine(RestoreMP()) to Update function, it start a lot of timers each tick, and curMP < defMP just ignored (I don’t know why), I tried to use some bool variables to prevent this, but didn’t get any success. Help me please, I think it’s simple, just my head can’t find right way for this.

Also if someone ask about SetMP(), this is it, but I think that’s no matter.

    public void SetMP(int amount, int type)
    {
        switch (type)
        {
            case 0: // Take MP from player
                curMP -= amount;
                break;
            case 1: // Give MP to player
                curMP += amount;
                break;
        }

        UIManager.Instance.UpdateMPBar(curMP);
        Debug.Log("Player::SetMP function set player MP to: " + curMP);
    }

Hi, and welcome!
You dont need Coroutines for this. You can do most things you’d do with Coroutines in the Update() loop as well. There are some few things for which Coroutines offer a slight advantage, or simply more convenience.

To regenerate a fixed amount of mana per second, you can write something along the lines of this Update():

currentMP += mpRegeneration * Time.deltaTime;

currentMP being the value where you store the current MP of the player, mpRegeneration being how much MP is being regenerated per second, and Time.deltaTime basically makes it so you take 1/FPS part of that, such that after one second you end up with 1*mpRegeneration which was added to currentMP.
So in your example, your mpRegeneration would be a float value of 0.2f, because you want 1 MP to be regenerated every 5 seconds. Of course, you could also do it with a sort of timer in an Update() loop, or a Coroutine, but i deem these to add unnecessary complexity for what you want.
If you want your MP to only be shown as integers (0, 1, 2, …, 5) then you can either have two values for MP (one internal which is a float and adds up the regeneration and one that is an int), or you just use (int)currentMP when you want to get the integer value you show, for example, in your GUI. This is called a cast, where one type is casted into another. For floats to integers this basically means that everything after the dot gets cut off.

Also as a small advice, you do not need to compare boolean values to true or false. Comparing a true boolean to true will return true, so you can just take the boolean itself instead. So, for example, instead of IsGrounded() == true, you could (and should!) simply write IsGrounded() since it’s the same, shorter, costs less performance and makes the code less confusing for longer statements. If you want a negative boolean (“== false”) then you can simply write !IsGrounded(), where the ! basically means “not”. So !IsGrounded() is true, when IsGrounded() is false and vice versa.

1 Like

Great thanks!
I change defMP and curMP variables to float, no matter what type it is actually, anyway slider with float values looks more smooth and cool.
Then I make function regenMP()

void regenMP()
    {
        if(curMP < defMP)
        {
            curMP += mpRegenRate * Time.deltaTime;
            UIManager.Instance.UpdateMPBar(curMP);
        }
    }

and add regenMP() to Update function, this work perfectly! I really like this, because now I can change mp regen rate with items or events and it give me more freedom in gameplay design.

One more question, should I use UIManager.Instance.UpdateMPBar(curMP); ? Because Update() work every tick, how much load it will affect on device? Or I shouldn’t worry about this?

Also I clear my code as you advice, didn’t compare IsSomething == true/false anymore, thanks!

Depends on what exactly you are doing in that function, but generally speaking you should not worry about it too much.
Since i never worked with GUI (in Unity) myself, i hope somebody corrects me if i’m wrong, but if you want something to be accurately represented every frame, then you need to update it every frame. There is a huge variety of optimizations that can be done, depending on the topic. However, since performance optimization is an advanced (and huge) topic, the general advice is to not worry about it until it becomes a problem. And when it does, Unity itself gives you some tools to figure out which causes the performance problems, and the forum can help you figure it out when you are stuck too.

From a design perspective however, i would probably separate the UI updating part from the mp regeneration method. You generally want your code to be modular, so splitting game logics and visuals is a good first step. Generally, one method should have one task. So if it’s called “regenMP”, then it should regenerate MP - anything else is unexpected behavior. This is partly subjective, but the less you tangle up your code, the more clean it will look and the easier it is to work with it.
One last thing: i would strongly recomment you to follow C# naming conventions. This helps with readability, makes code uniform between different programmers, and also helps to differentiate between methods and variables and such. Commonly this means you want to use camelCase (like you are doing) for most things, but use UpperCamelCase for things like method, class and property names. That way you can easily tell apart what’s a method and, for example, a variable. There are other naming conventions but i’d say that’s the biggest and thus most important one. So “regenMP” should rather be “RegenMP”.

Just set slider value

    public void UpdateMPBar(float curMP)
    {
        MPBar.value = curMP;
    }

I see, I will not worry about that stuff.
I understand that UI should be separate from core game function, but I just start study, so for now I will use it that way, maybe a bit later I rewrite this. It more simple for me. Get a some worked version and then change different aspects of project.

I’m a bit lazy about naming, I prefer old php style or java style, and never write on C# before, now I study 2D Platformer official course, and sometimes author use _var sometimes Var, sometimes var, that made me mad a bit. Thanks for your help and advices, I will take a read about naming in C#, however we have find and replace :wink: