using System.Collections.Generic; using UnityEngine; namespace ServiceSim { /// /// サイトの試算の値(serving-robot-params.json)を読む .asset。 /// /// 2026-10-22 にエイイチと決めた「何について正か」(unity_design_v0.md の 0 章): /// 条件(スタッフ・来客・ロボットの台数)と、店の回し方の決まりと時間は **サイトが正**。 /// なので、店の回し方の数字は .asset に手で写さず、この JSON から読む。 /// /// role(meta.roles)の扱い: /// site_rule … サイトが正。Unity はこの値を使う /// unity_measures … **読まない**(Unity で測ってサイトに戻すための仮の値) /// site_only … 使わない(試算の中だけの設定) /// unity_measures の名前(robot_speed / pause_rate / pause_s / stuck_rate)は、 /// まちがって使わないように、読もうとしたら例外ではなく「読まない」として弾く。 /// /// 版の突き合わせ: meta.layout_generation が、店の形の JSON の meta.generation と /// 違ったら回さない(Ready が false になる)。 /// [CreateAssetMenu(menuName = "ServiceSim/Sim Params", fileName = "SimParams")] public class SimParams : ScriptableObject { /// Unity が読まない値(unity_measures)の名前 public static readonly string[] UnityMeasureKeys = { "robot_speed", "pause_rate", "pause_s", "stuck_rate" }; [Header("読む JSON(サイトから写したもの)")] public TextAsset paramsJson; [Header("写した記録(報告に出す)")] public string fetchedAt = ""; public string sourceUrl = ""; // ---- 読んだ中身(Load のたびに作り直す)---- JsonValue _root; readonly List _problems = new List(); string _layoutGeneration = ""; string _model = ""; public string LayoutGeneration { get { return _layoutGeneration; } } public string Model { get { return _model; } } public bool Ready { get { return _root != null && _problems.Count == 0; } } public IList Problems { get { return _problems; } } /// 1 つの条件(unity_conditions の 1 件) public class Condition { public string store; // fam_p1 / fam_p5 public int staff; public int robots; public int arrivalsPerHour; public string mode; // serve(上げ膳だけ) public int trays; // 1 往復で運ぶ卓の数 public string fragileBy; // any(倒れやすい物もロボットが運ぶ) public readonly Dictionary siteResult = new Dictionary(); /// 書き出しのフォルダ名に使う名前 public string Id { get { return store + "_s" + staff + "_r" + robots + "_a" + arrivalsPerHour; } } } readonly List _conditions = new List(); public IList Conditions { get { return _conditions; } } /// /// JSON を読む。storeGeneration に店の形の JSON の meta.generation を渡すと、 /// meta.layout_generation と突き合わせる(違えば Ready が false)。 /// public bool Load(string storeGeneration) { _problems.Clear(); _conditions.Clear(); _root = null; _layoutGeneration = ""; _model = ""; if (paramsJson == null) { _problems.Add("serving-robot-params.json(paramsJson)が入っていません"); return false; } string err; JsonValue root = JsonValue.Parse(paramsJson.text, out err); if (root == null || !root.IsObject) { _problems.Add("serving-robot-params.json を読めません: " + err); return false; } _root = root; JsonValue meta = root["meta"]; _layoutGeneration = meta["layout_generation"].S(""); _model = meta["model"].S(""); if (string.IsNullOrEmpty(_layoutGeneration)) _problems.Add("meta.layout_generation がありません"); else if (!string.IsNullOrEmpty(storeGeneration) && _layoutGeneration != storeGeneration) _problems.Add("版の番号が店の形の JSON と違います: 試算 " + _layoutGeneration + " / 店の形 " + storeGeneration + "(写し直してください)"); // ---- 条件 ---- JsonValue list = root["unity_conditions"]; for (int i = 0; i < list.Count; i++) { JsonValue c = list[i]["condition"]; Condition cond = new Condition(); cond.store = c["store"].S(""); cond.staff = c["staff"].I(0); cond.robots = c["robots"].I(0); cond.arrivalsPerHour = c["arrivals_per_h"].I(0); cond.mode = c["mode"].S("serve"); cond.trays = c["trays"].I(1); cond.fragileBy = c["fragile_by"].S("any"); JsonValue sr = list[i]["site_result"]; foreach (string k in sr.Keys) cond.siteResult[k] = sr[k].F(0f); if (string.IsNullOrEmpty(cond.store) || cond.staff <= 0) _problems.Add("unity_conditions[" + i + "] の中身が足りません"); _conditions.Add(cond); } if (_conditions.Count == 0) _problems.Add("unity_conditions が空です"); return Ready; } // ---------------- 値の読み出し ---------------- /// /// common の値(role=site_rule のものだけ)。 /// unity_measures / site_only を読もうとしたら、使わずに fallback を返して問題に残す。 /// public float SiteRule(string key, float fallback) { JsonValue v = Common(key); if (v == null) return fallback; return v["value"].F(fallback); } public float[] SiteRuleArray(string key, float[] fallback) { JsonValue v = Common(key); if (v == null) return fallback; JsonValue a = v["value"]; if (!a.IsArray) return fallback; float[] r = new float[a.Count]; for (int i = 0; i < a.Count; i++) r[i] = a[i].F(0f); return r; } /// common の 1 件。role が site_rule でなければ null(使わない) JsonValue Common(string key) { if (_root == null) return null; JsonValue v = _root["common"][key]; if (v.IsNull) { _problems.Add("common." + key + " がありません"); return null; } string role = v["role"].S(""); if (role == "site_rule") return v; _problems.Add("common." + key + " は role=" + role + " なので Unity は読みません"); return null; } /// stores.{key} の 1 件(fam_p1 / fam_p5) public JsonValue Store(string storeKey) { if (_root == null) return null; JsonValue s = _root["stores"][storeKey]; if (s.IsNull) { _problems.Add("stores." + storeKey + " がありません"); return null; } return s; } public float StoreValue(string storeKey, string key, float fallback) { JsonValue s = Store(storeKey); if (s == null) return fallback; JsonValue v = s[key]; return v.kind == JsonValue.Kind.Number ? v.F(fallback) : fallback; } /// stores.{key} の配列([最短, 最頻, 最長] など) public float[] StoreArray(string storeKey, string key, float[] fallback) { JsonValue s = Store(storeKey); if (s == null) return fallback; JsonValue a = s[key]; if (!a.IsArray) return fallback; float[] r = new float[a.Count]; for (int i = 0; i < a.Count; i++) r[i] = a[i].F(0f); return r; } /// /// 配列の配列の、index 番めを [最短, 最頻, 最長] で取る /// (first_dishes は [[5,8,12],[8,12,18]] の形) /// public Vector3 StoreTriangleAt(string storeKey, string key, int index, Vector3 fallback) { JsonValue s = Store(storeKey); if (s == null) return fallback; JsonValue a = s[key]; if (!a.IsArray || index < 0 || index >= a.Count) return fallback; JsonValue t = a[index]; if (!t.IsArray || t.Count < 3) return fallback; return new Vector3(t[0].F(0f), t[1].F(0f), t[2].F(0f)); } /// [最短, 最頻, 最長] を Vector3 で public Vector3 StoreTriangle(string storeKey, string key, Vector3 fallback) { float[] a = StoreArray(storeKey, key, null); if (a == null || a.Length < 3) return fallback; return new Vector3(a[0], a[1], a[2]); } /// 真ん中の値(arrivals・staff の「普通」) public int StoreMiddle(string storeKey, string key, int fallback) { float[] a = StoreArray(storeKey, key, null); if (a == null || a.Length == 0) return fallback; return Mathf.RoundToInt(a[a.Length / 2]); } /// 何番めの条件(_conditions の並びのまま)。範囲外なら null public Condition ConditionAt(int index) { return index >= 0 && index < _conditions.Count ? _conditions[index] : null; } /// metrics の名前と意味(meta.metrics) public string MetricNote(string key) { if (_root == null) return ""; return _root["meta"]["metrics"][key].S(""); } public List MetricKeys() { List keys = new List(); if (_root == null) return keys; foreach (string k in _root["meta"]["metrics"].Keys) keys.Add(k); return keys; } /// 報告に出す覚え書き public string Report() { System.Text.StringBuilder sb = new System.Text.StringBuilder(); sb.AppendLine("サイトの試算の値(serving-robot-params.json)"); sb.AppendLine(" ファイル: " + (paramsJson != null ? paramsJson.name : "(未設定)")); sb.AppendLine(" 写した日時: " + (string.IsNullOrEmpty(fetchedAt) ? "(未記入)" : fetchedAt)); sb.AppendLine(" 写し元: " + (string.IsNullOrEmpty(sourceUrl) ? "(未記入)" : sourceUrl)); sb.AppendLine(" 試算の版 (meta.model): " + _model + " / 店の形の版 (meta.layout_generation): " + _layoutGeneration); sb.AppendLine(" 条件: " + _conditions.Count + " 件"); for (int i = 0; i < _conditions.Count; i++) { Condition c = _conditions[i]; sb.AppendLine(" " + c.Id + " スタッフ " + c.staff + " / ロボット " + c.robots + " / " + c.arrivalsPerHour + " 組毎時 / " + c.mode + " / 1往復 " + c.trays + " 卓 / 倒れやすい物 " + c.fragileBy); } sb.AppendLine(" Unity が読まない値 (unity_measures): " + string.Join(" / ", UnityMeasureKeys)); if (_problems.Count == 0) sb.AppendLine(" 合わないもの: なし"); else { sb.AppendLine(" 合わないもの: " + _problems.Count + " 件"); for (int i = 0; i < _problems.Count; i++) sb.AppendLine(" - " + _problems[i]); } return sb.ToString().TrimEnd(); } } }