游戏天气系统中平滑天气转换的algorithm

我一直在慢慢地学习c ++,并建立一个代码库来创建一个简单的第一人称角色扮演游戏。

我已经编写了一个时间管理工具来跟踪游戏,库存和容器系统以及其他一些比特stream逝的时间。 我一直在考虑改变游戏天气的基本系统。

我还没有真正完成代码,但最简单的天气解决scheme(不包括渲染)将是一个非常粗糙的状态系统。

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(); 

这显然是一个非常粗略的近似值,它会导致天气非常恶劣的变化。 它可以立即从FINE走向STORM,这是非常不现实的。

我不想要一个基于现实参数模拟天气的系统。 我很高兴与一个相对简单的随机变化的系统开始。 (虽然值得注意的是我的时间管理课程跟踪季节以及天气温度)

我正在思考如何使用WeatherManager类来跟踪当前的天气状态,并决定何时转换它们。 每个“天气types”将成为它自己的类,它可以在使用时更新。 每个气候类别将有一个“开始”状态,可以从前一个状态转换到下一个天气状态。

例如,如果从FINE变为RAINY,则在RAINY天气的“开始”状态期间,它将慢慢地建立云层,以less量粒子启动雨水粒子系统并且建立到期望的数量。 这个想法是提供一点积累。

在从RAINY改为FINE的情况下,还需要能够做相反的处理。

我真的不知道其他人是如何在游戏中实现天气系统的。 我在这个问题上的思路是否正确?每个人都可以指点一些资源来学习实现游戏的天气系统吗?

一种方法是使用隐马尔可夫模型(HMM)将天气建模为随机过程。 有关它的完整说明,请查看隐马尔可夫模型 – 维基百科

这将解决以下问题:我处于状态A.从状态A,这是我应该进入的下一个状态。换言之,如果您处于多云状态,下一步应该去哪里:保持多云状态或进入Rainy或Fine状态。 这不会告诉你什么时候应该进行转换。 为此,您可以简单地使用一些随机variables来指示每个状态的持续时间。

简而言之,HMM是基于概率论的状态和状态之间的转换(这当然是一个粗略的解释,math家如果看到这一点就会生气)。 您可以为所有天气types(即FINE_STATECLOUDY_STATESTORM_STATERAINY_STATE)设置状态 ,并定义它们之间的转换集合,并为其分配概率,如下所示:
HMM
如你所见,来自每个状态的输出path的概率总和必须是1。

HMM是比这更普遍的一种方式,但它做得很好。 您可以使用状态devise模式并将HMM用作加权有向图。 有关状态模式的说明,请检查
状态模式 – 维基百科 。

正如Alan所说的那样,“交叉淡入淡出”是一个非常好的function,您可以使用状态模式轻松地将其与HMM相结合。

这里是对应于上述理论的代码(请注意,为了简单起见,我没有考虑国家的持续时间和“交叉衰落”):

 //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&ltstring, float>(to, probability)); } //Return adjencent nodes to vertex "from" //Complexity: O(1) average, O(n) worst case list&ltpair&ltstring, float>> operator[](string from) { return mAdj[from]; } private: unordered_map&ltstring, list&ltpair&ltstring, 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 //ie Each state have duration of 1 frame void changeWeather() { if (mCurrentState) { State* pState = mCurrentState-&gtgetNextState(mHMM); delete mCurrentState; mCurrentState = pState; } } void setState(string state) { if (mCurrentState) delete mCurrentState; mCurrentState = State::createState(state); } void printState() { cout << mCurrentState-&gttoString() << 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 &ltfloat> (rand()) / static_cast &ltfloat> (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&ltunsigned>(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; } 

您可以展开具体的状态(即FineState )类来增加其他事物的持续时间,并且使用另一个State*variables作为“交叉渐变”LERP(线性插值)来保存之前的状态。

您也可以考虑在切换时从一个天气状态到下一个天气状态的“交叉淡化”。 基本上,旧状态的权重随着时间从1.0变为0.0,而新状态从0.0变到1.0在相同的时间上。 这当然用于audio从一个音轨切换到另一个音轨,但也用于骨骼animation编程从一个animation切换到另一个,以及其他地方。 它是处理一般状态转变的好方法。