2
\$\begingroup\$

How can set up time elapsed based events separate from the game loop?

I want the character to heal over real time rather than per cycle of the game loop. How might i do that?

\$\endgroup\$
  • \$\begingroup\$ For non-game-specific questions like "how to convert a string to lowercase," you should search our general programming sister site, StackOverflow, where such topics typically already have answers. Beware that converting case this way is not always safe for localization (eg. the German letter eszett ß traditionally lacks an uppercase form, usually being replaced by "SS"). I'll edit your question to focus on the remaining issue - in future, please only ask one question per post. \$\endgroup\$ – DMGregory Jul 10 '18 at 17:50
  • \$\begingroup\$ thanks for the link, that was exactly what i needed and googke wasnt giving me! \$\endgroup\$ – Richard Carpenter Jul 10 '18 at 21:21
  • \$\begingroup\$ @DMGregory Since recently there is a upper-case ß in german \$\endgroup\$ – Anonymous Anonymous Jul 11 '18 at 10:06
3
\$\begingroup\$

This is a very wide question, and difficult to answer specifically, so I will be general instead:

Any "over time" effects will still be based on your game loop, but have their own "tick" of time they are bound to. Diablo 3 does this for damage and healing over time effects. The tool tips list the amounts as "per second" but it is actually applied "per tick", which is more than a frame (max 16ms), but less than a second.

An example could be:

//Base effect class
class OverTimeEffect
{
public:
    virtual void apply(Character& c, float dt)=0;
    void setTickSize(float tick);
    void setDuration(float seconds);
    float getRemaining();
protected:
    float m_tick_s;
    float m_duration;
    float m_accum;
    float m_remaining;
};

class HealOverTime:
    public OverTimeEffect
{
public:
    virtual void apply(Character& c, float dt)
    {
        if(m_accum + dt >= m_tick_s)
        {
            c.addHealth(1);
            m_accum = 0.0f;
            m_remaining-=dt;
        }
        else {
            m_accum += dt;
            m_remaining-=dt;
        }   
    }
};

// This is your game character base class
class Character
{
public:
    void addHealth(int hp);
    void addOTE(OverTimeEffect& ote)
    {
        m_ote.push_back(ote);
        //then min sort by remaining time in effect
        //so shortest duration is always at the front of the queue
    }
    void update(float dt)
    {
         for (auto& effect: m_ote)
         {
             effect.apply(*this,dt);
             if(effect.getRemaining() <= 0.0f)
             {
                 m_ote.pop_front();
             }
         }

    }
private:
    deque<OverTimeEffect> m_ote;
    float m_hp;
};

This is but one of the many, many ways you could implement such effects. I recommend further reading up on game engine architecture books and articles, as this is a very common feature of games, and so there is likely to be a solution that will suit your game.

\$\endgroup\$
3
\$\begingroup\$

Time elapsed events are not separate from the game loop but are controlled from the game loop.

The easiest way to achieve the effect is to have a floating-point value that represents the number of seconds the event should be active.

From the game loop you call an update function that takes the number of seconds since the last frame: Update(float deltaSeconds); and in that you increment an internal value based on that elapsed time. If it's greater or equal to your pre-determined length of time you stop:

Example Pseudo-code:

class Player {
    //Other stuff...
    public:
    void Update(float deltaSeconds);
    void DoHealOverTime(float deltaSeconds);

    float maxTimeToHeal = 5.0f;
    float currentTimeToHeal = 0.0f;
    float health = 0.0f;
    float healthPerSecond = 1.0f;
    float maxHealth = 100.0f;
    bool isHealing = false;
};


void Player::Update(float deltaSeconds) {
    DoHealOverTime(deltaSeconds);
}
void Player::DoHealOverTime(float deltaSeconds) {
    if(!isHealing || maxHealth < health) {
        return;
    }
    timeToHeal += deltaSeconds;
    health += healthPerSecond * deltaSeconds;
    if(maxTimeToHeal < timeToHeal) {
        isHealing = false;
        timeToHeal = 0.0f;
    }
    //std::clamp is a better use here, but need c++17 compiler.
    //Parenthesis around function names are intentional to avoid name-collisions with preexisting macros.
    health = (std::max)(0.0f, (std::min)(health, maxHealth));
}
\$\endgroup\$
  • \$\begingroup\$ that is fantastic. lots of new ideas for me to digest. thank you very much. \$\endgroup\$ – Richard Carpenter Jul 11 '18 at 2:55

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.