会员: 密码:  免费注册 | 忘记密码 | 会员登录 网页功能: 加入收藏 设为首页 网站搜索  
游戏开发 > 程序设计 > 设计思想
用流水线的方法在游戏中渲染场景
发表日期:2006-09-03 21:23:11作者: 出处:  

作者:吴振华(kylinx)

E-mail:game-diy@163.com

Homepage:http://kylinx.yeah.net

OICQ:30784290

Date:2003/6/4

!!!转载请保证文档的完整性!!!

从开始写游戏到现在,总感觉自己在渲染场景这方面的思想做得不好。虽然写了3DEMO,但是在渲染场景这方面总是显得很笨重。前几天无意间翻开一本《微机原理》,有几个字跳入了我的眼中——流水线!大概是灵光一闪吧,脑子里就有了使用流水线的方法渲染场景这个念头。

好了,进入正题,大家先想一想那些即时战略游戏吧,比如说星际(我最爱的即时战略游戏)

在游戏画面刷新的时候,尤其是在战斗的时候,凌乱的爆炸,血的效果,魔法的效果……总之有很多很多的动画序列需要处理(比如爆炸这个动画),而且这些动画又是不定时产生的。

在更新画面的时候,该怎样处理呢?如果用一种常见的方法就是这样:(伪代码如下)

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;                 //为了方便,声明CtaskQueueCtask的友元,访问私有成员

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!

终于写完了!声明一下,在任务队列中,也许我用的入队算法效率不高,这个就要靠大家自己去研究数据结构吧!嗬嗬,毕竟我只是在说一种更新游戏画面的思想,说得不对的地方,还恳请大家不吝赐教!game-diy@163.com

返回顶部】 【打印本页】 【关闭窗口

关于我们 / 给我留言 / 版权举报 / 意见建议 / 网站编程QQ群   
Copyright ©2003- 2024 Lihuasoft.net webmaster(at)lihuasoft.net 加载时间 0.00388