新版移动系统学习记录
目录
整体结构
移动目前分为几个模式:
徘徊模式,特殊模式,跳跃模式,普通模式
普通模式又有姿态:
站立姿态,游泳姿态,骑马姿态,载具姿态
普通模式又有移动状态:
静止,走,跑,疾跑,冲刺
类的继承关系
1.移动模块CharacterMoveModule继承关系
移动在游戏里是一个模块,不仅属于玩家,还属于npc等生物模块。
所以往上层逻辑放,不应改挂在actor下。
/**
* 生物基类 玩家、npc、宠物都继承与此基类 Created by wangqiang on 2017/7/6.
*/
public abstract class AbstractCharacter extends BPObject
{
/**
* 移动模块
*/
private CharacterMoveModule moveModule = new CharacterMoveModule(this);
/**
* 命格模块
*/
private PersonalityModule personalityModule = new PersonalityModule(this);
}
直接挂在生物基类AbstractCharacter下。
/**
* 抽象的模块基类,挂在character上的模块都需要继承此模块
*/
public abstract class AbstractCharacterModule implements ModuleInteface
{
protected AbstractCharacter character;
}
挂在AbstractCharacter的module都需要继承模块基类AbstractCharacterModule,
AbstractCharacterModule抽象的模块基类最重要的是缓存了character引用。
/**
* 抽象的模块基类,挂在actor上的模块都需要继承此模块
*/
public abstract class AbstractActorModule extends AbstractCharacterModule
{
public Actor getActor()
{
return actor;
}
/** 推送错误 */
public void sendError(int code)
{
actor.sendError(code);
}
/** 推送错误 */
public void sendError(int code, int[] intParams, String[] strParams)
{
actor.sendError(code, intParams, strParams);
}
/** 更改 */
public void modified()
{
setModify(true);
}
/** 时间戳 */
public long getNowTickTime()
{
return actor.getNowTickTime();
}
}
如果是仅属于玩家模块,AbstractCharacterModule派生一个玩家模块基类AbstractActorModule。
例如:背包,生活技能,货币,装备…
这些模块都需要上面几个常用的方法,获取actor,获取时间,推送错误,所以很有必要再抽象出一层,即AbstractActorModule。
移动模块不仅属于玩家,所以不是AbstractActorModule玩家模块的派生类。它应该和AbstractActorModule同级。
/**
* 移动模块
*
* <pre>
*
* 移动模式:
* - 特殊
* - 徘徊
* - 跳跃
* - 普通
* 速度姿态 x 速度状态:
* - 站立 - 静止
* - 骑马 - 走
* - 游泳 - 跑
* - 载具 - 疾跑
* - 冲刺
*
* </pre>
*
*/
public class CharacterMoveModule extends AbstractCharacterModule
{
/**
* 当前移动模式 实例
*/
private AbstractMoveMode curMoveMode;
/**
* 移动模式 实例组
*/
private AbstractMoveMode[] moveModeArray = new AbstractMoveMode[MoveModeEnum.SIZE];
}
目前移动模块直接继承模块基类AbstractCharacterModule,没有再抽象一层。
CharacterMoveModule会对当前的移动模式curMoveMode进行tick,init()…,等等各种操作。
同时缓存了移动模式实例组,防止不停的new移动模式。
2.移动模式AbstractMoveMode类的继承关系
/**
* 移动模式的基类
*
*/
public abstract class AbstractMoveMode
{
/**
* 移动模块引用
*/
protected CharacterMoveModule moveModule;
/**
* 对应的移动模式枚举
*/
public abstract MoveModeEnum getMoveModeEnum();
/**
* 初始化
*/
public abstract void init();
/**
* 加载后处理
*/
public void afterLoad()
{
}
/**
* tick
*/
public abstract void tick(int interval);
}
缓存了移动模块引用,这里的tick, init…都是在CharacterMoveModule里调用。
/**
* 跳跃移动模式
*
*/
public class MoveModeJump extends AbstractMoveMode
/**
* 移动模式 - 特殊移动
*
*/
public class MoveModeSpecial extends AbstractMoveMode
/**
* 移动模式 - 徘徊
*
*/
public class MoveModeWander extends AbstractMoveMode
/**
* 移动模式 - 普通
*
*/
public class MoveModeNormal extends AbstractMoveMode
对应的各个移动模式,分别实现。
3.总结
CharacterMoveModule模块下,缓存一个当前的抽象的移动模式类,对当前移动模式进行tick等各种操作。
同时,借助工厂类创建当前移动模式。
也就是像我们的小游戏模块,玩家小游戏模块,缓存一个抽象的游戏逻辑操作类,每次对当前的游戏逻辑进行tick等各种操作,本质是一样的。
CharacterMoveModule移动模块一些初始操作
1.init()
MoveModeEnum[] values = MoveModeEnum.values();
for (MoveModeEnum moveModeEnum : values)
{
AbstractMoveMode moveMode = MoveHelper.createMoveMode(moveModeEnum, this);
if (moveMode == null)
{
BPLog.BP_LOGIC.error("【移动错误】移动模块初始化错误,创建移动模式实例错误 [moveModeEnum] {}", moveModeEnum);
return;
}
if (moveMode.getMoveModeEnum() != moveModeEnum)
{
BPLog.BP_LOGIC.error("【移动错误】移动模块初始化错误,创建移动模式实例错误 [moveModeEnum] {}", moveModeEnum);
return;
}
moveModeArray[moveModeEnum.getIndex()] = moveMode;
}
// 初始化上来先是普通移动
this.lastMoveModeEnum = DEFAULT_MOVE_MODE_ENUM;
this.curMoveModeEnum = DEFAULT_MOVE_MODE_ENUM;
this.curMoveMode = getMoveMode(DEFAULT_MOVE_MODE_ENUM);
this.curMoveMode.onEnterMode();
迭代移动模式枚举列表,MoveHelper.createMoveMode()根据工厂模式创建对应的移动模式
缓存到实例组。
初始化上来是普通移动。
单位移动
1.客户端发送单位移动信息
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 bool nearBlock = 6; // 顶着墙行走(true为顶着墙行走,保持跑步状态)
optional int32 moveAttitude = 7; // 移动姿态
optional int32 moveState = 8; // 移动状态
optional int32 moveSeedType = 9; // 速度方式
optional bool needAcc = 10; // 是否加速
optional bool speedChange = 11; // 是否改变了速度
optional int32 passThroughParam = 12; // 透传参数
optional int32 jumpResumeSpeed = 13; // 跳跃落地接移动时的当前速度(只有跳跃落地接移动才发)
values = 5; //x,y,z 坐标依次,移动点组
moveState = 8; // 移动状态
STOP(0), // 静止
WALK(1), // 走
RUN(2), // 跑
GALLOP(3), // 疾跑
SPRINT(4), // 冲刺
moveSeedType = 9; // 速度方式, 分为通用和战斗
根据前端上发的消息,服务端获取对应状态。
普通移动和跳跃落地接移动,都上发此消息。
2.对客户端上发的路径点组做处理
/**
* 客户端移动
*/
public void clientMoveTo(...)
{
// 路径是否过长
int movePathSize = moveList.size();
if (movePathSize > CLT_MOVE_PATH_MAX)
{
moveList = moveList.subList(0, CLT_MOVE_PATH_MAX);
}
// 加工路径
TIntArrayList pathListCahce = getAndClearPathListCache();
CollectionUtils.addAll(pathListCahce,moveList);
}
路径过长,只截取最大的长度路径组CLT_MOVE_PATH_MAX = 32 * 3;
为什么是32 * 3, 以什么为准则考虑的呢?
CollectionUtils.addAll()
/**
* 添加全部(jdk list 转为 trove4j list)
* @param dst 目标 集合
* @param src 源 集合
*/
public static void addAll(TIntArrayList dst,List<Integer> src)
{
if(dst == null || src == null)
{
return;
}
if(src.isEmpty())
{
return;
}
//如果实现了随机访问接口,用循环遍历,效率高
if(src instanceof RandomAccess)
{
for (int i = 0,iSize = src.size();i < iSize; i++)
{
Integer integer = src.get(i);
if(integer == null)
{
continue;
}
dst.add(integer);
}
return;
}
//没有实现随机访问接口,采用迭代器遍历,效率高
for(Iterator<Integer> iterator = src.iterator();iterator.hasNext();)
{
Integer integer = iterator.next();
if(integer == null)
{
continue;
}
dst.add(integer);
}
}
加工路径,jdk list 转为 trove4j list,防止频繁装箱拆箱。
如果实现了随机访问接口,用循环遍历,效率高。
没有实现随机访问接口,也就是类似链表顺序访问,采用迭代器遍历,效率高。
3.实际移动
/**
* 实际移动
*/
private int doMoveTo(int nowX ...)
{
}
// 当前移动是否是特殊,如果是特殊,并且正在特殊移动 就不打断
if (curMoveModeEnum == MoveModeEnum.SPECIAL)
{
if (curMoveMode.isMoving())
{
return BPErrorCodeEnum.MOVE_SPECIAL_MOVING_CANNOT_SWITCH_NORMAL;
}
}
// 当前移动是否是跳跃,如果是跳跃,并且正在跳跃移动
if (curMoveModeEnum == MoveModeEnum.JUMP)
{
if (curMoveMode.isMoving())
{
return BPErrorCodeEnum.MOVE_JUMPING_CANNOT_SWITCH_NORMAL;
}
}
// 能否移动
int rs = character.canMove();
当前移动是否是特殊,如果是特殊,并且正在特殊移动 就不打断。
当前移动是否是跳跃,如果是跳跃,并且正在跳跃移动 不打断。
判断能否移动
// 如果是跳跃落地接移动时的当前速度
// 只有从静止到移动,并且有跳跃恢复速度,才恢复
if(jumpResumeSpeed != null)
{
//校验当前服务端移动状态是否为禁止,客户端上发的移动状态是否不为null并且也是静止状态
if(curStateEnum == MoveStateEnum.STOP && moveState != null && moveState != MoveStateEnum.STOP)
{
BPLog.BP_LOGIC.debug("【跳跃前速度恢复】跳跃落地接普通移动,恢复跳跃前速度 [jumpResumeSpeed] {}",jumpResumeSpeed);
}
else
{
//跳跃接移动恢复移动速度错误,必须是从起步才能恢复
return BPErrorCodeEnum.MOVE_JUMP_RESUME_SPEED_ERROR_STATE;
}
}
如果是跳跃落地接移动时的当前速度,校验当前服务端移动状态是否为禁止,客户端上发的移动状态是否不为null并且也是静止状态。
// 切换到普通移动
if (curMoveModeEnum != MoveModeEnum.NORMAL)
{
if (curMoveMode.isMoving())
{
curMoveMode.breakMove(true);
}
rs = switchMoveMode(MoveModeEnum.NORMAL,false);
if (rs < 0)
{
BPLog.BP_LOGIC.warn("【移动错误】 切换移动模式失败 [objectID] {} [objectType] {} [srcMode] {} [dstMode] {}",
character.getObjectID(), character.getObjectType().name(), curMoveModeEnum.name(), MoveModeEnum.NORMAL.name());
return rs;
}
}
当前如果不是普通移动,切换为普通移动的一些操作。
// 执行移动
rs = moveModeNoraml.moveTo(...)
// 开始移动事件
if (curStateEnum == MoveStateEnum.STOP)
{
onStartMove();
}
character.onMove();
调用移动,执行移动事件
4.moveModeNoraml.moveTo(…)普通移动执行
1.一些校验
// 检测
int rs = moveChecker.checkMove(nowX, nowY, nowZ, pathList, needcheck);
if (rs < 0)
{
return rs;
}
1.客户上发状态不能为停止和移动矛盾。
2.如果已经处于移动状态,客户可以不上发移动状态,同时校验状态枚举是否为stop。
3.检测上发路径列表,检测当前生物是否可走,检测终点和当前点是否可走。
4.当前姿态、状态、速度方式都没有改变,那么前端不能上发此协议。
这里特别注意下检查传入的当前位置是否合法:
/**
* 检查传入的当前位置是否合法
*
* @param nowX
* @param nowY
* @param nowZ
* @param slideId
* @return
*/
private int checkClientNowPosition(int nowX, int nowY, int nowZ, int slideId)
{
...
...
//当前点坐标距离 校验 大于容错值后拉回
if (mdx + mdy > checkValue + distanceCompensate)
{
BPLog.BP_SCENE.warn("【移动拉回】 单位被拉回by距离(xz): [objectID] {} [checkValue] {} [xDiff] {} [zDiff] {} [serverNowPos] ({},{},{}) [clientNowPos] ({},{},{})",
character.getObjectID(), checkValue, mdx, mdy, character.getX(), character.getY(), character.getZ(), nowX, nowY, nowZ);
moveBack(false);
return BPErrorCodeEnum.MOVE_OUT_OF_MAX_VALIDATE_RANGE;
}
...
...
}
当前点坐标距离 校验 大于容错值后拉回, 这个也叫回溯(也就是服务器之间拖拽)。
后期上线,玩家当前移动时突然发生了转向,由于网络延迟导致客户端上发的包服务器接收时间延长,服务器继续按照之前的移动向量移动了一定距离,
客户端上发的路径点就和服务器当前的路径点不一致,那么服务器会采用回溯也就是直接拖拽然后下发告诉客户端。
2.移动前,一些处理
// 移动前,清一些数据
private void onBeforeMove()
{
int pathQueueSize = this.pathQueue.size();
for (int i = 0; i < pathQueueSize; i++)
{
PathNode pathNode = this.pathQueue.pollFirst();
recyclePathNode(pathNode);
}
this.serverStopedFlag = false;
}
回收路径点缓存
// 记录客户端位置
moveChecker.onMoveTo(nowX, nowY, nowZ);
记录客户端位置
3.姿态或状态改变
// 姿态或状态改变
if (moveAttitude != null && moveState != null && speedType != null)
{
changeAttitudeState(moveAttitude, moveState, speedType, resumeSpeedV0);
}
普通移动状态下的姿态或状态改变事件
private void onAttitudeStateChange()
{
speedManager.onAttitudeStateChange();
moveChecker.onAttitudeStateChange();
}
``
一个tick内最大处理姿态改变个数目前为4个
```java
//单帧内 姿态 状态 变化 次数
attitudeStateChangeNumOnTick++;
if(moveStateEnum == MoveStateEnum.STOP)
{
// 如果是停止,那么清空之前记录的改变
this.attitudeStateChangeNumOnTick = 0;
clearAttitudeStateChangeArgs();
}
else
{
// 一帧内改变一次不记录,从两次开始逐个变化记录
if(attitudeStateChangeNumOnTick > 1)
{
// 记录改变
AttitudeStateChange attitudeStateChange = createAttitudeStateChange();
if(attitudeStateChange != null)
{
attitudeStateChange.setAttitudeEnum(lastMoveAttitudeEnum);
attitudeStateChange.setStateEnum(lastMoveStateEnum);
attitudeStateChange.setSpeedTypeEnum(lastMoveSpeedType);
// 切姿态状态时,还未修改速度标志,所以此时的速度标志,可以用作上一次速度标志使用
attitudeStateChange.setNeedAcc(this.needAcc);
this.attitudeStateChangeQueue.offerLast(attitudeStateChange);
}
if(BPLog.BP_LOGIC.isDebugEnabled())
{
AbstractCharacter character = getMoveMode().getMoveModule().getCharacter();
BPLog.BP_LOGIC.debug("【同帧多次切换状态】 [objectID] {} [objetType] {} [attitude] {} [state] {} [speedType] {} [last-attitude] {} [last-state] {} [last-speedType] {} [last-NeedAcc] {} ",
character.getObjectID(),character.getObjectType().name(),moveAttitudeEnum,moveStateEnum,moveSpeedType,lastMoveAttitudeEnum,lastMoveStateEnum,lastMoveSpeedType,this.needAcc);
}
}
}
为什么一帧内如果姿态变化多次需要缓存? 为了处理加速度用
AbstractService类下:
/**
* processor调用的tick,派生类不要实现此方法!!!!!!
* @param interval
*/
public final void tick0(int interval)
{
tickMessage(interval);
tick(interval);
}
目前服务器场景里线程执行逻辑是先处理客户端上发的消息包,再进行服务器场景线程的tick。
由于移动变化频繁,存在客户端连续上发多个改变姿态的包,那么再进行tick就存在一个tick内需要处理多个姿态的情况。
目前,一个tick里最多处理4个姿态改变,不然处理太多姿态切换,处理不过来了,影响tick效率。
所以,同一个tick里上发了多个姿态包,需要对姿态进行缓存,统一在一个tick里进行处理。
// 姿态状态变化带来的速度变化,立即改变
setSpeedChangeFlag();
setSpeedChangeConsumeFlag();
private void setSpeedChangeFlag()
{
this.speedChangeFlag = true;
if(speedStrategy.needConsumeSpeedChange())
{
this.speedChangeConsumeRemain = SPEED_CHANGE_CONSUME_REMAIN_ALL;
}
else
{
setSpeedChangeConsumeFlag();
}
}
姿态和状态改变速设置相关状态,然后tickSpeedChange时候计算速度
有些速度改变后,需要消费标志才改变speedChangeConsumeRemain
if (this.moveAttitudeEnum != null)
{
if (moveAttitude == MoveAttitudeEnum.SWIM && this.lastMoveAttitudeEnum != MoveAttitudeEnum.SWIM)
{
// 切换至游泳状态
AbstractCharacter character = getMoveModule().getCharacter();
character.onStartSwim();
}
}
if (this.moveStateEnum != null)
{
if (moveState == MoveStateEnum.GALLOP && this.lastMoveStateEnum != MoveStateEnum.GALLOP)
{
// 开始疾跑
AbstractCharacter character = getMoveModule().getCharacter();
character.onStartGallop();
}
}
切换,游泳姿态和疾跑姿态。
4.添加路径点
// 添加路点
AbstractCharacter character = getMoveModule().getCharacter();
if (nowX != character.getX() || nowZ != character.getZ())
{
PathNode pathNode = createPathNode();
pathNode.setXYZ(nowX, nowY, nowZ);
this.pathQueue.offerLast(pathNode);
}
int size = pathList.size();
int loop = size / 3;
for (int i = 0; i < loop; i++)
{
int x = pathList.get(i * 3);
int y = pathList.get(i * 3 + 1);
int z = pathList.get(i * 3 + 2);
PathNode pathNode = createPathNode();
pathNode.setXYZ(x, y, z);
this.pathQueue.offerLast(pathNode);
}
添加移动路径点
5.移动开始要执行走下一个路点
goNextPathNode();
从移动路径中取出下一个点,判断服务器是否到达终点,
如果未达到终点,设置目标点,触发下一个路点事件。
/**
* 下一个路点事件
*/
private void onGoNextPathNode()
{
int x = targetPos.getX();
int y = targetPos.getY();
int z = targetPos.getZ();
speedManager.onGoNextPathPoint(x, y, z);
moveChecker.onGoNextPathPoint(x, y, z);
// TODO fixme xxx
moveModule.getCharacter().onGriMoveTo(x, y, z);
}
取出目标点x,y,z
我们看speedManager.onGoNextPathPoint(x, y, z);
public void countVector(int x, int y, int z)
{
CharacterMoveModule moveModule = getMoveMode().getMoveModule();
AbstractCharacter character = moveModule.getCharacter();
int nowX = character.getX();
int nowY = character.getY();
int nowZ = character.getZ();
//差值
float dx = x - nowX;
float dy = y - nowY;
float dz = z - nowZ;
//距离平方
double mDisSq = dx * dx + dz * dz;
//距离
float mDis = (float) Math.sqrt(mDisSq);
mDis = (mDis < 0.01 ? 1 : mDis);
// 单位方向向量(向量长度 / 模 , 也就是两点之间的差值除以两点之间的距离)
unitVector.setX(dx / mDis);
unitVector.setY(dy / mDis);
unitVector.setZ(dz / mDis);
// 单位速度向量(单位方向向量 / 每毫秒移动长度)
millisVector.setX(unitVector.getX() * this.milliDistance);
millisVector.setY(unitVector.getY() * this.milliDistance);
millisVector.setZ(unitVector.getZ() * this.milliDistance);
}
注意下单位方向向量和单位速度向量的计算
每毫秒移动长度向量计算
if(this.speedV0 == this.speedVt)
{
// 如果是终速度了,直接用终速度
this.milliDistance = this.speedV0 * 1.f / TimeUtils.MILLIS_PER_SECOND;
}
else
{
// 用 V0 和 V0' 计算的 V avg
this.milliDistance = (oldV0 + this.speedV0) / 2.0f / TimeUtils.MILLIS_PER_SECOND;
}
millisVector.setX(unitVector.getX() * this.milliDistance);
millisVector.setY(unitVector.getY() * this.milliDistance);
millisVector.setZ(unitVector.getZ() * this.milliDistance);
这里需要注意,根据当前速度和下一速度计算的每毫秒移动长度向量milliDistance
5.tick处理
@Override
public void tick(int interval)
{
speedManager.tick(interval);
moveChecker.tick(interval);
tickSendMessage();
tickMove(interval);
}
1.speedManager.tick()速度管理器下的处理
1.tickSpeedChange(interval) 速度改变计算终速度
主要逻辑:
/**
* 计算改变后的终速度
*/
private void dealSpeedChange()
{
}
姿态改变会影响终速度,speedVt
// 基础部分 获取对应属性值
if (moveAttitudeEnum == MoveAttitudeEnum.SWIM)
{
result.setMoveSpeedA(attributeLogic.getAttribute(AttributeTypeEnum.SWIM_MOVE_SPEED_A));
result.setMoveSpeedB(attributeLogic.getAttribute(AttributeTypeEnum.SWIM_MOVE_SPEED_B));
result.setMoveSpeedC(attributeLogic.getAttribute(AttributeTypeEnum.SWIM_MOVE_SPEED_C));
result.setMoveSpeedD(attributeLogic.getAttribute(AttributeTypeEnum.SWIM_MOVE_SPEED_D));
result.setMoveSpeedE(attributeLogic.getAttribute(AttributeTypeEnum.SWIM_MOVE_SPEED_E));
}
else
{
result.setMoveSpeedA(attributeLogic.getAttribute(AttributeTypeEnum.MOVE_SPEED_A));
result.setMoveSpeedB(attributeLogic.getAttribute(AttributeTypeEnum.MOVE_SPEED_B));
result.setMoveSpeedC(attributeLogic.getAttribute(AttributeTypeEnum.MOVE_SPEED_C));
result.setMoveSpeedD(attributeLogic.getAttribute(AttributeTypeEnum.MOVE_SPEED_D));
result.setMoveSpeedE(attributeLogic.getAttribute(AttributeTypeEnum.MOVE_SPEED_E));
}
// 系统/战斗部分 现在类似骑马改变速度都走的是这里
TIntHashSet speedChangeIndexes = moveModule.getSpeedChangeIndexes();
if (!CollectionUtils.isBlank(speedChangeIndexes))
{
for (TIntIterator iterator = speedChangeIndexes.iterator(); iterator.hasNext(); )
{
int speedChangeIndex = iterator.next();
DictMoveSpeedChange dict = DictMoveSpeedChange.getRecordById(speedChangeIndex);
if (dict == null)
{
BPLog.BP_LOGIC.warn("【移动错误】移动速度处理错误moveSpeedChange索引找不到对应的配置 [] {}", speedChangeIndex);
continue;
}
MoveHelper.addenMoveSpeedChange(dict,moveAttitudeEnum,moveStateEnum,moveSpeedType,result);
}
}
获取属性,然后计算系统和战斗部分的加成或减益。
// 需要处理 一帧内切换多个姿态状态的处理
if(needAcc)
{
if(!attitudeStateChangeQueue.isEmpty())
{
//迭代 当前帧内姿态状态变化集合
for(Iterator<AttitudeStateChange> iterator = attitudeStateChangeQueue.iterator();iterator.hasNext();)
{
// 计算本帧内之前的速度改变的结果
AttitudeStateChange attitudeStateChange = iterator.next();
paramCache.clear();
//拷贝一份临时速度参数(传入方法计算用)
MoveSpeedChangeParam attitudeStateResult = paramCache;
attitudeStateResult.copyValue(result);
speedStrategy.calcLastAttitudeStateSpeed(attitudeStateChange.getAttitudeEnum(),attitudeStateChange.getStateEnum(),attitudeStateChange.getSpeedTypeEnum(),attitudeStateResult);
// 速度
if (attitudeStateResult.getLockSpeedValue() > 0)
{
//如果有锁定速度值,那么终速度设置为锁定的速度
attitudeStateChange.setSpeedVt(attitudeStateResult.getLockSpeedValue());
}
else
{
// V = [ ( A * B + C ) * D ] + E;
double mainValue = (attitudeStateResult.getMoveSpeedA() * (attitudeStateResult.getMoveSpeedB() / GameConstant.TEN_THOUSAND_DOUBLE) + attitudeStateResult.getMoveSpeedC()) * (attitudeStateResult.getMoveSpeedD() / GameConstant.TEN_THOUSAND_DOUBLE) + attitudeStateResult.getMoveSpeedE();
//计算终速度
if(mainValue > 0)
{
attitudeStateChange.setSpeedVt((int) mainValue);
}
}
// 加速度
attitudeStateChange.setAcc(attitudeStateResult.getAcc());
attitudeStateChange.setDec(attitudeStateResult.getDec());
if(BPLog.BP_LOGIC.isDebugEnabled())
{
BPLog.BP_LOGIC.debug("【同帧多次切换 终速度改变】 [objectID] {} [objetType] {} [ever-attitude] {} [ever-state] {} [ever-speedType] {} [spedVt] {} [acc] {} [dec] {} [needAcc] {}",
character.getObjectID(),character.getObjectType().name(),attitudeStateChange.getAttitudeEnum(),attitudeStateChange.getStateEnum(),attitudeStateChange.getSpeedTypeEnum()
,attitudeStateChange.getSpeedVt(),attitudeStateChange.getAcc(),attitudeStateChange.getDec(),attitudeStateChange.isNeedAcc());
}
}
}
}
如果需要加速处理(有些姿态过度需要加速,例如疾跑),迭代 当前帧内姿态状态变化集合。
//将速度辩护表格 累积到 总速度变化中,其实就是根据MoveSpeedChange辩护表格,做相应的姿态改变处理
speedStrategy.calcLastAttitudeStateSpeed(attitudeStateChange.getAttitudeEnum(),attitudeStateChange.getStateEnum(),attitudeStateChange.getSpeedTypeEnum()
这里计算的是attitudeStateChangeQueue姿态列表里,每个姿态的终止速度。会设置一帧内多个姿态状态的终止速度。
会在tickSpeedAccelerate(int interval)里处理。
// 速度
if (result.getLockSpeedValue() > 0)
{
this.speedVt = result.getLockSpeedValue();
}
else
{
// V = [ ( A * B + C ) * D ] + E;
double mainValue = (result.getMoveSpeedA() * (result.getMoveSpeedB() / GameConstant.TEN_THOUSAND_DOUBLE) + result.getMoveSpeedC()) * (result.getMoveSpeedD() / GameConstant.TEN_THOUSAND_DOUBLE) + result.getMoveSpeedE();
// 范围检测
if (mainValue < 0)
{
BPLog.BP_LOGIC.warn("【移动错误】移动速度计算结果小于零! [objectID] {} [objectType] {} [moveSpeed] {} [moveA] {} [moveB] {} [moveC] {} [moveD] {} [moveE] {}",
character.getObjectID(),character.getObjectType().name(),mainValue,result.getMoveSpeedA(),result.getMoveSpeedB(),result.getMoveSpeedC(),result.getMoveSpeedD(),result.getMoveSpeedE());
return;
}
else if(mainValue == 0)
{
if(!character.isAllowSpeedZero())
{
BPLog.BP_LOGIC.warn("【移动错误】移动速度计算结果等于零! [objectID] {} [objectType] {} [moveSpeed] {} [moveA] {} [moveB] {} [moveC] {} [moveD] {} [moveE] {}",
character.getObjectID(),character.getObjectType().name(),mainValue,result.getMoveSpeedA(),result.getMoveSpeedB(),result.getMoveSpeedC(),result.getMoveSpeedD(),result.getMoveSpeedE());
return;
}
}
this.speedVt = (int) mainValue;
}
速度是否锁定,计算终速度。
2.tickSpeedAccelerate()处理速度加速
//当前速度
int oldV0 = speedV0;
// 计算速度
if (needAcc)
{
// 需要处理 一帧内切换多个姿态状态的处理
if(!attitudeStateChangeQueue.isEmpty())
{
for (Iterator<AttitudeStateChange> iterator = attitudeStateChangeQueue.iterator(); iterator.hasNext(); )
{
// 计算本帧内之前的速度改变的结果
AttitudeStateChange attitudeStateChange = iterator.next();
boolean lastNeedAcc = attitudeStateChange.isNeedAcc();
//终速度
int lastSpeedVt = attitudeStateChange.getSpeedVt();
int lastAcc = attitudeStateChange.getAcc();
int lastDec = attitudeStateChange.getDec();
if(lastSpeedVt <= 0)
{
continue;
}
if (speedV0 == lastSpeedVt)
{
continue;
}
if(!lastNeedAcc)
{
//不需要加速,那么当前速度直接等于终速度
speedV0 = lastSpeedVt;
if(BPLog.BP_LOGIC.isDebugEnabled())
{
AbstractCharacter character = getMoveMode().getMoveModule().getCharacter();
BPLog.BP_LOGIC.debug("【同帧多次切换 速度计算】 [objectID] {} [objetType] {} [计算v0] {} [ever-attitude] {} [ever-state] {} [ever-speedType] {} [spedVt] {} [acc] {} [dec] {} [needAcc] {}",
character.getObjectID(),character.getObjectType().name(),this.speedV0,attitudeStateChange.getAttitudeEnum(),attitudeStateChange.getStateEnum(),attitudeStateChange.getSpeedTypeEnum()
,attitudeStateChange.getSpeedVt(),attitudeStateChange.getAcc(),attitudeStateChange.getDec(),attitudeStateChange.isNeedAcc());
}
continue;
}
//需要加速度,上一次的终速度是否大于当前速度,大于这为加速度,小于则为减速
int lastA = lastSpeedVt > speedV0 ? lastAcc : lastDec;
if(lastA == 0)
{
//说明切换姿态,但是没有导致速度变化
this.speedV0 = lastSpeedVt;
if(BPLog.BP_LOGIC.isDebugEnabled())
{
AbstractCharacter character = getMoveMode().getMoveModule().getCharacter();
BPLog.BP_LOGIC.debug("【同帧多次切换 速度计算】 [objectID] {} [objetType] {} [计算v0] {} [ever-attitude] {} [ever-state] {} [ever-speedType] {} [spedVt] {} [acc] {} [dec] {} [needAcc] {}",
character.getObjectID(),character.getObjectType().name(),this.speedV0,attitudeStateChange.getAttitudeEnum(),attitudeStateChange.getStateEnum(),attitudeStateChange.getSpeedTypeEnum()
,attitudeStateChange.getSpeedVt(),attitudeStateChange.getAcc(),attitudeStateChange.getDec(),attitudeStateChange.isNeedAcc());
}
continue;
}
// 时间用完,继续下一个,有可能下一个状态是直接设置速度
if(interval < 0)
{
continue;
}
//两个数之间的 绝对值差
int speedDiff = MathUtils.absDiff(speedV0, lastSpeedVt);
//计算加速或减速到当前速度的消耗时间
float costSec = speedDiff * 1.0f / Math.abs(lastA);
//转换成毫秒
int costMillis = (int)(costSec * TimeUtils.MILLIS_PER_SECOND);
if(interval >= costMillis)
{
//扣除消耗的时间,并设置速度
interval -= costMillis;
this.speedV0 = lastSpeedVt;
if(BPLog.BP_LOGIC.isDebugEnabled())
{
AbstractCharacter character = getMoveMode().getMoveModule().getCharacter();
BPLog.BP_LOGIC.debug("【同帧多次切换 速度计算】 [objectID] {} [objetType] {} [计算v0] {} [ever-attitude] {} [ever-state] {} [ever-speedType] {} [spedVt] {} [acc] {} [dec] {} [needAcc] {}",
character.getObjectID(),character.getObjectType().name(),this.speedV0,attitudeStateChange.getAttitudeEnum(),attitudeStateChange.getStateEnum(),attitudeStateChange.getSpeedTypeEnum()
,attitudeStateChange.getSpeedVt(),attitudeStateChange.getAcc(),attitudeStateChange.getDec(),attitudeStateChange.isNeedAcc());
}
}
else
{
interval = 0;
//转成秒 todo todo 这个interval肯定是0啊,那下面的计算没有意义吧?
float secondT = interval * 1.0f / TimeUtils.MILLIS_PER_SECOND;
//当前速度等于 = 当前速度 + 加速或减速 * 经过的时间
this.speedV0 = (int) (speedV0 + lastA * secondT);
if(BPLog.BP_LOGIC.isDebugEnabled())
{
AbstractCharacter character = getMoveMode().getMoveModule().getCharacter();
BPLog.BP_LOGIC.debug("【同帧多次切换 速度计算】 [objectID] {} [objetType] {} [计算v0] {} [ever-attitude] {} [ever-state] {} [ever-speedType] {} [spedVt] {} [acc] {} [dec] {} [needAcc] {}",
character.getObjectID(),character.getObjectType().name(),this.speedV0,attitudeStateChange.getAttitudeEnum(),attitudeStateChange.getStateEnum(),attitudeStateChange.getSpeedTypeEnum()
,attitudeStateChange.getSpeedVt(),attitudeStateChange.getAcc(),attitudeStateChange.getDec(),attitudeStateChange.isNeedAcc());
}
}
}
}
// 开始处理本帧操作
if (speedV0 == speedVt)
{
return;
}
int a = this.speedVt > speedV0 ? acc : dec;
if (a == 0)
{
this.speedV0 = speedVt;
}
else
{
// interval 可能为零,因为前面处理一帧内切多次状态可能用掉时间
if(interval > 0)
{
float secondT = interval * 1.0f / TimeUtils.MILLIS_PER_SECOND;
this.speedV0 = (int) (speedV0 + a * secondT);
}
if(a > 0)
{
if (this.speedV0 > this.speedVt)
{
this.speedV0 = this.speedVt;
}
}
else
{
if (this.speedV0 < this.speedVt)
{
this.speedV0 = this.speedVt;
}
}
}
}
else
{
//不用处理加速度的话,那么当前速度直接等于终止速度
this.speedV0 = speedVt;
}
//如果处理加速度,那么可能导致当前速度和终止速度不一样
//如果不用理加速度,那么当前速度和终止速度相同
// 计算每毫秒移动长度
if(this.speedV0 == this.speedVt)
{
// 如果是终速度了,直接用终速度
this.milliDistance = this.speedV0 * 1.f / TimeUtils.MILLIS_PER_SECOND;
}
else
{
// 用 V0 和 V0' 计算的 V avg
this.milliDistance = (oldV0 + this.speedV0) / 2.0f / TimeUtils.MILLIS_PER_SECOND;
}
//每毫秒移动长度向量 = 单位向量 * 每毫秒移动长度
millisVector.setX(unitVector.getX() * this.milliDistance);
millisVector.setY(unitVector.getY() * this.milliDistance);
millisVector.setZ(unitVector.getZ() * this.milliDistance);
大体就是流程:
1.如果是不需要处理加速或减速问题,那么当前速度直接等于终速度。
2.需要处理加速问题,需要处理一帧内切换多个姿态状态的处理(计算每次的最终速度)。
3.处理本帧的速度。
4.最后计算每毫秒移动长度向量 (每毫秒移动长度向量 = 单位向量 * 每毫秒移动长度)。
2.tickSendMessage()发送移动消息
3.tickMove(interval)处理移动
private void tickMove(int delay)
{
...
...
AbstractCharacter character = moveModule.getCharacter();
//每毫秒移动长度向量
Vector3 millisVector = speedManager.getMillisVector();
//朝向单位向量
Vector3 unitVector = speedManager.getUnitVector();
//每毫秒移动长度
float millisDistance = speedManager.getMilliDistance();
//当前的float坐标
float sx = character.getFX();
float sy = character.getFY();
float sz = character.getFZ();
//差值
float dx = targetPos.getX() - sx;
float dz = targetPos.getZ() - sz;
// 距离平方
float mDisSq = dx * dx + dz * dz;
//当前tick行走的距离
float dis = delay * millisDistance;
//行走的距离需要加上一次补偿的值
dis += saveDistance;
//行走距离的平方
float disSq = dis * dis;
boolean reach = false;
//到达(行走距离的平方 >= 下一目标点距离平方)
if (disSq >= mDisSq)
{
//距离下一个目标的值
float mDis = (float) Math.sqrt(mDisSq);
//缓存多走的移动距离,因为过了一个tick时间,玩家从开始移动的距离超出到目标点的距离。
//我们缓存多行走的距离,下一次tick到下一个目标格子需要加上多行走的距离。
saveDistance = dis - mDis;
reach = true;
character.setPos(targetPos.getX(), targetPos.getY(), targetPos.getZ());
}
//未到达
else
{
float x = sx + millisVector.getX() * delay;
float y = sy + millisVector.getY() * delay;
float z = sz + millisVector.getZ() * delay;
if (saveDistance > 0)
{
//上一个tick到达目标点时行走的距离已超出,所以要加上上一次多走的距离
x += saveDistance * unitVector.getX();
y += saveDistance * unitVector.getY();
z += saveDistance * unitVector.getZ();
}
//清空缓存的多走的距离
saveDistance = 0;
//直接设置目标
character.setPos(x, y, z);
}
if (reach)
{
// 到达事件
onReachPathNode();
// 走下一个点
goNextPathNode();
}
...
...
}
这里和老版移动逻辑基本没变:
这里要特别注意saveDistance这个缓存变量,缓存多走的移动距离,因为过了一个tick时间,玩家从开始移动的距离超出到目标点的距离。
我们缓存多行走的距离,下一次tick到下一个目标格子需要加上多行走的距离。
其它的注释上写的很清楚了,看注释。
主要就是计算经过一个tick时间当前生物移动的距离是否到达目标点,不管到达不到打,都会通过character.setPos(x, y, z),更新当前。
到此,我们新版核心的速度处理计算,已经基本学习了一遍了。
姿态改变
1.坐骑
/**
* 上坐骑通用处理:加速度,刷新外观,重置数据
*
* @param rideId 坐骑id
* @return 结果
*/
private int rideUpCommonHandle(int rideId)
{
}