using System; using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEditor.SceneManagement; using UnityEngine; using UnityEngine.SceneManagement; namespace ServiceSim.EditorTools { /// /// 1条件だけ回すメニュー(v2 の確かめ用)。 /// 条件と数字は ExperimentPlan の .asset。回し方だけここで決める。 /// public static class ServiceSimStage2 { [MenuItem("ServiceSim/Run One/1条件を回す(1コマ固定 0.05 秒)", false, 40)] public static void RunMeasure() { Run(0f, "v3", false, 0f); } [MenuItem("ServiceSim/Run One/1条件を回す(確かめ用 0.02 秒)", false, 41)] public static void RunReference() { Run(-1f, "v3ref", false, 0f); } // C1:停止点だけを変えて2回回す。ほかはすべて ExperimentPlan の trial… の値。 // Inspector を触らずにメニューから選べるようにして、出力フォルダ名に停止点を入れる // (…_off000 と …_off015)ので、2回目で1回目が上書きされない。 // 選んだ値はその回の Job にだけ使う。StoreLayout の .asset は書き換えない // (ExperimentRunner が回し終わりにもとの値へ戻す)。 [MenuItem("ServiceSim/Run One/停止点 0 で回す(通路の中央)", false, 50)] public static void RunOffset000() { Run(0f, "v3", true, 0f); } [MenuItem("ServiceSim/Run One/停止点 0.15 で回す(卓寄り・直す前と同じ)", false, 51)] public static void RunOffset015() { Run(0f, "v3", true, 0.15f); } // 見る用:その回だけ字幕と描画を出し、1倍速で進める。 // 設定ファイル(ExperimentPlan・StoreLayout)は書き換えない。 // 計測の比べには使わない回なので、出力フォルダの名前に _view を付ける。 [MenuItem("ServiceSim/Run One/見る用:字幕あり・描画あり(1倍速)", false, 55)] public static void RunView() { Run(0f, "view", false, 0f, true); } [MenuItem("ServiceSim/Run One/出力フォルダを開く", false, 60)] public static void OpenOutput() { ExperimentPlan plan = AssetDatabase.LoadAssetAtPath(ServiceSimSetup.ExperimentPlanPath); if (plan == null) { Debug.LogError("[ServiceSim] ExperimentPlan が見つかりません"); return; } if (!Directory.Exists(plan.trialOutputRoot)) { Debug.LogWarning("[ServiceSim] まだありません: " + plan.trialOutputRoot); return; } EditorUtility.RevealInFinder(plan.trialOutputRoot); } /// /// captureOverride: 0 なら plan の計測用、-1 なら plan の確かめ用。 /// setOffset が true なら、卓の停止点を offset(m)にしてこの回だけ回す。 /// .asset は書き換えない(ExperimentRunner が回し終わりにもとの値へ戻す)。 /// false なら .asset にいま入っている値のまま回す。 /// どちらでも、出力フォルダ名には実際に使う停止点を入れる。 /// static void Run(float captureOverride, string label, bool setOffset, float offset) { Run(captureOverride, label, setOffset, offset, false); } /// view が true なら、その回だけ字幕と描画を出して1倍速で回す(見る用) static void Run(float captureOverride, string label, bool setOffset, float offset, bool view) { ExperimentRunner runner; ExperimentPlan plan; if (!ServiceSimRunSupport.Prepare(out runner, out plan)) return; StoreLayout layout = CurrentLayout(); if (layout == null) { Debug.LogError("[ServiceSim] StoreLayout が見つかりません。ServiceSim/Setup を実行してください"); return; } float useOffset = setOffset ? offset : layout.deliveryOffsetFromAisleCenter; float capture = captureOverride < 0f ? plan.referenceCaptureDeltaTime : 0f; ExperimentRunner.Job job = new ExperimentRunner.Job(); job.aisleWidthIndex = plan.trialAisleWidthIndex; job.crowd = plan.trialCrowd; job.robots = plan.trialRobots; job.seed = plan.trialSeed; job.applyDeliveryOffset = setOffset; job.deliveryOffset = offset; job.folder = Path.Combine(plan.trialOutputRoot, "one_" + plan.runName + "_" + label + "_w" + plan.trialAisleWidthIndex + "_" + ExperimentPlan.CrowdName(plan.trialCrowd) + "_r" + plan.trialRobots + "_s" + plan.trialSeed + "_" + OffsetTag(useOffset) + (view ? "_view" : "") + "_" + DateTime.Now.ToString("yyyyMMdd_HHmm")); // フォルダ名に回し始めた時刻を入れるので、同じ条件でも上書きにならない if (Directory.Exists(job.folder)) Debug.LogWarning("[ServiceSim] このフォルダはすでにあります。上書きになります: " + job.folder); runner.SetSingleJob(job); runner.timeMode = view ? ExperimentRunner.TimeMode.RealTime : ExperimentRunner.TimeMode.FixedStep; runner.captureDeltaTimeOverride = view ? 0f : capture; runner.runLabel = label; runner.viewMode = view; runner.runOnPlay = true; EditorUtility.SetDirty(runner); float stepSec = capture > 0f ? capture : plan.measureCaptureDeltaTime; float simMin = plan.warmupMinutes + plan.measureMinutes; Debug.Log("[ServiceSim] " + (view ? "見る用に1条件を回します" : "1条件を回します") + "(Play に入ります)。" + (view ? "\n 見る用:字幕あり・描画あり・1倍速。設定ファイルは書き換えていません。" + "\n 計測の比べには使いません(フォルダ名に _view が付きます)。" + "\n 途中で Play を止めてよいです(そのときは何も書き出しません)" : "") + "\n 1コマ " + (view ? "実時間(1倍速)" : stepSec.ToString("0.###") + " 秒") + "\n 店の中 " + simMin + " 分(慣らし " + plan.warmupMinutes + " 分を含む)" + "\n 細い通路: 番号 " + plan.trialAisleWidthIndex + " / 混み具合: " + ExperimentPlan.CrowdName(plan.trialCrowd) + " / 台数: " + plan.trialRobots + " / 種: " + plan.trialSeed + "\n 停止点: " + useOffset.ToString("0.###") + " m" + (setOffset ? "(メニューが決めた値。この回だけ使い、.asset は " + layout.deliveryOffsetFromAisleCenter.ToString("0.###") + " m のまま)" : "(.asset の値)") + "\n 置き場所: " + job.folder + "\n おわると自動で Play を抜けます"); EditorApplication.EnterPlaymode(); } /// フォルダ名に入れる停止点(0 → off000、0.15 → off015) static string OffsetTag(float offsetM) { return "off" + Mathf.RoundToInt(offsetM * 100f).ToString("000"); } static StoreLayout CurrentLayout() { StoreBuilder builder = UnityEngine.Object.FindObjectOfType(); if (builder != null && builder.layout != null) return builder.layout; return AssetDatabase.LoadAssetAtPath(ServiceSimSetup.FamilyLayoutPath); } } /// メニューから回すときの、共通の下ごしらえ public static class ServiceSimRunSupport { public static bool Prepare(out ExperimentRunner runner, out ExperimentPlan plan) { runner = null; plan = null; if (EditorApplication.isPlayingOrWillChangePlaymode) { Debug.LogWarning("[ServiceSim] すでに Play 中です。止めてからもう一度押してください"); return false; } Scene active = SceneManager.GetActiveScene(); if (active.path != ServiceSimSetup.ScenePath) { if (!File.Exists(ServiceSimSetup.ScenePath)) { Debug.LogError("[ServiceSim] シーンがありません。先に ServiceSim/Setup を実行してください"); return false; } if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo()) return false; EditorSceneManager.OpenScene(ServiceSimSetup.ScenePath, OpenSceneMode.Single); } runner = UnityEngine.Object.FindObjectOfType(); if (runner == null) { Debug.LogError("[ServiceSim] シーンに ExperimentRunner がありません。ServiceSim/Setup を実行してください"); return false; } plan = AssetDatabase.LoadAssetAtPath(ServiceSimSetup.ExperimentPlanPath); if (plan == null) { Debug.LogError("[ServiceSim] ExperimentPlan が見つかりません"); return false; } if (runner.plan == null) runner.plan = plan; if (runner.service == null) runner.service = AssetDatabase.LoadAssetAtPath(ServiceSimSetup.ServiceSpecPath); if (runner.robotPrefab == null) runner.robotPrefab = AssetDatabase.LoadAssetAtPath( ServiceSimSetup.Root + "/Robot/ServingRobot.prefab"); if (runner.robotSpec == null) runner.robotSpec = AssetDatabase.LoadAssetAtPath(ServiceSimSetup.RobotSpecPath); if (runner.simParams == null) runner.simParams = AssetDatabase.LoadAssetAtPath(ServiceSimSetup.SimParamsPath); // 実時間の上限と、版の覚え書きは **段階Bだけ** が入れる。 // ここで 0・空に戻しておき、ほかのメニューの回し方を前と同じに保つ runner.maxWallSecondsPerRun = 0f; runner.unityCs = ""; runner.pendingNote = ""; return true; } /// Play を抜けたら runOnPlay を下ろす(手で Play しても回り出さないように) [InitializeOnLoadMethod] static void Hook() { EditorApplication.playModeStateChanged -= OnChanged; EditorApplication.playModeStateChanged += OnChanged; } static void OnChanged(PlayModeStateChange state) { if (state != PlayModeStateChange.EnteredEditMode) return; ExperimentRunner runner = UnityEngine.Object.FindObjectOfType(); if (runner == null) return; if (!runner.runOnPlay && !runner.viewMode) return; runner.runOnPlay = false; runner.viewMode = false; // 見る用は、その回だけ EditorUtility.SetDirty(runner); } } }