using UnityEngine; namespace ServiceSim { /// /// 店の回し方の数字。試算(haizen_sim.py の COMMON と STORES["family"])と同じ値にそろえる。 /// 店の運営に関する数字はすべてこの .asset に置く。 /// /// 三角分布は(いちばん短い, いちばん多い, いちばん長い)の分(min)で持つ。 /// [CreateAssetMenu(menuName = "ServiceSim/Service Spec", fileName = "ServiceSpec")] public class ServiceSpec : ScriptableObject { [Header("来客(STORES[\"family\"][\"arrivals\"])")] [Tooltip("混み具合ごとの来客のペース(組/時)。[空いている, 普通, 混雑]")] public int[] arrivalsPerHourByCrowd = { 25, 35, 45 }; [Tooltip("ピークの長さ(分)。1回の実行の長さ(STORES[\"family\"][\"peak_min\"])")] public float peakMinutes = 120f; [Header("サイトの試算から読む値(v5 3章。SimParams が回すときだけ入れる)")] [Tooltip("その条件の来客のペース(組/時)。unity_conditions の arrivals_per_h。" + "0 なら、これまでどおり arrivalsPerHourByCrowd(混み具合)を使う")] public int arrivalsPerHour; [Tooltip("動けなくなったロボットを、スタッフが動かす時間(秒)(common.rescue_s)")] public float rescueSec = 15f; [Tooltip("来客が終わったあと、全員が帰るのを待つ上限(分)(common.max_extra_min)")] public float maxExtraMin = 600f; [Header("組(Unity だけの値。試算に人数はない)")] [Tooltip("組の人数を選ぶ重み。[1人, 2人, 3人, 4人]")] public float[] groupSizeWeights = { 0.20f, 0.35f, 0.20f, 0.25f }; [Header("案内・会計・呼び出し(COMMON)")] [Tooltip("案内にかかる時間(秒)。歩く時間は別(guide_s)")] public float guideSec = 45f; [Tooltip("会計の時間(秒)(pay_s)")] public float paySec = 90f; [Tooltip("呼び出し対応の時間(秒)。歩く時間は別(call_s)")] public float callSec = 30f; [Tooltip("レジの前の人数を数える半径(m)。記録だけに使う")] public float registerCrowdRadiusM = 2.0f; [Tooltip("会計はレジに立つ組の1人だけで済ませる(ほかの人は席で待つ)。" + "外すと前のとおり、誰もレジに行かずスタッフだけで会計する(前後を比べるときに使う)")] public bool payAtRegisterByOne = true; [Tooltip("会計のとき、スタッフが立つ点からこれ以上離れていたら数える(m)。" + "店員側に入れていないしるしなので、0 回になるはず")] public float registerPayOffSpotM = 0.1f; [Tooltip("1組あたりの呼び出しの起きやすさ(call_rate)。試算は2回ぶん 0.6/2 で判定")] public float callRate = 0.6f; [Header("料理(first_dishes・extra・fragile)")] [Tooltip("最初の注文:1皿めができるまで(分, 最短/最頻/最長)")] public Vector3 firstDish1CookMin = new Vector3(5f, 8f, 12f); [Tooltip("最初の注文:2皿めができるまで(分, 最短/最頻/最長)")] public Vector3 firstDish2CookMin = new Vector3(8f, 12f, 18f); [Tooltip("食事中のデザート・ドリンクの数の平均(fragile)")] public float fragilePerGroup = 0.5f; [Tooltip("デザート・ドリンクができるまで(分, 最短/最頻/最長)(fragile_cook)")] public Vector3 fragileCookMin = new Vector3(2f, 4f, 7f); [Tooltip("デザート・ドリンクをロボットも運ぶか。false なら人だけ(fragile_by)")] public bool fragileByRobot = true; [Tooltip("これを超えて届いた皿を「待たせた皿」とする(分)(cold_min)")] public float coldMin = 3f; [Header("運ぶ(COMMON)")] [Tooltip("ロボットが1走りで載せる料理の数(trays)。1 か 3")] public int trays = 3; [Tooltip("客がロボットから皿を取る時間(秒)(robot_handoff_s)")] public float robotHandoffSec = 25f; [Tooltip("スタッフが皿を置く時間(秒)(staff_place_s)")] public float staffPlaceSec = 10f; [Tooltip("料理ができてから、ロボットだけを待つ秒数(robot_first_wait_s)。" + "このあいだスタッフは料理を持たず、案内・片付け・会計を続ける。" + "過ぎたら、ロボットとスタッフのうち先に空いた方が運ぶ。" + "0 にすると、はじめから先に空いた方が運ぶ(v2.7 より前と同じ)")] public float robotFirstWaitSec = 120f; [Header("食事(eat_min)")] [Tooltip("食事の長さ(分, 最短/最頻/最長)")] public Vector3 eatMin = new Vector3(20f, 30f, 45f); [Tooltip("1人あたりドリンクバーへ行く平均の回数(Unity だけの値)")] public float drinkTripsPerPerson = 1.5f; [Tooltip("ドリンクバーで過ごす時間(秒)(Unity だけの値)")] public float drinkBarSec = 20f; [Tooltip("1組あたりトイレへ行く平均の回数(Unity だけの値)")] public float toiletTripsPerGroup = 0.3f; [Tooltip("トイレの中で過ごす時間(秒)(Unity だけの値)")] public float toiletSec = 180f; [Header("片付け(COMMON・bus_trips)")] [Tooltip("卓の皿をまとめる時間(秒)(clear_s)")] public float clearSec = 40f; [Tooltip("卓を拭いてセットする時間(秒)(wipe_s)")] public float wipeSec = 40f; [Tooltip("下げ膳の往復回数(皿の量)(bus_trips)")] public int busTrips = 2; [Header("待合")] [Tooltip("待合に立たせる組の上限。あふれた組は店の外で待つ扱い(人として置かない)")] public int waitingVisibleMax = 8; [Header("あきらめて、やり直す(B1)")] [Tooltip("会計・片付けで止まって卓が空かない状態がこの秒数続いたら、卓を空ける")] public float tableStuckFreeSec = 600f; // ---- 読み出し ---- public int ArrivalsPerHour(CrowdLevel crowd) { // サイトの条件で回すときは、混み具合を使わずその値をそのまま使う(v5 3章) if (arrivalsPerHour > 0) return arrivalsPerHour; if (arrivalsPerHourByCrowd == null || arrivalsPerHourByCrowd.Length == 0) return 35; int i = Mathf.Clamp((int)crowd, 0, arrivalsPerHourByCrowd.Length - 1); return arrivalsPerHourByCrowd[i]; } /// 三角分布(分)から秒を引く。試算の tri() と同じ public static float TriangularSec(System.Random rng, Vector3 lowModeHigh) { float lo = lowModeHigh.x, mode = lowModeHigh.y, hi = lowModeHigh.z; if (hi <= lo) return lo * 60f; mode = Mathf.Clamp(mode, lo, hi); double u = rng.NextDouble(); double c = (mode - lo) / (double)(hi - lo); double v = u < c ? lo + System.Math.Sqrt(u * (hi - lo) * (mode - lo)) : hi - System.Math.Sqrt((1.0 - u) * (hi - lo) * (hi - mode)); return (float)v * 60f; } /// 指数分布(秒)。試算の expovariate と同じ public static float ExponentialSec(System.Random rng, float ratePerSec) { if (ratePerSec <= 0f) return float.MaxValue; double u = rng.NextDouble(); if (u <= 1e-12) u = 1e-12; return (float)(-System.Math.Log(u) / ratePerSec); } /// 組の人数を重みで選ぶ public int PickGroupSize(System.Random rng) { float total = 0f; int n = groupSizeWeights != null ? groupSizeWeights.Length : 0; for (int i = 0; i < n; i++) total += Mathf.Max(0f, groupSizeWeights[i]); if (total <= 0f) return 2; float r = (float)rng.NextDouble() * total; for (int i = 0; i < n; i++) { r -= Mathf.Max(0f, groupSizeWeights[i]); if (r <= 0f) return i + 1; } return n; } /// 平均 mean 回を、整数回に落とす(試算の fragile と同じやり方) public static int PickCount(System.Random rng, float mean) { int n = (int)mean; if (rng.NextDouble() < mean - n) n++; return n; } } }