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); } } }