项目移动模块一些思考
目录
一:项目移动设计的核心思想
1.单点和点组:
移动同步分为单点和点组两种协议,点组在客户端定点寻路用,单点为视野内无阻挡点击和摇杆移动用。
2.移动贴墙:
对于移动贴墙时,按策划意图,不做移动速度分量,保持原速绕墙。
主客户端面向墙壁,其他客户端面向自身移动方向,与客户端沟通后,客户端表示主客户端的部分由客户端组自行处理。
服务器可认为没有人物移动时还需区分移动朝向和面向不同这事儿。
3.校验:
摇杆移动时客户端预估一个大约2秒后的抵达位置发到服务器,
包括阻挡也是客户端自行判定,服务器只做校验。
4.修正
移动时客户端发当前坐标和朝向到服务器,朝向直接信客户端(如需可以有改变朝向协议),
坐标如与服务器差距较大,则拉回服务器坐标。
如偏差不大,信客户端,并服务器赋值为客户端坐标,并记录修正向量,每次修正都做向量叠加,
当该向量距离超限时,判定为客户端非法,按此距离与单位移动速度计算出额定移动时间,
将此客户端原地定身该时间。(定身,解定,为特殊协议,与战斗定身buff无关)
二:玩家移动
给定两点坐标,计算朝向:
/** 返回点1对于点0的角度 */
public static int rotationBetweenPos(float x0,float y0,float x1,float y1)
{
//弧度, 调用Math类方法返回的是弧度
double d=Math.atan2(y1-y0,x1-x0);
//角度=弧度/Math.PI*180
d=d/Math.PI*180;
return rotationCut((int)d);
}
/** 将360度角的值缩到0-360间 */
public static int rotationCut(int rotation)
{
while(rotation<0)
{
rotation+=360;
}
while(rotation>=360)
{
rotation-=360;
}
return rotation;
}
// 单位移动组消息
message CSUnitMove{//20200
required int32 index = 1; //单位序号(0:主角,1:宠物,2:傀儡,3:运镖车)
required int32 nowX = 2; //当前x坐标
required int32 nowY = 3; //当前y坐标
required int32 nowZ = 4; //当前z坐标
repeated int32 values = 5; //x,y,z坐标依次
//(目标点组:视野内无阻挡点击和摇杆移动发的是单点,定点寻路用发的是点组)
optional int32 dummyObjectId = 6; // 傀儡移动专用:傀儡的ObjectId
optional bool isSyncY = 7; // 是否同步y
optional int32 targetDistance = 8; // 目标距离 (若此距离小于配置距离,本次寻路不疾跑)
optional bool nearBlock = 9; // 顶着墙行走(true为顶着墙行走,保持跑步状态)
}
1.移动时不用发朝向,是因为服务器有当前坐标点,根据客户端上发的目标路径点,
每次对比当前的坐标点和下一个坐标点,根据两点坐标就能计算出朝向。
目标路径点可能不是直线,而是拐来拐去的,所以朝向也会一直在变。
2.理论上有些项目客户端改变朝向也需要上发到服务器,通知服务器改变朝向,
但是因为上发了目标路径点,客户端就可以不用发朝向了。
3.服务器为了性能考虑目前只校验起始坐标和终点坐标,客户端自己判断中间过程不能穿墙,
如果发生外挂行为,目前确实可能出现中间过程穿墙现象(其实还好)。
服务器移动计算核心逻辑:
1.假设从A点坐标(x1,y1)移动到B点坐标(x2, y2):
在服务器看来其实就是x轴移动了x2 - x1长度,y轴移动了y2 - y1长度,3d寻路类似只是加了z轴。
2.分别计算出服务器1毫秒内:
相对于x轴可以移动的长度,相对于y轴可以移动的长度。
3.当前点移动到下一个目标点:
a.tick里一直更新当前位置, 根据上面步骤2和tick的间隔时间(一般50毫秒)就能计算出一个tick里
x轴走了多长,y轴走了多长,设置当前坐标。
b.每个tick不断更新当前位置,同时判断是否到达了下一个目标点,
到达下一个目标点则从路径点组里取下一个目标,继续。
如此反复,直到到达终点
d.tick可以理解为定时执行某个方法, 例如每50毫秒就会调用一次tick()方法。
消息同步频率:
1.如果是点组移动,由于客户端上发的是目标点组(一连串目标点),
所以只要中途玩家没有做其它操作(例如:停止),那么其实只需要上发一次,
客户端和服务器就会按自己各自逻辑移动,不需要再额外同步消息了。
2.如果是单点移动(遥感), 由于客户端会预估一个大约2秒后的抵达位置发到服务器,所以
同步消息也不会很频繁。
3.至于在玩家在移动过程中,同步给其他玩家,由于有视野管理算法(AOI),不是全屏广播,
消息广播量也不是很频繁。
三:玩家停止
// 单位停止消息
message CSUnitStop{//20202
required int32 index = 1; //单位序号(0:主角,1:宠物,2:傀儡)
required int32 nowX = 2; //当前x坐标
required int32 nowY = 3; //当前y坐标
required int32 nowZ = 4; //当前z坐标
required int32 rotation = 5;//客户端朝向(0-360)
optional int32 slideId = 6; // 滑行ID
optional int32 dummyObjectId = 7; // 傀儡移动专用:傀儡的ObjectId
}
客户端上发当前停止玩家坐标和玩家朝向:
1.服务器校验当前坐标是否合法。
不合法,服务器拉回,通知客户端,信任服务器。
2.服务器校验上方的坐标和玩家当前坐标距离是否大于容错值。
如果超出容错值,服务器拉回,通知客户端,信任服务器。
3.校验都通过了,直接信任客户端,根据上方的消息服务器设置坐标和朝向。
4.需要客户端上方朝向是因为没有目标点,无法根据两点计算出朝向。