using UnityEngine; using UnityEngine.AI; namespace ServiceSim { /// 混み具合の3段階 public enum CrowdLevel { Empty = 0, // 空いている Normal = 1, // 普通 Busy = 2 // 混雑 } /// スタッフの仕事 public enum StaffTask { Serve = 0, // 料理を運ぶ(受け渡し口 → 卓 → 受け渡し口) Clear = 1, // 下げ膳(卓 → 返却口) Pantry = 2, // パントリーに立ち寄る Guide = 3, // 待合の客を卓へ案内する Register = 4 // レジ } /// 歩く客の行き先 public enum GuestTrip { Toilet = 0, // 卓 → トイレ → 卓 DrinkBar = 1, // 卓 → ドリンクバー → 卓 Exit = 2 // 卓 → レジ → 入口から出る } /// /// 人の数字。人に関する数字はすべてこの .asset に置く。 /// [CreateAssetMenu(menuName = "ServiceSim/People Spec", fileName = "PeopleSpec")] public class PeopleSpec : ScriptableObject { [Header("体の大きさ(箱と円柱で作る)")] [Tooltip("体の半径(m)")] public float personRadius = 0.2f; [Tooltip("身長(m)")] public float personHeight = 1.7f; [Tooltip("座っている客の形の高さ(m・床から頭の先まで)。見た目だけ。" + "背もたれ(1.1 m)より少し上に出し、仕切り(1.2 m)よりも高くして、" + "真上から見ても隠れないようにする。立っている客(1.7 m)より低くする")] public float seatedBodyHeight = 1.35f; [Tooltip("座っている客の形が腰かける高さ(m)。店の形の JSON の " + "meta.heights.bench_seat / chair(0.45 m)に合わせる。見た目だけ")] public float seatedSeatHeight = 0.45f; [Header("スタッフ")] [Tooltip("スタッフの人数")] public int staffCount = 2; [Tooltip("スタッフの歩く速さ(m/秒)")] public float staffSpeed = 1.0f; [Tooltip("仕事を選ぶ重み。[料理を運ぶ, 下げ膳, パントリー, 案内, レジ]。初期値は同じ割合")] public float[] staffTaskWeights = { 1f, 1f, 1f, 1f, 1f }; [Tooltip("受け渡し口で料理を取る時間(秒)")] public float staffDwellAtPassSec = 3f; [Tooltip("卓で料理を出す・下げる時間(秒)")] public float staffDwellAtTableSec = 5f; [Tooltip("返却口に皿を戻す時間(秒)")] public float staffDwellAtReturnSec = 5f; [Tooltip("パントリーに立ち寄る時間(秒)")] public float staffDwellAtPantrySec = 5f; [Tooltip("待合で客に声をかける時間(秒)")] public float staffDwellAtWaitingSec = 5f; [Tooltip("レジにいる時間(秒)")] public float staffDwellAtRegisterSec = 30f; [Header("歩く客")] [Tooltip("混み具合ごとの、歩く客の人数。[空いている, 普通, 混雑]")] public int[] guestCountByCrowd = { 1, 4, 8 }; [Tooltip("歩く客の速さ(m/秒)")] public float guestSpeed = 0.8f; [Tooltip("行き先を選ぶ重み。[トイレ, ドリンクバー, 帰る]")] public float[] guestTripWeights = { 1f, 1f, 1f }; [Tooltip("トイレ・ドリンクバーで過ごす時間(秒)")] public float guestDwellAtPoiSec = 10f; [Tooltip("レジで支払う時間(秒)")] public float guestDwellAtRegisterSec = 20f; [Tooltip("卓に戻って座っている時間(秒)")] public float guestDwellAtTableSec = 30f; [Header("慣らし(始めの埋まり方)")] [Tooltip("始めるときに、食事の途中の組で埋めておく卓の割合")] public float warmupOccupancy = 0.8f; [Header("人どうしのすれ違い")] [Tooltip("前に進めない状態がこの秒数続いたら、体を横に向ける(半径を細める)")] public float jamNarrowAfterSec = 1.0f; [Tooltip("体を横に向けたときの半径(m)")] public float jamNarrowRadius = 0.12f; [Tooltip("それでも抜けられないとき、下がるまでの秒数")] public float jamBackOffAfterSec = 3.0f; [Tooltip("これより遅ければ「前に進めない」とみなす(m/秒)")] public float jamStuckSpeed = 0.05f; [Header("待合に立つ客")] [Tooltip("混み具合ごとの、待合に立つ客の人数。[空いている, 普通, 混雑]")] public int[] waitingCountByCrowd = { 0, 2, 5 }; [Tooltip("待合で立ち止まったまま、位置を少し変えるまでの時間(秒)")] public float waitingShuffleSec = 25f; [Header("椅子を引く(人の動きに合わせて引く)")] [Tooltip("引いた椅子をそのままにしておく時間(秒)。座る・立つときに引いて、この秒数で戻す")] public float chairPullHoldSec = 30f; [Tooltip("椅子を通路側へ引く距離(m)")] public float chairPullDistance = 0.3f; [Tooltip("食事中に何もなく引く分の、1卓あたりの平均の間隔(秒)。" + "座る・立つときの引きとは別。0 以下なら引かない")] public float chairPullIdleMeanIntervalSec = 1800f; [Header("あきらめて、やり直す(B1)")] [Tooltip("客:行き先へ向かってこの秒数で着けなければ、あきらめて席に戻る")] public float guestGiveUpSec = 60f; [Tooltip("客:席に戻るのにもこの秒数で着けなければ、席の位置へ移して座らせる")] public float guestWarpHomeSec = 60f; [Tooltip("スタッフ:行き先へ向かってこの秒数で着けなければ、仕事を板に戻す")] public float staffGiveUpSec = 60f; [Tooltip("スタッフ:同じ仕事をこの回数あきらめたら、その場所へ移して仕事を済ませる")] public int staffGiveUpMaxTries = 3; [Header("よけ方(人の側がよける)")] [Tooltip("レジの内側(店員側)に使う NavMesh の区画の番号。" + "客の NavMeshAgent はこの区画を外して歩くので、店員側に入れない。" + "0=Walkable・1=Not Walkable・2=Jump は使えないので 3 以上にする")] public int registerBackArea = 3; [Tooltip("人の譲り合いの優先度。ロボットより大きくして、人がよけるようにする")] public int avoidancePriority = 50; [Tooltip("自動でよける機能の質。人はこれを使ってよい")] public ObstacleAvoidanceType avoidanceQuality = ObstacleAvoidanceType.GoodQualityObstacleAvoidance; [Tooltip("人の加速(m/秒^2)")] public float acceleration = 2.0f; [Tooltip("人の向きを変える速さ(度/秒)")] public float angularSpeedDegPerSec = 360f; [Tooltip("行き先にこれだけ近づいたら着いたとみなす(m)")] public float stoppingDistance = 0.3f; [Header("ロボットをよける(人の側がよける。値はすべて仮)")] [Tooltip("人がロボットをよけるか。切ると前のとおり気にせず歩く(比べるときに使う)")] public bool robotYieldEnabled = true; [Tooltip("細い通路に入る前に、経路の先をどこまで見るか(m)")] public float robotAisleLookAheadM = 3.0f; [Tooltip("通路の中にロボットがいるとみなす、通路のまわりの余白(m)")] public float robotAisleMarginM = 0.3f; [Tooltip("回り道してよい遠回りの上限(m)。これを超えるなら入口で待つ")] public float robotDetourExtraMaxM = 5.0f; [Tooltip("細い通路の中で向かい合ったとき、これを下回ったら下がってよける(m。外周どうしの空き)")] public float robotFaceToFaceM = 1.5f; [Tooltip("広い通路ですれ違うときに空ける距離(m。ロボットの外周から)")] public float robotKeepClearM = 0.3f; [Tooltip("ロボットの通り道からはずれるときの、横にずれる距離(m)")] public float robotSideStepM = 0.5f; [Tooltip("下がる先(よけ場所)を探す距離(m)")] public float robotBackOffSearchM = 4.0f; [Tooltip("ロボットが通り過ぎたとみなす、向かい合う距離からの余裕(m)")] public float robotPassedClearM = 0.5f; [Tooltip("待つ・下がる・回り道をあきらめて進むまでの秒数(秒)。B1 のあきらめより短くする")] public float robotYieldMaxSec = 20f; [Tooltip("上のよけ方を守らない割合(1回の行動につき)。スマホを見ている客などの代わり")] public float robotCarelessChance = 0.02f; [Tooltip("ロボットの外周からこの距離まで近づいたら contacts.csv に残す(m)")] public float robotContactGapM = 0.1f; [Tooltip("よけるときの横移動の速さ(m/秒)")] public float robotNudgeSpeedMps = 0.5f; // ---- 読み出し ---- public int GuestCount(CrowdLevel crowd) { return Pick(guestCountByCrowd, crowd); } public int WaitingCount(CrowdLevel crowd) { return Pick(waitingCountByCrowd, crowd); } /// 通路を歩く人の数(スタッフ+歩く客)。待合に立つ客は数えない public int WalkerCount(CrowdLevel crowd) { return staffCount + GuestCount(crowd); } static int Pick(int[] values, CrowdLevel crowd) { if (values == null || values.Length == 0) return 0; int i = Mathf.Clamp((int)crowd, 0, values.Length - 1); return values[i]; } /// 重みで仕事を選ぶ public StaffTask PickStaffTask(System.Random rng) { int i = PickWeighted(staffTaskWeights, 5, rng); return (StaffTask)i; } /// 重みで行き先を選ぶ public GuestTrip PickGuestTrip(System.Random rng) { int i = PickWeighted(guestTripWeights, 3, rng); return (GuestTrip)i; } static int PickWeighted(float[] weights, int count, System.Random rng) { float total = 0f; for (int i = 0; i < count; i++) total += WeightAt(weights, i); if (total <= 0f) return rng.Next(count); float r = (float)rng.NextDouble() * total; for (int i = 0; i < count; i++) { r -= WeightAt(weights, i); if (r <= 0f) return i; } return count - 1; } static float WeightAt(float[] weights, int i) { if (weights == null || i >= weights.Length) return 0f; return Mathf.Max(0f, weights[i]); } } }