网游服务器端设计思考:游戏的错误提示

来源:互联网 发布:淘宝开店注意事项 编辑:程序博客网 时间:2024/05/16 15:07

在网络游戏中常常会出现这样的情况,玩家在进行一些操作的同时是不能进行另一些操作的。比如说:正在移动的玩家不能释放带有吟唱的技能;死亡时不能交易;不能同时和两个玩家进行交易;不同阵营不允许交易等等。而很多需要交互的操作(比如交易)当一方取消或异常操作时,服务器会撤消双方的操作,并通知双方撤消的原因。在这就会面临一个问题,应该使用什么方式来通知客户端这些错误?

    在设计游戏错误提示模块时应该考虑一下几个问题:

  1. 易用性:每次添加新的错误提示都不费劲!
  2. 可维护性:来个新人和他说两句,下次添加的时候他想都不想就能找到在哪加!
  3. 可扩展性:今天加逻辑服务器给客户端的错误提示,明天要加连接服务器给客户端的错误提示也没有什么心理压力;哪天游戏走运了在海外运营,挣外国人钱的时候,错误提示改成鸟文也不会伤筋动骨!

 

    常见的有三种方法通知客户端错误:

一、向客户端发送错误码

    向客户端发送错误码是最常见的一种服务器向客户端发送错误通知的方法。服务器端在发现player的操作不合法时,向客户端发送错误码,这个错误码往往是整型数字,客户端收到错误码的协议后在本地查找配置文件,使用错误码查找配置文件的对应项,找到对应错误信息,然后把错误信息显示给玩家。

    客户端的配置文件可以写成这样的形式(server_error.ini):

[Trade] 
0     = "成功无错误"

1     = “包裹已满”

2     = “对方正在交易”

3     = “对方拒绝进行交易”

………

    服务器端根据各种条件检查的结果向客户端发送对应的错误码,下面以交易操作为例,结合代码来看错误码方法:

Mangos

   1: void WorldSession::HandleInitiateTradeOpcode(WorldPacket& recvPacket)
   2: {
   3:     uint64 ID;
   4:     recvPacket >> ID;
   5:  
   6:     if (GetPlayer()->m_trade)
   7:         return;
   8:  
   9:     if (!GetPlayer()->isAlive())
  10:     {
  11:         SendTradeStatus(TRADE_STATUS_YOU_DEAD);
  12:         return;
  13:     }
  14:  
  15:     if (GetPlayer()->hasUnitState(UNIT_STAT_STUNNED))
  16:     {
  17:         SendTradeStatus(TRADE_STATUS_YOU_STUNNED);
  18:         return;
  19:     }
  20:  
  21:     if (isLogingOut())
  22:     {
  23:         SendTradeStatus(TRADE_STATUS_YOU_LOGOUT);
  24:         return;
  25:     }
  26:  
  27:     if (GetPlayer()->IsTaxiFlying())
  28:     {
  29:         SendTradeStatus(TRADE_STATUS_TARGET_TO_FAR);
  30:         return;
  31:     }
  32:  
  33:     Player* pOther = ObjectAccessor::FindPlayer( ID );
  34:  
  35:     if (!pOther)
  36:     {
  37:         SendTradeStatus(TRADE_STATUS_NO_TARGET);
  38:         return;
  39:     }
  40:  
  41:     if (pOther == GetPlayer() || pOther->m_trade)
  42:     {
  43:         SendTradeStatus(TRADE_STATUS_BUSY);
  44:         return;
  45:     }
  46:  
  47:     if (!pOther->isAlive())
  48:     {
  49:         SendTradeStatus(TRADE_STATUS_TARGET_DEAD);
  50:         return;
  51:     }
  52:  
  53:     if (pOther->IsTaxiFlying())
  54:     {
  55:         SendTradeStatus(TRADE_STATUS_TARGET_TO_FAR);
  56:         return;
  57:     }
  58:  
  59:     if (pOther->hasUnitState(UNIT_STAT_STUNNED))
  60:     {
  61:         SendTradeStatus(TRADE_STATUS_TARGET_STUNNED);
  62:         return;
  63:     }
  64:  
  65:     if (pOther->GetSession()->isLogingOut())
  66:     {
  67:         SendTradeStatus(TRADE_STATUS_TARGET_LOGOUT);
  68:         return;
  69:     }
  70:  
  71:     if (pOther->GetSocial()->HasIgnore(GetPlayer()->GetObjectGuid()))
  72:     {
  73:         SendTradeStatus(TRADE_STATUS_IGNORE_YOU);
  74:         return;
  75:     }
  76:  
  77:     if (!sWorld.getConfig(CONFIG_BOOL_ALLOW_TWO_SIDE_INTERACTION_TRADE) && pOther->GetTeam() !=_player->GetTeam() )
  78:     {
  79:         SendTradeStatus(TRADE_STATUS_WRONG_FACTION);
  80:         return;
  81:     }
  82:  
  83:     if (!pOther->IsWithinDistInMap(_player,10.0f,false))
  84:     {
  85:         SendTradeStatus(TRADE_STATUS_TARGET_TO_FAR);
  86:         return;
  87:     }
  88:  
  89:     // OK start trade
  90:     _player->m_trade = new TradeData(_player, pOther);
  91:     pOther->m_trade = new TradeData(pOther, _player);
  92:  
  93:     WorldPacket data(SMSG_TRADE_STATUS, 12);
  94:     data << (uint32) TRADE_STATUS_BEGIN_TRADE;
  95:     data << (uint64)_player->GetGUID();
  96:     pOther->GetSession()->SendPacket(&data);
  97: }

 

    可以看到上面华丽丽的九十多行代码就是玩家提出交易申请时服务器端的检查,条件不满足时就会调用SendTradeStatus ()函数向客户端发送对应的错误码。来看看错误码是怎么定义的:

   1: enum TradeStatus
   2: {
   3:     TRADE_STATUS_BUSY           = 0,
   4:     TRADE_STATUS_BEGIN_TRADE    = 1,
   5:     TRADE_STATUS_OPEN_WINDOW    = 2,
   6:     TRADE_STATUS_TRADE_CANCELED = 3,
   7:     TRADE_STATUS_TRADE_ACCEPT   = 4,
   8:     TRADE_STATUS_BUSY_2         = 5,
   9:     TRADE_STATUS_NO_TARGET      = 6,
  10:     TRADE_STATUS_BACK_TO_TRADE  = 7,
  11:     TRADE_STATUS_TRADE_COMPLETE = 8,
  12:     // 9?
  13:     TRADE_STATUS_TARGET_TO_FAR  = 10,
  14:     TRADE_STATUS_WRONG_FACTION  = 11,
  15:     TRADE_STATUS_CLOSE_WINDOW   = 12,
  16:     // 13?
  17:     TRADE_STATUS_IGNORE_YOU     = 14,
  18:     TRADE_STATUS_YOU_STUNNED    = 15,
  19:     TRADE_STATUS_TARGET_STUNNED = 16,
  20:     TRADE_STATUS_YOU_DEAD       = 17,
  21:     TRADE_STATUS_TARGET_DEAD    = 18,
  22:     TRADE_STATUS_YOU_LOGOUT     = 19,
  23:     TRADE_STATUS_TARGET_LOGOUT  = 20,
  24:     TRADE_STATUS_TRIAL_ACCOUNT  = 21, // Trial accounts can not perform that action
  25:     TRADE_STATUS_ONLY_CONJURED  = 22  // You can only trade conjured items... (cross realm BG related).
  26: };

再来看看SendTradeStatus ()函数,可以看到只有当状态正常进行转化时,协议带参数发给客户端,如果出现错误就把错误码直接发给客户端。

   1: void WorldSession::SendTradeStatus(TradeStatus status)
   2: {
   3:     WorldPacket data;
   4:  
   5:     switch(status)
   6:     {
   7:         case TRADE_STATUS_BEGIN_TRADE:
   8:             data.Initialize(SMSG_TRADE_STATUS, 4+8);
   9:             data << uint32(status);
  10:             data << uint64(0);
  11:             break;
  12:         case TRADE_STATUS_OPEN_WINDOW:
  13:             data.Initialize(SMSG_TRADE_STATUS, 4+4);
  14:             data << uint32(status);
  15:             break;
  16:         case TRADE_STATUS_CLOSE_WINDOW:
  17:             data.Initialize(SMSG_TRADE_STATUS, 4+4+1+4);
  18:             data << uint32(status);
  19:             data << uint32(0);
  20:             data << uint8(0);
  21:             data << uint32(0);
  22:             break;
  23:         case TRADE_STATUS_ONLY_CONJURED:
  24:             data.Initialize(SMSG_TRADE_STATUS, 4+1);
  25:             data << uint32(status);
  26:             data << uint8(0);
  27:             break;
  28:         default:
  29:             data.Initialize(SMSG_TRADE_STATUS, 4);
  30:             data << uint32(status);
  31:             break;
  32:     }
  33:  
  34:     SendPacket(&data);
  35: }

天龙

      根据流出的代码(交易命名为exchange害我找了老半天………好吧,我鸟文不好):
 
   1: uint CGExchangeApplyIHandler::Execute( CGExchangeApplyI* pPacket, Player* pPlayer )
   2: {
   3:     __ENTER_FUNCTION
   4:     GamePlayer* pGamePlayer = (GamePlayer*)pPlayer ;
   5:     Assert( pGamePlayer ) ;
   6:  
   7:     Obj_Human* pHuman = pGamePlayer->GetHuman() ;
   8:     Assert( pHuman ) ;
   9:  
  10:     Scene* pScene = pHuman->getScene() ;
  11:     if( pScene==NULL )
  12:     {
  13:         Assert(FALSE) ;
  14:         return PACKET_EXE_ERROR ;
  15:     }
  16:  
  17:     //检查线程执行资源是否正确
  18:     Assert( MyGetCurrentThreadID()==pScene->m_ThreadID ) ;
  19:  
  20:     ObjID_t        TargetID = pPacket->GetObjID();
  21:     Obj_Human* pSourceHuman = pHuman;//交易发起者
  22:     Obj_Human* pDestHuman = pScene->GetHumanManager()->GetHuman( TargetID );//交易对象
  23:  
  24:     //验证
  25:     if( pDestHuman == NULL )
  26:     {
  27:         Assert(FALSE);
  28:         return PACKET_EXE_CONTINUE;
  29:     }
  30:     // 不同阵营,不让查看
  31:     if( pSourceHuman->IsEnemy( pDestHuman ) )
  32:     {
  33:         g_pLog->FastSaveLog( ........ ) ;
  34:         return    PACKET_EXE_CONTINUE;
  35:     }
  36:  
  37:     INT iSettingData = pDestHuman->GetDB()->GetSetting(SETTING_TYPE_GAME)->m_SettingData;
  38:     if(SETTINGFLAGISTRUE(iSettingData, GSF_REFUSE_TRADE))
  39:     {
  40:         GCExchangeError Msg;
  41:         Msg.SetID(EXCHANGE_MSG::ERR_REFUSE_TRADE);
  42:         pGamePlayer->SendPacket(&Msg);
  43:         g_pLog->FastSaveLog( ........ ) ;
  44:         return PACKET_EXE_CONTINUE;
  45:     }
  46:     if(pSourceHuman->m_ExchangBox.m_Status >= ServerExchangeBox::EXCHANGE_SYNCH_DATA)
  47:     {//发起者正在交易中
  48:         GCExchangeError Msg;
  49:         Msg.SetID(EXCHANGE_MSG::ERR_SELF_IN_EXCHANGE);
  50:         pGamePlayer->SendPacket(&Msg);
  51:         g_pLog->FastSaveLog( ........ ) ;
  52:         return PACKET_EXE_CONTINUE;
  53:     }
  54:     if(pDestHuman->m_ExchangBox.m_Status >= ServerExchangeBox::EXCHANGE_SYNCH_DATA)
  55:     {//目标正在交易中
  56:         GCExchangeError Msg;
  57:         Msg.SetID(EXCHANGE_MSG::ERR_TARGET_IN_EXCHANGE);
  58:         pGamePlayer->SendPacket(&Msg);
  59:         g_pLog->FastSaveLog( ........ ) ;
  60:         return PACKET_EXE_CONTINUE;
  61:     }
  62:  
  63:     //操作
  64:     //发送消息向目标申请
  65:     GCExchangeApplyI Msg;
  66:     Msg.SetObjID(pSourceHuman->GetID());
  67:     pDestHuman->GetPlayer()->SendPacket(&Msg);
  68:     g_pLog->FastSaveLog( ........ ) ;
  69:         return PACKET_EXE_CONTINUE ;
  70:  
  71:     __LEAVE_FUNCTION
  72:  
  73:         return PACKET_EXE_ERROR ;
  74: }

    还是华丽丽的很多检查,如果条件不满足则使用Msg.SetID()设置错误码。错误码声明如下:

   1: enum
   2: {
   3:     ERR_ERR = 0,
   4:     ERR_SELF_IN_EXCHANGE,
   5:     ERR_TARGET_IN_EXCHANGE,
   6:     ERR_DROP,
   7:     ERR_ALREADY_LOCKED,
   8:     ERR_ILLEGAL,
   9:     ERR_NOT_ENOUGHT_ROOM_SELF,
  10:     ERR_NOT_ENOUGHT_ROOM_OTHER,
  11:     ERR_NOT_ENOUGHT_EXROOM,
  12:     ERR_NOT_ENOUGHT_MONEY_SELF,
  13:     ERR_NOT_ENOUGHT_MONEY_OTHER,
  14:     ERR_TOO_FAR,
  15:     ERR_REFUSE_TRADE,
  16:     ERR_PET_LEVEL_TOO_HIGH,
  17:  };

错误码方式的优点:

    a) 扩展性比较好,不同语言版本的客户端只要替换server_error.ini文件就可以了。

    b) 错误码分类、维护都比较简单。

错误码方式的缺点:

    a) 错误码必须服务器端和客户端事先确定好。

    b) 如果遇到比较复杂的逻辑,可能服务器需要判断很多条件,如果每种条件判断失败都发送一个错误码给客户的话,客户端程序估计会哭。

二、状态冲突方法

    该方法需要在客户端和服务器端都维护一张相同的状态冲突表,表中定义了在不同状态下能就行的操作。客户端对于不需要交互的操作可以做预先的判断,比如正在摆摊的时候玩家进行骑马操作,客户端可以查找冲突表,发现玩家在摆摊的状态下不能进行骑马操作,这个时候显示一条错误信息,这个错误信息也可以写在配置文件里。状态冲突表如表2-1

 

image

表 2-1

状态冲突方法的优点:

    a) 客户端能够快速的提示玩家的错误操作,不需要等待服务器端的回应。

    b) 易用性、扩展性比较好,新加的错误只需在状态冲突表里加上一项,多语言版本只需要替换对应的错误信息文件即可。

状态冲突方法的缺点:

    a) 涉及有交互的操作无法用状态冲突表解决,比如交易时对方主动拒绝。

    b) 一些重要的操作方法客户端预先判断还是有点不放心…… 再者,可能不需要别人拿到这些状态的冲突信息。


三、服务器直接发送错误信息

    第三种方法就是由服务器直接以字符串流的形式发送给客户端,客户端不需要关系错误的内容,直接显示给玩家即可。这样做能省去客户端的工作,服务器在处理较为复杂的逻辑时,错误处理可以随时加:-),但是这样做会付出比较大的代价:首先网络传输量变大,而且很多错误信息都是重复的,对于租用机房特别是双线的机房,流量那可是白花花的银子啊。另外一方面,客户端有时候需要根据错误码来识别一些特殊的操作错误,以便做出对应的处理,比如吟唱中被打断,可以根据错误码来取消读条,切换动作姿态等等。


原文来自:http://www.cnblogs.com/ychellboy/archive/2012/02/24/2367025.html

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 电脑跳舞毯不正常电脑游戏怎么办 PS中缺失的字体怎么办 黑板墙不想要了怎么办 淘宝代练打坏了怎么办 绝地求生与ipad不兼容怎么办 小米手机玩绝地求生卡怎么办 绝地求生服务器目前非常繁忙怎么办 玩绝地求生手机发烫怎么办 绝地求生刺激战场延迟高怎么办 怀孕八个半月打喷嚏头疼怎么办 20岁打喷嚏漏尿怎么办 鼻炎犯了不停打喷嚏怎么办 鼻炎犯了不停打喷嚏流鼻涕怎么办 感冒鼻痒怎么办小窍门 腰扭了屁股也疼怎么办 小三把房子过户怎么办 小三把房子卖了怎么办 打印机ip地址变了怎么办 电脑ip地址错误不能上网怎么办 修改了注册表电脑无法启动怎么办 香水喷到衣服上有印怎么办 家里一股猫的味道怎么办 干菊花里面有虫怎么办 安装时显示程序已关闭怎么办 电脑一直重启开不了机怎么办 应用安装在sd卡打不开怎么办 安装ps打不开安装包怎么办 安装好的软件打不开怎么办? win10系统语言修改不了怎么办 一个月婴儿吵夜怎么办 玩游戏一直闪退怎么办 钱站一直闪退怎么办 win7重装连不上网怎么办 笔记本屏幕横过来了怎么办 3D贴图丢了怎么办 百度文库安装后手机打不开怎么办 win7系统不带usb驱动怎么办 手机网页上的pdf打不开怎么办 网页下载pdf后缀是.do怎么办 ps界面太小怎么办win10 ps软件打不开程序错误怎么办