《游戏编程模式》一7.5 状态对象应该放在哪里呢

简介:

本节书摘来异步社区《游戏编程模式》一书中的第7章,第7.5节,作者: 【美】Robert Nystrom (尼斯卓姆) 译者: 赵卫兵 , 许新星 , 姜召阳 , 陈侃 , 屈光辉 , 郑炯彬 责编: 陈冀康,更多章节内容可以访问云栖社区“异步社区”公众号查看。

7.5 状态对象应该放在哪里呢

我这里忽略了一些细节。为了修改一个状态,我们需要给state_指针赋值为一个新的状态,但是这个新的状态对象要从哪里来呢?我们之前的枚举方法是定义一些数字。但是,现在我们的状态是类,我们需要获取这些类的实例。通常来说,有两种实现方法。

7.5.1 静态状态

如果一个状态对象没有任何数据成员,那么它的唯一数据成员便是虚表指针了。那样的话,我们就没有必要创建此状态的多个实例了,因为它们的每一个实例都是相同的。

在那种情况下,我们可以定义一个静态实例。即使你有一系列的FSM在同时运转,所有的状态机也能同时指向这一个唯一的实例。

如果你的状态类没有任何数据成员,并且只有一个虚函数方法。那么我们还可以进一步简化此模式。我们可以使用一个普通的状态函数来替换状态类。这样的话,我们的state_变量就变成一个状态函数指针。

这个就是享元模式。(第3章)
你把静态方法放置在哪里,这个由你自己来决定。如果没有任何特殊原因的话,我们可以把它放置到基类状态类中:

class HeroineState
{
public:
 static StandingState standing;
 static DuckingState ducking;
 static JumpingState jumping;
 static DivingState diving;

 // Other code...
};

每一个静态成员变量都是对应状态类的一个实例。如果我们想让主角跳跃,那么站立状态应该是这样子:

if (input == PRESS_B)
{
 heroine.state_ = &HeroineState::jumping;
 heroine.setGraphics(IMAGE_JUMP);
}

7.5.2 实例化状态

有时候上面的方法可能不行。一个静态状态对于躲避状态而言是行不通的。因为它有一个chargeTime_成员变量,所以这个具体取决于每一个躲避状态下的主角类。如果我们的游戏里面只有一个主角的话,那么定义一个静态类也是没有什么问题的。但是,如果我们想加入多个玩家,那么此方法就行不通了。

当你为状态实例动态分配空间时,你不得不考虑碎片化问题了。对象池模式(第19章)可以帮助到你。
在那种情况下,我们不得不在状态切换的时候动态地创建一个躲避状态实例。这样,我们的有限状态机就拥有了它自己的实例。当然,如果我们又动态分配了一个新的状态实例,则要负责清理老的状态实例。这里必须相当小心,因为修改状态的函数是在当前状态里面,所以我们需要小心地处理删除的顺序。

另外,我们也可以选择在HeroineState类中的handleInput()方法里面可选地返回一个新的状态。当这个状态返回的时候,主角将会删除老的状态并切换到这个新的状态,如下所示:

void Heroine::handleInput(Input input)
{
 HeroineState* state = state_−>handleInput(  
      *this, input);
 if (state != NULL)
 {
  delete state_;
  state_ = state;
 }
}

那样的话,我们只有在从handleInput方法返回的时候才有可能去删除前面的状态对象。现在,站立状态可以通过创建一个躲避状态的实例来切换状态了。

HeroineState* StandingState::handleInput(  
      Heroine& heroine, Input input)
{
 if (input == PRESS_DOWN)
 {
  // Other code...
  return new DuckingState();
 }

 // Stay in this state.
 return NULL;
}

通常情况下,我倾向于使用静态状态。因为它们不会占用太多的CPU和内存资源。

相关文章
|
2月前
|
缓存 小程序 UED
如何利用小程序的生命周期函数实现数据的加载和更新?
如何利用小程序的生命周期函数实现数据的加载和更新?
74 4
|
7月前
|
Linux API C++
【C++ 线程包裹类设计】跨平台C++线程包装类:属性设置与平台差异的全面探讨
【C++ 线程包裹类设计】跨平台C++线程包装类:属性设置与平台差异的全面探讨
125 2
|
7月前
|
开发框架 JavaScript 前端开发
描述JavaScript事件循环机制,并举例说明在游戏循环更新中的应用。
JavaScript的事件循环机制是单线程处理异步操作的关键,由调用栈、事件队列和Web APIs构成。调用栈执行函数,遇到异步操作时交给Web APIs,完成后回调函数进入事件队列。当调用栈空时,事件循环取队列中的任务执行。在游戏开发中,事件循环驱动游戏循环更新,包括输入处理、逻辑更新和渲染。示例代码展示了如何模拟游戏循环,实际开发中常用框架提供更高级别的抽象。
42 1
|
前端开发
前端学习案例1-this指向问题-函数的独立调用
前端学习案例1-this指向问题-函数的独立调用
51 0
前端学习案例1-this指向问题-函数的独立调用
|
前端开发
前端学习案例2-this指向问题-函数的独立调用2
前端学习案例2-this指向问题-函数的独立调用2
67 0
前端学习案例2-this指向问题-函数的独立调用2
|
安全 C++ Windows
C++调用外部应用程序的方法的整理总结(常用)
一、三个SDK函数:  WinExec,ShellExecute ,CreateProcess可以实现调用其他程序的要求,其中以WinExec最为简单,ShellExecute比WinExec灵活一些,CreateProcess最为复杂。
3029 0
线程与更新UI,细谈原理(下)
相信不少读者都阅读过相类似的文章了,但是我还是想完整的把这之间的关系梳理清楚,细节聊好,希望你也能从中学到一些。
143 0
线程与更新UI,细谈原理(下)
|
存储 XML 安全
线程与更新UI,细谈原理(上)
相信不少读者都阅读过相类似的文章了,但是我还是想完整的把这之间的关系梳理清楚,细节聊好,希望你也能从中学到一些。
254 0
线程与更新UI,细谈原理(上)
|
存储 负载均衡 Oracle
面向(过程、对象、组件、服务)编程
面向(过程、对象、组件、服务)编程
312 0
|
前端开发 数据格式
【Taro工作记录一】如何判断对象为空得方式
【Taro工作记录一】如何判断对象为空得方式
145 0