ET服务器框架学习笔记(十七)

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怎么使用吧。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值