关于毕业设计之状态模式

来源:互联网 发布:cab a4 编辑软件 编辑:程序博客网 时间:2024/05/01 00:41

       毕业设计是我用OGRE作为底层引擎做的2次开发的封装做的一个第一人称角色扮演游戏,所以用到的技术就相当基础。在游戏里面,做敌人的时候,就用到了状态设计模式。这里我是看《游戏人工智能编程案例精髓》学会的。

      有限状态模式,常常称FSM,一般就是用来设计具有智能幻觉的游戏智能体。简单的说,就是智能体能自行的切换自己当下所处的状态。有限意思就是在有一定数量的条件下进行转换。旧时比较繁琐的做法就是,在更新函数里面,用一个很长很长的switch。。。case。。。语句来不断手动判断当前的智能体是不是满足了某个条件,满足,就变换它的状态。这里,先提这种做法的繁琐,应为一个函数,一般来说不应该太长,同样要让人能一目了然知道是做什么用的。这里的更新函数将很大,而且当有新的条件的时候,就会无限的加大函数体,还会让人看不懂,在后期自己维护和重构自己的代码的时候,就会很难下手。还有一个死穴,就是这样的判断方法,在改变的时候,很不灵活,无论是添加还是删除每个条件和这个条件的各种无法预见的变换,都会导致代码容易出各种莫名其妙的错误。所以,这个时候,用FSM,就显得更为可行。

    既然是有限状态,那么就必须在做的时候,列出会有的状态和改变这种状态的条件。因为这里,之所以能实现自动转换状态,那是因为每种状态就将是一个单独的类,这些类就仅仅是做关于自己这种状态下,应该实现的动作。

     一般来说,FSM通常都会有一个纯虚的模版基类,用来统一接口。例如我的敌人状态类:

 template<class enimy_T>
class FSMState
{
public:
 virtual ~FSMState(){}
public:
 //最开始进入这个状态的函数
 virtual void Enter(enimy_T *enimy) = 0;
 //推出这个状态的函数
 virtual void Exit(enimy_T *enimy) = 0;
 //这个更新函数必须在敌人类的更新函数里面调用
 virtual void Update(enimy_T *enimy,float delta) = 0;
};

我的敌人状态包括:闲逛,追逐攻击玩家和死亡,如下:

class Enimy;
//闲逛状态
class SWalkAround : public FSMState<Enimy>
{
public:
 SWalkAround(){}

public:
 void Enter(Enimy *enimy);
 void Exit(Enimy *enimy);
 void Update(Enimy *enimy,float delta);
public:
 //实现单例
 static SWalkAround * Instance();
};

//攻击
class SAttack : public FSMState<Enimy>
{
public:
 SAttack(){}

public:
 void Enter(Enimy *enimy);
 void Exit(Enimy *enimy);
 void Update(Enimy *enimy,float delta);
public:
 //实现单例
 static SAttack * Instance();
};

//死亡
class SDead : public FSMState<Enimy>
{
public:
 SDead(){}

public:
 void Enter(Enimy *enimy);
 void Exit(Enimy *enimy);
 void Update(Enimy *enimy,float delta);
public:
 //实现单例
 static SDead * Instance();
};

 

在这里用到了设计模式里面的单例模式,单例模式的实现,就是统一外部调用的该类的对象,这个对象是一个local-static,这样,就不需要再要调用这个类的某个函数或者仅仅是取这个类里面的某个信息的时候,要处处创建和调用构造和析构。而且同一了对象。实现如下:

SAttack* SAttack::Instance()
{
 static SAttack instance;
 return &instance;
}

在写好了里面的所有函数的实现,现在就必须在敌人智能体里面添加一个基类对象的成员变量:

//FSM 用于敌人类的状态自治
    FSMState<Enimy>             *mCurrentState;

还有在更新函数里面调用的接口函数:

void  ChangeState(FSMState<Enimy> *st);

void   Enimy::ChangeState(FSMState<Enimy> *st)
{
    assert(mCurrentState && st);

 mCurrentState->Exit(this);
 mCurrentState = st;
 mCurrentState->Enter(this);
}

在更新函数里面调用状态的更新函数:

void  Enimy::Update(Real delta)
{
 if(mCurrentState == NULL) return ;

 mCurrentState->Update(this,delta);
 mCurrentAnim->addTime(delta);
 。。。。

}

这里,状态的更新函数,就是用来在满足条件的时候,调用敌人智能体的ChangeState,这个时候,单例模式就能独领风骚了,因为在这个函数传参数的时候,是传对应的状态的单例,例如,在闲逛的函数实现里面:

void SWalkAround::Update(Enimy *enimy,float delta)
{
 if(enimy->IsInAttckRange(delta))
 {
  if(enimy->IsCollPlayer())
  {
   enimy->ChangeState(SAttack::Instance());
   return;
  }
  enimy->WalkForward(delta);
  return;
 }
 else
  enimy->WalkAI(delta);
}

这样,一个状态仅仅只是实现自己状态类要实现的功能,类就会简短而且功能单一,在改动状态的功能的时候,也不会牵一发而动全身,添加新的功能也会很方便,就直接在添加一个类继承基类就行了,仅仅在智能体类里面调用一下下,就能放手任状态自己自动去改变。相当的好用啊!

把闲逛的状态函数实现贴出来,仅当参考:

//闲逛
SWalkAround* SWalkAround::Instance()
{
 static SWalkAround instance;
 return &instance;
}
//-------------------------------------------------------------------
void SWalkAround::Enter(Enimy *enimy)
{
 enimy->SetCurrentAnim(enimy->GetWalkAnim());
}
//-------------------------------------------------------------------
void SWalkAround::Update(Enimy *enimy,float delta)
{
 if(enimy->IsInAttckRange(delta))
 {
  if(enimy->IsCollPlayer())
  {
   enimy->ChangeState(SAttack::Instance());
   return;
  }
  enimy->WalkForward(delta);
  return;
 }
 else
  enimy->WalkAI(delta);
}
//-------------------------------------------------------------------
void SWalkAround::Exit(Enimy *enimy)
{

}

不过要提醒的是,在用状态模式的时候,必须要有能被调用的智能体的接口,这样才能在智能体外部对智能体进行操作,当状态变多的时候,这些接口也会变得很多,所以这个也是FSM不太好的地方。

 

这里是我初步实现的有限状态,当然,里面状态的控制还是有待完善的。比如,在一般的情况下,应该是智能体在追逐玩家的时候,不是直接一发现玩家的时候,玩家的位置为目标的,因为在现实里面,敌人在发现玩家后,玩家还是会不断的运动的,所以,智能体应该预测玩家的下一个位置是在哪里,玩家正在按哪个方向走,智能体也按玩家的运动目标点去运动,而不是直接朝向玩家就得了。这里就必须用到玩家的运动速度和智能体的运动速度来计算在哪个点,将是他们相遇的点,那个点就是智能体的目标点。还有,在运动的时候,应该会出现,距离玩家远一点的时候, 就要运动快一点,在快接近玩家的时候,速度开始慢下来,这样就能在玩家面前停下来攻击了。不过这些,将在我现在工作的项目中实现。因为那个是一个飞机项目。所以运动的模式,相对比较灵活。以后将会介绍各种运动的方式和各种运动方式的切换设计。

 

 

原创粉丝点击