Repository: Synesthesias/PLATEAU-SDK-for-Unity
Pull request #488: ランタイムでAddressableのロード、アンロード用のPLATEAUTileManagerのコンポーネント作成
Author: login: yhikishima
Created: 2025-05-20T07:16:28Z | Merged: 2025-05-28T04:20:07Z

﻿## 関連リンク
<!-- libplateauのPR等、関連するPRがあれば書く。 -->

https://synesthesias.atlassian.net/browse/CPSDK25-126

## 実装内容
<!-- 実装した内容、背景について書く。妥協点があれば書いておく。 -->
- PLATEAUTileManagerコンポーネントの作成
  - Addressを指定してLoad処理
  - Addressを指定してUnLoad処理
- 保存先フォルダを選択して保存した場合は、保存先にcatalogファイルと、bundleファイルが作成されるように
- load時はcatalogとbundleからloadする

## マージ前確認項目
- [x] Squash and Mergeが選択されていること
- [ ] UI、挙動に変更ある場合、ユーザーマニュアルが更新されていること

## 動作確認
<!-- レビュアーが動作確認するのに必要な手順と結果を書く。 -->

- UIから動的タイル実行
- PLATEAUDynamicTileのメニューで「Load」と「UnLoad」が指定すると、AddressableからLoad、Unloadされる
- プレイ時に、AddressableからLoadされる

## その他
<!-- 気になる点、特にレビューしてほしい点等があれば書く。 -->


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **新機能**
  - Unity Addressablesを活用した動的タイルの非同期ロード・アンロード機能を追加しました。
  - タイル管理用のマネージャークラスとタイル情報クラスを新規導入しました。
  - エクスポート時にフォルダ名の重複を回避し、アドレスアブルアセットの登録・ビルドを自動化しました。

- **改善**
  - Addressablesのグループ設定、プロファイル管理、ビルド処理を大幅に強化しました。
  - フォルダ選択UIの表示制御を改善し、保存先指定の入力検証を最適化しました。
  - 変換処理の進捗表示を呼び出し元で制御可能にし、柔軟性を向上しました。

- **その他**
  - 必要なアセンブリ参照の追加とメタデータファイルの更新を行いました。
<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/Editor/Addressables/AddressablesUtility.cs b/Editor/Addressables/AddressablesUtility.cs
index 047cec133..6a3917eda 100644
--- a/Editor/Addressables/AddressablesUtility.cs
+++ b/Editor/Addressables/AddressablesUtility.cs
@@ -3,6 +3,9 @@
 using UnityEditor.AddressableAssets.Settings;
 using UnityEngine;
 using System.Collections.Generic;
+using System.Linq;
+using UnityEditor.AddressableAssets.Build;
+using UnityEngine.ResourceManagement.ResourceProviders;
 
 namespace PLATEAU.Editor.Addressables
 {
@@ -27,6 +30,7 @@ private static AddressableAssetSettings RequireAddressableSettings()
         /// <summary>
         /// 指定した名前のグループを取得。なければ新規作成。
         /// </summary>
+        /// <param name="groupName">グループ名</param>
         public static AddressableAssetGroup GetOrCreateGroup(string groupName)
         {
             var settings = RequireAddressableSettings();
@@ -53,10 +57,9 @@ public static AddressableAssetGroup GetOrCreateGroup(string groupName)
             {
                 bundledSchema = group.AddSchema<UnityEditor.AddressableAssets.Settings.GroupSchemas.BundledAssetGroupSchema>();
             }
-
             return group;
         }
-
+        
         /// <summary>
         /// 指定したアセットを指定グループにAddressableとして登録
         /// <param name="assetPath">登録するアセットのパス</param>
@@ -116,8 +119,7 @@ public static void RegisterAssetAsAddressable(
         /// 指定したグループのロードパス(LoadPath)とビルドパス(buildPath)を変更します。
         /// </summary>
         /// <param name="groupName">グループ名</param>
-        /// <param name="path">新しいパス（例: "Assets/AddressableAssetsData/BuildPath"）</param>
-        public static void SetGroupLoadAndBuildPath(string groupName, string path)
+        public static void SetGroupLoadAndBuildPath(string groupName)
         {
             var settings = RequireAddressableSettings();
             if (settings == null)
@@ -139,60 +141,186 @@ public static void SetGroupLoadAndBuildPath(string groupName, string path)
                 return;
             }
 
-            string customPathName = "CustomPath_" + groupName;
-            string profileName = "CustomProfile_" + groupName;
-            if (!AddressablesUtility.TryCreateProfile(profileName, out var profileId))
+            // グループのBuildPathとLoadPathに変数名をセット
+            bundledSchema.BuildPath.SetVariableByName(settings, groupName);
+            bundledSchema.LoadPath.SetVariableByName(settings, groupName);
+            
+            // グループの設定
+            bundledSchema.IncludeAddressInCatalog = true;
+            bundledSchema.IncludeGUIDInCatalog = true;
+            bundledSchema.IncludeLabelsInCatalog = true;
+            bundledSchema.IncludeInBuild = true;
+            bundledSchema.AssetLoadMode = AssetLoadMode.AllPackedAssetsAndDependencies;
+            
+            // bundleを個別にパックする
+            bundledSchema.BundleMode = UnityEditor.AddressableAssets.Settings.GroupSchemas.BundledAssetGroupSchema.BundlePackingMode.PackSeparately;
+        }
+        
+        /// <summary>
+        /// プロファイルを設定します。
+        /// </summary>
+        public static void SetRemoteProfileSettings(string path, string pathVar)
+        {
+            if (string.IsNullOrEmpty(path))
             {
-                Debug.LogError($"プロファイルの作成に失敗しました: {profileName}");
+                Debug.LogError("pathパラメータが無効です。");
                 return;
             }
-            var profileSettings = settings.profileSettings;
             
-            // プロファイル変数がなければ作成
-            if (!profileSettings.GetVariableNames().Contains(customPathName))
+            if (string.IsNullOrEmpty(pathVar))
             {
-                profileSettings.CreateValue(customPathName, path);
+                Debug.LogError("pathVarパラメータが無効です。");
+                return;
+            }
+            
+            var settings = RequireAddressableSettings();
+            if (settings == null)
+            {
+                return;
             }
+            
+            // カタログ設定
+            settings.BuildRemoteCatalog = true;
 
-            // そのプロファイルIDにのみ値をセット
-            profileSettings.SetValue(profileId, customPathName, path);
+            var profileSettings = settings.profileSettings;
+            if (!profileSettings.GetVariableNames().Contains(pathVar))
+            {
+                profileSettings.CreateValue(pathVar, path);
+            }
+            profileSettings.SetValue(settings.activeProfileId, pathVar, path);
 
-            // アクティブプロファイルをこのプロファイルに切り替え
-            settings.activeProfileId = profileId;
+            settings.RemoteCatalogBuildPath.SetVariableByName(settings, pathVar);
+            settings.RemoteCatalogLoadPath.SetVariableByName(settings, pathVar);
+        }
+        
+        /// <summary>
+        /// デフォルトのプロファイル設定を行います。
+        /// </summary>
+        public static void SetDefaultProfileSettings()
+        {
+            var settings = RequireAddressableSettings();
+            if (settings == null)
+            {
+                return;
+            }
 
-            // グループのBuildPathとLoadPathに変数名をセット
-            bundledSchema.BuildPath.SetVariableByName(settings, customPathName);
-            bundledSchema.LoadPath.SetVariableByName(settings, customPathName);
+            // デフォルトプロファイルの作成
+            var profileId = SetOrCreateProfile("Default");
+            if (string.IsNullOrEmpty(profileId))
+            {
+                Debug.LogError("デフォルトプロファイルの作成に失敗しました。");
+                return;
+            }
 
-            Debug.Log($"{groupName} の BuildPath/LoadPath を {path} に設定しました。");
+            // プロファイルをアクティブに設定
+            settings.activeProfileId = profileId;
+            
+            // リモートカタログを無効化
+            settings.BuildRemoteCatalog = false;
         }
 
         /// <summary>
-        /// 新しいAddressablesプロファイルを作成します。
+        /// Addressablesのプロファイルを作成し、アクティブにします。
         /// </summary>
-        /// <param name="profileName">新しいプロファイル名</param>
-        /// <param name="templateProfileName">テンプレートとするプロファイル名（省略時は"Default"）</param>
-        /// <returns>作成したプロファイルのID。失敗時はnull。</returns>
-        private static bool TryCreateProfile(string profileName, out string newProfileId, string templateProfileName = "Default")
+        /// <param name="profileName">作成するプロファイル名</param>
+        /// <returns>作成したプロファイルのID</returns>
+        public static string SetOrCreateProfile(string profileName)
         {
-            newProfileId = null;
             var settings = RequireAddressableSettings();
             if (settings == null)
             {
-                return false;
+                Debug.LogWarning("AddressableAssetSettingsが見つかりません。");
+                return null;
             }
 
+            // プロファイルが既に存在するか確認
             var profileSettings = settings.profileSettings;
-            string templateProfileId = profileSettings.GetProfileId(templateProfileName);
-            if (string.IsNullOrEmpty(templateProfileId))
+            var existingProfileId = profileSettings.GetProfileId(profileName);
+            if (!string.IsNullOrEmpty(existingProfileId))
             {
-                Debug.LogWarning($"テンプレートプロファイルが見つかりません: {templateProfileName}");
-                return false;
+                settings.activeProfileId = existingProfileId;
+                return existingProfileId;
             }
 
-            newProfileId = profileSettings.AddProfile(profileName, templateProfileId);
-            Debug.Log($"新しいプロファイルを作成: {profileName} (ID: {newProfileId})");
-            return true;
+            // 新しいプロファイルを作成
+            var newProfileId = profileSettings.AddProfile(profileName, settings.activeProfileId);
+            if (string.IsNullOrEmpty(newProfileId))
+            {
+                Debug.LogError($"プロファイル '{profileName}' の作成に失敗しました。");
+                return null;
+            }
+
+            // プロファイルをアクティブに設定
+            settings.activeProfileId = newProfileId;
+            return newProfileId;
+        }
+        
+        /// <summary>
+        /// Addressablesのビルドを実行します。
+        /// </summary>
+        /// <param name="cleanCache">ビルド前にキャッシュを消す場合はtrue</param>
+        public static void BuildAddressables(bool cleanCache = false)
+        {
+            var settings = RequireAddressableSettings();
+            if (settings == null)
+            {
+                Debug.LogWarning("AddressableAssetSettingsが見つかりません。");
+                return;
+            }
+
+            if (cleanCache)
+            {
+                UnityEditor.AddressableAssets.Settings.AddressableAssetSettings.CleanPlayerContent();
+                Debug.Log("Addressablesのキャッシュをクリアしました。");
+            }
+
+            // Addressablesのビルドを実行
+            UnityEditor.AddressableAssets.Settings.AddressableAssetSettings.BuildPlayerContent(out var result);
+            if (!string.IsNullOrEmpty(result.Error))
+            {
+                Debug.LogError($"Addressablesのビルドでエラーが発生しました: {result.Error}");
+            }
+            else
+            {
+                Debug.Log("Addressablesのビルドが完了しました。");
+            }
+        }
+
+        /// <summary>
+        /// デフォルトグループ以外のグループを削除します。
+        /// </summary>
+        public static void RemoveNonDefaultGroups(string targetLabel = "")
+        {
+            var settings = RequireAddressableSettings();
+            if (settings == null)
+            {
+                return;
+            }
+
+            var defaultGroup = settings.DefaultGroup;
+            if (defaultGroup == null)
+            {
+                Debug.LogWarning("デフォルトグループが設定されていません。");
+                return;
+            }
+
+            // グループのリストをコピー（削除中に変更されるため）
+            var groups = settings.groups.ToList();
+            foreach (var group in groups)
+            {
+                if (!string.IsNullOrEmpty(targetLabel) &&
+                    !group.entries.Any(e => e.labels.Contains(targetLabel)))
+                {
+                    continue;
+                }
+                
+                if (group == defaultGroup)
+                {
+                    continue;
+                }
+                settings.RemoveGroup(group);
+                Debug.Log($"グループを削除しました: {group.Name}");
+            }
         }
     }
 } 
\ No newline at end of file
diff --git a/Editor/PLATEAU.Editor.asmdef b/Editor/PLATEAU.Editor.asmdef
index 95dbe7d1e..25824aa66 100644
--- a/Editor/PLATEAU.Editor.asmdef
+++ b/Editor/PLATEAU.Editor.asmdef
@@ -12,7 +12,8 @@
         "NetTopologySuiteAssemblyDefinition",
         "PlateauToolkit.Sandbox",
         "Unity.Addressables",
-        "Unity.Addressables.Editor"
+        "Unity.Addressables.Editor",
+        "Unity.ResourceManager"
     ],
     "includePlatforms": [
         "Editor"
diff --git a/Editor/Window/Main/DynamicTileGui.cs b/Editor/Window/Main/DynamicTileGui.cs
index 56a898211..fbca32786 100644
--- a/Editor/Window/Main/DynamicTileGui.cs
+++ b/Editor/Window/Main/DynamicTileGui.cs
@@ -31,6 +31,8 @@ public class DynamicTileGui : ITabContent
         private const string PrefabsSavePath = "Assets/PLATEAUPrefabs";
         private const string CustomFolderLabel = "任意のフォルダに保存";
 
+        private bool isFolderShow;
+
         public VisualElement CreateGui()
         {
             container = LoadMainUxml();
@@ -56,6 +58,7 @@ private void InitializeState()
             assetConfig = ConvertToAssetConfig.DefaultValue;
             assetConfig.AssetPath = PrefabsSavePath;
             excludeObjects = new List<PLATEAUCityObjectGroup>();
+            isFolderShow = false;
         }
 
         private void FindUIElements()
@@ -92,7 +95,7 @@ private void RegisterEvents()
             // 保存先ドロップダウン
             saveLocationDropdown.RegisterValueChangedCallback(evt =>
             {
-                bool isFolderShow = evt.newValue == CustomFolderLabel;
+                isFolderShow = evt.newValue == CustomFolderLabel;
                 folderPathLabel.text = "";
                 folderSelectRow.style.display = isFolderShow ? DisplayStyle.Flex : DisplayStyle.None;
             });
@@ -155,14 +158,13 @@ private void RemoveExcludeObjectField()
 
         private void Export()
         {
-            if (string.IsNullOrEmpty(assetConfig.AssetPath))
+            if (isFolderShow && string.IsNullOrEmpty(folderPathLabel.text))
             {
                 Dialogue.Display("保存先フォルダを指定してください。", "OK");
                 return;
             }
             DynamicTileExporter.Export(
                 assetConfig,
-                excludeObjects,
                 folderPathLabel.text, 
                 msg => Dialogue.Display(msg, "OK"));
         }
diff --git a/Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs b/Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs
index 4ceb3eae0..0d54c8f04 100644
--- a/Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs
+++ b/Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs
@@ -1,5 +1,6 @@
 using PLATEAU.CityAdjust.ConvertToAsset;
 using PLATEAU.CityInfo;
+using PLATEAU.DynamicTile;
 using PLATEAU.Editor.Addressables;
 using PLATEAU.Util;
 using UnityEditor;
@@ -22,24 +23,25 @@ public static class DynamicTileExporter
         /// 都市モデルをDynamicTile用にプレハブ化し、一括エクスポートする。
         /// </summary>
         /// <param name="assetConfig">変換設定</param>
-        /// <param name="excludeObjects">除外するオブジェクト</param>
         /// <param name="buildFolderPath"></param>
         /// <param name="onError">エラー時のコールバック</param>
         public static void Export(
             ConvertToAssetConfig assetConfig,
-            List<PLATEAUCityObjectGroup> excludeObjects,
             string buildFolderPath,
             Action<string> onError = null)
         {
+            var isExcludeAssetFolder = !string.IsNullOrEmpty(buildFolderPath);
             var cityObjects = GameObject.FindObjectsOfType<PLATEAUCityObjectGroup>();
             if (cityObjects == null || cityObjects.Length == 0)
             {
                 onError?.Invoke("都市モデルが見つかりません。都市モデルをインポートしてください。");
                 return;
             }
+            
+            using var progressBar = new ProgressBar();
 
             var groupName = AddressableGroupName;
-            if (!string.IsNullOrEmpty(buildFolderPath))
+            if (isExcludeAssetFolder)
             {
                 // ビルドフォルダパスを指定する場合はグループを分ける
                 var directoryName = Path.GetFileName(
@@ -47,72 +49,142 @@ public static void Export(
                 groupName += "_" + directoryName;
             }
 
-            foreach (var cityObject in cityObjects)
+            // グループを削除
+            AddressablesUtility.RemoveNonDefaultGroups(AddressableLabel);
+
+            var addresses = new List<string>();
+            for (var i = 0; i < cityObjects.Length; i++)
             {
-                if (excludeObjects.Contains(cityObject))
+                var cityObject = cityObjects[i];
+                if (cityObject == null || cityObject.gameObject == null)
                 {
+                    Debug.LogWarning($"GameObjectがnullです。");
                     continue;
                 }
-                assetConfig.SrcGameObj = cityObject.gameObject;
+                
+                float progress = (float)(i+1) / cityObjects.Length;
+                progressBar.Display("動的タイルを生成中..", progress);
 
-                // 変換実行
-                var convertedObject = PrepareAndConvert(assetConfig, onError);
+                assetConfig.SrcGameObj = cityObject.gameObject;
+            
+                var baseFolderPath = Path.Combine(assetConfig.AssetPath, cityObject.gameObject.name);
+                var saveFolderPath = baseFolderPath;
+                int count = 1;
+                // 同名のディレクトリが存在する場合は、_1, _2, ... のように連番を付けて保存
+                while (Directory.Exists(saveFolderPath))
+                {
+                    saveFolderPath = $"{baseFolderPath}_{count}";
+                    count++;
+                }
+                
+                var convertedObject = PrepareAndConvert(assetConfig, saveFolderPath, onError);
                 if (convertedObject == null)
                 {
                     Debug.LogWarning($"{cityObject.gameObject.name} の変換に失敗しました。");
                     continue;
                 }
                 
-                string prefabPath = $"{assetConfig.AssetPath}/{convertedObject.name}.prefab";
+                // TODO: タイルごとにプレハブを保存する
+                string prefabPath = saveFolderPath + ".prefab";
                 var prefabAsset = PrefabUtility.SaveAsPrefabAsset(convertedObject, prefabPath);
                 if (prefabAsset == null)
                 {
-                    Debug.LogWarning($"{cityObject.gameObject.name} プレハブの保存に失敗しました。");
+                    Debug.LogWarning($"{convertedObject.name} プレハブの保存に失敗しました。");
                     continue;
                 }
 
+                progressBar.Display("動的タイルをAddressableに登録中..", progress);
+    
                 // プレハブをAddressableに登録
+                // TODO : タイルごとにAddress名を設定する
+                var address = prefabAsset.name;
                 AddressablesUtility.RegisterAssetAsAddressable(
                     prefabPath,
-                    prefabPath,
+                    address,
                     groupName,
                     new List<string> { AddressableLabel });
+                
+                addresses.Add(address);
+            
+                // シーン上のオブジェクトを削除
+                GameObject.DestroyImmediate(convertedObject);
             }
 
-            if (!string.IsNullOrEmpty(buildFolderPath))
+            progressBar.Display("Addressableのビルドを実行中...", 0.1f);
+
+            if (isExcludeAssetFolder)
             {
-                if (!Directory.Exists(buildFolderPath))
+                // Remote用のプロファイルを作成
+                var profileID = AddressablesUtility.SetOrCreateProfile(groupName);
+                if (!string.IsNullOrEmpty(profileID))
                 {
-                    onError?.Invoke("指定されたビルドフォルダが存在しません。");
-                    return;
+                    AddressablesUtility.SetRemoteProfileSettings(buildFolderPath, groupName);
+                    AddressablesUtility.SetGroupLoadAndBuildPath(groupName);
                 }
+            }
+            else
+            {
+                // プロファイルをデフォルトに設定
+                AddressablesUtility.SetDefaultProfileSettings();
+            }
+
+            // Addressablesのビルドを実行
+            AddressablesUtility.BuildAddressables(true);
+            
+            // DynamicTile管理用GameObjectを生成
+            var manager = GameObject.FindObjectOfType<PLATEAUTileManager>();
+            if (manager == null)
+            {
+                GameObject managerObj = new GameObject("DynamicTileManager");
+                manager = managerObj.AddComponent<PLATEAUTileManager>();
+            }
 
-                // ビルドパスを指定
-                AddressablesUtility.SetGroupLoadAndBuildPath(groupName, buildFolderPath);
+            manager.SaveCatalogPath("");
+            if (isExcludeAssetFolder)
+            {
+                // カタログファイルのパスを取得
+                var catalogFiles = Directory.GetFiles(buildFolderPath, "catalog_*.json");
+                if (catalogFiles.Length == 0)
+                {
+                    Debug.LogError("カタログファイルが見つかりません");
+                    return;
+                }
+                var catalogPath = catalogFiles[0]; // 最新のカタログファイルを使用
+                manager.SaveCatalogPath(catalogPath);
+            }
+            
+            // DynamicTileのアドレスを設定
+            manager.ClearTiles();
+            foreach (var address in addresses)
+            {
+                var tile = new PLATEAUDynamicTile(address);
+                manager.AddTile(tile);
             }
             
+            progressBar.Display("Addressableのビルドを実行中...", 0.99f);
             Dialogue.Display("動的タイルの保存が完了しました！", "OK");
         }
 
-        private static GameObject PrepareAndConvert(ConvertToAssetConfig config, Action<string> onError)
+        private static GameObject PrepareAndConvert(
+            ConvertToAssetConfig config,
+            string saveFolderPath,
+            Action<string> onError)
         {
             var assetPath = config.AssetPath;
-            string subFolderName = config.SrcGameObj.name;
-            string subFolderFullPath = Path.Combine(assetPath, subFolderName);
             List<GameObject> convertObjects = null;
             
             try
             {
-                if (!Directory.Exists(subFolderFullPath))
+                if (!Directory.Exists(saveFolderPath))
                 {
-                    Directory.CreateDirectory(subFolderFullPath);
+                    Directory.CreateDirectory(saveFolderPath);
                 }
                 
                 // AssetDatabase用のパスに変換
-                config.SetByFullPath(subFolderFullPath);
+                config.SetByFullPath(saveFolderPath);
 
                 // 変換
-                convertObjects = new ConvertToAsset().ConvertCore(config);
+                convertObjects = new ConvertToAsset().ConvertCore(config, new DummyProgressBar());
                 
                 // アセットパスを戻す
                 config.AssetPath = assetPath;
diff --git a/Runtime/CityAdjust/ConvertToAsset/ConvertToAsset.cs b/Runtime/CityAdjust/ConvertToAsset/ConvertToAsset.cs
index cae68ace4..2f2e700be 100644
--- a/Runtime/CityAdjust/ConvertToAsset/ConvertToAsset.cs
+++ b/Runtime/CityAdjust/ConvertToAsset/ConvertToAsset.cs
@@ -34,21 +34,20 @@ public void Convert(ConvertToAssetConfig conf)
                 return;
             }
 
-            ConvertCore(conf);
+            using var progress = new ProgressBar();
+            ConvertCore(conf, progress);
             Dialogue.Display("Assetsへの保存が完了しました！", "OK");
 #else
             throw new NotImplementedException("ConvertToAssetはランタイムでの実行には未対応です。");
 #endif
         }
         
-        public List<GameObject> ConvertCore(ConvertToAssetConfig conf)
+        public List<GameObject> ConvertCore(ConvertToAssetConfig conf, IProgressBar progress)
         {
 #if UNITY_EDITOR
-            using var progress = new ProgressBar();
-
             var srcTransforms = new UniqueParentTransformList(conf.SrcGameObj.transform);
             var srcTrans = conf.SrcGameObj.transform;
-            
+
             progress.Display("都市モデルの情報を記録中...", 0.1f);
 
             var subMeshConverter = new UnityMeshToDllSubMeshWithTexture(true);
@@ -61,9 +60,9 @@ public List<GameObject> ConvertCore(ConvertToAssetConfig conf)
                 new NonLibComponentsDict()
             );
             nonLibDataHolder.ComposeFrom(srcTransforms);
-            
+
             progress.Display("共通ライブラリのモデルに変換中...", 0.35f);
-            
+
             // 共通ライブラリのModelに変換します。
             using var model = UnityMeshToDllModelConverter.Convert(
                 srcTransforms,
@@ -71,8 +70,9 @@ public List<GameObject> ConvertCore(ConvertToAssetConfig conf)
                 false,
                 VertexConverterFactory.LocalCoordinateSystemConverter(CoordinateSystem.WUN, srcTrans.position),
                 true);
-            
+
             progress.Display("FBXに出力中...", 0.6f);
+
             // FBXに出力します。
             var fullPath = Path.GetFullPath(conf.AssetPath);
             string fbxNameWithoutExtension = conf.SrcGameObj.name;
@@ -98,9 +98,9 @@ public List<GameObject> ConvertCore(ConvertToAssetConfig conf)
                 modelImporter.isReadable = true;
                 modelImporter.SaveAndReimport();
             }
-            
+
             progress.Display("FBXをシーンに配置中...", 0.8f);
-            
+
             // FBXをシーンに配置します。
             var fbxs = Directory.GetFiles(fullPath, "*.fbx", SearchOption.TopDirectoryOnly);
 
@@ -121,7 +121,7 @@ public List<GameObject> ConvertCore(ConvertToAssetConfig conf)
                 AdjustGameObjectNames(newObj.transform);
                 newTransforms.Add(newObj.transform);
             }
-            
+
             progress.Display("都市の情報を復元中...", 0.9f);
 
             // 覚えておいたマテリアル、属性情報、都市情報を復元します。
diff --git a/Runtime/DynamicTile.meta b/Runtime/DynamicTile.meta
new file mode 100644
index 000000000..e1a29dd16
--- /dev/null
+++ b/Runtime/DynamicTile.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 1183f6be5ca840b3b4d60873acda8245
+timeCreated: 1747698028
\ No newline at end of file
diff --git a/Runtime/DynamicTile/AddressableLoader.cs b/Runtime/DynamicTile/AddressableLoader.cs
new file mode 100644
index 000000000..113f82e6a
--- /dev/null
+++ b/Runtime/DynamicTile/AddressableLoader.cs
@@ -0,0 +1,153 @@
+using UnityEngine;
+using UnityEngine.AddressableAssets;
+using UnityEngine.ResourceManagement.AsyncOperations;
+using System.Threading.Tasks;
+using UnityEngine.ResourceManagement.ResourceLocations;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace PLATEAU.DynamicTile
+{
+    public class AddressableLoader
+    {
+        private const string DynamicTileLabelName = "DynamicTile";
+        private string bundlePath;
+
+        public async Task<List<string>> Initialize(string catalogPath)
+        {
+            Addressables.InternalIdTransformFunc = (location) =>
+            {
+                string originalPath = location.InternalId;
+                if (string.IsNullOrEmpty(bundlePath) ||
+                    !originalPath.EndsWith(".bundle"))
+                {
+                    return originalPath;
+                }
+                // bundlePathが設定されている場合は、パスを結合して返す
+                return Path.Combine(bundlePath, location.PrimaryKey);
+            };
+            
+            List<string> addresses;
+            if (!string.IsNullOrEmpty(catalogPath))
+            {
+                // カタログパスが指定されている場合は、カタログをロード
+                addresses = await LoadCatalogAsync(catalogPath, DynamicTileLabelName);
+            }
+            else
+            {
+                // ローカルのアドレスをロード
+                addresses = await LoadLocalAddresses(DynamicTileLabelName);
+            }
+            return addresses;
+        }
+        
+        /// <summary>
+        /// Addressからアセットをインスタンス化します
+        /// 注意: 返されたGameObjectは呼び出し側でAddressables.ReleaseInstanceを使用して解放する必要があります
+        /// </summary>
+        public async Task<GameObject> InstantiateAssetAsync(string address, Transform parent = null)
+        {
+            var handle = Addressables.InstantiateAsync(address, parent);
+            await handle.Task;
+            if (handle.Status == AsyncOperationStatus.Succeeded)
+            {
+                Debug.Log($"アセットのロードに成功しました: {address}");
+                handle.Result.name = address;
+                return handle.Result;
+            }
+            else
+            {
+                Debug.LogError($"アセットのロードに失敗しました: {address}");
+                return null;
+            }
+        }
+
+        /// <summary>
+        /// カタログファイルをロードし、Addressを読み込みます
+        /// </summary>
+        /// <param name="catalogPath">カタログファイルのパス</param>
+        /// <param name="label">ラベル</param>
+        /// <returns>ロードされたGameObjectのリスト</returns>
+        public async Task<List<string>> LoadCatalogAsync(string catalogPath, string label)
+        {
+            var addresses = new List<string>();
+            try
+            {
+                // パスを正規化（バックスラッシュをスラッシュに変換）
+                catalogPath = catalogPath.Replace('\\', '/');
+                
+                // bundlePathを保持
+                bundlePath = Path.GetDirectoryName(catalogPath);
+
+                // file://プロトコルを追加
+                if (!catalogPath.StartsWith("file://"))
+                {
+                    catalogPath = "file://" + catalogPath;
+                }
+
+                // カタログファイルをロード
+                var catalogHandle = Addressables.LoadContentCatalogAsync(catalogPath);
+                while (!catalogHandle.IsDone)
+                {
+                    await Task.Yield();
+                }
+
+                if (catalogHandle.Status != AsyncOperationStatus.Succeeded)
+                {
+                    Debug.LogError($"カタログファイルのロードに失敗しました: {catalogPath}");
+                    return addresses;
+                }
+    
+                // カタログからアセットのアドレスを取得
+                IList<IResourceLocation> locations;
+                if (catalogHandle.Result.Locate(label, typeof(GameObject), out locations) && locations.Count > 0)
+                {
+                    // 各アセットのアドレスを取得
+                    foreach (var location in locations)
+                    {
+                        addresses.Add(location.PrimaryKey);
+                    }
+                }
+                else
+                {
+                    Debug.LogError($"アセットのアドレスを取得できませんでした");
+                }
+                // カタログ用のhandleの解放
+                Addressables.Release(catalogHandle);
+            }
+            catch (System.Exception ex)
+            {
+                Debug.LogError($"カタログのロード中にエラーが発生しました: {ex.Message}");
+            }
+            return addresses;
+        }
+        
+        public async Task<List<string>> LoadLocalAddresses(string label)
+        {
+            var addresses = new List<string>();
+            try
+            {
+                // ラベルでリソースロケーションを非同期取得
+                var locationsHandle = Addressables.LoadResourceLocationsAsync(label, typeof(GameObject));
+                await locationsHandle.Task;
+                if (locationsHandle.Status == AsyncOperationStatus.Succeeded && locationsHandle.Result.Count > 0)
+                {
+                    addresses = locationsHandle.Result.Select(x => x.PrimaryKey).ToList();
+                }
+                else
+                {
+                    Debug.LogError($"アセットのアドレスを取得できませんでした: {label}");
+                }
+                
+                // 取得したリソースの解放
+                Addressables.Release(locationsHandle);
+            }
+            catch (System.Exception ex)
+            {
+                Debug.LogError($"アセットのロード中にエラーが発生しました: {ex.Message}");
+            }
+            return addresses;
+        }
+    }
+} 
\ No newline at end of file
diff --git a/Runtime/DynamicTile/AddressableLoader.cs.meta b/Runtime/DynamicTile/AddressableLoader.cs.meta
new file mode 100644
index 000000000..8ae894d36
--- /dev/null
+++ b/Runtime/DynamicTile/AddressableLoader.cs.meta
@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: d1158b21cfb1321479b97e17c67f1c5f
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 
diff --git a/Runtime/DynamicTile/PLATEAUDynamicTile.cs b/Runtime/DynamicTile/PLATEAUDynamicTile.cs
new file mode 100644
index 000000000..3c84726f2
--- /dev/null
+++ b/Runtime/DynamicTile/PLATEAUDynamicTile.cs
@@ -0,0 +1,34 @@
+using PLATEAU.CityInfo;
+using PLATEAU.Dataset;
+using System;
+using System.Text.RegularExpressions;
+using UnityEngine;
+
+namespace PLATEAU.DynamicTile
+{
+    [Serializable]
+    public class PLATEAUDynamicTile
+    {
+        [SerializeField]
+        private string address;
+        public string Address => address;
+        
+        [SerializeField]
+        private Transform parent;
+        public Transform Parent => parent;
+        
+        [SerializeField]
+        private bool isExcluded;
+        public bool IsExcluded => isExcluded;
+
+        // TODO: Extentの取得
+        [SerializeField]
+        private Bounds extent;
+        public Bounds Extent => extent;
+
+        public PLATEAUDynamicTile(string address)
+        {
+            this.address = address;
+        }
+    }
+} 
\ No newline at end of file
diff --git a/Runtime/DynamicTile/PLATEAUDynamicTile.cs.meta b/Runtime/DynamicTile/PLATEAUDynamicTile.cs.meta
new file mode 100644
index 000000000..5aea70c62
--- /dev/null
+++ b/Runtime/DynamicTile/PLATEAUDynamicTile.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: 42cbcb7a430746a3b6cef7016cd4f12a
+timeCreated: 1748323080
\ No newline at end of file
diff --git a/Runtime/DynamicTile/PLATEAUTileManager.cs b/Runtime/DynamicTile/PLATEAUTileManager.cs
new file mode 100644
index 000000000..b74040e6d
--- /dev/null
+++ b/Runtime/DynamicTile/PLATEAUTileManager.cs
@@ -0,0 +1,148 @@
+using PLATEAU.CityInfo;
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEngine.AddressableAssets;
+using UnityEngine.ResourceManagement.AsyncOperations;
+
+namespace PLATEAU.DynamicTile
+{
+    public class PLATEAUTileManager : MonoBehaviour
+    {
+        [SerializeField]
+        private List<PLATEAUDynamicTile> dynamicTiles = new();
+        
+        [SerializeField]
+        private string catalogPath;
+        public string CatalogPath => catalogPath;
+
+        private const string DynamicTileLabelName = "DynamicTile";
+        private Dictionary<string, GameObject> loadedObjects = new Dictionary<string, GameObject>();
+        private AddressableLoader addressableLoader = new ();
+
+        async void Start()
+        {
+            // 初期化
+            var addresses = await addressableLoader.Initialize(catalogPath);
+
+            // TODO: カメラからの距離に応じてLoad
+            var cityModel = FindObjectOfType<PLATEAUInstancedCityModel>();
+            if (cityModel == null)
+            {
+                Debug.LogWarning("都市モデルが見つかりません。");
+                return;
+            }
+            
+            foreach (var address in addresses)
+            {
+                Load(address, cityModel.transform);
+            }
+        }
+        
+        /// <summary>
+        /// カタログパスを保存します。
+        /// </summary>
+        /// <param name="path"></param>
+        public void SaveCatalogPath(string path)
+        {
+            // パスを正規化（バックスラッシュをスラッシュに変換）
+            catalogPath = path.Replace('\\', '/');
+        }
+
+        /// <summary>
+        /// Addressを指定してAddressablesからロードする
+        /// </summary>
+        public async void Load(string address, Transform parent = null)
+        {
+            if (loadedObjects.ContainsKey(address))
+            {
+                Debug.LogWarning($"アセット {address} は既にロードされています");
+                return;
+            }
+
+            try
+            {
+                var instance = await addressableLoader.InstantiateAssetAsync(address, parent);
+                if (instance != null)
+                {
+                    loadedObjects[address] = instance;
+                }
+            }
+            catch (Exception ex)
+            {
+                Debug.LogError($"アセットのロード中にエラーが発生しました: {ex.Message}");
+            }
+        }
+
+        /// <summary>
+        /// Addressを指定してAddressablesからアンロードする
+        /// </summary>
+        /// <param name="address"></param>
+        public void Unload(string address)
+        {
+            if (!loadedObjects.ContainsKey(address))
+            {
+                Debug.LogWarning($"アセット {address} はロードされていません");
+                return;
+            }
+
+            if (loadedObjects.TryGetValue(address, out var instance))
+            {
+                try
+                {
+                    Addressables.ReleaseInstance(instance);
+                    loadedObjects.Remove(address);
+                }
+                catch (Exception ex)
+                {
+                    Debug.LogError($"アセットのアンロード中にエラーが発生しました: {ex.Message}");
+                }
+            }
+            else
+            {
+                Debug.LogWarning($"アセット {address} はロードされていません");
+            }
+        }
+
+        /// <summary>
+        /// 破棄時にすべてのロード済みオブジェクトをアンロード
+        /// </summary>
+        private void OnDestroy()
+        {
+            foreach (var loadedObject  in loadedObjects)
+            {
+                Addressables.ReleaseInstance(loadedObject.Value);
+            }
+            loadedObjects.Clear();
+            Debug.Log("全てのアセットをアンロードしました");
+        }
+        
+        /// <summary>
+        /// Tileデータを追加
+        /// </summary>
+        /// <param name="tile"></param>
+        public void AddTile(PLATEAUDynamicTile tile)
+        {
+            if (tile == null)
+            {
+                Debug.LogWarning("nullのタイルは追加できません");
+                return;
+            }
+            
+            if (dynamicTiles.Contains(tile))
+            {
+                Debug.LogWarning("既に追加済みのタイルです");
+                return;
+            }
+            dynamicTiles.Add(tile);
+        }
+        
+        /// <summary>
+        /// 登録したタイルをクリア
+        /// </summary>
+        public void ClearTiles()
+        {
+            dynamicTiles.Clear();
+        }
+    }
+}
\ No newline at end of file
diff --git a/Runtime/DynamicTile/PLATEAUTileManager.cs.meta b/Runtime/DynamicTile/PLATEAUTileManager.cs.meta
new file mode 100644
index 000000000..9710c6933
--- /dev/null
+++ b/Runtime/DynamicTile/PLATEAUTileManager.cs.meta
@@ -0,0 +1,3 @@
+fileFormatVersion: 2
+guid: fdd1fb55ed794f6087892a3d08cfbc23
+timeCreated: 1747698565
\ No newline at end of file
diff --git a/Runtime/PLATEAU.Runtime.asmdef b/Runtime/PLATEAU.Runtime.asmdef
index ae9d3fe0e..ce2e8a323 100644
--- a/Runtime/PLATEAU.Runtime.asmdef
+++ b/Runtime/PLATEAU.Runtime.asmdef
@@ -5,7 +5,9 @@
         "libplateau",
         "Unity.Splines",
         "Unity.Mathematics",
-        "LibTessDotNet"
+        "LibTessDotNet",
+        "Unity.Addressables",
+        "Unity.ResourceManager"
     ],
     "includePlatforms": [],
     "excludePlatforms": [],


Reviews:
user: login: copilot-pull-request-reviewer[bot]
body: ## Pull Request Overview

Adds a runtime component for loading and unloading tiles via Addressables, a DynamicTile component to store per-level addresses, and updates the editor exporter to register assets, replace scene objects with DynamicTile instances, and ensure a manager exists.

- Added `PLATEAUTileManager` and `PLATEAUDynamicTile` to support runtime Addressable load/unload.
- Updated `.asmdef` to include `Unity.Addressables` and `Unity.ResourceManager` dependencies.
- Enhanced `DynamicTileExporter` to register addressables, replace prefabs with dynamic tiles, and create the manager if missing.

### Reviewed Changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

| File                                                             | Description                                                      |
|------------------------------------------------------------------|------------------------------------------------------------------|
| Runtime/PLATEAU.Runtime.asmdef                                   | Added Addressables and ResourceManager package references.       |
| Runtime/DynamicTile/PLATEAUTileManager.cs                        | New component for Addressables-based load/unload of tiles.      |
| Runtime/DynamicTile/PLATEAUDynamicTile.cs                        | New component storing original and down-sample addresses.        |
| Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs     | Extended exporter: asset registration, dynamic tile replacement, and manager instantiation. |


<details>
<summary>Comments suppressed due to low confidence (3)</summary>

**Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs:113**
* [nitpick] The GameObject name "DynamicTileManager" does not match the component PLATEAUTileManager; consider renaming it to "PLATEAUTileManager" for consistency.
```
GameObject managerObj = new GameObject("DynamicTileManager");
```
**Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs:59**
* This warning is generic; include context (e.g., object name or identifier) to help identify which GameObject is null.
```
Debug.LogWarning($"GameObjectがnullです。");
```
**Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs:157**
* [nitpick] Parameter name "originalAddress" may be confusing since it represents the base address; consider renaming to "baseAddress" for clarity.
```
private static GameObject ReplaceWithDynamicTile(string objectName, string originalAddress, int downSampleLevel, Transform parent)
```
</details>


state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T07:19:08Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 14**

<details>
<summary>🧹 Nitpick comments (9)</summary><blockquote>

<details>
<summary>Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs (2)</summary><blockquote>

`57-61`: **null チェックのログに対象名を含めるとデバッグが容易です**

`GameObjectがnullです。` だけではどの CityObject か判別しづらいです。  
`cityObject` が null でなければ `cityObject.name` を出力すると調査が楽になります。  

```diff
-    Debug.LogWarning($"GameObjectがnullです。");
+    Debug.LogWarning($"GameObjectがnullです。CityObject: {cityObject?.name}");
```

---

`109-115`: **マネージャー生成時に DontDestroyOnLoad を検討してください**

シーン遷移をまたいで DynamicTile を扱う場合、  
`PLATEAUTileManager` を `DontDestroyOnLoad` にするか、  
Bootstrap シーンで生成する設計にした方が安定します。

</blockquote></details>
<details>
<summary>Runtime/DynamicTile/PLATEAUDynamicTile.cs (3)</summary><blockquote>

`22-25`: **GetAddress メソッドの実装を見直す**

現在の実装では、ダウンサンプルレベルが 0 の場合でも "_down_0" というサフィックスが追加されます。これは冗長であり、将来的にアドレス形式を変更したい場合に混乱を招く可能性があります。


下記のように条件分岐を追加することで、レベル 0 の場合は元のアドレスをそのまま返すようにすることを検討してください：

```diff
public string GetAddress(int downSampleLevel)
{
-   return originalAddress + "_down_" + downSampleLevel;
+   if (downSampleLevel == 0)
+   {
+       return originalAddress;
+   }
+   else
+   {
+       return originalAddress + "_down_" + downSampleLevel;
+   }
}
```

---

`30-33`: **GetOriginalAddress メソッドの実装を簡略化**

GetOriginalAddress メソッドは GetAddress(0) を呼び出していますが、前述の GetAddress メソッドを修正した場合、そのままでは問題ありません。しかし、現在の実装では冗長なサフィックスが追加されています。


GetAddress メソッドの修正が行われない場合は、以下のように直接 originalAddress を返すよう変更することを検討してください：

```diff
public string GetOriginalAddress()
{
-   return GetAddress(0);
+   return originalAddress;
}
```

---

`19-33`: **アドレス取得ロジックは明確ですが、命名が少し紛らわしいかもしれません。**

`GetOriginalAddress()`メソッドはdownSampleLevel=0のアドレスを返しますが、名前から考えるとアンダースコアなしの純粋なオリジナルアドレスを返すように思えます。コメントで説明されていますが、少し直感的ではないかもしれません。

```diff
-        /// <summary>
-        /// オリジナル（downSampleLevel=0）のアドレスを返す。
-        /// </summary>
-        public string GetOriginalAddress()
-        {
-            return GetAddress(0);
-        }
+        /// <summary>
+        /// ダウンサンプルレベル0のアドレスを返す。
+        /// これはオリジナルアドレスに "_down_0" が付加されたものになる。
+        /// </summary>
+        public string GetLevelZeroAddress()
+        {
+            return GetAddress(0);
+        }
+
+        /// <summary>
+        /// サフィックスなしのオリジナルアドレスを返す。
+        /// </summary>
+        public string GetRawOriginalAddress()
+        {
+            return originalAddress;
+        }
```

</blockquote></details>
<details>
<summary>Runtime/DynamicTile/PLATEAUTileManager.cs (4)</summary><blockquote>

`12-13`: **excludeTiles リストの目的を明確にする**

現在のコメント「除外タイルリスト」だけでは、これらのタイルが何から除外されているのか、またどのような基準で除外されるのかが明確ではありません。


コメントをより詳細にして、除外タイルの目的と使用方法を説明することを推奨します：

```diff
[SerializeField]
- private List<PLATEAUDynamicTile> excludeTiles = new List<PLATEAUDynamicTile>(); // 除外タイルリスト
+ private List<PLATEAUDynamicTile> excludeTiles = new List<PLATEAUDynamicTile>(); // ダウンサンプリングから除外し、常にオリジナルアドレスを使用するタイルのリスト
```

---

`99-111`: **UnloadByAddress メソッドの改善**

UnloadByAddress メソッドでは、ロードされていないアドレスに対する警告のみを行っています。実際のユースケースでは、よりきめ細かいエラーハンドリングが必要な場合があります。


より詳細なエラー処理と戻り値を追加することを検討してください：

```diff
/// <summary>
/// Addressで指定したインスタンスをAddressablesからアンロードする
/// </summary>
- private void UnloadByAddress(string address)
+ private bool UnloadByAddress(string address)
{
    if (loadedObjects.ContainsKey(address))
    {
+       try
+       {
            Addressables.ReleaseInstance(loadedObjects[address]);
            loadedObjects.Remove(address);
            Debug.Log($"Unloaded: {address}");
+           return true;
+       }
+       catch (System.Exception ex)
+       {
+           Debug.LogError($"Error unloading: {address}. Exception: {ex.Message}");
+           return false;
+       }
    }
    else
    {
        Debug.LogWarning($"Object not loaded: {address}");
+       return false;
    }
}
```

---

`32-53`: **Loadメソッドは機能していますが、エラー処理を強化できます。**

アドレスが空の場合のチェックはありますが、より明確なエラーメッセージやエラーハンドリングの追加が望ましいです。また、すでにロードされているかどうかのチェックも追加すると良いでしょう。

```diff
     public void Load(PLATEAUDynamicTile tile, int downSampleLevel)
     {
+        if (tile == null)
+        {
+            Debug.LogError("PLATEAUDynamicTileがnullです");
+            return;
+        }
+
         string address;
         if (excludeTiles.Contains(tile))
         {
             address = tile.GetOriginalAddress();
         }
         else
         {
             address = tile.GetAddress(downSampleLevel);
         }
         if (string.IsNullOrEmpty(address))
         {
             Debug.LogWarning($"指定したアドレスが見つかりません: {tile.name}");
             return;
         }
+        
+        // すでにロードされているか確認
+        if (loadedObjects.ContainsKey(address))
+        {
+            Debug.Log($"アドレス {address} はすでにロードされています");
+            return;
+        }
+        
         Transform parent = tile.transform;
         LoadByAddress(address, parent);
     }
```

---

`55-72`: **非同期オペレーションのエラーハンドリングを強化できます。**

現在のコードでは、アセットのロードが失敗した場合の詳細な情報が不足しています。また、非同期オペレーションのキャンセルやタイムアウト処理も考慮するとより堅牢になります。

```diff
     private void LoadByAddress(string address, Transform parent = null)
     {
-        Addressables.InstantiateAsync(address, parent).Completed += handle =>
+        var operation = Addressables.InstantiateAsync(address, parent);
+        operation.Completed += handle =>
         {
             if (handle.Status == AsyncOperationStatus.Succeeded)
             {
                 loadedObjects[address] = handle.Result;
                 Debug.Log($"Loaded: {address}");
             }
             else
             {
-                Debug.LogError($"Failed to load: {address}");
+                Debug.LogError($"Failed to load: {address}, Error: {handle.OperationException?.Message}");
+                
+                // 失敗時のフォールバック処理をここに追加できます
             }
         };
+        
+        // 必要に応じて、シーンの終了時などにオペレーションをキャンセルする処理を追加できます
+        // オペレーションを保存しておき、OnDestroy()で Addressables.Release(operation) を呼び出すなど
     }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 18a7b03bbbf096ae2e2cc8fcdcc3535592b1e72c and 86214f0ed59b15be3a234c5e1ee03e5cc34c0897.

</details>

<details>
<summary>📒 Files selected for processing (7)</summary>

* `Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs` (5 hunks)
* `Runtime/DynamicTile.meta` (1 hunks)
* `Runtime/DynamicTile/PLATEAUDynamicTile.cs` (1 hunks)
* `Runtime/DynamicTile/PLATEAUDynamicTile.cs.meta` (1 hunks)
* `Runtime/DynamicTile/PLATEAUTileManager.cs` (1 hunks)
* `Runtime/DynamicTile/PLATEAUTileManager.cs.meta` (1 hunks)
* `Runtime/PLATEAU.Runtime.asmdef` (1 hunks)

</details>

<details>
<summary>🔇 Additional comments (7)</summary><blockquote>

<details>
<summary>Runtime/DynamicTile.meta (1)</summary>

`1-3`: **メタファイル追加は問題なし**

GUID の重複や誤った `fileFormatVersion` も見当たらず、特に懸念はありません。

</details>
<details>
<summary>Runtime/DynamicTile/PLATEAUTileManager.cs.meta (1)</summary>

`1-3`: **メタファイル追加は問題なし**

Unity のインポート設定もデフォルトで問題ありません。

</details>
<details>
<summary>Runtime/PLATEAU.Runtime.asmdef (1)</summary>

`8-10`: **Addressables 参照追加後の manifest.json も確認を推奨**

`.asmdef` に `Unity.Addressables` と `Unity.ResourceManager` を追加したことで、  
`Packages/manifest.json` に同パッケージが含まれていない場合はコンパイルエラーになります。  
ブランチをビルド出来るか念のためご確認ください。

</details>
<details>
<summary>Runtime/DynamicTile/PLATEAUDynamicTile.cs.meta (1)</summary>

`1-11`: **メタファイル追加は問題なし**

デフォルト設定で問題ありません。

</details>
<details>
<summary>Runtime/DynamicTile/PLATEAUDynamicTile.cs (1)</summary>

`1-18`: **コンポーネントの基本構造が適切に実装されています。**

クラス定義、プロパティの実装が明確で、Addressに関する情報を適切に保持する構造になっています。シリアライズ対応もされており、Inspector上で編集可能になっています。

</details>
<details>
<summary>Runtime/DynamicTile/PLATEAUTileManager.cs (2)</summary>

`1-14`: **基本構造とフィールドの定義は適切です。**

Addressablesの参照、ロードされたオブジェクトの追跡用ディクショナリ、除外タイルリストなど、必要な要素が適切に定義されています。

---

`96-111`: **UnloadByAddressメソッドは適切に実装されています。**

このメソッドはアドレスによるオブジェクトのアンロードを正しく処理し、ディクショナリからの削除も行っています。エラーハンドリングも適切です。

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-20T07:22:57Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>🔭 Outside diff range comments (2)</summary><blockquote>

<details>
<summary>Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs (1)</summary><blockquote>

`29-35`: _⚠️ Potential issue_

**ビルドフォルダパスの検証が必要です**

`buildFolderPath`が指定された場合、そのディレクトリが存在することを確認すべきです。


エクスポート処理の開始時に以下の検証を追加してください：

```diff
 public static void Export(
     ConvertToAssetConfig assetConfig,
     List<PLATEAUCityObjectGroup> excludeObjects,
     string buildFolderPath,
     Action<string> onError = null)
 {
     var isExcludeAssetFolder = !string.IsNullOrEmpty(buildFolderPath);
+    
+    if (isExcludeAssetFolder && !Directory.Exists(buildFolderPath))
+    {
+        onError?.Invoke($"ビルドフォルダが存在しません: {buildFolderPath}");
+        return;
+    }
```

</blockquote></details>
<details>
<summary>Editor/Addressables/AddressablesUtility.cs (1)</summary><blockquote>

`87-134`: _⚠️ Potential issue_

**未使用のパラメータがあります**

`packSeparately`パラメータが追加されましたが、メソッド内で使用されていません。


このパラメータを使用するか、削除することを検討してください：

```diff
 public static void RegisterAssetAsAddressable(
     string assetPath,
     string address,
     string groupName,
-    List<string> labels,
-    bool packSeparately = false)
+    List<string> labels)
```

または、パラメータを使用する実装を追加してください。

</blockquote></details>

</blockquote></details>

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs (1)</summary><blockquote>

`83-95`: _🛠️ Refactor suggestion_

**TODOコメントとアドレス命名の問題が未解決です**

過去のレビューで指摘されたように、`downSampleLevel`を考慮したアドレス命名が必要です。現在の実装では将来的な拡張性に問題があります。



TODOを解決し、より拡張性のあるアドレス命名規則を実装してください：

```diff
-// TODO: タイルごとにプレハブを保存する
 string prefabPath = saveFolderPath + ".prefab";
 var prefabAsset = PrefabUtility.SaveAsPrefabAsset(convertedObject, prefabPath);
 if (prefabAsset == null)
 {
     Debug.LogWarning($"{convertedObject.name} プレハブの保存に失敗しました。");
     continue;
 }

 // プレハブをAddressableに登録
-// TODO : タイルごとにAddress名を設定する
-var address = prefabAsset.name;
+// タイルとLODレベルを考慮したアドレス名を生成
+int downSampleLevel = 0; // 将来的にはパラメータ化
+var address = $"{prefabAsset.name}_lod_{downSampleLevel}";
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (3)</summary><blockquote>

<details>
<summary>Runtime/DynamicTile/AddressableLoader.cs (3)</summary><blockquote>

`19-23`: **非同期処理の効率を改善できます**

`while`ループでポーリングする代わりに、`handle.Task`を直接`await`することで、より効率的な非同期処理が可能です。


```diff
-var handle = Addressables.InstantiateAsync(address, parent);
-while (!handle.IsDone)
-{
-    await Task.Yield();
-}
+var handle = Addressables.InstantiateAsync(address, parent);
+await handle.Task;
```

---

`48-55`: **パス処理をより堅牢にできます**

文字列置換でのパス正規化は、エッジケースで問題が発生する可能性があります。`Path`クラスのメソッドを使用することで、より信頼性の高い処理が可能です。


```diff
-// パスを正規化（バックスラッシュをスラッシュに変換）
-catalogPath = catalogPath.Replace('\\', '/');
-
-// file://プロトコルを追加
-if (!catalogPath.StartsWith("file://"))
-{
-    catalogPath = "file://" + catalogPath;
-}
+// パスを正規化してfile://プロトコルを追加
+catalogPath = new Uri(Path.GetFullPath(catalogPath)).AbsoluteUri;
```

---

`58-62`: **非同期処理の効率を改善できます**


```diff
-var catalogHandle = Addressables.LoadContentCatalogAsync(catalogPath);
-while (!catalogHandle.IsDone)
-{
-    await Task.Yield();
-}
+var catalogHandle = Addressables.LoadContentCatalogAsync(catalogPath);
+await catalogHandle.Task;
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between a325c617217183231594cef8bc98b2fba5133bd8 and 71febd89fc6beffefe0a82ab1d4c4ad2950f6fc2.

</details>

<details>
<summary>📒 Files selected for processing (8)</summary>

* `Editor/Addressables/AddressablesUtility.cs` (6 hunks)
* `Editor/PLATEAU.Editor.asmdef` (1 hunks)
* `Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs` (3 hunks)
* `Editor/Window/Main/Tab/DynamicTileGUI/PLATEAUTileManagerEditor.cs` (1 hunks)
* `Editor/Window/Main/Tab/DynamicTileGUI/PLATEAUTileManagerEditor.cs.meta` (1 hunks)
* `Runtime/DynamicTile/AddressableLoader.cs` (1 hunks)
* `Runtime/DynamicTile/AddressableLoader.cs.meta` (1 hunks)
* `Runtime/DynamicTile/PLATEAUTileManager.cs` (1 hunks)

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (3)</summary>

* Editor/Window/Main/Tab/DynamicTileGUI/PLATEAUTileManagerEditor.cs.meta
* Runtime/DynamicTile/AddressableLoader.cs.meta
* Editor/PLATEAU.Editor.asmdef

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* Runtime/DynamicTile/PLATEAUTileManager.cs

</details>

<details>
<summary>🔇 Additional comments (7)</summary><blockquote>

<details>
<summary>Runtime/DynamicTile/AddressableLoader.cs (1)</summary>

`94-119`: **実装が適切です**

このメソッドは非同期処理、エラーハンドリング、リソース解放が適切に実装されています。

</details>
<details>
<summary>Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs (2)</summary>

`66-74`: **ユニークなフォルダ名生成の実装が良いです**

既存のディレクトリとの名前衝突を避ける実装が適切に行われています。

---

`149-185`: **PrepareAndConvertメソッドの実装が適切です**

`saveFolderPath`パラメータの追加により、より柔軟なディレクトリ管理が可能になりました。エラーハンドリングも適切に実装されています。

</details>
<details>
<summary>Editor/Addressables/AddressablesUtility.cs (4)</summary>

`33-62`: **デフォルトグループの自動設定に注意が必要です**

`GetOrCreateGroup`を呼び出すたびにデフォルトグループが変更されます。これは意図的な動作でしょうか？


複数のグループを扱う場合に予期しない動作を引き起こす可能性があります。この動作が意図的であることを確認してください。

---

`140-175`: **グループパス設定の実装が適切です**

プロファイル変数の使用とスキーマ設定が正しく実装されています。

---

`180-210`: **リモートプロファイル設定の実装が良いです**

プロファイル変数の作成と設定、リモートカタログの設定が適切に実装されています。

---

`252-278`: **Addressablesビルドメソッドの実装が適切です**

キャッシュクリアオプションとエラーハンドリングが適切に実装されています。

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-27T02:52:26Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 9**

<details>
<summary>♻️ Duplicate comments (1)</summary><blockquote>

<details>
<summary>Runtime/DynamicTile/AddressableLoader.cs (1)</summary><blockquote>

`58-62`: **LoadCatalogAsyncでも同様の非効率性があります**

InstantiateAssetAsyncと同様に、手動ループではなく直接awaitを使用することを推奨します。

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (4)</summary><blockquote>

<details>
<summary>Runtime/DynamicTile/PLATEAUDynamicTile.cs (1)</summary><blockquote>

`21-23`: **TODOコメントに対応が必要です**

Extentの取得に関するTODOコメントが残っています。この機能が重要な場合は、実装計画を明確にし、issueとして管理することを推奨します。


Extentの取得機能の実装をお手伝いしましょうか？新しいissueを作成して追跡しますか？

</blockquote></details>
<details>
<summary>Runtime/DynamicTile/AddressableLoader.cs (1)</summary><blockquote>

`19-23`: **非効率な非同期処理パターンが使用されています**

`Task.Yield()`を使用した手動ループではなく、直接`await`を使用する方が効率的で読みやすくなります。


```diff
 public async Task<GameObject> InstantiateAssetAsync(string address, Transform parent = null)
 {
     var handle = Addressables.InstantiateAsync(address, parent);
-    while (!handle.IsDone)
-    {
-        await Task.Yield();
-    }
+    await handle.Task;

     if (handle.Status == AsyncOperationStatus.Succeeded)
     {
         handle.Result.name = address;
         return handle.Result;
     }
     else
     {
         Debug.LogError($"アセットのロードに失敗しました: {address}");
         return null;
     }
 }
```

</blockquote></details>
<details>
<summary>Runtime/DynamicTile/PLATEAUTileManager.cs (2)</summary><blockquote>

`27-32`: **PLATEAUInstancedCityModelへの依存が強すぎます**

`FindObjectOfType<PLATEAUInstancedCityModel>()`に依存しており、このコンポーネントがシーンに存在しない場合、タイル管理機能が全く動作しません。より柔軟な設計を検討するか、少なくとも代替手段を提供することを推奨します。


```diff
 var cityModel = FindObjectOfType<PLATEAUInstancedCityModel>();
 if (cityModel == null)
 {
-    Debug.LogWarning("都市モデルが見つかりません。");
-    return;
+    Debug.LogWarning("都市モデルが見つかりません。ルートTransformを使用します。");
+    // フォールバック処理
+    foreach (var address in addresses)
+    {
+        Load(address, null); // 親なしでロード
+    }
+    return;
 }
```

---

`23-52`: **Startメソッドの設計に関する提案**

現在、すべてのアドレスを自動的にロードしていますが、TODOコメントで示されているように、カメラ位置に応じた選択的ロードが理想的です。パフォーマンス改善のため、この機能の実装を検討することを推奨します。


カメラベースのカリング機能の実装により、以下のメリットが得られます：
- メモリ使用量の削減
- ロード時間の短縮
- フレームレートの向上

実装時は、視錐台カリングやLODシステムと組み合わせることを検討してください。

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 71febd89fc6beffefe0a82ab1d4c4ad2950f6fc2 and 180863f23db34a01ea3a63e5d157c3847f33d1aa.

</details>

<details>
<summary>📒 Files selected for processing (8)</summary>

* `Editor/Addressables/AddressablesUtility.cs` (6 hunks)
* `Editor/Window/Main/DynamicTileGui.cs` (0 hunks)
* `Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs` (3 hunks)
* `Editor/Window/Main/Tab/DynamicTileGUI/PLATEAUTileManagerEditor.cs` (1 hunks)
* `Runtime/DynamicTile/AddressableLoader.cs` (1 hunks)
* `Runtime/DynamicTile/PLATEAUDynamicTile.cs` (1 hunks)
* `Runtime/DynamicTile/PLATEAUDynamicTile.cs.meta` (1 hunks)
* `Runtime/DynamicTile/PLATEAUTileManager.cs` (1 hunks)

</details>

<details>
<summary>💤 Files with no reviewable changes (1)</summary>

* Editor/Window/Main/DynamicTileGui.cs

</details>

<details>
<summary>✅ Files skipped from review due to trivial changes (1)</summary>

* Runtime/DynamicTile/PLATEAUDynamicTile.cs.meta

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* Editor/Window/Main/Tab/DynamicTileGUI/PLATEAUTileManagerEditor.cs

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>Runtime/DynamicTile/PLATEAUTileManager.cs (3)</summary><blockquote>

<details>
<summary>Runtime/DynamicTile/PLATEAUDynamicTile.cs (1)</summary>

* `PLATEAUDynamicTile` (25-28)

</details>
<details>
<summary>Runtime/DynamicTile/AddressableLoader.cs (1)</summary>

* `AddressableLoader` (12-120)

</details>
<details>
<summary>Runtime/CityInfo/PLATEAUInstancedCityModel.cs (1)</summary>

* `PLATEAUInstancedCityModel` (14-206)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>🔇 Additional comments (12)</summary><blockquote>

<details>
<summary>Runtime/DynamicTile/PLATEAUDynamicTile.cs (1)</summary>

`25-28`: _🛠️ Refactor suggestion_

**コンストラクタが不完全です**

コンストラクタでは`address`フィールドのみを初期化していますが、他のフィールド（`parent`、`isExcluded`、`Extent`）は未初期化のままです。これらのフィールドも適切に初期化するか、必要に応じて追加のコンストラクタオーバーロードを提供することを検討してください。


```diff
 public PLATEAUDynamicTile(string address)
 {
     this.address = address;
+    this.parent = null;
+    this.isExcluded = false;
+    this.Extent = new Bounds();
 }
```



> Likely an incorrect or invalid review comment.

</details>
<details>
<summary>Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs (2)</summary>

`61-69`: **フォルダパスのユニーク化ロジックが適切です**

同名ディレクトリの衝突を避けるための連番付与ロジックが実装されており、確実性があります。この実装は適切です。

---

`120-125`: **PLATEAUTileManagerの重複チェックが適切です**

シングルトンパターンの実装が適切で、既存のマネージャーがある場合は再利用し、ない場合のみ新規作成する仕組みになっています。

</details>
<details>
<summary>Runtime/DynamicTile/AddressableLoader.cs (2)</summary>

`48-56`: **パス正規化とプロトコル処理が適切です**

バックスラッシュからスラッシュへの変換と、file://プロトコルの自動追加により、異なるOS環境での互換性が確保されています。

---

`84-85`: **リソースハンドルの適切な解放**

`Addressables.Release(catalogHandle)`でリソースハンドルを適切に解放しており、メモリリークを防いでいます。LoadLocalAddressesメソッドでも同様の処理が実装されています。

</details>
<details>
<summary>Runtime/DynamicTile/PLATEAUTileManager.cs (1)</summary>

`110-118`: **OnDestroyメソッドの実装が適切です**

過去のレビューコメントで指摘されたリソースリーク問題に対応し、適切なクリーンアップ処理が実装されています。

</details>
<details>
<summary>Editor/Addressables/AddressablesUtility.cs (6)</summary>

`6-7`: **LGTM!**

新しい機能に必要な適切なimportが追加されています。

---

`64-78`: **LGTM!**

適切な警告付きでnullチェックが実装されたシンプルで正しいゲッターメソッドです。

---

`209-242`: **LGTM!**

適切なエラーハンドリングとログ出力を含む、よく実装されたメソッドです。プロファイルの存在確認、作成、アクティブ化の流れが正しく実装されています。

---

`244-274`: **LGTM!**

適切なエラーハンドリングとログ出力を含む、よく実装されたビルドメソッドです。オプションのキャッシュクリア機能とビルド結果の確認も正しく実装されています。

---

`140-175`: <details>
<summary>✅ Verification successful</summary>

**メソッドシグネチャの変更による影響を確認してください。**

`SetGroupLoadAndBuildPath`メソッドのシグネチャが変更され、パラメータが削除されています。既存の呼び出し箇所がすべて更新されているか確認が必要です。

新しい設定項目の追加は適切に見えます。


次のスクリプトで呼び出し箇所を確認してください：

---

🏁 Script executed:

```shell
#!/bin/bash
# Description: SetGroupLoadAndBuildPathメソッドの呼び出し箇所を確認

# メソッドの呼び出し箇所を検索
rg -A 3 -B 3 "SetGroupLoadAndBuildPath" --type cs
```

Length of output: 2268

---



</details>

**呼び出し箇所はすべて新シグネチャに対応済みです。**

- Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs  
- Editor/Window/Main/Tab/DynamicTileGUI/PLATEAUTileManagerEditor.cs  

どちらも `SetGroupLoadAndBuildPath(groupName)` のみを渡す形に更新されており、既存の呼び出し箇所に問題はありません。

---

`60-60`: <details>
<summary>✅ Verification successful</summary>

**デフォルトグループの設定による副作用を検証してください。**

`GetOrCreateGroup`を呼び出すたびにデフォルトグループが変更されるのは予期しない動作の可能性があります。他のコードがデフォルトグループの安定性に依存していないか確認が必要です。


次のスクリプトでデフォルトグループの使用箇所を確認してください：

---

🏁 Script executed:

```shell
#!/bin/bash
# Description: デフォルトグループの使用箇所を検索し、副作用がないか確認

# DefaultGroupプロパティの使用箇所を検索
rg -A 3 -B 3 "DefaultGroup" --type cs

# GetOrCreateGroupメソッドの呼び出し箇所を検索
rg -A 5 "GetOrCreateGroup" --type cs
```

Length of output: 3727

---



</details>

**デフォルトグループ設定の副作用は発生しないことを確認しました**

スクリプトで調査したところ、  
- GetOrCreateGroup は AddressablesUtility.cs 内でのみ使用され、  
- 他のコードで DefaultGroup を参照しているのは PLATEAUTileManagerEditor.cs にある GetDefaultGroup 呼び出し箇所のみ  

でした。したがって、GetOrCreateGroup 内で settings.DefaultGroup を上書きする挙動が他箇所に影響を及ぼすリスクはありません。

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-27T06:07:53Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>♻️ Duplicate comments (3)</summary><blockquote>

<details>
<summary>Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs (3)</summary><blockquote>

`78-89`: _🛠️ Refactor suggestion_

**アドレス命名戦略の課題が未解決です**



現在もプレハブアセット名をそのままアドレスとして使用しており、複数のタイルが同じ名前を持つ場合に衝突が発生する可能性があります。過去のレビューで指摘された通り、より一意性のあるアドレス命名戦略が必要です。



一意性を確保するため、以下のような改善を検討してください：

```diff
-var address = prefabAsset.name;
+var address = $"{groupName}_{prefabAsset.name}_{System.Guid.NewGuid():N}";
```

---

`103-121`: _🛠️ Refactor suggestion_

**Addressablesプロファイル管理のエラーハンドリング強化が必要です**



`SetOrCreateProfile`の戻り値チェックや各メソッド呼び出しの例外処理が不足しています。特に「Use Existing Build」モードでの問題解決のためにも、エラーハンドリングの強化が重要です。



以下のような改善が必要です：

```diff
+try
+{
     var profileID = AddressablesUtility.SetOrCreateProfile(groupName);
-    if (!string.IsNullOrEmpty(profileID))
+    if (string.IsNullOrEmpty(profileID))
     {
+        Debug.LogError($"プロファイル '{groupName}' の作成に失敗しました");
+        onError?.Invoke($"プロファイル作成エラー: {groupName}");
+        return;
+    }
     AddressablesUtility.SetRemoteProfileSettings(buildFolderPath, groupName);
     AddressablesUtility.SetGroupLoadAndBuildPath(groupName);
+}
+catch (System.Exception ex)
+{
+    Debug.LogError($"Addressables設定中にエラーが発生しました: {ex.Message}");
+    onError?.Invoke($"Addressables設定エラー: {ex.Message}");
+    return;
+}
```

---

`134-141`: _🛠️ Refactor suggestion_

**カタログファイル選択の堅牢性に問題があります**



複数のカタログファイルが存在する場合の選択基準が不明確で、ファイルの有効性チェックがありません。過去のレビューで指摘された問題が未解決です。



以下の改善を適用してください：

```diff
 var catalogFiles = Directory.GetFiles(buildFolderPath, "catalog_*.json");
 if (catalogFiles.Length == 0)
 {
     Debug.LogError("カタログファイルが見つかりません");
     return;
 }
-var catalogPath = catalogFiles[0]; // 最新のカタログファイルを使用
+// 最新のカタログファイルを使用（作成日時順）
+var catalogPath = catalogFiles
+    .OrderByDescending(f => File.GetCreationTime(f))
+    .FirstOrDefault();
+
+if (!File.Exists(catalogPath))
+{
+    Debug.LogError($"カタログファイルが無効です: {catalogPath}");
+    return;
+}
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs (1)</summary><blockquote>

`33-48`: **ビルドフォルダパス有無による分岐処理**

`isExcludeAssetFolder`という変数名が実際の処理内容と一致していないように見えます。この変数は実際にはビルドフォルダパスが指定されているかどうかを表しているため、より明確な名前に変更することをお勧めします。



```diff
-var isExcludeAssetFolder = !string.IsNullOrEmpty(buildFolderPath);
+var hasBuildFolderPath = !string.IsNullOrEmpty(buildFolderPath);
```

</blockquote></details>
<details>
<summary>Editor/Addressables/AddressablesUtility.cs (1)</summary><blockquote>

`60-60`: **デフォルトグループの自動設定について検討が必要です**

`GetOrCreateGroup`メソッド内で自動的に`settings.DefaultGroup = group`を設定していますが、これは呼び出し側の意図と異なる場合があります。デフォルトグループの設定は明示的に行うか、パラメータで制御できるようにすることをお勧めします。



```diff
-settings.DefaultGroup = group; // デフォルトグループに設定
+// 必要に応じてデフォルトグループに設定
+// settings.DefaultGroup = group;
```

または、パラメータで制御：

```diff
-public static AddressableAssetGroup GetOrCreateGroup(string groupName)
+public static AddressableAssetGroup GetOrCreateGroup(string groupName, bool setAsDefault = false)
{
    // ... existing code ...
+   if (setAsDefault)
+   {
+       settings.DefaultGroup = group;
+   }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 180863f23db34a01ea3a63e5d157c3847f33d1aa and 34f7d5f1c788ef8e03983c5f601fa99338795bfa.

</details>

<details>
<summary>📒 Files selected for processing (5)</summary>

* `Editor/Addressables/AddressablesUtility.cs` (5 hunks)
* `Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs` (3 hunks)
* `Runtime/DynamicTile/AddressableLoader.cs` (1 hunks)
* `Runtime/DynamicTile/PLATEAUDynamicTile.cs` (1 hunks)
* `Runtime/DynamicTile/PLATEAUTileManager.cs` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (3)</summary>

* Runtime/DynamicTile/PLATEAUDynamicTile.cs
* Runtime/DynamicTile/PLATEAUTileManager.cs
* Runtime/DynamicTile/AddressableLoader.cs

</details>

<details>
<summary>🔇 Additional comments (8)</summary><blockquote>

<details>
<summary>Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs (3)</summary>

`61-69`: **保存フォルダパスの重複回避処理が追加されました**

ディレクトリ名の重複を避けるための連番付与処理が適切に実装されています。これにより同名の都市オブジェクトが存在する場合でも安全に処理できます。

---

`123-148`: **PLATEAUTileManager統合処理が適切に実装されています**

シングルトンパターンでのPLATEAUTileManager作成と、カタログパス設定、タイル登録処理が適切に実装されています。動的タイル管理の基盤として良い設計です。

---

`153-189`: **PrepareAndConvert メソッドの改善**

保存フォルダパスを受け取るように修正され、ディレクトリ作成処理も追加されています。エラーハンドリングも適切に実装されており、良い改善です。

</details>
<details>
<summary>Editor/Addressables/AddressablesUtility.cs (5)</summary>

`64-78`: **GetDefaultGroup メソッドの実装**

デフォルトグループを取得するためのユーティリティメソッドが追加されています。適切なnullチェックと警告ログが実装されており、良い実装です。

---

`161-174`: **グループのビルドパス・ロードパス設定の強化**

BundledAssetGroupSchemaの詳細な設定が追加され、個別パッキング設定も含まれています。Addressablesの適切な運用に必要な設定が網羅されており、良い実装です。

---

`179-199`: **リモートプロファイル設定メソッドの追加**

リモートカタログのビルド設定とパス変数の管理が適切に実装されています。プロファイル変数の存在チェックと作成処理も含まれており、堅牢な実装です。

---

`206-235`: **プロファイル作成・設定メソッドの実装**

既存プロファイルの確認、新規作成、アクティブ化の処理が適切に実装されています。エラーハンドリングも含まれており、良い実装です。

---

`241-266`: **Addressablesビルド機能の強化**

キャッシュクリアオプションとビルド結果のエラーチェックが追加されています。ビルド処理の監視と適切なログ出力により、開発者にとって使いやすい実装になっています。

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-27T06:33:44Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>♻️ Duplicate comments (3)</summary><blockquote>

<details>
<summary>Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs (3)</summary><blockquote>

`78-89`: **アドレス命名戦略のTODOが未解決です**

プレハブアセット名をそのままアドレスとして使用していますが、TODOコメントが示すように、同名のタイルが存在する場合に衝突が発生する可能性があります。現在の実装では一意性が保証されていません。

---

`134-141`: **カタログファイル選択の堅牢性を向上させてください**

複数のカタログファイルが存在する場合、配列の最初の要素を無条件で選択していますが、選択基準が不明確です。



```diff
-var catalogPath = catalogFiles[0]; // 最新のカタログファイルを使用
+// 最新のカタログファイルを使用（作成日時順）
+var catalogPath = catalogFiles
+    .OrderByDescending(f => File.GetCreationTime(f))
+    .FirstOrDefault();
+
+if (!File.Exists(catalogPath))
+{
+    Debug.LogError($"カタログファイルが無効です: {catalogPath}");
+    return;
+}
```

---

`103-121`: _🛠️ Refactor suggestion_

**エラーハンドリングの強化を検討してください**

Addressablesの設定やビルド処理において、各ステップの失敗時の対応が不十分です。特に`SetOrCreateProfile`の戻り値のnullチェックはありますが、他のメソッドの例外処理が不足しています。



```diff
 if (isExcludeAssetFolder)
 {
-    // Remote用のプロファイルを作成
-    var profileID = AddressablesUtility.SetOrCreateProfile(groupName);
-    if (!string.IsNullOrEmpty(profileID))
+    try
     {
-        AddressablesUtility.SetRemoteProfileSettings(buildFolderPath, groupName);
-        AddressablesUtility.SetGroupLoadAndBuildPath(groupName);
+        // Remote用のプロファイルを作成
+        var profileID = AddressablesUtility.SetOrCreateProfile(groupName);
+        if (!string.IsNullOrEmpty(profileID))
+        {
+            AddressablesUtility.SetRemoteProfileSettings(buildFolderPath, groupName);
+            AddressablesUtility.SetGroupLoadAndBuildPath(groupName);
+        }
+        else
+        {
+            Debug.LogError("プロファイルの作成に失敗しました");
+            return;
+        }
+    }
+    catch (Exception ex)
+    {
+        onError?.Invoke($"Addressables設定中にエラーが発生しました: {ex.Message}");
+        return;
     }
 }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>Runtime/DynamicTile/AddressableLoader.cs (1)</summary><blockquote>

`48-68`: **InstantiateAssetAsyncメソッドのリソース管理に関する注意**

ドキュメンテーションで呼び出し側でのリソース解放責任を明記している点は評価できます。しかし、while文による待機処理は効率的ではありません。



```diff
 public async Task<GameObject> InstantiateAssetAsync(string address, Transform parent = null)
 {
     var handle = Addressables.InstantiateAsync(address, parent);
-    while (!handle.IsDone)
-    {
-        await Task.Yield();
-    }
+    await handle.Task;

     if (handle.Status == AsyncOperationStatus.Succeeded)
     {
         Debug.Log($"アセットのロードに成功しました: {address}");
         handle.Result.name = address;

         return handle.Result;
     }
     else
     {
         Debug.LogError($"アセットのロードに失敗しました: {address}");
         return null;
     }
 }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 34f7d5f1c788ef8e03983c5f601fa99338795bfa and 666080a4d72df1dd60324219e3d43eda11b09c9d.

</details>

<details>
<summary>📒 Files selected for processing (4)</summary>

* `Editor/Addressables/AddressablesUtility.cs` (5 hunks)
* `Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs` (3 hunks)
* `Runtime/DynamicTile/AddressableLoader.cs` (1 hunks)
* `Runtime/DynamicTile/PLATEAUTileManager.cs` (1 hunks)

</details>

<details>
<summary>🔇 Additional comments (12)</summary><blockquote>

<details>
<summary>Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs (1)</summary>

`61-69`: **ディレクトリ名の衝突回避ロジックが適切に実装されています**

同名ディレクトリの存在チェックと連番付与による衝突回避が適切に実装されています。この実装により、複数の都市オブジェクトが同じ名前を持つ場合でも安全に処理できます。

</details>
<details>
<summary>Runtime/DynamicTile/AddressableLoader.cs (2)</summary>

`17-42`: **Initializeメソッドの実装が適切です**

カタログパスの有無による条件分岐とリソース変換関数の設定が適切に実装されています。bundlePathの管理とエラーハンドリングも考慮されています。

---

`76-128`: **LoadCatalogAsyncメソッドの堅牢性が高いです**

パス正規化、プロトコル追加、適切なリソース解放が実装されており、エラーハンドリングも充実しています。カタログハンドルの確実な解放も適切です。

</details>
<details>
<summary>Runtime/DynamicTile/PLATEAUTileManager.cs (4)</summary>

`23-40`: **Startメソッドの非同期処理が適切に実装されています**

AddressableLoaderの初期化、都市モデルの検索、各アドレスの読み込み処理が適切に実装されています。null チェックとエラーハンドリングも考慮されています。

---

`55-75`: **Loadメソッドの重複保護が適切です**

既存のロード済みオブジェクトのチェック、例外処理、適切なログ出力が実装されており、過去のレビューコメントで指摘された問題が解決されています。

---

`110-118`: **OnDestroyメソッドでのリソース管理が適切です**

すべてのロード済みオブジェクトの適切な解放とディクショナリのクリアが実装されており、メモリリークを防ぐ仕組みが整っています。

---

`124-137`: **AddTileメソッドのバリデーションが充実しています**

nullチェックと重複チェックが適切に実装されており、過去のレビューコメントで指摘された問題が解決されています。

</details>
<details>
<summary>Editor/Addressables/AddressablesUtility.cs (5)</summary>

`64-78`: **GetDefaultGroupメソッドが適切に実装されています**

デフォルトグループの取得とnullチェック、適切な警告ログが実装されています。エラーハンドリングも考慮されています。

---

`139-174`: **SetGroupLoadAndBuildPathメソッドの機能強化が適切です**

グループのビルドパス・ロードパス設定に加えて、カタログ設定やアセットロードモード、バンドルパッキングモードの設定が適切に追加されています。

---

`232-261`: **SetOrCreateProfileメソッドの実装が堅牢です**

既存プロファイルの確認、新規作成、アクティブ化の処理が適切に実装されています。エラーハンドリングと戻り値の管理も考慮されています。

---

`267-292`: **BuildAddressablesメソッドの実装が充実しています**

キャッシュクリア機能、ビルド実行、エラーハンドリングが適切に実装されています。成功・失敗のログ出力も考慮されています。

---

`179-199`: <details>
<summary>✅ Verification successful</summary>

**SetRemoteProfileSettingsメソッドの検証が必要です**

プロファイル変数の作成と設定処理が実装されていますが、`pathVar`パラメータがグループ名として使用される前提での実装になっています。呼び出し側との整合性を確認してください。



以下のスクリプトでSetRemoteProfileSettingsの呼び出し箇所を確認してください：

---

🏁 Script executed:

```shell
#!/bin/bash
# Description: SetRemoteProfileSettingsの呼び出し箇所と引数を確認

rg -n "SetRemoteProfileSettings" -A 2 -B 2
```

Length of output: 1063

---



</details>

**SetRemoteProfileSettingsの呼び出し確認 – 問題なし**

DynamicTileExporter.cs（行109）で `buildFolderPath` と `groupName` を引数に渡しており、`pathVar` にグループ名を使用する実装は意図どおりです。追加の修正は不要です。

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-27T09:27:22Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🔭 Outside diff range comments (1)</summary><blockquote>

<details>
<summary>Editor/Addressables/AddressablesUtility.cs (1)</summary><blockquote>

`122-157`: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**メソッドシグネチャの変更により破壊的変更の可能性があります。**

`SetGroupLoadAndBuildPath`メソッドから`path`パラメータが削除されており、既存のコードがこのメソッドを呼び出している場合、コンパイルエラーが発生する可能性があります。

また、新しい実装では適切にグループの設定が行われていますが、変数名の検証がありません。


以下のスクリプトで既存の呼び出し箇所を確認してください：

---

🏁 Script executed:

```shell
#!/bin/bash
# SetGroupLoadAndBuildPathメソッドの既存の呼び出し箇所を検索
rg -A 3 -B 1 "SetGroupLoadAndBuildPath\s*\(" --type cs
```

Length of output: 1020

---



</details>

**SetGroupLoadAndBuildPathのシグネチャ変更は非破壊的です**  
リポジトリ内の呼び出し箇所を確認したところ、旧シグネチャ（`path`引数あり）を使っている箇所はありませんでした。破壊的変更の影響はありません。

**しかし、変数名（groupName）の検証を追加してください**  
`bundledSchema.BuildPath.SetVariableByName(settings, groupName)` や  
`bundledSchema.LoadPath.SetVariableByName(settings, groupName)` を呼ぶ前に、  
Addressables のプロファイル変数として `groupName` が定義されているかチェックし、  
未定義の場合は例外を投げるか警告ログを出力するようにすると安全です。  

- File: Editor/Addressables/AddressablesUtility.cs  
- メソッド: SetGroupLoadAndBuildPath(string groupName)  
- 修正例:  
  ```diff
  var settings = RequireAddressableSettings();
  if (settings == null) return;

+ // プロファイル変数の存在チェック
+ if (!settings.profileSettings.profileEntryList.Any(e => e.profileNameVariable == groupName))
+ {
+     Debug.LogWarning($"Addressables のプロファイル変数が見つかりません: {groupName}");
+     return;
+ }
  var group = settings.FindGroup(groupName);
  ```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>Editor/Addressables/AddressablesUtility.cs (1)</summary><blockquote>

`250-275`: **ビルド結果の詳細な検証を追加することを推奨します。**

`BuildAddressables`メソッドは基本的な実装として適切ですが、ビルド結果の詳細情報（警告、成功時の詳細など）も確認できるようにすると良いでしょう。


以下の改善を検討してください：

```diff
 // Addressablesのビルドを実行
 UnityEditor.AddressableAssets.Settings.AddressableAssetSettings.BuildPlayerContent(out var result);
 if (!string.IsNullOrEmpty(result.Error))
 {
     Debug.LogError($"Addressablesのビルドでエラーが発生しました: {result.Error}");
 }
 else
 {
     Debug.Log("Addressablesのビルドが完了しました。");
+    if (result.FileRegistry?.GetFilePaths()?.Count() > 0)
+    {
+        Debug.Log($"ビルドされたファイル数: {result.FileRegistry.GetFilePaths().Count()}");
+    }
 }
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 666080a4d72df1dd60324219e3d43eda11b09c9d and 6fb5ba46f37adb5e644b618ebd77aa3399ebe89f.

</details>

<details>
<summary>📒 Files selected for processing (3)</summary>

* `Editor/Addressables/AddressablesUtility.cs` (5 hunks)
* `Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs` (3 hunks)
* `Runtime/DynamicTile/AddressableLoader.cs` (1 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (2)</summary>

* Runtime/DynamicTile/AddressableLoader.cs
* Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs

</details>

<details>
<summary>🔇 Additional comments (4)</summary><blockquote>

<details>
<summary>Editor/Addressables/AddressablesUtility.cs (4)</summary>

`6-8`: **新しいusing文の追加は適切です。**

新機能（ビルド機能、LINQ使用、リソースプロバイダー）に必要な名前空間が適切に追加されています。

---

`187-208`: **デフォルトプロファイル設定の実装は適切です。**

メソッドの実装は論理的で、適切なエラーハンドリングが含まれています。プロファイル作成の失敗時にも適切にエラーログが出力されます。

---

`215-244`: **プロファイル管理の実装は良好です。**

既存プロファイルの確認と新規作成の両方を適切に処理し、エラーケースも考慮されています。ログ出力も適切です。

---

`280-305`: **グループ削除処理の実装は適切です。**

デフォルトグループの存在確認、リストのコピーによる安全な反復処理、適切なログ出力が含まれており、よく設計されています。

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-27T09:58:18Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>♻️ Duplicate comments (3)</summary><blockquote>

<details>
<summary>Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs (3)</summary><blockquote>

`100-100`: **Addressの命名規約が依然として問題です**

プレハブアセット名をそのままアドレスとして使用していますが、複数のタイルが同じ名前を持つ場合に衝突が発生する可能性があります。



プレハブアセット名をそのままアドレスとして使用する現在の実装では、命名衝突のリスクが残っています。より一意性を保証するアドレス命名戦略の実装を検討してください。

---

`146-154`: **カタログファイル取得の堅牢性に改善の余地があります**

複数のカタログファイルが存在する場合の選択基準が明確でなく、ファイルの有効性チェックもありません。



`catalog_*.json`パターンでファイルを検索していますが、複数ファイル存在時の選択基準が不明確で、ファイルの有効性チェックがありません。最新ファイルの選択ロジックと有効性検証の追加が必要です。

---

`118-123`: _🛠️ Refactor suggestion_

**プロファイル作成エラーのハンドリングが不十分です**

`SetOrCreateProfile` の戻り値をチェックしていますが、その後の処理（`SetRemoteProfileSettings`、`SetGroupLoadAndBuildPath`）で例外が発生した場合のエラーハンドリングがありません。



```diff
if (!string.IsNullOrEmpty(profileID))
{
-    AddressablesUtility.SetRemoteProfileSettings(buildFolderPath, groupName);
-    AddressablesUtility.SetGroupLoadAndBuildPath(groupName);
+    try 
+    {
+        AddressablesUtility.SetRemoteProfileSettings(buildFolderPath, groupName);
+        AddressablesUtility.SetGroupLoadAndBuildPath(groupName);
+    }
+    catch (Exception ex)
+    {
+        Debug.LogError($"プロファイル設定中にエラーが発生しました: {ex.Message}");
+        onError?.Invoke($"プロファイル設定に失敗しました: {ex.Message}");
+        return;
+    }
}
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs (1)</summary><blockquote>

`33-33`: **変数名の改善提案**

`isExcludeAssetFolder` という名前は実際の動作（ビルドフォルダパスが指定されているかどうか）と一致していません。



```diff
-var isExcludeAssetFolder = !string.IsNullOrEmpty(buildFolderPath);
+var hasBuildFolderPath = !string.IsNullOrEmpty(buildFolderPath);
```

以降の使用箇所も同様に修正してください。

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between afb4fe4168b0fba38fef157e8c3e6f50482dd17c and 7e968c837cb47630a24b8853398378078a1ead58.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs` (2 hunks)
* `Runtime/CityAdjust/ConvertToAsset/ConvertToAsset.cs` (4 hunks)

</details>

<details>
<summary>🔇 Additional comments (5)</summary><blockquote>

<details>
<summary>Runtime/CityAdjust/ConvertToAsset/ConvertToAsset.cs (2)</summary>

`44-44`: **プログレス表示制御の実装が適切です**

`isShowProgress` パラメータの追加により、バッチ処理時のプログレス表示を制御できるようになっています。デフォルト値 `true` により既存コードとの後方互換性も保たれています。

---

`52-55`: **プログレス表示の条件分岐が正しく実装されています**

各 `progress.Display` 呼び出しが適切に `isShowProgress` フラグで制御されており、DynamicTileExporter からの呼び出し時にプログレス表示を抑制できます。




Also applies to: 68-71, 81-84, 112-115, 138-141

</details>
<details>
<summary>Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs (3)</summary>

`70-78`: **ユニークフォルダパス生成ロジックが改善されています**

同名ディレクトリの重複を避けるために連番を付加する実装が追加されており、ファイル競合問題を適切に解決しています。

---

`187-187`: **ConvertCoreの呼び出しパラメータが適切です**

`ConvertCore(config, false)` の呼び出しで、プログレス表示を適切に無効化しており、バッチ処理での重複表示を防いでいます。

---

`135-162`: **PLATEAUTileManagerの統合が適切に実装されています**

シングルトンパターンでマネージャーを取得し、カタログパスの設定とタイル登録が正しく実装されています。動的タイル管理の基盤が整備されています。

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-28T00:19:54Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 2**

<details>
<summary>♻️ Duplicate comments (2)</summary><blockquote>

<details>
<summary>Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs (2)</summary><blockquote>

`88-89`: **アドレス命名における TODO が未解決のままです**

プレハブアセット名をそのままアドレスとして使用していますが、TODOコメントが示すように、将来的にタイルごとやLOD別の命名が必要になる可能性があります。現在の命名規約では、複数のタイルが同じ名前を持つ場合に衝突が発生する可能性があります。




Also applies to: 100-100

---

`146-154`: **カタログファイル選択ロジックの改善が必要です**

現在の実装では `catalogFiles[0]` で最初のファイルを選択していますが、複数のカタログファイルが存在する場合の選択基準が不明確です。また、ファイルの有効性チェックがありません。

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs (1)</summary><blockquote>

`70-78`: **ディレクトリ名の衝突回避ロジックを改善してください**

現在の実装では、同名ディレクトリの存在時に連番を付ける処理が含まれていますが、`count++` の実行回数が多くなった場合のパフォーマンスへの影響や、ディレクトリ作成の競合状態については考慮されていません。



以下の改善を検討してください：

```diff
-var baseFolderPath = Path.Combine(assetConfig.AssetPath, cityObject.gameObject.name);
+var timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
+var baseFolderPath = Path.Combine(assetConfig.AssetPath, $"{cityObject.gameObject.name}_{timestamp}");
 var saveFolderPath = baseFolderPath;
-int count = 1;
-// 同名のディレクトリが存在する場合は、_1, _2, ... のように連番を付けて保存
-while (Directory.Exists(saveFolderPath))
-{
-    saveFolderPath = $"{baseFolderPath}_{count}";
-    count++;
-}
+if (Directory.Exists(saveFolderPath))
+{
+    saveFolderPath = $"{baseFolderPath}_{System.Guid.NewGuid():N}";
+}
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 7e968c837cb47630a24b8853398378078a1ead58 and 4c4d1fde5591666916c444879d37f0b6ff64ebec.

</details>

<details>
<summary>📒 Files selected for processing (3)</summary>

* `Editor/Addressables/AddressablesUtility.cs` (5 hunks)
* `Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs` (2 hunks)
* `Runtime/CityAdjust/ConvertToAsset/ConvertToAsset.cs` (4 hunks)

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (1)</summary>

* Runtime/CityAdjust/ConvertToAsset/ConvertToAsset.cs

</details>

<details>
<summary>🔇 Additional comments (9)</summary><blockquote>

<details>
<summary>Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs (3)</summary>

`53-53`: **グループ削除の対象を適切に制限できています**

`RemoveNonDefaultGroups(AddressableLabel)` の呼び出しにより、ラベルフィルタリングが適用され、動的タイル関連のグループのみが削除されるようになっています。これにより、他のプロジェクトで作成されたAddressableグループが誤って削除されるリスクが軽減されました。

---

`59-63`: **null チェックとログ出力が適切に実装されています**

cityObject と gameObject の null チェックが追加され、適切な警告メッセージが出力されるようになっています。これにより、無効なオブジェクトによる処理の中断を防ぎ、ユーザーに明確なフィードバックを提供できます。

---

`118-123`: **Addressables プロファイル設定の堅牢性向上**

`SetOrCreateProfile` の戻り値をチェックしてからプロファイル設定を行っている点は評価できます。エラーハンドリングが適切に実装されており、プロファイル作成失敗時の処理が改善されています。

</details>
<details>
<summary>Editor/Addressables/AddressablesUtility.cs (6)</summary>

`122-122`: **SetGroupLoadAndBuildPath メソッドの機能が明確化されました**

メソッドシグネチャがシンプルになり、グループ名ベースでのパス設定が実装されています。バンドルモードの設定（PackSeparately）や、カタログ設定の包含フラグが適切に設定されており、動的タイル用途に最適化されています。




Also applies to: 145-157

---

`162-194`: **SetRemoteProfileSettings メソッドの実装品質が向上しています**

パラメータ検証が適切に実装され、null/空文字チェックによる早期リターンが確保されています。リモートカタログの設定とプロファイル変数の管理が整理されており、エラーハンドリングも改善されています。

---

`199-220`: **SetDefaultProfileSettings メソッドが追加されました**

デフォルトプロファイル設定の一元管理が実現されており、リモートカタログの無効化も含めて適切に実装されています。エラーハンドリングも含まれており、設定の一貫性が保たれています。

---

`227-256`: **SetOrCreateProfile メソッドの公開と改善**

プライベートメソッドから公開メソッドに変更され、既存プロファイルの確認ロジックが追加されています。重複作成の防止と適切なエラーハンドリングが実装されており、再利用性が向上しています。

---

`292-324`: **RemoveNonDefaultGroups メソッドのラベルフィルタリング実装**

ラベルフィルタリング機能が追加され、対象グループの限定が可能になりました。過去のレビューで指摘された「他のプロジェクトで作成されたグループの誤削除」リスクが軽減されています。実装ロジックも適切で、デフォルトグループの保護も確保されています。

---

`262-287`: <details>
<summary>❓ Verification inconclusive</summary>

**BuildAddressables メソッドの実装を確認してください**

Addressables のビルド処理が適切に実装されていますが、PR目的で言及されている「Use Existing Build」モードでのエラーに関連する問題への対処が含まれているか確認が必要です。



以下の点について確認してください：

---

🏁 Script executed:

```shell
#!/bin/bash
# Description: Use Existing Build モードでの動作確認とエラーハンドリングの検証

# Addressables の PlayModeScript 設定を確認
rg -n "PlayModeScript" -C 3
rg -n "Use Existing Build" -C 3

# InvalidKeyException の処理を確認
rg -n "InvalidKeyException" -C 5
```

Length of output: 94

---



</details>

**BuildAddressables メソッドの “Use Existing Build” モード時のエラーハンドリング確認**  
現状、以下の点がコード内に見当たりませんでした。  
- Addressables の PlayModeScript が “Use Existing Build” に設定されているか  
- `InvalidKeyException` 等の例外処理  

上記について手動で動作確認し、必要であれば該当モードでのエラー検知・例外キャッチ処理を追加してください。

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-28T04:15:01Z

Review comments:
diff_hunk: @@ -0,0 +1,113 @@
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEngine.AddressableAssets;
+using UnityEngine.ResourceManagement.AsyncOperations;
+
+namespace PLATEAU.DynamicTile
+{
+    public class PLATEAUTileManager : MonoBehaviour
+    {
+        private Dictionary<string, GameObject> loadedObjects = new Dictionary<string, GameObject>();
+
+        [SerializeField]
+        private List<PLATEAUDynamicTile> excludeTiles = new List<PLATEAUDynamicTile>(); // 除外タイルリスト
+
+        void Start()
+        {
+            // TODO: カメラ位置・視野に応じてロード処理を行うようにする
+            int downSampleLevel = 0; // 仮のダウンサンプルレベル
+            PLATEAUDynamicTile[] dynamicTiles = GameObject.FindObjectsOfType<PLATEAUDynamicTile>();
+            foreach (var tile in dynamicTiles)
+            {
+                // 子要素をすべて削除
+                for (int i = tile.transform.childCount - 1; i >= 0; i--)
+                {
+                    DestroyImmediate(tile.transform.GetChild(i).gameObject);
path: Runtime/DynamicTile/PLATEAUTileManager.cs
user: login: Copilot
body: Using DestroyImmediate in runtime code can lead to unexpected behavior; consider using Destroy for runtime cleanup instead.
```suggestion
                    Destroy(tile.transform.GetChild(i).gameObject);
```
created_at: 2025-05-20T07:19:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,113 @@
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEngine.AddressableAssets;
+using UnityEngine.ResourceManagement.AsyncOperations;
+
+namespace PLATEAU.DynamicTile
+{
+    public class PLATEAUTileManager : MonoBehaviour
+    {
+        private Dictionary<string, GameObject> loadedObjects = new Dictionary<string, GameObject>();
+
+        [SerializeField]
+        private List<PLATEAUDynamicTile> excludeTiles = new List<PLATEAUDynamicTile>(); // 除外タイルリスト
+
+        void Start()
+        {
+            // TODO: カメラ位置・視野に応じてロード処理を行うようにする
+            int downSampleLevel = 0; // 仮のダウンサンプルレベル
+            PLATEAUDynamicTile[] dynamicTiles = GameObject.FindObjectsOfType<PLATEAUDynamicTile>();
+            foreach (var tile in dynamicTiles)
+            {
+                // 子要素をすべて削除
+                for (int i = tile.transform.childCount - 1; i >= 0; i--)
+                {
+                    DestroyImmediate(tile.transform.GetChild(i).gameObject);
+                }
+                
+                Load(tile, downSampleLevel);
+            }
+        }
+
+        /// <summary>
+        /// 指定したDynamicTileをもとにAddressablesからロードする
+        /// </summary>
+        public void Load(PLATEAUDynamicTile tile, int downSampleLevel)
+        {
+            string address;
+            if (excludeTiles.Contains(tile))
+            {
+                address = tile.GetOriginalAddress();
+            }
+            else
+            {
+                address = tile.GetAddress(downSampleLevel);
+            }
+            if (string.IsNullOrEmpty(address))
+            {
+                Debug.LogWarning($"指定したアドレスが見つかりません: {tile.name}");
+                return;
+            }
+            Transform parent = tile.transform;
+            LoadByAddress(address, parent);
+        }
+
+        /// <summary>
+        /// Addressを指定してAddressablesからロードする
+        /// </summary>
+        private void LoadByAddress(string address, Transform parent = null)
+        {
+            Addressables.InstantiateAsync(address, parent).Completed += handle =>
+            {
+                if (handle.Status == AsyncOperationStatus.Succeeded)
+                {
path: Runtime/DynamicTile/PLATEAUTileManager.cs
user: login: Copilot
body: Overwriting the entry in loadedObjects without checking for an existing key may leak previously instantiated GameObjects; consider verifying ContainsKey or handling multiple instances.
```suggestion
                {
                    if (loadedObjects.ContainsKey(address))
                    {
                        Addressables.ReleaseInstance(loadedObjects[address]);
                        Debug.LogWarning($"Releasing previously loaded object for address: {address}");
                    }
```
created_at: 2025-05-20T07:19:07Z
author_association: CONTRIBUTOR
diff_hunk: @@ -67,16 +74,24 @@ public static void Export(
                 var prefabAsset = PrefabUtility.SaveAsPrefabAsset(convertedObject, prefabPath);
                 if (prefabAsset == null)
                 {
-                    Debug.LogWarning($"{cityObject.gameObject.name} プレハブの保存に失敗しました。");
+                    Debug.LogWarning($"{convertedObject.name} プレハブの保存に失敗しました。");
                     continue;
                 }
 
                 // プレハブをAddressableに登録
+                var downSampleLevel = 0; // TODO: ダウンサンプルレベルごとに登録
+                var address = convertedObject.name + "_down_" + downSampleLevel; // TODO : タイルごとにAddressを設定する
path: Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs
user: login: Copilot
body: [nitpick] Hard-coded downSampleLevel with a TODO indicates incomplete implementation; consider parameterizing or removing the TODO when full support is added.
```suggestion
                // ダウンサンプルレベルを使用して登録
                var address = convertedObject.name + "_down_" + downSampleLevel;
```
created_at: 2025-05-20T07:19:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -130,5 +153,26 @@ private static GameObject PrepareAndConvert(ConvertToAssetConfig config, Action<
             }
             return null;
         }
+        
+        private static GameObject ReplaceWithDynamicTile(string objectName, string originalAddress, int downSampleLevel, Transform parent)
+        {
+            GameObject oldObj = GameObject.Find(objectName);
path: Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs
user: login: Copilot
body: Using GameObject.Find by name is brittle and inefficient; consider using the provided reference (e.g., convertedObject) directly for destruction.
```suggestion
        private static GameObject ReplaceWithDynamicTile(GameObject oldObj, string objectName, string originalAddress, int downSampleLevel, Transform parent)
        {
```
created_at: 2025-05-20T07:19:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -67,16 +74,24 @@ public static void Export(
                 var prefabAsset = PrefabUtility.SaveAsPrefabAsset(convertedObject, prefabPath);
                 if (prefabAsset == null)
                 {
-                    Debug.LogWarning($"{cityObject.gameObject.name} プレハブの保存に失敗しました。");
+                    Debug.LogWarning($"{convertedObject.name} プレハブの保存に失敗しました。");
                     continue;
                 }
 
                 // プレハブをAddressableに登録
+                var downSampleLevel = 0; // TODO: ダウンサンプルレベルごとに登録
+                var address = convertedObject.name + "_down_" + downSampleLevel; // TODO : タイルごとにAddressを設定する
                 AddressablesUtility.RegisterAssetAsAddressable(
                     prefabPath,
-                    prefabPath,
+                    address,
                     groupName,
path: Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Address の命名規約 TODO が残ったままです**

`downSampleLevel` が固定 0、かつ Address も `name_down_0` で決め打ちになっています。  
今後タイル単位や LOD 別に登録する場合は互換性が崩れるため、早めに仕様を確定させて  
`TODO` を issue 等で管理してください。

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs around lines 82
to 87, the Address naming uses a fixed downSampleLevel of 0 and a hardcoded
pattern "name_down_0", which is not scalable for future tile or LOD-based
registrations. Update the code to dynamically determine downSampleLevel and
construct the Address accordingly to support multiple levels. Remove the TODO
comment and create an issue to track finalizing the naming convention and ensure
compatibility.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-20T07:22:54Z
author_association: NONE
diff_hunk: @@ -67,16 +74,24 @@ public static void Export(
                 var prefabAsset = PrefabUtility.SaveAsPrefabAsset(convertedObject, prefabPath);
                 if (prefabAsset == null)
                 {
-                    Debug.LogWarning($"{cityObject.gameObject.name} プレハブの保存に失敗しました。");
+                    Debug.LogWarning($"{convertedObject.name} プレハブの保存に失敗しました。");
                     continue;
                 }
 
                 // プレハブをAddressableに登録
+                var downSampleLevel = 0; // TODO: ダウンサンプルレベルごとに登録
+                var address = convertedObject.name + "_down_" + downSampleLevel; // TODO : タイルごとにAddressを設定する
                 AddressablesUtility.RegisterAssetAsAddressable(
                     prefabPath,
-                    prefabPath,
+                    address,
                     groupName,
                     new List<string> { AddressableLabel });
+
+                ReplaceWithDynamicTile(
+                    convertedObject.name,
+                    convertedObject.name,
+                    downSampleLevel,
+                    convertedObject.transform.parent);
             }
path: Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**DynamicTile に渡すアドレスが不一致になる可能性があります**

`ReplaceWithDynamicTile()` へ `originalAddress` として `convertedObject.name` を渡していますが、  
実際に Addressables に登録したのは `convertedObject.name + "_down_" + downSampleLevel` です。  
このままだとロード時に Address が見つからず失敗します。  

```diff
 ReplaceWithDynamicTile(
     convertedObject.name,
-    convertedObject.name,
+    address,                    // Addressables に登録した文字列を渡す
     downSampleLevel,
     convertedObject.transform.parent);
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
                ReplaceWithDynamicTile(
                    convertedObject.name,
                    address,                    // Addressables に登録した文字列を渡す
                    downSampleLevel,
                    convertedObject.transform.parent);
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs around lines 90
to 95, the call to ReplaceWithDynamicTile incorrectly passes
convertedObject.name as the originalAddress, but the actual Addressables key
includes a suffix "_down_" plus the downSampleLevel. Update the argument to pass
convertedObject.name concatenated with "_down_" and downSampleLevel to match the
registered Addressables key, ensuring the address matches and loading succeeds.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-20T07:22:54Z
author_association: NONE
diff_hunk: @@ -130,5 +153,26 @@ private static GameObject PrepareAndConvert(ConvertToAssetConfig config, Action<
             }
             return null;
         }
+        
+        private static GameObject ReplaceWithDynamicTile(string objectName, string originalAddress, int downSampleLevel, Transform parent)
+        {
+            GameObject oldObj = GameObject.Find(objectName);
+            if (oldObj != null)
+            {
+                GameObject.DestroyImmediate(oldObj);
+            }
+
+            GameObject newObj = new GameObject(objectName);
+            
+            // PLATEAUDynamicTileコンポーネントを付与し、Addressをセット
+            var dynamicTileComp = newObj.AddComponent<PLATEAU.DynamicTile.PLATEAUDynamicTile>();
+            dynamicTileComp.OriginalAddress = originalAddress;
+            newObj.transform.SetParent(parent, false);
+            
+            // 画面の表示上、DynamicTileをロードする
+            dynamicTileComp.LoadTile();
+            
+            return newObj;
+        }
path: Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**名前検索による Destroy は衝突リスク大 ― インスタンス参照を渡しましょう**

`GameObject.Find(objectName)` は同名オブジェクトが複数あると誤判定します。  
現在の呼び出し側では `convertedObject` を保持しているので、  
直接参照を渡す形に変更すると安全です。  

```diff
-private static GameObject ReplaceWithDynamicTile(string objectName, string originalAddress, int downSampleLevel, Transform parent)
+private static GameObject ReplaceWithDynamicTile(GameObject oldObj, string originalAddress, int downSampleLevel, Transform parent)
 {
-    GameObject oldObj = GameObject.Find(objectName);
-    if (oldObj != null)
-    {
-        GameObject.DestroyImmediate(oldObj);
-    }
-
-    GameObject newObj = new GameObject(objectName);
+    var objectName = oldObj.name;
+    GameObject.DestroyImmediate(oldObj);
+
+    GameObject newObj = new GameObject(objectName);
     ...
 }
```
呼び出し側:
```diff
-ReplaceWithDynamicTile(
-    convertedObject.name,
-    address,
-    downSampleLevel,
-    convertedObject.transform.parent);
+ReplaceWithDynamicTile(
+    convertedObject,
+    address,
+    downSampleLevel,
+    convertedObject.transform.parent);
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
// Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs

private static GameObject ReplaceWithDynamicTile(GameObject oldObj, string originalAddress, int downSampleLevel, Transform parent)
{
    var objectName = oldObj.name;
    GameObject.DestroyImmediate(oldObj);

    GameObject newObj = new GameObject(objectName);
    
    // PLATEAUDynamicTileコンポーネントを付与し、Addressをセット
    var dynamicTileComp = newObj.AddComponent<PLATEAU.DynamicTile.PLATEAUDynamicTile>();
    dynamicTileComp.OriginalAddress = originalAddress;
    newObj.transform.SetParent(parent, false);
    
    // 画面の表示上、DynamicTileをロードする
    dynamicTileComp.LoadTile();
    
    return newObj;
}
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs around lines 157
to 176, avoid using GameObject.Find with objectName to destroy objects due to
risk of name collisions. Instead, modify the method to accept a direct
GameObject reference from the caller (e.g., convertedObject) and destroy that
instance directly. Update the method signature and all call sites accordingly to
pass the GameObject reference rather than a string name.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit a325c61
created_at: 2025-05-20T07:22:54Z
author_association: NONE
diff_hunk: @@ -0,0 +1,65 @@
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace PLATEAU.DynamicTile
+{
+    /// <summary>
+    /// DynamicTileオブジェクトに付与し、ダウンサンプルレベルごとのAddress情報を保持するコンポーネント
+    /// </summary>
+    public class PLATEAUDynamicTile : MonoBehaviour
+    {
+        [SerializeField]
+        private string originalAddress;
+        public string OriginalAddress
+        {
+            get => originalAddress;
+            set => originalAddress = value;
+        }
+
+        /// <summary>
+        /// 指定したダウンサンプルレベルのAddressを取得
+        /// </summary>
+        public string GetAddress(int downSampleLevel)
+        {
+            return originalAddress + "_down_" + downSampleLevel;
+        }
+
+        /// <summary>
+        /// オリジナル（downSampleLevel=0）のアドレスを返す。
+        /// </summary>
+        public string GetOriginalAddress()
+        {
+            return GetAddress(0);
+        }
+
+       [ContextMenu("DynamicTile/Load Tile")]
+        public void LoadTile()
+        {
+            var manager = GameObject.FindObjectOfType<PLATEAUTileManager>();
+            if (manager != null)
+            {
+                manager.Load(this, 0);
+                Debug.Log($"{this.name} のタイルをロードしました");
+            }
+            else
+            {
+                Debug.LogWarning("PLATEAUTileManagerがシーンに見つかりません");
+            }
+        }
+
+        [ContextMenu("DynamicTile/Unload Tile")]
+        public void UnloadTile()
+        {
+            var manager = GameObject.FindObjectOfType<PLATEAUTileManager>();
+            if (manager != null)
+            {
+                manager.Unload(this, 0);
+                Debug.Log($"{this.name} のタイルをアンロードしました");
+            }
+            else
+            {
+                Debug.LogWarning("PLATEAUTileManagerがシーンに見つかりません");
+            }
+        }
path: Runtime/DynamicTile/PLATEAUDynamicTile.cs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**UnloadTile メソッドのパフォーマンス改善**

LoadTile メソッドと同様に、FindObjectOfType の使用によるパフォーマンスの問題があります。


LoadTile と同様の改善を実装し、コードの重複を減らすことも検討してください：

```diff
[ContextMenu("DynamicTile/Unload Tile")]
public void UnloadTile()
{
    var manager = GameObject.FindObjectOfType<PLATEAUTileManager>();
    if (manager != null)
    {
-       manager.Unload(this, 0);
-       Debug.Log($"{this.name} のタイルをアンロードしました");
+       bool success = manager.Unload(this, 0);
+       if (success) {
+           Debug.Log($"{this.name} のタイルをアンロードしました");
+       }
    }
    else
    {
        Debug.LogWarning("PLATEAUTileManagerがシーンに見つかりません");
    }
}
```

また、LoadTile と UnloadTile の共通処理を抽出する方法も検討してください。


> Committable suggestion skipped: line range outside the PR's diff.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In Runtime/DynamicTile/PLATEAUDynamicTile.cs around lines 50 to 63, the
UnloadTile method uses FindObjectOfType to get the PLATEAUTileManager, which
causes performance issues similar to those in LoadTile. Refactor by caching the
reference to PLATEAUTileManager instead of calling FindObjectOfType each time.
Additionally, extract the common logic used in LoadTile and UnloadTile into a
shared private method to reduce code duplication and improve maintainability.
```

</details>

---

_🛠️ Refactor suggestion_

**UnloadTileメソッドにもFindObjectOfTypeの効率化が必要です。**

LoadTileと同様に、このメソッドもキャッシュされたマネージャー参照を使用するべきです。また、LoadTileとの間にコード重複があります。

```diff
         [ContextMenu("DynamicTile/Unload Tile")]
         public void UnloadTile()
         {
-            var manager = GameObject.FindObjectOfType<PLATEAUTileManager>();
+            var manager = GetTileManager();
             if (manager != null)
             {
                 manager.Unload(this, 0);
                 Debug.Log($"{this.name} のタイルをアンロードしました");
             }
             else
             {
                 Debug.LogWarning("PLATEAUTileManagerがシーンに見つかりません");
             }
         }
```

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In Runtime/DynamicTile/PLATEAUDynamicTile.cs lines 50 to 63, the UnloadTile
method uses GameObject.FindObjectOfType to get the PLATEAUTileManager, which is
inefficient and duplicates code found in LoadTile. Refactor by caching the
PLATEAUTileManager reference in a private field or property, initialize it once,
and reuse this cached reference in UnloadTile to avoid repeated costly lookups
and reduce code duplication with LoadTile.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit a325c61
created_at: 2025-05-20T07:22:54Z
author_association: NONE
diff_hunk: @@ -0,0 +1,65 @@
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace PLATEAU.DynamicTile
+{
+    /// <summary>
+    /// DynamicTileオブジェクトに付与し、ダウンサンプルレベルごとのAddress情報を保持するコンポーネント
+    /// </summary>
+    public class PLATEAUDynamicTile : MonoBehaviour
+    {
+        [SerializeField]
+        private string originalAddress;
+        public string OriginalAddress
+        {
+            get => originalAddress;
+            set => originalAddress = value;
+        }
+
+        /// <summary>
+        /// 指定したダウンサンプルレベルのAddressを取得
+        /// </summary>
+        public string GetAddress(int downSampleLevel)
+        {
+            return originalAddress + "_down_" + downSampleLevel;
+        }
+
+        /// <summary>
+        /// オリジナル（downSampleLevel=0）のアドレスを返す。
+        /// </summary>
+        public string GetOriginalAddress()
+        {
+            return GetAddress(0);
+        }
+
+       [ContextMenu("DynamicTile/Load Tile")]
+        public void LoadTile()
+        {
+            var manager = GameObject.FindObjectOfType<PLATEAUTileManager>();
+            if (manager != null)
+            {
+                manager.Load(this, 0);
+                Debug.Log($"{this.name} のタイルをロードしました");
+            }
+            else
+            {
+                Debug.LogWarning("PLATEAUTileManagerがシーンに見つかりません");
+            }
+        }
path: Runtime/DynamicTile/PLATEAUDynamicTile.cs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**LoadTile メソッドのパフォーマンス改善**

GameObject.FindObjectOfType は処理コストが高く、特に多数のオブジェクトが存在するシーンでは問題になる可能性があります。また、操作の成功を確認する前にログメッセージを出力しています。


以下の改善を検討してください：

1. PLATEAUTileManager へのアクセスをキャッシュする
2. 操作結果を確認してからログメッセージを出力する

```diff
[ContextMenu("DynamicTile/Load Tile")]
public void LoadTile()
{
    var manager = GameObject.FindObjectOfType<PLATEAUTileManager>();
    if (manager != null)
    {
-       manager.Load(this, 0);
-       Debug.Log($"{this.name} のタイルをロードしました");
+       bool success = manager.Load(this, 0);
+       if (success) {
+           Debug.Log($"{this.name} のタイルをロードしました");
+       }
    }
    else
    {
        Debug.LogWarning("PLATEAUTileManagerがシーンに見つかりません");
    }
}
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
       [ContextMenu("DynamicTile/Load Tile")]
        public void LoadTile()
        {
            var manager = GameObject.FindObjectOfType<PLATEAUTileManager>();
            if (manager != null)
            {
                bool success = manager.Load(this, 0);
                if (success)
                {
                    Debug.Log($"{this.name} のタイルをロードしました");
                }
            }
            else
            {
                Debug.LogWarning("PLATEAUTileManagerがシーンに見つかりません");
            }
        }
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In Runtime/DynamicTile/PLATEAUDynamicTile.cs lines 35 to 48, the LoadTile method
uses GameObject.FindObjectOfType to get PLATEAUTileManager each time, which is
costly. To fix this, cache the reference to PLATEAUTileManager in a private
field so it is retrieved only once. Also, modify the code to check the success
of the manager.Load call before logging the success message, ensuring logs
reflect actual operation results.
```

</details>

---

_🛠️ Refactor suggestion_

**FindObjectOfTypeの使用はパフォーマンスに影響する可能性があります。**

`GameObject.FindObjectOfType<PLATEAUTileManager>()`はシーン内のすべてのオブジェクトを検索するため、頻繁に呼び出されると非効率です。TileManagerへの参照をキャッシュするか、シングルトンパターンを採用することで改善できます。

```diff
+        private PLATEAUTileManager _cachedManager;
+        
+        private PLATEAUTileManager GetTileManager()
+        {
+            if (_cachedManager == null)
+            {
+                _cachedManager = GameObject.FindObjectOfType<PLATEAUTileManager>();
+            }
+            return _cachedManager;
+        }
+
         [ContextMenu("DynamicTile/Load Tile")]
         public void LoadTile()
         {
-            var manager = GameObject.FindObjectOfType<PLATEAUTileManager>();
+            var manager = GetTileManager();
             if (manager != null)
             {
                 manager.Load(this, 0);
                 Debug.Log($"{this.name} のタイルをロードしました");
             }
             else
             {
                 Debug.LogWarning("PLATEAUTileManagerがシーンに見つかりません");
             }
         }
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
        private PLATEAUTileManager _cachedManager;
        
        private PLATEAUTileManager GetTileManager()
        {
            if (_cachedManager == null)
            {
                _cachedManager = GameObject.FindObjectOfType<PLATEAUTileManager>();
            }
            return _cachedManager;
        }

        [ContextMenu("DynamicTile/Load Tile")]
        public void LoadTile()
        {
            var manager = GetTileManager();
            if (manager != null)
            {
                manager.Load(this, 0);
                Debug.Log($"{this.name} のタイルをロードしました");
            }
            else
            {
                Debug.LogWarning("PLATEAUTileManagerがシーンに見つかりません");
            }
        }
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In Runtime/DynamicTile/PLATEAUDynamicTile.cs around lines 35 to 48, the use of
GameObject.FindObjectOfType<PLATEAUTileManager>() causes performance issues due
to searching all objects in the scene. To fix this, cache the reference to
PLATEAUTileManager in a private field or implement a singleton pattern in
PLATEAUTileManager to access it efficiently without repeated scene searches.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit a325c61
created_at: 2025-05-20T07:22:55Z
author_association: NONE
diff_hunk: @@ -0,0 +1,113 @@
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEngine.AddressableAssets;
+using UnityEngine.ResourceManagement.AsyncOperations;
+
+namespace PLATEAU.DynamicTile
+{
+    public class PLATEAUTileManager : MonoBehaviour
+    {
+        private Dictionary<string, GameObject> loadedObjects = new Dictionary<string, GameObject>();
+
+        [SerializeField]
+        private List<PLATEAUDynamicTile> excludeTiles = new List<PLATEAUDynamicTile>(); // 除外タイルリスト
+
+        void Start()
+        {
+            // TODO: カメラ位置・視野に応じてロード処理を行うようにする
+            int downSampleLevel = 0; // 仮のダウンサンプルレベル
+            PLATEAUDynamicTile[] dynamicTiles = GameObject.FindObjectsOfType<PLATEAUDynamicTile>();
+            foreach (var tile in dynamicTiles)
+            {
+                // 子要素をすべて削除
+                for (int i = tile.transform.childCount - 1; i >= 0; i--)
+                {
+                    DestroyImmediate(tile.transform.GetChild(i).gameObject);
+                }
+                
+                Load(tile, downSampleLevel);
+            }
+        }
path: Runtime/DynamicTile/PLATEAUTileManager.cs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Start メソッドでの DestroyImmediate 使用の見直し**

実行時に DestroyImmediate を使用することは一般的に推奨されません。また、Start メソッドでは全てのタイルを無条件でロードしていますが、必要に応じてロードする戦略も検討すべきです。


以下の改善を検討してください：

1. DestroyImmediate を Destroy に変更する
2. excludeTiles リストを考慮する
3. TODO コメントに対応した実装計画を明確にする

```diff
void Start()
{
    // TODO: カメラ位置・視野に応じてロード処理を行うようにする
    int downSampleLevel = 0; // 仮のダウンサンプルレベル
    PLATEAUDynamicTile[] dynamicTiles = GameObject.FindObjectsOfType<PLATEAUDynamicTile>();
    foreach (var tile in dynamicTiles)
    {
        // 子要素をすべて削除
        for (int i = tile.transform.childCount - 1; i >= 0; i--)
        {
-           DestroyImmediate(tile.transform.GetChild(i).gameObject);
+           Destroy(tile.transform.GetChild(i).gameObject);
        }
        
        Load(tile, downSampleLevel);
    }
}
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
        void Start()
        {
            // TODO: カメラ位置・視野に応じてロード処理を行うようにする
            int downSampleLevel = 0; // 仮のダウンサンプルレベル
            PLATEAUDynamicTile[] dynamicTiles = GameObject.FindObjectsOfType<PLATEAUDynamicTile>();
            foreach (var tile in dynamicTiles)
            {
                // 子要素をすべて削除
                for (int i = tile.transform.childCount - 1; i >= 0; i--)
                {
                    Destroy(tile.transform.GetChild(i).gameObject);
                }
                
                Load(tile, downSampleLevel);
            }
        }
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In Runtime/DynamicTile/PLATEAUTileManager.cs around lines 15 to 30, replace
DestroyImmediate with Destroy to avoid issues during runtime. Modify the Start
method to consider an excludeTiles list so that only necessary tiles are
processed. Additionally, clarify and implement the TODO comment by adding logic
to load tiles based on camera position and view frustum rather than loading all
tiles unconditionally.
```

</details>

---

_🛠️ Refactor suggestion_

**Startメソッドに最適化の余地があります。**

1. `FindObjectsOfType`はシーン内の全オブジェクトをスキャンするため、パフォーマンスに影響する可能性があります
2. Play中に`DestroyImmediate`を使用していますが、通常は`Destroy`を使用するべきです
3. TODO部分でカメラ位置に応じたロード処理を計画されていますが、その実装が重要です


```diff
     void Start()
     {
         // TODO: カメラ位置・視野に応じてロード処理を行うようにする
         int downSampleLevel = 0; // 仮のダウンサンプルレベル
         PLATEAUDynamicTile[] dynamicTiles = GameObject.FindObjectsOfType<PLATEAUDynamicTile>();
         foreach (var tile in dynamicTiles)
         {
             // 子要素をすべて削除
             for (int i = tile.transform.childCount - 1; i >= 0; i--)
             {
-                DestroyImmediate(tile.transform.GetChild(i).gameObject);
+                Destroy(tile.transform.GetChild(i).gameObject);
             }
             
             Load(tile, downSampleLevel);
         }
     }
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
        void Start()
        {
            // TODO: カメラ位置・視野に応じてロード処理を行うようにする
            int downSampleLevel = 0; // 仮のダウンサンプルレベル
            PLATEAUDynamicTile[] dynamicTiles = GameObject.FindObjectsOfType<PLATEAUDynamicTile>();
            foreach (var tile in dynamicTiles)
            {
                // 子要素をすべて削除
                for (int i = tile.transform.childCount - 1; i >= 0; i--)
                {
                    Destroy(tile.transform.GetChild(i).gameObject);
                }
                
                Load(tile, downSampleLevel);
            }
        }
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In Runtime/DynamicTile/PLATEAUTileManager.cs around lines 15 to 30, optimize the
Start method by replacing GameObject.FindObjectsOfType with a more efficient
approach such as caching references or using event-driven loading to avoid
scanning all objects each time. Replace DestroyImmediate with Destroy to safely
remove child objects during play mode. Begin implementing the TODO by adding
logic to load tiles based on the camera's position and view frustum to improve
performance.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit a325c61
created_at: 2025-05-20T07:22:55Z
author_association: NONE
diff_hunk: @@ -0,0 +1,113 @@
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEngine.AddressableAssets;
+using UnityEngine.ResourceManagement.AsyncOperations;
+
+namespace PLATEAU.DynamicTile
+{
+    public class PLATEAUTileManager : MonoBehaviour
+    {
+        private Dictionary<string, GameObject> loadedObjects = new Dictionary<string, GameObject>();
+
+        [SerializeField]
+        private List<PLATEAUDynamicTile> excludeTiles = new List<PLATEAUDynamicTile>(); // 除外タイルリスト
+
+        void Start()
+        {
+            // TODO: カメラ位置・視野に応じてロード処理を行うようにする
+            int downSampleLevel = 0; // 仮のダウンサンプルレベル
+            PLATEAUDynamicTile[] dynamicTiles = GameObject.FindObjectsOfType<PLATEAUDynamicTile>();
+            foreach (var tile in dynamicTiles)
+            {
+                // 子要素をすべて削除
+                for (int i = tile.transform.childCount - 1; i >= 0; i--)
+                {
+                    DestroyImmediate(tile.transform.GetChild(i).gameObject);
+                }
+                
+                Load(tile, downSampleLevel);
+            }
+        }
+
+        /// <summary>
+        /// 指定したDynamicTileをもとにAddressablesからロードする
+        /// </summary>
+        public void Load(PLATEAUDynamicTile tile, int downSampleLevel)
+        {
+            string address;
+            if (excludeTiles.Contains(tile))
+            {
+                address = tile.GetOriginalAddress();
+            }
+            else
+            {
+                address = tile.GetAddress(downSampleLevel);
+            }
+            if (string.IsNullOrEmpty(address))
+            {
+                Debug.LogWarning($"指定したアドレスが見つかりません: {tile.name}");
+                return;
+            }
+            Transform parent = tile.transform;
+            LoadByAddress(address, parent);
+        }
+
+        /// <summary>
+        /// Addressを指定してAddressablesからロードする
+        /// </summary>
+        private void LoadByAddress(string address, Transform parent = null)
+        {
+            Addressables.InstantiateAsync(address, parent).Completed += handle =>
+            {
+                if (handle.Status == AsyncOperationStatus.Succeeded)
+                {
+                    loadedObjects[address] = handle.Result;
+                    Debug.Log($"Loaded: {address}");
+                }
+                else
+                {
+                    Debug.LogError($"Failed to load: {address}");
+                }
+            };
+        }
+
+        /// <summary>
+        /// 指定したDynamicTileに対応するAddressablesインスタンスをアンロードする
+        /// </summary>
+        public void Unload(PLATEAUDynamicTile tile, int downSampleLevel)
+        {
+            string address;
+            if (excludeTiles.Contains(tile))
+            {
+                address = tile.GetOriginalAddress();
+            }
+            else
+            {
+                address = tile.GetAddress(downSampleLevel);
+            }
+            if (string.IsNullOrEmpty(address))
+            {
+                Debug.LogWarning($"指定したアドレスが見つかりません: {tile.name}");
+                return;
+            }
+            UnloadByAddress(address);
+        }
path: Runtime/DynamicTile/PLATEAUTileManager.cs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Unload メソッドのコード重複削減**

Load メソッドと同様に、アドレス決定のロジックが重複しています。また、操作結果を示す戻り値がありません。


前述の GetTileAddress メソッドを利用し、操作結果を示す戻り値を追加してください：

```diff
/// <summary>
/// 指定したDynamicTileに対応するAddressablesインスタンスをアンロードする
/// </summary>
- public void Unload(PLATEAUDynamicTile tile, int downSampleLevel)
+ public bool Unload(PLATEAUDynamicTile tile, int downSampleLevel)
{
-   string address;
-   if (excludeTiles.Contains(tile))
-   {
-       address = tile.GetOriginalAddress();
-   }
-   else
-   {
-       address = tile.GetAddress(downSampleLevel);
-   }
+   string address = GetTileAddress(tile, downSampleLevel);
    if (string.IsNullOrEmpty(address))
    {
        Debug.LogWarning($"指定したアドレスが見つかりません: {tile.name}");
-       return;
+       return false;
    }
    UnloadByAddress(address);
+   return true;
}
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
        /// <summary>
        /// 指定したDynamicTileに対応するAddressablesインスタンスをアンロードする
        /// </summary>
        public bool Unload(PLATEAUDynamicTile tile, int downSampleLevel)
        {
            string address = GetTileAddress(tile, downSampleLevel);
            if (string.IsNullOrEmpty(address))
            {
                Debug.LogWarning($"指定したアドレスが見つかりません: {tile.name}");
                return false;
            }
            UnloadByAddress(address);
            return true;
        }
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In Runtime/DynamicTile/PLATEAUTileManager.cs around lines 77 to 94, the Unload
method duplicates the address determination logic found in the Load method and
lacks a return value indicating operation success. Refactor Unload to use the
existing GetTileAddress method for address retrieval and modify the method
signature to return a boolean indicating whether the unload operation was
successful or not.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit a325c61
created_at: 2025-05-20T07:22:55Z
author_association: NONE
diff_hunk: @@ -0,0 +1,113 @@
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEngine.AddressableAssets;
+using UnityEngine.ResourceManagement.AsyncOperations;
+
+namespace PLATEAU.DynamicTile
+{
+    public class PLATEAUTileManager : MonoBehaviour
+    {
+        private Dictionary<string, GameObject> loadedObjects = new Dictionary<string, GameObject>();
+
+        [SerializeField]
+        private List<PLATEAUDynamicTile> excludeTiles = new List<PLATEAUDynamicTile>(); // 除外タイルリスト
+
+        void Start()
+        {
+            // TODO: カメラ位置・視野に応じてロード処理を行うようにする
+            int downSampleLevel = 0; // 仮のダウンサンプルレベル
+            PLATEAUDynamicTile[] dynamicTiles = GameObject.FindObjectsOfType<PLATEAUDynamicTile>();
+            foreach (var tile in dynamicTiles)
+            {
+                // 子要素をすべて削除
+                for (int i = tile.transform.childCount - 1; i >= 0; i--)
+                {
+                    DestroyImmediate(tile.transform.GetChild(i).gameObject);
+                }
+                
+                Load(tile, downSampleLevel);
+            }
+        }
+
+        /// <summary>
+        /// 指定したDynamicTileをもとにAddressablesからロードする
+        /// </summary>
+        public void Load(PLATEAUDynamicTile tile, int downSampleLevel)
+        {
+            string address;
+            if (excludeTiles.Contains(tile))
+            {
+                address = tile.GetOriginalAddress();
+            }
+            else
+            {
+                address = tile.GetAddress(downSampleLevel);
+            }
+            if (string.IsNullOrEmpty(address))
+            {
+                Debug.LogWarning($"指定したアドレスが見つかりません: {tile.name}");
+                return;
+            }
+            Transform parent = tile.transform;
+            LoadByAddress(address, parent);
+        }
+
+        /// <summary>
+        /// Addressを指定してAddressablesからロードする
+        /// </summary>
+        private void LoadByAddress(string address, Transform parent = null)
+        {
+            Addressables.InstantiateAsync(address, parent).Completed += handle =>
+            {
+                if (handle.Status == AsyncOperationStatus.Succeeded)
+                {
+                    loadedObjects[address] = handle.Result;
+                    Debug.Log($"Loaded: {address}");
+                }
+                else
+                {
+                    Debug.LogError($"Failed to load: {address}");
+                }
+            };
+        }
path: Runtime/DynamicTile/PLATEAUTileManager.cs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**LoadByAddress メソッドのエラーハンドリング改善**

Addressables の非同期操作においてエラー処理が限定的です。また、すでにロードされているアドレスの重複ロードについての考慮がありません。


以下の改善を検討してください：

1. 既存の割り当てを確認する
2. エラー処理を強化する
3. リトライメカニズムの検討

```diff
/// <summary>
/// Addressを指定してAddressablesからロードする
/// </summary>
private void LoadByAddress(string address, Transform parent = null)
{
+   // 既にロードされている場合は処理をスキップ
+   if (loadedObjects.ContainsKey(address))
+   {
+       Debug.Log($"Already loaded: {address}");
+       return;
+   }

    Addressables.InstantiateAsync(address, parent).Completed += handle =>
    {
        if (handle.Status == AsyncOperationStatus.Succeeded)
        {
            loadedObjects[address] = handle.Result;
            Debug.Log($"Loaded: {address}");
        }
        else
        {
            Debug.LogError($"Failed to load: {address}");
+           // 失敗の詳細情報を記録
+           if (handle.OperationException != null)
+           {
+               Debug.LogException(handle.OperationException);
+           }
        }
    };
}
```

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In Runtime/DynamicTile/PLATEAUTileManager.cs around lines 58 to 72, improve
LoadByAddress by first checking if the address is already loaded in
loadedObjects to prevent duplicate loading. Enhance error handling by capturing
and logging detailed error information from the failed async operation.
Optionally, implement a retry mechanism that attempts to reload the address a
limited number of times upon failure before logging the error.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit a325c61
created_at: 2025-05-20T07:22:55Z
author_association: NONE
diff_hunk: @@ -0,0 +1,113 @@
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEngine.AddressableAssets;
+using UnityEngine.ResourceManagement.AsyncOperations;
+
+namespace PLATEAU.DynamicTile
+{
+    public class PLATEAUTileManager : MonoBehaviour
+    {
+        private Dictionary<string, GameObject> loadedObjects = new Dictionary<string, GameObject>();
+
+        [SerializeField]
+        private List<PLATEAUDynamicTile> excludeTiles = new List<PLATEAUDynamicTile>(); // 除外タイルリスト
+
+        void Start()
+        {
+            // TODO: カメラ位置・視野に応じてロード処理を行うようにする
+            int downSampleLevel = 0; // 仮のダウンサンプルレベル
+            PLATEAUDynamicTile[] dynamicTiles = GameObject.FindObjectsOfType<PLATEAUDynamicTile>();
+            foreach (var tile in dynamicTiles)
+            {
+                // 子要素をすべて削除
+                for (int i = tile.transform.childCount - 1; i >= 0; i--)
+                {
+                    DestroyImmediate(tile.transform.GetChild(i).gameObject);
+                }
+                
+                Load(tile, downSampleLevel);
+            }
+        }
+
+        /// <summary>
+        /// 指定したDynamicTileをもとにAddressablesからロードする
+        /// </summary>
+        public void Load(PLATEAUDynamicTile tile, int downSampleLevel)
+        {
+            string address;
+            if (excludeTiles.Contains(tile))
+            {
+                address = tile.GetOriginalAddress();
+            }
+            else
+            {
+                address = tile.GetAddress(downSampleLevel);
+            }
+            if (string.IsNullOrEmpty(address))
+            {
+                Debug.LogWarning($"指定したアドレスが見つかりません: {tile.name}");
+                return;
+            }
+            Transform parent = tile.transform;
+            LoadByAddress(address, parent);
+        }
path: Runtime/DynamicTile/PLATEAUTileManager.cs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Load メソッドの改善**

現在の実装では、オブジェクトが既にロードされているかどうかのチェックがなく、重複ロードの可能性があります。また、Unload メソッドと共通のコード部分があります。


以下の改善を検討してください：

1. 対象アドレスが既にロードされているかチェックする
2. 共通処理を抽出する
3. 操作の成功/失敗を示す戻り値を追加する

```diff
/// <summary>
/// 指定したDynamicTileをもとにAddressablesからロードする
/// </summary>
- public void Load(PLATEAUDynamicTile tile, int downSampleLevel)
+ public bool Load(PLATEAUDynamicTile tile, int downSampleLevel)
{
-   string address;
-   if (excludeTiles.Contains(tile))
-   {
-       address = tile.GetOriginalAddress();
-   }
-   else
-   {
-       address = tile.GetAddress(downSampleLevel);
-   }
+   string address = GetTileAddress(tile, downSampleLevel);
    if (string.IsNullOrEmpty(address))
    {
        Debug.LogWarning($"指定したアドレスが見つかりません: {tile.name}");
-       return;
+       return false;
+   }
+   
+   // 既にロードされている場合は処理をスキップ
+   if (loadedObjects.ContainsKey(address))
+   {
+       Debug.Log($"Already loaded: {address}");
+       return true;
    }
    Transform parent = tile.transform;
    LoadByAddress(address, parent);
+   return true;
}

+ /// <summary>
+ /// タイルのアドレスを取得する（除外リストを考慮）
+ /// </summary>
+ private string GetTileAddress(PLATEAUDynamicTile tile, int downSampleLevel)
+ {
+     if (excludeTiles.Contains(tile))
+     {
+         return tile.GetOriginalAddress();
+     }
+     else
+     {
+         return tile.GetAddress(downSampleLevel);
+     }
+ }
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
        public bool Load(PLATEAUDynamicTile tile, int downSampleLevel)
        {
            string address = GetTileAddress(tile, downSampleLevel);
            if (string.IsNullOrEmpty(address))
            {
                Debug.LogWarning($"指定したアドレスが見つかりません: {tile.name}");
                return false;
            }
            
            // 既にロードされている場合は処理をスキップ
            if (loadedObjects.ContainsKey(address))
            {
                Debug.Log($"Already loaded: {address}");
                return true;
            }
            
            Transform parent = tile.transform;
            LoadByAddress(address, parent);
            return true;
        }

        /// <summary>
        /// タイルのアドレスを取得する（除外リストを考慮）
        /// </summary>
        private string GetTileAddress(PLATEAUDynamicTile tile, int downSampleLevel)
        {
            if (excludeTiles.Contains(tile))
            {
                return tile.GetOriginalAddress();
            }
            else
            {
                return tile.GetAddress(downSampleLevel);
            }
        }
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In Runtime/DynamicTile/PLATEAUTileManager.cs around lines 35 to 53, the Load
method lacks a check to see if the object at the target address is already
loaded, risking duplicate loads. Refactor by adding a check to verify if the
address is already loaded before proceeding. Extract common code shared with the
Unload method into a separate helper method to avoid duplication. Modify the
Load method to return a boolean indicating success or failure of the load
operation.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit a325c61
created_at: 2025-05-20T07:22:55Z
author_association: NONE
diff_hunk: @@ -0,0 +1,113 @@
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEngine.AddressableAssets;
+using UnityEngine.ResourceManagement.AsyncOperations;
+
+namespace PLATEAU.DynamicTile
+{
+    public class PLATEAUTileManager : MonoBehaviour
+    {
+        private Dictionary<string, GameObject> loadedObjects = new Dictionary<string, GameObject>();
+
+        [SerializeField]
+        private List<PLATEAUDynamicTile> excludeTiles = new List<PLATEAUDynamicTile>(); // 除外タイルリスト
+
+        void Start()
+        {
+            // TODO: カメラ位置・視野に応じてロード処理を行うようにする
+            int downSampleLevel = 0; // 仮のダウンサンプルレベル
+            PLATEAUDynamicTile[] dynamicTiles = GameObject.FindObjectsOfType<PLATEAUDynamicTile>();
+            foreach (var tile in dynamicTiles)
+            {
+                // 子要素をすべて削除
+                for (int i = tile.transform.childCount - 1; i >= 0; i--)
+                {
+                    DestroyImmediate(tile.transform.GetChild(i).gameObject);
+                }
+                
+                Load(tile, downSampleLevel);
+            }
+        }
+
+        /// <summary>
+        /// 指定したDynamicTileをもとにAddressablesからロードする
+        /// </summary>
+        public void Load(PLATEAUDynamicTile tile, int downSampleLevel)
+        {
+            string address;
+            if (excludeTiles.Contains(tile))
+            {
+                address = tile.GetOriginalAddress();
+            }
+            else
+            {
+                address = tile.GetAddress(downSampleLevel);
+            }
+            if (string.IsNullOrEmpty(address))
+            {
+                Debug.LogWarning($"指定したアドレスが見つかりません: {tile.name}");
+                return;
+            }
+            Transform parent = tile.transform;
+            LoadByAddress(address, parent);
+        }
+
+        /// <summary>
+        /// Addressを指定してAddressablesからロードする
+        /// </summary>
+        private void LoadByAddress(string address, Transform parent = null)
+        {
+            Addressables.InstantiateAsync(address, parent).Completed += handle =>
+            {
+                if (handle.Status == AsyncOperationStatus.Succeeded)
+                {
+                    loadedObjects[address] = handle.Result;
+                    Debug.Log($"Loaded: {address}");
+                }
+                else
+                {
+                    Debug.LogError($"Failed to load: {address}");
+                }
+            };
+        }
+
+        /// <summary>
+        /// 指定したDynamicTileに対応するAddressablesインスタンスをアンロードする
+        /// </summary>
+        public void Unload(PLATEAUDynamicTile tile, int downSampleLevel)
+        {
+            string address;
+            if (excludeTiles.Contains(tile))
+            {
+                address = tile.GetOriginalAddress();
+            }
+            else
+            {
+                address = tile.GetAddress(downSampleLevel);
+            }
+            if (string.IsNullOrEmpty(address))
+            {
+                Debug.LogWarning($"指定したアドレスが見つかりません: {tile.name}");
+                return;
+            }
+            UnloadByAddress(address);
+        }
path: Runtime/DynamicTile/PLATEAUTileManager.cs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Unloadメソッドはコードの重複があります。**

LoadメソッドとUnloadメソッドの間でアドレス決定ロジックが重複しています。これを共通メソッドに抽出するとコード品質が向上します。

```diff
+    /// <summary>
+    /// タイルとダウンサンプルレベルからアドレスを取得する
+    /// </summary>
+    private string GetTileAddress(PLATEAUDynamicTile tile, int downSampleLevel)
+    {
+        if (tile == null) return null;
+        
+        string address;
+        if (excludeTiles.Contains(tile))
+        {
+            address = tile.GetOriginalAddress();
+        }
+        else
+        {
+            address = tile.GetAddress(downSampleLevel);
+        }
+        return address;
+    }
+
     /// <summary>
     /// 指定したDynamicTileに対応するAddressablesインスタンスをアンロードする
     /// </summary>
     public void Unload(PLATEAUDynamicTile tile, int downSampleLevel)
     {
-        string address;
-        if (excludeTiles.Contains(tile))
+        if (tile == null)
         {
-            address = tile.GetOriginalAddress();
-        }
-        else
-        {
-            address = tile.GetAddress(downSampleLevel);
+            Debug.LogError("PLATEAUDynamicTileがnullです");
+            return;
         }
+        
+        string address = GetTileAddress(tile, downSampleLevel);
         if (string.IsNullOrEmpty(address))
         {
             Debug.LogWarning($"指定したアドレスが見つかりません: {tile.name}");
             return;
         }
         UnloadByAddress(address);
     }
```

Loadメソッドも同様に修正すべきです:

```diff
     public void Load(PLATEAUDynamicTile tile, int downSampleLevel)
     {
         if (tile == null)
         {
             Debug.LogError("PLATEAUDynamicTileがnullです");
             return;
         }
-        
-        string address;
-        if (excludeTiles.Contains(tile))
-        {
-            address = tile.GetOriginalAddress();
-        }
-        else
-        {
-            address = tile.GetAddress(downSampleLevel);
-        }
+
+        string address = GetTileAddress(tile, downSampleLevel);
         if (string.IsNullOrEmpty(address))
         {
             Debug.LogWarning($"指定したアドレスが見つかりません: {tile.name}");
             return;
         }
```


> Committable suggestion skipped: line range outside the PR's diff.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In Runtime/DynamicTile/PLATEAUTileManager.cs around lines 74 to 94, the Unload
method duplicates the logic for determining the address of a tile, which is also
present in the Load method. To fix this, extract the address determination logic
into a separate private method that takes a tile and downSampleLevel as
parameters and returns the address string. Then, update both Load and Unload
methods to call this new method to get the address, eliminating the duplicated
code and improving maintainability.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit a325c61
created_at: 2025-05-20T07:22:55Z
author_association: NONE
diff_hunk: @@ -0,0 +1,113 @@
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEngine.AddressableAssets;
+using UnityEngine.ResourceManagement.AsyncOperations;
+
+namespace PLATEAU.DynamicTile
+{
+    public class PLATEAUTileManager : MonoBehaviour
+    {
+        private Dictionary<string, GameObject> loadedObjects = new Dictionary<string, GameObject>();
+
+        [SerializeField]
+        private List<PLATEAUDynamicTile> excludeTiles = new List<PLATEAUDynamicTile>(); // 除外タイルリスト
+
+        void Start()
+        {
+            // TODO: カメラ位置・視野に応じてロード処理を行うようにする
+            int downSampleLevel = 0; // 仮のダウンサンプルレベル
+            PLATEAUDynamicTile[] dynamicTiles = GameObject.FindObjectsOfType<PLATEAUDynamicTile>();
+            foreach (var tile in dynamicTiles)
+            {
+                // 子要素をすべて削除
+                for (int i = tile.transform.childCount - 1; i >= 0; i--)
+                {
+                    DestroyImmediate(tile.transform.GetChild(i).gameObject);
+                }
+                
+                Load(tile, downSampleLevel);
+            }
+        }
+
+        /// <summary>
+        /// 指定したDynamicTileをもとにAddressablesからロードする
+        /// </summary>
+        public void Load(PLATEAUDynamicTile tile, int downSampleLevel)
+        {
+            string address;
+            if (excludeTiles.Contains(tile))
+            {
+                address = tile.GetOriginalAddress();
+            }
+            else
+            {
+                address = tile.GetAddress(downSampleLevel);
+            }
+            if (string.IsNullOrEmpty(address))
+            {
+                Debug.LogWarning($"指定したアドレスが見つかりません: {tile.name}");
+                return;
+            }
+            Transform parent = tile.transform;
+            LoadByAddress(address, parent);
+        }
+
+        /// <summary>
+        /// Addressを指定してAddressablesからロードする
+        /// </summary>
+        private void LoadByAddress(string address, Transform parent = null)
+        {
+            Addressables.InstantiateAsync(address, parent).Completed += handle =>
+            {
+                if (handle.Status == AsyncOperationStatus.Succeeded)
+                {
+                    loadedObjects[address] = handle.Result;
+                    Debug.Log($"Loaded: {address}");
+                }
+                else
+                {
+                    Debug.LogError($"Failed to load: {address}");
+                }
+            };
+        }
+
+        /// <summary>
+        /// 指定したDynamicTileに対応するAddressablesインスタンスをアンロードする
+        /// </summary>
+        public void Unload(PLATEAUDynamicTile tile, int downSampleLevel)
+        {
+            string address;
+            if (excludeTiles.Contains(tile))
+            {
+                address = tile.GetOriginalAddress();
+            }
+            else
+            {
+                address = tile.GetAddress(downSampleLevel);
+            }
+            if (string.IsNullOrEmpty(address))
+            {
+                Debug.LogWarning($"指定したアドレスが見つかりません: {tile.name}");
+                return;
+            }
+            UnloadByAddress(address);
+        }
+
+        /// <summary>
+        /// Addressで指定したインスタンスをAddressablesからアンロードする
+        /// </summary>
+        private void UnloadByAddress(string address)
+        {
+            if (loadedObjects.ContainsKey(address))
+            {
+                Addressables.ReleaseInstance(loadedObjects[address]);
+                loadedObjects.Remove(address);
+                Debug.Log($"Unloaded: {address}");
+            }
+            else
+            {
+                Debug.LogWarning($"Object not loaded: {address}");
+            }
+        }
+    }
+}
path: Runtime/DynamicTile/PLATEAUTileManager.cs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**クラス全体のライフサイクル管理についての提案**

このクラスには、シーン終了時やコンポーネント破棄時に残存するリソースをクリーンアップするOnDestroyメソッドがありません。Addressablesリソースのメモリリークを防ぐため、追加することをお勧めします。

```diff
+    void OnDestroy()
+    {
+        // すべてのロード済みオブジェクトをアンロード
+        var addresses = new List<string>(loadedObjects.Keys);
+        foreach (var address in addresses)
+        {
+            UnloadByAddress(address);
+        }
+        
+        // ディクショナリをクリア
+        loadedObjects.Clear();
+    }
```

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In Runtime/DynamicTile/PLATEAUTileManager.cs from lines 1 to 113, the class
lacks an OnDestroy method to clean up loaded Addressables resources when the
component or scene is destroyed. To fix this, implement an OnDestroy method that
iterates over all loadedObjects, releases each instance using
Addressables.ReleaseInstance, clears the loadedObjects dictionary, and logs the
cleanup. This ensures proper resource management and prevents memory leaks.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit a325c61
created_at: 2025-05-20T07:22:55Z
author_association: NONE
diff_hunk: @@ -0,0 +1,65 @@
+using PLATEAU.DynamicTile;
+using PLATEAU.Editor.Addressables;
+using PLATEAU.Util;
+using System.IO;
+using UnityEditor;
+using UnityEngine;
+using UnityEngine.AddressableAssets;
+
+namespace PLATEAU.Editor.Window.Main.Tab.DynamicTileGUI
+{
+    [CustomEditor(typeof(PLATEAUTileManager))]
+    public class PLATEAUTileManagerEditor : UnityEditor.Editor
+    {
+        private string previousCatalogPath;
+
+        private void OnEnable()
+        {
+            var manager = (PLATEAUTileManager)target;
+            previousCatalogPath = manager.CatalogPath;
+            
+            EditorPrefs.SetBool("PLATEAU_NEEDS_ADDRESSABLES_BUILD", false);
+        }
+
+        public override void OnInspectorGUI()
+        {
+            base.OnInspectorGUI();
+
+            var manager = (PLATEAUTileManager)target;
+            if (manager.CatalogPath != previousCatalogPath)
+            {
+                previousCatalogPath = manager.CatalogPath;
+
+                if (string.IsNullOrEmpty(previousCatalogPath))
+                {
+                    return;
+                }
+                EditorPrefs.SetBool("PLATEAU_NEEDS_ADDRESSABLES_BUILD", true);
+            }
+
+            if (EditorPrefs.GetBool("PLATEAU_NEEDS_ADDRESSABLES_BUILD"))
+            {
+                if (GUILayout.Button("Addressablesを再ビルド"))
+                {
+                    if (!File.Exists(previousCatalogPath))
+                    {
+                        Dialogue.Display("カタログファイルが見つかりません: " + previousCatalogPath, "OK");
+                        return;
+                    }
+                    
+                    var directoryPath = Path.GetDirectoryName(previousCatalogPath);
+                    
path: Editor/Window/Main/Tab/DynamicTileGUI/PLATEAUTileManagerEditor.cs
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**ディレクトリパスのnullチェックが必要です**

`Path.GetDirectoryName`は無効なパスの場合`null`を返す可能性があります。


```diff
 if (!File.Exists(previousCatalogPath))
 {
     Dialogue.Display("カタログファイルが見つかりません: " + previousCatalogPath, "OK");
     return;
 }
 
 var directoryPath = Path.GetDirectoryName(previousCatalogPath);
+if (string.IsNullOrEmpty(directoryPath))
+{
+    Dialogue.Display("カタログファイルのディレクトリパスが無効です: " + previousCatalogPath, "OK");
+    return;
+}
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
                    if (!File.Exists(previousCatalogPath))
                    {
                        Dialogue.Display("カタログファイルが見つかりません: " + previousCatalogPath, "OK");
                        return;
                    }
                    
                    var directoryPath = Path.GetDirectoryName(previousCatalogPath);
+                   if (string.IsNullOrEmpty(directoryPath))
+                   {
+                       Dialogue.Display("カタログファイルのディレクトリパスが無効です: " + previousCatalogPath, "OK");
+                       return;
+                   }
                    
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In Editor/Window/Main/Tab/DynamicTileGUI/PLATEAUTileManagerEditor.cs around
lines 44 to 51, add a null check for the directoryPath variable after calling
Path.GetDirectoryName(previousCatalogPath) because it can return null for
invalid paths. If directoryPath is null, handle this case appropriately, such as
displaying an error message or returning early, to prevent null reference
exceptions later in the code.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit 180863f
created_at: 2025-05-27T02:52:25Z
author_association: NONE
diff_hunk: @@ -0,0 +1,65 @@
+using PLATEAU.DynamicTile;
+using PLATEAU.Editor.Addressables;
+using PLATEAU.Util;
+using System.IO;
+using UnityEditor;
+using UnityEngine;
+using UnityEngine.AddressableAssets;
+
+namespace PLATEAU.Editor.Window.Main.Tab.DynamicTileGUI
+{
+    [CustomEditor(typeof(PLATEAUTileManager))]
+    public class PLATEAUTileManagerEditor : UnityEditor.Editor
+    {
+        private string previousCatalogPath;
+
+        private void OnEnable()
+        {
+            var manager = (PLATEAUTileManager)target;
+            previousCatalogPath = manager.CatalogPath;
+            
+            EditorPrefs.SetBool("PLATEAU_NEEDS_ADDRESSABLES_BUILD", false);
+        }
path: Editor/Window/Main/Tab/DynamicTileGUI/PLATEAUTileManagerEditor.cs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**EditorPrefsのキーを定数化すべきです**

ハードコードされた文字列は、タイプミスや重複を招きやすいため、定数として定義することを推奨します。


```diff
 public class PLATEAUTileManagerEditor : UnityEditor.Editor
 {
+    private const string NEEDS_ADDRESSABLES_BUILD_KEY = "PLATEAU_NEEDS_ADDRESSABLES_BUILD";
     private string previousCatalogPath;

     private void OnEnable()
     {
         var manager = (PLATEAUTileManager)target;
         previousCatalogPath = manager.CatalogPath;
         
-        EditorPrefs.SetBool("PLATEAU_NEEDS_ADDRESSABLES_BUILD", false);
+        EditorPrefs.SetBool(NEEDS_ADDRESSABLES_BUILD_KEY, false);
     }
```

OnInspectorGUIメソッド内でも同様に置き換えてください。


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
public class PLATEAUTileManagerEditor : UnityEditor.Editor
{
    private const string NEEDS_ADDRESSABLES_BUILD_KEY = "PLATEAU_NEEDS_ADDRESSABLES_BUILD";
    private string previousCatalogPath;

    private void OnEnable()
    {
        var manager = (PLATEAUTileManager)target;
        previousCatalogPath = manager.CatalogPath;
        
        EditorPrefs.SetBool(NEEDS_ADDRESSABLES_BUILD_KEY, false);
    }
}
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In Editor/Window/Main/Tab/DynamicTileGUI/PLATEAUTileManagerEditor.cs around
lines 14 to 22, the EditorPrefs key "PLATEAU_NEEDS_ADDRESSABLES_BUILD" is
hardcoded. Define this string as a private constant at the class level and
replace all occurrences of the hardcoded string, including in the OnInspectorGUI
method, with this constant to avoid typos and duplication.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit 666080a
created_at: 2025-05-27T02:52:25Z
author_association: NONE
diff_hunk: @@ -0,0 +1,30 @@
+using PLATEAU.CityInfo;
+using PLATEAU.Dataset;
+using System;
+using System.Text.RegularExpressions;
+using UnityEngine;
+
+namespace PLATEAU.DynamicTile
+{
+    [Serializable]
+    public class PLATEAUDynamicTile
+    {
+        [SerializeField]
+        private string address;
+        
+        [SerializeField]
+        private Transform parent;
+        
+        [SerializeField]
+        private bool isExcluded;
+
+        // TODO: Extentの取得
+        [SerializeField]
+        private Bounds Extent;
+
path: Runtime/DynamicTile/PLATEAUDynamicTile.cs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**フィールドへのアクセサーが不足しています**

すべてのフィールドがprivateで、外部からアクセスする方法がありません。PLATEAUTileManagerなどの他のクラスからこれらの値を読み取る必要がある場合、public getterプロパティを追加することを検討してください。


```diff
 [SerializeField]
 private string address;
+public string Address => address;

 [SerializeField]
 private Transform parent;
+public Transform Parent => parent;

 [SerializeField]
 private bool isExcluded;
+public bool IsExcluded => isExcluded;

 // TODO: Extentの取得
 [SerializeField]
 private Bounds Extent;
+public Bounds ExtentBounds => Extent;
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
        [SerializeField]
        private string address;
        public string Address => address;
        
        [SerializeField]
        private Transform parent;
        public Transform Parent => parent;
        
        [SerializeField]
        private bool isExcluded;
        public bool IsExcluded => isExcluded;
        
        // TODO: Extentの取得
        [SerializeField]
        private Bounds Extent;
        public Bounds ExtentBounds => Extent;
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In Runtime/DynamicTile/PLATEAUDynamicTile.cs around lines 12 to 24, all
serialized fields are private without any public accessors, preventing external
classes like PLATEAUTileManager from reading their values. Add public getter
properties for each private field to allow safe read-only access from other
classes while keeping the fields themselves private.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit 34f7d5f
created_at: 2025-05-27T06:07:52Z
author_association: NONE
diff_hunk: @@ -39,77 +39,131 @@ public static void Export(
             }
 
             var groupName = AddressableGroupName;
-            if (!string.IsNullOrEmpty(buildFolderPath))
+            if (isExcludeAssetFolder)
             {
                 // ビルドフォルダパスを指定する場合はグループを分ける
                 var directoryName = Path.GetFileName(
                     buildFolderPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar));
                 groupName += "_" + directoryName;
             }
 
+            var addresses = new List<string>();
             foreach (var cityObject in cityObjects)
             {
-                if (excludeObjects.Contains(cityObject))
+                if (cityObject == null || cityObject.gameObject == null)
                 {
+                    Debug.LogWarning($"GameObjectがnullです。");
                     continue;
                 }
+            
                 assetConfig.SrcGameObj = cityObject.gameObject;
-
-                // 変換実行
-                var convertedObject = PrepareAndConvert(assetConfig, onError);
+            
+                var baseFolderPath = Path.Combine(assetConfig.AssetPath, cityObject.gameObject.name);
+                var saveFolderPath = baseFolderPath;
+                int count = 1;
+                // 同名のディレクトリが存在する場合は、_1, _2, ... のように連番を付けて保存
+                while (Directory.Exists(saveFolderPath))
+                {
+                    saveFolderPath = $"{baseFolderPath}_{count}";
+                    count++;
+                }
+                
+                var convertedObject = PrepareAndConvert(assetConfig, saveFolderPath, onError);
                 if (convertedObject == null)
                 {
                     Debug.LogWarning($"{cityObject.gameObject.name} の変換に失敗しました。");
                     continue;
                 }
                 
-                string prefabPath = $"{assetConfig.AssetPath}/{convertedObject.name}.prefab";
+                // TODO: タイルごとにプレハブを保存する
+                string prefabPath = saveFolderPath + ".prefab";
                 var prefabAsset = PrefabUtility.SaveAsPrefabAsset(convertedObject, prefabPath);
                 if (prefabAsset == null)
                 {
-                    Debug.LogWarning($"{cityObject.gameObject.name} プレハブの保存に失敗しました。");
+                    Debug.LogWarning($"{convertedObject.name} プレハブの保存に失敗しました。");
                     continue;
                 }
-
+            
                 // プレハブをAddressableに登録
+                // TODO : タイルごとにAddress名を設定する
+                var address = prefabAsset.name;
                 AddressablesUtility.RegisterAssetAsAddressable(
                     prefabPath,
-                    prefabPath,
+                    address,
                     groupName,
                     new List<string> { AddressableLabel });
+                Debug.Log($"プレハブをAddressableに登録: {address}");
+                
+                addresses.Add(address);
+            
+                // シーン上のオブジェクトを削除
+                GameObject.DestroyImmediate(convertedObject);
+            }
+
+            if (isExcludeAssetFolder)
+            {
+                // Remote用のプロファイルを作成
+                AddressablesUtility.SetOrCreateProfile(groupName);
+                AddressablesUtility.SetRemoteProfileSettings(buildFolderPath, groupName);
+                AddressablesUtility.SetGroupLoadAndBuildPath(groupName);
+            }
+            else
+            {
+                // プロファイルをデフォルトに設定
+                AddressablesUtility.SetOrCreateProfile("Default");
+            }
+
+            // Addressablesのビルドを実行
+            AddressablesUtility.BuildAddressables();
+            
+            // DynamicTile管理用GameObjectを生成
+            var manager = GameObject.FindObjectOfType<PLATEAUTileManager>();
+            if (manager == null)
+            {
+                GameObject managerObj = new GameObject("DynamicTileManager");
+                manager = managerObj.AddComponent<PLATEAUTileManager>();
             }
 
-            if (!string.IsNullOrEmpty(buildFolderPath))
+            manager.SaveCatalogPath("");
+            if (isExcludeAssetFolder)
             {
-                if (!Directory.Exists(buildFolderPath))
+                // カタログファイルのパスを取得
+                var catalogFiles = Directory.GetFiles(buildFolderPath, "catalog_*.json");
+                if (catalogFiles.Length == 0)
                 {
-                    onError?.Invoke("指定されたビルドフォルダが存在しません。");
+                    Debug.LogError("カタログファイルが見つかりません");
                     return;
                 }
-
-                // ビルドパスを指定
-                AddressablesUtility.SetGroupLoadAndBuildPath(groupName, buildFolderPath);
+                var catalogPath = catalogFiles[0]; // 最新のカタログファイルを使用
+                manager.SaveCatalogPath(catalogPath);
             }
path: Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**カタログファイル取得の堅牢性に問題があります**

`catalog_*.json`パターンでファイルを検索していますが、複数のファイルが存在する場合の選択基準が不明確です。また、ファイルの有効性チェックがありません。


```diff
 // カタログファイルのパスを取得
 var catalogFiles = Directory.GetFiles(buildFolderPath, "catalog_*.json");
 if (catalogFiles.Length == 0)
 {
     Debug.LogError("カタログファイルが見つかりません");
     return;
 }
-var catalogPath = catalogFiles[0]; // 最新のカタログファイルを使用
+// 最新のカタログファイルを使用（作成日時順）
+var catalogPath = catalogFiles
+    .OrderByDescending(f => File.GetCreationTime(f))
+    .FirstOrDefault();
+
+if (!File.Exists(catalogPath))
+{
+    Debug.LogError($"カタログファイルが無効です: {catalogPath}");
+    return;
+}
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
                var catalogFiles = Directory.GetFiles(buildFolderPath, "catalog_*.json");
                if (catalogFiles.Length == 0)
                {
                    Debug.LogError("カタログファイルが見つかりません");
                    return;
                }
                // 最新のカタログファイルを使用（作成日時順）
                var catalogPath = catalogFiles
                    .OrderByDescending(f => File.GetCreationTime(f))
                    .FirstOrDefault();
                
                if (!File.Exists(catalogPath))
                {
                    Debug.LogError($"カタログファイルが無効です: {catalogPath}");
                    return;
                }
                manager.SaveCatalogPath(catalogPath);
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs around lines 131
to 139, the code retrieves catalog files using a wildcard pattern but does not
clearly define which file to select if multiple matches exist, nor does it
validate the file's integrity. To fix this, implement a clear selection
criterion such as choosing the most recently modified file among the matches,
and add checks to verify that the selected file is valid and accessible before
proceeding. This will improve robustness in handling catalog files.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-27T06:07:52Z
author_association: NONE
diff_hunk: @@ -39,77 +39,131 @@ public static void Export(
             }
 
             var groupName = AddressableGroupName;
-            if (!string.IsNullOrEmpty(buildFolderPath))
+            if (isExcludeAssetFolder)
             {
                 // ビルドフォルダパスを指定する場合はグループを分ける
                 var directoryName = Path.GetFileName(
                     buildFolderPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar));
                 groupName += "_" + directoryName;
             }
 
+            var addresses = new List<string>();
             foreach (var cityObject in cityObjects)
             {
-                if (excludeObjects.Contains(cityObject))
+                if (cityObject == null || cityObject.gameObject == null)
                 {
+                    Debug.LogWarning($"GameObjectがnullです。");
                     continue;
                 }
+            
                 assetConfig.SrcGameObj = cityObject.gameObject;
-
-                // 変換実行
-                var convertedObject = PrepareAndConvert(assetConfig, onError);
+            
+                var baseFolderPath = Path.Combine(assetConfig.AssetPath, cityObject.gameObject.name);
+                var saveFolderPath = baseFolderPath;
+                int count = 1;
+                // 同名のディレクトリが存在する場合は、_1, _2, ... のように連番を付けて保存
+                while (Directory.Exists(saveFolderPath))
+                {
+                    saveFolderPath = $"{baseFolderPath}_{count}";
+                    count++;
+                }
+                
+                var convertedObject = PrepareAndConvert(assetConfig, saveFolderPath, onError);
                 if (convertedObject == null)
                 {
                     Debug.LogWarning($"{cityObject.gameObject.name} の変換に失敗しました。");
                     continue;
                 }
                 
-                string prefabPath = $"{assetConfig.AssetPath}/{convertedObject.name}.prefab";
+                // TODO: タイルごとにプレハブを保存する
+                string prefabPath = saveFolderPath + ".prefab";
                 var prefabAsset = PrefabUtility.SaveAsPrefabAsset(convertedObject, prefabPath);
                 if (prefabAsset == null)
                 {
-                    Debug.LogWarning($"{cityObject.gameObject.name} プレハブの保存に失敗しました。");
+                    Debug.LogWarning($"{convertedObject.name} プレハブの保存に失敗しました。");
                     continue;
                 }
-
+            
                 // プレハブをAddressableに登録
+                // TODO : タイルごとにAddress名を設定する
+                var address = prefabAsset.name;
                 AddressablesUtility.RegisterAssetAsAddressable(
                     prefabPath,
-                    prefabPath,
+                    address,
                     groupName,
                     new List<string> { AddressableLabel });
+                Debug.Log($"プレハブをAddressableに登録: {address}");
+                
+                addresses.Add(address);
+            
+                // シーン上のオブジェクトを削除
+                GameObject.DestroyImmediate(convertedObject);
+            }
+
+            if (isExcludeAssetFolder)
+            {
+                // Remote用のプロファイルを作成
+                AddressablesUtility.SetOrCreateProfile(groupName);
+                AddressablesUtility.SetRemoteProfileSettings(buildFolderPath, groupName);
+                AddressablesUtility.SetGroupLoadAndBuildPath(groupName);
+            }
+            else
+            {
+                // プロファイルをデフォルトに設定
+                AddressablesUtility.SetOrCreateProfile("Default");
+            }
+
+            // Addressablesのビルドを実行
+            AddressablesUtility.BuildAddressables();
path: Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**プロファイル管理とビルド戦略の検証が必要です**

リモートビルドとローカルビルドで異なるプロファイル設定を行っていますが、プロファイル作成やパス設定の失敗時のエラーハンドリングが不足しています。PRの目的で言及されている「Use Existing Build」モードでの問題に関連する可能性があります。


AddressablesUtilityメソッドの動作とエラーハンドリングを確認するスクリプトを実行します：

---

🏁 Script executed:

```shell
#!/bin/bash
# Description: AddressablesUtilityメソッドの実装を確認し、エラーハンドリングの有無をチェック

# AddressablesUtilityクラスの実装を確認
ast-grep --pattern $'class AddressablesUtility {
  $$$
  SetOrCreateProfile($_) {
    $$$
  }
  $$$
}'

# SetRemoteProfileSettingsメソッドの実装確認
ast-grep --pattern $'SetRemoteProfileSettings($_, $_) {
  $$$
}'

# BuildAddressablesメソッドの実装確認
ast-grep --pattern $'BuildAddressables() {
  $$$
}'
```

Length of output: 222

---

以下のスクリプトで `AddressablesUtility` クラスと各メソッドの定義箇所をリポジトリ全体から検索し、実装とエラーハンドリングの有無を確認してください。

---

🏁 Script executed:

```shell
#!/bin/bash
# AddressablesUtility クラス定義の検索
rg -n "class AddressablesUtility"

# 各メソッド定義の検索
rg -n "SetOrCreateProfile" -A 5
rg -n "SetRemoteProfileSettings" -A 5
rg -n "SetGroupLoadAndBuildPath" -A 5
rg -n "BuildAddressables" -A 5
```

Length of output: 7678

---



</details>

**AddressablesUtility のエラーハンドリング強化が必要です**  

現在、`RequireAddressableSettings()` の null チェックと警告ログのみを行い、失敗時には早期リターンしていますが、以下の観点が未対応です。  
- `SetOrCreateProfile(groupName)` の戻り値が null の場合のフォールバック処理  
- 各メソッド内（`SetRemoteProfileSettings`／`SetGroupLoadAndBuildPath`／`BuildAddressables`）での例外キャッチ  
- ビルド失敗時の明示的なエラーメッセージ出力やユーザー通知  

修正提案：  
- DynamicTileExporter.cs（103–117行目）で、`SetOrCreateProfile` の返り値を受け取って null チェックを追加  
- 上記メソッド呼び出しを try/catch で囲み、失敗理由をログまたはダイアログでユーザーにフィードバック  
- 必要に応じて「Use Existing Build」モード時の挙動検証とエラーパスを整備  

該当箇所：  
- Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs:103–117

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs around lines 103
to 117, enhance error handling by capturing the return value of
SetOrCreateProfile and checking for null to handle profile creation failures
gracefully. Wrap calls to SetRemoteProfileSettings, SetGroupLoadAndBuildPath,
and BuildAddressables in try/catch blocks to catch exceptions, log detailed
error messages, and notify the user appropriately. Ensure that in case of any
failure, the process either falls back safely or aborts with clear feedback,
especially considering the "Use Existing Build" mode behavior.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit 34f7d5f
created_at: 2025-05-27T06:07:52Z
author_association: NONE
diff_hunk: @@ -39,77 +39,131 @@ public static void Export(
             }
 
             var groupName = AddressableGroupName;
-            if (!string.IsNullOrEmpty(buildFolderPath))
+            if (isExcludeAssetFolder)
             {
                 // ビルドフォルダパスを指定する場合はグループを分ける
                 var directoryName = Path.GetFileName(
                     buildFolderPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar));
                 groupName += "_" + directoryName;
             }
 
+            var addresses = new List<string>();
             foreach (var cityObject in cityObjects)
             {
-                if (excludeObjects.Contains(cityObject))
+                if (cityObject == null || cityObject.gameObject == null)
                 {
+                    Debug.LogWarning($"GameObjectがnullです。");
                     continue;
                 }
+            
                 assetConfig.SrcGameObj = cityObject.gameObject;
-
-                // 変換実行
-                var convertedObject = PrepareAndConvert(assetConfig, onError);
+            
+                var baseFolderPath = Path.Combine(assetConfig.AssetPath, cityObject.gameObject.name);
+                var saveFolderPath = baseFolderPath;
+                int count = 1;
+                // 同名のディレクトリが存在する場合は、_1, _2, ... のように連番を付けて保存
+                while (Directory.Exists(saveFolderPath))
+                {
+                    saveFolderPath = $"{baseFolderPath}_{count}";
+                    count++;
+                }
+                
+                var convertedObject = PrepareAndConvert(assetConfig, saveFolderPath, onError);
                 if (convertedObject == null)
                 {
                     Debug.LogWarning($"{cityObject.gameObject.name} の変換に失敗しました。");
                     continue;
                 }
                 
-                string prefabPath = $"{assetConfig.AssetPath}/{convertedObject.name}.prefab";
+                // TODO: タイルごとにプレハブを保存する
+                string prefabPath = saveFolderPath + ".prefab";
                 var prefabAsset = PrefabUtility.SaveAsPrefabAsset(convertedObject, prefabPath);
                 if (prefabAsset == null)
                 {
-                    Debug.LogWarning($"{cityObject.gameObject.name} プレハブの保存に失敗しました。");
+                    Debug.LogWarning($"{convertedObject.name} プレハブの保存に失敗しました。");
                     continue;
                 }
-
+            
                 // プレハブをAddressableに登録
+                // TODO : タイルごとにAddress名を設定する
+                var address = prefabAsset.name;
path: Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**アドレス命名戦略を明確化する必要があります**

現在はプレハブアセット名をそのままアドレスとして使用していますが、TODOコメントが示すように、将来的にタイルごとやLOD別の命名が必要になる可能性があります。現在の命名規約では、複数のタイルが同じ名前を持つ場合に衝突が発生する可能性があります。


衝突を防ぐため、より一意性のあるアドレス命名戦略を検討してください：

```diff
 // プレハブをAddressableに登録
 // TODO : タイルごとにAddress名を設定する
-var address = prefabAsset.name;
+var address = $"{prefabAsset.name}_{System.Guid.NewGuid():N}";
```

または、階層パスベースの命名：

```diff
-var address = prefabAsset.name;
+var address = $"{groupName}_{prefabAsset.name}";
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
                // TODO: タイルごとにプレハブを保存する
                string prefabPath = saveFolderPath + ".prefab";
                var prefabAsset = PrefabUtility.SaveAsPrefabAsset(convertedObject, prefabPath);
                if (prefabAsset == null)
                {
                    Debug.LogWarning($"{convertedObject.name} プレハブの保存に失敗しました。");
                    continue;
                }
            
                // プレハブをAddressableに登録
                // TODO : タイルごとにAddress名を設定する
                var address = $"{prefabAsset.name}_{System.Guid.NewGuid():N}";
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs around lines 78
to 89, the current address naming uses the prefab asset name directly, which
risks collisions if multiple tiles share the same name. To fix this, implement a
more unique address naming strategy by incorporating tile-specific identifiers
such as tile coordinates, LOD levels, or hierarchical folder paths into the
address string. This ensures each address is unique and prevents conflicts when
registering prefabs as Addressables.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-27T06:07:52Z
author_association: NONE
diff_hunk: @@ -0,0 +1,121 @@
+using UnityEngine;
+using UnityEngine.AddressableAssets;
+using UnityEngine.ResourceManagement.AsyncOperations;
+using System.Threading.Tasks;
+using UnityEngine.ResourceManagement.ResourceLocations;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace PLATEAU.DynamicTile
+{
+    public class AddressableLoader
+    {
+        /// <summary>
+        /// Addressからアセットをインスタンス化します
+        /// </summary>
+        public async Task<GameObject> InstantiateAssetAsync(string address, Transform parent = null)
+        {
+            var handle = Addressables.InstantiateAsync(address, parent);
+            while (!handle.IsDone)
+            {
+                await Task.Yield();
+            }
+
+            if (handle.Status == AsyncOperationStatus.Succeeded)
+            {
+                handle.Result.name = address;
+                return handle.Result;
+            }
+            else
+            {
+                Debug.LogError($"アセットのロードに失敗しました: {address}");
+                return null;
+            }
+        }
path: Runtime/DynamicTile/AddressableLoader.cs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**戻り値のリソースハンドル管理が不足しています**

InstantiateAssetAsyncメソッドで返されるGameObjectのハンドルが管理されていません。呼び出し側でReleaseInstanceを呼ぶ必要がありますが、この責任が不明確です。


ドキュメンテーションでリソース管理の責任を明確化するか、ハンドルも返すオーバーロードを提供することを検討してください：

```diff
 /// <summary>
 /// Addressからアセットをインスタンス化します
+/// 注意: 返されたGameObjectは呼び出し側でAddressables.ReleaseInstanceを使用して解放する必要があります
 /// </summary>
 public async Task<GameObject> InstantiateAssetAsync(string address, Transform parent = null)
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
/// <summary>
/// Addressからアセットをインスタンス化します
/// 注意: 返されたGameObjectは呼び出し側でAddressables.ReleaseInstanceを使用して解放する必要があります
/// </summary>
public async Task<GameObject> InstantiateAssetAsync(string address, Transform parent = null)
{
    var handle = Addressables.InstantiateAsync(address, parent);
    while (!handle.IsDone)
    {
        await Task.Yield();
    }

    if (handle.Status == AsyncOperationStatus.Succeeded)
    {
        handle.Result.name = address;
        return handle.Result;
    }
    else
    {
        Debug.LogError($"アセットのロードに失敗しました: {address}");
        return null;
    }
}
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In Runtime/DynamicTile/AddressableLoader.cs around lines 17 to 35, the
InstantiateAssetAsync method returns a GameObject but does not expose or manage
the Addressables handle, making it unclear who is responsible for releasing the
resource. To fix this, either update the method's documentation to clearly state
that the caller must call ReleaseInstance on the returned GameObject, or provide
an overloaded version of the method that returns both the GameObject and its
handle so the caller can manage the lifecycle explicitly.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit 34f7d5f
created_at: 2025-05-27T06:07:52Z
author_association: NONE
diff_hunk: @@ -0,0 +1,129 @@
+using PLATEAU.CityInfo;
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEngine.AddressableAssets;
+using UnityEngine.ResourceManagement.AsyncOperations;
+
+namespace PLATEAU.DynamicTile
+{
+    public class PLATEAUTileManager : MonoBehaviour
+    {
+        [SerializeField]
+        private List<PLATEAUDynamicTile> dynamicTiles = new();
+        
+        [SerializeField]
+        private string catalogPath;
+        public string CatalogPath => catalogPath;
+
+        private const string DynamicTileLabelName = "DynamicTile";
+        private Dictionary<string, GameObject> loadedObjects = new Dictionary<string, GameObject>();
+        private AddressableLoader addressableLoader = new ();
+
+        async void Start()
+        {
+            // TODO: カメラからの距離に応じてLoad
+            
+            var cityModel = FindObjectOfType<PLATEAUInstancedCityModel>();
+            if (cityModel == null)
+            {
+                Debug.LogWarning("都市モデルが見つかりません。");
+                return;
+            }
+            
+            List<string> addresses;
+            if (!string.IsNullOrEmpty(catalogPath))
+            {
+                // カタログパスが指定されている場合は、カタログをロード
+                addresses = await addressableLoader.LoadCatalogAsync(catalogPath, DynamicTileLabelName);
+            }
+            else
+            {
+                // ローカルのアドレスをロード
+                addresses = await addressableLoader.LoadLocalAddresses(DynamicTileLabelName);
+            }
+            
+            foreach (var address in addresses)
+            {
+                Load(address, cityModel.transform);
+            }
+            
+            Debug.Log("全てのAddressablesのロードが完了しました");
+        }
+        
+        /// <summary>
+        /// カタログパスを保存します。
+        /// </summary>
+        /// <param name="path"></param>
+        public void SaveCatalogPath(string path)
+        {
+            // パスを正規化（バックスラッシュをスラッシュに変換）
+            catalogPath = path.Replace('\\', '/');
+        }
+
+        /// <summary>
+        /// Addressを指定してAddressablesからロードする
+        /// </summary>
+        public async void Load(string address, Transform parent = null)
+        {
+            try
+            {
+                var instance = await addressableLoader.InstantiateAssetAsync(address, parent);
+                if (instance != null)
+                {
+                    loadedObjects[address] = instance;
+                }
+            }
+            catch (Exception ex)
+            {
+                Debug.LogError($"アセットのロード中にエラーが発生しました: {ex.Message}");
+            }
+        }
+
+        /// <summary>
+        /// Addressを指定してAddressablesからアンロードする
+        /// </summary>
+        /// <param name="address"></param>
+        public void Unload(string address)
+        {
+            if (loadedObjects.TryGetValue(address, out var instance))
+            {
+                try
+                {
+                    Addressables.ReleaseInstance(instance);
+                    loadedObjects.Remove(address);
+                }
+                catch (Exception ex)
+                {
+                    Debug.LogError($"アセットのアンロード中にエラーが発生しました: {ex.Message}");
+                }
+            }
+            else
+            {
+                Debug.LogWarning($"アセット {address} はロードされていません");
+            }
+        }
+
+        /// <summary>
+        /// 破棄時にすべてのロード済みオブジェクトをアンロード
+        /// </summary>
+        private void OnDestroy()
+        {
+            foreach (var loadedObject  in loadedObjects)
+            {
+                Addressables.ReleaseInstance(loadedObject.Value);
+            }
+            loadedObjects.Clear();
+            Debug.Log("全てのアセットをアンロードしました");
+        }
+        
+        /// <summary>
+        /// Tileデータを追加
+        /// </summary>
+        /// <param name="tile"></param>
+        public void AddTile(PLATEAUDynamicTile tile)
+        {
+            dynamicTiles.Add(tile);
+        }
path: Runtime/DynamicTile/PLATEAUTileManager.cs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**AddTileメソッドにバリデーションが不足しています**

nullチェックや重複チェックがないため、無効なデータが追加される可能性があります。


```diff
 public void AddTile(PLATEAUDynamicTile tile)
 {
+    if (tile == null)
+    {
+        Debug.LogWarning("nullのタイルは追加できません");
+        return;
+    }
+    
+    if (dynamicTiles.Contains(tile))
+    {
+        Debug.LogWarning("既に追加済みのタイルです");
+        return;
+    }
+    
     dynamicTiles.Add(tile);
 }
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
        public void AddTile(PLATEAUDynamicTile tile)
        {
            if (tile == null)
            {
                Debug.LogWarning("nullのタイルは追加できません");
                return;
            }

            if (dynamicTiles.Contains(tile))
            {
                Debug.LogWarning("既に追加済みのタイルです");
                return;
            }

            dynamicTiles.Add(tile);
        }
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In Runtime/DynamicTile/PLATEAUTileManager.cs around lines 124 to 127, the
AddTile method lacks validation, allowing null or duplicate tiles to be added.
Add a null check to ensure the tile parameter is not null, and add a check to
prevent adding a tile that already exists in dynamicTiles before adding it.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit 34f7d5f
created_at: 2025-05-27T06:07:52Z
author_association: NONE
diff_hunk: @@ -0,0 +1,129 @@
+using PLATEAU.CityInfo;
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEngine.AddressableAssets;
+using UnityEngine.ResourceManagement.AsyncOperations;
+
+namespace PLATEAU.DynamicTile
+{
+    public class PLATEAUTileManager : MonoBehaviour
+    {
+        [SerializeField]
+        private List<PLATEAUDynamicTile> dynamicTiles = new();
+        
+        [SerializeField]
+        private string catalogPath;
+        public string CatalogPath => catalogPath;
+
+        private const string DynamicTileLabelName = "DynamicTile";
+        private Dictionary<string, GameObject> loadedObjects = new Dictionary<string, GameObject>();
+        private AddressableLoader addressableLoader = new ();
+
+        async void Start()
+        {
+            // TODO: カメラからの距離に応じてLoad
+            
+            var cityModel = FindObjectOfType<PLATEAUInstancedCityModel>();
+            if (cityModel == null)
+            {
+                Debug.LogWarning("都市モデルが見つかりません。");
+                return;
+            }
+            
+            List<string> addresses;
+            if (!string.IsNullOrEmpty(catalogPath))
+            {
+                // カタログパスが指定されている場合は、カタログをロード
+                addresses = await addressableLoader.LoadCatalogAsync(catalogPath, DynamicTileLabelName);
+            }
+            else
+            {
+                // ローカルのアドレスをロード
+                addresses = await addressableLoader.LoadLocalAddresses(DynamicTileLabelName);
+            }
+            
+            foreach (var address in addresses)
+            {
+                Load(address, cityModel.transform);
+            }
+            
+            Debug.Log("全てのAddressablesのロードが完了しました");
+        }
+        
+        /// <summary>
+        /// カタログパスを保存します。
+        /// </summary>
+        /// <param name="path"></param>
+        public void SaveCatalogPath(string path)
+        {
+            // パスを正規化（バックスラッシュをスラッシュに変換）
+            catalogPath = path.Replace('\\', '/');
+        }
+
+        /// <summary>
+        /// Addressを指定してAddressablesからロードする
+        /// </summary>
+        public async void Load(string address, Transform parent = null)
+        {
+            try
+            {
+                var instance = await addressableLoader.InstantiateAssetAsync(address, parent);
+                if (instance != null)
+                {
+                    loadedObjects[address] = instance;
+                }
path: Runtime/DynamicTile/PLATEAUTileManager.cs
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Load メソッドでの重複ロード対策が不足しています**

同じアドレスに対して複数回Load()が呼ばれた場合、既存のオブジェクトが上書きされてしまい、前のインスタンスがリークする可能性があります。過去のレビューコメントでも指摘されていた問題です。


```diff
 public async void Load(string address, Transform parent = null)
 {
+    // 既にロードされているかチェック
+    if (loadedObjects.ContainsKey(address))
+    {
+        Debug.LogWarning($"アドレス {address} は既にロードされています");
+        return;
+    }
+
     try
     {
         var instance = await addressableLoader.InstantiateAssetAsync(address, parent);
         if (instance != null)
         {
             loadedObjects[address] = instance;
         }
     }
     catch (Exception ex)
     {
         Debug.LogError($"アセットのロード中にエラーが発生しました: {ex.Message}");
     }
 }
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
public async void Load(string address, Transform parent = null)
{
    // 既にロードされているかチェック
    if (loadedObjects.ContainsKey(address))
    {
        Debug.LogWarning($"アドレス {address} は既にロードされています");
        return;
    }

    try
    {
        var instance = await addressableLoader.InstantiateAssetAsync(address, parent);
        if (instance != null)
        {
            loadedObjects[address] = instance;
        }
    }
    catch (Exception ex)
    {
        Debug.LogError($"アセットのロード中にエラーが発生しました: {ex.Message}");
    }
}
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In Runtime/DynamicTile/PLATEAUTileManager.cs around lines 71 to 75, the Load
method lacks protection against duplicate loads for the same address, causing
existing objects to be overwritten and potentially leaked. To fix this, check if
the address already exists in loadedObjects before instantiating a new asset; if
it does, return or reuse the existing instance instead of creating a new one.
This prevents multiple Load calls for the same address from overwriting and
leaking previous instances.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit 34f7d5f
created_at: 2025-05-27T06:07:52Z
author_association: NONE
diff_hunk: @@ -139,60 +159,118 @@ public static void SetGroupLoadAndBuildPath(string groupName, string path)
                 return;
             }
 
-            string customPathName = "CustomPath_" + groupName;
-            string profileName = "CustomProfile_" + groupName;
-            if (!AddressablesUtility.TryCreateProfile(profileName, out var profileId))
+            // グループのBuildPathとLoadPathに変数名をセット
+            bundledSchema.BuildPath.SetVariableByName(settings, groupName);
+            bundledSchema.LoadPath.SetVariableByName(settings, groupName);
+            
+            // グループの設定
+            bundledSchema.IncludeAddressInCatalog = true;
+            bundledSchema.IncludeGUIDInCatalog = true;
+            bundledSchema.IncludeLabelsInCatalog = true;
+            bundledSchema.IncludeInBuild = true;
+            bundledSchema.AssetLoadMode = AssetLoadMode.AllPackedAssetsAndDependencies;
+            
+            // bundleを個別にパックする
+            bundledSchema.BundleMode = UnityEditor.AddressableAssets.Settings.GroupSchemas.BundledAssetGroupSchema.BundlePackingMode.PackSeparately;
+        }
+        
+        /// <summary>
+        /// プロファイルを設定します。
+        /// </summary>
+        public static void SetRemoteProfileSettings(string path, string pathVar)
+        {
+            var settings = RequireAddressableSettings();
+            if (settings == null)
             {
-                Debug.LogError($"プロファイルの作成に失敗しました: {profileName}");
                 return;
             }
-            var profileSettings = settings.profileSettings;
             
-            // プロファイル変数がなければ作成
-            if (!profileSettings.GetVariableNames().Contains(customPathName))
+            // カタログ設定
+            settings.BuildRemoteCatalog = true;
+
+            var profileSettings = settings.profileSettings;
+            if (!profileSettings.GetVariableNames().Contains(pathVar))
             {
-                profileSettings.CreateValue(customPathName, path);
+                profileSettings.CreateValue(pathVar, path);
+            }
+            if (!profileSettings.GetVariableNames().Contains(pathVar))
+            {
+                profileSettings.CreateValue(pathVar, path);
             }
 
-            // そのプロファイルIDにのみ値をセット
-            profileSettings.SetValue(profileId, customPathName, path);
-
-            // アクティブプロファイルをこのプロファイルに切り替え
-            settings.activeProfileId = profileId;
-
-            // グループのBuildPathとLoadPathに変数名をセット
-            bundledSchema.BuildPath.SetVariableByName(settings, customPathName);
-            bundledSchema.LoadPath.SetVariableByName(settings, customPathName);
+            profileSettings.SetValue(settings.activeProfileId, pathVar, path);
+            profileSettings.SetValue(settings.activeProfileId, pathVar, path);
path: Editor/Addressables/AddressablesUtility.cs
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**重複コードを削除してください。**

コードに重複した部分があります：
- lines 192-199と196-199が重複
- lines 201-202が重複



以下の修正を適用してください：

```diff
             var profileSettings = settings.profileSettings;
             if (!profileSettings.GetVariableNames().Contains(pathVar))
             {
                 profileSettings.CreateValue(pathVar, path);
             }
-            if (!profileSettings.GetVariableNames().Contains(pathVar))
-            {
-                profileSettings.CreateValue(pathVar, path);
-            }

             profileSettings.SetValue(settings.activeProfileId, pathVar, path);
-            profileSettings.SetValue(settings.activeProfileId, pathVar, path);
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
            var profileSettings = settings.profileSettings;
            if (!profileSettings.GetVariableNames().Contains(pathVar))
            {
                profileSettings.CreateValue(pathVar, path);
            }

            profileSettings.SetValue(settings.activeProfileId, pathVar, path);
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In Editor/Addressables/AddressablesUtility.cs around lines 192 to 202, there are
duplicate code blocks: the checks and calls to CreateValue for pathVar and path
are repeated twice, as well as the calls to SetValue. Remove the duplicated if
block and the duplicated SetValue call so that each operation occurs only once.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit 34f7d5f
created_at: 2025-05-27T06:07:53Z
author_association: NONE
diff_hunk: @@ -68,7 +88,8 @@ public static void RegisterAssetAsAddressable(
             string assetPath,
             string address,
             string groupName,
-            List<string> labels)
+            List<string> labels,
+            bool packSeparately = false)
path: Editor/Addressables/AddressablesUtility.cs
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**未使用のパラメータを修正してください。**

`packSeparately`パラメータが追加されていますが、メソッド本体で使用されていません。このパラメータの目的を実装するか、不要であれば削除してください。


パラメータの目的が個別パッキングの設定であれば、以下のような実装が必要です：

```diff
             entry.SetAddress(address);
+            
+            if (packSeparately)
+            {
+                var bundledSchema = group.GetSchema<UnityEditor.AddressableAssets.Settings.GroupSchemas.BundledAssetGroupSchema>();
+                if (bundledSchema != null)
+                {
+                    bundledSchema.BundleMode = UnityEditor.AddressableAssets.Settings.GroupSchemas.BundledAssetGroupSchema.BundlePackingMode.PackSeparately;
+                }
+            }
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion
             entry.SetAddress(address);
            
             if (packSeparately)
             {
                 var bundledSchema = group.GetSchema<UnityEditor.AddressableAssets.Settings.GroupSchemas.BundledAssetGroupSchema>();
                 if (bundledSchema != null)
                 {
                     bundledSchema.BundleMode = UnityEditor.AddressableAssets.Settings.GroupSchemas.BundledAssetGroupSchema.BundlePackingMode.PackSeparately;
                 }
             }
```

</details>

<!-- suggestion_end -->

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In Editor/Addressables/AddressablesUtility.cs at line 92, the parameter
'packSeparately' is declared but not used in the method body. To fix this,
either implement the logic that uses 'packSeparately' to control separate
packing behavior as intended, or if this parameter is unnecessary, remove it
from the method signature to avoid unused parameter warnings.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit 34f7d5f
created_at: 2025-05-27T06:07:53Z
author_association: NONE
diff_hunk: @@ -139,60 +141,167 @@ public static void SetGroupLoadAndBuildPath(string groupName, string path)
                 return;
             }
 
-            string customPathName = "CustomPath_" + groupName;
-            string profileName = "CustomProfile_" + groupName;
-            if (!AddressablesUtility.TryCreateProfile(profileName, out var profileId))
+            // グループのBuildPathとLoadPathに変数名をセット
+            bundledSchema.BuildPath.SetVariableByName(settings, groupName);
+            bundledSchema.LoadPath.SetVariableByName(settings, groupName);
+            
+            // グループの設定
+            bundledSchema.IncludeAddressInCatalog = true;
+            bundledSchema.IncludeGUIDInCatalog = true;
+            bundledSchema.IncludeLabelsInCatalog = true;
+            bundledSchema.IncludeInBuild = true;
+            bundledSchema.AssetLoadMode = AssetLoadMode.AllPackedAssetsAndDependencies;
+            
+            // bundleを個別にパックする
+            bundledSchema.BundleMode = UnityEditor.AddressableAssets.Settings.GroupSchemas.BundledAssetGroupSchema.BundlePackingMode.PackSeparately;
+        }
+        
+        /// <summary>
+        /// プロファイルを設定します。
+        /// </summary>
+        public static void SetRemoteProfileSettings(string path, string pathVar)
+        {
+            var settings = RequireAddressableSettings();
+            if (settings == null)
             {
-                Debug.LogError($"プロファイルの作成に失敗しました: {profileName}");
                 return;
             }
-            var profileSettings = settings.profileSettings;
             
-            // プロファイル変数がなければ作成
-            if (!profileSettings.GetVariableNames().Contains(customPathName))
+            // カタログ設定
+            settings.BuildRemoteCatalog = true;
+
+            var profileSettings = settings.profileSettings;
+            if (!profileSettings.GetVariableNames().Contains(pathVar))
             {
-                profileSettings.CreateValue(customPathName, path);
+                profileSettings.CreateValue(pathVar, path);
             }
+            profileSettings.SetValue(settings.activeProfileId, pathVar, path);
 
-            // そのプロファイルIDにのみ値をセット
-            profileSettings.SetValue(profileId, customPathName, path);
+            settings.RemoteCatalogBuildPath.SetVariableByName(settings, pathVar);
+            settings.RemoteCatalogLoadPath.SetVariableByName(settings, pathVar);
+        }
path: Editor/Addressables/AddressablesUtility.cs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**パラメータ検証の追加を推奨します。**

`SetRemoteProfileSettings`メソッドでは`path`と`pathVar`パラメータのnullチェックが行われていません。無効な値が渡された場合に予期しない動作を引き起こす可能性があります。


以下の検証を追加してください：

```diff
 public static void SetRemoteProfileSettings(string path, string pathVar)
 {
+    if (string.IsNullOrEmpty(path))
+    {
+        Debug.LogError("pathパラメータが無効です。");
+        return;
+    }
+    
+    if (string.IsNullOrEmpty(pathVar))
+    {
+        Debug.LogError("pathVarパラメータが無効です。");
+        return;
+    }
+    
     var settings = RequireAddressableSettings();
```

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In Editor/Addressables/AddressablesUtility.cs around lines 162 to 182, the
SetRemoteProfileSettings method lacks null checks for the parameters path and
pathVar. Add validation at the start of the method to check if either path or
pathVar is null or empty, and if so, return early or throw an appropriate
exception to prevent unexpected behavior caused by invalid inputs.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->

✅ Addressed in commit 4a07d8a
created_at: 2025-05-27T09:58:18Z
author_association: NONE
diff_hunk: @@ -22,97 +23,168 @@ public static class DynamicTileExporter
         /// 都市モデルをDynamicTile用にプレハブ化し、一括エクスポートする。
         /// </summary>
         /// <param name="assetConfig">変換設定</param>
-        /// <param name="excludeObjects">除外するオブジェクト</param>
         /// <param name="buildFolderPath"></param>
         /// <param name="onError">エラー時のコールバック</param>
         public static void Export(
             ConvertToAssetConfig assetConfig,
-            List<PLATEAUCityObjectGroup> excludeObjects,
             string buildFolderPath,
             Action<string> onError = null)
         {
+            var isExcludeAssetFolder = !string.IsNullOrEmpty(buildFolderPath);
             var cityObjects = GameObject.FindObjectsOfType<PLATEAUCityObjectGroup>();
             if (cityObjects == null || cityObjects.Length == 0)
             {
                 onError?.Invoke("都市モデルが見つかりません。都市モデルをインポートしてください。");
                 return;
             }
+            
+            using var progressBar = new ProgressBar();
 
             var groupName = AddressableGroupName;
-            if (!string.IsNullOrEmpty(buildFolderPath))
+            if (isExcludeAssetFolder)
             {
                 // ビルドフォルダパスを指定する場合はグループを分ける
                 var directoryName = Path.GetFileName(
                     buildFolderPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar));
                 groupName += "_" + directoryName;
             }
 
-            foreach (var cityObject in cityObjects)
+            // グループを削除
+            AddressablesUtility.RemoveNonDefaultGroups();
path: Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**グループ削除の影響範囲の確認**

`RemoveNonDefaultGroups()` の呼び出しにより、他のプロジェクトで作成されたAddressableグループも削除される可能性があります。



以下のスクリプトでRemoveNonDefaultGroups()の実装を確認し、削除対象が適切かどうか検証してください：

---

🏁 Script executed:

```shell
#!/bin/bash
# Description: RemoveNonDefaultGroups メソッドの実装を確認

ast-grep --pattern $'RemoveNonDefaultGroups() {
  $$$
}'
```

Length of output: 57

---

実装箇所が出力されなかったため、以下のスクリプトで定義を再検索してください:

---

🏁 Script executed:

```shell
#!/bin/bash
# RemoveNonDefaultGroups メソッドの定義を検索
rg -n "RemoveNonDefaultGroups" -C 3 --glob "*.cs"
```

Length of output: 1265

---

実装全体を確認するため、以下のスクリプトを実行してください:

---

🏁 Script executed:

```shell
#!/bin/bash
# RemoveNonDefaultGroups の完全な実装を取得
rg -n "public static void RemoveNonDefaultGroups" -A 200 --glob "Editor/Addressables/AddressablesUtility.cs"
```

Length of output: 2203

---



</details>

**RemoveNonDefaultGroups() の影響範囲を限定してください**  
現状、`settings.groups` に登録されたデフォルトグループ以外をすべて削除しているため、プロジェクト内で手動追加したAddressableグループや他機能が利用するグループまで消去されてしまいます。  

以下の箇所を起点に、削除対象を必要最小限に絞り込むよう実装を見直してください。  
- Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs:53  
- Editor/Addressables/AddressablesUtility.cs:292–316  

改善例：  
- グループ名に接頭辞（例："DynamicTile_"）を付けてフィルタリング  
- `RemoveNonDefaultGroups()` に削除対象を指定する引数を追加  
- タイル専用のクリアメソッドを新規に用意

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs at line 53, the
call to RemoveNonDefaultGroups() currently deletes all non-default Addressable
groups, which risks removing groups created by other projects or features. To
fix this, modify the implementation in
Editor/Addressables/AddressablesUtility.cs (lines 292–316) to restrict deletion
only to groups with a specific prefix like "DynamicTile_". You can do this by
adding a parameter to RemoveNonDefaultGroups() to specify which groups to remove
or by creating a new method dedicated to clearing only the tile-related groups.
Then update the call in DynamicTileExporter.cs to use this refined method or
pass the appropriate filter argument.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-28T00:19:54Z
author_association: NONE
diff_hunk: @@ -22,97 +23,168 @@ public static class DynamicTileExporter
         /// 都市モデルをDynamicTile用にプレハブ化し、一括エクスポートする。
         /// </summary>
         /// <param name="assetConfig">変換設定</param>
-        /// <param name="excludeObjects">除外するオブジェクト</param>
         /// <param name="buildFolderPath"></param>
         /// <param name="onError">エラー時のコールバック</param>
         public static void Export(
             ConvertToAssetConfig assetConfig,
-            List<PLATEAUCityObjectGroup> excludeObjects,
             string buildFolderPath,
             Action<string> onError = null)
         {
+            var isExcludeAssetFolder = !string.IsNullOrEmpty(buildFolderPath);
             var cityObjects = GameObject.FindObjectsOfType<PLATEAUCityObjectGroup>();
             if (cityObjects == null || cityObjects.Length == 0)
             {
                 onError?.Invoke("都市モデルが見つかりません。都市モデルをインポートしてください。");
                 return;
             }
+            
+            using var progressBar = new ProgressBar();
 
             var groupName = AddressableGroupName;
-            if (!string.IsNullOrEmpty(buildFolderPath))
+            if (isExcludeAssetFolder)
             {
                 // ビルドフォルダパスを指定する場合はグループを分ける
                 var directoryName = Path.GetFileName(
                     buildFolderPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar));
                 groupName += "_" + directoryName;
             }
 
-            foreach (var cityObject in cityObjects)
+            // グループを削除
+            AddressablesUtility.RemoveNonDefaultGroups(AddressableLabel);
+
+            var addresses = new List<string>();
+            for (var i = 0; i < cityObjects.Length; i++)
             {
-                if (excludeObjects.Contains(cityObject))
+                var cityObject = cityObjects[i];
+                if (cityObject == null || cityObject.gameObject == null)
                 {
+                    Debug.LogWarning($"GameObjectがnullです。");
                     continue;
                 }
-                assetConfig.SrcGameObj = cityObject.gameObject;
+                
+                float progress = (float)(i+1) / cityObjects.Length;
+                progressBar.Display("動的タイルを生成中..", progress);
 
-                // 変換実行
-                var convertedObject = PrepareAndConvert(assetConfig, onError);
+                assetConfig.SrcGameObj = cityObject.gameObject;
+            
+                var baseFolderPath = Path.Combine(assetConfig.AssetPath, cityObject.gameObject.name);
+                var saveFolderPath = baseFolderPath;
+                int count = 1;
+                // 同名のディレクトリが存在する場合は、_1, _2, ... のように連番を付けて保存
+                while (Directory.Exists(saveFolderPath))
+                {
+                    saveFolderPath = $"{baseFolderPath}_{count}";
+                    count++;
+                }
+                
+                var convertedObject = PrepareAndConvert(assetConfig, saveFolderPath, onError);
                 if (convertedObject == null)
                 {
                     Debug.LogWarning($"{cityObject.gameObject.name} の変換に失敗しました。");
                     continue;
                 }
                 
-                string prefabPath = $"{assetConfig.AssetPath}/{convertedObject.name}.prefab";
+                // TODO: タイルごとにプレハブを保存する
+                string prefabPath = saveFolderPath + ".prefab";
                 var prefabAsset = PrefabUtility.SaveAsPrefabAsset(convertedObject, prefabPath);
                 if (prefabAsset == null)
                 {
-                    Debug.LogWarning($"{cityObject.gameObject.name} プレハブの保存に失敗しました。");
+                    Debug.LogWarning($"{convertedObject.name} プレハブの保存に失敗しました。");
                     continue;
                 }
 
+                progressBar.Display("動的タイルをAddressableに登録中..", progress);
+    
                 // プレハブをAddressableに登録
+                // TODO : タイルごとにAddress名を設定する
+                var address = prefabAsset.name;
                 AddressablesUtility.RegisterAssetAsAddressable(
                     prefabPath,
-                    prefabPath,
+                    address,
                     groupName,
                     new List<string> { AddressableLabel });
+                
+                addresses.Add(address);
+            
+                // シーン上のオブジェクトを削除
+                GameObject.DestroyImmediate(convertedObject);
             }
 
-            if (!string.IsNullOrEmpty(buildFolderPath))
+            progressBar.Display("Addressableのビルドを実行中...", 0.1f);
+
+            if (isExcludeAssetFolder)
             {
-                if (!Directory.Exists(buildFolderPath))
+                // Remote用のプロファイルを作成
+                var profileID = AddressablesUtility.SetOrCreateProfile(groupName);
+                if (!string.IsNullOrEmpty(profileID))
                 {
-                    onError?.Invoke("指定されたビルドフォルダが存在しません。");
-                    return;
+                    AddressablesUtility.SetRemoteProfileSettings(buildFolderPath, groupName);
+                    AddressablesUtility.SetGroupLoadAndBuildPath(groupName);
                 }
+            }
+            else
+            {
+                // プロファイルをデフォルトに設定
+                AddressablesUtility.SetDefaultProfileSettings();
+            }
+
+            // Addressablesのビルドを実行
+            AddressablesUtility.BuildAddressables(true);
+            
+            // DynamicTile管理用GameObjectを生成
+            var manager = GameObject.FindObjectOfType<PLATEAUTileManager>();
+            if (manager == null)
+            {
+                GameObject managerObj = new GameObject("DynamicTileManager");
+                manager = managerObj.AddComponent<PLATEAUTileManager>();
+            }
 
-                // ビルドパスを指定
-                AddressablesUtility.SetGroupLoadAndBuildPath(groupName, buildFolderPath);
+            manager.SaveCatalogPath("");
+            if (isExcludeAssetFolder)
+            {
+                // カタログファイルのパスを取得
+                var catalogFiles = Directory.GetFiles(buildFolderPath, "catalog_*.json");
+                if (catalogFiles.Length == 0)
+                {
+                    Debug.LogError("カタログファイルが見つかりません");
+                    return;
+                }
+                var catalogPath = catalogFiles[0]; // 最新のカタログファイルを使用
+                manager.SaveCatalogPath(catalogPath);
+            }
+            
+            // DynamicTileのアドレスを設定
+            manager.ClearTiles();
+            foreach (var address in addresses)
+            {
+                var tile = new PLATEAUDynamicTile(address);
+                manager.AddTile(tile);
             }
path: Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**PLATEAUTileManager との統合における設計改善の提案**

現在の実装では、毎回新しい PLATEAUTileManager インスタンスを検索・作成していますが、以下の点で改善が可能です：

1. シングルトンパターンの適用検討
2. 既存のタイル管理との整合性確保
3. カタログパス設定の検証



以下の改善を検討してください：

```diff
-// DynamicTile管理用GameObjectを生成
-var manager = GameObject.FindObjectOfType<PLATEAUTileManager>();
-if (manager == null)
-{
-    GameObject managerObj = new GameObject("DynamicTileManager");
-    manager = managerObj.AddComponent<PLATEAUTileManager>();
-}
+// DynamicTile管理用GameObjectの取得または生成
+var manager = PLATEAUTileManager.Instance ?? 
+              new GameObject("DynamicTileManager").AddComponent<PLATEAUTileManager>();
```


> Committable suggestion skipped: line range outside the PR's diff.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs around lines 135
to 162, the code repeatedly searches for or creates a new PLATEAUTileManager
instance, which can lead to multiple instances and inconsistent tile management.
Refactor this by implementing or using a singleton pattern for
PLATEAUTileManager to ensure only one instance exists. Adjust the code to access
this singleton instance instead of creating new ones. Also, verify that the
catalog path setting logic correctly updates the singleton's state without
redundant calls, maintaining consistency with existing tile management.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-28T04:15:00Z
author_association: NONE
diff_hunk: @@ -22,97 +23,168 @@ public static class DynamicTileExporter
         /// 都市モデルをDynamicTile用にプレハブ化し、一括エクスポートする。
         /// </summary>
         /// <param name="assetConfig">変換設定</param>
-        /// <param name="excludeObjects">除外するオブジェクト</param>
         /// <param name="buildFolderPath"></param>
         /// <param name="onError">エラー時のコールバック</param>
         public static void Export(
             ConvertToAssetConfig assetConfig,
-            List<PLATEAUCityObjectGroup> excludeObjects,
             string buildFolderPath,
             Action<string> onError = null)
         {
+            var isExcludeAssetFolder = !string.IsNullOrEmpty(buildFolderPath);
             var cityObjects = GameObject.FindObjectsOfType<PLATEAUCityObjectGroup>();
             if (cityObjects == null || cityObjects.Length == 0)
             {
                 onError?.Invoke("都市モデルが見つかりません。都市モデルをインポートしてください。");
                 return;
             }
+            
+            using var progressBar = new ProgressBar();
 
             var groupName = AddressableGroupName;
-            if (!string.IsNullOrEmpty(buildFolderPath))
+            if (isExcludeAssetFolder)
             {
                 // ビルドフォルダパスを指定する場合はグループを分ける
                 var directoryName = Path.GetFileName(
                     buildFolderPath.TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar));
                 groupName += "_" + directoryName;
             }
 
-            foreach (var cityObject in cityObjects)
+            // グループを削除
+            AddressablesUtility.RemoveNonDefaultGroups(AddressableLabel);
+
+            var addresses = new List<string>();
+            for (var i = 0; i < cityObjects.Length; i++)
             {
-                if (excludeObjects.Contains(cityObject))
+                var cityObject = cityObjects[i];
+                if (cityObject == null || cityObject.gameObject == null)
                 {
+                    Debug.LogWarning($"GameObjectがnullです。");
                     continue;
                 }
-                assetConfig.SrcGameObj = cityObject.gameObject;
+                
+                float progress = (float)(i+1) / cityObjects.Length;
+                progressBar.Display("動的タイルを生成中..", progress);
 
-                // 変換実行
-                var convertedObject = PrepareAndConvert(assetConfig, onError);
+                assetConfig.SrcGameObj = cityObject.gameObject;
+            
+                var baseFolderPath = Path.Combine(assetConfig.AssetPath, cityObject.gameObject.name);
+                var saveFolderPath = baseFolderPath;
+                int count = 1;
+                // 同名のディレクトリが存在する場合は、_1, _2, ... のように連番を付けて保存
+                while (Directory.Exists(saveFolderPath))
+                {
+                    saveFolderPath = $"{baseFolderPath}_{count}";
+                    count++;
+                }
+                
+                var convertedObject = PrepareAndConvert(assetConfig, saveFolderPath, onError);
                 if (convertedObject == null)
                 {
                     Debug.LogWarning($"{cityObject.gameObject.name} の変換に失敗しました。");
                     continue;
                 }
                 
-                string prefabPath = $"{assetConfig.AssetPath}/{convertedObject.name}.prefab";
+                // TODO: タイルごとにプレハブを保存する
+                string prefabPath = saveFolderPath + ".prefab";
                 var prefabAsset = PrefabUtility.SaveAsPrefabAsset(convertedObject, prefabPath);
                 if (prefabAsset == null)
                 {
-                    Debug.LogWarning($"{cityObject.gameObject.name} プレハブの保存に失敗しました。");
+                    Debug.LogWarning($"{convertedObject.name} プレハブの保存に失敗しました。");
                     continue;
                 }
 
+                progressBar.Display("動的タイルをAddressableに登録中..", progress);
+    
                 // プレハブをAddressableに登録
+                // TODO : タイルごとにAddress名を設定する
+                var address = prefabAsset.name;
                 AddressablesUtility.RegisterAssetAsAddressable(
                     prefabPath,
-                    prefabPath,
+                    address,
                     groupName,
                     new List<string> { AddressableLabel });
+                
+                addresses.Add(address);
+            
+                // シーン上のオブジェクトを削除
+                GameObject.DestroyImmediate(convertedObject);
             }
 
-            if (!string.IsNullOrEmpty(buildFolderPath))
+            progressBar.Display("Addressableのビルドを実行中...", 0.1f);
+
+            if (isExcludeAssetFolder)
             {
-                if (!Directory.Exists(buildFolderPath))
+                // Remote用のプロファイルを作成
+                var profileID = AddressablesUtility.SetOrCreateProfile(groupName);
+                if (!string.IsNullOrEmpty(profileID))
                 {
-                    onError?.Invoke("指定されたビルドフォルダが存在しません。");
-                    return;
+                    AddressablesUtility.SetRemoteProfileSettings(buildFolderPath, groupName);
+                    AddressablesUtility.SetGroupLoadAndBuildPath(groupName);
                 }
+            }
+            else
+            {
+                // プロファイルをデフォルトに設定
+                AddressablesUtility.SetDefaultProfileSettings();
+            }
+
+            // Addressablesのビルドを実行
+            AddressablesUtility.BuildAddressables(true);
+            
+            // DynamicTile管理用GameObjectを生成
+            var manager = GameObject.FindObjectOfType<PLATEAUTileManager>();
+            if (manager == null)
+            {
+                GameObject managerObj = new GameObject("DynamicTileManager");
+                manager = managerObj.AddComponent<PLATEAUTileManager>();
+            }
 
-                // ビルドパスを指定
-                AddressablesUtility.SetGroupLoadAndBuildPath(groupName, buildFolderPath);
+            manager.SaveCatalogPath("");
+            if (isExcludeAssetFolder)
+            {
+                // カタログファイルのパスを取得
+                var catalogFiles = Directory.GetFiles(buildFolderPath, "catalog_*.json");
+                if (catalogFiles.Length == 0)
+                {
+                    Debug.LogError("カタログファイルが見つかりません");
+                    return;
+                }
+                var catalogPath = catalogFiles[0]; // 最新のカタログファイルを使用
+                manager.SaveCatalogPath(catalogPath);
+            }
+            
+            // DynamicTileのアドレスを設定
+            manager.ClearTiles();
+            foreach (var address in addresses)
+            {
+                var tile = new PLATEAUDynamicTile(address);
+                manager.AddTile(tile);
             }
             
+            progressBar.Display("Addressableのビルドを実行中...", 0.99f);
             Dialogue.Display("動的タイルの保存が完了しました！", "OK");
         }
 
-        private static GameObject PrepareAndConvert(ConvertToAssetConfig config, Action<string> onError)
+        private static GameObject PrepareAndConvert(
+            ConvertToAssetConfig config,
+            string saveFolderPath,
+            Action<string> onError)
         {
             var assetPath = config.AssetPath;
-            string subFolderName = config.SrcGameObj.name;
-            string subFolderFullPath = Path.Combine(assetPath, subFolderName);
             List<GameObject> convertObjects = null;
             
             try
             {
-                if (!Directory.Exists(subFolderFullPath))
+                if (!Directory.Exists(saveFolderPath))
                 {
-                    Directory.CreateDirectory(subFolderFullPath);
+                    Directory.CreateDirectory(saveFolderPath);
                 }
                 
                 // AssetDatabase用のパスに変換
-                config.SetByFullPath(subFolderFullPath);
+                config.SetByFullPath(saveFolderPath);
 
                 // 変換
-                convertObjects = new ConvertToAsset().ConvertCore(config);
+                convertObjects = new ConvertToAsset().ConvertCore(config, new DummyProgressBar());
path: Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs
user: login: coderabbitai[bot]
body: _💡 Verification agent_

<details>
<summary>🧩 Analysis chain</summary>

**ConvertCore 呼び出しでの ProgressBar 連携を確認してください**

`new DummyProgressBar()` を渡していますが、外部から渡された `progressBar` を活用することで、より一貫したプログレス表示が可能になります。



以下のスクリプトで ConvertCore メソッドのシグネチャを確認し、progressBar の連携可能性を調査してください：

---

🏁 Script executed:

```shell
#!/bin/bash
# ConvertCore メソッドのシグネチャと IProgressBar の実装を確認
ast-grep --pattern $'ConvertCore($_, $_) {
  $$$
}'

# DummyProgressBar の実装を確認
rg -n "class DummyProgressBar" -A 10
```

Length of output: 527

---

以下のスクリプトで `ConvertCore` の定義を直接検索し、シグネチャを確認してください:

---

🏁 Script executed:

```shell
#!/bin/bash
# ConvertCore メソッドのシグネチャを確認
rg -n "ConvertCore" -C 2

# ConvertToAsset クラスの定義場所を確認
rg -n "class ConvertToAsset" -A 5
```

Length of output: 2993

---

🏁 Script executed:

```shell
#!/bin/bash
# DynamicTileExporter.cs の該当箇所を確認
sed -n '150,220p' Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs
```

Length of output: 1887

---



</details>

**外部の ProgressBar を ConvertCore に渡すよう修正してください**

`ConvertCore(ConvertToAssetConfig, IProgressBar)` は受け取ったプログレスバーを使えるため、現在の `new DummyProgressBar()` ではなく、呼び出し元の `progressBar` を活用すると一貫した表示が可能です。

修正手順例:

- PrepareAndConvert メソッドのシグネチャを更新し、`IProgressBar progressBar` を引数に追加  
- ConvertCore 呼び出しで `new DummyProgressBar()` の代わりに渡された `progressBar` を使用  
- 呼び出し元（エクスポーター本体）でも `progressBar` を渡すように変更

```diff
- private static GameObject PrepareAndConvert(
-     ConvertToAssetConfig config,
-     string saveFolderPath,
-     Action<string> onError)
+ private static GameObject PrepareAndConvert(
+     ConvertToAssetConfig config,
+     string saveFolderPath,
+     Action<string> onError,
+     IProgressBar progressBar)
 {
     …
-    convertObjects = new ConvertToAsset().ConvertCore(config, new DummyProgressBar());
+    convertObjects = new ConvertToAsset().ConvertCore(config, progressBar);
     …
 }
```

呼び出し元のメソッドにも同様に `progressBar` を渡すようご対応ください。  


> Committable suggestion skipped: line range outside the PR's diff.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
In Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs at line 187,
replace the hardcoded new DummyProgressBar() argument in the ConvertCore call
with the externally passed progressBar parameter to ensure consistent progress
display. To do this, update the PrepareAndConvert method signature to accept an
IProgressBar progressBar parameter, pass this progressBar to ConvertCore instead
of creating a new DummyProgressBar, and modify all calling methods accordingly
to propagate the progressBar argument through the call chain.
```

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-28T04:15:00Z
author_association: NONE

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-05-20T07:16:35Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

"""
## Walkthrough

本変更では、ダイナミックタイルのエクスポート処理を刷新し、Addressablesのグループ・プロファイル管理やビルド機能を拡張しました。さらに、ランタイムでタイルの動的ロード・アンロードを管理するPLATEAUTileManagerとAddressableLoaderの新規クラスを追加し、アセンブリ定義に必要な参照を追加しています。

## Changes

| ファイル・グループ | 変更概要 |
|---|---|
| Editor/Window/Main/Tab/DynamicTileGUI/DynamicTileExporter.cs, Editor/Window/Main/DynamicTileGui.cs | DynamicTileExporterのエクスポート処理を改修。除外オブジェクト引数削除、一意の保存フォルダ生成、Addressables登録・ビルド処理追加。DynamicTileGuiの呼び出しも対応。 |
| Editor/Addressables/AddressablesUtility.cs | Addressablesのグループ・プロファイル設定のリファクタリングと拡張。リモート・デフォルトプロファイル設定、ビルド実行機能追加。非デフォルトグループの削除処理改善。 |
| Runtime/DynamicTile/PLATEAUTileManager.cs, Runtime/DynamicTile/PLATEAUDynamicTile.cs, Runtime/DynamicTile/AddressableLoader.cs | ダイナミックタイルの動的ロード・アンロード・管理を行う新規クラス群を追加。Addressablesカタログの動的読み込みやインスタンス管理を実装。 |
| Runtime/CityAdjust/ConvertToAsset/ConvertToAsset.cs | ConvertCoreメソッドに進捗表示のオンオフ制御パラメータを追加。 |
| Runtime/PLATEAU.Runtime.asmdef, Editor/PLATEAU.Editor.asmdef | Unity.AddressablesおよびUnity.ResourceManagerへの参照を追加。 |
| Runtime/DynamicTile.meta, Runtime/DynamicTile/PLATEAUTileManager.cs.meta, Runtime/DynamicTile/PLATEAUDynamicTile.cs.meta, Runtime/DynamicTile/AddressableLoader.cs.meta | 新規コードファイルに対応する.metaファイルを追加。 |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant Editor as DynamicTileExporter
    participant Addressables as AddressablesUtility
    participant Runtime as PLATEAUTileManager
    participant Loader as AddressableLoader

    Editor->>Addressables: グループ・プロファイル設定、アセット登録
    Editor->>Addressables: Addressablesビルド実行
    Editor->>Runtime: PLATEAUTileManager生成・カタログパス設定
    Runtime->>Loader: カタログ初期化・アドレス一覧取得
    Runtime->>Loader: アドレスごとにInstantiateAssetAsync
    Loader-->>Runtime: GameObjectインスタンス返却
    Runtime->>Runtime: タイルリスト管理・ロード/アンロード
```

## Possibly related PRs

- [Synesthesias/PLATEAU-SDK-for-Unity#487](https://github.com/Synesthesias/PLATEAU-SDK-for-Unity/pull/487): DynamicTileExporterとAddressablesUtilityの拡張・初期ダイナミックタイルUI対応など、Addressables登録やダイナミックタイルエクスポートに関する機能が重複・連携しています。  
- [Synesthesias/PLATEAU-SDK-for-Unity#485](https://github.com/Synesthesias/PLATEAU-SDK-for-Unity/pull/485): Addressables初期化と基本的なユーティリティクラスの追加。今回のPRのAddressables管理機能の基礎に関連。  

## Suggested reviewers

- linoal

## Poem

> うさぎの耳でタイルを数え  
> ひとつずつアドレスで呼び出しぴょん  
> 新しいマネージャーでジャンプして  
> カタログの森を駆け抜ける  
> エクスポートもロードも任せてね  
> ふわふわタイル、今日も元気に  
> Unityの野原でぴょこぴょこ！ 🐇🌱
"""

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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= -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=Synesthesias/PLATEAU-SDK-for-Unity&utm_content=488):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: NONE
user: login: tomoki-suzuki-syn
created_at: 2025-05-21T17:13:05Z
body: AddressableGroupsウィンドウでPlayModeScript➡Use Existing Buildにしている状態で動的タイル生成すると、次のようなエラーがたくさん出ます。  
アセットバンドルをビルドするとこのエラーは出なくなります。  
動的タイル生成時、自動でビルドするようにすると親切かと思います。

![image](https://github.com/user-attachments/assets/57ee181e-730e-43bf-8735-0751c4961b58)


```
(Filename: ./Packages/PlateauUnitySDK/Runtime/DynamicTile/PLATEAUTileManager.cs Line: 67)

InvalidKeyException: Exception of type 'UnityEngine.AddressableAssets.InvalidKeyException' was thrown. No Location found for Key=bldg_18e2cb5c-1b25-430a-9d1d-ca31c4f38bf8_down_0
Rethrow as OperationException: ChainOperation failed because dependent operation failed

UnityEngine.DebugLogHandler:Internal_LogException(Exception, Object)
UnityEngine.DebugLogHandler:LogException(Exception, Object)
UnityEngine.Logger:LogException(Exception, Object)
UnityEngine.Debug:LogException(Exception)
PLATEAU.DynamicTile.<>c__DisplayClass4_0:<LoadByAddress>b__0(AsyncOperationHandle`1) (at .\Packages\PlateauUnitySDK\Runtime\DynamicTile\PLATEAUTileManager.cs:71)
DelegateList`1:Invoke(AsyncOperationHandle`1) (at .\Library\PackageCache\com.unity.addressables@1.22.3\Runtime\ResourceManager\Util\DelegateList.cs:75)
UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationBase`1:InvokeCompletionEvent() (at .\Library\PackageCache\com.unity.addressables@1.22.3\Runtime\ResourceManager\AsyncOperations\AsyncOperationBase.cs:397)
UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationBase`1:UnityEngine.ResourceManagement.AsyncOperations.IAsyncOperation.InvokeCompletionEvent() (at .\Library\PackageCache\com.unity.addressables@1.22.3\Runtime\ResourceManager\AsyncOperations\AsyncOperationBase.cs:598)
UnityEngine.ResourceManagement.ResourceManager:ExecuteDeferredCallbacks() (at .\Library\PackageCache\com.unity.addressables@1.22.3\Runtime\ResourceManager\ResourceManager.cs:1163)
UnityEngine.ResourceManagement.ResourceManager:Update(Single) (at .\Library\PackageCache\com.unity.addressables@1.22.3\Runtime\ResourceManager\ResourceManager.cs:1219)
MonoBehaviourCallbackHooks:Update() (at .\Library\PackageCache\com.unity.addressables@1.22.3\Runtime\ResourceManager\Util\MonoBehaviourCallbackHooks.cs:29)

(Filename: ./Library/PackageCache/com.unity.addressables@1.22.3/Runtime/ResourceManager/Util/MonoBehaviourCallbackHooks.cs Line: 29)

Failed to load: bldg_b1ca184f-de14-4b8c-8682-0da98d20ec1d_down_0
UnityEngine.StackTraceUtility:ExtractStackTrace ()
UnityEngine.DebugLogHandler:LogFormat (UnityEngine.LogType,UnityEngine.Object,string,object[])
UnityEngine.Logger:Log (UnityEngine.LogType,object)
UnityEngine.Debug:LogError (object)
PLATEAU.DynamicTile.PLATEAUTileManager/<>c__DisplayClass4_0:<LoadByAddress>b__0 (UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle`1<UnityEngine.GameObject>) (at ./Packages/PlateauUnitySDK/Runtime/DynamicTile/PLATEAUTileManager.cs:67)
DelegateList`1<UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle`1<UnityEngine.GameObject>>:Invoke (UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationHandle`1<UnityEngine.GameObject>) (at ./Library/PackageCache/com.unity.addressables@1.22.3/Runtime/ResourceManager/Util/DelegateList.cs:75)
UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationBase`1<UnityEngine.GameObject>:InvokeCompletionEvent () (at ./Library/PackageCache/com.unity.addressables@1.22.3/Runtime/ResourceManager/AsyncOperations/AsyncOperationBase.cs:397)
UnityEngine.ResourceManagement.AsyncOperations.AsyncOperationBase`1<UnityEngine.GameObject>:UnityEngine.ResourceManagement.AsyncOperations.IAsyncOperation.InvokeCompletionEvent () (at ./Library/PackageCache/com.unity.addressables@1.22.3/Runtime/ResourceManager/AsyncOperations/AsyncOperationBase.cs:598)
UnityEngine.ResourceManagement.ResourceManager:ExecuteDeferredCallbacks () (at ./Library/PackageCache/com.unity.addressables@1.22.3/Runtime/ResourceManager/ResourceManager.cs:1163)
UnityEngine.ResourceManagement.ResourceManager:Update (single) (at ./Library/PackageCache/com.unity.addressables@1.22.3/Runtime/ResourceManager/ResourceManager.cs:1219)
MonoBehaviourCallbackHooks:Update () (at ./Library/PackageCache/com.unity.addressables@1.22.3/Runtime/ResourceManager/Util/MonoBehaviourCallbackHooks.cs:29)
```
author_association: CONTRIBUTOR
user: login: linoal
created_at: 2025-05-27T17:00:37Z
body: 「任意のフォルダに保存」でフォルダ選択をしていなくても実行ボタンを押せてしまうようです
author_association: COLLABORATOR
user: login: linoal
created_at: 2025-05-27T17:06:04Z
body: 実行中にプログレスバーが左右に反復横跳びします。PLATEAU.UtilのProgressBarクラスを使ってプログレスバーの進行を穏やかにしつつ進行を表示できないでしょうか？
author_association: COLLABORATOR