https://www.jblearning.com/catalog/productdetails/9781556220784
第1章 数学和物理学初探 (已看)
第2章 状态驱动智能体设计 (已看)
第1章 数学和物理学初探
1.1 数学
1.1.1 笛卡尔坐标系
1.1.2 函数和方程
1.1.3 三角学
一条射线是一条只有一个端点的直线.它是无限长的并且用一个方向(常常表示成一个归一化的矢量)和一个原点来定义
一个角定义为有公共原点的两条射线的分散度
1.1.4 矢量
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
struct Vector2D { double x; double y; Vector2D() :x(0.0), y(0.0) {} Vector2D(double a, double b) : x(a), y(b) {} // 置x和y为0 inline void Zero(); // 如果x和y都为0的话返回TRUE inline bool isZero() const; // 返回矢量的长度 inline double Length() const; // 返回矢量长度的平方(从而可以避免开方运算 inline double LengthSq() const; inline void Normalize(); // 返回this和v2的点乘值 inline double Dot(const Vector2D & v2) const; // 如果v2在this矢量的顺时针方向返回正值 // 如果在逆时针方向返回负值(假设Y轴的箭头是指向下面的,X轴指向右边就像一个窗户应用) inline int Sign(const Vector2D & v2) const; // 返回与this矢量正交的向量 inline Vector2D Perp() const; // 调整x和y使矢量的长度不会超过最大值 inline void Truncate(double max); // 返回this矢量与一个作为参数被传递的矢量之间的距离 inline double Distance(const Vector2D & v2) const; // 上面的平方 inline double DistanceSq(const Vector2D & v2) const; // 返回与this矢量相反的矢量 inline Vector2D GetReverse() const; // 我们需要的一些操作 const Vector2D & operator+=(const Vector2D & rhs); const Vector2D & operator-=(const Vector2D & rhs); const Vector2D & operator*=(const double & rhs); const Vector2D & operator/=(const double & rhs); bool operator==(const Vector2D & rhs) const; bool operator!=(const Vector2D & rhs) const; };
1.1.5 局部空间和世界空间
1.2 物理学
1.2.1 时间
时间是一个标量(用它的大小就可以完全确定,没有方向).今天1s的度量为:对应于铯133原子在基态的两个超精细级别之间转换的9,192,631,770个辐射周期的持续时间
在计算机游戏中时间的度量可以是两种方法中的一个,或者用秒(就如同真实世界中一样),或者使用更新之间的时间间隔作为一种虚拟秒.后一种度量方法可以简化许多公式,但是你不得不小心,因为除非更新速率是锁定的,否则在速度不同的机器之间这个物理量是不同的!
1.2.2 距离
距离(一个标量)的标准单位是m(米)
1.2.3 质量
质量是一个标量,用千克度量,简写成kg.
1.2.4 位置
你可能认为一个对象的位置是一个容易度量的属性,但是你要从哪里来确切地度量它的位置呢?
物理学家通过采用物体质量的中心作为它的位置解决了这个问题.质量的中心是物体的平衡点.假想在这一点上系一根绳子悬挂物体,物体可以在任何一个位置都保持平衡.另一个想象质心的好方法是把它看作物体内所有质量的平衡位置
1.2.5 速度
速度是一个矢量(一个具有大小和方向的量),表达了距离相对于时间的变化率.度量速度的标准单位是m/s(米每秒).v = Δx/Δt
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
class Vehicle { // 在空间中用一个矢量表现车的位置 vector m_vPosition; // 一个矢量表现车的速度 vector m_vVelocity; public: // 调用每一帧更新车的位置 void Update(float TimeElapsedSinceLastUpdate) { m_vPosition += m_vVelocity * TimeElapsedSinceLastUpdate; } }; // 使用一个恒定的更新步长进行模拟更新 void Vehicle::Update() { m_vPosition += m_vVelocity; }
1.2.6 加速度
加速度是一个矢量,表达的是在时间段上速度的变化率,用米每二次方秒来度量,写作m/s2.a = Δv/Δt
1.2.7 力
根据英国物理学家牛顿的学说: 外力是为了改变物体的静止或匀速直线运动的状态而施加在物体上的作用.因此,力(Force)是可以改变物体的速度或者运动线路的量.
力的单位是是牛顿,简写作N,被定义为:在1s内使1kg的质量从静止到以1m/s的速度运动所需要的力
1.3 总结
第2章 状态驱动智能体设计
有限状态机,常常称作FSM(Finite State Machine),多年来已经作为人工智能编程者们选用的工具用于设计具有智能幻觉的游戏智能体.你会发现从视频游戏的早期开始,这种或那种FSM正是每个游戏所选中的架构;尽管更专业的智能体结构越来越普及,但FSM架构还将在今后很长时间内无处不在.为何会这样?原因如下
[编程快速简单] 有很多方法编码一个有限状态机,并且几乎所有的有限状态机实现都相当的简单
[易于调试] 因为一个游戏智能体的行为被分解成简单的易于管理的块,如果一个智能体开始变得行动怪异,会通过对每一个状态增加跟踪代码l来调试它.用这种方法,人工智能程序员可以很容易跟踪错误行为出现前的事件序列,并且采取相应的行动
[很少的计算开销] 有限状态机几乎不占用珍贵的处理器时间,因为它们本质上遵守硬件编码的规则.除了if-this-then-that类型的思考处理之外,是不存在真正的"思考"的
[直觉性] 人们总是自然地把事物思考为处在一种或另一种状态.并且我们也常常提到我们自己处在这样那样的状态中.有多少次你"使自己进入一种状态"或者发现自己处于"头脑的正确状态".当然人类并不是像有限状态机一样工作,但是有时候我们发现在这种方式下考虑我们的行为是有用的.相似地,将一个游戏智能体的行为分解成一些状态并且创建需要的规则来操作它们是相当容易的.出于同样的原因,有限状态机能够使你很容易地与非程序员(例如与游戏制片人和关卡设计师)来讨论你的人工智能的设计,能够更好地进行设计概念的沟通和交流
[灵活性] 一个游戏智能体的有限状态机可以很容易地由程序员j进行调整,来达到游戏设计者所要求的行为.同样通过增添新的状态和规则也很容易扩展一个智能体的行为的范围.此外,当你的人工智能技术提高了,你会发现有限状态机提供了一个坚固的支柱,使你可以用它来组合其他的技术,例如模糊逻辑和神经网络.
2.1 什么是有限状态机
作为一个人工智能程序员,我们可以放弃有限状态机的正式的数学定义:一个描述性的定义就足够了:
一个有限状态机是一个设备,或是一个设备模型,具有有限数量的状态,它可以在任何给定的时间根据输入进行操作,使得从一个状态变换到另一个状态,或者是促使一个输出或者一种行为的发生.一个有限状态机在任何瞬间只能处在一种状态
2.2 有限状态机的实现
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
enum StateType{ RuanAway, Patrol, Attack }; void Agent::UpdateState(StateType CurrentState) { switch(CurrentState) { case state_RunAway: EvadeEnemy(); if (Safe()) { ChangeState(state_Patrol); } break; case state_Patrol: FollowPatrolPath(); if (Theatened()) { if (StrongerThanEnemy()) { ChangeState(state_Attack); } else { ChangeState(state_RunAway); } } break; case state_Attack: if (WeakerThanEnemy()) { ChangeState(state_RunAway); } else { BashEnemyOverHead(); } break; } }
虽然初看之下,这个方法是合理的,但当实际应用到任何一个比最简单的游戏稍复杂的情况,switch/if-then 解决方法就变成了一个怪物
此外,作为一个人工智能编程者,当处于初始进入状态或者退出状态时,你会常常需要一个状态完成一个指定的行动(或多个行动).例如,当一个智能体进入逃跑状态,你可能会希望它向空中挥动着胳膊并且喊道"啊!",当它最后逃脱了并且改变成巡逻状态,你可能希望它发出一声叹息,擦去额头的汗水,并且说"哟!".这些行为只能是在j进入或退出逃跑状态时出现的,而不会发生在通常的更新步骤中.因此,这个附加的函数必须被理想地建立在你的状态机架构中.要想在switch或if-then架构中做到这些,你必定会咬牙切齿,恶心反胃,并写出实在是非常糟糕的代码
2.2.1 状态转换表
一个用于组织状态和影响状态变换的更好的机制是一个状态变换表
这个表可以被一个智能体在规则的间隔内询问,使得它能基于从游戏环境中接受到的刺激进行必须的状态转换.每个状态可以模型化为一个分离的对象或者存在于智能体外部的函数,提供了一个清楚的和灵活的结构,这个表于前面讨论的if-then/switch方法相比,将少有出现意大利面条式的失控情况.
2.2.2 内置的规则
另一种方法就是将状态转换规则嵌入到状态本身的内部.
虽然每个模块可以意识到任何其他模块的存在,但每一个模块是一个独立的单位并且不依赖任何外部的逻辑来决定它是否应该允许自己交换到一个替代状态因此增加状态或者用一个完全新的集合来交换整个的模块集是简单易行的
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
class State { public: virtual void Execute(Troll * troll) = 0; }; class Troll { State * m_pCurrentState; public: void Update() { m_pCurrentState->Execute(this); } void ChangeState(const State * pNewState) { delete m_pCurrentState; m_pCurrentState = pNewState; } }; class State_RunAway : public State { public: void Execute(Troll * troll) { if (troll->isSafe()) { troll->ChangeState(new State); } else { troll->MoveAwayFromEnemy(); } } }; class State_Sleep: public State { public: void Execute(Troll * troll) { if (troll->isThreatened()) { troll->ChangeState(new State_RunAway()); } else { troll->Snore(); } } };
2.3 West World 项目
2.3.1 BaseGameEntity类
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
class BaseGameEntity { private: // 每个实体具有一个唯一的识别数字 int m_ID; // 这是下一个有效的ID.每次BaseGameEntity被实例化这个值就被更新 static int m_iNextValidID; // 在构造函数中调用这个来确认ID被正确设置,在设置ID和增量前, // 它校验传递给方法的值是大于还是等于下一个有效的ID void SetID(int val); public: BaseGameEntity(int id) { SetID(id); } virtual ~BaseGameEntity() {} // 所有的实体必须执行一个更新函数 virtual void Update() = 0; int ID() const { return m_ID; } };
2.3.2 Miner类
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
class Miner : public BaseGameEntity { private: // 指向一个状态实例的指针 State * m_pCurrentState; // 矿工当前所处的位置 location_type m_Location; // 矿工的包中装了多少天然金块 int m_iGoldCarried; // 矿工在银行存了多少钱 int m_iMoneyInBank; // 矿工的口渴值 int m_iThirst; // 矿工的疲劳值 int m_iFatigue; public: Miner(int ID); // 这是必须被执行的 void Update(); // 这个方法改变当前的状态到一个新的状态 void ChangeState(State * pNewState); // 省略了大量的接口 }; void Miner::Update() { m_iThirst += 1; if (m_pCurrentState) { m_pCurrentState->Execute(this); } }
2.3.3 Miner状态
2.3.4 重访问的状态设计模式
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
class State { public: virtual ~State() {} // 当状态被进入时执行这个 virtual void Enter(Miner *) = 0; // 每一更新步骤这个被矿工更新函数调用 virtual void Execute(Miner *) = 0; // 当状态退出时执行这个 virtual void Exit(Miner *) = 0; }; void Miner::ChangeState(State * pNewState) { // 调用现有状态的退出方法 m_pCurrentState->Exit(this); // 改变状态到新的状态 m_pCurrentState = pNewState; // 调用新状态的进入方法 m_pCurrentState->Enter(this); }
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
/* --------------------- MyClass.h ----------------- */ #ifndef MY_SINGLETON #define MY_SINGLETON class MyClass { private: // 数据成员 int m_iNum; // 构造器是私有的 MyClass() {} // 拷贝构造函数和赋值运算符应该是私有的 MyClass& operator=(const MyClass &); public: // 严格的说,singleton的析构函数应该是私有的,但是一些编译器 // 处理这种情况会出现问题,因此让它们作为公有的 ~MyClass(); // 方法 int GetVal() const { return m_iNum; } static MyClass * Instance(); }; /* --------------------- MyClass.cpp ----------------- */ MyClass* MyClass::Instance() { static MyClass instance; return &instance; }
2.4 使State基类可重用
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
template <class entity_type> class State { public: virtual void Enter(entity_type *) = 0; virtual void Execute(entity_type *) = 0; virtual void Exit(entity_type *) = 0; virtual ~State() {} }; class EnterMineAndDigForNugget : public State<Miner> { public: /* 省略 */ };
2.5 全局状态和状态翻转 (State Blip)
当设计一个有限状态机时,你往往会因为在每一个状态中复制代码而死掉.例如,在流行的游戏Maxis公司的<<模拟人生>>(The Sims)中,Sim可能会感到本能的迫切要求,不得不去洗手间方便.这种急切的需求会发生在Sim的任何状态或任何可能的时间.假设当前的设计,是为了把这类行为赋予挖金矿工,复制条件的逻辑将会被加进他的每一个状态,或者,放置进Miner::Update函数.虽然后面的解决方法是可接受的,但最好创建一个全局状态,这样每次FSM更新时就会被调用.那样,所有用于FSM的逻辑被包含在状态中并且不在拥有FSM的智能体类中
除了全局行为之外,偶尔地让智能体带着一个条件进入一个状态也会带来方便,条件就是当状态退出时,智能体返回到前一个状态.我们称这种行为为状态翻转(State Blip)
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
class Miner : public BaseGameEntity { private: State<Miner> * m_pCurrentState; State<Miner> * m_pPreviousState; State<Miner> * m_pGlobalState; ... public: void ChangeState(State<Miner> * pNewState); void RevertToPreviousState(); ... };
2.6 创建一个StateMachine类
通过把所有与状态相关的数据和方法封装到一个StateMachine类中,可以使得设计更为简洁.这种方式下一个智能体可以拥有一个StateMachine类的实例,并且委托它管理当前状态,全局状态,前面的状态.
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
template <class entity_type> class StateMachine { private: // 指向拥有这个实例的智能体的指针 entity_type * m_pOwner; State<entity_type> * m_pCurrentState; // 智能体处于的上一个状态的记录 State<entity_type> * m_pPreviousState; // 每次FSM被更新时,这个状态逻辑被调用 State<entity_type> * m_pGlobalState; public: StateMachine(entity_type * owner) : m_pOwner(owner), m_pCurrentState(NULL), m_pPreviousState(NULL), m_pGlobalState(NULL) {} // 使用这些方法来初始化FSM void SetCurrentState(State<entity_type> * s) { m_pCurrentState = s;} void SetGlobalState(State<entity_type> * s) { m_pGlobalState = s; } void SetPreviousState(State<entity_type> * s) { m_pPrevisouState = s; } // 调用这个来更新FSM void Update() const { // 如果一个全局状态存在,调用它的执行方法 if (m_pGlobalState) m_pGlobalState->Execute(m_pOwner); // 对当前的状态相同 if (m_pCurrentState) m_pCurrentState->Execute(m_pOwner); } // 改变到一个新状态 void ChangeState(State<entity_type> * pNewState) { assert(pNewState && "<StateMachine::ChangeState>: trying to change to a null state"); // 保留前一个状态的记录 m_pPreviousState = m_pCurrentState; // 调用现有的状态的退出方法 m_pCurrentState->Exit(m_pOwner); // 改变状态到一个新状态 m_pCurrentState = pNewState; // 调用新状态的进入方法 m_pCurrentState->Enter(m_pOwner); } // 改变状态回到前一个状态 void RevertToPreviousState() { ChangeState(m_pPreviousState); } // 访问 State<entity_type> * CurrentState() const { return m_pCurrentState; } State<entity_type> * GlobalState() const { return m_pGlobalState; } State<entity_type> * PrevisouState() const { return m_pPreviousState; } // 如果当前的状态类型等于作为指针传递的类的类型,返回true bool isInState(const State<entity_type> & st) const; };
一个智能体所需要做的全部事情就是去拥有一个StateMachine类的实例,并且为了得到完全的FSM功能,实现一个方法来更新状态机
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
class Miner : public BaseGameEntity { private: // state machine类的一个实例 StateMachine<Miner> * m_pStateMachine; /* 无关系的细节被省略了 */ public: Miner(int id) : m_Location(shack), m_iGoldCarried(0), m_iMoneyInBank(0), m_iThirst(0), m_iFatigue(0), BaseGameEntity(id) { // 建立state machine m_pStateMachine = new StateMachine<Miner>(this); m_pStateMachine->SetCurrentState(GoHomeAndSleepTilRested::Instance()); m_pStateMachine->SetGlobalState(MinerGlobalState::Instance()); } ~Miner() { delete m_pStateMachine; } void Update() { ++m_iThirst; m_pStateMachine->Update(); } StateMachine<Miner> * GetFSM() const { return m_pStateMachine;} /* 无关系的细节被省略了 */ };
2.7 引入Elsa
2.8 为你的FSM增加消息功能
设计精度的游戏趋向于事件驱动.即当一个事件发生了(发射了武器,搬动了手柄,一个犯错的警告,等等),事件被广播给游戏中相关的对象.这样它们可以恰当地做出反应.这些事件一般是以一个数据包的形式送出,数据包包括关于事件的信息例如什么事件发送了它,什么对象应该对它做出反应,实际的事件是什么,一个时间戳,等等
事件驱动结构被普遍选取的原因是因为它们是高效率的.没有事件处理,对象不得不持续地检测游戏世界来看是否有一个特定的行为已经发生了.使用事件处理,对象可以简单地继续它们自己的工作,知道有一个事件消息广播给它们.然后,如果消息是与己相关的,它们可以遵照它行事
聪明的游戏智能体可以使用同样的概念来相互交流.当具有发送,处理和对事件做出反应的机制,很容易设计出如下行为
一个巫师向一个妖魔扔出火球.巫师发出一个消息给妖魔,通知它迫近的命运,因此它可能会做出相应的反应.例如,壮烈地死去
一个足球运动员从队友旁边通过.传球者可以发送一个消息给接收者,让他知道应该移动到什么位置来拦截这个球,什么时间他应该出现在那个位置
一个受伤的步兵.他发送一个消息给他的每一个通知寻求帮助.当一个救援者来到了,另一个消息要广播出去通知其他的人,以便他们可以重新开始行动了
一个角色点燃了火柴来照亮他所走的幽暗走廊.一个延迟的消息发送出警告:他在30s内火柴将会烧到他的手指.当他收到这个消息时,如果他还拿着火柴,他的反应是扔掉火柴并悲伤地喊叫
2.8.1 Telegram的结构
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
struct Telegram { // 发送这个telegram的实体 int Sender; // 接收这个telegram的实体 int Receiver; // 信息本身,所有的枚举值都在文件中 // "MessageType.h" int Msg; // 可以被立即发送或者延迟一个指定数量的时间后发送的信息 // 如果一个延迟是必须的,这个域打上时间戳,消息应该在此时间后被发送 double DispatchTime; // 任何应该伴随着消息的额外信息 void * ExtraInfo; /* 省略构造器 */ };
2.8.2 矿工Bob和Elsa交流
2.8.3 消息发送和管理
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
class EntityManager { private: // to save the ol' fingers typedef std::map<int, BaseGameEntity *> EntityMap; // 促进更快的寻找存储在std::map中的实体, // 其中指向实体的指针是利用实体的识别数值来交叉参考的 EntityMap m_EntityMap; EntityManager() {} // 拷贝ctor和分配应该是私有的 EntityManager(const EntityManager &); EntityManager& operator=(const EntityManager &); public: static EntityManager * Instance(); // 该方法存储了一个指向实体的指针在std::vector中 // m_Entities在索引位置上由实体的ID显示(获得更快速的访问) void RegisterEntity(BaseGameEntity * NewEntity); // 给出ID作为一个参数,返回一个指向实体的指针 BaseGameEntity * GetEntityFromID(int id) const; // 该方法从列表中移除实体 void RemoveEntity(BaseGameEntity * pEntity); }; // 提供了对EntityManager的一个实例的访问 #define EntityMgr EntityManager::Instance() Miner * Bob = new Miner(ent_Miner_Bob); EntityMgr->RegisterEntity(Bob); Entity * pBob = EntityMgr->GetEntityFromID(ent_Miner_Bob); class MessageDispatcher { private: // 一个std::set被用于作为延迟的消息的容器,因为这样的好处是可以 // 自动地排序和避免产生重复 std::set<Telegram> PriorityQ; // 该方法被DispatchMessage或者DispatchDelaydMessage利用 // 该方法用最新创建的telegram调用接收实体的消息处理成员函数 // pReceiver void Discharge(Entity * pReceiver, const Telegram & msg); MessageDispatcher() {} public: // 这是一个singleton类 static MessageDispatcher * Instance(); // 向另一个智能体发送消息 void DispatchMessage(double delay, int sender, int receiver, int msg, void * ExtraInfo); // 发送任何延迟的消息,该方法每次通过主游戏循环被调用 void DispatchDelayMessage(); }; // 使生活更舒适.. 哈哈 #define Dispatch MessageDispatcher::Instance() // 得到一个指向信息接收者的指针 Entity * pReceiver = EntityMgr->GetEntityFromID(receiver); // 创建一个telegram Telegram telegram(delay, sender, receiver, msg, ExtraInfo); // 如果不存在延迟,立即发送telegram if (delay <= 0.0) { // 发送telegram到接收器 Discharge(pReceiver, telegram); } // 否则,当telegram应该被发送的时候计算时间 else { double CurrentTime = Clock->GetCurrentTime(); telegram.DispatchTime = CurrentTime + delay; // 并且将它放入队列 PriorityQ.insert(telegram); } void MessageDispatcher::DispatchDelayMessages() { // 首先得到当前的时间 double CurrentTime = Clock->GetCurrentTime(); // 查看队列中是否有telegram需要发送. // 从队列的前端移除所有的已经过期的telegram while ((PriorityQ.begin()->DispatchTime < CurrentTime) && (PriorityQ.begin()->DispatchTime > 0) { // 从队列的前面读telegram Telegram telegram = *PriorityQ.begin(); // 找到接收者 Entity * pReceiver = EntityMgr->GetEntityFromID(telegram.Receiver); // 发送telegram到接收者 Discharge(pReceiver, telegram); // 并且从队列中移除它 PriorityQ.erase(PriorityQ.begin()); } }
2.8.4 消息处理
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
class BaseGameEntity { private: int m_ID; /* 为清晰起见,移除无关系的细节 */ public: // 所有的子类可以使用消息交流 virtual bool HandleMessage(const Telegram & msg) = 0; /* 为清晰起见移除无关系的细节 */ }; template <class entity_type> class State { public: // 如果智能体从消息发送器中接收了一条消息执行这个 virtual bool OnMessage(entity_type *, const Telegram &) = 0; /* 为清晰起见移除无关系的细节 */ }; bool StateMachine::HandleMessage(const Telegram & msg) cosnt { // 首先看看当前的状态是否是有效的并且可以处理消息 if (m_pCurrentState && m_pCurrentState->OnMessage(m_pOwner, msg)) { return true; } // 如果不是,且如果一个全局状态被执行,发送消息给全局状态 if (m_pGlobalState && m_pGlobalState->OnMessage(m_pOwner, msg)) { return true; } return false; } bool Miner::HandleMessage(const Telegram & msg) { return m_pStateMachine->HandleMessage(msg); }
2.8.5 Elsa做晚饭
2.8.6 总结
WestWorld1 代码
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#ifndef LOCATION_H_ #define LOCATION_H_ enum location_type { shack, goldmine, bank, saloon }; #endif
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#ifndef NAMES_H #define NAMES_H #include <string> enum { ent_Miner_Bob, ent_Elsa }; inline std::string GetNameOfEntity(int n) { switch (n) { case ent_Miner_Bob: return "Miner Bob"; case ent_Elsa: return "Elsa"; default: return "UNKNOWN"; } } #endif
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#ifndef STATE_H_ #define STATE_H_ class Miner; class State { public: virtual ~State() {} // this will execute when the state is entered virtual void Enter(Miner *) = 0; // this is the state's normal update function virtual void Execute(Miner *) = 0; // this will execute when the state is exited virtual void Exit(Miner *) = 0; }; #endif
![](https://i-blog.csdnimg.cn/blog_migrate/8f900a89c6347c561fdf2122f13be562.gif)
![](https://i-blog.csdnimg.cn/blog_migrate/961ddebeb323a10fe0623af514929fc1.gif)
#ifndef MINER_OWNED_STATES_H #define MINER_OWNED_STATES_H #include <iostream> #include "State.h" // In this state the miner will walk to a goldmine and pick up a nugget of gold. // If the miner already has a nugget of gold he'll change state to VisitBankAndDepositGold. // If he gets thirsty he'll change state to QuenchThirst class EnterMineAndDigForNugget : public State { public: EnterMineAndDigForNugget() { } EnterMineAndDigForNugget(const EnterMineAndDigForNugget &); EnterMineAndDigForNugget& operator=(const EnterMineAndDigForNugget &); public: static EnterMineAndDigForNugget * Instance(); virtual void Enter(Miner * miner); virtual void Execute(Miner * miner); virtual void Exit(Miner * miner); }; #endif // Entity will go to a bank and deposit any nuggets he is carrying. // If the miner is subsequently wealthy enough he'll walk home, // otherwise he'll keep going to get more gold class VisitBankAndDepositGold : public State { private: VisitBankAndDepositGold() {} VisitBankAndDepositGold(const VisitBankAndDepositGold &); VisitBankAndDepositGold& operator=(const VisitBankAndDepositGold &); public: static VisitBankAndDepositGold * Instance(); virtual void Enter(Miner * miner); virtual void Execute(Miner * miner); virtual void Exit(Miner * miner); }; // Miner will go home and sleep until his fatigue is decreased sufficiently class GoHomeAndSleepTilRested : public State { private: GoHomeAndSleepTilRested() {} GoHomeAndSleepTilRested(const GoHomeAndSleepTilRested &); GoHomeAndSleepTilRested& operator=(const GoHomeAndSleepTilRested &); public: