登录处理事件:
0.WinMain主函数调用g_xLoginProc.Load();加载图片等初始化,设置g_bProcState 的状态。
1.CLoginProcess::OnKeyDown-> m_xLogin.OnKeyDown->g_xClientSocket.OnLogin;
WSAAsyncSelect模型ID_SOCKCLIENT_EVENT_MSG,因此,(登录,角色选择,游戏逻辑处理)都回调g_xClientSocket.OnSocketMessage(wParam, lParam)进行处理。
OnSocketMessage函数中:FD_READ事件中:
2.g_bProcState判断当前状态,_GAME_PROC时,把GameGate的发送过来的消息压入PacketQ队列中,再进行处理。否则则调用OnMessageReceive(虚方法,根据g_bProcState状态,调用CloginProcess或者是CcharacterProcess的OnMessageReceive方法)。
3.CloginProcess:调用OnSocketMessageRecieve处理返回情况。如果服务器验证失败(SM_ID_NOTFOUND, SM_PASSWD_FAIL)消息,否则收到SM_PASSOK_SELECTSERVER消息(SelGate服务器列表消息)。m_Progress = PRG_SERVER_SELE;进行下一步选择SelGate服务器操作。
4. m_xSelectSrv.OnButtonDown->CselectSrv. OnButtonUp->
g_xClientSocket.OnSelectServer(CM_SELECTSERVER),得到真正的IP地址。调用OnSocketMessageRecieve处理返回的SM_SELECTSERVER_OK消息。并且断开与loginSrv服务器连接。 g_xClientSocket.DisconnectToServer();设置状态为PRG_TO_SELECT_CHR状态。
角色选择处理:
1. WinMain消息循环处理:g_xLoginProc.RenderScene(dwDelay)-> RenderScroll->
SetNextProc调用
g_xClientSocket.m_pxDefProc = g_xMainWnd.m_pxDefProcess = &g_xChrSelProc;
g_xChrSelProc.Load();
g_bProcState = _CHAR_SEL_PROC;
2.g_xChrSelProc.Load();连接SelGate服务器(从LoginGate服务器得到IP地址)。
g_xClientSocket.OnQueryChar();查询用户角色信息,发送消息:CM_QUERYCHR,设置状态为_CHAR_SEL_PROC, m_Progress = PRG_CHAR_SELE; 在OnSocketMessageRecieve函数中接收到SelGate服务器发送的消息。
3.点击ChrStart按钮:g_xChrSelProc.OnLButtonDown-> CSelectChr::OnButtonUp->
g_xClientSocket.OnSelChar->发送CM_SELCHR消息到SelGate服务器。
4.CClientSocket::OnSocketMessage->CCharacterProcess::OnMessageReceive
(SM_STARTPLAY) 接受到SelGate服务器发送的GameGate服务器IP地址,并断开与SelGate服务器的连接。m_xSelectChr.m_nRenderState = 2;
5. WinMain消息循环处理:g_xLoginProc.RenderScene ->
m_xSelectChr.Render(nLoopTime);-> CSelectChr::Render(INT nLoopTime)-> m_nRenderState = m_nRenderState + 10; 为12-> CCharacterProcess::RenderScene执行
m_Progress = PRG_SEL_TO_GAME;
m_Progress = PRG_PLAY_GAME;
SetNextProc();
6.SetNextProc();执行: g_xGameProc.Load(); g_bProcState = _GAME_PROC;进行游戏状态。
游戏逻辑处理:
1.客户端处理:
CGameProcess::Load() 初始化游戏环境,加载地图等操作,调用ConnectToServer(m_pxDefProc->OnConnectToServer)连接到GameGate游戏网关服务器(DBSrv处理后经SelGate服务器返回的GameGate服务器IP地址)。
CClientSocket->ConnectToServer调用connect时,由GameGate服务器发送GM_OPEN消息到GameSrv服务器。WSAAsyncSelect I/O模型回调函数 g_xClientSocket.OnSocketMessage。然后由m_pxDefProc->OnConnectToServer()调用CGameProcess::OnConnectToServer()函数,调用:g_xClientSocket.SendRunLogin。
2. GameGate服务器ServerWorkerThread处理:
GameGate服务器ServerWorkerThread收到消息,ThreadFuncForMsg处理数据,生成MsgHdr结构,并设置
MsgHdr.nCode = 0xAA55AA55; //数据标志
MsgHdr.wIdent = GM_DATA; //数据类型
3. GameSrv服务器ServerWorkerThread线程处理
GameSrv服务器ServerWorkerThread线程处理调用DoClientCertification设置用户信息,及USERMODE_LOGIN的状态。并且调用LoadPlayer(CUserInfo* pUserInfo)函数-> LoadHumanFromDB-> SendRDBSocket发送DB_LOADHUMANRCD请求,返回该玩家的所有数据信息。
4. 客户端登录验证(GameSrv服务器的线程ProcessLogin处理)
用户的验证是由GameSrv服务器的线程ProcessLogin处理。g_xReadyUserInfoList2列表中搜索,判断用户是否已经登录,一旦登录就调用LoadPlayer(这里两个参数):
a. 设置玩家游戏状态。m_btCurrentMode状态为USERMODE_PLAYGAME
b. 加载物品,个人设置,魔法等。
c. pUserInfo->m_pxPlayerObject->Initialize();初始化用户信息,加载用户坐标,方向,地图。
Initialize执行流程:
1) AddProcess(this, RM_LOGON, 0, 0, 0, 0, NULL);加入登录消息。
2) m_pMap->AddNewObject 地图中单元格(玩家列表)加入该游戏玩家。OS_MOVINGOBJECT玩家状态。
3) AddRefMsg(RM_TURN 向周围玩家群发 RM_TURN消息。以玩家自己为中心,以24*24的区域里,向这个区域所属的块里的所有玩家列表发送消息)广播 AddProcess。
4) RecalcAbilitys 设置玩家的能力属性(攻击力(手,衣服),武器力量等)。
5) 循环处理本游戏玩家的附属物品,把这些物品的力量加到(手,衣服等)的攻击力量里。
6) RM_CHARSTATUSCHANGED消息,通知玩家状态改变消息。
7) AddProcess(this, RM_ABILITY, 0, 0, 0, 0, NULL); 等级
AddProcess(this, RM_SUBABILITY, 0, 0, 0, 0, NULL);
AddProcess(this, RM_DAYCHANGING, 0, 0, 0, 0, NULL); 校时
AddProcess(this, RM_SENDUSEITEMS, 0, 0, 0, 0, NULL); 装备
AddProcess(this, RM_SENDMYMAGIC, 0, 0, 0, 0, NULL); 魔法
SysMsg(szMsg, 1) 攻击力
并把用户数据从g_xReadyUserInfoList2列表中删除。
说明:
一旦通过验证,就从验证列表中该玩家,改变玩家状态,LoadPlayer加载用户资源(地图中加入用户信息,向用户24*24区域内的块内玩家发送上线消息GameSrv广播新玩家上线(坐标)的消息。向该新玩家发送玩家信息(等级,装备,魔法,攻击力等)。
5.接受登录成功后,接收GameSrv服务器发送的消息:
接收GameGate发送的消息:CClientSocket::OnSocketMessage的FD_READ事件中,PacketQ.PushQ((BYTE*)pszPacket);把接收到的消息,压入PacketQ队列中。处理PacketQ队列数据是由CGameProcess::Load()时调用OnTimer在CGameProcess::OnTimer中处理的,
处理过程为:
OnMessageReceive;
ProcessPacket();
ProcessDefaultPacket();
OnMessageReceive函数;
1. 判断是否收到心跳数据包,发送'*',发送心跳数据包。
2. 调用OnSocketMessageRecieve函数。这个函数里面详细处理了客户端的游戏执行逻辑。如果是‘+’开头(数据包)则调用OnProcPacketNotEncode处理这种类型数据包。否则得到_TDEFAULTMESSAGE数据包,进行游戏逻辑处理。
OnProcPacketNotEncode说明:
收到GameSrv服务器的相应消息:
"GOOD":可以执行动作。 m_bMotionLock为假。
"FAIL":不允许执行动作。人物被拉回移动前位置。
"LNG":
"ULNG":
"WID":
"UWID":
"FIR":
"UFIR":
"PWR":
3. CGameProcess::OnSocketMessageRecieve(char *pszMsg)函数。处理游戏相关的消息。
SM_SENDNOTICE: 服务器提示信息:
SM_NEWMAP: 用户登录后,服务器发送的初始化地图消息。
SM_LOGON: 用户登录消息(服务器处理后返回结果)。用户登录成功后,在本地创建游戏对象,并发送消息,请求返回用户物品清单(魔法,等级,物品等)。
SM_MAPDESCRIPTION: 得到服务器发送的地图的描述信息。
SM_ABILITY:服务器发送的本玩家金钱,职业信息。
SM_WINEXP:
SM_SUBABILITY : 服务器发送的玩家技能(魔法,杀伤力,速度,毒药,中毒恢复,生命恢复,符咒恢复)
SM_ SM_SENDMYMAGIC: 用户魔法列表信息。
SM_MAGIC_LVEXP: 魔法等级列表。
SM_BAGITEMS:用户物品清单 (玩家CM_QUERYBAGITEMS消息)
SM_SENDUSEITEMS:用户装备清单
SM_ADDITEM: 拣东西
SM_DELITEM: 丢弃物品。
等等。
4. 部分数据未处理,加入m_xWaitPacketQueue队列中由ProcessPacket处理。
新登录游戏玩家:在OnSocketMessageRecieve函数中依次收到的消息为:
1. GameSrv 服务器ProcessLogin线程返回GameGate服务器后返回的:
AddProcess(this, RM_LOGON, 0, 0, 0, 0, NULL);加入登录消息。
SM_NEWMAP, SM_LOGON, SM_USERNAME, SM_MAPDESCRIPTION消息
AddProcess(this, RM_ABILITY, 0, 0, 0, 0, NULL); 等级
SM_ABILITY
AddProcess(this, RM_SUBABILITY, 0, 0, 0, 0, NULL);
SM_SUBABILITY
AddProcess(this, RM_DAYCHANGING, 0, 0, 0, 0, NULL); 校时
SM_DAYCHANGING
AddProcess(this, RM_SENDUSEITEMS, 0, 0, 0, 0, NULL); 装备
SM_SENDUSEITEMS
AddProcess(this, RM_SENDMYMAGIC, 0, 0, 0, 0, NULL); 魔法
SM_SENDMYMAGIC
客户端收到消息后相应的处理:
SM_NEWMAP 接受地图消息 OnSvrMsgNewMap
初始化玩家坐标,m_xMyHero.m_wPosX = ptdm->wParam;
m_xMyHero.m_wPosY = ptdm->wTag;
加载地图文件 m_xMap.LoadMapData(szMapName);
设置场景。 m_xLightFog.ChangeLightColor(dwFogColor);
SM_LOGON 返回登录消息 OnSvrMsgLogon
m_xMyHero.Create初始化玩家信息(头发,武器,加载图片等),设置玩家
地图m_xMyHero.SetMapHandler(&m_xMap),创建用户魔法。加入m_xMagicList列表,pxMagic->CreateMagic, m_xMagicList.AddNode(pxMagic);并向服务器发送CM_QUERYBAGITEMS消息(用户物品清单,血,气,衣服,兵器等)。
SM_USERNAME 获取玩家的游戏角色名字。
SM_MAPDESCRIPTION 地图对应的名字。
SM_BAGITEMS 用户物品清单 (玩家CM_QUERYBAGITEMS消息)
SM_CHARSTATUSCHANGED 通知玩家状态改变消息(攻击力,状态)。
SM_ABILITY 玩家金钱,职业
SM_SUBABILITY 玩家技能(魔法,杀伤力,速度,毒药,中毒恢复,生命恢复,符
咒恢复)
SM_DAYCHANGING 返回游戏状态。(Day, Fog)让客户端随着服务器的时间,加载不同场景。
SM_SENDUSEITEMS 用户装备清单
SM_SENDMYMAGIC 用户魔法列表信息。
总结:
客户端连接到GameGate游戏网关服务器,并通过GameSrv服务器验证之后,就会收到GameSrv服务器发来的消息。主要是地图消息,登录消息,玩家的装备,技能,魔法,个人设置等等。GameSrv把地图分成若干块,把该玩家加入其中一块,并加入这一块的用户对象列表中,设置其状态为OS_MOVINGOBJECT。客户端加载地图,设置场景,设置自己的玩家状态(此时还没有怪物和其它玩家,所以玩家还需要接收其它游戏玩家和怪物的清单列表)。
. 接收怪物,商人,其它玩家的消息:
ProcessUserHuman:(其它玩家—服务器处理)
CPlayerObject->SearchViewRange();
CPlayerObject->Operate();
遍历UserInfoList列表,依次调用每个UserInfo的Operate来处理命令队列中的所有操作; pUserInfo->Operate()调用m_pxPlayerObject->Operate()调用。根据分发消息(RM_TURN)向客户端发送SM_TURN消息。GameSrv广播新玩家上线(坐标)的消息。向该新玩家发送玩家信息(等级,装备,魔法,攻击力等)。
玩家,移动对象:
1. 遍历m_xVisibleObjectList列表,所有(玩家,商人,怪物)发送调用AddProcess
(RM_TURN向周围玩家发送消息)。
地图:
2.遍历m_xVisibleItemList,发送AddProcess(this, RM_ITEMSHOW消息更新地图。
3.遍历m_xVisibleEventList,发送AddProcess(this, RM_SHOWEVENT
ProcessMonster线程:(怪物—服务器处理)
GameSrv服务器在ProcessMonster线程:创建不同的CMonsterObject对象,并且加入xMonsterObjList列表和pMapCellInfo->m_xpObjectList列表中,然后再调用CMonsterObject::SearchViewRange()更新视线范围内目标,根据g_SearchTable计算出搜索坐标,转换为相应的地图单元格,遍历所有可移动生物,加入m_xVisibleObjectList列表,调用Operate;Operate遍历m_DelayProcessQ列表,过滤出RM_DOOPENHEALTH,RM_STRUCK和RM_MAGSTRUCK三个事件(恢复生命值,攻击,魔法攻击),并处理。
ProcessMerchants线程:(商人–服务器处理)
1). 遍历g_pMerchantInfo结构(根据nNumOfMurchantInfo数量)。得到商人类型相关的地图,创建商人对象,设置不同的编号,坐标,头像及所属地图。在该地图中加入该商人,且在g_xMerchantObjList商人清单中加入该商人。
2). 遍历g_xMerchantObjList, SearchViewRange,对每个商人更新视线范围内目标
a). 遍历m_xVisibleObjectList,设置每个pVisibleObject->nVisibleFlag = 0;设置状态(删除)。
b). 搜索VisibleObjectList列表,(服务器启动时InitializingServer加载 searchTable.tbl),根据坐标,找到相应的地图单元格。然后遍历pMapCellInfo->m_xpObjectList列表,判断如果为OS_MOVINGOBJECT标志,调用UpdateVisibleObject函数,该函数遍历 m_xVisibleObjectList列表,如果找到该商人对象,则pVisibleObject->nVisibleFlag = 1;否则判断pNewVisibleObject对象,设置nVisibleFlag为2,设置对象为该商人实体,然后加入m_xVisibleObjectList列表中。
总结:循环列表,找出地图单元格中的所有玩家,把所有玩家(OS_MOVINGOBJECT)加入到m_xVisibleObjectList列表中。
c). 遍历m_xVisibleObjectList列表,(pVisibleObject->nVisibleFlag == 0)则删除该pVisibleObject对象。
d). RunRace调用AddRefMsg 向周围玩家发送SM_TURN和SM_HIT
客户端收到消息后相应的处理:
1.CGameProcess::OnSocketMessageRecieve加入m_xWaitPacketQueue队列
遍历m_xVisibleObjectList队列中所有移动物体(角色):
RM_DISAPPEAR 消失(SM_DISAPPEAR) ProcessDefaultPacket函数
RM_DEATH 死亡(SM_NOWDEATH, SM_DEATH)
CHero::OnDeath 其它玩家。
CActor::OnDeath 怪物。
//g_xGameProc.m_xMagicList
RM_TURN 移动
遍历m_xVisibleItemList队列中所有移动物体(地图):
RM_ITEMHIDE 从m_stMapItemList列表中删除该移动对象
RM_ITEMSHOW 遍历m_stMapItemList,如果不存在,则创建一个GROUNDITEM结构,并加入m_stMapItemList列表中。
typedef struct tagGROUNDITEM
{
INT nRecog;
SHORT shTileX;
SHORT shTileY;
WORD wLooks;
CHAR szItemName[40];
}GROUNDITEM, *LPGROUNDITEM;
遍历m_xVisibleEventList队列中所有移动物体(事件):
RM_HIDEEVENT
RM_SHOWEVENT
2. 部分数据未处理,加入m_xWaitPacketQueue队列中由ProcessPacket处理。
CClientSocket::OnSocketMessage的FD_READ事件中,PacketQ.PushQ把接收到的消息,压入PacketQ队列中。处理PacketQ队列数据是由CGameProcess::Load()时调用OnTimer在CGameProcess::OnTimer中处理的,处理过程为:
OnTimer -> ProcessPacket -> ProcessPacket处理m_xWaitPacketQueue队列消息(OnSocketMessageRecieve函数中未处理的消息)。
ProcessPacket 函数处理流程:
1. 处理本玩家(SM_NOWDEATH, SM_DEATH, SM_CHANGEMAP, SM_STRUCK)
a.如果接收到消息是SM_NOWDEATH或SM_DEATH 则加入m_xPriorPacketQueue队列。
b. 如果接收到消息是SM_CHANGEMAP则调用LoadMapChanged,设置场景。
c. SM_STRUCK 处理受攻击(本玩家,或者其它的玩家,NPC等)。
2. 其它消息:m_xMyHero.StruckMsgReassign();
m_xMyHero.m_xPacketQueue.PushQ((BYTE*)lpPacketMsg);
判断服务器发送来的消息ID是否相同。m_xMyHero.m_dwIdentity在登录成功的时
候由服务器发送的用户消息获取的。
if ( lpPacketMsg->stDefMsg.nRecog == m_xMyHero.m_dwIdentity )
如果是服务器端游戏玩家自己发送的消息,则处理自己的消息。否则如果是其它玩家(怪物)发送的消息,遍历m_xActorList列表, 判断该对象是否存在,如果该不存在,则根据stFeature.bGender的类型
_GENDER_MAN: 创建一个CHero对象,加入到m_xActorList列表中。
_GENDER_WOMAN:
_GENDER_NPC: 创建一个CNPC对象,加入到m_xActorList列表中。
_GENDER_MON: 创建一个CActor对象,加入到m_xActorList列表中。
然后pxActor->m_xPacketQueue.PushQ 然后把消息压入该对象的xPacketQueue列表中。
总结:ProcessPacket处理 CClientSocket类接受的消息(m_xWaitPacketQueue),判断是否是服务器发送给自己的消息,处理一些发送给自己的重要消息,其它消息处理则加入m_xMyHero.m_xPacketQueue队列中,然后再遍历m_xActorList队列,判断如果服务器端发来的消息里的玩家(NPC,怪物),在m_xActorList队列中找不到,就判断一个加入m_xActorList列表中,并且把该消息压入pxActor->m_xPacketQueue交给该NPC去处理该事件。
而xPacketQueue队列的消息分别由该对象的UpdatePacketState处理,如下:
BOOL CActor::UpdatePacketState() ,BOOL CNPC::UpdatePacketState()
BOOL CHero::UpdatePacketState()。
处理CGameProcess::OnSocketMessageRecieve 中 SM_CLEAROBJECT消息:
处理(SM_DISAPPEAR,SM_CLEAROBJECT)消息。
遍历m_xWaitDefaultPacketQueue消息列表
SM_DISAPPEAR和SM_CLEAROBJECT:
遍历m_xActorList列表,清除pxActor->m_xPacketQueue队列内所有消息。
m_xActorList.DeleteCurrentNodeEx();从对列中删除该对象。
CHero* pxHero = (CHero*)pxActor; delete((CHero*)pxHero);销毁该玩家。
游戏循环处理: CGameProcess::RenderScene(INT nLoopTime)函数:
主要流程如下:
wMoveTime += nLoopTime; 判断wMoveTime>100时,bIsMoveTime置为真。
1.m_xMyHero.UpdateMotionState(nLoopTime, bIsMoveTime);处理本玩家消息。
a. UpdatePacketState函数:
遍历m_xPriorPacketQueue队列,如果有SM_NOWDEATH或SM_DEATH消息,则优先处理。
处理m_xPacketQueue队列中消息。
SM_STRUCK:
SM_RUSH
SM_BACKSTEP
SM_FEATURECHANGED:
SM_OPENHEALTH:
SM_CLOSEHEALTH:
SM_CHANGELIGHT:
SM_USERNAME:
SM_CHANGENAMECOLOR:
SM_CHARSTATUSCHANGE:
SM_MAGICFIRE:
SM_HEALTHSPELLCHANGED:
2.CheckMappedData函数:遍历m_xActorList列表分别调用
CActor::UpdateMotionState(INT nLoopTime, BOOL bIsMoveTime)
CNPC::UpdateMotionState(INT nLoopTime, BOOL bIsMoveTime)
CMyHero::UpdateMotionState(INT nLoopTime, BOOL bIsMoveTime)
处理自己消息。
CHero::UpdatePacketState()
case SM_SITDOWN:
case SM_BUTCH:
case SM_FEATURECHANGED:
case SM_CHARSTATUSCHANGE:
case SM_OPENHEALTH:
case SM_CLOSEHEALTH:
case SM_CHANGELIGHT:
case SM_USERNAME:
case SM_CHANGENAMECOLOR:
case SM_HEALTHSPELLCHANGED:
case SM_RUSH:
case SM_BACKSTEP:
case SM_NOWDEATH:
case SM_DEATH:
case SM_WALK:
case SM_RUN:
case SM_TURN:
case SM_STRUCK:
case SM_HIT:
case SM_FIREHIT:
case SM_LONGHIT:
case SM_POWERHIT:
case SM_WIDEHIT:
case SM_MAGICFIRE:
case SM_SPELL:
CNPC::UpdatePacketState()
case SM_OPENHEALTH:
case SM_CLOSEHEALTH:
case SM_CHANGELIGHT:
case SM_USERNAME:
case SM_CHANGENAMECOLOR:
case SM_HEALTHSPELLCHANGED:
case SM_TURN:
case SM_HIT:
CActor::UpdatePacketState()
case SM_DEATH: SetMotionFrame(_MT_MON_DIE, bDir);
case SM_WALK: SetMotionFrame(_MT_MON_WALK, bDir);
case SM_TURN: SetMotionFrame(_MT_MON_STAND, bDir);
case SM_DIGUP: SetMotionFrame(_MT_MON_APPEAR, bDir);
case SM_DIGDOWN: SetMotionFrame(_MT_MON_APPEAR, bDir);
case SM_FEATURECHANGED:
case SM_OPENHEALTH:
case SM_CLOSEHEALTH:
case SM_CHANGELIGHT:
case SM_CHANGENAMECOLOR:
case SM_USERNAME:
case SM_HEALTHSPELLCHANGED:
case SM_BACKSTEP: SetMotionFrame(_MT_MON_WALK, bDir);
case SM_STRUCK: SetMotionFrame(_MT_MON_HITTED, m_bCurrDir);
case SM_HIT: SetMotionFrame(_MT_MON_ATTACK_A, bDir);
case SM_FLYAXE:
case SM_LIGHTING:
case SM_SKELETON:
收到多个NPC,玩家发送的SM_TURN消息:由下面对象调用处理:
CHero::OnTurn
CNPC::OnTurn
CActor::OnTurn
根据服务器发送的消息,(创建一个虚拟玩家NPC,怪物,在客户端),根据参数,初始化该对象设置(方向,坐标,名字,等级等)。在后面的处理中绘制该对象到UI界面中(移动对象的UI界面处理。)
SetMotionFrame(_MT_MON_STAND, bDir); m_bCurrMtn := _MT_MON_STAND
m_dwFstFrame , m_dwEndFrame , m_wDelay 第一帧,最后一帧,延迟时间。
3. AutoTargeting 自动搜索目标(NPC,怪物,玩家等)
4. RenderObject补偿对象时间
5. RenderMapTileGrid
m_xMagicList,处理玩家魔法后,UI界面的处理。
6. m_xSnow, m_xRain, m_xFlyingTail, m_xSmoke, m_xLightFog设置场景UI界面处理。
7. m_xMyHero.ShowMessage(nLoopTime); 显示用户(UI处理)
m_xMyHero.DrawHPBar(); 显示用户HP值。
遍历m_xActorList,处理所有NPC的UI界面重绘
pxHero->ShowMessage(nLoopTime);
pxHero->DrawHPBar();
8. DropItemShow下拉显示。
9. 判断m_pxMouseTargetActor(玩家查看其它玩家,NPC,怪物时)
g_xClientSocket.SendQueryName向服务器提交查询信息。
m_pxMouseOldTargetActor = m_pxMouseTargetActor; 保存该对象
m_pxMouseTargetActor->DrawName(); 重绘对象名字(UI界面显示)
下面分析一下用户登录之后的流程:
从前面的分析中可以看到,该用户玩家登录成功之后,得到了服务器发送来的各种消息。处理也比较复杂,同时有一定的优先级处理。并且根据用户登录后的XY坐标,向用户发送来了服务器XY坐标为中心附近单元格中的所有玩家(NPC,怪物)的SM_TURN消息。
客户端根据数据包的标志,创建这些NPC,设置属性,并且把它们加入m_xActorList对列中。最后在UI界面上绘制这些对象。
现在假设玩家开始操作游戏:
传奇的客户端源代码工程WindHorn
一、CWHApp派生CWHWindow和CWHDXGraphicWindow。
二、CWHDefProcess派生出CloginProcess、CcharacterProcess、CgameProcess
客户端WinMain调用CWHDXGraphicWindow g_xMainWnd;创建一个窗口。
客户端CWHDXGraphicWindow在自己的Create函数中调用了CWHWindow的Create来创建窗口,然后再调用自己的CreateDXG()来初始化DirectX。
消息循环:
因此,当客户端鼠标单击的时候,先调用CWHWindow窗口的回调函数WndProc,即: g_pWHApp->MainWndProc g_pWHApp定义为:static CWHApp* g_pWHApp = NULL;在CWHApp
构造函数中赋值为:g_pWHApp = this;
g_pWHApp->MainWndProc便调用了CWHApp::MainWndProc,这是一个虚函数,实际上则是调用它的派生类CWHDXGraphicWindow::MainWndProc。
if ( m_pxDefProcess )
return m_pxDefProcess->DefMainWndProc(hWnd, uMsg, wParam, lParam);
根据g_xMainWnd.m_pxDefProcess和全局变量g_bProcState标记当前的处理状态。调用
CLoginProcess->DefMainWndProc
CCharacterProcess->DefMainWndProc
CGameProcess->DefMainWndProc
当用户进行游戏之后,点击鼠标左键,来处理玩家走动的动作:
客户端执行流程:(玩家走动)
CGameProcess::OnLButtonDown(WPARAM wParam, LPARAM lParam)函数:该函数的处理流程:
1. g_xClientSocket.SendNoticeOK();如果点中CnoticeBox则m_xNotice.OnButtonDown
if m_xMsgBtn.OnLButtonDown则调用g_xClientSocket.SendNoticeOK()方法,发送还CM_LOGINNOTICEOK消息。
2.m_pxSavedTargetActor = NULL;设置为空。CInterface::OnLButtonDown函数会判断
鼠标点击的位置(CmirMsgBox, CscrlBar,CgameBtn,GetWindowInMousePos)
a. g_xClientSocket.SendItemIndex(CM_DROPITEM 丢弃物品)
游戏服务器执行流程m_pxPlayerObject->Operate()调用
m_pUserInfo->UserDropGenItem
m_pUserInfo->UserDropItem 删除普通物品。
SM_DROPITEM_SUCCESS 返回删除成功命令
SM_DROPITEM_FAIL 返回删除失败命令
b. 遍历m_stMapItemList列表(存储玩家,怪物,NPC), g_xClientSocket.SendPickUp 发送CM_PICKUP命令。
游戏服务器:m_pxPlayerObject->Operate()调用 PickUp(捡东西)消息处理:
m_pMap->GetItem(m_nCurrX, m_nCurrY) 返回地图里的物体(草药,物品,金子等)
1.memcmp(pMapItem->szName, g_szGoldName 如果是黄金:
m_pMap->RemoveObject从地图中移走该的品。
if (m_pUserInfo->IncGold(pMapItem->nCount))增加用户的金钱(向周转玩家发送RM_ITEMHIDE 消息,隐藏该物体,GoldChanged(),改变玩家的金钱。否则,把黄金返回地图中。
2.m_pUserInfo->IsEnoughBag()
如果玩家的还可以随身带装备(空间)。m_pMap->RemoveObject从地图中移走该的品。UpdateItemToDB,更新用户信息到数据库。(向周转玩家发送RM_ITEMHIDE 消息,隐藏该物体,SendAddItem(lptItemRcd)向本玩家发送捡到东西的消息。m_pUserInfo->m_lpTItemRcd.AddNewNode并把该物品加入自己的列表中。
c. if m_pxMouseTargetActor g_xClientSocket.SendNPCClick发送CM_CLICKNPC命令。
客户端RenderScene调用m_pxMouseTargetActor = NULL;
CheckMappedData(nLoopTime, bIsMoveTime)处理,如果鼠标在某个移动对象的区域内就会设置 m_pxMouseTargetActor为该对象。
如果是NPC:
if ( m_pxMouseTargetActor->m_stFeature.bGender == _GENDER_NPC )
g_xClientSocket.SendNPCClick(m_pxMouseTargetActor->m_dwIdentity);
CM_CLICKNPC消息:
否则:
m_xMyHero.OnLButtonDown
d. 否则m_xMyHero.OnLButtonDown
先判断m_xPacketQueue是否有数据,有则先处理。返回。
判断m_pxMap->GetNextTileCanMove 根据坐标,判断地图上该点属性是否可以移动到该位置:
可移动时:
人:SetMotionState(_MT_WALK
骑马:SetMotionState(_MT_HORSEWALK
不可移动时:
人:SetMotionState(_MT_STAND, bDir);
骑马:SetMotionState(_MT_HORSESTAND, bDir);
SetMotionState函数:
判断循环遍历目标点的周围八个坐标,如果发现是一扇门,则向服务器发送打开这扇门的命令。g_xClientSocket.SendOpenDoor,否则则发送CM_WALK命令到服务器。
m_bMotionLock = m_bInputLock = TRUE; 设置游戏状态
m_wOldPosX = m_wPosX; 保存玩家X点
m_wOldPosY = m_wPosY; 保存玩家Y点
m_bOldDir = m_bCurrDir; 保存玩家方向
然后调用SetMotionFrame设置m_bCurrMtn = _MT_WALK,方向等游戏状态。
设置m_bMoveSpeed = _SPEED_WALK(移动速度1)。m_pxMap->ScrollMap设置地图的偏移位置(m_shViewOffsetX, m_shViewOffsetY)。然后滚动地图,重绘玩家由CGameProcess::RenderScene CGameProcess::RenderObject->DrawActor重绘。
传奇源码分析-客户端(游戏逻辑处理源分析五 服务器端响应)
2008-10-02 15:09
GameSrv服务器ProcessUserHuman线程处理玩家消息: 遍历UserInfoList列表,依次调用每个UserInfo的Operate来处理命令队列中的所有操作; pUserInfo->Operate()调用m_pxPlayerObject->Operate()调用。 判断玩家if (!m_fIsDead),如果已死,则发送_MSG_FAIL消息。我们在前面看到过,该消息是被优先处理的。否则则调用WalkTo,并发送_MSG_GOOD消息给客户端。 WalkTo函数的流程: 1) WalkNextPos 根据随机值产生,八个方向的坐标位置。 2) WalkXY怪物走动到一个坐标值中。 CheckDoorEvent根据pMapCellInfo->m_sLightNEvent返回四种状态。 a) 要移动的位置是一扇门 _DOOR_OPEN b) 不是一扇门 _DOOR_NOT c) 是一扇门不可以打开返回 _DOOR_MAPMOVE_BACK或_DOOR_MAPMOVE_FRONT玩家前/后移动 3) 如果_DOOR_OPEN则发送SM_DOOROPEN消息给周围玩家。 4) m_pMap->CanMove如果可以移动,则MoveToMovingObject从当前点移动到另一点。并发送AddRefMsg(RM_WALK)给周围玩家。 AddRefMsg函数,我们在后面的服务器代码里分析过:它会根据X,Y坐标,在以自己坐标为中心周围26*26区域里面,按地图单元格的划分,遍历所有单元格,再遍历所有单元格内的玩家列表,广播发送RM_WALK消息。 客户端执行流程:(反馈服务器端本玩家走动) 1. 服务器如果发送_MSG_FAIL 由客户端CGameProcess::OnProcPacketNotEncode处理。 m_xMyHero.SetOldPosition(); 人: SetMotionFrame(_MT_STAND AdjustMyPostion(); 重绘地图 m_bMotionLock = m_bInputLock = FALSE; 骑马:SetMotionFrame(_MT_HORSESTAND AdjustMyPostion(); 重绘地图 m_bMotionLock = m_bInputLock = FALSE; 2. 服务器如果发送_MSG_GOOD, 由客户端CGameProcess::OnProcPacketNotEncode处理。m_xMyHero.m_bMotionLock = FALSE; 其它客户端执行流程:(反馈服务器端其它玩家) 1.其它玩家: 人: SetMotionFrame(_MT_WALK, bDir); 骑马:SetMotionFrame(_MT_HORSEWALK, bDir); m_bMoveSpeed = _SPEED_WALK; SetMoving(); 设置m_shShiftPixelX, m_shShiftPixelY坐标。 2.NPC,怪物: SetMotionFrame(_MT_MON_WALK, bDir); m_bMoveSpeed = _SPEED_WALK; SetMoving(); 设置m_shShiftPixelX, m_shShiftPixelY坐标。 CGameProcess::RenderObject->DrawActor(m_shShiftPixelX, m_shShiftPixelY)重绘发消息的玩家,NPC怪物位置。 |
评论前必须登录!
注册