新版移动系统

2020/06/02 Mmo-Game

新版移动系统学习记录

目录

整体结构

移动目前分为几个模式:

徘徊模式,特殊模式,跳跃模式,普通模式

普通模式又有姿态:

站立姿态,游泳姿态,骑马姿态,载具姿态

普通模式又有移动状态:

静止,走,跑,疾跑,冲刺

类的继承关系

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 doMoveToint 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)
{

}

Search

    Table of Contents