魔兽争霸唤起回忆,D3D+mfc 编写即时战略游戏教程

佚名 次浏览

摘要:文章浏览阅读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

好了,这部分的主要代码就讲到这里,剩下的代码大家可以自己看源码,有问题可以和我讨论,下一节我会给大家介绍即时战略游戏中一个非常重要的章节:寻路。

本文有一些不足之处,希望大家指出。

随机内容