1.问题描述
旧版本的坐骑系统依赖于人物装备了【法宝】(一种装备类型),装备了法宝的人物变拥有了【幻化】坐骑的能力,即在人物装备栏中的【外观】中会有已经幻化和未幻化(解锁)的坐骑。
如果玩家至少幻化一种坐骑(动物外观),那么玩家在使用坐骑按钮后变成【骑乘】形态,如果没有解锁任何一种坐骑。外观默认使用【法宝】骑乘的状态。
后续,玩家可以在坐骑选择界面手动选择使用【法宝】还是已经解锁的动物作为坐骑外观。
两种坐骑外观
【骑乘状态】可以转化为【飞行状态】,处在飞行状态时,向上滑动左侧摇杆会使玩家向上飞行。
【飞行状态】向下滑动转化为【骑乘状态】,【骑乘状态】再次点击按钮为坐骑。
新版本的需求变化为坐骑的【幻化】不依赖于装备【法宝】了,未装备法宝,无法飞行。
2.问题分析
客户端发起
先从上坐骑的事情回调部分开始调试。行为是通过客户端发起,对于【申请】性质的行为一般是服务器做校验,更新服务器共享内存组,判断是否需要持久化(如果需要,在GameServer进程的DBRoutine线程中在定期心跳包中与DBAgent进程通信,持久化存储主线程接收到的新数据字段。这部分是游戏的数据管理进程做的,不是本文重点)
现在出现了一个问题:
游戏的自动寻路系统在导航阶段,会根据当前位置与目标点的距离和高度差 ,通过状态机选择是否需要骑乘坐骑再寻路。也就是除了按钮回调事件,在update中可能也会调用到UseSkillMount的逻辑,
而原先在客户端本地UseSkillMount条件筛选中,玩家必须装备法宝才有后面的步骤。现在新需求,上坐骑与法宝取消了绑定关系。那么如果玩家此时没有幻化坐骑,那么一旦开启任务自动寻路,会一直弹“未幻化坐骑,不可骑乘”的提示字。
所以需要设置一个flag做区分,在发包的时候告诉服务器。这一帧是玩家点击按钮使用坐骑,而不是导航系统自动上马使用坐骑,把逻辑分开就不会一直走错误判断了
public string UseSkillMount_WithError(bool isPlayerClick = false)
{
//-----错误条件筛选--
//code zone
CG_MOUNT_MOUNT_PAK pak = new CG_MOUNT_MOUNT_PAK();
pak.data.MountID = isPlayerClick ? -1 : 0; // 传0有特殊含义,意思就是让服务器决定自动坐骑
//-1表示由玩家发起(非导航系统)
if (GlobeVar._AutoGameConfig.IsOpenGetInfo)
{
pak.data.MountEffect = PlatformHelper.ReqSource();
pak.data.MountMCount = PlatformHelper.ReqCount();
pak.data.MountHColor = PlatformHelper.ReqOption();
pak.data.MountBColor = PlatformHelper.ReqContent();
pak.data.MountFColor = PlatformHelper.ReqSubType();
int data = 0;
//--机器适配的宏-省略
#if UNITY_IPHONE && !UNITY_EDITOR
pak.data.MountVersion = data;
#else
pak.data.MountData = data;
#endif
}
pak.SendPacket();
}
服务器校验和计算
接下来在服务器工程的 HandlePacket重载函数,参数列表是CG_MOUNT_MOUNT 中找到解析包的部分大概是下面这样
int32_t nMountID = rPacket.mountID();
if(nMountID < -1)
{
return PACKET_EXE_CONTINUE;
}
int32_t real_nMountID = AutoMountID(nMountID);
看看服务器是怎么判断当前选取哪个坐骑的
int32_t Obj_Player::AutoMountID(int32_t nMountID)
{
//1.参数有效直接使用这个参数,查表上指定坐骑
//2.如果有幻化坐骑,并且在包裹里,那么直接使用幻化的坐骑
//差异性判断:
/*
如果是自动任务:那么先检查当前默认坐骑ID是否为空(保存的是上一次幻化坐骑,或者使用法宝的ID)
如果为空,先遍历坐骑缓存池的全部ID,如果有不为空则使用
若缓存池为空,那么只剩下一种情况就是购买了法宝但从未使用,即没有幻化。保险起见,直接返回法宝ID
如果是-1那么不可上坐骑(实际是不可能的,因为要保证导航系统的稳定可靠)
如果是玩家发起的行为
那么只需要检测坐骑背包是否有坐骑,没有就返回当前装备栏的法宝ID,如果是-1.
那么不可上坐骑
*/
}
接下来如果没有return,说明服务器拿到了合法的ID。
private bool Mount_Mount(int mountID)
{
//1.判断坐骑是在包里还是当前装备的法宝
//2.有效性检测
//3.如果是绑定系统主人,则解除所有人的绑定关系
//4.重置状态(解除隐身 -- 解除对话状态 -- 打断采集 -- 打断技能行为)
//5.判断有无效果追加(如果在空中换坐骑,则附加一次空中加速)
SendMountData();
BroadCast_MountData();
}
接下来服务器回包给客户端,主要是表明接受到的ID是合法且有效的。回传当前玩家绑定的服务器ID 和合法坐骑ID号
void Obj_Player::SendMountData()
{
__SOL_TRACE;
if(IsSceneVaild() == false)
{
return;
}
Packets::GC_MOUNT_DATA_PAK pak;
pak.m_PacketData.set_objserverid(GetID());
pak.m_PacketData.set_mountid(m_CurMountID());
SendPacket(pak);
SOL_TRACE__;
}
客户端变更表现
接下来在客户端DataHandler类中找到Receive的方法
public static void ReceivePacket(GC_MOUNT_DATA packet)
{
int nObjServerID = packet.ObjServerID;
int nMountID = packet.MountID;
int EnterSceneServerID = GameManager.PlayerDataPool.CreateMainPlayerCache.m_ServerID;
// 切场景缓存
if ( EnterSceneServerID == nObjServerID )
{
GameManager.PlayerDataPool.CreateMainPlayerCache.m_MountID = nMountID;
}
else
{
Obj_Char obj = ObjManager.FindObjCharInScene(nObjServerID);
if ( obj )
{
if ( obj.IsPlayer())
{
Obj_Player Player = obj as Obj_Player;
if (Player != null)
{
if(Player.IsInJump())
{
Player.ResetJumpState();
Player.RideOrUnMount(nMountID,false);
}
else
{
Player.RideOrUnMount(nMountID);
}
}
}
}
}
}
我们发现如果接受到的ServerID 绑定了有效的玩家后,就开始判断玩家是否可以上坐骑(跳跃过程中显然是不可以上坐骑的,得等动画播放结束后。但游戏为了玩家体验,直接选择重置状态然后立即上坐骑)
这里还发现,上下马的客户端收包位置是一样的。可以确定服务器只是做了数据方面的校验和计算。那么唯一ID怎么标识,玩家是上马还是下马呢。
合法的mountID一定大于0,等于0是客户端发起计算请求,如果上马合法,服务器一单会回传一个大于0的MountID。
所以,客户端只需要判断如果mountID <0,那么说明是下马的行为。