《游戏人工智能编程》读书总结一

1,矢量的运算、速度、加速度和力

矢量的大小代表了车辆的速度,方向代表了车的方向

(1)矢量的加法:


如果两个矢量箭头相对,进行加法要对向量进行平移,再相加


(2)实例:判断敌人在主角的正面还是后面


s1是主角的正前方,通过主角.getForward()得到,s2是主角到敌人的向量,s2=(y2-y1,x2-x1)

由公式cos(向量夹角)=(s1*s2)/(s1的模*s2的模)

因此cos(夹角)小于90度为正数说明敌人在主角正面,由于分母为正数,因此s1*s2>0为正面<0为背面

(2)速度

对于均匀变化的速度



因此物体的位置,对于一个移动车辆Vehicle类这样描述速度均匀变化的位置:

class Vehicle
{
	vector m_vPosition;
	vector m_vVelocity;
public:
	void Update(float TimeElapsedSinceLastUpdate) 
	{
		m_vPosition += m_vVelocity * TimeElapsedSinceLastUpdate;
	}
};

(3)加速度

加速度a和速度、时间的关系:          

当前速度v=加速度*时间+起始速度u:

距离=起始速度*时间+加速度*时间的平方*(1/2):

当前速度v的平方=起始速度u的平方+2*加速度*距离(适用于加速度a是恒定的情况):

(4)力

加速度=力/质量   

实例:

class SpaceShip
{
private:
	vector m_Position;
	vector m_Velocity;
	float m_fMass;
public:
	void Update(float TimeElapsedSinceLastUpdate,float ForceOnShip) 
	{
		//计算加速度
		float acceleration = ForceOnShip / m_fMass;
		//计算速度
		m_Velocity += acceleration * TimeElapsedSinceLastUpdate;
		//更新位置
		m_Position += m_Velocity * TimeElapsedSinceLastUpdate;
	}
};

个人感想:

我们从小学习的牛顿定律,通过坐标系下多个向量对物体运动的分析就是能够在游戏世界运用这些定律来描述这个物体如何运动和变化的。先认识世界,再创造世界。


2,、游戏开发中的状态模式

在状态模式中每个状态进入和退出需要传入角色Miner的指针

class State
{
public:
   virtual ~State(){}
   virtual void Enter(Miner *)=0;
   virtual void Execute(Miner *)=0;
   virtual void Exit(Miner *)=0;
}

在ChangeState中:

//在changeState中传入新改变状态的指针
void Miner::changeState(State* pNewState)
{
    //首先要判断传入的状态指针和当前状态的指针都是有效的
    assert(m_pCurrentState && pNewState);
    //退出和进入状态传入角色的指针
    m_pCurrentState->Exit(this);
    m_pCurrentState=pNewState;
    m_pCurrentState->Enter(this);
}

注意角色Miner是如何将this指针传递给每个状态的,这使得状态可以调用角色Miner的接口来访问任何相关数据。(状态模式通过在状态转换时调用旧状态退出,新状态进入来销毁,传入角色指针的。拿到指针才能调用主角血量等数据)

在状态模式中每个状态都应该是一个单例对象,这是为了确保每个状态只有一个实例。消除了在状态每次切换时分配和释放内存的需要。但是有一个缺点,单例状态无法使用自身局部的,智能体专用的数据,例如一个智能体使用一个状态,当进入状态应该移动它到一个任意位置,这个位置不能被存储在状态当中,因为每个智能体使用这个状态位置不一样,只能在外部存储要经过智能体接口才能访问,如果智能体要重复访问大量外部数据,就应该考虑放弃单例设计模式,写一些代码来管理分配释放状态内存。

另外,游戏设计中包含许多不同实体类型的环境(玩家、怪物、植被),需要一个管理者对象来进行创建物体,删除物体等操作,往往要设计成单例实例进行全局访问。

比如c++中的单例模式:



比如一个状态机的UML类图如下:


在每个状态子类中都有一个instance()方法来获取该状态的单例(而不是state基类设为单例),比如第一个EnterMineAndDigForNugget状态中Instance函数返回的是一个static的EnterMineAndDigForNugget状态对象:


在EnterMineAndDigForNugget状态中的Execute函数中包含了该状态下角色要进行的所有操作,如果要进行状态change,要传入将要转换的状态单例


在设计State基类中,有两种方法,第一种创建State基类,继承出去,第二种通过让它变成一个类的模板,让它可重用


子类状态只需继承即可


对于游戏中如果有上厕所这种突发状态要打断任何状态,有三种解决方式:

1、每个状态都加入判断是否上厕所的判断逻辑(不好)

2、在Update函数中持续判断(相对好点)

3、在角色类中除了有当前状态,再加一个全局状态和之前状态,当全局(上厕所)状态结束后,退到之前状态(最好)


把所有状态相关数据和方法封装在一个StateMachine(相当于StateManager类)中,负责管理状态切换和设置当前状态

那么这个状态机什么时候生成呢?被谁调用呢?可以参考角色(Miner)类


UML关系如下:



3、角色间消息的传递

角色的消息发送:     

传递消息的消息被封装到了一个叫Telegram结构体中,包含了发送角色和接受角色id,信息文件id,延迟时间,额外信息

        对于消息的发送和管理由MessageDispatcher类完成,调用MessageDispatcher::DispatchMessage并附上必要信息(消息类型,发送时间,接受者id),根据这些信息创建Telegram。同时,该类必须知道哪个角色的指针发送的,必须调用EntityManager这个单例类,该类根据传入的角色id返回角色指针


一个新的角色创建在字典中注册


在MessageDispatcher类负责管理消息的发送,是一个单例


在DispatchMessage函数如下图定义:

根据参数来构造Telegram并调用Discharge来发送消息

而在DispatchDelayedMessages函数如下:

主要检查排队的Telegram是否有过期的时间戳,如果有,就发送到接受角色并从队列里面移除


角色的消息处理:

角色的基类中设置纯虚函数HandleMessage用来接受消息

在HandleMessage方法中交给角色状态机中的HandleMessage处理

bool Miner::HandleMessage(const Telegram& msg)
{
      return m_pStateMachine->HandleMessage(msg);
}

在State中添加OnMessage纯虚方法用来处理消息,

virtual bool OnMessage(entity_type*,const Telegram&)=0

在StateMachine::HandleMessage方法中:

加入消息系统后的UML图:



应用场景实例:







结尾:

        消息的加入使得智能的假象得到了巨大的加强,不要将游戏智能体只限制在一个有限状态机,有时候可以用两个进行并行工作:一个FSM控制角色行动,一个控制武器的瞄准、射击等,也可以一个状态本身就包含一个状态机,比如游戏包含搜索、战斗、巡逻,而战斗状态包含一个状态机管理战斗需要的状态(躲闪,追踪敌人,射击)


猜你喜欢

转载自blog.csdn.net/zhangxiaofan666/article/details/79550501