项目移动模块一些思考

2022/09/03 Mmo-Game

项目移动模块一些思考

目录

一:项目移动设计的核心思想

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.需要客户端上方朝向是因为没有目标点,无法根据两点计算出朝向。

Search

    Table of Contents