using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using UnityEngine;
namespace ServiceSim
{
///
/// サイトが書き出した「店の形の JSON」を読んで店を建てる StoreLayout。
///
/// 2026-10-22 にエイイチと決めた「何について正か」(unity_design_v0.md の 0 章):
/// 店の形(卓・壁・部屋・止まる位置)は **サイトが正**。Unity は形を作らない。
/// なので、このクラスは JSON を StorePlan に写すだけで、足りないものを自分で埋めない。
/// 合わないものが出たら、埋めずに報告へ書いて **建てずに止める**(Compose が null を返す)。
///
/// 読むもの:
/// layoutJson https://pixelstomotors.com/sim/layouts/unity/{名前}.json を写したもの
/// summaryJson https://pixelstomotors.com/sim/layouts/summary.json を写したもの
/// 版の番号(meta.generation)が summary.json と違ったら建てない。
///
/// 座標: JSON の x → Unity の X、JSON の y → Unity の Z、高さ → Y。単位は m。
/// 原点は店の外壁の内側の左下の角。
///
[CreateAssetMenu(fileName = "StoreLayout_Json", menuName = "ServiceSim/Json Store Layout", order = 12)]
public class JsonStoreLayout : StoreLayout
{
[Header("読む JSON(サイトから写したもの)")]
[Tooltip("店の形の JSON(unity/{名前}.json)")]
public TextAsset layoutJson;
[Tooltip("一覧の JSON(summary.json)。版の番号を突き合わせる")]
public TextAsset summaryJson;
[Tooltip("summary.json の file と突き合わせる名前(例 p1_efficient_corner)")]
public string layoutFileName = "";
[Header("写した記録(報告に出す)")]
[Tooltip("サイトから写した日時")]
public string fetchedAt = "";
[Tooltip("写し元の URL")]
public string sourceUrl = "";
[Header("見た目だけ(当たりも NavMesh も持たない)")]
[Tooltip("背もたれの板の厚み(m)。机と反対側のふちに立てる。0 で出さない")]
public float benchBackThickness = 0.08f;
[Tooltip("座面の見た目を、ベンチのふちから内側へ詰める長さ(m)。0 でふちまで")]
public float benchSeatInset = 0f;
// ---- 建てたときの覚え書き(報告に出す。Compose のたびに作り直す)----
readonly List _problems = new List();
readonly List _doors = new List();
string _generation = "";
string _storeName = "";
float _jsonNarrowWidth = 0f;
float _normalWidth = 0f;
int _tableCount, _seatCount, _narrowAisleCount, _chairCount, _spotCount, _seatPointCount;
int _roomCount, _placeCount, _wallCount, _dividerCount;
int _walkableRoomCount, _staffOnlyRoomCount;
int _routeFromPass, _routeBetween;
public override string StoreKindName
{
get { return "JsonStoreLayout(" + (string.IsNullOrEmpty(layoutFileName) ? "?" : layoutFileName) + ")"; }
}
/// この JSON の版の番号(meta.generation)。建てたあとに読める
public string Generation { get { return _generation; } }
///
/// 版の番号を、建てる前でも読む(試算の値と突き合わせるのに使う)。
/// 建てたあとは Compose が読んだものを返す。
///
public string ReadGeneration()
{
if (!string.IsNullOrEmpty(_generation)) return _generation;
if (layoutJson == null) return "";
string err;
JsonValue root = JsonValue.Parse(layoutJson.text, out err);
return root != null && root.IsObject ? root["meta"]["generation"].S("") : "";
}
/// 建てた卓の数(JSON の tables の数)
public int TableCount { get { return _tableCount; } }
///
/// サイトの決まりの「ふだん走らせる道の幅」(meta.settings.normal_width, m)。
/// これ未満の幅の通路が「狭い通路」。0 なら JSON に無い
///
public float NormalWidth { get { return _normalWidth; } }
///
/// 試算の値(serving-robot-params.json)の stores の名前(fam_p1 / fam_p5)。
/// layoutFileName(p1_efficient_corner / p5_crammed)の頭の "p1" "p5" から作る。
/// 合っているかは SimParams.Store() が null かどうかで分かる。
///
public string ParamsStoreKey
{
get
{
if (string.IsNullOrEmpty(layoutFileName)) return "";
int cut = layoutFileName.IndexOf('_');
string head = cut > 0 ? layoutFileName.Substring(0, cut) : layoutFileName;
return "fam_" + head;
}
}
///
/// Build Store が store_report と Console に出す欄。
/// JSON から建てたときは、版の番号・写した日時・数えたもの・合わなかったものをここに出す。
///
public override string FrontFitReport()
{
StringBuilder sb = new StringBuilder();
sb.AppendLine("JSON から建てた店(Unity では形を作っていない)");
sb.AppendLine(" ファイル: " + (layoutJson != null ? layoutJson.name : "(未設定)") +
" / 名前: " + _storeName);
sb.AppendLine(" 版の番号 (meta.generation): " + _generation);
sb.AppendLine(" 写した日時: " + (string.IsNullOrEmpty(fetchedAt) ? "(未記入)" : fetchedAt));
sb.AppendLine(" 写し元: " + (string.IsNullOrEmpty(sourceUrl) ? "(未記入)" : sourceUrl));
sb.AppendLine(" 卓 " + _tableCount + " / 席 " + _seatCount +
" / 細い通路 " + _narrowAisleCount + " 本(幅 " + F(_jsonNarrowWidth) + " m)");
sb.AppendLine(" 通れない部屋 " + _roomCount +
" / 通れる部屋 " + _walkableRoomCount +
"(うちスタッフだけ " + _staffOnlyRoomCount + ")");
sb.AppendLine(" 場所の点 " + _placeCount +
" / 壁 " + _wallCount + " / 仕切り " + _dividerCount +
" / 椅子 " + _chairCount +
" / 席に着く点 " + _spotCount + " / 座る位置 " + _seatPointCount);
sb.AppendLine(" 道順 (routes): 受け渡し口から " + _routeFromPass + " 本 / 卓から卓 " +
_routeBetween + " 本"
+ "(狭い道を使うかは routes の use が正。Unity では印を持たない)");
sb.AppendLine(" doors: " + (_doors.Count == 0
? "0(壁の切れ目として、もう空いている)"
: _doors.Count + " → " + string.Join(" / ", _doors.ToArray())));
sb.AppendLine(" refuges(よけ場所): 0(JSON にまだ無い。空のまま)");
sb.AppendLine(" 止まる位置は JSON の stop をそのまま使う"
+ "(deliveryOffsetFromAisleCenter と aisleEntranceSetback は使わない)");
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();
}
protected override StorePlan Compose()
{
_problems.Clear();
_doors.Clear();
_generation = ""; _storeName = ""; _jsonNarrowWidth = 0f; _normalWidth = 0f;
_tableCount = _seatCount = _narrowAisleCount = _chairCount = 0;
_spotCount = _seatPointCount = _roomCount = _placeCount = _wallCount = _dividerCount = 0;
_walkableRoomCount = _staffOnlyRoomCount = 0;
_routeFromPass = _routeBetween = 0;
if (layoutJson == null)
{
Fail("店の形の JSON(layoutJson)が入っていません");
return null;
}
string err;
JsonValue root = JsonValue.Parse(layoutJson.text, out err);
if (root == null || !root.IsObject)
{
Fail("店の形の JSON を読めません: " + err);
return null;
}
JsonValue meta = root["meta"];
_generation = meta["generation"].S("");
_storeName = root["name"].S("");
// サイトの決まりの「ふだん走らせる道の幅」(meta.settings.normal_width)。
// これ未満の通路が「狭い通路」。往復ごとの測りを中と外で分けるのに使う(v5 3章)
_normalWidth = meta["settings"]["normal_width"].F(0f);
if (!CheckGeneration()) return null;
// ---- 高さ(JSON の meta.heights。Unity 側では決めない)----
JsonValue heights = meta["heights"];
float hWall = Req(heights, "wall", "meta.heights.wall");
float hTable = Req(heights, "table", "meta.heights.table");
float hBench = Req(heights, "bench", "meta.heights.bench");
float hBenchSeat = Req(heights, "bench_seat", "meta.heights.bench_seat");
float hChair = Req(heights, "chair", "meta.heights.chair");
float hCounter = Req(heights, "counter", "meta.heights.counter");
float hPartition = heights["partition"].F(1.2f); // これ以下の walls は仕切り
StorePlan plan = new StorePlan();
// ---- 床 ----
JsonValue floor = root["floor"];
for (int i = 0; i < floor.Count; i++)
{
PlanRect r;
if (TryRect(floor[i], "floor[" + i + "]", out r)) plan.AddFloor(r);
}
if (plan.floorPieces.Count == 0) Fail("floor が空です");
// ---- 外壁 ----
JsonValue outerWalls = root["outer_walls"];
for (int i = 0; i < outerWalls.Count; i++)
{
PlanRect r;
if (!TryRect(outerWalls[i], "outer_walls[" + i + "]", out r)) continue;
plan.AddBox("OuterWall" + i.ToString("00"), BoxStyle.Wall, r, hWall);
_wallCount++;
}
// ---- 中の壁・仕切り(高さで分ける)----
JsonValue walls = root["walls"];
for (int i = 0; i < walls.Count; i++)
{
JsonValue w = walls[i];
PlanRect r;
if (!TryRect(w["rect"], "walls[" + i + "].rect", out r)) continue;
float h = w["height"].F(hWall);
bool divider = h <= hPartition + 0.0005f;
string label = w["label"].S("Wall" + i.ToString("00"));
plan.AddBox(Safe(label) + i.ToString("00"),
divider ? BoxStyle.Divider : BoxStyle.Wall, r, h);
if (divider) _dividerCount++; else _wallCount++;
}
// ---- 場所の点(先に入れる。PointOf がこちらを先に拾うように)----
Dictionary firstPoint = new Dictionary();
JsonValue places = root["places"];
for (int i = 0; i < places.Count; i++)
{
JsonValue p = places[i];
string kindName = p["kind"].S("");
PlaceKind kind;
if (!TryParseKind(kindName, out kind))
{
Fail("places[" + i + "]: PlaceKind に無い kind \"" + kindName + "\"");
continue;
}
Vector3 point;
if (!TryPoint(p["point"], "places[" + i + "].point", out point)) continue;
PlanRect rect;
if (!p.Has("rect") || !TryRect(p["rect"], "places[" + i + "].rect", out rect))
rect = new PlanRect(0f, 0f, 0f, 0f);
// 箱は置かない(通れない箱は rooms が持つ)。ここは点だけ。
plan.AddPlace(kind, Safe(p["label"].S(kindName)) + i.ToString("00"), rect, 0f, point, false);
if (!firstPoint.ContainsKey(kind)) firstPoint[kind] = point;
_placeCount++;
}
// ---- 部屋(通れない箱)----
JsonValue rooms = root["rooms"];
for (int i = 0; i < rooms.Count; i++)
{
JsonValue rm = rooms[i];
string label = rm["label"].S("");
PlaceKind kind;
if (!TryRoomKind(label, out kind))
{
Fail("rooms[" + i + "]: PlaceKind に対応の無いラベル \"" + label + "\"");
continue;
}
PlanRect r;
if (!TryRect(rm["rect"], "rooms[" + i + "].rect", out r)) continue;
bool walkable = rm["walkable"].B(false);
bool staffOnly = rm["staff_only"].B(false);
// スタッフだけが入れる区画は、いまの作りでは「レジの内側」だけ
// (NavMeshBaker.AddRegisterBackArea が PlaceKind.RegisterBack の rect を見て
// 区画の番号を変え、客の NavMeshAgent がその区画を外して歩く)。
// ほかの部屋に staff_only が付いたら、こちらで作らずに報告して止める。
if (staffOnly && kind != PlaceKind.RegisterBack)
{
Fail("rooms[" + i + "] \"" + label + "\": staff_only は今の作りでは" +
"レジの内側(RegisterBack)だけです");
continue;
}
if (walkable)
{
// 通れない箱にしない(人が入る部屋)。場所の点だけ置く。
// 同じ種類の点が places にあれば、そちらが区画の広がりも持っているので足さない。
if (!firstPoint.ContainsKey(kind))
{
plan.AddPlace(kind, Safe(label) + "_Room" + i.ToString("00"), r, 0f, r.Center, false);
firstPoint[kind] = r.Center;
}
_walkableRoomCount++;
if (staffOnly) _staffOnlyRoomCount++;
continue;
}
// レジとドリンクバーはカウンターの高さ、それ以外は壁の高さ
float h = (kind == PlaceKind.Register || kind == PlaceKind.DrinkBar) ? hCounter : hWall;
// 点は、同じ種類の場所の点(JSON が決めた立ち位置)に合わせる。無ければ真ん中
Vector3 point;
if (!firstPoint.TryGetValue(kind, out point)) point = r.Center;
plan.AddPlace(kind, Safe(label) + "_Room" + i.ToString("00"), r, h, point, true);
_roomCount++;
}
// ---- 細い通路 ----
Dictionary aisleIndexById = new Dictionary();
JsonValue aisles = root["aisles"];
for (int i = 0; i < aisles.Count; i++)
{
JsonValue a = aisles[i];
PlanRect r;
if (!TryRect(a["rect"], "aisles[" + i + "].rect", out r)) continue;
Vector3 entrance;
if (!TryPoint(a["entrance"], "aisles[" + i + "].entrance", out entrance)) continue;
string id = a["id"].S("A" + (i + 1).ToString("00"));
bool narrow = a["narrow"].B(true);
aisleIndexById[id] = plan.aisles.Count;
plan.AddAisle(id, r, narrow, entrance);
float aw = a["width"].F(0f);
if (narrow)
{
_narrowAisleCount++;
if (_jsonNarrowWidth <= 0f) _jsonNarrowWidth = aw;
else if (Mathf.Abs(_jsonNarrowWidth - aw) > 0.0005f)
Fail("細い通路の幅がそろっていません: " + F(_jsonNarrowWidth) + " と " + F(aw));
}
}
// ---- 卓 ----
JsonValue tables = root["tables"];
for (int i = 0; i < tables.Count; i++)
{
ComposeTable(plan, tables[i], i, aisleIndexById,
hTable, hBench, hBenchSeat, hChair);
}
_tableCount = plan.seats.Count;
// ---- ロボットの道順(v5b。サイトが決めた曲がり角の点)----
plan.routes = ReadRoutes(root["routes"]);
// ---- doors(箱は置かない。報告に一覧だけ出す)----
JsonValue doors = root["doors"];
for (int i = 0; i < doors.Count; i++)
{
JsonValue d = doors[i];
string label = d["label"].S(d["id"].S("door" + i));
PlanRect r;
string where = TryRect(d["rect"], "doors[" + i + "].rect", out r) ? r.ToString() : "(rect なし)";
_doors.Add(label + " " + where);
}
// ---- 細い通路の幅が .asset の値と合っているか ----
if (_jsonNarrowWidth > 0f && Mathf.Abs(AisleWidth - _jsonNarrowWidth) > 0.0005f)
{
Fail("細い通路の幅が .asset と JSON で違います: .asset " + F(AisleWidth) +
" m / JSON " + F(_jsonNarrowWidth) + " m" +
"(aisleWidthOptions を JSON の幅だけにしてください)");
}
if (_problems.Count > 0)
{
Debug.LogError("[ServiceSim] " + name + ": JSON と合わないものが " + _problems.Count +
" 件あるので建てませんでした。\n" + FrontFitReport());
return null;
}
return plan;
}
///
/// routes を読む(v5b)。from_pass["T05"] と between["T05|T12"]。
/// 点は [x, y] で、JSON の y → Unity の Z。
///
RouteBook ReadRoutes(JsonValue routes)
{
if (!routes.IsObject)
{
Fail("routes がありません(v5b の版の JSON を写してください)");
return null;
}
RouteBook book = new RouteBook();
JsonValue fromPass = routes["from_pass"];
foreach (string id in fromPass.Keys)
{
RouteBook.Route r = ReadRoute(fromPass[id], "routes.from_pass[" + id + "]");
if (r != null) book.AddFromPass(NumberFromId(id, -1), r);
}
JsonValue between = routes["between"];
foreach (string key in between.Keys)
{
int bar = key.IndexOf('|');
if (bar <= 0)
{
Fail("routes.between のキーが \"T01|T02\" の形ではありません: " + key);
continue;
}
RouteBook.Route r = ReadRoute(between[key], "routes.between[" + key + "]");
if (r != null)
book.AddBetween(NumberFromId(key.Substring(0, bar), -1),
NumberFromId(key.Substring(bar + 1), -1), r);
}
_routeFromPass = book.FromPassCount;
_routeBetween = book.BetweenCount;
if (_routeFromPass == 0) Fail("routes.from_pass が空です");
return book;
}
RouteBook.Route ReadRoute(JsonValue v, string where)
{
JsonValue pts = v["points"];
if (pts.Count < 2)
{
Fail(where + ": points が 2 点未満です");
return null;
}
RouteBook.Route r = new RouteBook.Route();
for (int i = 0; i < pts.Count; i++)
{
Vector3 p;
if (!TryPoint(pts[i], where + ".points[" + i + "]", out p)) return null;
r.points.Add(p);
}
r.use = v["use"].S("normal");
r.width = v["width"].F(0f);
r.length = v["length"].F(r.PolylineLength);
if (r.use != "normal" && r.use != "narrow")
{
Fail(where + ": use が normal / narrow ではありません(" + r.use + ")");
return null;
}
return r;
}
// ---------------- 卓 ----------------
void ComposeTable(StorePlan plan, JsonValue t, int index,
Dictionary aisleIndexById,
float hTable, float hBench, float hBenchSeat, float hChair)
{
string id = t["id"].S("T" + (index + 1).ToString("00"));
string where = "tables[" + index + "] " + id;
SeatKind kind;
string type = t["type"].S("");
if (!TryTableKind(type, out kind))
{
Fail(where + ": SeatKind に対応の無い type \"" + type + "\"");
return;
}
JsonValue parts = t["parts"];
PlanRect top;
if (!TryRect(parts["top"], where + ".parts.top", out top)) return;
Vector3 stop;
if (!TryPoint(t["stop"], where + ".stop", out stop)) return;
PlanSeat seat = new PlanSeat();
seat.tableId = NumberFromId(id, index + 1);
seat.kind = kind;
seat.tableRect = top;
seat.tableHeight = hTable;
seat.seatCount = t["seats"].I(0);
seat.deliveryPoint = stop; // JSON の stop をそのまま(ずらさない)
// 通る細い通路
string aisleId = t["aisle"].S(null);
if (string.IsNullOrEmpty(aisleId)) seat.aisleIndex = -1;
else
{
int ai;
if (aisleIndexById.TryGetValue(aisleId, out ai)) seat.aisleIndex = ai;
else { Fail(where + ": aisles に無い通路 \"" + aisleId + "\""); return; }
}
// 天板
plan.AddBox(id + "_Top", BoxStyle.Table, top, hTable);
// ベンチ。当たりの箱はこれまでと同じ大きさ・位置・高さのまま、見た目を持たない。
// 見た目は、当たりなしの座面と背もたれで作る(本物に近い形にするため)
JsonValue benches = parts["benches"];
for (int b = 0; b < benches.Count; b++)
{
PlanRect r;
if (!TryRect(benches[b], where + ".parts.benches[" + b + "]", out r)) continue;
plan.AddBox(id + "_Bench" + (b + 1), BoxStyle.BenchBody, r, hBench);
AddBenchLook(plan, id + "_Bench" + (b + 1), r, top, hBench, hBenchSeat);
}
// 椅子
JsonValue chairs = parts["chairs"];
for (int c = 0; c < chairs.Count; c++)
{
JsonValue ch = chairs[c];
PlanRect r;
if (!TryRect(ch["rect"], where + ".parts.chairs[" + c + "].rect", out r)) continue;
PlanChair chair = new PlanChair();
chair.rect = r;
chair.height = hChair;
chair.pullable = ch["pullable"].B(false);
JsonValue dir = ch["pull_dir"];
chair.pullDirection = new Vector3(dir[0].F(0f), 0f, dir[1].F(0f));
if (chair.pullDirection.sqrMagnitude > 0.000001f)
chair.pullDirection = chair.pullDirection.normalized;
seat.chairs.Add(chair);
_chairCount++;
}
// 座る位置
JsonValue sp = parts["seat_points"];
for (int s = 0; s < sp.Count; s++)
{
Vector3 p;
if (!TryPoint(sp[s], where + ".parts.seat_points[" + s + "]", out p)) continue;
seat.seatPoints.Add(p);
_seatPointCount++;
}
// 席に着くために立つ点
JsonValue spots = t["spots"];
for (int s = 0; s < spots.Count; s++)
{
Vector3 p;
if (!TryPoint(spots[s]["point"], where + ".spots[" + s + "].point", out p)) continue;
seat.spots.Add(new PlanSeatSpot(spots[s]["side"].S("spot" + (s + 1)), p));
_spotCount++;
}
plan.seats.Add(seat);
_seatCount += seat.seatCount;
}
///
/// ベンチの見た目(当たりなし)。座面と背もたれを、ベンチの箱の中に重ならないように置く。
/// 背もたれ … 机と反対側のふちに、厚さ benchBackThickness・高さ benchHeight(1.1 m)
/// 座面 … 背もたれの板を除いた残り、高さ seatHeight(0.45 m)
/// どちらも当たりを持たないので、NavMesh・前のセンサー・左右の空きの測りには効かない。
///
void AddBenchLook(StorePlan plan, string name, PlanRect bench, PlanRect table,
float benchHeight, float seatHeight)
{
float back = Mathf.Max(0f, benchBackThickness);
PlanRect seatRect = bench;
if (back > 0.0005f)
{
// 机から遠い側のふちを選ぶ。長い向きではなく、机との位置関係で決める
float dx = bench.CenterX - table.CenterX;
float dz = bench.CenterZ - table.CenterZ;
bool alongZ = Mathf.Abs(dz) >= Mathf.Abs(dx); // 机の南北にあるベンチか
if (alongZ && bench.SizeZ > back + 0.01f)
{
if (dz >= 0f) // ベンチは机の北 → 背もたれは北のふち
{
plan.AddBox(name + "_Back", BoxStyle.BenchBack,
new PlanRect(bench.xMin, bench.zMax - back, bench.xMax, bench.zMax), benchHeight);
seatRect = new PlanRect(bench.xMin, bench.zMin, bench.xMax, bench.zMax - back);
}
else
{
plan.AddBox(name + "_Back", BoxStyle.BenchBack,
new PlanRect(bench.xMin, bench.zMin, bench.xMax, bench.zMin + back), benchHeight);
seatRect = new PlanRect(bench.xMin, bench.zMin + back, bench.xMax, bench.zMax);
}
}
else if (!alongZ && bench.SizeX > back + 0.01f)
{
if (dx >= 0f) // ベンチは机の東 → 背もたれは東のふち
{
plan.AddBox(name + "_Back", BoxStyle.BenchBack,
new PlanRect(bench.xMax - back, bench.zMin, bench.xMax, bench.zMax), benchHeight);
seatRect = new PlanRect(bench.xMin, bench.zMin, bench.xMax - back, bench.zMax);
}
else
{
plan.AddBox(name + "_Back", BoxStyle.BenchBack,
new PlanRect(bench.xMin, bench.zMin, bench.xMin + back, bench.zMax), benchHeight);
seatRect = new PlanRect(bench.xMin + back, bench.zMin, bench.xMax, bench.zMax);
}
}
}
float m = Mathf.Max(0f, benchSeatInset);
PlanRect r = new PlanRect(seatRect.xMin + m, seatRect.zMin + m,
seatRect.xMax - m, seatRect.zMax - m);
if (!r.IsEmpty && seatHeight > 0.0005f)
plan.AddBox(name + "_Seat", BoxStyle.BenchSeat, r, seatHeight);
}
// ---------------- 小さな道具 ----------------
bool CheckGeneration()
{
if (string.IsNullOrEmpty(_generation))
{
Fail("meta.generation がありません");
return false;
}
if (summaryJson == null)
{
Fail("summary.json(summaryJson)が入っていません。版の番号を突き合わせられません");
return false;
}
string err;
JsonValue sum = JsonValue.Parse(summaryJson.text, out err);
if (sum == null || !sum.IsArray)
{
Fail("summary.json を読めません: " + err);
return false;
}
string key = string.IsNullOrEmpty(layoutFileName)
? (layoutJson != null ? layoutJson.name : "")
: layoutFileName;
for (int i = 0; i < sum.Count; i++)
{
if (sum[i]["file"].S("") != key) continue;
string g = sum[i]["generation"].S("");
if (g != _generation)
{
Fail("版の番号が summary.json と違います: この JSON " + _generation +
" / summary.json " + g + "(写し直してください)");
return false;
}
return true;
}
Fail("summary.json に \"" + key + "\" がありません(layoutFileName を確かめてください)");
return false;
}
float Req(JsonValue obj, string key, string where)
{
JsonValue v = obj[key];
if (v.kind != JsonValue.Kind.Number)
{
Fail(where + " がありません");
return 0f;
}
return v.F(0f);
}
bool TryRect(JsonValue v, string where, out PlanRect rect)
{
rect = new PlanRect(0f, 0f, 0f, 0f);
if (v.Count < 4)
{
Fail(where + " が [x0, y0, x1, y1] ではありません");
return false;
}
rect = new PlanRect(v[0].F(0f), v[1].F(0f), v[2].F(0f), v[3].F(0f));
return true;
}
bool TryPoint(JsonValue v, string where, out Vector3 p)
{
p = Vector3.zero;
if (v.Count < 2)
{
Fail(where + " が [x, y] ではありません");
return false;
}
p = new Vector3(v[0].F(0f), 0f, v[1].F(0f));
return true;
}
static bool TryParseKind(string s, out PlaceKind kind)
{
kind = PlaceKind.Kitchen;
if (string.IsNullOrEmpty(s)) return false;
try
{
kind = (PlaceKind)Enum.Parse(typeof(PlaceKind), s, false);
return true;
}
catch (Exception) { return false; }
}
/// 部屋のラベルを PlaceKind に直す(依頼の対応表どおり)
static bool TryRoomKind(string label, out PlaceKind kind)
{
kind = PlaceKind.Kitchen;
if (string.IsNullOrEmpty(label)) return false;
switch (label)
{
case "パントリー": kind = PlaceKind.Pantry; return true;
case "キッチン": kind = PlaceKind.Kitchen; return true;
case "冷蔵・冷凍・保存": kind = PlaceKind.Kitchen; return true;
case "洗い場": kind = PlaceKind.Kitchen; return true;
case "更衣室・社員トイレ": kind = PlaceKind.Office; return true;
case "トイレ": kind = PlaceKind.Toilet; return true;
case "待合": kind = PlaceKind.Waiting; return true;
case "レジ": kind = PlaceKind.Register; return true;
case "レジ内": kind = PlaceKind.RegisterBack; return true;
case "ドリンクバー": kind = PlaceKind.DrinkBar; return true;
}
return false;
}
static bool TryTableKind(string type, out SeatKind kind)
{
kind = SeatKind.WallBooth;
switch (type)
{
case "booth": kind = SeatKind.WallBooth; return true;
case "t2": kind = SeatKind.WallTable; return true;
case "booth6L": kind = SeatKind.LargeTable; return true;
}
return false;
}
static int NumberFromId(string id, int fallback)
{
if (string.IsNullOrEmpty(id)) return fallback;
int n = 0; bool any = false;
for (int i = 0; i < id.Length; i++)
{
char c = id[i];
if (c < '0' || c > '9') continue;
n = n * 10 + (c - '0');
any = true;
}
return any ? n : fallback;
}
/// GameObject の名前に使える形にする(/ は階層になってしまう)
static string Safe(string s)
{
if (string.IsNullOrEmpty(s)) return "x";
return s.Replace('/', '_').Replace('\\', '_');
}
void Fail(string message)
{
_problems.Add(message);
}
static string F(float v)
{
return v.ToString("0.##", CultureInfo.InvariantCulture);
}
}
}