贸易时代的文档(二)--地图服务器和客户端移动功能【邹志兵】

(一)问题定义
1、概述
《贸易时代》游戏是体现欧洲15、16世纪航海大发现时代航海历史的游戏,游戏主要体现在贸易,探险和海盗这三个主题上。游戏主要体现在贸易这个环节上,突出玩家寻找到安全的黄金贸易线为主题。在航海贸易的过程里,可能会遇到各种各样的危险事情,例如遇上暴风雨,进入无风地带造成补给困难,遇上npc或者玩家扮演的海盗,以及因为市场行情突变造成的货物积压等等问题。这时玩家可以从进行探险的玩家手里购买高等级的物品来装备和充实自己的船只,提高抵御风险的能力。
 
2 、航海模式
内容:整个公海采用一块超大无缝地图给玩家进行移动,让玩家可以由一个城市移动到另外一个城市。
移动方式:乘船在在公海里中移动,通过鼠标左键点击海面,船只从当前点向终点直线移动,遇到阻拦物则停止移动。船只不互相阻挡。在海上可以循环移动,实现环球航行。遇到港口可以进入城市,转入城市移动模式。
 
3 、城市移动模式
内容:玩家可以在不同的城市里移动,可以进入不同的场景(码头,造船厂,交易所,银行,酒馆),进行游戏操作。
移动方式:人徒步在城市里行走,通过鼠标左键点击城市地面,人从当前点向终点直线移动,遇到阻拦物,则停止移动。人互相阻挡。城市是一个封闭的区域,无法直接移动到其他城市或海上。在码头场景中可以进入海上,转入航海模式。
地图服务器:定时进行服务器中所有玩家的移动,并发送同步消息给客户端,
1、在当前地图中移动:定时向客户端发送移动同步信息
2、跨越地图:从当前地图中移动到邻接地图
3、进入建筑物:将玩家状态设为在建筑物中,并通知逻辑服务器
4、退出建筑物:收到退出建筑物请求后,发回确认消息给客户端
5、进入港口(进入城市):将玩家状态设为在城市中,并通知逻辑服务器
6、退出港口(进入海上):收到退出港口请求后,发回确认消息给客户端
7、补给变化:在海上移动定时减少玩家的补给,并通知逻辑服务器和客户端
8、玩家登陆:接收登陆请求,读取玩家游戏进度,返回给客户端
9、玩家退出游戏:接收退出游戏请求,保存玩家游戏进度,并通知逻辑服务器
客户端地图功能:定时进行当前区域中所有玩家的移动,并根据服务器同步消息修正先前的移动,并向客户端游戏引擎功能提供场景数据
1、在当前地图中移动
2、跨越地图: 从当前地图中移动到邻接地图
3、进入建筑物:在城市中移动进入建筑物的大门,等服务器确认后将玩家精灵隐藏。
4、退出建筑物:收到服务器退出建筑物确认后,将玩家精灵显示
5、进入港口(进入城市):在海上移动进入港口,等服务器确认后将玩家状态设为在城市,显示城市地图。
6、退出港口(进入海上):收到服务器退出港口确认后,将玩家状态设为在海上,显示海上地图。
7、补给变化:收到服务器同步信息后,修改补给。
8、获得场景内的其他玩家信息:在从海上进入城市、从城市进入海上、跨越地图时,场景内玩家会发生改变,要进行更新。
9、向游戏引擎提供场景数据:场景的地图编号、镜头的坐标、玩家精灵的坐标。
地图服务器:作为一个大型多人网络游戏的地图服务器软件,在实际应用中应该至少能处理上千个玩家的移动。由于条件有限,测试时地图服务器只能布署在普通PC上,不过考虑到游戏操作的简化,应该也能承受几百个玩家的登陆。
客户端移动功能:客户端的性能需求主要针对游戏引擎功能,对移动功能没特别要求。
地图编辑器,顾名思义,就是用来编辑地图的工具。地图编辑器可以拿来节省绘图的工时成本,甚且具有地形标示的功效,方便进行事件处理。
整个项目的各个模块之间有许多公共的数据结构和方法,将这些公共部分提取出来作为接口,放在一个公用模块中。本论文将使用的接口如下:
消息:服务器和客户端采用相同的消息结构,而且所有消息的接收和发送都经过了登陆服务器的转发。地图服务器接收客户端的移动请求消息,登陆服务器的登陆消息,逻辑服务器的补给同步消息,发送处理结果消息给逻辑服务器和客户端。客户端移动功能接收地图服务器的移动确认消息,逻辑服务器的事件确认消息,发送移动请求消息给服务器。
网络通信功能:提供接收消息和发送消息的方法。由于各模块使用相同的消息结构,所需的基本网络通信功能也是一样的。
Ini文件功能:提供ini 文件的读写方法。 Ini文件是Initialization file的缩写,意即初始化文件,游戏将在ini中读取和保存玩家游戏进度。
 
本文论述的是地图服务器和客户端移动功能的实现。需求分析中可以看出,两者最主要的功能都是实现玩家在地图中的移动,移动算法一样,只不过前者是定时进行服务器中所有玩家的移动,并发送同步消息给客户端,后者是定时进行当前区域中所有玩家的移动,并根据服务器同步消息修正先前的移动。
地图服务器和客户端移动功能采用相同结构,都分为游戏状态机和消息处理两部分。
图4.1
 
游戏状态机:在主程序中定时更新所有玩家的状态,发送同步消息
消息处理:产生一个消息线程,接收外部的消息并进行处理
主程序和消息处理线程会将共享数据,通过临界区互斥。
                                      图4.2
 
 
1、地图设计
在超大地图的游戏中,地图并不保存为单张图片,而是被切割成许多地图块。游戏运行时,只加载部分相同邻接的地图,拼接成一个区域地图。单地图的大小等于或者超过屏幕(镜头)的大小。
游戏中地图的邻接有八个方向:西北、北、东北、东、东南、南、西南、西。每张地图的相邻地图关系都保存在地图的配置文件里。
   
图4.3
 
地图场景又由许多个正方形图元(tile)组成。图元的作用:
1、提高图形素材的的重复利用率,减少图片资源的内存消耗,同时提高地图制作的效率。
地图场景一般由地面(陆地、湖泊、海洋)、建筑物、植物(树、草)等图形素材组成,不同的地图场景之间有很多重复的图形素材。这样就可以用图元来表示图形素材,图元一般分解得较小,本文采用了16*16(以像素为单位)。每个图形素材分解成若干个图元,所有图形素材分解出的图元放在一起,分别给予不同的图元编号。
地图场景结构中只需要保存一个图元编号的数组。游戏运行只需要载入包含所有基本图元的图片资源,图场景的每个图元根据其编号在图片资源中选择对应的区域进行绘制,从而拼接也整块地图。显然这种方式对内存的使用量少,读盘时间少,处理速度快,十分实用。
在地图编辑器中编辑地图时,将图形素材载入素材区,编辑区已经用地面图元布满整个地图,用户只需从素材区截取所需的素材覆盖在地面上,就可拼也各种各样的地图,大大提高地图制作的效率。
2、实现物体的阻挡、固定事件的触发(如进入建筑物)
地图的移动信息中保存了每个图元的阻挡属性,阻挡属性可以分成可通行、障碍物、固定事件触发点。其中固定事件是指进入建筑物一类的事件,例如我们将建筑物门口的图元设为触发点,当人物移动的坐标位于门口图元的范围内,我们就认为人物进入建筑物,将背景换成建筑物的场景。 
 
2、游戏引擎
游戏引擎将由地图邻接关系拼接成的当前区域地图作为场景的背景,通过镜头选择要显示的区域,本文中镜头大小设为800*600。 
客户端地图移动功能将向游戏引擎提供场景数据。当前玩家移动时,重新设置场景的镜头和精灵位置。
                                                                                                                                                                                                                                                                                                          
1、外部接口
1、网络通信类TClientSocket:在公用模块中实现,将在地图服务器和客户端移动功能派生出各自的网络通信类。
TClientSocket提供了方法virtual void OnRead(const char *buf,int len)。 各模块的网络通信类将派生自TClientSocket,并重载OnRead()方法,用于将消息从socket消息缓冲区读取到自定义的消息缓冲区,并调用消息处理功能。
 
2、玩家精灵类CSprite:在游戏引擎功能中实现, 客户端移动功能中玩家信息类CClientMapPlayer将包含CSprite对象的指针。
 
3、场景类CSuperScene:在游戏引擎功能实现,
方法OpenMap( )用于读取地图图片资源,作为场景的背景
方法SetCamera( )用于设置镜头
方法CreateSprite( )用于产生玩家的精灵
方法ReleaseSprite( )用于释放玩家的精灵
 
4、Ini文件操作类CInitSet
  方法Open( ),用于打开配置文件
方法Save( ),用于保存配置文件
方法WriteInt( ) ,用于写一个整数
方法WriteText( ) ,用于写一个字符串
方法ReadInt( ) ,用于读一个整数
方法ReadText( ) ,用于读一个字符串
 
5、通信消息
整个游戏项目各个模块使用相同的消息结构。消息整体由消息头和消息实体组成。
 
//消息整体
struct TMessage
{
      char * p;        //     消息头所在的位置
      long len;         //     整个消息的长度
};
//     消息头
struct TMsgHead
{
      int    userID;                        //     用户身份ID
      unsigned char type;             //     消息的一级编号        用于分大类
      unsigned char ID;         //     消息的二级编号        用于分小类
      int len;                         //     整个消息的长度,包括消息头和消息内容
};
 
整个游戏的现有消息分成12大类,在消息头给予不同type:
#define LoginMsg               1            //     登陆消息
#define GameMsg              2            //     游戏内容相关的消息
#define CharMsg         3            //     聊天消息
#define InitMsg                   4            //     初始化相关的消息
#define TradeMsg               5            //     交易 与交易相关的消息
#define BankMsg               6            //     银行 与银行相关的消息
#define BoatyardMsg 7            //     造船厂相关消息
#define MapMoveMsg       8            //     地图移动相关的消息
#define MapEventMsg        9            //     地图事件相关的消息
#define TaproomMsg 10           //     酒馆相关消息
#define ShipsideMsg           11          //     码头相关的消息
#define ServerSyn              12          //     服务器之间的同步消息
其中地图服务器和客户端地图移动功能相关的消息包括MapMoveMsg和MapEventMsg两大类。
每一大类又包括许多小类, 在消息头给予不同ID。每一小类消息将对应自己的消息实体。消息实体的结构由消息决定,例如以下是移动消息的消息实体:
struct msgMove
{
      int playerID; //玩家编号
      int mapID;  //玩家所处地图编号
      iPoint global;// 玩家坐标
};
 
2、实现的基本类
1、地图类CMapInfo
变量type表示地图类型,为0时表示海上地图,为1时表示城市地图
变量global表示左上角在全局地图中的坐标(x和y)
数组tile[][]表示地图图元的阻挡关系
数组adjMap[]表示所有邻接地图的编号
方法Init( )用于对地图进行初始化
方法InMap( )用于判断玩家的移动点是否处于地图中
方法GetMoveResult( )用于在尝试移动时判断移动结果,如玩家移动所至图元所对应的阻挡关系为通行(PASS)则移动,为阻挡(BLOCK)则阻挡,为触发点则触发事件。
方法Add( )用于当玩家进入地图时,在地图的玩家编号列表加入其编号
方法Delete( )用于当玩家离开地图时,在地图的玩家编号列表删除其编号
 
2、玩家信息类CMapPlayer
由于地图服务器和客户端的地图移动功能不同,需要从基本玩家信息类CMapPlayer派生出CSeverMapPlayer和CClientMapPlayer。
在CMapPlayer中,
变量type表示玩家类型,为0时表示船,为1时表示人
变量state表示玩家运动状态,为0时表示静止,为1时移动中,为2时表示处于建筑物中
结构体变量global表示玩家(精灵左上角)在全局地图中的坐标(x和y)
结构体变量target表示玩家移动目标在全局地图中的坐标(x和y)
结构体变量moveOffset表示玩家在x和y方向上的移动速度,单位为像素每状态机周期
数组hitPoint[]表示玩家的踫撞点,包括左上,右上、左下、右下和中心点
方法SetSailMode( )用于计算玩家的移动参数,包括进行初始化
方法ArriveTarget( )用于判断玩家是否到达终点。
方法GetMoveResult( )用于在尝试移动时判断移动结果,如玩家移动所至图元所对应的阻挡关系为通行(PASS)则移动,为阻挡(BLOCK)则阻挡,为触发点则触发事件。
方法TryMove( )用于玩家尝试进行移动,以便判断移动结果
方法EndMove( )用于玩家结束移动
 
地图服务器中CSeverMapPlayer需要从ini文件中读取和保存玩家游戏进度,加入了以下方法:
方法Init( ),用于从Ini文件中读取游戏进度
方法Save( ),用于向Ini文件中保存游戏进度
客户端CClientMapPlayer不需要读取玩家游戏进度,玩家在登陆时从服务器获得游戏进度,更不保存玩家游戏进度。但是客户端移动功能要向游戏引擎提供玩家场景数据,需保存玩家精灵对象的指针,为此加入了CSprite *pSprite。
3、地图服务器实现的功能类
1、移动管理类CMapManager
地图服务器CMapManager由CMapInfo和CServerMapPlayer组合而成,其中
数组player[]表示所有玩家的移动信息
数组mapList[]表示所有地图信息
方法InitMapServer( )用于初始化地图服务器
方法RunVM( )用于运行状态机。
方法DoMsg( )用于处理一条消息
 
2、地图服务器通信类MapSocket
地图服务器网络通信类将并重载基类的OnRead()方法,进行自己消息处理。
4、客户端移动功能实现的功能类
1、移动管理类CMapManager
客户端CMapManager由CMapInfo和CClientMapPlayer组合而成,其中
typedef list<CClientMapPlayer CClientPlayerList;
变量CClientPlayerList clientPlayer表示场景内玩家的移动信息
typedef list<CMapInfo > CClientMapList;
变量CClientMapList clientMap表示场景内地图信息
方法Init ( )用于初始化地图移动功能
方法Close( )用于退出地图移动功能
方法RunVM( )用于运行状态机。
方法DoMsg( )用于处理一条消息
方法DrawScene( )用于绘制场景
2、客户端通信类MySocket
客户端网络通信类将并重载基类的OnRead()方法,进行自己消息处理。
5、对象协作
1、地图服务器的协作图
2、客户端移动功能的协作图
 
 
 
 
 
 
1、处理流程
玩家登陆服务器
玩家退出服务器
玩家移动同步
玩家停止移动
玩家进入港口,出现在城市中
玩家退出港口,出现在海上
 
 
 
 
玩家进入建筑物
玩家退出建筑物
 
 
 
 
 
 
 
 
 
 
 
2、算法设计
1、玩家在地图中移动算法
玩家的移动是一个连续的过程,移动开始时计算出玩家在一个周期(0.5秒)内的移动偏移,然后在状态机每个周期移动一段,直接到达终点。以下流程图只描述了一个周期内的移动。
图4.4
 
2、玩家之间互相阻挡的算法
为了较真实的模拟人物的移动,将玩家也视为阻挡,当移动中遇到其他玩家认为被阻挡。判断是否阻挡时,就是对玩家精灵侦测碰撞。常见的是以区域侦测碰撞,发现两个精灵区域重叠时就认为碰撞。
在游戏中为了简化计算,在游戏中在以下情况下会出现玩家重叠的现象。例如当多个玩家从建筑物中出来,均被显示在门口,出现重叠;当多个玩家同时移向同一点,也可能出现重叠。如果按照常见的区域侦测方法,在以上情况中玩家都无法再移动了。
另外,侦测碰撞在状态机每周期进行一次,当玩家移动较快时,可能已经越过了踫撞点,使侦测碰撞失效。
为此加入以下条件,对其进行改造。
首先,由于侦测碰撞不要求很精确,将玩家重叠定义为玩家A的精灵中心坐标处于玩家B的精灵区域中。如果玩家的区域只是重叠一点,仍然认为不重叠。
其次,侦测碰撞的区域扩大为玩家移动所扫过的区域,而不只是玩家的精灵区域。当玩家B的中心坐标处于玩家A移动所扫过的区域,那么A将被B阻挡。玩家A要被玩家B阻挡的算法如下:  
1)获得玩家A的移动区域和玩家B的中心坐标
2)A和B不重叠,跳转3,否则跳转4
3)如果B的中心坐标不处于A的移动区域中,返回不阻拦结果,否则返回阻挡结果
4)如果移动使将A远离B,返回不阻拦结果,否则返回阻拦结果
 
 
由于玩家的互相阻挡主要出现在同一张地图中,所以只针对某一地图中的玩家才可能互相阻挡。在某一地图中玩家互相阻挡的算法实现了静止玩家对运动玩家的阻挡,运动玩家之间互相阻挡的则没有完全实现,具体如下:
 
     
图4.6
 
3、状态机算法
地图服务器和客户端的状态机定时运行,更新玩家状态,并根据计算出的新状态触发各类事件,每隔0.5s就运行一次。
当状态机开始运行时,消息处理线程可能还未退出临界区。为使服务器玩家状态得到及时更新, 状态机应该尽快抢占临界区。于是加入一个信息量signal,初始值为false。状态机开始运行时将signal设为true。消息线程在处理完一条消息后发现signal变成true,就退出临界区,等待。这样状态机就可以抢占临界区。状态机在收到定时器的触发消息后,处理算法如下:
1)将signal设为true
2)进入临界区
3)在每一张地图中将会被其他人阻挡的玩家状态预先设为静止
4)验证所有玩家的移动
5)退出临界区
6)将signal设为false
 
4、消息处理算法
在消息线程中网络通信功能接收网络通信消息后,将消息一次性加入自定义的消息缓冲区。然后一条一条读出,由移动管理对象进行消息处理,处理结果仍由网络通信功能返回。如果发现状态机要抢占,就处理完当前消息然后退出临界区。如果还有消息缓冲区还有消息未处理,就等待状态机退出临界区。直到所有消息处理完,才清空消息缓冲区,进入阻塞状态。
图4.8
五   编码
VC中的Win32应用程序能够方便灵活地开发网络应用程序,相比于MFC程序结构清晰,执行效率高,而且编程更加灵活,更加适合进行网络游戏服务器和客户端的开发。
编码时遵循了以下原则,摘自林锐的《高质量C++/C编程指南》 [10]
1、为了防止头文件被重复引用,应当用ifndef/define/endif结构产生预处理块。程序的版式
2、在每个类声明之后、每个函数定义结束之后都要加空行。
3、一行代码只做一件事情,如只定义一个变量,或只写一条语句。这样的代码容易阅读,并且方便于写注释。
4、尽可能在定义变量的同时初始化该变量(就近原则)
5、程序的分界符‘{’和‘}’应独占一行并且位于同一列,同时与引用它们的语句左对齐。
6、命名规则
标识符应当直观且可以拼读,可望文知意,不必进行“解码”。
标识符最好采用英文单词或其组合,便于记忆和阅读。切忌使用汉语拼音来命名。程序中的英文单词一般不会太复杂,用词应当准确。
标识符的长度应当符合“min-length && max-information”原则。
命名规则尽量与所采用的操作系统或开发工具的风格保持一致。Windows应用程序的标识符通常采用“大小写”混排的方式,如AddChild。
程序中不要出现仅靠大小写区分的相似的标识符。
变量的名字应当使用“名词”或者“形容词+名词”。
全局函数的名字应当使用“动词”或者“动词+名词”(动宾词组)。类的成员函数应当只使用“动词”,被省略掉的名词就是对象本身。
用正确的反义词组命名具有互斥意义的变量或相反动作的函数等。
类名和函数名用大写字母开头的单词组合而成。
变量和参数用小写字母开头的单词组合而成。
常量全用大写的字母,用下划线分割单词。
1、常数定义为常量
2、所有动态内存退出时手动释放
3、经常性地对代码进行重构,消除重复代码段,整理类的方法
常用的版本控制工具有CVS,SourceSafe等,但这些都是适用于局域网内的小组开发。由于整个网络游戏是由三个人共同开发,每个人处于不同的地方,没办法组成一个局域网。最后,在网上找到一个的版本控制软件Subversion,能跨互联网进行版本控制。在一台机装版本控制服务器,其他机上装版本控制客户端,可以方便地从服务器更新最新版本,或是向服务器提交已修改版本。
3月1日-3月15日实现了基本类CMapInfo,CMapPlayer
3月16日-3月31日从CMapPlayer派生出CServerMapPlayer,由CServerMapPlayer和CMapInfo组合成地图服务器的CMapManager,从TClientSocket派生出CMapSocket,完成了地图服务器移动功能。
4月11日-4月20日从CMapPlayer派生出CClientMapPlayer,由CClientMapPlayer和CMapInfo组合成客户端移动功能的CMapManager,基本完成了客户端地图移动功能,等测试完地图服务器和客户端移动功能之间的消息发送后,再添加向游戏引擎功能提供场景数据。
4月25日-4月30日添加了客户端的移动功能向游戏引擎提供场景数据
 
在各模块完成时进行单元测试后,之后以增量方式进行测试。
第一阶段:4月1日-4月10日
测试内容:地图服务器接收和处理客户端模拟消息,发送同步消息给客户端,
包括如下的全部处理:
1、在当前地图中移动:接收移动开始消息,发送移动同步和移动结束消息
2、跨越地图:发送跨越地图消息
3、进入建筑物:将玩家状态设为在建筑物中,并通知逻辑服务器
4、退出建筑物:收到退出建筑物请求后,发回确认消息给客户端
5、进入港口(进入城市):将玩家状态设为在城市中,并通知逻辑服务器
6、退出港口(进入海上):收到退出港口请求后,发回确认消息给客户端
7、补给变化:在海上移动定时减少玩家的补给,并通知逻辑服务器和客户端
8、玩家登陆:接收登陆请求,读取玩家游戏进度,返回给客户端
9、玩家退出游戏:接收退出游戏请求,保存玩家游戏进度,并通知逻辑服务器
测试方法:用模拟客户端登陆地图服务器。服务器和客户端收到消息时将显示在一个Edit中。模拟客户端只有发送模拟消息和接收消息的功能,不具备消息处理的功能。地图服务器对消息的处理通过客户端收到的返回结果消息来判断。地图服务器的许多操作与逻辑服务器协同,在没有逻辑服务器的情况下,就将原本发给逻辑服务器的消息发给客户端。
测试条件:服务器和客户端在WindowsXP下运行于同一台普通PC
一些测试用例:
用例1:4月2日多个玩家先后登陆地图服务器。
预期结果:所有玩家正常登陆
实际结果:第一个玩家正常登陆,而且在两端的Edit显示的消息正常。但是第二个玩家无法登陆,Edit中显示消息也不正常了。以后的玩家也无法登陆。
调试:地图服务器中每次触发消息处理线程,会把从socket接收的几条消息放入自定义的消息缓冲区,一条一条的处理,然后清空。但是缓冲区清空方法有错。消息缓冲区清空由黄良毅实现,他进行了修改。
用例2:4月5日地图某一方向上没有邻接地图,将玩家的移动目标设为不处于邻接地图的坐标,让玩家开始移动。
预期结果:玩家移动至地图的边缘时停止。
实际结果:玩家移动至地图的边缘时停止,因为在编辑地图时,如果地图某一方向上没有邻接地图,就将该方向上的边界设为阻挡,当移动到边界就会被阻挡停止。
用例3:4月6日将玩家的移动目标设为地图的左上角,水平移动。
预期结果:玩家水平向前移动。
实际结果:玩家无法移动。
调试:判断玩家是否处于地图中的方法不小心少了一个等号,将左边界和上边界认为不在地图中。为此将等号加上。
 
第二阶段:4月21日-4月24日
测试内容:地图服务器和加入移动功能的模拟客户端协同工作,由于地图服务器的功能已经在上一阶段测试完成,本阶段其实主要是测试客户端的移动功能。
客户端地图功能:定时进行当前区域中所有玩家的移动,并根据服务器同步消息修正先前的移动,并向客户端游戏引擎功能提供场景数据
1、在当前地图中移动
2、跨越地图: 从当前地图中移动到邻接地图
3、进入建筑物:在城市中移动进入建筑物的大门,等服务器确认后将玩家精灵隐藏。
4、退出建筑物:收到服务器退出建筑物确认后,将玩家精灵显示
5、进入港口(进入城市):在海上移动进入港口,等服务器确认后将玩家状态设为在城市,显示城市地图。
6、退出港口(进入海上):收到服务器退出港口确认后,将玩家状态设为在海上,显示海上地图。
7、补给变化:收到服务器同步信息后,修改补给。
测试方法:地图服务器和加入移动功能的模拟客户端协同工作。
测试条件:服务器和客户端在WindowsXP下运行于同一台普通PC
一些测试用例:
用例1:4月21日打开地图服务器后,再运行客户端。
预期结果:客户端登陆成功。
实际结果:客户端立刻错误退出。
调试:地图服务器和客户端的状态机都设置了编号为1的定时器,当客户端运行时就会冲突。于是将客户端的定时器编号改成2。
用例2:4月22日将玩家移动目标设为海上的一个港口,开始移动。
预期结果:玩家进入港口。
实际结果:玩家进入港口。如果服务器的进入港口确认消息先到,客户端则进入港口。如果客户端移动至港口时,进入港口确认消息还没到,先停止,等待确认消息。
 
第三阶段:5月1日-5月10日
测试内容:登陆服务器、逻辑服务器、地图服务器、客户端协同工作,一方面测试新加入的客户端地图移动功能向游戏引擎功能提供场景数据,另一方面也是开始运行游戏的完整功能。
测试方法:先准备了测试的地图和玩家信息,然后登陆服务器、逻辑服务器、地图服务器、客户端协同工作
测试条件:服务器和客户端在WindowsXP下运行于同一台普通PC
一些测试用例:
用例1:5月3日玩家移动到港口,从海上进入城市。
预期结果:玩家进入城市。
实际结果:客户端错误退出。
调试:玩家收到进入港口确认消息后切换地图,忘记了重新镜头和玩家精灵坐标,致使原有玩家精灵错误。为此加入重设镜头和玩家精灵坐标。
用例2:5月5日玩家在海上移动。
预期结果:移动流畅。
实际结果:移动不很流畅,在移动中途和移动结束时有时会跳跃。
调试:原来服务器每0.5秒计算一次移动偏移,客户端为0.1秒计算一次。由于移动偏移是像素的整数倍,客户端的小数被约去更多,服务器的0.5秒移动一般会大于客户端的5次0.1秒移动。于是在服务器不去直接0.5秒移动停偏移,而是用0.1秒偏移的5倍表示,使服务器和客户端的移动尽量一致。但是由于客户端在移动中途会进行移动同步,当服务器同步消息可能滞后,会使移动不流畅,目前还没想出更合理的同步方案。因此,本次调试对问题的解决并不彻底,调试后移动流畅性有所改善,但有时还会因移动同步而跳跃。
用例3:5月7日登陆两个客户端,玩家1和玩家2都处于同一张地图中,一起移动到地图边界附近。当玩家1跨越地图后,就看不到玩家2。同时玩家2也看不到玩家1。
预期结果:虽然分处不同地图,但是距离不远,互相还看得到。
实际结果:当玩家1跨越地图后,就看不到玩家2。
调试:获得精灵坐标的方法有误。重设玩家精灵坐标有错误。为此加入更改获得玩家精灵坐标的方法。
 
第四阶段:5月11日-5月15日
测试内容:从单机测试转向互联网测试,测试整个游戏
测试方法:黄良毅在外网运行服务器,邹志兵、兰红在内网远程运行客户端
测试条件:服务器在外网普通PC上运行,客户端在内网普通PC上运行,系统环境均为WindowsXP
一些测试用例:
用例1:5月22日邹志兵和兰红在各自的电脑上运行客户端远程登陆服务器。
预期结果:服务器和客户端运行正常
实际结果:服务器和客户端运行正常
 
第五阶段:5月16日-5月30日
测试内容:游戏服务器和客户端的稳定性,游戏操作的趣味性
测试方法:发布到网上进行公测,定时在外网普通PC上运行服务器。
测试条件:黄良毅在外网运行服务器,玩家远程运行客户端
一些测试用例:
用例1:5月22日22:00-5月23日2:00,历时四个小时。
预期结果:服务器和客户端运行正常
实际结果:最高同时在线玩家10人,服务器运行正常,客户端有时会错误退出。
调试:由于在线人数不多,出错的机会较少,玩家在出错时上报的错误描述不够详细,看不出明显原因。最后只是在客户端移动功能中加入一些防错判断。
 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值