D3D游戏编程系列(二):自己动手编写即时战略游戏之地图编辑器的制作

        说起即时战略游戏,我第一时间想起魔兽争霸,这个不知道陪伴我多少个日日夜夜,让我哭让我笑的游戏,让我想起了sky,moon,grubby等人牵动心弦的战斗历程,让我想起了当年日日守在电脑前专注的欣赏着wcg的每一场比赛,想起了当年学校门口的网吧里我跟我哥在浩方上奋力的拼杀着,想起了很多年前和寝室室友打赌谁输谁请一天杂粮饼的承诺。哎,不说了,说起来都是泪啊。那么进入本文的正题吧,用D3D加mfc编写一个即时战略游戏。

       其实这个游戏只是一个很简单的demo,各位千万不要把他想复杂了,但是我也实现了即时战略游戏的基本需求:地图编辑器,人物寻路,动态行走,网络同步等功能。在写这个游戏之前,我也在网上拼命的搜寻着相关资料,却发现这方面的资料十分残缺,许多都是只言片语,所以我觉得我需要把自己掌握的东西与大家一起分享,这样也能让跟我一样迷茫的朋友从中多少有点收获。

       任何一个像样的游戏都离不开一个地图编辑器,我们可以在这个编辑器上面去创建修改我们想要的地图,本文里的地图编辑器就是用mfc编写,而地图文件用xml储存,这样查看的话也很方便。下面给出一个地图文件的样式:

<?xml version="1.0"?>
<root>
	<class type="house" width="50" height="50">
		<item left="780" top="460"/>
	</class>
	<class type="tree" width="50" height="50">
		<item left="940" top="410"/>
	</class>
</root>

       这个文件很简单,class节点就是地图上元素的类型,目前支持house和tree,item则是每一个该类型元素所在的位置。下面我们来看一下如何用mfc来编写这个地图编辑器。

       首先,在我们的程序界面上需要一个可以实时渲染地图场景的窗口,这个窗口用d3d来渲染,那么这个窗口怎么实现,大家应该还记得在d3d初始化的时候会指定一个窗口的句柄,于是我便定义一个名叫m_DrawWnd的CStatic类型的变量,然后在OnInitDialog函数里面创建该窗口即可:

m_DrawWnd.Create(0,0,CRect(10,10,810,610),this,0);

m_DrawWnd.ShowWindow(1);

      然后在d3d初始化的时候指定该窗口的句柄:

m_d3dpp.hDeviceWindow              = m_DrawWnd.m_hWnd;

m_d3dpp.BackBufferWidth            = 800;
m_d3dpp.BackBufferHeight           = 600;

      是不是很简单,不过,等等,渲染那一块怎么办呢,mfc是消息驱动来重绘窗体的,很难做到实时渲染的啊,不要急,我仔细看了下mfc 的文档,发现WM_KICKIDLE这个消息,看下这个消息的官方解释:

So, how do you handle idle processing in a dialog-based app where the dialog has no parent window? Fortunately, it's trivial. The MFC developers provided a hook: WM_KICKIDLE. RunModalLoop sends this MFC-private message repeatedly when there are no messages in your dialog's queue just the way CWinThread::Run calls OnIdle. RunModalLoop even passes a counter and increments it for you. In effect, WM_KICKIDLE is the dialog equivalent of OnIdle. (Historical note: earlier versions of MFC did the modal/modeless swap and WM_KICKIDLE thing for property sheets. Apparently it worked so well they decided to make all modal dialogs modeless.)

      其实就可以简单的看做是窗体的空闲消息,如果我们需要做实时渲染的话,那么这个消息的返回值应该为1,否则返回0就可以了。(我在看了mfc源码后发现,当我们的消息为wm_mousemove或者wm_ncmousemove的时候,会重置idle状态,如果当前没有接受到新的消息时,且idle为true的时候,会去发送 WM_KICKIDLE消息,以上只针对模式对话框)。当然了,实时渲染的代价便是cpu的上升,所以会有一个idlecount来记录WM_KICKIDLE被send 的次数,感兴趣的朋友可以自己去试试,看看其中究竟是怎么一回事。

      好了,渲染部分已经差不多了,下面我们来看下怎么去显示地图。我们的地图大小是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);

         是不是也很简单啊,然后我们需要去记录当前地图上每一个位置的状态,比如说在地图的宽300,高200 的位置上有没有物体啊什么的,这里我们需要一个变量来记录这些,于是我定义:

byte m_MapInfo[600*2][800*2];

       这里一定要注意,我们需要修改堆栈的大小,默认堆栈的大小为1MB,我们这里设置为4MB,否则会报错,属性->链接器->系统->堆栈保留大小  设置为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<NewBuildRect.Height();row++)
						{
							for(int col=0;col<NewBuildRect.Width();col++)
							{
								m_MapInfo[NewBuildRect.top+row][NewBuildRect.left+col]=i;
								
							}
						}
					break;
					}
					
				}
			}
			m_strSelectTex.Empty();
       当我们选中一个物体,放置在地图上的时候,便会调用上面的代码,在这里我说一下,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<NewBuildRect.Height();row++)
				{
					for(int col=0;col<NewBuildRect.Width();col++)
					{
						if(m_MapInfo[NewBuildRect.top+row][NewBuildRect.left+col]!=200)
						{
							m_bNewBuildVaild=false;
							break;
						}
					}
				}
			}
			
		}
        好了,这一部分的主体代码差不多就这些,其他的代码大家可以看下源码,有什么不懂的可以一起交流下,下一节我将给大家带来即时战略游戏中非常重要的一章:寻路。

        本文有不足之处,还望大家多多指正。



  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值