AI服务器的设计与实现

本文来自sniperhuangwei的专栏,此处纯粹收藏,转载请标明作者及原文出处,以示尊重!!

原文作者:sniperhuangwei

原文出处:http://blog.youkuaiyun.com/sniperhuangwei/archive/2010/07/14/5735610.aspx

 

 

      经过一段时间的设计与完善,我们游戏的AI服务器已经达到了基本的性能要求,目前单个AI进程可同时运行4000+个频繁的AI对象。

在前面一篇博客中已经提到过,AI服务器的主逻辑循环是单线程的,这个线程上运行了数千个用户级线程,每个用户级线程运行一个AI对象。AI对象被激活之后就会运行一段lua脚本,以实现AI逻辑.

       之所以采用用户级线程(windows下是fiber,linux下使用ucontext)的方案,是因为AI的实现使用了大量的远程调用,如果使用同步调用势必导致主线程的阻塞,从而影响AI服务器的性能。采用异步调用又导致了逻辑的过分复杂。而用户级线程正好解决了这些问题,向上提供了一个同步调用的接口,又不会导致主线程的阻塞(当一个用户级线程处于等待结果的状态下,调度器可以选择另一个用户级线程来运行)。

AI服务器的主要构件是用户级线程调度器,和一个用户级线程池,服务器启动后会产生一组用户级线程序,并且在每个线程上创建一个lua虚拟机。基本的设计思路已经介绍完毕,下面介绍各个主要的组成部分:

首先是主循环:

view plaincopy to clipboardprint?
void CAIApp::Process()     
{     
    psarmor l_pa(*this);     
    Scheduler::Init();     
    while(!GetExitTaskFlag() && l_pa(psobj::realtime))     
    {     
        //如果到game的连接断开,执行错误处理并尝试重连     
        while(!m_flag2Game)     
        {     
            //连接断了,要清除所有已经绑定的Ai对象     
            //g_AiObjMap为空的话不可能有任务在运行     
            if(!g_AiObjMap.empty())     
            {        
                //连接已经断开,停掉所有运行的AI     
                {     
                    std::map<uLong,rptr<AiAvatar> >::iterator it =  g_AiObjMap.begin();     
                    std::map<uLong,rptr<AiAvatar> >::iterator end =  g_AiObjMap.end();     
                    for( ; it != end; ++it)     
                        it->second->StopAi();     
                }     
                //清理active列表     
                Scheduler::ClearActiveList();     
                //清理timeout列表     
                Scheduler::ClearTimeOut();     
                {     
                    std::cout << "到gameserver的连接断开,清除所有绑定对象" << std::endl;     
                    std::map<uLong,rptr<AiAvatar> >::iterator it =  g_AiObjMap.begin();     
                    std::map<uLong,rptr<AiAvatar> >::iterator end =  g_AiObjMap.end();     
                    for( ; it != end; ++it)     
                        it->second = 0;     
                    g_AiObjMap.clear();     
                }     
                //清理aigroup      
                {     
                    std::map<long,rptr<AiGroup> >::iterator it =  g_GroupMap.begin();     
                    std::map<long,rptr<AiGroup> >::iterator end =  g_GroupMap.end();     
                    for( ; it != end; ++it)     
                        it->second = 0;     
                    g_GroupMap.clear();     
                }     
            }     
            m_pToGame = 0;     
            while(m_pToGame._nil())     
            {     
                rptr<DataSocket> l_sock   =g_aiapp->Connect(g_aiapp->m_config.m_gameip,g_aiapp->m_config.m_gameport);     
                if(l_sock._nil())     
                {     
                    std::cout << "连接game失败!5秒后重试..." << std::endl;     
                }     
                else    
                {     
                    printf("连接game成功...");     
                    WPacket l_wpk   =g_aiapp->GetWPacket();     
                    l_wpk.WriteCmd(CMD_AM_AILOGIN);     
                    l_wpk.WriteShort(g_aiapp->m_config.m_mapcount);     
                    for( int i = 0;i < g_aiapp->m_config.m_mapcount; ++i)     
                    {     
                        l_wpk.WriteString(g_aiapp->m_config.m_names[i].c_str());     
                    }     
                    l_sock->SendData(l_wpk);     
                    m_pToGame = l_sock;     
                    m_flag2Game = true;     
                    break;     
                }     
                Sleep(5000);     
            }     
        }     
        Scheduler::Schedule();     
        PeekPacket(50);     
    }     
    Scheduler::Destroy();     
}    
void CAIApp::Process()  
{  
 psarmor l_pa(*this);  
 Scheduler::Init();  
 while(!GetExitTaskFlag() && l_pa(psobj::realtime))  
 {  
  //如果到game的连接断开,执行错误处理并尝试重连  
  while(!m_flag2Game)  
  {  
   //连接断了,要清除所有已经绑定的Ai对象  
   //g_AiObjMap为空的话不可能有任务在运行  
   if(!g_AiObjMap.empty())  
   {   
    //连接已经断开,停掉所有运行的AI  
    {  
     std::map<uLong,rptr<AiAvatar> >::iterator it =  g_AiObjMap.begin();  
     std::map<uLong,rptr<AiAvatar> >::iterator end =  g_AiObjMap.end();  
     for( ; it != end; ++it)  
      it->second->StopAi();  
    }  
    //清理active列表  
    Scheduler::ClearActiveList();  
    //清理timeout列表  
    Scheduler::ClearTimeOut();  
    {  
     std::cout << "到gameserver的连接断开,清除所有绑定对象" << std::endl;  
     std::map<uLong,rptr<AiAvatar> >::iterator it =  g_AiObjMap.begin();  
     std::map<uLong,rptr<AiAvatar> >::iterator end =  g_AiObjMap.end();  
     for( ; it != end; ++it)  
      it->second = 0;  
     g_AiObjMap.clear();  
    }  
    //清理aigroup   
    {  
     std::map<long,rptr<AiGroup> >::iterator it =  g_GroupMap.begin();  
     std::map<long,rptr<AiGroup> >::iterator end =  g_GroupMap.end();  
     for( ; it != end; ++it)  
      it->second = 0;  
     g_GroupMap.clear();  
    }  
   }  
   m_pToGame = 0;  
   while(m_pToGame._nil())  
   {  
    rptr<DataSocket> l_sock =g_aiapp->Connect(g_aiapp->m_config.m_gameip,g_aiapp->m_config.m_gameport);  
    if(l_sock._nil())  
    {  
     std::cout << "连接game失败!5秒后重试..." << std::endl;  
    }  
    else 
    {  
     printf("连接game成功...");  
     WPacket l_wpk =g_aiapp->GetWPacket();  
     l_wpk.WriteCmd(CMD_AM_AILOGIN);  
     l_wpk.WriteShort(g_aiapp->m_config.m_mapcount);  
     for( int i = 0;i < g_aiapp->m_config.m_mapcount; ++i)  
     {  
      l_wpk.WriteString(g_aiapp->m_config.m_names[i].c_str());  
     }  
     l_sock->SendData(l_wpk);  
     m_pToGame = l_sock;  
     m_flag2Game = true;  
     break;  
    }  
    Sleep(5000);  
   }  
  }  
  Scheduler::Schedule();  
  PeekPacket(50);  
 }  
 Scheduler::Destroy();  

void CAIApp::Process()  
{  
    psarmor l_pa(*this);  
    Scheduler::Init();  
    while(!GetExitTaskFlag() && l_pa(psobj::realtime))  
    {  
        //如果到game的连接断开,执行错误处理并尝试重连  
        while(!m_flag2Game)  
        {  
            //连接断了,要清除所有已经绑定的Ai对象  
            //g_AiObjMap为空的话不可能有任务在运行  
            if(!g_AiObjMap.empty())  
            {     
                //连接已经断开,停掉所有运行的AI  
                {  
                    std::map<uLong,rptr<AiAvatar> >::iterator it =  g_AiObjMap.begin();  
                    std::map<uLong,rptr<AiAvatar> >::iterator end =  g_AiObjMap.end();  
                    for( ; it != end; ++it)  
                        it->second->StopAi();  
                }  
                //清理active列表  
                Scheduler::ClearActiveList();  
                //清理timeout列表  
                Scheduler::ClearTimeOut();  
                {  
                    std::cout << "到gameserver的连接断开,清除所有绑定对象" << std::endl;  
                    std::map<uLong,rptr<AiAvatar> >::iterator it =  g_AiObjMap.begin();  
                    std::map<uLong,rptr<AiAvatar> >::iterator end =  g_AiObjMap.end();  
                    for( ; it != end; ++it)  
                        it->second = 0;  
                    g_AiObjMap.clear();  
                }  
                //清理aigroup   
                {  
                    std::map<long,rptr<AiGroup> >::iterator it =  g_GroupMap.begin();  
                    std::map<long,rptr<AiGroup> >::iterator end =  g_GroupMap.end();  
                    for( ; it != end; ++it)  
                        it->second = 0;  
                    g_GroupMap.clear();  
                }  
            }  
            m_pToGame = 0;  
            while(m_pToGame._nil())  
            {  
                rptr<DataSocket> l_sock   =g_aiapp->Connect(g_aiapp->m_config.m_gameip,g_aiapp->m_config.m_gameport);  
                if(l_sock._nil())  
                {  
                    std::cout << "连接game失败!5秒后重试..." << std::endl;  
                }  
                else 
                {  
                    printf("连接game成功...");  
                    WPacket l_wpk   =g_aiapp->GetWPacket();  
                    l_wpk.WriteCmd(CMD_AM_AILOGIN);  
                    l_wpk.WriteShort(g_aiapp->m_config.m_mapcount);  
                    for( int i = 0;i < g_aiapp->m_config.m_mapcount; ++i)  
                    {  
                        l_wpk.WriteString(g_aiapp->m_config.m_names[i].c_str());  
                    }  
                    l_sock->SendData(l_wpk);  
                    m_pToGame = l_sock;  
                    m_flag2Game = true;  
                    break;  
                }  
                Sleep(5000);  
            }  
        }  
        Scheduler::Schedule();  
        PeekPacket(50);  
    }  
    Scheduler::Destroy();  

void CAIApp::Process()
{
 psarmor l_pa(*this);
 Scheduler::Init();
 while(!GetExitTaskFlag() && l_pa(psobj::realtime))
 {
  //如果到game的连接断开,执行错误处理并尝试重连
  while(!m_flag2Game)
  {
   //连接断了,要清除所有已经绑定的Ai对象
   //g_AiObjMap为空的话不可能有任务在运行
   if(!g_AiObjMap.empty())
   {
    //连接已经断开,停掉所有运行的AI
    {
     std::map<uLong,rptr<AiAvatar> >::iterator it =  g_AiObjMap.begin();
     std::map<uLong,rptr<AiAvatar> >::iterator end =  g_AiObjMap.end();
     for( ; it != end; ++it)
      it->second->StopAi();
    }
    //清理active列表
    Scheduler::ClearActiveList();
    //清理timeout列表
    Scheduler::ClearTimeOut();
    {
     std::cout << "到gameserver的连接断开,清除所有绑定对象" << std::endl;
     std::map<uLong,rptr<AiAvatar> >::iterator it =  g_AiObjMap.begin();
     std::map<uLong,rptr<AiAvatar> >::iterator end =  g_AiObjMap.end();
     for( ; it != end; ++it)
      it->second = 0;
     g_AiObjMap.clear();
    }
    //清理aigroup
    {
     std::map<long,rptr<AiGroup> >::iterator it =  g_GroupMap.begin();
     std::map<long,rptr<AiGroup> >::iterator end =  g_GroupMap.end();
     for( ; it != end; ++it)
      it->second = 0;
     g_GroupMap.clear();
    }
   }
   m_pToGame = 0;
   while(m_pToGame._nil())
   {
    rptr<DataSocket> l_sock =g_aiapp->Connect(g_aiapp->m_config.m_gameip,g_aiapp->m_config.m_gameport);
    if(l_sock._nil())
    {
     std::cout << "连接game失败!5秒后重试..." << std::endl;
    }
    else
    {
     printf("连接game成功...");
     WPacket l_wpk =g_aiapp->GetWPacket();
     l_wpk.WriteCmd(CMD_AM_AILOGIN);
     l_wpk.WriteShort(g_aiapp->m_config.m_mapcount);
     for( int i = 0;i < g_aiapp->m_config.m_mapcount; ++i)
     {
      l_wpk.WriteString(g_aiapp->m_config.m_names[i].c_str());
     }
     l_sock->SendData(l_wpk);
     m_pToGame = l_sock;
     m_flag2Game = true;
     break;
    }
    Sleep(5000);
   }
  }
  Scheduler::Schedule();
  PeekPacket(50);
 }
 Scheduler::Destroy();

上面代码的主要作用就是尝试连接gameserver,如果连接成功就在循环中调用调度器的调度函数以选择合适的用户级线程运行。PeekPacket(50);会从网络层提取网络包,如果没有网络包则会休眠最多50毫秒.

下面在来看看调度器:


view plaincopy to clipboardprint?
void Scheduler::Schedule()     
{     
    //将所有等待添加到m_activeList中的纤程都添加进去     
    {     
        for(unsigned int i = 0; i < pending_index; ++i)     
        {     
            uthread *ut = m_uthreads[m_pendingAdd[i]];     
            ut->SetNext(0);     
            if(m_active_tail)     
            {     
                m_active_tail->SetNext(ut);     
                m_active_tail = ut;     
            }     
            else    
            {     
                m_active_head = m_active_tail = ut;     
            }     
        }     
        pending_index = 0;     
    }     
    uthread *cur = m_active_head;     
    uthread *pre = NULL;     
    while(cur)     
    {     
        g_aiapp->PeekPacket(0);     
        m_curuid = cur->GetUid();     
        SwitchToFiber(cur->GetUContext());     
        m_curuid = -1;     
        unsigned char status = cur->GetStatus();     
        //当纤程处于以下状态时需要从可运行队列中移除     
        if(status == DEAD || status == SLEEP || status == WAIT4EVENT || status == UNACTIVED || status == YIELD)     
        {     
            //删除首元素     
            if(cur == m_active_head)     
            {     
                //同时也是尾元素     
                if(cur == m_active_tail)     
                    m_active_head = m_active_tail = NULL;     
                else    
                    m_active_head = cur->Next();     
            }     
            else if(cur == m_active_tail)     
            {     
                    pre->SetNext(NULL);     
                    m_active_tail = pre;     
            }     
            else    
                pre->SetNext(cur->Next());     
            uthread *tmp = cur;     
            cur = cur->Next();     
            tmp->SetNext(0);     
            //如果仅仅是让出处理器,需要重新投入到可运行队列中     
            if(status == YIELD)     
                Add2Active(tmp);     
                 
        }     
        else    
        {     
            pre = cur;     
            cur = cur->Next();     
        }     
    }     
    //看看有没有timeout的纤程     
    {     
        uLong now = dbc::GetTickCount();     
        while(m_timeoutlist.Min() !=0 && m_timeoutlist.Min() <= now)     
        {     
            st_timeout *timeout = m_timeoutlist.PopMin();     
            if(timeout->ut->GetStatus() == WAIT4EVENT || timeout->ut->GetStatus() == SLEEP)     
            {     
                timeout->ut->wakeuptick = timeout->_timeout;     
                Add2Active(timeout->ut);     
            }     
        }     
    }     
}    
void Scheduler::Schedule()  
{  
 //将所有等待添加到m_activeList中的纤程都添加进去  
 {  
  for(unsigned int i = 0; i < pending_index; ++i)  
  {  
   uthread *ut = m_uthreads[m_pendingAdd[i]];  
   ut->SetNext(0);  
   if(m_active_tail)  
   {  
    m_active_tail->SetNext(ut);  
    m_active_tail = ut;  
   }  
   else 
   {  
    m_active_head = m_active_tail = ut;  
   }  
  }  
  pending_index = 0;  
 }  
 uthread *cur = m_active_head;  
 uthread *pre = NULL;  
 while(cur)  
 {  
  g_aiapp->PeekPacket(0);  
  m_curuid = cur->GetUid();  
  SwitchToFiber(cur->GetUContext());  
  m_curuid = -1;  
  unsigned char status = cur->GetStatus();  
  //当纤程处于以下状态时需要从可运行队列中移除  
  if(status == DEAD || status == SLEEP || status == WAIT4EVENT || status == UNACTIVED || status == YIELD)  
  {  
   //删除首元素  
   if(cur == m_active_head)  
   {  
    //同时也是尾元素  
    if(cur == m_active_tail)  
     m_active_head = m_active_tail = NULL;  
    else 
     m_active_head = cur->Next();  
   }  
   else if(cur == m_active_tail)  
   {  
     pre->SetNext(NULL);  
     m_active_tail = pre;  
   }  
   else 
    pre->SetNext(cur->Next());  
   uthread *tmp = cur;  
   cur = cur->Next();  
   tmp->SetNext(0);  
   //如果仅仅是让出处理器,需要重新投入到可运行队列中  
   if(status == YIELD)  
    Add2Active(tmp);  
     
  }  
  else 
  {  
   pre = cur;  
   cur = cur->Next();  
  }  
 }  
 //看看有没有timeout的纤程  
 {  
  uLong now = dbc::GetTickCount();  
  while(m_timeoutlist.Min() !=0 && m_timeoutlist.Min() <= now)  
  {  
   st_timeout *timeout = m_timeoutlist.PopMin();  
   if(timeout->ut->GetStatus() == WAIT4EVENT || timeout->ut->GetStatus() == SLEEP)  
   {  
    timeout->ut->wakeuptick = timeout->_timeout;  
    Add2Active(timeout->ut);  
   }  
  }  
 }  

void Scheduler::Schedule()  
{  
    //将所有等待添加到m_activeList中的纤程都添加进去  
    {  
        for(unsigned int i = 0; i < pending_index; ++i)  
        {  
            uthread *ut = m_uthreads[m_pendingAdd[i]];  
            ut->SetNext(0);  
            if(m_active_tail)  
            {  
                m_active_tail->SetNext(ut);  
                m_active_tail = ut;  
            }  
            else 
            {  
                m_active_head = m_active_tail = ut;  
            }  
        }  
        pending_index = 0;  
    }  
    uthread *cur = m_active_head;  
    uthread *pre = NULL;  
    while(cur)  
    {  
        g_aiapp->PeekPacket(0);  
        m_curuid = cur->GetUid();  
        SwitchToFiber(cur->GetUContext());  
        m_curuid = -1;  
        unsigned char status = cur->GetStatus();  
        //当纤程处于以下状态时需要从可运行队列中移除  
        if(status == DEAD || status == SLEEP || status == WAIT4EVENT || status == UNACTIVED || status == YIELD)  
        {  
            //删除首元素  
            if(cur == m_active_head)  
            {  
                //同时也是尾元素  
                if(cur == m_active_tail)  
                    m_active_head = m_active_tail = NULL;  
                else 
                    m_active_head = cur->Next();  
            }  
            else if(cur == m_active_tail)  
            {  
                    pre->SetNext(NULL);  
                    m_active_tail = pre;  
            }  
            else 
                pre->SetNext(cur->Next());  
            uthread *tmp = cur;  
            cur = cur->Next();  
            tmp->SetNext(0);  
            //如果仅仅是让出处理器,需要重新投入到可运行队列中  
            if(status == YIELD)  
                Add2Active(tmp);  
              
        }  
        else 
        {  
            pre = cur;  
            cur = cur->Next();  
        }  
    }  
    //看看有没有timeout的纤程  
    {  
        uLong now = dbc::GetTickCount();  
        while(m_timeoutlist.Min() !=0 && m_timeoutlist.Min() <= now)  
        {  
            st_timeout *timeout = m_timeoutlist.PopMin();  
            if(timeout->ut->GetStatus() == WAIT4EVENT || timeout->ut->GetStatus() == SLEEP)  
            {  
                timeout->ut->wakeuptick = timeout->_timeout;  
                Add2Active(timeout->ut);  
            }  
        }  
    }  

void Scheduler::Schedule()
{
 //将所有等待添加到m_activeList中的纤程都添加进去
 {
  for(unsigned int i = 0; i < pending_index; ++i)
  {
   uthread *ut = m_uthreads[m_pendingAdd[i]];
   ut->SetNext(0);
   if(m_active_tail)
   {
    m_active_tail->SetNext(ut);
    m_active_tail = ut;
   }
   else
   {
    m_active_head = m_active_tail = ut;
   }
  }
  pending_index = 0;
 }
 uthread *cur = m_active_head;
 uthread *pre = NULL;
 while(cur)
 {
  g_aiapp->PeekPacket(0);
  m_curuid = cur->GetUid();
  SwitchToFiber(cur->GetUContext());
  m_curuid = -1;
  unsigned char status = cur->GetStatus();
  //当纤程处于以下状态时需要从可运行队列中移除
  if(status == DEAD || status == SLEEP || status == WAIT4EVENT || status == UNACTIVED || status == YIELD)
  {
   //删除首元素
   if(cur == m_active_head)
   {
    //同时也是尾元素
    if(cur == m_active_tail)
     m_active_head = m_active_tail = NULL;
    else
     m_active_head = cur->Next();
   }
   else if(cur == m_active_tail)
   {
     pre->SetNext(NULL);
     m_active_tail = pre;
   }
   else
    pre->SetNext(cur->Next());
   uthread *tmp = cur;
   cur = cur->Next();
   tmp->SetNext(0);
   //如果仅仅是让出处理器,需要重新投入到可运行队列中
   if(status == YIELD)
    Add2Active(tmp);
  
  }
  else
  {
   pre = cur;
   cur = cur->Next();
  }
 }
 //看看有没有timeout的纤程
 {
  uLong now = dbc::GetTickCount();
  while(m_timeoutlist.Min() !=0 && m_timeoutlist.Min() <= now)
  {
   st_timeout *timeout = m_timeoutlist.PopMin();
   if(timeout->ut->GetStatus() == WAIT4EVENT || timeout->ut->GetStatus() == SLEEP)
   {
    timeout->ut->wakeuptick = timeout->_timeout;
    Add2Active(timeout->ut);
   }
  }
 }

调度器首先将重新处于激活态的线程投入到运行队列中,然后遍历可运行队列,运行其中的线程,调度器的最后将处理所有处于休眠状态的线程,如果线程的休眠时间到了,则将线程重新投入到可运行队列中。在这里使用了一个极小堆来处理超时。

从上面的代码可以看出,当调度器挑选了一个线程运行之后,代码路径就跳转到线程中,当线程需要阻塞时,就会设置一个状态(YIELD, WAIT4EVENT或SLEEP)并将运行权又重新交回给调度器,当调度器重新获得运行权后,代码会从SwitchToFiber(cur->GetUContext());中返回,调度器需要根据上次运行的线程的状态,或者将线程投入休眠队列(SLEEP),或者重新将线程投入到队列的末尾(YIELD)或者从运行队列中删除(WAIT4EVENT).

      

下面再看看一个同步调用的例子:

以移动为例,假设AI请求移动到某给位置,则需要向gameserver发送移动请求,直到到达目标点,或者发现移动失败才会从调用中返回:

view plaincopy to clipboardprint?
int AiAvatar::Move(Point3D &pt,short cntx,uLong ms)     
{     
    class PosBlock : public BlockStruct     
    {        
    public:     
        PosBlock(Point3D &pos,AiAvatar *ava)     
            :m_ava(ava),m_targetpos(pos){}     
             
        //返回true则纤程从阻塞中恢复     
        bool WakeUp()     
        {     
            //到达了请求点,恢复     
            if(m_targetpos.x == m_ava->GetPos().x &&     
               m_targetpos.y == m_ava->GetPos().y )     
            {     
                return true;     
            }     
            return false;     
        }     
    private:     
        Point3D m_targetpos;     
        AiAvatar *m_ava;     
    };     
    //printf("开始移动/n");     
    //向GameServer发送移动请求     
    WPacket l_wpk = g_aiapp->GetWPacket();     
    l_wpk.WriteCmd(CMD_AM_BEGMOV);     
    l_wpk.WriteLong(pt.x);     
    l_wpk.WriteLong(pt.y);     
    l_wpk.WriteLong(pt.z);     
    l_wpk.WriteShort(cntx);     
    Send2Game(this,l_wpk);     
    //阻塞所在fiber直到pos到达要求的值/或者收到移动失败消息/或则AI被请求停止     
    PosBlock pb(pt,this);     
    Scheduler::Block(&pb,ms);     
    //接到了停止AI的命令     
    if(!isAiRunning())     
        return -1;     
    bool ret = (pt.x == m_pos.x && pt.y == m_pos.y);     
    return ret ? 1:0;     
}    
int AiAvatar::Move(Point3D &pt,short cntx,uLong ms)  
{  
 class PosBlock : public BlockStruct  
 {   
 public:  
  PosBlock(Point3D &pos,AiAvatar *ava)  
   :m_ava(ava),m_targetpos(pos){}  
    
  //返回true则纤程从阻塞中恢复  
  bool WakeUp()  
  {  
            //到达了请求点,恢复  
   if(m_targetpos.x == m_ava->GetPos().x &&  
      m_targetpos.y == m_ava->GetPos().y )  
   {  
    return true;  
   }  
   return false;  
  }  
 private:  
  Point3D m_targetpos;  
  AiAvatar *m_ava;  
 };  
 //printf("开始移动/n");  
 //向GameServer发送移动请求  
 WPacket l_wpk = g_aiapp->GetWPacket();  
 l_wpk.WriteCmd(CMD_AM_BEGMOV);  
 l_wpk.WriteLong(pt.x);  
 l_wpk.WriteLong(pt.y);  
 l_wpk.WriteLong(pt.z);  
 l_wpk.WriteShort(cntx);  
 Send2Game(this,l_wpk);  
    //阻塞所在fiber直到pos到达要求的值/或者收到移动失败消息/或则AI被请求停止  
 PosBlock pb(pt,this);  
 Scheduler::Block(&pb,ms);  
 //接到了停止AI的命令  
 if(!isAiRunning())  
  return -1;  
 bool ret = (pt.x == m_pos.x && pt.y == m_pos.y);  
 return ret ? 1:0;  

int AiAvatar::Move(Point3D &pt,short cntx,uLong ms)  
{  
    class PosBlock : public BlockStruct  
    {     
    public:  
        PosBlock(Point3D &pos,AiAvatar *ava)  
            :m_ava(ava),m_targetpos(pos){}  
          
        //返回true则纤程从阻塞中恢复  
        bool WakeUp()  
        {  
            //到达了请求点,恢复  
            if(m_targetpos.x == m_ava->GetPos().x &&  
               m_targetpos.y == m_ava->GetPos().y )  
            {  
                return true;  
            }  
            return false;  
        }  
    private:  
        Point3D m_targetpos;  
        AiAvatar *m_ava;  
    };  
    //printf("开始移动/n");  
    //向GameServer发送移动请求  
    WPacket l_wpk = g_aiapp->GetWPacket();  
    l_wpk.WriteCmd(CMD_AM_BEGMOV);  
    l_wpk.WriteLong(pt.x);  
    l_wpk.WriteLong(pt.y);  
    l_wpk.WriteLong(pt.z);  
    l_wpk.WriteShort(cntx);  
    Send2Game(this,l_wpk);  
    //阻塞所在fiber直到pos到达要求的值/或者收到移动失败消息/或则AI被请求停止  
    PosBlock pb(pt,this);  
    Scheduler::Block(&pb,ms);  
    //接到了停止AI的命令  
    if(!isAiRunning())  
        return -1;  
    bool ret = (pt.x == m_pos.x && pt.y == m_pos.y);  
    return ret ? 1:0;  

int AiAvatar::Move(Point3D &pt,short cntx,uLong ms)
{
 class PosBlock : public BlockStruct
 {
 public:
  PosBlock(Point3D &pos,AiAvatar *ava)
   :m_ava(ava),m_targetpos(pos){}
 
  //返回true则纤程从阻塞中恢复
  bool WakeUp()
  {
            //到达了请求点,恢复
   if(m_targetpos.x == m_ava->GetPos().x &&
      m_targetpos.y == m_ava->GetPos().y )
   {
    return true;
   }
   return false;
  }
 private:
  Point3D m_targetpos;
  AiAvatar *m_ava;
 };
 //printf("开始移动/n");
 //向GameServer发送移动请求
 WPacket l_wpk = g_aiapp->GetWPacket();
 l_wpk.WriteCmd(CMD_AM_BEGMOV);
 l_wpk.WriteLong(pt.x);
 l_wpk.WriteLong(pt.y);
 l_wpk.WriteLong(pt.z);
 l_wpk.WriteShort(cntx);
 Send2Game(this,l_wpk);
    //阻塞所在fiber直到pos到达要求的值/或者收到移动失败消息/或则AI被请求停止
 PosBlock pb(pt,this);
 Scheduler::Block(&pb,ms);
 //接到了停止AI的命令
 if(!isAiRunning())
  return -1;
 bool ret = (pt.x == m_pos.x && pt.y == m_pos.y);
 return ret ? 1:0;

函数首先创建了一个阻塞条件的结构,然后阻塞在这个条件上,在这里是判断AI对象是否到达了目标点。然后将移动请求发送出去并阻塞在条件上。当gameserver把对象移动到正确的点之后,会把对象的坐标通过网络同步到AI服务器,处理网络包的时候发现那个对象对应的线程正被阻塞,就会调用阻塞条件的WakeUp函数尝试唤醒线程,此时如果条件满足,WakeUp就会返回true,线程被重新投入到可运行队列中,否则线程就会继续被阻塞。

最用来看一段AI脚本,当一个AI对象被激活(进入玩家的视野),就会为这个对象分配一个线程,这个线程就会马上运行与这个对象相关的lua入口函数:

view plaincopy to clipboardprint?
function monster_routine(this)           
    --出生点     
    local start_pos = {}     
    start_pos.x,start_pos.y,start_pos.z = getbegpos(this)     
             
    local c = 1     
         
    --巡逻点     
    local points = {     
        {x=start_pos.x+300,y=start_pos.y,z=start_pos.z},     
        {x=start_pos.x,y=start_pos.y,z=start_pos.z}     
    }     
         
         
    --生成状态机     
    stateMachine = AiStateMachine:new()     
    stateMachine.owner = this    
    --初始化trace     
    stateMachine.state_trace = trace:new():init(this,stateMachine,start_pos)     
    --stateMachine.state_trace:init(this,stateMachine,start_pos)     
    --初始化partol     
    stateMachine.state_partol = partol:new():init(this,stateMachine,start_pos,points)     
    --stateMachine.state_partol:init(this,stateMachine,start_pos,points)     
    --初始化attack     
    stateMachine.state_attack = attack:new():init(this,stateMachine)     
    --stateMachine.state_attack:init(this,stateMachine)     
    --初始化goback     
    stateMachine.state_goback = goback:new():init(this,stateMachine,start_pos)     
    --stateMachine.state_goback:init(this,stateMachine,start_pos)     
    --初始化help     
    stateMachine.state_help = help:new():init(this,stateMachine)     
    --stateMachine.state_help:init(this,stateMachine)     
         
    stateMachine.cur_state = stateMachine.state_partol     
         
    while isAiRunning(this) == true do    
                         
        if isdead(this) == true then     
            sc_yield()     
        else         
            stateMachine.cur_pos.x,stateMachine.cur_pos.y,stateMachine.cur_pos.z = getpos(this)     
            stateMachine.target = get_target(this)     
            if stateMachine.target == nil then     
                stateMachine.target = select_target(this)     
            end     
                 
                 
            --查看是否有消息要处理     
            local sender     
            local recver     
            local msg     
            local sendtick     
            sender,recver,msg,sendtick = PopMsg(this)     
            if sender ~= nil then     
                print("消息队列非空")     
                if msg == "help" then     
                    --如果自己没有目标才处理帮助请求     
                    if stateMachine.target == nil then     
                        stateMachine.target = sender     
                        stateMachine.cur_state = stateMachine.state_help     
                    end     
                end     
            end     
                 
            local ret = 0     
            ret,stateMachine.cur_state = stateMachine.cur_state:execute()        
            if ret == -1 then     
                return    
            end     
            sc_yield()     
        end     
             
    end     
end    
function monster_routine(this)    
 --出生点  
 local start_pos = {}  
 start_pos.x,start_pos.y,start_pos.z = getbegpos(this)  
    
 local c = 1  
   
 --巡逻点  
 local points = {  
  {x=start_pos.x+300,y=start_pos.y,z=start_pos.z},  
  {x=start_pos.x,y=start_pos.y,z=start_pos.z}  
 }  
   
   
 --生成状态机  
 stateMachine = AiStateMachine:new()  
 stateMachine.owner = this 
 --初始化trace  
 stateMachine.state_trace = trace:new():init(this,stateMachine,start_pos)  
 --stateMachine.state_trace:init(this,stateMachine,start_pos)  
 --初始化partol  
 stateMachine.state_partol = partol:new():init(this,stateMachine,start_pos,points)  
 --stateMachine.state_partol:init(this,stateMachine,start_pos,points)  
 --初始化attack  
 stateMachine.state_attack = attack:new():init(this,stateMachine)  
 --stateMachine.state_attack:init(this,stateMachine)  
 --初始化goback  
 stateMachine.state_goback = goback:new():init(this,stateMachine,start_pos)  
 --stateMachine.state_goback:init(this,stateMachine,start_pos)  
 --初始化help  
 stateMachine.state_help = help:new():init(this,stateMachine)  
 --stateMachine.state_help:init(this,stateMachine)  
   
 stateMachine.cur_state = stateMachine.state_partol  
   
 while isAiRunning(this) == true do 
       
  if isdead(this) == true then  
   sc_yield()  
  else   
   stateMachine.cur_pos.x,stateMachine.cur_pos.y,stateMachine.cur_pos.z = getpos(this)  
   stateMachine.target = get_target(this)  
   if stateMachine.target == nil then  
    stateMachine.target = select_target(this)  
   end  
     
     
   --查看是否有消息要处理  
   local sender  
   local recver  
   local msg  
   local sendtick  
   sender,recver,msg,sendtick = PopMsg(this)  
   if sender ~= nil then  
    print("消息队列非空")  
    if msg == "help" then  
     --如果自己没有目标才处理帮助请求  
     if stateMachine.target == nil then  
      stateMachine.target = sender  
      stateMachine.cur_state = stateMachine.state_help  
     end  
    end  
   end  
     
   local ret = 0  
   ret,stateMachine.cur_state = stateMachine.cur_state:execute()   
   if ret == -1 then  
    return 
   end  
   sc_yield()  
  end  
    
 end  
end 
function monster_routine(this)        
    --出生点  
    local start_pos = {}  
    start_pos.x,start_pos.y,start_pos.z = getbegpos(this)  
          
    local c = 1  
      
    --巡逻点  
    local points = {  
        {x=start_pos.x+300,y=start_pos.y,z=start_pos.z},  
        {x=start_pos.x,y=start_pos.y,z=start_pos.z}  
    }  
      
      
    --生成状态机  
    stateMachine = AiStateMachine:new()  
    stateMachine.owner = this 
    --初始化trace  
    stateMachine.state_trace = trace:new():init(this,stateMachine,start_pos)  
    --stateMachine.state_trace:init(this,stateMachine,start_pos)  
    --初始化partol  
    stateMachine.state_partol = partol:new():init(this,stateMachine,start_pos,points)  
    --stateMachine.state_partol:init(this,stateMachine,start_pos,points)  
    --初始化attack  
    stateMachine.state_attack = attack:new():init(this,stateMachine)  
    --stateMachine.state_attack:init(this,stateMachine)  
    --初始化goback  
    stateMachine.state_goback = goback:new():init(this,stateMachine,start_pos)  
    --stateMachine.state_goback:init(this,stateMachine,start_pos)  
    --初始化help  
    stateMachine.state_help = help:new():init(this,stateMachine)  
    --stateMachine.state_help:init(this,stateMachine)  
      
    stateMachine.cur_state = stateMachine.state_partol  
      
    while isAiRunning(this) == true do 
                      
        if isdead(this) == true then  
            sc_yield()  
        else      
            stateMachine.cur_pos.x,stateMachine.cur_pos.y,stateMachine.cur_pos.z = getpos(this)  
            stateMachine.target = get_target(this)  
            if stateMachine.target == nil then  
                stateMachine.target = select_target(this)  
            end  
              
              
            --查看是否有消息要处理  
            local sender  
            local recver  
            local msg  
            local sendtick  
            sender,recver,msg,sendtick = PopMsg(this)  
            if sender ~= nil then  
                print("消息队列非空")  
                if msg == "help" then  
                    --如果自己没有目标才处理帮助请求  
                    if stateMachine.target == nil then  
                        stateMachine.target = sender  
                        stateMachine.cur_state = stateMachine.state_help  
                    end  
                end  
            end  
              
            local ret = 0  
            ret,stateMachine.cur_state = stateMachine.cur_state:execute()     
            if ret == -1 then  
                return 
            end  
            sc_yield()  
        end  
          
    end  
end 
function monster_routine(this) 
 --出生点
 local start_pos = {}
 start_pos.x,start_pos.y,start_pos.z = getbegpos(this)
 
 local c = 1
 
 --巡逻点
 local points = {
  {x=start_pos.x+300,y=start_pos.y,z=start_pos.z},
  {x=start_pos.x,y=start_pos.y,z=start_pos.z}
 }
 
 
 --生成状态机
 stateMachine = AiStateMachine:new()
 stateMachine.owner = this
 --初始化trace
 stateMachine.state_trace = trace:new():init(this,stateMachine,start_pos)
 --stateMachine.state_trace:init(this,stateMachine,start_pos)
 --初始化partol
 stateMachine.state_partol = partol:new():init(this,stateMachine,start_pos,points)
 --stateMachine.state_partol:init(this,stateMachine,start_pos,points)
 --初始化attack
 stateMachine.state_attack = attack:new():init(this,stateMachine)
 --stateMachine.state_attack:init(this,stateMachine)
 --初始化goback
 stateMachine.state_goback = goback:new():init(this,stateMachine,start_pos)
 --stateMachine.state_goback:init(this,stateMachine,start_pos)
 --初始化help
 stateMachine.state_help = help:new():init(this,stateMachine)
 --stateMachine.state_help:init(this,stateMachine)
 
 stateMachine.cur_state = stateMachine.state_partol
 
 while isAiRunning(this) == true do
    
  if isdead(this) == true then
   sc_yield()
  else
   stateMachine.cur_pos.x,stateMachine.cur_pos.y,stateMachine.cur_pos.z = getpos(this)
   stateMachine.target = get_target(this)
   if stateMachine.target == nil then
    stateMachine.target = select_target(this)
   end
  
  
   --查看是否有消息要处理
   local sender
   local recver
   local msg
   local sendtick
   sender,recver,msg,sendtick = PopMsg(this)
   if sender ~= nil then
    print("消息队列非空")
    if msg == "help" then
     --如果自己没有目标才处理帮助请求
     if stateMachine.target == nil then
      stateMachine.target = sender
      stateMachine.cur_state = stateMachine.state_help
     end
    end
   end
  
   local ret = 0
   ret,stateMachine.cur_state = stateMachine.cur_state:execute()
   if ret == -1 then
    return
   end
   sc_yield()
  end
 
 end
end 

AI主入口函数首先创建了一个状态机,并选择一个初始状态运行。下面再看看追击状态的处理:

view plaincopy to clipboardprint?
trace = {     
owner = 0,     
StateMachine = 0,     
start_pos = 0     
}     
      
function trace:init(owner,statemachine,start_pos)     
    self.owner = owner     
    self.StateMachine = statemachine     
    self.start_pos = start_pos     
    return self     
end      
      
--追击      
function trace:execute()     
         
    if self.StateMachine.target == nil then     
        --判断离出生点的距离,太远了就回出生点     
        local dis2begpos = calDistance(self.start_pos.x,self.start_pos.y,self.StateMachine.cur_pos.x,self.StateMachine.cur_pos.y)     
        if dis2begpos >= 500 then     
            return 0,self.StateMachine.state_goback     
        else    
            --没有目标,巡逻     
            return 0,self.StateMachine.state_partol     
        end     
    else    
        local dis2begpos = calDistance(self.start_pos.x,self.start_pos.y,self.StateMachine.cur_pos.x,self.StateMachine.cur_pos.y)     
        if dis2begpos >= 4000 then     
            return 0,self.StateMachine.state_goback     
        else    
            --取得目标当前点                
            local target_pos ={}     
            target_pos.x,target_pos.y,target_pos.z = getpos(self.StateMachine.target)     
                 
            local dis = calDistance(self.StateMachine.cur_pos.x,self.StateMachine.cur_pos.y,target_pos.x,target_pos.y)       
            if dis <= 200 then     
                --print("选择攻击点")     
                --选择攻击点     
                --if cur_pos.x ~= self.cur_pos and cur_pos.y ~= self.cur_pos.y then     
                    local d_x,d_y = gen_pos_circle(target_pos.x,target_pos.y,200)     
             
                    if -1 == mov(self.owner,d_x,d_y,target_pos.z,804,1000) then     
                        return -1,nil     
                    end     
                    --面向目标     
                    turnface(self.owner,self.StateMachine.target)     
                    --切换到攻击态     
                  --end     
                return 0,self.StateMachine.state_attack     
            else    
                --在目标半径2米内随机选择一个点,作为目标点     
                local d_x,d_y = gen_pos_line(self.StateMachine.cur_pos.x,self.StateMachine.cur_pos.y,target_pos.x,target_pos.y,200,100)     
                if dis <= 300 then     
                    --离目标点小于3米直接过去     
                    if -1 == mov(self.owner,d_x,d_y,target_pos.z,804,1000) then     
                        return -1,nil     
                    end     
                else                             
                    local ttx,tty = forword(self.owner,d_x,d_y,300)     
                    if -1 == mov(self.owner,ttx,tty,target_pos.z,804,1000) then     
                        return -1,nil     
                    end     
                end     
            end     
        end     
    end     
    return 0,self.StateMachine.state_trace     
end     
             
function trace:new(o)     
  o = o or {}        
  setmetatable(o, self)     
  self.__index = self     
  return o     
end    
trace = {  
owner = 0,  
StateMachine = 0,  
start_pos = 0  
}  
   
function trace:init(owner,statemachine,start_pos)  
 self.owner = owner  
 self.StateMachine = statemachine  
 self.start_pos = start_pos  
 return self  
end   
   
--追击   
function trace:execute()  
   
 if self.StateMachine.target == nil then  
  --判断离出生点的距离,太远了就回出生点  
  local dis2begpos = calDistance(self.start_pos.x,self.start_pos.y,self.StateMachine.cur_pos.x,self.StateMachine.cur_pos.y)  
  if dis2begpos >= 500 then  
   return 0,self.StateMachine.state_goback  
  else 
     --没有目标,巡逻  
     return 0,self.StateMachine.state_partol  
  end  
 else 
  local dis2begpos = calDistance(self.start_pos.x,self.start_pos.y,self.StateMachine.cur_pos.x,self.StateMachine.cur_pos.y)  
  if dis2begpos >= 4000 then  
   return 0,self.StateMachine.state_goback  
  else 
     --取得目标当前点     
   local target_pos ={}  
   target_pos.x,target_pos.y,target_pos.z = getpos(self.StateMachine.target)  
     
   local dis = calDistance(self.StateMachine.cur_pos.x,self.StateMachine.cur_pos.y,target_pos.x,target_pos.y)   
   if dis <= 200 then  
    --print("选择攻击点")  
    --选择攻击点  
    --if cur_pos.x ~= self.cur_pos and cur_pos.y ~= self.cur_pos.y then  
     local d_x,d_y = gen_pos_circle(target_pos.x,target_pos.y,200)  
    
     if -1 == mov(self.owner,d_x,d_y,target_pos.z,804,1000) then  
      return -1,nil  
     end  
     --面向目标  
       turnface(self.owner,self.StateMachine.target)  
     --切换到攻击态  
      --end  
    return 0,self.StateMachine.state_attack  
   else 
    --在目标半径2米内随机选择一个点,作为目标点  
    local d_x,d_y = gen_pos_line(self.StateMachine.cur_pos.x,self.StateMachine.cur_pos.y,target_pos.x,target_pos.y,200,100)  
    if dis <= 300 then  
     --离目标点小于3米直接过去  
     if -1 == mov(self.owner,d_x,d_y,target_pos.z,804,1000) then  
        return -1,nil  
       end  
    else            
     local ttx,tty = forword(self.owner,d_x,d_y,300)  
       if -1 == mov(self.owner,ttx,tty,target_pos.z,804,1000) then  
        return -1,nil  
       end  
    end  
   end  
  end  
 end  
 return 0,self.StateMachine.state_trace  
end  
          
function trace:new(o)  
  o = o or {}     
  setmetatable(o, self)  
  self.__index = self  
  return o  
end 
trace = {  
owner = 0,  
StateMachine = 0,  
start_pos = 0  
}  
   
function trace:init(owner,statemachine,start_pos)  
    self.owner = owner  
    self.StateMachine = statemachine  
    self.start_pos = start_pos  
    return self  
end   
   
--追击   
function trace:execute()  
      
    if self.StateMachine.target == nil then  
        --判断离出生点的距离,太远了就回出生点  
        local dis2begpos = calDistance(self.start_pos.x,self.start_pos.y,self.StateMachine.cur_pos.x,self.StateMachine.cur_pos.y)  
        if dis2begpos >= 500 then  
            return 0,self.StateMachine.state_goback  
        else 
            --没有目标,巡逻  
            return 0,self.StateMachine.state_partol  
        end  
    else 
        local dis2begpos = calDistance(self.start_pos.x,self.start_pos.y,self.StateMachine.cur_pos.x,self.StateMachine.cur_pos.y)  
        if dis2begpos >= 4000 then  
            return 0,self.StateMachine.state_goback  
        else 
            --取得目标当前点             
            local target_pos ={}  
            target_pos.x,target_pos.y,target_pos.z = getpos(self.StateMachine.target)  
              
            local dis = calDistance(self.StateMachine.cur_pos.x,self.StateMachine.cur_pos.y,target_pos.x,target_pos.y)    
            if dis <= 200 then  
                --print("选择攻击点")  
                --选择攻击点  
                --if cur_pos.x ~= self.cur_pos and cur_pos.y ~= self.cur_pos.y then  
                    local d_x,d_y = gen_pos_circle(target_pos.x,target_pos.y,200)  
          
                    if -1 == mov(self.owner,d_x,d_y,target_pos.z,804,1000) then  
                        return -1,nil  
                    end  
                    --面向目标  
                    turnface(self.owner,self.StateMachine.target)  
                    --切换到攻击态  
                  --end  
                return 0,self.StateMachine.state_attack  
            else 
                --在目标半径2米内随机选择一个点,作为目标点  
                local d_x,d_y = gen_pos_line(self.StateMachine.cur_pos.x,self.StateMachine.cur_pos.y,target_pos.x,target_pos.y,200,100)  
                if dis <= 300 then  
                    --离目标点小于3米直接过去  
                    if -1 == mov(self.owner,d_x,d_y,target_pos.z,804,1000) then  
                        return -1,nil  
                    end  
                else                          
                    local ttx,tty = forword(self.owner,d_x,d_y,300)  
                    if -1 == mov(self.owner,ttx,tty,target_pos.z,804,1000) then  
                        return -1,nil  
                    end  
                end  
            end  
        end  
    end  
    return 0,self.StateMachine.state_trace  
end  
          
function trace:new(o)  
  o = o or {}     
  setmetatable(o, self)  
  self.__index = self  
  return o  
end 
trace = {
owner = 0,
StateMachine = 0,
start_pos = 0
}
 
function trace:init(owner,statemachine,start_pos)
 self.owner = owner
 self.StateMachine = statemachine
 self.start_pos = start_pos
 return self
end
 
--追击
function trace:execute()
 
 if self.StateMachine.target == nil then
  --判断离出生点的距离,太远了就回出生点
  local dis2begpos = calDistance(self.start_pos.x,self.start_pos.y,self.StateMachine.cur_pos.x,self.StateMachine.cur_pos.y)
  if dis2begpos >= 500 then
   return 0,self.StateMachine.state_goback
  else
     --没有目标,巡逻
     return 0,self.StateMachine.state_partol
  end
 else
  local dis2begpos = calDistance(self.start_pos.x,self.start_pos.y,self.StateMachine.cur_pos.x,self.StateMachine.cur_pos.y)
  if dis2begpos >= 4000 then
   return 0,self.StateMachine.state_goback
  else
     --取得目标当前点  
   local target_pos ={}
   target_pos.x,target_pos.y,target_pos.z = getpos(self.StateMachine.target)
  
   local dis = calDistance(self.StateMachine.cur_pos.x,self.StateMachine.cur_pos.y,target_pos.x,target_pos.y)
   if dis <= 200 then
    --print("选择攻击点")
    --选择攻击点
    --if cur_pos.x ~= self.cur_pos and cur_pos.y ~= self.cur_pos.y then
     local d_x,d_y = gen_pos_circle(target_pos.x,target_pos.y,200)
 
     if -1 == mov(self.owner,d_x,d_y,target_pos.z,804,1000) then
      return -1,nil
     end
     --面向目标
       turnface(self.owner,self.StateMachine.target)
     --切换到攻击态
      --end
    return 0,self.StateMachine.state_attack
   else
    --在目标半径2米内随机选择一个点,作为目标点
    local d_x,d_y = gen_pos_line(self.StateMachine.cur_pos.x,self.StateMachine.cur_pos.y,target_pos.x,target_pos.y,200,100)
    if dis <= 300 then
     --离目标点小于3米直接过去
     if -1 == mov(self.owner,d_x,d_y,target_pos.z,804,1000) then
        return -1,nil
       end
    else         
     local ttx,tty = forword(self.owner,d_x,d_y,300)
       if -1 == mov(self.owner,ttx,tty,target_pos.z,804,1000) then
        return -1,nil
       end
    end
   end
  end
 end
 return 0,self.StateMachine.state_trace
end
       
function trace:new(o)
  o = o or {}  
  setmetatable(o, self)
  self.__index = self
  return o
end 


在追击状态下,根据各种条件或者执行追击,或者返回下一个状态.

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值