Mosaic Game 中的寻路系统

mosaic_game的寻路方案完全基于RecastNavigation, 不过为了适应大世界的精度需求使用了double来替换原来的float,修改后的仓库见https://github.com/huangfeidian/recastnavigation。基于此项目的RecastDemo导出的NavMesh文件需要放到工程目录的/data/map/navmesh文件夹下,而对应的Obj文件则需要放到/data/map/wavefront文件夹下,指定编号的场景对应的地形文件和寻路文件映射则需要在/data/xlsx/场景表.xlsx中进行配置。每个Space都有一个对应的space_navi_component, 当一个Space被创建之后,space_navi_componentactivate函数则会向map_server请求创建一个对应的群体寻路管理器:

void space_navi_component::activate()
{
	json cur_msg, cur_param;
	cur_msg["cmd"] = "request_create_navi_crowd";
	cur_param["anchor"] = *m_owner->get_call_proxy();
	cur_param["resource_path"] = m_navi_map;
	cur_param["max_agent_num"] = m_navi_agent_num;
	cur_param["max_agent_radius"] = m_max_agent_radius;
	cur_param["extend"] = m_half_extend;
	cur_msg["param"] = std::move(cur_param);
	m_owner->call_map_server(cur_msg);
}

这里传递的参数都在space_navi_component::init的时候从对应的配置项中读取:

bool space_navi_component::init(const json& data)
{
	
	auto cur_space_sysd = m_owner->space_sysd();

	if(!cur_space_sysd.expect_value("navi_map", m_navi_map))
	{
		m_owner->logger()->error("cant find navi_map for space {} with value {}", m_owner->space_no(), cur_space_sysd.get_cell("navi_map").dump());
		return false;
	}
	if(!cur_space_sysd.expect_value("navi_agent_num", m_navi_agent_num))
	{
		m_owner->logger()->error("cant find navi_agent_num for space {}", m_owner->space_no());
		return false;
	}
	if(!cur_space_sysd.expect_value("navi_half_extend", m_half_extend))
	{
		m_owner->logger()->error("cant find navi_half_extend for space {}", m_owner->space_no());
		return false;
	}
	if(!cur_space_sysd.expect_value("navi_agent_max_radius", m_max_agent_radius))
	{
		m_owner->logger()->error("cant find navi_agent_max_radius for space {}", m_owner->space_no());
		return false;
	}
	m_handler_to_entities = std::vector<actor_entity*>(m_navi_agent_num+1, nullptr);
	
	return true;
}

map_server接收到创建新场景的群体寻路管理器时,并不会每个场景都尝试加载对应的NavMesh文件到内存中,而是会先判断对应的NavMesh是否已经被加载了,如果加载了则复用这份const dtNavMesh*来创建群体寻路管理器,如果没有加载则再尝试加载,通过寻路资源复用来减少内存占用。

resource_path = m_map_config.navi_resource_folder +"/"+ resource_path;
if(!std::filesystem::exists(resource_path))
{
	m_logger->error("on_request_create_navi_crowd cant find map {} ", resource_path);
	return;
}
auto loaded_nav_iter = m_loaded_navi_maps.find(resource_path);
if(loaded_nav_iter == m_loaded_navi_maps.end()) // 如果没有加载
{
	auto loading_nav_iter = m_pending_load_navmesh.find(resource_path);
	if(loading_nav_iter == m_pending_load_navmesh.end())// 如果不在加载过程中
	{
		// 执行真正的加载
		add_task_to_async_loop([this, resource_path]()
		{
			begin_load_navi_map(resource_path);
		});
		// 同时记录同时有多少个请求在等待这个寻路资源
		m_pending_load_navmesh[resource_path].push_back(msg);
	}
	else
	{
		// 添加当前消息到等待队列
		loading_nav_iter->second.push_back(msg);
	}
}
else
{
	if(!loaded_nav_iter->second)
	{
		m_logger->error("on_request_create_navi_crowd invalid map {}", resource_path);
		return;
	}
	// 给每个群体寻路管理器分配一个唯一id
	auto cur_crowd_index = ++m_navi_crowd_counter;
	auto cur_nav_crowd = std::make_shared<navi_crowd>(this, cur_crowd_index, loaded_nav_iter->second, max_agent_num, max_agent_radius, space_anchor);
	m_navi_crowds[cur_crowd_index] = cur_nav_crowd;
	utility::rpc_msg cur_msg;
	cur_msg.cmd = "reply_create_navi_map";
	cur_msg.set_args(cur_crowd_index); // 将这个id返回给space 后续的通信需要带上这个crowd index
	call_entity(cur_nav_crowd->m_space_anchor, cur_msg);
	finish_update_navi_crowd(cur_nav_crowd->m_crowd_index); // 将当前的navi_crowd放到等待更新的队列中
	return;
}

然后map_server的每帧更新中,都会处理固定数量的navi_crowd的更新:

void map_server::on_new_frame()
{
	json_server::on_new_frame();
	update_navi_crowds();
}
void map_server::update_navi_crowds()
{
	std::uint32_t i = 10;
	auto cur_ts = utility::timer_manager::now_ts();
	while(i > 1 && !m_navi_crowd_update_queue.empty())
	{
		i--;
		if(m_navi_crowd_update_queue.front().second > cur_ts)
		{
			// 当前task还没到执行时间 后续的task也不需要继续处理了
			return;
		}
		auto temp_navi_crowd_index = m_navi_crowd_update_queue.front().first;
		m_navi_crowd_update_queue.pop();
		auto temp_iter = m_navi_crowds.find(temp_navi_crowd_index);
		if(temp_iter == m_navi_crowds.end())
		{
			continue;;
		}
		auto temp_crowd = temp_iter->second;
		add_task_to_async_loop([this, temp_crowd]()
		{
			temp_crowd->update();
			add_task_to_main_loop([this, temp_crowd]()
			{
				finish_update_navi_crowd(temp_crowd->m_crowd_index);
			});
		}, temp_crowd->m_crowd_index);
	}
}

上面的add_task_to_async_loop会将一个任务扔到异步线程池中的一个线程中执行,同时为了保证同一个navi_crowd的所有操作都是在同一个线程中执行的,这里需要传递m_crowd_index来指定线程:

void basic_stub::add_task_to_async_loop(std::function<void()>&& cur_task, std::uint64_t channel_index)
{
	if(m_async_task_channels.empty())
	{
		m_logger->error("add_task_to_async_loop fail  task channel is empty");
		return;
	}
	if(m_async_task_channels.size() == 1)
	{
		channel_index = 0;
	}
	else
	{
		if(channel_index == 0)
		{
			channel_index = gen_unique_uint64() % m_async_task_channels.size();
		}
		else
		{
			channel_index = channel_index % m_async_threads.size();
		}
	}
	
	m_async_task_channels[channel_index].push_msg(std::forward<std::function<void()>>(cur_task));
}

如果外部传递的索引是0,则代表这个任务不需要与其他任务串行化,内部则随机指定一个线程即可。

当单个的navi_crowd执行完成之后,需要将这个navi_crowd重新挂载到待更新的队列中,由于这个队列只在主线程维护,因此使用了add_task_to_main_loop来操作这个线程安全队列:

void basic_stub::add_task_to_main_loop(std::function<void()>&& cur_task)
{
	m_main_loop_tasks.push_msg(std::forward<std::function<void()>>(cur_task));
}

在主线程的main_loop中会主动调用poll_mainloop_tasks去获取m_main_loop_tasks里的任务去执行:

std::size_t basic_stub::poll_mainloop_tasks()
{
	const static std::uint32_t batch_task_num  =10;
	std::array<std::function<void()>, batch_task_num> temp_tasks;
	std::uint64_t pop_get_num = 0;
	std::uint64_t total_pop_num = 0;
	while((pop_get_num = m_main_loop_tasks.pop_bulk_msg(temp_tasks.data(), batch_task_num)))
	{
		total_pop_num += pop_get_num;
		for(std::uint32_t i = 0; i< pop_get_num; i++)
		{
			temp_tasks[i]();
		}
	}
	return total_pop_num;
}

space_navi_component发起创建群体寻路管理器的请求到接收到map_server的回应期间,所有actor_entity寻路相关的请求都会被缓存起来,当收到map_server的创建群体寻路管理器回答之后,再将这些请求发送到map_server上:

void space_navi_component::reply_create_navi_map(const utility::rpc_msg& msg, const std::uint64_t navi_map_handler)
{
	m_navi_map_handler = navi_map_handler;
	m_owner->logger()->info("reply_create_navi_map {} with {} buffered_reqs", m_navi_map_handler, m_buffered_reqs.size());
	for(auto & one_req: m_buffered_reqs)
	{
		one_req["param"]["navi_crowd"] = m_navi_map_handler;
		m_owner->call_map_server(one_req);
	}
	m_buffered_reqs.clear();
}

当一个actor_entity发起一个具体的寻路请求时,会调用到space_navi_component上的中转接口:

bool actor_navi_component::navi_to_pos(const std::array<double, 3>& dest, const double radius)
{
	// 这里省略一些代码
	cur_space_navi_comp->navi_to_pos(m_owner, cur_navi_prop->request_version(), dest, radius);
}
void space_navi_component::navi_to_pos(actor_entity* cur_entity, std::uint32_t req_version, const std::array<double, 3>& dest, const double radius)
{
	utility::navi_request cur_nav_req;
	cur_nav_req.cmd = utility::navi_cmd::move_to_pos;
	cur_nav_req.req_version = req_version;
	cur_nav_req.pos = dest;
	cur_nav_req.dest_radius = radius;
	cur_nav_req.from_handler = cur_entity->navi_handler();


	send_navi_req(cur_entity, cur_nav_req);
}

这个中转接口负责打包出来一个navi_request,调用send_navi_req发送到map_server的对应navi_crowd去处理,为了正确的路由到对应的navi_crowd,请求中需要携带创建群体寻路管理器响应中初始化的navi_map_handler

void space_navi_component::send_navi_req(actor_entity* cur_entity, const utility::navi_request& cur_req)
{
	cur_entity->dispatcher().dispatch(cur_req.cmd, cur_req);
	json cur_msg, cur_param;
	cur_msg["cmd"] = "navi_request";
	cur_param["navi_req"] = serialize::encode(cur_req);
	cur_param["navi_crowd"] = m_navi_map_handler; // navi_crowd的唯一索引
	if(m_owner)
	{
		if(m_owner->is_cell_space())
		{
			cur_param["anchor"] = *cur_entity->get_call_proxy();
		}
		else
		{
			cur_param["anchor"] = "";
		}
	}
	else
	{
		cur_param["anchor"] = "";
	}
	
	cur_msg["param"] = std::move(cur_param);
	if(m_navi_map_handler == 0)
	{
		m_owner->logger()->warn("send_navi_req {} while map handler invalid ", cur_req.encode().dump());
		m_buffered_reqs.push_back(cur_msg);
		return;
	}
	
	m_owner->call_map_server(cur_msg);
}

map_server接收到具体的寻路请求之后,解析出其中传递的navi_crowd_index,然后创建一个异步任务将这个请求转发到对应的navi_crowd所在的线程:

void map_server::on_navi_request(const json& msg)
{
	std::uint64_t temp_navi_crowd_index;
	json navi_req_json;
	utility::navi_request navi_req;
	std::string callback_anchor;
	try
	{
		msg.at("navi_crowd").get_to(temp_navi_crowd_index);
		msg.at("navi_req").get_to(navi_req_json);
		msg.at("anchor").get_to(callback_anchor);
	}
	catch(const std::exception& e)
	{
		m_logger->error("on_navi_request fail to parse msg {} with error {}", msg.dump(), e.what());
		return;
	}
	if(!serialize::decode(navi_req_json, navi_req))
	{
		m_logger->error("on_navi_request fail to parse msg {} to navi_req", navi_req_json.dump());
		return;
	}
	auto temp_iter = m_navi_crowds.find(temp_navi_crowd_index);
	if(temp_iter == m_navi_crowds.end())
	{
		return;
	}
	auto temp_crowd = temp_iter->second;
	add_task_to_async_loop([temp_crowd, navi_req, callback_anchor]()
	{
		temp_crowd->on_navi_request(callback_anchor, navi_req);
	}, temp_crowd->m_crowd_index);
	
}

上面构造lambda的时候可以尝试使用右值引用来初始化被捕获的变量,以避免各种不必要的内存分配,不过这里偷懒就没有去做优化。

navi_crowd处理传递过来的navi_request时,根据navi_request内部的cmd进行指令路由:

void navi_crowd::on_navi_request(const std::string& agent_anchor, const utility::navi_request& req_data)
{
	switch(req_data.cmd)
	{
		case utility::navi_cmd::add:
		{
			return on_navi_add_req(agent_anchor, req_data);
		}
		case utility::navi_cmd::remove:
		{
			return on_navi_remove_req(agent_anchor, req_data);
		}
		// 此处省略其他cmd
	}
}

当一个请求被对应的函数处理之后,会构造一个navi_reply对象发送到请求方:

//navi_crowd::on_navi_add_req
utility::navi_reply rep_data;
rep_data.cmd = req_data.cmd;
rep_data.req_version = req_data.req_version;
rep_data.entity_index = req_data.entity_index;
rep_data.from_handler = cur_agent_idx + 1;

if(agent_anchor.empty())
{
	m_agent_anchors[rep_data.from_handler] = m_space_anchor;
}
else
{
	m_agent_anchors[rep_data.from_handler] = std::make_shared<std::string>(agent_anchor);
}
m_agent_cmd_versions[rep_data.from_handler] = req_data.req_version;
send_reply(m_agent_anchors[rep_data.from_handler], rep_data);

上面的from_handler记录了本次创建的dtCrowdAgentAgentIndex + 1,而entity_index则对应了对应entity的线上唯一索引,send_reply函数负责将请求传递到主线程去执行真正的消息发送:

void navi_crowd::send_reply(std::shared_ptr<std::string> anchor, const utility::navi_reply& rep_data)
{
	if(!anchor)
	{
		anchor = m_space_anchor;
	}
	utility::rpc_msg rep_msg;
	rep_msg.cmd = "navi_reply";
	rep_msg.args.push_back(rep_data.encode());
	m_server->add_task_to_main_loop([=, rep_msg = std::move(rep_msg)]()
	{
		m_server->call_entity(anchor, rep_msg);
	});
}

space_navi_component收到对应的navi_reply之后,会根据对应的from_handler找到发起请求的actor_entity来执行应答通知:

void space_navi_component::navi_reply(const utility::rpc_msg& msg, const json& rep_msg)
{
	utility::navi_reply cur_reply;
	if(!serialize::decode(rep_msg, cur_reply))
	{
		return;
	}
	if(cur_reply.cmd == utility::navi_cmd::add)
	{
		auto cur_entity = m_owner->get_entity(utility::entity_desc::gen_local_id(cur_reply.entity_index));
		if(!cur_entity)
		{
			utility::navi_request cur_nav_req;
			cur_nav_req.cmd = utility::navi_cmd::remove;
			cur_nav_req.req_version = 0;
			cur_nav_req.from_handler = cur_reply.from_handler;
			send_navi_req(nullptr, cur_nav_req);
			return;
		}
		m_handler_to_entities[cur_reply.from_handler] = cur_entity;
		

	}
	if(cur_reply.cmd == utility::navi_cmd::remove)
	{
		return;
	}
	m_handler_to_entities[cur_reply.from_handler]->on_navi_reply(cur_reply);
}

至此一个完成的寻路请求处理流程结束,下面是对应的调用时序图:

寻路系统调用时序图

map_server中创建的navi_crowd其实并不负责真正的群体寻路管理器的更新,只是作为一个异步操作群体寻路管理器的封装而存在的,将请求解析好之后进一步转发到真正的DetourCrowd中:

void navi_crowd::on_navi_to_pos_req(const std::string& agent_anchor, const utility::navi_request& req_data)
{
	if(req_data.from_handler == 0 || !m_agent_anchors[req_data.from_handler])
	{
		return;
	}
	m_agent_cmd_versions[req_data.from_handler] = req_data.req_version;
	m_detour_crowd->requestMoveTarget(req_data.from_handler - 1, 0, req_data.pos.data(), req_data.dest_radius);
	utility::navi_reply rep_data;
	rep_data.cmd = req_data.cmd;
	rep_data.req_version = req_data.req_version;
	rep_data.from_handler = req_data.from_handler;
	send_reply(m_agent_anchors[rep_data.from_handler], rep_data);
}

navi_crowd主要的逻辑除了这些寻路请求接口的转发之外,还负责在detourCrwod执行更新之后将所有的dtAgent的新位置收集起来通知对应的actor_entity,同时将寻路任务的完成状况通知到对应的actor_entity:

void navi_crowd::update()
{
	auto cur_ms = utility::timer_manager::now_ts();
	float dt = (cur_ms - m_last_update_ms) / 1000.0f;
	m_last_update_ms = cur_ms;
	m_detour_crowd->update(dt, nullptr); // 驱动detourcrowd的更新
	auto active_num = m_detour_crowd->getActiveAgents(m_active_agent_vec.data(), m_active_agent_vec.size());
	system::navigation::ExtendDetourCrowdAgent* idx_0_agent = m_detour_crowd->getEditableAgent(0);
	m_temp_batch_replys.clear();
	for (int i = 0; i < active_num; i++) // 将所有的正在移动的agent的最新位置收集起来
	{
		auto cur_agent = m_active_agent_vec[i];
		if(cur_agent->targetState != spiritsaway::system::navigation::ExtendDetourMoveRequestState::TARGET_VALID)
		{
			continue;
		}
		utility::navi_reply one_ack;
		one_ack.from_handler = std::distance(idx_0_agent, cur_agent) + 1;
		one_ack.req_version = m_agent_cmd_versions[one_ack.from_handler];
		one_ack.cmd = utility::navi_cmd::notify_sync_pos;
		std::copy(cur_agent->npos, cur_agent->npos + 3, one_ack.pos.data());
		std::copy(cur_agent->vel, cur_agent->vel + 3, one_ack.speed.data());
		if(m_agent_anchors[one_ack.from_handler] == m_space_anchor)
		{
			// 对应的是非分布式场景 直接加入到这个数组中 
			m_temp_batch_replys.push_back(one_ack);
		}
		else
		{
			// 否则每个agent的位置单独走rpc推送更新
			send_reply(m_agent_anchors[one_ack.from_handler], one_ack);
		}
	}
	if(!m_temp_batch_replys.empty())
	{
		// 这里走批量更新 避免构造多个数据包 减少流量
		send_replys(m_temp_batch_replys);
	}
	m_temp_batch_replys.clear();
	// 收集所有已经达到寻路目标点的agent 通知其寻路任务结束
	auto finish_num = m_detour_crowd->fetchAndClearArrived(m_finished_agent_vec.data(), m_finished_agent_vec.size());
	for (int i = 0; i < finish_num; i++)
	{
		utility::navi_reply  one_ack;
		one_ack.from_handler = m_finished_agent_vec[i] + 1;
		one_ack.req_version = m_agent_cmd_versions[one_ack.from_handler];
		one_ack.cmd = utility::navi_cmd::notify_move_finish;
		if(m_agent_anchors[one_ack.from_handler] == m_space_anchor)
		{
			m_temp_batch_replys.push_back(one_ack);
		}
		else
		{
			send_reply(m_agent_anchors[one_ack.from_handler], one_ack);
		}

	}
	if(!m_temp_batch_replys.empty())
	{
		send_replys(m_temp_batch_replys);
	}
}

由于detour_crowd的更新在map_server上,而actor_entity的寻路请求在space_server上,两者只能通过网络异步通信,所以有可能actor_entity取消了寻路并强制修改本地位置之后,之前的寻路请求导致的位置更新被推送下来,导致位置被覆写,出现拉扯的情况。为了避免这种情况的发生,我们在actor_entity上增加了寻路请求版本号req_version,每次发起寻路时这个版本号都会自增,初始时为0

// bool actor_navi_component::navi_to_pos(const std::array<double, 3>& dest, const double radius)
cur_navi_prop_proxy->request_version().set(cur_navi_prop->request_version() + 1);
cur_navi_prop_proxy->state().set(std::uint8_t(utility::navi_cmd::move_to_pos));
cur_navi_prop_proxy->dest_pos().set(dest);
cur_navi_prop_proxy->dest_radius().set(radius);
cur_space_navi_comp->navi_to_pos(m_owner, cur_navi_prop->request_version(), dest, radius);

actor_entity收到navi_reply的时候,需要检查一下版本号是否匹配:

void actor_navi_component::on_navi_reply(const utility::navi_reply& cur_navi_reply)
{
	auto cur_navi_prop_proxy = m_owner->navi_prop_proxy();
	if(cur_navi_reply.req_version != cur_navi_prop_proxy->request_version().get())
	{
		return;
	}
	// 省略其他代码
}

actor_navi_component上提供了如下的几个寻路接口:

bool navi_to_pos(const std::array<double, 3>& dest, const double radius);
bool navi_to_entity(actor_entity* dest_entity, const double radius);
bool navi_follow_entity(actor_entity* dest_entity, const double radius);
void navi_cancel();

navi_to_pos负责开启一个走向固定点的寻路任务,对应的就是detourCrowd中的requestMoveTarget接口。而navi_to_entitynavi_follow_entity分别对应了移动到某个Entity周围和开启对某个Entity的跟随移动,这两个移动任务在原始的DetourCrowd中是没有的,属于mosaic_game中对DetourCrowd的扩展。我将这个扩展版本的DetourCrowd命名为ExtendDetourCrowd,对应的文件在工程目录的/common/extend_detour中。代码的大部分都来自于DetourCrowd,为了处理上述的趋近寻路和跟随寻路增加了下面的接口:

/// Submits a new move request for the specified agent.
///  @param[in]		idx		The agent index. [Limits: 0 <= value < #getAgentCount()]
///  @param[in]		ref		The position's polygon reference.
///  @param[in]		pos		The position within the polygon. [(x, y, z)]
///  @param[in]		target_radius		distance to pos less than raget_radius considered complete
///  @param[in]		follow		continue to follow when in target_radius
/// @return True if the request was successfully submitted.
bool requestChaseTarget(const int idx, const int target_idx, const dtReal_t target_radius = 0, const bool follow = false);

idx代表发起寻路的agent索引,target_idx代表目标agent索引,follow代表是否开启持续性的跟随。跟requestMoveTarget接口一样,这个接口只负责初始化相关的dtAgent成员变量:

ag->target_agent = target_idx; // 所追踪的目标agent的索引
m_agents[target_idx].follow_by_agent_count++; // 目标agent上记录当前被多少agent追踪
ag->continue_follow = follow; // 是否开启持续跟随
updateAgentFollowTarget(ag); // 根据目标agent来设置当前agent的终点poly和position信息

驱动跟随时位移其实就是定期的以趋近目标所在的polyposition来更新当前agent的目标位置并触发路径重新规划,这样就可以复用原始DetourCrowd的所有基于点目标进行移动的代码:

// 这个函数负责驱动趋近移动时corridor的更新
void ExtendDetourCrowd::checkFollowSteer(ExtendDetourCrowdAgent** agents, const int nagents)
{
	for (int i = 0; i < nagents; ++i)
	{
		ExtendDetourCrowdAgent* ag = agents[i];
		if (ag->targetState != ExtendDetourMoveRequestState::TARGET_VALID && ag->targetState != ExtendDetourMoveRequestState::TARGET_VELOCITY)
		{
			continue;
		}
		if (ag->state != ExtendDetourCrowdAgentState::STATE_WALKING)
		{
			continue;
		}
		if (ag->move_state != ExtendDetourCrowdAgentMoveState::STATE_MOVING)
		{
			continue;
		}
		if (ag->target_agent == -1)
		{
			continue;
		}
		auto target_ag = getAgent(ag->target_agent);
		if (!target_ag || !target_ag->active)
		{
			ag->move_state = ExtendDetourCrowdAgentMoveState::STATE_FINISHED;
			dtVset(ag->dvel, 0, 0, 0);
			ag->targetState = ExtendDetourMoveRequestState::TARGET_FAILED;
			continue;
		}
		// 两倍半径和作为阈值
		auto dist_threhold = 2*(ag->params.radius + ag->target_radius + target_ag->params.radius);
		auto cur_dis = dtVdist(ag->npos, target_ag->npos);
		dtReal_t offset[3];
		dtVsub(offset, target_ag->npos, ag->npos);
		dtVnormalize(offset);
		dtVscale(offset, offset, ag->params.maxSpeed);

		if (ag->targetState == ExtendDetourMoveRequestState::TARGET_VALID)
		{
			if (cur_dis < dist_threhold)// 如果两个agent的距离小于上面计算的阈值 则开启存粹的基于速度的移动
			{
				// 根据两者的位置差异来确定移动速度方向
				requestMoveVelocity(getAgentIndex(ag), offset);
				continue;
			}
		}
		else // 在基于速度的移动状态
		{
			if (cur_dis > 2 * dist_threhold) // 如果大于了两倍阈值 则切换到目标点移动状态
			{
				updateAgentFollowTarget(ag);
				ag->targetState = ExtendDetourMoveRequestState::TARGET_VALID;
			}
			else
			{
				// 继续使用基于速度的插值位置更新
				dtVcopy(ag->targetPos, offset);
				continue;
			}
		}
		auto now_to_cor_dis = dtVdist(ag->npos, ag->corridor.getTarget());
		auto cor_to_target_dis = dtVdist(ag->corridor.getTarget(), target_ag->npos);
		// 如果当前agent的corridor末尾点距离目标agent的距离大于指定阈值  且当前corridor的长度小于这个距离的10倍
		// 则更新当前agent的corridor末尾为target_ag->pos
		// 这样避免离很远时经常更新corridor
		if (cor_to_target_dis < dist_threhold && now_to_cor_dis < 10 * cor_to_target_dis)
		{
			updateAgentFollowTarget(ag);
			ag->corridor.moveTargetPosition(target_ag->npos, m_navquery, &m_filters[ag->params.queryFilterType]);

			continue;
		}
		// 如果ag->npos ag->corridor target_ag->pos 三个点组成了一个倒角
		// 则认为当前路径离最短路径差异较大 需要重新规划
		if (cor_to_target_dis + now_to_cor_dis > 1.5 * cur_dis)
		{
			updateAgentFollowTarget(ag);
			requestMoveTargetReplan(getAgentIndex(ag), ag->targetRef, ag->targetPos);

			continue;
		}
	}
}

如果开启了持续跟随,当当前agent与目标agent之间的距离小于跟随范围时,当前agent会暂停移动,临时性的不再参与寻路系统的更新。为了将这种临时暂停与没有寻路任务区分开来,我们增加了一个枚举来代表当前agent的移动状态:

enum class ExtendDetourCrowdAgentMoveState
{
	STATE_IDLE, // 无移动任务
	STATE_MOVING, // 正在移动
	STATE_WAITING, // 正在休眠
	STATE_FINISHED, // 运动已经结束
};
struct ExtendDetourCrowdAgent
{
	// 增加了一个move_state字段代表移动状态
	ExtendDetourCrowdAgentMoveState move_state = ExtendDetourCrowdAgentMoveState::STATE_IDLE;
};

当跟随移动时两者距离小于指定阈值时进入STATE_WAITING状态,直到两者之间的距离差距拉大到了ag->params.radius + ag->target_radius + target_ag->params.radius时重新进入到STATE_MOVING。而STATE_FINISHED则代表当前寻路任务完成,这个状态由checkArrived函数进行设置,该函数在ExtendDetourCrowd::update的末尾进行调用:

void ExtendDetourCrowd::checkArrived(ExtendDetourCrowdAgent** agents, const int nagents)
{
	for (int i = 0; i < nagents; ++i)
	{
		ExtendDetourCrowdAgent* ag = agents[i];
		
		if (ag->state != ExtendDetourCrowdAgentState::STATE_WALKING)
		{
			continue;
		}
		if (ag->target_agent < 0) // 基于目标点的寻路
		{
			if (ag->targetState != ExtendDetourMoveRequestState::TARGET_VALID)
			{
				continue;
			}
			auto cur_dis = dtVdist(ag->npos, ag->targetPos);
			bool close_tag = false; //是否已经到了阈值半径内
			if (ag->target_radius != 0)
			{
				close_tag = cur_dis <= ag->target_radius;
			}
			else
			{
				close_tag = cur_dis <= ag->params.radius * 0.5;
			}
			if (close_tag)
			{// 到了阈值半径内标记为寻路结束 
				ag->move_state = ExtendDetourCrowdAgentMoveState::STATE_FINISHED;
				dtVset(ag->vel, 0, 0, 0);

			}
		}
		else // 在执行趋近寻路
		{
			if (ag->targetState != ExtendDetourMoveRequestState::TARGET_VALID && ag->targetState != ExtendDetourMoveRequestState::TARGET_VELOCITY)
			{
				continue;
			}
			auto target_ag = getAgent(ag->target_agent);
			if (!target_ag) // 趋近目标不存在时 认为寻路完成
			{
				ag->move_state = ExtendDetourCrowdAgentMoveState::STATE_FINISHED;
				dtVset(ag->vel, 0, 0, 0);

				continue;
			}
			if (ag->move_state == ExtendDetourCrowdAgentMoveState::STATE_WAITING) // 跟随等待状态 不处理
			{
				continue;
			}
			auto cur_dis = dtVdist(ag->npos, target_ag->npos);
			bool close_tag = false;
			if (ag->target_radius != 0)
			{
				close_tag = cur_dis <= ag->target_radius;
			}
			else
			{
				close_tag = cur_dis <= (ag->params.radius + target_ag->params.radius) * 0.5;
			}
			if (close_tag)//是否已经到了阈值半径内
			{
				if (ag->continue_follow) // 如果开启了持续跟随 则进入暂停等待状态
				{
					ag->move_state = ExtendDetourCrowdAgentMoveState::STATE_WAITING;
					dtVset(ag->vel, 0, 0, 0);
				}
				else // 代表趋近寻路完成
				{
					ag->move_state = ExtendDetourCrowdAgentMoveState::STATE_FINISHED;
					dtVset(ag->vel, 0, 0, 0);

				}
			}
				
		}
	}				
}

外部的navi_crowd在触发了ExtendDetourCrowdupdate之后,会调用ExtendDetourCrowd::fetchAndClearArrived来收集所有寻路完成的agent,然后向相关的actor_entity进行通知任务完成:

int  ExtendDetourCrowd::fetchAndClearArrived(int* result, const int max_result)
{
	ExtendDetourCrowdAgent** agents = m_activeAgents;
	int nagents = getActiveAgents(agents, m_maxAgents);
	int count = 0;
	for (int i = 0; i < nagents; i++)
	{
		if (agents[i]->move_state == ExtendDetourCrowdAgentMoveState::STATE_MOVING)
		{
			if (agents[i]->targetState == ExtendDetourMoveRequestState::TARGET_FAILED)
			{
				agents[i]->move_state = ExtendDetourCrowdAgentMoveState::STATE_FINISHED;
			}
		}
		if (agents[i]->move_state == ExtendDetourCrowdAgentMoveState::STATE_FINISHED)
		{
			auto cur_agent_idx = getAgentIndex(agents[i]);
			result[count] = cur_agent_idx;
			count++;
			resetMoveTarget(cur_agent_idx);
			if (count >= max_result)
			{
				break;
			}
		}
	}
	return count;
}