ET服务器框架学习笔记(十七)
文章目录
前言
上篇分析了关于ET服务器的服务(进程)管理器。本篇则分析ET服务器框架里面的寻路系统
一、PathfindingComponent
ET寻路客户端与服务器都使用的一套寻路方式。本篇只梳理ET有关寻路的,具体的寻路算法,比较复杂不是一两篇就能介绍完毕的,能用起来即可。
1.PathReturnQueue
简单来说他保存了所有路径,一个PathfindingComponent对应有且只有一个PathReturnQueue。
- pathReturnQueue:一个Path队列,用于存放所有需要寻路的路径
- pathsClaimedSilentlyBy:Path声明时用到的对象,防止Path被回收
2.PathProcessor
路径处理器,将上面实例化的PathReturnQueue传入进去,当作要处理的路径队列。同时声明需要的处理器数量,以及是否开启多线程。在ET中处理器指定为1个,并且不开启多线程。
- ThreadControlQueue:处理器关联的一个方法,专门用于管理队列中的Path,主要就是提供Path之间关联的管理,不对Path内部做处理。
- GraphNode:图形节点,对应于寻路数据中的各个节点,包含各种节点信息
- PathNode:路径节点,包含图形节点,同时会附带PathNode parent:父节点,最最要的是包含了G,H,F,这些值分别代表,G:一条路径中,从起点移动到这个点要花的耗费,H:从这个点到目标点预估的耗费,预估的算法很多(曼哈顿,弗洛伊德,等等算法),F:G+H算是一个综合评定。有了这些耗费计算,才能从所有节点中找到最优解,即最短路等相关路径节点。
- PathHandler:用于管理一个PathNode数组,将它们形成一条路径
- PathProcessor的CalculatePaths方法,从之前的路径队列中取出一条,然后调用各个路径的CalculateStep方法(这个根据不同的路径,有各个路径自己的方式)。
3.AStarConfig
A星寻路使用的配置,主要用于从地图数据文件中读取地图文件,同时包含了
4.ABPathWrap
主要用于生成一个ABPath
示例:pandas 是基于NumPy 的一种工具,该工具是为了解决数据分析任务而创建的。
5.Search
通过传入的ABPath,调用PathProcessor的CalculatePaths,向ABPath中添加PathNode节点数量。
6.PathModifyHelper
PathModifyHelper用于对产生后的路径进行优化处理。
StartEndModify:有时候寻路可能只有一个点,在进行移动的时候,必须要一个起点和一个终点,这个方法用于只有1个点时,复制一个点出来。同时将起点设置为初始点,因为寻路只会判定后面要经过点插入到路径中,这样当移动的时候,可能在第一步会瞬移一下。
FunnelModify:用于优化路径,通过寻路获得的路径,会获取到所有经过的路径点。真正的寻路时不需要这么多寻路点。只需要几个拐点就可以真正的寻路。这个方法首先将路径分成几段,然后在每一段中筛掉那些被包含的点,这样每一段就只有拐点存在,然后再添加到最终路径中去。
二、使用步骤
1.获取路径
根据Unit当前位置,传入目标位置,生成一条AB路径。
Unit unit = self.GetParent<Unit>();
PathfindingComponent pathfindingComponent = Game.Scene.GetComponent<PathfindingComponent>();
self.ABPath = ComponentFactory.Create<ABPathWrap, Vector3, Vector3>(unit.Position, new Vector3(target.x, target.y, target.z));
pathfindingComponent.Search(self.ABPath);
Log.Debug($"find result: {self.ABPath.Result.ListToString()}");
2.开启异步移动
这里使用了CancellationTokenSource,当完成整个移动时,唤醒异步,会取消掉CancellationTokenSource,设置为null。如果没完成移动,又来一条新的移动命令,则调用self.CancellationTokenSource?.Cancel();
取消之前的异步移动,再开启新的异步移动。
self.CancellationTokenSource?.Cancel();
self.CancellationTokenSource = new CancellationTokenSource();
await self.MoveAsync(self.ABPath.Result);
self.CancellationTokenSource.Dispose();
self.CancellationTokenSource = null;
3.UnitPathComponent
- MoveAsync方法:核心思想就是遍历路径,取出一个点,当作目标点,同时每经过3个点,将这个unit的位置给广播出去,拿到取出的点调用移动组件MoveComponent的异步MoveToAsync。
// 第一个点是unit的当前位置,所以不用发送
for (int i = 1; i < path.Count; ++i)
{
// 每移动3个点发送下3个点给客户端
if (i % 3 == 1)
{
self.BroadcastPath(path, i, 3);
}
Vector3 v3 = path[i];
await self.Entity.GetComponent<MoveComponent>().MoveToAsync(v3, self.CancellationTokenSource.Token);
}
4.MoveComponent
- MoveToAsync:设置目标点,await异步调用StartMove
- StartMove:因为已经设置过目标点。核心思想就是,先根据移动速度,算出来要移动到目标点,需要多长时间结束,然后while(true),增加一个异步时间延时,每50毫秒进行一次判定,根据当前时间,与开始时间,与结束时间进行计算,然后用一个Lerp函数,改变目标位置。
需要主意点:当异步取消时,也需要立即通过取消的时间点,算出一个位置,并赋值给玩家,这样不会显得移动不平滑。
Unit unit = this.GetParent<Unit>();
this.StartPos = unit.Position;
this.StartTime = TimeHelper.Now();
float distance = (this.Target - this.StartPos).magnitude;
if (Math.Abs(distance) < 0.1f)
{
return;
}
this.needTime = (long)(distance / this.Speed * 1000);
TimerComponent timerComponent = Game.Scene.GetComponent<TimerComponent>();
// 协程如果取消,将算出玩家的真实位置,赋值给玩家
cancellationToken.Register(() =>
{
long timeNow = TimeHelper.Now();
if (timeNow - this.StartTime >= this.needTime)
{
unit.Position = this.Target;
}
else
{
float amount = (timeNow - this.StartTime) * 1f / this.needTime;
unit.Position = Vector3.Lerp(this.StartPos, this.Target, amount);
}
});
while (true)
{
await timerComponent.WaitAsync(50, cancellationToken);
long timeNow = TimeHelper.Now();
if (timeNow - this.StartTime >= this.needTime)
{
unit.Position = this.Target;
break;
}
float amount = (timeNow - this.StartTime) * 1f / this.needTime;
unit.Position = Vector3.Lerp(this.StartPos, this.Target, amount);
}
总结
这一篇简单理解一下ET的寻路,由于内部包含的第三方寻路插件,很多东西都没有涉及到,后续需要详细研究的时候再看,先直接看下ET怎么使用吧。