using System;
using System.Collections.Generic;
using System.IO;
using UnityEditor;
using UnityEditor.SceneManagement;
using UnityEngine;
using UnityEngine.AI;
using UnityEngine.SceneManagement;
namespace ServiceSim.EditorTools
{
///
/// Assets/ServiceSim/ の中の .asset・prefab・シーンを作る。
/// すでにあるものは作り直さないが、マテリアルのシェーダーだけは毎回確かめて、
/// いまの描画方式に合わせ直す(合っていないとピンクになるため)。
/// Assets/ServiceSim/ の外のファイルは一切作らない・変えない。
/// 新しいシーンはビルド設定に足さない。
///
public static class ServiceSimSetup
{
public const string Root = "Assets/ServiceSim";
public const string ScenePath = Root + "/Scenes/ServiceSim_Family.unity";
public const string FamilyLayoutPath = Root + "/Store/StoreLayout_Family.asset";
public const string GridLayoutPath = Root + "/Store/StoreLayout_Grid.asset";
public const string RobotSpecPath = Root + "/Robot/RobotSpec.asset";
public const string PeopleSpecPath = Root + "/People/PeopleSpec.asset";
public const string ExperimentPlanPath = Root + "/Experiment/ExperimentPlan.asset";
public const string ServiceSpecPath = Root + "/Service/ServiceSpec.asset";
/// サイトの試算の値(v5)。serving-robot-params.json を読む .asset
public const string SimParamsPath = Root + "/Experiment/SimParams.asset";
/// 座っている客の形(見た目だけ)の prefab
public const string SeatedBodyPrefabPath = Root + "/People/SeatedGuestBody.prefab";
/// 客の黄色(立っている客・座っている客で同じ色を使う)
static readonly Color GuestColor = new Color(0.98f, 0.80f, 0.13f);
/// スタッフの青・待合の客の紫(見た目を作り直すときにも使う)
static readonly Color StaffColor = new Color(0.09f, 0.38f, 0.90f);
static readonly Color WaitingColor = new Color(0.55f, 0.30f, 0.75f);
const string MatDir = Root + "/Store/Materials";
const string StorePrefabDir = Root + "/Store/Prefabs";
const string OldDir = Root + "/Store/old~";
enum WalkerKind { Staff, Guest, Waiting }
static Shader _shader;
static int _materialsFixed;
[MenuItem("ServiceSim/Check Render Pipeline", false, 1)]
public static void CheckRenderPipeline()
{
string how;
Shader shader = ServiceSimRender.FindOpaqueShader(out how);
string body = ServiceSimRender.Describe() + "使うシェーダー: " + how;
if (shader == null) Debug.LogError("[ServiceSim] 描画方式に合うシェーダーが見つかりません\n" + body);
else Debug.Log("[ServiceSim] 描画方式のしらべ\n" + body);
}
[MenuItem("ServiceSim/Use Store Layout/Family (ファミレス型)", false, 30)]
public static void UseFamilyLayout() { SwitchLayout(FamilyLayoutPath); }
[MenuItem("ServiceSim/Use Store Layout/Grid (格子型・比べる用)", false, 31)]
public static void UseGridLayout() { SwitchLayout(GridLayoutPath); }
static void SwitchLayout(string path)
{
StoreLayout layout = AssetDatabase.LoadAssetAtPath(path);
if (layout == null)
{
Debug.LogError("[ServiceSim] " + path + " が読めません。先に ServiceSim/Setup を実行してください");
return;
}
StoreBuilder builder = UnityEngine.Object.FindObjectOfType();
NavMeshBaker baker = UnityEngine.Object.FindObjectOfType();
if (builder == null)
{
Debug.LogError("[ServiceSim] シーンに StoreBuilder がありません。ServiceSim_Family シーンを開いてください");
return;
}
builder.layout = layout;
if (baker != null) baker.layout = layout;
EditorUtility.SetDirty(builder);
if (baker != null) EditorUtility.SetDirty(baker);
EditorSceneManager.MarkSceneDirty(SceneManager.GetActiveScene());
Debug.Log("[ServiceSim] 店の形を切り替えました: " + layout.name + "(" + layout.StoreKindName + ")" +
"\n このあと ServiceSim/Build Store を実行してください");
}
[MenuItem("ServiceSim/Setup (アセット・prefab・シーンを作る)", false, 10)]
public static void Setup()
{
// まず描画方式を確かめる。合うシェーダーが無ければ何も作らずに止める
string how;
_shader = ServiceSimRender.FindOpaqueShader(out how);
if (_shader == null)
{
Debug.LogError("[ServiceSim] この描画方式に合うシェーダーが見つかりません。" +
"マテリアルを作らずに止めました。\n" +
ServiceSimRender.Describe() + "探した結果: " + how);
return;
}
_materialsFixed = 0;
if (!EditorSceneManager.SaveCurrentModifiedScenesIfUserWantsTo())
{
Debug.Log("[ServiceSim] Setup を取りやめました");
return;
}
EnsureFolders();
// 前の版の StoreLayout_Family.asset(格子型)は種類が変わったので、
// Unity が見ないフォルダ(old~)へ寄せておく。消さない
string moved = MoveStaleFamilyLayoutAside();
Scene scene;
if (File.Exists(ScenePath))
{
scene = EditorSceneManager.OpenScene(ScenePath, OpenSceneMode.Single);
}
else
{
scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
EditorSceneManager.SaveScene(scene, ScenePath);
scene = EditorSceneManager.OpenScene(ScenePath, OpenSceneMode.Single);
}
// 設定ファイル(数字の置き場所)
BandStoreLayout family = LoadOrCreate(FamilyLayoutPath);
LoadOrCreate(GridLayoutPath);
RobotSpec robotSpec = LoadOrCreate(RobotSpecPath);
PeopleSpec peopleSpec = LoadOrCreate(PeopleSpecPath);
ExperimentPlan plan = LoadOrCreate(ExperimentPlanPath);
ServiceSpec serviceSpec = LoadOrCreate(ServiceSpecPath);
// 見た目。真上から見て、床・通路・卓・椅子・ベンチ・人・ロボットが見分けられる色にする
Material matFloor = Mat("Mat_Floor", new Color(0.86f, 0.84f, 0.79f)); // 床:明るいクリーム
Material matAisle = Mat("Mat_Aisle", new Color(0.52f, 0.58f, 0.67f)); // 通路:青灰色
Material matWall = Mat("Mat_Wall", new Color(0.40f, 0.43f, 0.48f)); // 壁・厨房・事務所
Material matBench = Mat("Mat_Bench", new Color(0.55f, 0.29f, 0.26f)); // ベンチ・仕切り:暗い赤茶
Material matTable = Mat("Mat_Table", new Color(0.86f, 0.62f, 0.30f)); // 卓:明るい木の色
Material matChair = Mat("Mat_Chair", new Color(0.13f, 0.37f, 0.42f)); // 椅子:濃い青緑
Material matCounter = Mat("Mat_Counter", new Color(0.20f, 0.22f, 0.26f)); // カウンター類:ほぼ黒
Material matRobot = Mat("Mat_Robot", new Color(0.96f, 0.96f, 0.97f)); // ロボット:白
Material matRobotFront = Mat("Mat_RobotFront", new Color(1.00f, 0.33f, 0.04f)); // ロボットの前:橙
Material matStaff = Mat("Mat_Staff", StaffColor); // スタッフ:青
Material matGuest = Mat("Mat_Guest", GuestColor); // 歩く客:黄
Material matWaiting = Mat("Mat_WaitingGuest", WaitingColor); // 待合の客:紫
// ベンチの見た目(当たりなし)。色と素材はここ1か所で変えられる
Material matBenchSeat = Mat("Mat_BenchSeat", new Color(0.62f, 0.33f, 0.29f)); // 座面:赤茶(明るめ)
Material matBenchBack = Mat("Mat_BenchBack", new Color(0.48f, 0.25f, 0.22f)); // 背もたれ:赤茶(暗め)
// 床の塗り分け(見た目だけ)。家具の色が透けて見える程度にして、光と影も乗せる
Material matRobotFloor = TransparentMat("Mat_RobotFloor", new Color(0.45f, 0.78f, 0.95f, 0.35f));
Material matPeopleFloor = TransparentMat("Mat_PeopleFloor", new Color(1.00f, 0.90f, 0.35f, 0.45f));
// 部品(prefab)。無ければ作る
EnsureBoxPrefab(StorePrefabDir + "/Floor.prefab", "Floor", matFloor, true);
EnsureBoxPrefab(StorePrefabDir + "/Wall.prefab", "Wall", matWall, true);
EnsureBoxPrefab(StorePrefabDir + "/Bench.prefab", "Bench", matBench, true);
EnsureBoxPrefab(StorePrefabDir + "/Table_Booth4.prefab", "Table_Booth4", matTable, true);
EnsureBoxPrefab(StorePrefabDir + "/KitchenPass.prefab", "KitchenPass", matCounter, true);
// 通路の色板。当たりは付けない(NavMesh に焼かれないようにする)
EnsureBoxPrefab(StorePrefabDir + "/AisleMark.prefab", "AisleMark", matAisle, false);
// ベンチの見た目(座面・背もたれ)。当たりは付けない
// (NavMesh・前のセンサー・左右の空きの測りに効かないようにする)
EnsureBoxPrefab(StorePrefabDir + "/BenchSeat.prefab", "BenchSeat", matBenchSeat, false);
EnsureBoxPrefab(StorePrefabDir + "/BenchBack.prefab", "BenchBack", matBenchBack, false);
// ベンチの当たりの箱。大きさ・位置・高さはベンチのまま、**見た目を持たない**
EnsureColliderOnlyBoxPrefab(StorePrefabDir + "/BenchBody.prefab", "BenchBody");
// 色を変えたときに追いつくよう、すでにある prefab のマテリアルも合わせ直す
SetPrefabMaterial(StorePrefabDir + "/BenchSeat.prefab", matBenchSeat);
SetPrefabMaterial(StorePrefabDir + "/BenchBack.prefab", matBenchBack);
EnsureChairPrefab(StorePrefabDir + "/Chair.prefab", matChair);
EnsureRobotPrefab(Root + "/Robot/ServingRobot.prefab", robotSpec, matRobot, matRobotFront);
EnsureWalkerPrefab(Root + "/People/Staff.prefab", "Staff", peopleSpec, matStaff, WalkerKind.Staff);
EnsureWalkerPrefab(Root + "/People/Guest.prefab", "Guest", peopleSpec, matGuest, WalkerKind.Guest);
EnsureWalkerPrefab(Root + "/People/WaitingGuest.prefab", "WaitingGuest", peopleSpec, matWaiting, WalkerKind.Waiting);
EnsureSeatedGuestPrefab(Root + "/People/SeatedGuest.prefab", peopleSpec);
EnsureSeatedBodyPrefab(SeatedBodyPrefabPath, peopleSpec, matGuest);
// すでにある人の prefab の見た目を、胴+頭にそろえる(当たり判定などは触らない)
EnsurePersonLook(peopleSpec);
// 種類の印(Tag)。あとで映像から見分ける学習に使う。いまは付けるだけ
int tagsAdded = EnsureTags();
SetPrefabTag(Root + "/Robot/ServingRobot.prefab", SimTags.Robot);
SetPrefabTag(Root + "/People/Staff.prefab", SimTags.Staff);
SetPrefabTag(Root + "/People/Guest.prefab", SimTags.Guest);
SetPrefabTag(Root + "/People/WaitingGuest.prefab", SimTags.Guest);
SetPrefabTag(SeatedBodyPrefabPath, SimTags.SeatedGuest);
SetPrefabTag(StorePrefabDir + "/Chair.prefab", SimTags.Chair);
SetPrefabTag(StorePrefabDir + "/BenchSeat.prefab", SimTags.Bench);
SetPrefabTag(StorePrefabDir + "/BenchBack.prefab", SimTags.Bench);
SetPrefabTag(StorePrefabDir + "/BenchBody.prefab", SimTags.Bench);
// すでに作ってある prefab も直す。
// ・NavMeshAgent は切る(置く前に有効だと「NavMesh に近くないので作れません」の警告が出る)
// ・通路の色板の当たりは外す(NavMesh に焼かれないようにする)
int agentsDisabled = 0;
agentsDisabled += FixPrefab(Root + "/Robot/ServingRobot.prefab", true, false) ? 1 : 0;
agentsDisabled += FixPrefab(Root + "/People/Staff.prefab", true, false) ? 1 : 0;
agentsDisabled += FixPrefab(Root + "/People/Guest.prefab", true, false) ? 1 : 0;
agentsDisabled += FixPrefab(Root + "/People/WaitingGuest.prefab", true, false) ? 1 : 0;
int collidersRemoved = FixPrefab(StorePrefabDir + "/AisleMark.prefab", false, true) ? 1 : 0;
collidersRemoved += FixPrefab(StorePrefabDir + "/BenchSeat.prefab", false, true) ? 1 : 0;
collidersRemoved += FixPrefab(Root + "/People/SeatedGuestBody.prefab", false, true) ? 1 : 0;
// 人の種類(止まった原因を分けるのに使う)を、すでにある prefab にも入れ直す
int kindsFixed = 0;
kindsFixed += FixPersonKind(Root + "/People/Staff.prefab", PersonKind.Staff) ? 1 : 0;
kindsFixed += FixPersonKind(Root + "/People/Guest.prefab", PersonKind.Guest) ? 1 : 0;
kindsFixed += FixPersonKind(Root + "/People/WaitingGuest.prefab", PersonKind.Waiting) ? 1 : 0;
kindsFixed += FixPersonKind(Root + "/People/SeatedGuest.prefab", PersonKind.Seated) ? 1 : 0;
// 直したあとに読み直す(保存し直すと、前に取った参照が使えなくなるため)
AssetDatabase.Refresh();
GameObject floorPrefab = Load(StorePrefabDir + "/Floor.prefab");
GameObject wallPrefab = Load(StorePrefabDir + "/Wall.prefab");
GameObject benchPrefab = Load(StorePrefabDir + "/Bench.prefab");
GameObject tablePrefab = Load(StorePrefabDir + "/Table_Booth4.prefab");
GameObject passPrefab = Load(StorePrefabDir + "/KitchenPass.prefab");
GameObject aisleMarkPrefab = Load(StorePrefabDir + "/AisleMark.prefab");
GameObject benchSeatPrefab = Load(StorePrefabDir + "/BenchSeat.prefab");
GameObject chairPrefab = Load(StorePrefabDir + "/Chair.prefab");
GameObject robotPrefab = Load(Root + "/Robot/ServingRobot.prefab");
GameObject staffPrefab = Load(Root + "/People/Staff.prefab");
GameObject guestPrefab = Load(Root + "/People/Guest.prefab");
GameObject waitingPrefab = Load(Root + "/People/WaitingGuest.prefab");
GameObject seatedPrefab = Load(Root + "/People/SeatedGuest.prefab");
GameObject seatedBodyPrefab = Load(SeatedBodyPrefabPath);
if (floorPrefab == null || wallPrefab == null || benchPrefab == null || tablePrefab == null ||
passPrefab == null || aisleMarkPrefab == null || benchSeatPrefab == null ||
chairPrefab == null || robotPrefab == null ||
staffPrefab == null || guestPrefab == null || waitingPrefab == null || seatedPrefab == null)
{
Debug.LogError("[ServiceSim] prefab を用意できませんでした。止めます");
return;
}
// シーンの組み立て(置くだけ。中身は持たない)
GameObject root = FindOrCreateRoot(scene, "ServiceSim");
GameObject storeGo = FindOrCreateChild(root.transform, "Store");
StoreBuilder builder = Ensure(storeGo);
if (builder.layout == null) builder.layout = family;
builder.floorPrefab = floorPrefab;
builder.wallPrefab = wallPrefab;
builder.benchPrefab = benchPrefab;
builder.benchSeatPrefab = benchSeatPrefab;
builder.benchBackPrefab = Load(StorePrefabDir + "/BenchBack.prefab");
builder.benchBodyPrefab = Load(StorePrefabDir + "/BenchBody.prefab");
builder.tablePrefab = tablePrefab;
builder.chairPrefab = chairPrefab;
builder.kitchenPassPrefab = passPrefab;
builder.aisleMarkPrefab = aisleMarkPrefab;
builder.robotFloorMaterial = matRobotFloor;
builder.peopleFloorMaterial = matPeopleFloor;
NavMeshBaker baker = Ensure(storeGo);
baker.layout = builder.layout;
baker.bakeRoot = storeGo.transform;
baker.store = builder;
baker.peopleRadius = peopleSpec.personRadius; // 人用の NavMesh を焼く半径
baker.registerBackArea = peopleSpec.registerBackArea; // 店員側の区画(客は入れない)
GameObject peopleGo = FindOrCreateChild(root.transform, "People");
PeopleSpawner spawner = Ensure(peopleGo);
spawner.spec = peopleSpec;
spawner.store = builder;
spawner.staffPrefab = staffPrefab;
spawner.seatedGuestPrefab = seatedPrefab;
spawner.seatedBodyPrefab = seatedBodyPrefab;
spawner.service = serviceSpec;
GameObject robotsGo = FindOrCreateChild(root.transform, "Robots");
robotsGo.transform.position = Vector3.zero;
// 店の回し方(卓の空き・料理の待ち行列・仕事板)と、1組ごとの流れ
GameObject svcGo = FindOrCreateChild(root.transform, "Service");
ServiceOps opsC = Ensure(svcGo);
opsC.spec = serviceSpec;
opsC.robotSpec = robotSpec;
opsC.store = builder;
GroupManager groupsC = Ensure(svcGo);
groupsC.spec = serviceSpec;
groupsC.people = peopleSpec;
groupsC.ops = opsC;
groupsC.store = builder;
groupsC.guestPrefab = guestPrefab;
groupsC.waitingGuestPrefab = waitingPrefab;
groupsC.bodyRoot = svcGo.transform;
JamWatcher jamsC = Ensure(svcGo);
jamsC.spec = peopleSpec;
jamsC.ops = opsC;
jamsC.store = builder;
spawner.ops = opsC;
GameObject expGo = FindOrCreateChild(root.transform, "Experiment");
ExperimentRunner runner = Ensure(expGo);
runner.plan = plan;
runner.service = serviceSpec;
// サイトの試算の値(v5 3章)。無くてもこれまでの回し方はできる
runner.simParams = AssetDatabase.LoadAssetAtPath(SimParamsPath);
runner.ops = opsC;
runner.groups = groupsC;
runner.jams = jamsC;
runner.store = builder;
runner.baker = baker;
runner.people = spawner;
runner.robotSpec = robotSpec;
runner.robotPrefab = robotPrefab;
runner.robotsRoot = robotsGo.transform;
runner.runOnPlay = false; // メニューから回すときだけ立てる
EditorUtility.SetDirty(runner);
EnsureLighting(root.transform);
EnsureCameras(root.transform);
EditorUtility.SetDirty(builder);
EditorUtility.SetDirty(baker);
EditorUtility.SetDirty(spawner);
EditorUtility.SetDirty(opsC);
EditorUtility.SetDirty(groupsC);
EditorUtility.SetDirty(jamsC);
EditorSceneManager.MarkSceneDirty(scene);
EditorSceneManager.SaveScene(scene, ScenePath);
// .asset に書かれていない項目を、いまの初期値で書きそろえる。
// すでに書かれている値は、読み込んだときにそのまま入っているので上書きされない。
string added = FillMissingAssetFields(new ScriptableObject[]
{
family, robotSpec, peopleSpec, plan, serviceSpec,
AssetDatabase.LoadAssetAtPath(GridLayoutPath)
});
AssetDatabase.SaveAssets();
AssetDatabase.Refresh();
Debug.Log("[ServiceSim] Setup おわり。" +
"\n .asset に書き足した項目: " + (added.Length > 0 ? added : "なし") +
"\n 描画方式: " + (CurrentPipeline() == null ? "Built-in" : CurrentPipeline().GetType().Name) +
"\n 使うシェーダー: " + _shader.name + "(" + how + ")" +
"\n シェーダーを直したマテリアル: " + _materialsFixed + " 個" +
"\n NavMeshAgent を切った prefab: " + agentsDisabled + " 個" +
"\n 当たりを外した通路板: " + collidersRemoved + " 個" +
"\n 人の種類を直した prefab: " + kindsFixed + " 個" +
(moved.Length > 0 ? "\n 前の版の店の設定を寄せました: " + moved : "") +
"\n いまの店の形: " + builder.layout.name + "(" + builder.layout.StoreKindName + ")" +
"\n 店の設定: " + FamilyLayoutPath + " / " + GridLayoutPath +
"\n シーン: " + ScenePath + "(run_name=" + plan.runName + ")" +
"\n ※ このシーンはビルド設定に足していません" +
"\n 種類の印(Tag): " + SimTags.All.Length + " 種(新しく作った " + tagsAdded + " 種)");
}
// ---------------- .asset の項目を書きそろえる ----------------
///
/// .asset に書かれていない項目を、いまの初期値で書き出す。
/// Unity は .asset を読むとき、書かれていない項目に C# の初期値を入れる。
/// そのまま SetDirty して保存すると、読み込んだ値はそのまま・足りない項目だけが増える。
///
/// これをしないと、設計書の「数字はすべて .asset に置く」が守られず、
/// 実際の値が .cs の初期値になってしまう。
/// 何を書き足したかを返す(報告に出す)。
///
static string FillMissingAssetFields(ScriptableObject[] assets)
{
List added = new List();
for (int i = 0; i < assets.Length; i++)
{
ScriptableObject a = assets[i];
if (a == null) continue;
string path = AssetDatabase.GetAssetPath(a);
if (string.IsNullOrEmpty(path)) continue;
HashSet before = TopLevelKeys(path);
EditorUtility.SetDirty(a);
AssetDatabase.SaveAssets();
HashSet after = TopLevelKeys(path);
foreach (string k in after)
if (!before.Contains(k)) added.Add(a.name + "." + k);
}
if (added.Count == 0) return "";
added.Sort();
return string.Join(", ", added.ToArray());
}
/// .asset(YAML)の、いちばん外側の項目の名前を集める
static HashSet TopLevelKeys(string path)
{
HashSet keys = new HashSet();
if (!File.Exists(path)) return keys;
string[] lines = File.ReadAllLines(path);
for (int i = 0; i < lines.Length; i++)
{
string line = lines[i];
// MonoBehaviour の中身は半角スペース2つ下げ。さらに下げた行は中の要素なので見ない
if (!line.StartsWith(" ") || line.StartsWith(" ")) continue;
int colon = line.IndexOf(':');
if (colon <= 2) continue;
string name = line.Substring(2, colon - 2).Trim();
if (name.Length == 0 || name.StartsWith("-")) continue;
keys.Add(name);
}
return keys;
}
static UnityEngine.Rendering.RenderPipelineAsset CurrentPipeline()
{
return UnityEngine.Rendering.GraphicsSettings.currentRenderPipeline;
}
// ---------------- 前の版の .asset を寄せる ----------------
///
/// StoreLayout_Family.asset が前の版(格子型)のままなら、Unity が見ないフォルダへ寄せる。
/// 消さない。寄せたら、そのパスを返す。
///
static string MoveStaleFamilyLayoutAside()
{
if (!File.Exists(FamilyLayoutPath)) return "";
if (AssetDatabase.LoadAssetAtPath(FamilyLayoutPath) != null) return "";
try
{
Directory.CreateDirectory(OldDir);
string dst = OldDir + "/StoreLayout_Family_v0_grid.asset";
int n = 1;
while (File.Exists(dst))
{
dst = OldDir + "/StoreLayout_Family_v0_grid_" + n.ToString() + ".asset";
n++;
}
File.Move(FamilyLayoutPath, dst);
if (File.Exists(FamilyLayoutPath + ".meta")) File.Move(FamilyLayoutPath + ".meta", dst + ".meta");
AssetDatabase.Refresh();
return dst;
}
catch (Exception e)
{
Debug.LogError("[ServiceSim] 前の版の " + FamilyLayoutPath + " を寄せられませんでした。" +
"手で消すか動かしてから、もう一度 Setup を実行してください\n" + e.Message);
return "";
}
}
// ---------------- フォルダ ----------------
static void EnsureFolders()
{
EnsureFolder("Assets", "ServiceSim");
EnsureFolder(Root, "Scenes");
EnsureFolder(Root, "Store");
EnsureFolder(Root + "/Store", "Prefabs");
EnsureFolder(Root + "/Store", "Materials");
EnsureFolder(Root, "Robot");
EnsureFolder(Root, "People");
EnsureFolder(Root, "Experiment");
EnsureFolder(Root, "Service");
EnsureFolder(Root, "Recording");
}
static void EnsureFolder(string parent, string name)
{
string path = parent + "/" + name;
if (!AssetDatabase.IsValidFolder(path)) AssetDatabase.CreateFolder(parent, name);
}
// ---------------- 設定ファイル ----------------
static T LoadOrCreate(string path) where T : ScriptableObject
{
T asset = AssetDatabase.LoadAssetAtPath(path);
if (asset != null) return asset;
asset = ScriptableObject.CreateInstance();
AssetDatabase.CreateAsset(asset, path);
return asset;
}
// ---------------- 見た目 ----------------
///
/// マテリアルを用意する。新しく作るときは色も付ける。
/// すでにあるときはシェーダーだけ確かめて、合っていなければ直し、
/// そのときだけ色も付け直す(手で変えた色を毎回消さないため)。
///
static Material Mat(string name, Color color)
{
string path = MatDir + "/" + name + ".mat";
Material mat = AssetDatabase.LoadAssetAtPath(path);
if (mat == null)
{
mat = new Material(_shader);
ServiceSimRender.ApplyLook(mat, color);
AssetDatabase.CreateAsset(mat, path);
return mat;
}
bool wrong = mat.shader == null
|| mat.shader != _shader
|| mat.shader.name == ServiceSimRender.ErrorShaderName
|| !mat.shader.isSupported;
if (wrong)
{
mat.shader = _shader;
ServiceSimRender.ApplyLook(mat, color);
EditorUtility.SetDirty(mat);
_materialsFixed++;
}
return mat;
}
// ---------------- 部品 ----------------
///
/// 透けて光が乗るマテリアルを作る(床の塗り分け用)。
/// すでにあればシェーダーと色だけ合わせ直す(止まっているときと再生中で同じ見た目にする)。
///
static Material TransparentMat(string name, Color color)
{
string how;
Shader sh = ServiceSimRender.FindTransparentLitShader(out how);
string path = MatDir + "/" + name + ".mat";
Material mat = AssetDatabase.LoadAssetAtPath(path);
if (mat == null)
{
mat = new Material(sh != null ? sh : _shader);
AssetDatabase.CreateAsset(mat, path);
}
if (sh != null && mat.shader != sh) { mat.shader = sh; _materialsFixed++; }
mat.color = color;
if (mat.HasProperty("_BaseColor")) mat.SetColor("_BaseColor", color);
// Standard しか無いときは、透過の設定も入れる
if (mat.shader != null && mat.shader.name == "Standard")
{
mat.SetFloat("_Mode", 3f);
mat.SetOverrideTag("RenderType", "Transparent");
mat.SetInt("_SrcBlend", (int)UnityEngine.Rendering.BlendMode.SrcAlpha);
mat.SetInt("_DstBlend", (int)UnityEngine.Rendering.BlendMode.OneMinusSrcAlpha);
mat.SetInt("_ZWrite", 0);
mat.DisableKeyword("_ALPHATEST_ON");
mat.EnableKeyword("_ALPHABLEND_ON");
mat.renderQueue = 3000;
}
EditorUtility.SetDirty(mat);
return mat;
}
static void EnsureBoxPrefab(string path, string name, Material mat, bool withCollider)
{
if (AssetDatabase.LoadAssetAtPath(path) != null) return;
GameObject go = GameObject.CreatePrimitive(PrimitiveType.Cube);
go.name = name;
SetMaterial(go, mat);
if (!withCollider)
{
Collider col = go.GetComponent();
if (col != null) UnityEngine.Object.DestroyImmediate(col);
}
PrefabUtility.SaveAsPrefabAsset(go, path);
UnityEngine.Object.DestroyImmediate(go);
}
///
/// 当たりだけの箱(見た目を持たない)。ベンチの当たりの箱に使う。
/// 大きさ・位置・高さは今までのベンチと同じなので、NavMesh も前のセンサーも変わらない。
///
static void EnsureColliderOnlyBoxPrefab(string path, string name)
{
if (AssetDatabase.LoadAssetAtPath(path) != null) return;
GameObject go = GameObject.CreatePrimitive(PrimitiveType.Cube);
go.name = name;
// 見た目だけ外す(当たりは残す)
MeshRenderer mr = go.GetComponent();
if (mr != null) UnityEngine.Object.DestroyImmediate(mr);
MeshFilter mf = go.GetComponent();
if (mf != null) UnityEngine.Object.DestroyImmediate(mf);
PrefabUtility.SaveAsPrefabAsset(go, path);
UnityEngine.Object.DestroyImmediate(go);
}
/// prefab のマテリアルを合わせ直す(色を1か所で変えられるように)
static void SetPrefabMaterial(string path, Material mat)
{
if (mat == null || !File.Exists(path)) return;
GameObject contents = PrefabUtility.LoadPrefabContents(path);
if (contents == null) return;
try
{
bool changed = false;
MeshRenderer[] rs = contents.GetComponentsInChildren(true);
for (int i = 0; i < rs.Length; i++)
{
if (rs[i].sharedMaterial == mat) continue;
rs[i].sharedMaterial = mat;
changed = true;
}
if (changed) PrefabUtility.SaveAsPrefabAsset(contents, path);
}
finally { PrefabUtility.UnloadPrefabContents(contents); }
}
/// prefab に種類の印(Tag)を付ける
static void SetPrefabTag(string path, string tag)
{
if (string.IsNullOrEmpty(tag) || !File.Exists(path)) return;
GameObject contents = PrefabUtility.LoadPrefabContents(path);
if (contents == null) return;
try
{
if (contents.tag == tag) return;
contents.tag = tag;
PrefabUtility.SaveAsPrefabAsset(contents, path);
}
catch (UnityException)
{
Debug.LogWarning("[ServiceSim] Tag \"" + tag + "\" を付けられませんでした: " + path);
}
finally { PrefabUtility.UnloadPrefabContents(contents); }
}
///
/// 種類の印(Tag)を ProjectSettings/TagManager.asset に足す。
/// Tag は物理にも NavMesh にも効かないので、測る数字は変わらない。
/// **Assets/ServiceSim の外に出るのはここだけ**(LABEL_RULES.md に書いてある)。
///
static int EnsureTags()
{
int added = 0;
for (int i = 0; i < SimTags.All.Length; i++)
{
string tag = SimTags.All[i];
if (System.Array.IndexOf(UnityEditorInternal.InternalEditorUtility.tags, tag) >= 0) continue;
UnityEditorInternal.InternalEditorUtility.AddTag(tag);
added++;
}
return added;
}
static void EnsureChairPrefab(string path, Material mat)
{
if (AssetDatabase.LoadAssetAtPath(path) != null) return;
GameObject go = GameObject.CreatePrimitive(PrimitiveType.Cube);
go.name = "Chair";
SetMaterial(go, mat);
NavMeshObstacle obstacle = go.AddComponent();
obstacle.shape = NavMeshObstacleShape.Box;
obstacle.center = Vector3.zero;
obstacle.size = Vector3.one;
obstacle.carving = true;
obstacle.carveOnlyStationary = false;
obstacle.enabled = false; // 引かれているあいだだけ有効にする
go.AddComponent();
PrefabUtility.SaveAsPrefabAsset(go, path);
UnityEngine.Object.DestroyImmediate(go);
}
static void EnsureRobotPrefab(string path, RobotSpec spec, Material body, Material front)
{
if (AssetDatabase.LoadAssetAtPath(path) != null) return;
float r = spec.Radius;
float h = spec.height;
GameObject go = new GameObject("ServingRobot");
GameObject bodyGo = GameObject.CreatePrimitive(PrimitiveType.Cylinder);
bodyGo.name = "Body";
bodyGo.transform.SetParent(go.transform, false);
bodyGo.transform.localPosition = new Vector3(0f, h * 0.5f, 0f);
bodyGo.transform.localScale = new Vector3(spec.diameter, h * 0.5f, spec.diameter);
UnityEngine.Object.DestroyImmediate(bodyGo.GetComponent());
SetMaterial(bodyGo, body);
GameObject frontGo = GameObject.CreatePrimitive(PrimitiveType.Cube);
frontGo.name = "FrontMark";
frontGo.transform.SetParent(go.transform, false);
frontGo.transform.localPosition = new Vector3(0f, h * 0.86f, r * 0.9f);
frontGo.transform.localScale = new Vector3(spec.diameter * 0.7f, 0.08f, 0.08f);
UnityEngine.Object.DestroyImmediate(frontGo.GetComponent());
SetMaterial(frontGo, front);
CapsuleCollider col = go.AddComponent();
col.radius = r;
col.height = h;
col.center = new Vector3(0f, h * 0.5f, 0f);
NavMeshAgent agent = go.AddComponent();
agent.radius = r;
agent.height = h;
agent.speed = spec.maxSpeed;
agent.acceleration = spec.acceleration;
agent.angularSpeed = spec.turnSpeedDegPerSec;
agent.obstacleAvoidanceType = ObstacleAvoidanceType.NoObstacleAvoidance;
agent.avoidancePriority = spec.avoidancePriority;
agent.autoRepath = false;
agent.updatePosition = false;
agent.updateRotation = false;
agent.enabled = false; // 置いたあとに有効にする(警告を出さないため)
ServingRobotController controller = go.AddComponent();
FrontSensor sensor = go.AddComponent();
sensor.spec = spec;
sensor.owner = controller;
controller.spec = spec;
controller.sensor = sensor;
PrefabUtility.SaveAsPrefabAsset(go, path);
UnityEngine.Object.DestroyImmediate(go);
}
///
/// 人の見た目(胴+頭)を作る。立っている人も座っている客も同じ作りにする。
/// 当たり判定は付けない(親が持っているものには触れない)。
/// bottomY … 足もと(立っている人は 0、座っている客は座面の高さ)
/// topY … 頭の先(立っている人は背の高さ、座っている客は 1.35 m)
/// すでにある "Body" と "Head" は作り直す。ほかの子どもと親の部品には触れない。
///
static void BuildPersonLook(GameObject root, float radius, float bottomY, float topY, Material mat)
{
if (root == null) return;
for (int i = root.transform.childCount - 1; i >= 0; i--)
{
Transform c = root.transform.GetChild(i);
if (c.name == "Body" || c.name == "Head")
UnityEngine.Object.DestroyImmediate(c.gameObject);
}
float span = Mathf.Max(0.2f, topY - bottomY);
float headR = Mathf.Min(0.11f, span * 0.25f);
float headCenter = topY - headR;
float bodyH = Mathf.Max(0.1f, headCenter - bottomY); // 頭は胴に少し食い込ませる
GameObject body = GameObject.CreatePrimitive(PrimitiveType.Capsule);
body.name = "Body";
body.transform.SetParent(root.transform, false);
body.transform.localPosition = new Vector3(0f, bottomY + bodyH * 0.5f, 0f);
body.transform.localScale = new Vector3(radius * 2f, bodyH * 0.5f, radius * 2f);
UnityEngine.Object.DestroyImmediate(body.GetComponent());
SetMaterial(body, mat);
GameObject head = GameObject.CreatePrimitive(PrimitiveType.Sphere);
head.name = "Head";
head.transform.SetParent(root.transform, false);
head.transform.localPosition = new Vector3(0f, headCenter, 0f);
head.transform.localScale = new Vector3(headR * 2f, headR * 2f, headR * 2f);
UnityEngine.Object.DestroyImmediate(head.GetComponent());
SetMaterial(head, mat);
}
///
/// すでにある人の prefab の **見た目だけ** を胴+頭に取り替える。
/// 当たり判定・NavMeshAgent・PersonBody・よける処理・Tag には触れない。
/// もう胴+頭になっていれば何もしない。
///
static bool FixWalkerLook(string path, PeopleSpec spec, Material mat)
{
if (!File.Exists(path) || spec == null || mat == null) return false;
GameObject contents = PrefabUtility.LoadPrefabContents(path);
if (contents == null) return false;
try
{
float r = spec.personRadius;
float h = spec.personHeight;
Transform head = contents.transform.Find("Head");
if (head != null &&
Mathf.Abs(head.localPosition.y + head.localScale.y * 0.5f - h) < 0.001f)
return false; // もう同じ形
BuildPersonLook(contents, r, 0f, h, mat);
PrefabUtility.SaveAsPrefabAsset(contents, path);
return true;
}
finally { PrefabUtility.UnloadPrefabContents(contents); }
}
static void EnsureWalkerPrefab(string path, string name, PeopleSpec spec, Material mat, WalkerKind kind)
{
if (AssetDatabase.LoadAssetAtPath(path) != null) return;
float r = spec.personRadius;
float h = spec.personHeight;
GameObject go = new GameObject(name);
// 見た目は胴+頭(座っている客と同じ作り)。当たり判定はこの下で別に付ける
BuildPersonLook(go, r, 0f, h, mat);
CapsuleCollider col = go.AddComponent();
col.radius = r;
col.height = h;
col.center = new Vector3(0f, h * 0.5f, 0f);
NavMeshAgent agent = go.AddComponent();
agent.radius = r;
agent.height = h;
agent.speed = kind == WalkerKind.Staff ? spec.staffSpeed : spec.guestSpeed;
agent.acceleration = spec.acceleration;
agent.angularSpeed = spec.angularSpeedDegPerSec;
agent.stoppingDistance = spec.stoppingDistance;
agent.obstacleAvoidanceType = spec.avoidanceQuality;
agent.avoidancePriority = spec.avoidancePriority;
agent.enabled = false; // 置いたあとに有効にする(警告を出さないため)
PersonBody personBody = go.AddComponent();
personBody.radius = r;
personBody.walksInAisle = kind != WalkerKind.Waiting; // 待合に立つ客は通路を歩かない
personBody.kind = kind == WalkerKind.Staff ? PersonKind.Staff
: kind == WalkerKind.Waiting ? PersonKind.Waiting
: PersonKind.Guest;
if (kind == WalkerKind.Staff) { StaffAgent a = go.AddComponent(); a.spec = spec; }
else if (kind == WalkerKind.Guest) { GuestAgent a = go.AddComponent(); a.spec = spec; }
else { WaitingGuest a = go.AddComponent(); a.spec = spec; }
PrefabUtility.SaveAsPrefabAsset(go, path);
UnityEngine.Object.DestroyImmediate(go);
}
///
/// 座っている客の形(見た目だけ)。立っている客と同じ黄色で、背を低くする。
/// 当たり判定・PersonBody・NavMeshAgent は付けない
/// (NavMesh にも、前のセンサーにも、人の数の記録にも効かないようにする)。
///
///
/// 立っている人(スタッフ・歩く客・待合の客)の見た目を、胴+頭にそろえる。
/// Setup を押していなくても Build Store だけで揃うようにするための出口。
/// 取り替えるのは見た目(Renderer)だけで、
/// 当たり判定・NavMeshAgent・PersonBody・よける処理・Tag には触れない。
/// 作り直した prefab の数を返す。
///
public static int EnsurePersonLook(PeopleSpec spec)
{
if (spec == null) return 0;
if (_shader == null)
{
string how;
_shader = ServiceSimRender.FindOpaqueShader(out how);
if (_shader == null) return 0;
}
int fixedCount = 0;
fixedCount += FixWalkerLook(Root + "/People/Staff.prefab",
spec, Mat("Mat_Staff", StaffColor)) ? 1 : 0;
fixedCount += FixWalkerLook(Root + "/People/Guest.prefab",
spec, Mat("Mat_Guest", GuestColor)) ? 1 : 0;
fixedCount += FixWalkerLook(Root + "/People/WaitingGuest.prefab",
spec, Mat("Mat_WaitingGuest", WaitingColor)) ? 1 : 0;
if (fixedCount > 0)
Debug.Log("[ServiceSim] 立っている人の見た目を胴+頭にそろえました(" + fixedCount +
" 体ぶん・背の高さ " + spec.personHeight.ToString("0.##") + " m)。" +
"当たり判定・NavMeshAgent・PersonBody は触っていません");
return fixedCount;
}
///
/// ベンチの見た目(座面・背もたれ)と、当たりだけの箱を用意して StoreBuilder に入れる。
/// Setup を押していなくても Build Store だけで揃うようにするための出口(v5)。
/// 当たりの箱の大きさ・位置・高さは今までのベンチと同じなので、
/// NavMesh も前のセンサーも、通れる床も卓までの道のりも変わらない。
///
public static bool EnsureBenchLook(StoreBuilder builder)
{
if (builder == null) return false;
if (_shader == null)
{
string how;
_shader = ServiceSimRender.FindOpaqueShader(out how);
if (_shader == null)
{
Debug.LogError("[ServiceSim] 描画方式に合うシェーダーが見つかりません。" +
"ベンチの見た目は作りませんでした(探した結果: " + how + ")");
return false;
}
}
EnsureTags();
Material seatMat = Mat("Mat_BenchSeat", new Color(0.62f, 0.33f, 0.29f));
Material backMat = Mat("Mat_BenchBack", new Color(0.48f, 0.25f, 0.22f));
EnsureBoxPrefab(StorePrefabDir + "/BenchSeat.prefab", "BenchSeat", seatMat, false);
EnsureBoxPrefab(StorePrefabDir + "/BenchBack.prefab", "BenchBack", backMat, false);
EnsureColliderOnlyBoxPrefab(StorePrefabDir + "/BenchBody.prefab", "BenchBody");
SetPrefabMaterial(StorePrefabDir + "/BenchSeat.prefab", seatMat);
SetPrefabMaterial(StorePrefabDir + "/BenchBack.prefab", backMat);
SetPrefabTag(StorePrefabDir + "/BenchSeat.prefab", SimTags.Bench);
SetPrefabTag(StorePrefabDir + "/BenchBack.prefab", SimTags.Bench);
SetPrefabTag(StorePrefabDir + "/BenchBody.prefab", SimTags.Bench);
SetPrefabTag(StorePrefabDir + "/Chair.prefab", SimTags.Chair);
SetPrefabTag(Root + "/Robot/ServingRobot.prefab", SimTags.Robot);
SetPrefabTag(Root + "/People/Staff.prefab", SimTags.Staff);
SetPrefabTag(Root + "/People/Guest.prefab", SimTags.Guest);
SetPrefabTag(Root + "/People/WaitingGuest.prefab", SimTags.Guest);
SetPrefabTag(SeatedBodyPrefabPath, SimTags.SeatedGuest);
bool changed = false;
changed |= Assign(ref builder.benchSeatPrefab, StorePrefabDir + "/BenchSeat.prefab");
changed |= Assign(ref builder.benchBackPrefab, StorePrefabDir + "/BenchBack.prefab");
changed |= Assign(ref builder.benchBodyPrefab, StorePrefabDir + "/BenchBody.prefab");
if (changed) EditorUtility.SetDirty(builder);
return true;
}
static bool Assign(ref GameObject field, string path)
{
GameObject want = AssetDatabase.LoadAssetAtPath(path);
if (want == null || field == want) return false;
field = want;
return true;
}
///
/// 座っている客の形を用意して返す(Build Store からも呼ぶ)。
/// 10/21 の分では、この prefab を作る前から参照だけしていたので、席に何も出ていなかった。
///
public static GameObject EnsureSeatedBodyPrefab(PeopleSpec spec)
{
if (spec == null) return null;
if (_shader == null)
{
string how;
_shader = ServiceSimRender.FindOpaqueShader(out how);
if (_shader == null)
{
Debug.LogError("[ServiceSim] 描画方式に合うシェーダーが見つかりません。" +
"座っている客の形は作りませんでした(探した結果: " + how + ")");
return null;
}
}
EnsureSeatedBodyPrefab(SeatedBodyPrefabPath, spec, Mat("Mat_Guest", GuestColor));
return AssetDatabase.LoadAssetAtPath(SeatedBodyPrefabPath);
}
static void EnsureSeatedBodyPrefab(string path, PeopleSpec spec, Material mat)
{
float r = spec.personRadius;
float h = spec.seatedBodyHeight;
// すでにあっても、高さを変えたら作り直す(PeopleSpec.seatedBodyHeight が正)
GameObject had = AssetDatabase.LoadAssetAtPath(path);
if (had != null)
{
// 胴と頭の2つになっていて、頭の先が h と合っていれば、そのまま使う
Transform head0 = had.transform.Find("Head");
bool sameShape = had.transform.childCount == 2 && head0 != null &&
Mathf.Abs(head0.localPosition.y + head0.localScale.y * 0.5f - h) < 0.001f;
if (sameShape) return;
AssetDatabase.DeleteAsset(path);
}
// 座面の高さから立ち上がる胴と、その上の頭。h は床から頭の先までの高さ
float seat = Mathf.Clamp(spec.seatedSeatHeight, 0f, h - 0.3f);
GameObject go = new GameObject("SeatedGuestBody");
BuildPersonLook(go, r, seat, h, mat);
PrefabUtility.SaveAsPrefabAsset(go, path);
UnityEngine.Object.DestroyImmediate(go);
}
static void EnsureSeatedGuestPrefab(string path, PeopleSpec spec)
{
if (AssetDatabase.LoadAssetAtPath(path) != null) return;
// 座っている客は、椅子を引く仕掛けだけ。見た目は持たない(計測に効かないため)
GameObject go = new GameObject("SeatedGuest");
PersonBody body = go.AddComponent();
body.radius = spec.personRadius;
body.walksInAisle = false;
body.kind = PersonKind.Seated;
SeatedGuest seated = go.AddComponent();
seated.spec = spec;
PrefabUtility.SaveAsPrefabAsset(go, path);
UnityEngine.Object.DestroyImmediate(go);
}
///
/// すでにある prefab を直す(NavMeshAgent を切る/当たりを外す)。直したら true。
/// 中身を開いて保存し直す公式の手順を使う。
///
static bool FixPrefab(string path, bool disableAgent, bool removeCollider)
{
if (!File.Exists(path)) return false;
GameObject contents = PrefabUtility.LoadPrefabContents(path);
if (contents == null) return false;
bool changed = false;
try
{
if (disableAgent)
{
NavMeshAgent agent = contents.GetComponent();
if (agent != null && agent.enabled) { agent.enabled = false; changed = true; }
}
if (removeCollider)
{
Collider[] cols = contents.GetComponentsInChildren(true);
for (int i = 0; i < cols.Length; i++)
{
UnityEngine.Object.DestroyImmediate(cols[i]);
changed = true;
}
}
if (changed) PrefabUtility.SaveAsPrefabAsset(contents, path);
}
finally
{
PrefabUtility.UnloadPrefabContents(contents);
}
return changed;
}
/// すでにある人の prefab に、人の種類を入れ直す。直したら true
static bool FixPersonKind(string path, PersonKind kind)
{
if (!File.Exists(path)) return false;
GameObject contents = PrefabUtility.LoadPrefabContents(path);
if (contents == null) return false;
bool changed = false;
try
{
PersonBody body = contents.GetComponent();
if (body != null && body.kind != kind)
{
body.kind = kind;
changed = true;
}
if (changed) PrefabUtility.SaveAsPrefabAsset(contents, path);
}
finally
{
PrefabUtility.UnloadPrefabContents(contents);
}
return changed;
}
static GameObject Load(string path)
{
return AssetDatabase.LoadAssetAtPath(path);
}
static void SetMaterial(GameObject go, Material mat)
{
if (mat == null) return;
Renderer rend = go.GetComponent();
if (rend != null) rend.sharedMaterial = mat;
}
// ---------------- シーンの骨 ----------------
static GameObject FindOrCreateRoot(Scene scene, string name)
{
GameObject[] roots = scene.GetRootGameObjects();
for (int i = 0; i < roots.Length; i++)
if (roots[i].name == name) return roots[i];
GameObject go = new GameObject(name);
SceneManager.MoveGameObjectToScene(go, scene);
return go;
}
static GameObject FindOrCreateChild(Transform parent, string name)
{
Transform t = parent.Find(name);
if (t != null) return t.gameObject;
GameObject go = new GameObject(name);
go.transform.SetParent(parent, false);
return go;
}
static T Ensure(GameObject go) where T : Component
{
T c = go.GetComponent();
if (c == null) c = go.AddComponent();
return c;
}
static void EnsureLighting(Transform root)
{
GameObject lightsGo = FindOrCreateChild(root, "Lighting");
GameObject sunGo = FindOrCreateChild(lightsGo.transform, "Sun");
Light sun = Ensure(sunGo);
sun.type = LightType.Directional;
sun.intensity = 1.1f;
sun.shadows = LightShadows.Soft;
sun.color = new Color(1f, 0.98f, 0.94f);
sunGo.transform.rotation = Quaternion.Euler(52f, -34f, 0f);
GameObject fillGo = FindOrCreateChild(lightsGo.transform, "Fill");
Light fill = Ensure(fillGo);
fill.type = LightType.Directional;
fill.intensity = 0.35f;
fill.shadows = LightShadows.None;
fill.color = new Color(0.85f, 0.9f, 1f);
fillGo.transform.rotation = Quaternion.Euler(28f, 160f, 0f);
RenderSettings.ambientMode = UnityEngine.Rendering.AmbientMode.Flat;
RenderSettings.ambientLight = new Color(0.46f, 0.47f, 0.50f);
RenderSettings.fog = false;
}
static void EnsureCameras(Transform root)
{
GameObject camsGo = FindOrCreateChild(root, "Cameras");
GameObject mainGo = FindOrCreateChild(camsGo.transform, "Main Camera");
Camera main = Ensure(mainGo);
main.clearFlags = CameraClearFlags.SolidColor;
main.backgroundColor = new Color(0.13f, 0.14f, 0.17f);
mainGo.tag = "MainCamera";
GameObject topGo = FindOrCreateChild(camsGo.transform, "Cam_Top");
Camera top = Ensure(topGo);
top.enabled = false;
top.clearFlags = CameraClearFlags.SolidColor;
top.backgroundColor = new Color(0.13f, 0.14f, 0.17f);
GameObject obliqueGo = FindOrCreateChild(camsGo.transform, "Cam_Oblique");
Camera oblique = Ensure(obliqueGo);
oblique.enabled = false;
oblique.clearFlags = CameraClearFlags.SolidColor;
oblique.backgroundColor = new Color(0.13f, 0.14f, 0.17f);
// SRP が有効なときに必要な付属データを先に付けておく
EnsureSrpCameraData(mainGo);
EnsureSrpCameraData(topGo);
EnsureSrpCameraData(obliqueGo);
}
///
/// URP / HDRP のカメラ付属データを、型名で探して付ける。
/// URP が入っていなくてもコンパイルできるように、直接の参照は持たない。
///
static void EnsureSrpCameraData(GameObject camGo)
{
if (UnityEngine.Rendering.GraphicsSettings.currentRenderPipeline == null) return;
string[] typeNames =
{
"UnityEngine.Rendering.Universal.UniversalAdditionalCameraData, Unity.RenderPipelines.Universal.Runtime",
"UnityEngine.Rendering.HighDefinition.HDAdditionalCameraData, Unity.RenderPipelines.HighDefinition.Runtime"
};
for (int i = 0; i < typeNames.Length; i++)
{
Type t = Type.GetType(typeNames[i]);
if (t == null) continue;
if (camGo.GetComponent(t) == null) camGo.AddComponent(t);
return;
}
}
}
}