从零开始重写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)就是在窗口创建好了之后,在主消息循环开始之前,进行我们所需要的对象的初始化工作。
(3)然后开始主消息循环,这里要注意使用PeekMessage,并且Peek后REMOVE掉,否则将不是一个真正实时的游戏循环。在主消息循环里执行我们的游戏逻辑。
(4)最后在主循环退出后释放所有资源。
4. 角色动画的实现
鼠标动画的绘制非常简单,请下载一看源码就知道了,地图的实现方法就是在一个大表面上来取一块放在显存里,也很简单,这里都不再赘述了。
角色动画涉及8个方向和两个状态(站立和跑动)的动画。所以我们要准备好(8*站立动画帧数 + 8*跑动动画帧数)个表面,并且有两个动画索引数组。准备好后,我们可以考虑到实现角色动画无法就是判断在当前游戏帧,我们应该把上面那么多个表面其中的哪一个写到显存里去。因素有3:
(1)Direction - 人物朝向
(2)AnimationIndex - 人物动画当前索引
(3)Status - 人物状态是在站立还是跑动
首先分析人物朝向。人物朝向是受玩家给出的目标移动指令后才做改变的,所以可以考虑在Controller::PlayerMove()执行时来修改这个值。
接着是人物动画索引。这个可以在人物绘制时处理,在每次绘制完成后,将这个索引加1即可。
最后是Status。这个更简单了,必然是在用户做出操作后来修改。
因为以上3因素已经都有归属了,所以在每一帧,我们只要根据3因素画出相应的表面即可 。
5. 总结
至此,本期的功能都实现,但还有以下主要不足:
(1)目前角色的移动是将当前角色坐标和目标坐标做比对,然后在X和Y上都加上RunSpeed,而实际应该是求出向量,在向量上增加RunSpeed。这样角色移动的速度就更符合逻辑了。
(2)因为这个DEMO的图像都是我从游戏里截图出来的(PS了很久啊~~),所以帧数相当低。
6. 源码下载
>>下载地址页<< 资源分是1,有兴趣和诚意的朋友下吧。
7.下一期将完成除了现在地表图像的绘制,还要在地图上绘制可以阻挡住人物的护栏、石头等物体,以及人物可以自动绕过障碍的寻路逻辑。
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.下一期将完成除了现在地表图像的绘制,还要在地图上绘制可以阻挡住人物的护栏、石头等物体,以及人物可以自动绕过障碍的寻路逻辑。
相关文章推荐
- 从零开始重写KOK1(万王之王1) —— (2)优化地图加载
- 从零开始重写KOK1(万王之王1) —— (3)优化玩家移动与精确8方向朝向
- 从零开始重写KOK1(万王之王1) —— (4)遮挡、阻挡与寻路
- 使用OpenLayers3 添加地图鼠标右键菜单
- 鼠标控制人物在地图移动的方法
- 使用鼠标滚轮实现放大缩小地图
- 【高德地图API】从零开始学高德JS API(二)地图控件与插件——测距、圆形编辑器、鼠标工具、地图类型切换、鹰眼鱼骨
- 【高德地图API】从零开始学高德JS API(二)地图控件与插件——测距、圆形编辑器、鼠标工具、地图类型切换、鹰眼鱼骨
- 【VC++游戏开发#十】2D篇 —— 人工智能(一):滚动地图 & 用鼠标控制人物的走动
- [Unity][Animation&Animator]使用blend tree使得人物跑动动画平滑
- 使用Java实现人物跑动和放烟花的动画
- 使用OpenLayers3 添加地图鼠标右键菜单
- 从零开始搭建环境编写操作系统 AT&T GCC (八)使用键盘和滚轮鼠标
- 重写 geturl Openlayers中使用TileCache加载预切割图片作为基础地图图层
- 从零开始,使用Cocos2d HTML5完成一个游戏——第二步:鼠标交互
- 【高德地图API】从零开始学高德JS API(二)地图控件与插件——测距、圆形编辑器、鼠标工具、地图类型切换、鹰眼鱼骨
- Unity使用Animator实现人物头部朝向鼠标
- 【高德地图API】从零开始学高德JS API(二)地图控件与插件——测距、圆形编辑器、鼠标工具、地图类型切换、鹰眼鱼骨
- JavaScript DOM 中setAttribute()的使用 以及点击鼠标返回函数的处理
- 从零开始使用tensorflow(2)——词向量