摘要:文章浏览阅读7.3k次。说起即时战略游戏,我第一时间想起魔兽争霸,这个不知道陪伴我多少个日日夜夜,让我哭让我笑的游戏,让我想起了sky,moon
说起即时战略游戏,我首先想到的就是魔兽争霸,一个陪伴了我无数个日日夜夜的游戏人民日报社,一个让我哭过笑过的游戏即时战略类游戏笑话段子,一个让我想起Sky、Moon、Grubby等人那场场惊心动魄的战斗的游戏,一个让我想起每天坐在电脑前看WCG每一场比赛的游戏,一个让我想起我和哥哥在学校门前的网吧里拼命打HaoFang的游戏,一个让我想起多年前和室友立下的约定,谁输了就买一天的粗粮饼。哦,不说了,全是泪。那么就进入本文正题,使用D3D和MFC编写一个即时战略游戏。
其实这款游戏只是一个很简单的demo,请不要以为它很复杂,但是我也实现了即时战略游戏的基本要求:地图编辑器、角色寻路、动态行走、网络同步等等。在写这款游戏之前我也拼命的在网上查找相关资料,但是发现这方面的资料很不全,很多都只是寥寥数语,所以我觉得我有必要把我所掌握的分享给大家,让和我一样迷茫的朋友也能从中有所收获。
任何一个像样的游戏都离不开地图编辑器,我们可以通过这个编辑器来制作和修改我们想要的地图。本文中的地图编辑器是用 MFC 编写的,地图文件是以 XML 形式存储的,查看起来非常方便。下面是一个地图文件格式:
这个文件很简单,class节点是地图上元素的类型,目前支持house和tree,item是该类型每个元素的所在位置,下面我们来看看如何使用MFC来写这个地图编辑器。
首先我们的程序界面上需要一个可以实时渲染地图场景的窗口,这个窗口是用d3d来渲染的。那么这个窗口该怎么实现呢?大家应该还记得游戏,d3d在初始化的时候会指定一个窗口句柄,因此我定义了一个CStatic类型的变量手柄游戏,名字叫m_DrawWnd,然后在OnInitDialog函数中创建该窗口:
m_DrawWnd.创建(0,0,CRect(10,10,810,610),这个,0);
m_DrawWnd.显示窗口(1);
然后在d3d初始化的时候指定窗口句柄:
m_d3dpp.hDeviceWindow = m_DrawWnd.m_hWnd;
m_d3dpp.后缓冲区宽度 = 800;
m_d3dpp.后缓冲区高度 = 600;
是不是很简单呢?但是等一下,渲染部分呢?MFC 是通过消息驱动来重绘窗体的,很难做到实时渲染。别着急,我仔细看了 MFC 文档,找到了 WM_KICKIDLE 消息。下面是这个消息的官方解释:
那么,在基于对话框的应用程序中,如果对话框没有父窗口,您如何处理空闲处理呢?幸运的是,这很简单。MFC 开发人员提供了一个钩子:WM_KICKIDLE。当对话框队列中没有消息时,RunModalLoop 会重复发送此 MFC 私有消息,就像 CWinThread::Run 调用 OnIdle 一样。RunModalLoop 甚至会传递一个计数器并为您增加它。实际上,WM_KICKIDLE 是对话框的 OnIdle 等价物。(历史记录:早期版本的 MFC 进行了模式/非模式交换,并为属性表提供了 WM_KICKIDLE。显然,它工作得很好,他们决定将所有模式对话框都设为非模式的。)
其实可以简单看成是窗体的空闲消息,如果我们需要做实时渲染的话,这个消息的返回值应该是1,否则返回0就可以了。(在阅读MFC源码后发现,当我们的消息是wm_mousemove或者wm_ncmousemove时健康教育,会重新设置空闲状态,如果没有收到新的消息,idle为true的话,就会发送WM_KICKIDLE消息,以上只是针对模态对话框而言的。)当然实时渲染的代价就是CPU的增加,所以才会有一个idlecount来记录WM_KICKIDLE发送的次数,有兴趣的朋友可以自己尝试一下看看到底是怎么回事。
OK,渲染部分差不多完成了人民日报社,我们来看看如何显示地图。我们的地图大小是1600*1200,但是我们设置的bufferSize只有800*600生活有点甜,所以我们需要移动地图来显示地图上的不同位置。移动的话,按住鼠标左键拖动即可。代码如下:
POINT p;
GetCursorPos(&p);
if(m_bDown)
{
m_iScreenLeft-=(p.x-m_iDownX);
m_iScreenTop-=(p.y-m_iDownY);
m_iDownX=p.x;
m_iDownY=p.y;
if(m_iScreenLeft<0)
{
m_iScreenLeft=0;
}
if(m_iScreenLeft>800)
{
m_iScreenLeft=800;
}
if(m_iScreenTop<0)
{
m_iScreenTop=0;
}
if(m_iScreenTop>600)
{
m_iScreenTop=600;
}
CString strPoint;
strPoint.Format(L"Left:%d,Top:%d",m_iScreenLeft,m_iScreenTop);
SetWindowText(strPoint);
}
是不是很简单呢?那我们如何给这个地图场景添加元素呢?这个也不难医保报销,我创建一个imagelist,把我想要的图片添加到里面,然后绑定listctrl就可以了。
m_TexListControl.MoveWindow(850,350,120,200);
m_TexListControl.SetExtendedStyle(LVS_EX_FULLROWSELECT|LVS_EX_GRIDLINES);
m_TexListControl.SetIconSpacing(CSize(100, 90));
m_ImageList.Create(60,60,ILC_COLORDDB|ILC_COLOR32, 1,1);
WCHAR buf[255]={0};
GetCurrentDirectory(255,buf);
CString strDir=buf;
CBitmap bit;
bit.Attach(LoadPicture(strDir+L"\\img\\tree.jpg"));
m_ImageList.Add(&bit,RGB(0,0,0));
bit.Detach();
bit.Attach(LoadPicture(strDir+L"\\img\\house.jpg"));
m_ImageList.Add(&bit,RGB(0,0,0));
bit.Detach();
m_TexListControl.SetImageList(&m_ImageList,LVSIL_NORMAL);
m_TexListControl.InsertItem(0,L"tree",0);
m_TexListControl.InsertItem(1,L"house",1);
是不是很简单呢?然后我们需要记录当前地图上各个位置的状态,比如地图上width 300,height 200的位置是否有物体。这里就需要一个变量来记录这些,于是我定义:
字节m_MapInfo[600*2][800*2];
这里要注意的是,我们需要修改堆栈大小,默认堆栈大小是1MB,我们这里设置为4MB,否则会报错。将Properties->Linker->System->Stack Reserve Size设置为4096000。
if(m_bNewBuildVaild)
{
for(int i=0;i<100;i++)
{
if(m_BuildingInfo[i]==0)
{
m_BuildingInfo[i]=new sBuildingInfo;
m_BuildingInfo[i]->type=m_strSelectTex;
CRect NewBuildRect(m_iScreenLeft+m_NewBuildRect.left,m_iScreenTop+m_NewBuildRect.top,m_iScreenLeft+m_NewBuildRect.right,m_iScreenTop+m_NewBuildRect.bottom);
m_BuildingInfo[i]->rect=NewBuildRect;
for(int row=0;row
当我们选中一个物体并放置到地图上的时候,就会调用上面的代码。这里我要说的是,当m_MapInfo中存的元素为200时民生,就表示该位置为空,否则就是新创建物体的id。值得注意的是物体不能重叠明星,我在onmousemove中做了相应的判断。
CRect r;
m_DrawWnd.GetWindowRect(&r);
if(!m_strSelectTex.IsEmpty() && PtInRect(&r,p))
{
D3DSURFACE_DESC desc;
m_TexList[m_strSelectTex]->GetLevelDesc(0,&desc);
int width=desc.Width;
int height=desc.Height;
int left=p.x-r.left-width/2;
int top=p.y-r.top-height/2;
left=floor(left*1.0/10)*10;
top=floor(top*1.0/10)*10;
m_NewBuildRect.SetRect(left,top,left+width,top+height);
CRect NewBuildRect(m_iScreenLeft+left,m_iScreenTop+top,m_iScreenLeft+left+width,m_iScreenTop+top+height);
m_bNewBuildVaild=true;
if(NewBuildRect.top<0)
{
m_bNewBuildVaild=false;
}else if(NewBuildRect.left<0)
{
m_bNewBuildVaild=false;
}else if(NewBuildRect.bottom>1199)
{
m_bNewBuildVaild=false;
}else if(NewBuildRect.right>1599)
{
m_bNewBuildVaild=false;
}else
{
for(int row=0;row
好了,这部分的主要代码就讲到这里,剩下的代码大家可以自己看源码,有问题可以和我讨论,下一节我会给大家介绍即时战略游戏中一个非常重要的章节:寻路。
本文有一些不足之处,希望大家指出。