您的位置:首页 > 其它

从零开始重写KOK1(万王之王1) —— (1)让人物可在地图上使用鼠标跑动

2011-01-11 11:49 302 查看
从飞信裸辞已经2个月了,因为对游戏的爱,和做出好玩的游戏这个梦想。《Windows游戏编程大师技巧》(以下简称《大师》)这书已经读完,DEMO也都搞清楚了,为了确实的掌握2D游戏的技术,决定完成一个完整的KOK1咒术师打钱坑的DEMO,也是作为我踏入游戏开发行业的简历附加DEMO。【广告】对游戏有爱的游戏公司(北京)缺人(2D/3D游戏程序员)的话请随时把我拉走。这个系列文章将记载在这个DEMO开发过程中的一点一滴,废话不说了,进入正题。


0. 本期功能

(1)人物可以在地图上通过鼠标右键控制走动

(2)地图可以卷动,并且人物不会超过边界

(3)人物在站立和跑动时会有相应的动画

(4)人物在站立和跑动时会面向适当的方向

(5)鼠标有动画,并且无法移出窗口



截图:





可直接运行版本 >>下载页地址<<



1. 开发工具与语言

在学习《大师》的时候,他主要是用C实现的,并且在性能敏感的位置穿插了一些汇编代码,我的示例最开始也是用C来写,并且贯彻作者多使用全局变量的做法,但当逻辑逐渐复杂了之后,太多的函数与全局变量使得开发效率受到了不小的影响,所以这个DEMO我使用C/C++来写,主要的对象按类来封装,对于性能方面没有过多的注意,以后再重构和优化吧。本文的开发环境是VS2010,DirectX 8.1 SDK,图形部分使用DirectDraw7。



2. 主要对象

目前的DEMO主要有以下几个对象:

GameScreen - 游戏屏幕,就是用户窗口的客户区,这个类提供游戏屏幕相关的属性,如宽高、左上角在世界地图的坐标、以及一个视频控制器。

Map - 游戏地图,这个类提供了地图相关的属性,如每个小图块的宽高,地图数据数组,横向和纵向有多少块小图块。还有地图的初始化(Init)、每一帧绘制(Draw)、释放(Release)的方法。

Player - 玩家,这个类提供了玩家相关的属性,如玩家位图的宽高,在屏幕绘制的左上角坐标,在世界地图的坐标,跑动速度,目标坐标等等,同时也提供了初始化(Init)、每一帧绘制(Draw)、释放(Release)的方法。

Bitmap24 - 24位位图,这个类提供了对24位位图的读取功能,把字节顺序调整为正序,并且改为32BPP的格式。详细的实现方法可见我前面的两篇文章:32BPP窗口模式下24位位图的像素操作(1) 32BPP窗口模式下24位位图的像素操作(2)

VideoManger - 视频管理器,这个类封装了DirectDraw7的相关操作,如创建表面、裁剪表面、从位图中取元素等。

MouseManager - 鼠标管理器,这个类封装了DirectInput的相关操作,以获取最新的鼠标情况,详细的实现方式在前面的这篇文章里:使用dinput鼠标的相对模式达到绝对定位

Controller - 操作管理器,这个类分离了用户的操作与游戏里的逻辑。Controller::ProcessRequest()将在每帧调用,用来处理用户操作,并调用这个管理器封装的相关逻辑操作,如PlayerMove()。



3. 全局流程

(1)流程很简单,首先就是创建窗口,并且调整好窗口的大小和位置,为什么还要调整呢,原因和解决方案在这篇文章里:确定窗口实际用户区的一些问题

(2)就是在窗口创建好了之后,在主消息循环开始之前,进行我们所需要的对象的初始化工作。

gameScreen.Init(SCREEN_WIDTH, SCREEN_HEIGHT, hWnd, titleSize, borderSize, 100, 100);
gameMap.Init(&gameScreen);
player.Init(&gameScreen);
mouse.Init(&gameScreen);


(3)然后开始主消息循环,这里要注意使用PeekMessage,并且Peek后REMOVE掉,否则将不是一个真正实时的游戏循环。在主消息循环里执行我们的游戏逻辑。

gameScreen.Video.FillSurface(gameScreen.Video.Lpddsoffscreen, 0);
mouse.RefreshData(&gameScreen);
controller.ProcessRequest(&gameScreen, &gameMap, &player, &mouse);
gameMap.Draw(&gameScreen);
player.Draw(&gameScreen);
mouse.Draw(&gameScreen);
gameScreen.Video.FlipSurface();
Sleep(100);




(4)最后在主循环退出后释放所有资源。

gameScreen.Release();
gameMap.Release();
player.Release();
mouse.Release();




4. 角色动画的实现

鼠标动画的绘制非常简单,请下载一看源码就知道了,地图的实现方法就是在一个大表面上来取一块放在显存里,也很简单,这里都不再赘述了。



角色动画涉及8个方向和两个状态(站立和跑动)的动画。所以我们要准备好(8*站立动画帧数 + 8*跑动动画帧数)个表面,并且有两个动画索引数组。准备好后,我们可以考虑到实现角色动画无法就是判断在当前游戏帧,我们应该把上面那么多个表面其中的哪一个写到显存里去。因素有3:

(1)Direction - 人物朝向

(2)AnimationIndex - 人物动画当前索引

(3)Status - 人物状态是在站立还是跑动



首先分析人物朝向。人物朝向是受玩家给出的目标移动指令后才做改变的,所以可以考虑在Controller::PlayerMove()执行时来修改这个值。

int Controller::PlayerMove(GameScreen *gs, Map *map, Player *player, MouseManager *mouse)
{
	if (player->MapX == player->TargetX && player->MapY == player->TargetY)
	{
		player->Status = 0;
		return 1;
	}

	// 调整player->Direction
	if (player->MapX == player->TargetX && player->MapY < player->TargetY)
		player->Direction = 0;
	else if (player->MapX > player->TargetX && player->MapY < player->TargetY)
		player->Direction = 1;
	else if (player->MapX > player->TargetX && player->MapY == player->TargetY)
		player->Direction = 2;
	else if (player->MapX > player->TargetX && player->MapY > player->TargetY)
		player->Direction = 3;
	else if (player->MapX == player->TargetX && player->MapY > player->TargetY)
		player->Direction = 4;
	else if (player->MapX < player->TargetX && player->MapY > player->TargetY)
		player->Direction = 5;
	else if (player->MapX < player->TargetX && player->MapY == player->TargetY)
		player->Direction = 6;
	else if (player->MapX < player->TargetX && player->MapY < player->TargetY)
		player->Direction = 7;

	// 调整gs->MapX(Y)
	if (player->MapX < player->TargetX)
	{
		if (player->TargetX - player->MapX <= player->RunSpeed)
			player->MapX = player->TargetX;
		else
			player->MapX += player->RunSpeed;
	}

	if (player->MapX > player->TargetX)
	{
		if (player->MapX - player->TargetX <= player->RunSpeed)
			player->MapX = player->TargetX;
		else
			player->MapX -= player->RunSpeed;
	}

	if (player->MapY < player->TargetY)
	{
		if (player->TargetY - player->MapY <= player->RunSpeed)
			player->MapY = player->TargetY;
		else
			player->MapY += player->RunSpeed;
	}

	if (player->MapY > player->TargetY)
	{
		if (player->MapY - player->TargetY <= player->RunSpeed)
			player->MapY = player->TargetY;
		else
			player->MapY -= player->RunSpeed;
	}

	gs->MapX = player->MapX - gs->ScreenWidth / 2;
	gs->MapY = player->MapY - gs->ScreenHeight / 2;

	return 1;
}




接着是人物动画索引。这个可以在人物绘制时处理,在每次绘制完成后,将这个索引加1即可。



最后是Status。这个更简单了,必然是在用户做出操作后来修改。

int Controller::ProcessRequest(GameScreen *gs, Map *map, Player *player, MouseManager *mouse)
{
	if (mouse->RButton)
	{
		player->Status = 1;
		player->TargetX = gs->MapX + mouse->X;
		player->TargetY = gs->MapY + mouse->Y;
		if (player->TargetX > map->TileWidth * map->MapWidth - gs->ScreenWidth / 2 - 1 - player->RunSpeed)
			player->TargetX = map->TileWidth * map->MapWidth - gs->ScreenWidth / 2 - 1 - player->RunSpeed;
		if (player->TargetX < gs->ScreenWidth / 2 - 1 + player->RunSpeed)
			player->TargetX = gs->ScreenWidth / 2 - 1 + player->RunSpeed;
		if (player->TargetY > map->TileHeight * map->MapHeight - gs->ScreenHeight / 2 - 1 - player->RunSpeed)
			player->TargetY = map->TileHeight * map->MapHeight - gs->ScreenHeight / 2 - 1 - player->RunSpeed;
		if (player->TargetY < gs->ScreenHeight / 2 - 1 + player->RunSpeed)
			player->TargetY  = gs->ScreenHeight / 2 - 1 + player->RunSpeed;
	}

	if (player->Status == 1)
	{
		PlayerMove(gs, map, player, mouse);
	}
	return 1;
}




因为以上3因素已经都有归属了,所以在每一帧,我们只要根据3因素画出相应的表面即可 。

int Player::Draw(GameScreen *gs)
{
	RECT dest_rect = { DrawX, DrawY, DrawX + Width, DrawY + Height };
	LPDIRECTDRAWSURFACE7 drawSurface;
	
	if (Status == 1)
	{
		if (AnimationIndex >= RunAnimationDataCount)
			AnimationIndex = 0;
		drawSurface = RunSurfaces[RunAnimationData[AnimationIndex] + Direction*RunAnimationFrameCount];
	}
	else
	{
		if (AnimationIndex >= StandAnimationDataCount)
			AnimationIndex = 0;
		drawSurface = StandSurfaces[StandAnimationData[AnimationIndex] + Direction*StandAnimationFrameCount];
	}
		
	++AnimationIndex;
	gs->Video.Lpddsoffscreen->Blt(&dest_rect, drawSurface, NULL, DDBLT_WAIT | DDBLT_KEYSRC, NULL);
	return 1;
}




5. 总结

至此,本期的功能都实现,但还有以下主要不足:

(1)目前角色的移动是将当前角色坐标和目标坐标做比对,然后在X和Y上都加上RunSpeed,而实际应该是求出向量,在向量上增加RunSpeed。这样角色移动的速度就更符合逻辑了。

(2)因为这个DEMO的图像都是我从游戏里截图出来的(PS了很久啊~~),所以帧数相当低。



6. 源码下载

>>下载地址页<< 资源分是1,有兴趣和诚意的朋友下吧。



7.下一期将完成除了现在地表图像的绘制,还要在地图上绘制可以阻挡住人物的护栏、石头等物体,以及人物可以自动绕过障碍的寻路逻辑。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐