4
\$\begingroup\$

I've been slowly learning c++ and building up a codebase intending to create a simple first person role playing game.

I've coded a time management tool to track time elapsed in game, an inventory and container system, and some other bits. I have been considering a basic system to change the weather in game.

I haven't actually done code towards it yet but the simplest weather solution (not including rendering) would be a very very crude state system.

enum WEATHER_TYPES { FINE, CLOUDY, RAINY, STORM }
int min_duration = 2;   // 2 hours
int max_duration = 5;   // 5 hours

int current_duration;
if(current_duration >= min_duration && current_duration < max_duration) {
    float r = my_rand_range_float(0.f,1.f);
    if(r <= 0.25 )
        changeWeather();

That is obviously a really rough approximation and it would result in very harsh changes in the weather. It could instantly go from FINE to STORM which is very unrealistic.

I don't want a system where it models the weather based on realistic parameters. I am happy with a relatively simple randomly changing system to begin with. (Although it is worth noting that my timemanager class is tracking seasons as well as generating temperatures for the days)

I am thinking along the lines of having a WeatherManager class to keep track of the current weather state and decide when to transition between them. Each "Weather Type" would become it's own class that can be updated when it is in use. Each weather class would have a "starting" state where it would be possible to transition into the next weather state from the previous.

For example if changing from FINE to RAINY then during the "starting" state of the RAINY weather it would slowly build up the cloud layers, start the rain particle system with a small number of particles and build up to the desired number. The idea is to provide a bit of a build up.

It would also need to be able to do the reverse in the case of changing from RAINY to FINE.

I don't really know how others have implemented weather systems in game. Am I on the right track in my thinking in this regards and can anyone point me towards some resources to learn about implementing weather systems for a game?

\$\endgroup\$
1
  • \$\begingroup\$ I think with you basis, I would suggest you look into fuzzy logic for your weather system. This would imply that you could have FINE, CLOUDY, RAINY and STORM at the same time, in a ratio ranging from 0% to 100%. You would probably need to reorganize your architecture, but could be worth looked into. \$\endgroup\$
    – Vaillancourt
    Jun 7, 2015 at 1:40

2 Answers 2

7
\$\begingroup\$

One way to go is to model Weather as Random Process using Hidden Markov Model(HMM). For full explanation of it, check Hidden Markov Model - Wikipedia

This will solve the following problem: I'm in state A. From state A, which is the next state i should go into.In other words, if you are in Cloudy State, where should you go next:Remain in Cloudy state or go into Rainy or Fine state. This will not tell you when transition should happen. For that, you could simply use some random variable to indicate duration of each state.

Shortly, HMM is set of states and transitions between states based on probability theory(this is of course crude explanation and mathematicians will be angry if they see this). You could have state for all weather types, i.e. FINE_STATE, CLOUDY_STATE, STORM_STATE, and RAINY_STATE, and define set of transition between them and assign probabilities on them that would look something like this:
HMM
Sum of probabilities of outgoing paths from every state must be 1 as you see.

HMM is a way more general than this, but it does good job. You can use State Design Pattern with this and implement HMM as weighted digraph. For explanation of State Pattern, check
State Pattern - Wikipedia.

As Alan already said, "cross-fading" is nice feature to have, and you could easily combine it with HMM using State Pattern.

Here is the code that corresponds to theory said above(please note that for simplicity i did not accounted duration of the state, and "cross-fading"):


//Header files .h
class Graph
{
public:
    //If nodes "from" and "to" does not exist, they will be added
    //Complexity: O(1) average, O(n) worst case
    void addEdge(string from, string to, float probability)
    {
        mAdj[from].push_back(pair<string, float>(to, probability));
    }
    //Return adjencent nodes to vertex "from"
    //Complexity: O(1) average, O(n) worst case
    list<pair<string, float>> operator[](string from) { return mAdj[from]; }
private:
    unordered_map<string, list<pair<string, float>>> mAdj;
};

class State
{
public:
    //Complexity:O(n) where n is total number of weather states
    State* getNextState(Graph hmm);
    virtual string toString() = 0;
    static State* createState(string state); //Create State object from string argument
};

class FineState : public State
{
public:
    string toString() { return "fine"; }
};
class CloudyState : public State
{
public:
    string toString() { return "cloudy"; }
};
class RainyState : public State
{
public:
    string toString() { return "rainy"; }
};
class StormState : public State
{
public:
    string toString() { return "storm"; }
};

class WeatherManager
{
public:
    WeatherManager() : mCurrentState(0) {}
    ~WeatherManager() { if(mCurrentState) delete mCurrentState; }
    //Add weather states and transition between "from" -> "to" based on probability
    //Complexity: Same as Graph::addEdge()
    void addWeather(string from, string to, float probability)
    {
        mHMM.addEdge(from, to, probability);
    }
    //Calculate weather state for next frame
    //Complexity: Same as State::getNextState()
    //Please note that current implementation of weather system does not account duration of state
    //i.e. Each state have duration of 1 frame
    void changeWeather()
    {
        if (mCurrentState)
        {
            State* pState = mCurrentState->getNextState(mHMM);
            delete mCurrentState;
            mCurrentState = pState;
        }
    }
    void setState(string state)
    {
        if (mCurrentState) delete mCurrentState;
        mCurrentState = State::createState(state);
    }
    void printState()
    {
        cout << mCurrentState->toString() << endl;
    }
private:
    Graph mHMM;
    State* mCurrentState;
};

//Source files .cpp
State* State::getNextState(Graph hmm)
{
    string thisState = toString();
    //Get number in [0, 1] interval.
    //Use some advanced random generator if you need Normal Distribution
    float r = static_cast <float> (rand()) / static_cast <float> (RAND_MAX);
    float sum = .0f;
    for (auto it : hmm[thisState])
    {
        float prob = it.second;
        if (sum <= r && r < (sum + prob))
            return State::createState(it.first);
        sum += prob;
    }
    return nullptr;
}

State* State::createState(string state)
{
    if (state == "fine")
        return new FineState();
    else if (state == "cloudy")
        return new CloudyState();
    else if (state == "rainy")
        return new RainyState();
    else if (state == "storm")
        return new StormState();
    return nullptr;
}

int main() {
    srand(static_cast<unsigned>(time(nullptr)));
    WeatherManager mgr;
    mgr.addWeather("fine", "fine", 0.2f);
    mgr.addWeather("fine", "cloudy", 0.8f);
    mgr.addWeather("cloudy", "cloudy", 0.3f);
    mgr.addWeather("cloudy", "fine", 0.1f);
    mgr.addWeather("cloudy", "rainy", 0.2f);
    mgr.addWeather("cloudy", "storm", 0.7f);
    mgr.addWeather("rainy", "rainy", 0.4f);
    mgr.addWeather("rainy", "cloudy", 0.4f);
    mgr.addWeather("rainy", "storm", 0.2f);
    mgr.addWeather("storm", "storm", 0.1f);
    mgr.addWeather("storm", "cloudy", 0.1f);
    mgr.addWeather("storm", "rainy", 0.8f);
    mgr.setState("fine");

    for (unsigned i = 0; i < 10; i++)
    {
        mgr.printState();
        mgr.changeWeather();
    }

    return 0;
}

You can expand concrete states (i.e. FineState) classes to add duration among the other things, and just use another State* variable for "cross-fading" LERP(Linear Interpolation) to save previous state.

\$\endgroup\$
2
  • \$\begingroup\$ I sincerely appreciate you taking the time to answer my question to this extent. I had briefly read about HMM and dismissed it as too complex for the simple system I was looking to implement. However, based on your answer here it has really helped me grasp the concept of HMM's and I can certainly see the value in using them for weather states. \$\endgroup\$
    – Tim
    Jun 8, 2015 at 6:09
  • \$\begingroup\$ I'm glad if I helped you there. If you still need any help with HMM feel free to ask. \$\endgroup\$ Jun 8, 2015 at 9:51
1
\$\begingroup\$

You could also think about "crossfading" from one weather state to the next when they switch. Basically the weighting of the old state will go from 1.0 to 0.0 over time while the new state goes from 0.0 to 1.0 over the same amount of time. This is used of course in audio to switch from one audio track to another but its also used in skeletal animation programming to switch from one animation to another, and other places as well. Its a good way to handle state transitions in general.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Based on this I've experimented by keeping track of the current weather and the previous weather. On change of the weather I then have a specified time for transition in which I normalise a float to indicate the progress of the transition between 0 and 1. For the current weather I call the update function passing it the weighting as suggested. At the end of the transition time, the weighting for the current weather is 1 and the the previous weather is 1 - weighting = 0; The weatherManagers update method is called in the main loop & it in turn updates the current and previous weathers as req. \$\endgroup\$
    – Tim
    Jun 7, 2015 at 7:15

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .