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