发信人: nhyjq(人类·晤知想点) 
整理人: nhyjq(2003-06-10 11:09:18), 站内信件
 | 
 
 
作者:吴振华(kylinx)
 E-mail:[email protected]
 Homepage:http://kylinx.yeah.net
 OICQ:30784290
 Date:2003/6/4
 
 
 
 从开始写游戏到现在,总感觉自己在渲染场景这方面的思想做得不好。虽然写了3个DEMO,但是在渲染场景这方面总是显得很笨重。前几天无意间翻开一本《微机原理》,有几个字跳入了我的眼中——流水线!大概是灵光一闪吧,脑子里就有了使用流水线的方法渲染场景这个念头。
 
 好了,进入正题,大家先想一想那些即时战略游戏吧,比如说星际(我最爱的即时战略游戏)
 
 在游戏画面刷新的时候,尤其是在战斗的时候,凌乱的爆炸,血的效果,魔法的效果……总之有很多很多的动画序列需要处理(比如爆炸这个动画),而且这些动画又是不定时产生的。
 
 在更新画面的时候,该怎样处理呢?如果用一种常见的方法就是这样:(伪代码如下)
 
 UpdateScreen()
 {
 
        DrawMapGround();    //画地图最底层
 
        DrawMapLayer1();                 //画没有遮挡住精灵的层
 
        DrawAllSprite();//画所有精灵,精灵动画可以在里面实现
 
        DrawMapLayer2();//画遮挡住精灵的层
 
 If(有爆炸的动画)
 
    ShowBomb();
 
 ….
 
 }
 
 可以看出这样做是根本不能实现的
 
 原因如下:
 
 1:地图和精灵之间是有遮挡关系的,且精灵和精灵之间也是有遮挡关系的。这样做的话虽然可以实现正确的遮挡关系(当然需要做更多的判断,上面只是很简单的伪代码),但是大量的判断绝对让人受不了!再说,如果爆炸是在精灵层的下面发生的呢?(比如,一只航母下面的一量坦克爆炸了),这时候怎么处理?如果直接爆的话肯定是看见航母在爆炸了!
 
 那么在爆炸的上面再画一次航母?嗬嗬,对于高效的代码来说绝对不允许这样!
 
 2:爆炸的动画是不定个数的,并且每一个爆炸的每一帧都也许不会同时进行(比如,一个炸弹的动画有10帧。第一个炸弹爆炸到了第5帧的时候,第二个炸弹开始爆炸)再加上1所说的层的问题。所以上面的方法行不通
 
 3:(以上两点就足够说明了,这点就不用说了吧,嘿嘿……)
 
 下面谈谈该怎么解决这个问题(以下是解决这个问题的个人想法,并不敢确定星际中是否是这样做的!!!若觉得不值得一看请止目)
 
 考虑这样一个任务队列:
 
 Queue中假设有任务T1,T2,T3,T4;
 
 其中T1的任务是画地图的最底层
 
 T2是显示神族的一个龙骑士
 
 T3是显示龙骑士发出的炮弹动画
 
 T4是一个闪电兵对龙骑士释放闪电的动画
 
 我们希望的结果是这样:
 
 T1永远执行下去,直到游戏退出(废话,要不然就没有地图了)
 
 T2在龙骑士被K掉以后就从队列中删除
 
 T3在炮弹到达目标后,爆炸,然后从队列中删除
 
 T4闪电动画播放完成后,从队列中删除
 
 好了,大家看到这样处理就会很有很好的效果
 
 下面问题又来了,怎样在这个队列中把任务插入到合适的位置呢?又怎样任务完成后,自动从队列中删除呢?
 
 我先给大家分析一下
 
 要把任务插入到合适的位置,这就要判断该任务的“优先级别”,在游戏中可以用所在的“层”来表示,最优先执行的,也就是层最低的(因为正确的贴图总是从低层贴到高层嘛),通过优先级别很容易找到任务该插入的位置
 
 要把任务从队列中删除。可以这样做:在每一个任务中都设置一个状态bActive,如果bActive为真,表示这个任务还没有完成。否则,就删除这个任务节点!
 
 好了,思想就这么简单!下面看具体实现
 
 我是这样做的:(注意红色部分)
 
 写一个任务的类名曰KGDTask(在我现在正在写的游戏库中叫KGDLib (KGD---KylinxGameDevelop),由于是讲解,所以一些其他不重要的成员我没写上来)
 
 class KGDTaskQueue;                            //这个就是任务队列
 
 class CTask
 
 {
 
 friend class CtaskQueue;                 //为了方便,声明CtaskQueue为Ctask的友元,访问私有成员
 
 DWORD m_dwID;                            //任务的ID,通过这个ID从队列中寻找该任务(如果需要自己寻找的话)
 
 DWORD m_nPriority;                 //任务的优先级
 
 BOOL m_bTaskActive;                     //任务是否完成,如果完成,m_bTaskActive=false;,否则等于true
 
 Void*pExecuteParam;      //执行任务函数的参数
 
 Void*pCheckTaskEndParam;  //检查任务是否完成的函数的参数
 
 Void FreeResource()                     //释放所有资源
 
 {
 
 Release();                            //释放从本类派生下的类的资源
 
 Delete this;                            //为了在CtaskQueue中,这里代劳delete删除任务的指针
 }
 
 Public:
 
        virtual DWORD ExecuteTask(void*param)=0;                                             
 
 //任务执行的函数!每个任务从此类派生的都必须重载写此函数
 
        virtual BOOL  CheckTaskActive(void*param)=0;                                       
 
 //检查任务是否完成的函数,格式必须为:如果完成则返回false否则返回true
 
       virtual DWORD Release()=0;                                                    //释放派生类的资源
 
 KGDTask()                                   //构造
 
 {
 
        pExecuteParam=NULL;
 
        pCheckTaskEndParam=NULL;
 
         m_bTaskActive=false;
 
 }
 
 void SetExecuteParam(void*param)
 
 {
 
 pExecuteParam =param;
 
 }
 
 void SetCheckTaskEndParam(void*param) 
 
 {
 
 pCheckTaskEndParam=param;
 
 }
 
  
 
 void       CreateTask(DWORD nID,DWORD nPriority, //创建任务void*pExeParam=NULL,void*pChkTskEndParam=NULL)
 
        {
 
               m_nTaskID=nID;
 
               m_nPriority=nPriority;
 
               m_bTaskActive=true;
 
               pExecuteParam=pExeParam;
 
               pCheckTaskEndParam=pChkTskEndParam;
 
        }
 
        void SetPriority(DWORD nPriority)                      //设置任务优先级
 
        {
 
               m_nPriority=nPriority;
 
        }
 
        void ForceEndTask(DWORD code=0)                            //强行退出任务
 
        {
 
               m_bTaskActive=false;
 
        }
 
 DWORD GetTaskID()                                                        //返回任务ID
 
        {
 
               return m_nTaskID;
 
        }
 
 };
 
 class KGDTaskQueue
 
 {
 
        struct KGDTASKNODE                                               //任务节点
 
        {
 
               KGDTask*pTask;                                               //任务
 
               KGDTASKNODE*next;                                      //下一个任务
 
               KGDTASKNODE()                                     //节点的构造函数
 
               {
 
                      pTask=NULL;
 
                      next=NULL;
 
               }
 
               KGDTASKNODE(KGDTask*ptsk)                 //节点的构造函数
 
               {
 
                      pTask=ptsk;
 
                      next=NULL;
 
               }
 
        };
 
        KGDTASKNODE*m_pTaskHead;                         //任务队列头
 
        DWORD m_nQueueLength;                                       //当前队列中任务个数
 
        int RemoveTask(KGDTASKNODE * pNode);              //从队列中删除节点
 
 public:
 
        enum
 
        {
 
               ERR_OK=0,
 
               ERR_NOMEMORY,
 
               ERR_NOEXIST,
 
               ERR_IDEXIST
 
        };
 
        KGDTaskQueue();
 
        virtual ~KGDTaskQueue();
 
        int AddTask(KGDTask * cTask);                              //添加任务
 
        int ClearTaskQueue();                                              //清空任务队列
 
        int ExecuteTask();                                                        //“流水线”式的执行任务
 
        BOOL FindTaskWithID(DWORD dwID,KGDTask**ppTask);                                 
 
 //通过ID查找任务,如果成功,ppTask指向待查找的任务
 
        DWORD GetQueueLength()                                        //返回任务个数
 
        {
 
               return m_nQueueLength;
 
        }
 
 };
 
  
 
 KGDTaskQueue::KGDTaskQueue()                                                                    //构造
 
 {
 
        m_pTaskHead=new KGDTASKNODE();
 
        m_nQueueLength=0;
 
 }
 
 KGDTaskQueue::~KGDTaskQueue()                                                                //析构
 
 {
 
        ClearTaskQueue();
 
        delete m_pTaskHead;
 
 }
 
 BOOL KGDTaskQueue::FindTaskWithID(DWORD dwID,KGDTask**ppTask)             //查找任务
 
 {
 
        if(!ppTask)
 
               return false;
 
        KGDTASKNODE*pNode=m_pTaskHead->next;
 
        while(pNode)                                                                                        //顺着队列查找
 
        {
 
               if(pNode->pTask->m_nTaskID==dwID)
 
               {
 
                      *ppTask=pNode->pTask;
 
                      return true;
 
               }
 
               pNode=pNode->next;
 
        }
 
        return false;
 
 }
 
 int KGDTaskQueue::ClearTaskQueue()                                                             //清空队列
 
 {
 
        KGDTASKNODE*pTemp=NULL;
 
        KGDTASKNODE*pNode=m_pTaskHead->next;
 
        while(pNode)
 
        {
 
               pTemp=pNode;
 
               pNode=pNode->next;
 
               pTemp->pTask->FreeResource();
 
               delete pTemp;
 
        }
 
        m_pTaskHead->next=NULL;
 
        m_nQueueLength=0;
 
        return ERR_OK;
 
 }
 
 int KGDTaskQueue::ExecuteTask()                                                 //执行队列中的任务
 
 {
 
        KGDTASKNODE*pHeadNode=m_pTaskHead->next;
 
        KGDTASKNODE*pTemp=NULL;
 
        while(pHeadNode)
 
        {
 
               pTemp=pHeadNode->next;
 
               if(pHeadNode->pTask->m_bTaskActive)                //如果此任务仍然激活
 
               {
 
                      pHeadNode->pTask->ExecuteTask(pHeadNode->pTask->pExecuteParam);//执行此任务
 
                      pHeadNode->pTask->m_bTaskActive=pHeadNode->pTask->CheckTaskActive(pHeadNode->pTask->pCheckTaskEndParam);
 
                                                                //对此任务作结束与否的判断
 
               }
 
               else                       //如果此任务不再激活(已经完成或者被强行退出)
 
               {
 
                      pHeadNode->pTask->FreeResource();       //释放此任务的资源
 
                      RemoveTask(pHeadNode);                     //从队列中删除此任务节点
 
               }
 
               pHeadNode=pTemp;
 
        }
 
        return ERR_OK;
 
 }
 
 int KGDTaskQueue::RemoveTask(KGDTASKNODE*pNode)                                     //删除节点
 
 {
 
        KGDTASKNODE*pHeadNode=m_pTaskHead->next;
 
        KGDTASKNODE*pTempPrev=pHeadNode;
 
        pHeadNode=pHeadNode->next;
 
        if(pTempPrev==pNode)                                                                         //如果待删除的节点是第1个节点(在我写的这个结构中,对列头节点不存放东西)
 
        {
 
               m_pTaskHead->next=pTempPrev->next;
 
               delete pTempPrev;
 
               m_nQueueLength--;
 
               return ERR_OK;
 
        }
 
        while(pHeadNode)
 
        {
 
               if(pHeadNode==pNode)
 
               {
 
                      pTempPrev->next=pHeadNode->next;
 
                      delete pHeadNode;
 
                      m_nQueueLength--;
 
                      return ERR_OK;
 
               }
 
               pTempPrev=pHeadNode;
 
               pHeadNode=pHeadNode->next;
 
        }
 
        return ERR_NOEXIST;
 
 }
 
 int KGDTaskQueue::AddTask(KGDTask * cTask)                                             //添加任务
 
 {
 
        KGDTASKNODE*pNode=new KGDTASKNODE(cTask);                            //创建任务节点
 
        if(NULL==pNode)
 
               return ERR_NOMEMORY;
 
        if(m_pTaskHead->next==NULL)                                                                      //如果失空队列
 
        {
 
               m_nQueueLength=1;
 
               m_pTaskHead->next=pNode;
 
               return ERR_OK;
 
        }
 
        KGDTASKNODE*pTemp=m_pTaskHead->next;
 
        KGDTASKNODE*pTempPrev=pTemp;
 
        pTemp=pTemp->next;
 
        if(pTempPrev->pTask->m_nPriority<=pNode->pTask->m_nPriority)  //与第一个节点作优先级的判断
 
        {
 
               m_pTaskHead->next=pNode;                                                               //如果要插入的节点的优先级比第一个节点的优先级还要高,就这样插入
 
               m_nQueueLength++;
 
               pNode->next=pTempPrev;
 
               return ERR_OK;
 
        }
 
        while(pTemp)                                                                                        //在队列中插入
 
        {
 
               if(pTemp->pTask->m_nPriority<=pNode->pTask->m_nPriority)
 
               {
 
                      pNode->next=pTemp;
 
                      pTempPrev->next=pNode;
 
                      m_nQueueLength++;
 
                      return ERR_OK;
 
               }
 
               pTempPrev=pTemp;
 
               pTemp=pTemp->next;
 
        }                                                                           //编历队列完成
 
        pTempPrev->next=pNode;                                          //插入尾部
 
        m_nQueueLength++;
 
        return ERR_OK;
 
 }
 
 好了!每个任务都从KGDTask中派生而来,比如
 
 class MyTask:public KGDTask
 
 {
 
 …//你自己的定义
 
  
 
 public:
 
 BOOL InitMyTask();//自己对这个类做初始化
 
 virtual DWORD ExecuteTask(void*param);
 
 virtual BOOL CheckTaskEnd(void*param);
 
 virtual DWORD Release();
 };
 
 添加到任务中就这样处理(在任何函数中)
 
 //记住,每一个任务的ID应该独立哦,比如用#define 定义在某个头文件中
 
 //KGDTaskQueue*queue=new KGDTaskQueue;//假设queue是全局变量
 
 MyTask*pMyTask=new MyTask();
 
 MyTask->CreateTask(…..);
 
 MyTask->InitMyTask(….);
 
 Queue->AddTask(MyTask);
 
 …..
 
 在游戏更新画面的函数中,使用
 
 Queue->ExecuteTask();    //象流水线一样执行任务
 
 不需要自己释放MyTask,在执行的过程中会自动释放的(任务完成后)
 
 强行结束任务就是
 
 (如果MyTask是局部的,要在另一个函数中结束它,就应该先找到它的ID)
 
 MyTask*pMyTask;
 
 queue->FindTaskWithID(MyTaskID,&pMyTask);
 
 pMyTask->ForceEndTask();
 
 好了!万事OK!
 
 终于写完了!声明一下,在任务队列中,也许我用的入队算法效率不高,这个就要靠大家自己去研究数据结构吧!嗬嗬,毕竟我只是在说一种更新游戏画面的思想,说得不对的地方,还恳请大家不吝赐教[email protected]
 
 
  ---- ∵我是人类(♂)㊣,天蝎座
 ∴我冷静、深沉     
 My QQ is 726556     
 欢迎来广州社区的游戏开发版逛逛,我是斑竹            
 欢迎来北京社区的C语言版逛逛,我是Shadow斑竹             | 
 
 
 |