using UnityEngine;
using UnityEngine.AI;
namespace ServiceSim
{
///
/// 人の NavMeshAgent を、安全に使えるようにする小さな道具。
///
/// prefab では NavMeshAgent を切ってある。置いた直後に有効にすると
/// 「NavMesh に近くないので agent を作れません」の警告が出たり、
/// NavMesh に乗る前に SetDestination を呼んで
/// 「can only be called on an active agent that has been placed on a NavMesh」
/// のエラーが出るため、ここを通してから使う。
///
/// この script は人だけを扱う。
///
public static class PeopleNav
{
/// NavMesh の上を探す距離(m)
const float SampleRadius = 2.0f;
///
/// 人が歩く NavMesh の種類(agentTypeID)。NavMeshBaker が焼いたあとに入れる。
/// 人は半径 0.2 m の面、ロボットは 0.25 m の面を歩く。
/// 焼けていなければ 0(ロボットと同じ面)のまま。
///
public static int PeopleAgentTypeId { get; private set; }
/// 人用の面を探すための絞り込み
public static NavMeshQueryFilter Filter
{
get
{
NavMeshQueryFilter f = new NavMeshQueryFilter();
f.agentTypeID = PeopleAgentTypeId;
f.areaMask = NavMesh.AllAreas;
return f;
}
}
/// 焼いたあとに NavMeshBaker が呼ぶ
public static void SetPeopleAgentType(int agentTypeId)
{
PeopleAgentTypeId = Mathf.Max(0, agentTypeId);
}
///
/// 客が歩ける区画(レジの内側=店員側を外す)。
/// スタッフは NavMesh.AllAreas のまま。
///
public static int GuestAreaMask(PeopleSpec spec)
{
if (spec == null || spec.registerBackArea <= 2) return NavMesh.AllAreas;
return NavMesh.AllAreas & ~(1 << spec.registerBackArea);
}
/// その agent を人用の面で歩かせる
public static void ApplyAgentType(NavMeshAgent agent)
{
if (agent == null) return;
if (agent.agentTypeID != PeopleAgentTypeId) agent.agentTypeID = PeopleAgentTypeId;
}
///
/// agent を使える状態にする。
/// 編集モード(Play していないとき)は何もせず false を返す。
///
public static bool EnsureReady(Transform tr, NavMeshAgent agent)
{
if (!Application.isPlaying) return false;
if (agent == null || tr == null) return false;
if (!agent.enabled)
{
// 有効にする前に NavMesh の上へ移す(警告を出さないため)
NavMeshHit hit;
if (NavMesh.SamplePosition(tr.position, out hit, SampleRadius, Filter))
tr.position = hit.position;
else
return false; // 近くに NavMesh が無いので、まだ有効にしない
ApplyAgentType(agent);
agent.enabled = true;
}
if (!agent.isActiveAndEnabled) return false;
if (!agent.isOnNavMesh)
{
NavMeshHit hit;
if (NavMesh.SamplePosition(tr.position, out hit, SampleRadius, Filter))
agent.Warp(hit.position);
}
return agent.isOnNavMesh;
}
///
/// 行き先を指示する。指示できたら true。
/// EnsureReady が true を返したあとだけ呼ぶ。
///
///
/// 試し2(依頼 v11 の 5 章):人の道を **同期** で引く。
///
/// SetDestination は非同期なので、道が引けるまで agent.pathPending が true のままで、
/// 何コマかかるかは Unity の都合で変わる。
/// その間「着いたかの判定」と「よけるかの判断」が打ち切られるので、
/// 同じ種でも数コマずれる(確かめ B で出たずれ)。
///
/// true にすると NavMesh.CalculatePath(同期)で引いて SetPath で渡すので、
/// pathPending が true になる場面が無くなる。
///
/// ★ 調べるためだけの切り替え。ExperimentRunner が回し始めに
/// ExperimentPlan.detCheckSyncPeoplePaths から入れ、回し終わりに false に戻す
///
public static bool SyncPaths;
/// 同期で引くときに使い回す道の入れ物(毎回 new しない)
static readonly NavMeshPath _path = new NavMeshPath();
///
/// 行き先を指示する。指示できたら true。
/// EnsureReady が true を返したあとだけ呼ぶ。
///
public static bool Go(NavMeshAgent agent, Vector3 point)
{
if (agent == null || !agent.isActiveAndEnabled || !agent.isOnNavMesh) return false;
NavMeshHit hit;
Vector3 to = NavMesh.SamplePosition(point, out hit, 1.5f, Filter) ? hit.position : point;
if (!SyncPaths) return agent.SetDestination(to);
// 同期で引く。引けなければ、これまでどおり非同期に任せる
// (引けない所へ行こうとしている場面の動きを変えないため)
NavMeshQueryFilter f = new NavMeshQueryFilter();
f.agentTypeID = agent.agentTypeID;
f.areaMask = agent.areaMask;
if (!NavMesh.CalculatePath(agent.nextPosition, to, f, _path) ||
_path.corners.Length == 0)
return agent.SetDestination(to);
return agent.SetPath(_path);
}
}
}