はじめに
-
リソースプロジェクトでアセットバンドルをビルドして、本体プロジェクトではアセットを直接参照しない、というような開発体制に対応できます
-
ゲームでリモートでリソースを取得する必要がない場合などに有用です
- ゲーム上でリモートでダウンロードする場合、このような処置を取らずに直接ロードするだけでOKです
-
直接アセットを追加せずリソースを切り分けることで、Gitなどの動作も軽量になります。
対処法
リソースプロジェクトから本体プロジェクトにバンドルファイルとcatalog.jsonをコピーする
- 結構原始的なやり方ですが、調べた感じほかに手段がなかったので、消去法でこの方法で実装する必要があります
作り方
準備
-
リソースプロジェクトと本体プロジェクトを作成する
- ディレクトリ構成はこんな感じです
AddressablesTest ├ ResourceProject └ MainProject
- ディレクトリ構成はこんな感じです
-
Unityバージョン、Addressablesバージョンを揃える
リソースプロジェクト
- リソースプロジェクト側で、アセットをAddressablesに登録してビルドします
本体プロジェクト
- 本体プロジェクト側で、エディタ拡張を利用してリソースプロジェクトのバンドル情報をコピーします
- コードについては後ほど紹介します
結果
実行結果
本体プロジェクト内のAddressables Groupsウィンドウには何も登録されていませんが…
実行すると、リソースプロジェクト側でGroupsに追加したアセットが適用されます!
エディタ
こんな感じのエディタウィンドウで操作できます。
ここでバンドルとカタログをコピーすれば、リソースプロジェクトのバンドルデータが含まれます!
…が、ゲームをビルドすると、自動でバンドルの方のビルド処理がされてしまい、コピーされたバンドルファイルが上書きされてしまいます。
そこで、ビルド後にコピーするという処理が必要になります。
以下のコード説明で紹介します
エディタ拡張コード
CopyBundleFromResourceProject.cs
コード
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEditor.AddressableAssets.Build;
/// <summary> リソースプロジェクトからバンドルをコピーする </summary>
public class CopyBundleFromResourceProject : EditorWindow
{
// 設定ファイル
[SerializeField] CopyBundleSettings settings;
[SerializeField] bool isAbsolutePath = false; // 絶対パスフラグ
// ビルド時の設定
[SerializeField] bool isCopyOnBuild = true;
const string CATALOG_FILE_NAME = "catalog.json";
const int SPACE_SIZE = 32;
const string SETTING_FILE_PATH = "Assets/ScriptableObjects/CopyBundleSettings.asset";
//------------------------------------------------------------
[MenuItem("Addressable/CopyBundleFromResourceProject")]
static void ShowWindow()
{
var window = GetWindow<CopyBundleFromResourceProject>();
window.titleContent = new GUIContent("CopyBundleFromResourceProject");
window.Show();
Debug.Log("ShowWindow");
}
void OnEnable()
{
BuildScript.buildCompleted = null;
BuildScript.buildCompleted += OnBuildCompleted;
Debug.Log("CopyBundleFromResourceProject");
}
void OnGUI()
{
// 設定ScriptableObject設定
EditorGUILayout.LabelField("Copy Bundle Settings", EditorStyles.boldLabel);
settings = EditorGUILayout.ObjectField("Settings", settings, typeof(CopyBundleSettings), false) as CopyBundleSettings;
if (settings == null || settings.IsNullValue) return;
// 絶対パスフラグ
isAbsolutePath = EditorGUILayout.Toggle("Is Absolute Path", isAbsolutePath);
EditorGUILayout.Space(SPACE_SIZE);
EditorGUILayout.LabelField("Build Settings", EditorStyles.boldLabel);
isCopyOnBuild = EditorGUILayout.Toggle("Copy On Build", isCopyOnBuild);
EditorGUILayout.Space(SPACE_SIZE);
EditorGUILayout.LabelField("Copy Bundle", EditorStyles.boldLabel);
// パス指定
var sourceBundlePath = settings.GetSourceBundlePath(isAbsolutePath);
GUILayout.Label("Source Bundle Path: " + sourceBundlePath, EditorStyles.boldLabel);
var destBundlePath = settings.GetDestinationBundlePath(isAbsolutePath);
GUILayout.Label("Destination Bundle Path: " + destBundlePath, EditorStyles.boldLabel);
//------------------------------------------------------------
// バンドルファイルのコピー
if (GUILayout.Button("Copy"))
{
CopyBundles(sourceBundlePath, destBundlePath);
}
//------------------------------------------------------------
EditorGUILayout.Space(SPACE_SIZE);
EditorGUILayout.LabelField("Copy Catalog", EditorStyles.boldLabel);
// カタログパス
var sourceCatalogPath = settings.GetSourceCatalogPath(isAbsolutePath);
GUILayout.Label("Source Catalog Path: " + sourceCatalogPath, EditorStyles.boldLabel);
var destCatalogPath = settings.GetDestinationCatalogPath(isAbsolutePath);
GUILayout.Label("Destination Catalog Path: " + destCatalogPath, EditorStyles.boldLabel);
// カタログのコピー
if (GUILayout.Button("Copy Catalog"))
{
CopyCatalog(sourceCatalogPath, destCatalogPath);
}
}
//------------------------------------------------------------
// ビルド完了時の処理
static void OnBuildCompleted(AddressableAssetBuildResult result)
{
// 設定ファイルをAssetDatabaseで読み込む
var settings = AssetDatabase.LoadAssetAtPath<CopyBundleSettings>(SETTING_FILE_PATH);
if (settings == null || settings.IsNullValue)
{
throw new System.Exception("Not found settings.");
}
CopyBundles(settings.GetSourceBundlePath(false), settings.GetDestinationBundlePath(false));
CopyCatalog(settings.GetSourceCatalogPath(false), settings.GetDestinationCatalogPath(false));
Debug.Log("CopyBundleFromResourceProject: OnBuildCompleted");
}
//------------------------------------------------------------
// コピー
static void CopyBundles(string sourcePath, string destPath)
{
try
{
// コピー元パスチェック
if (!System.IO.Directory.Exists(sourcePath))
{
Debug.LogError("Not found source directory.: " + sourcePath);
return;
}
// コピー先パス取得
Debug.Log("Copy: " + sourcePath + " -> " + destPath);
// 指定されたディレクトリ内のファイルをコピー
System.IO.Directory.CreateDirectory(destPath);
foreach (var file in System.IO.Directory.GetFiles(sourcePath))
{
var fileName = System.IO.Path.GetFileName(file);
var targetFile = System.IO.Path.Combine(destPath, fileName);
System.IO.File.Copy(file, targetFile, true);
Debug.Log("Copy: " + targetFile);
}
}
catch (System.Exception e)
{
Debug.LogError(e);
}
}
// catalog.jsonのコピー
static void CopyCatalog(string sourceCatalogPath, string destPath)
{
try
{
// コピー元パスチェック
if (!System.IO.File.Exists(sourceCatalogPath))
{
Debug.LogError("Not found catalog file.: " + sourceCatalogPath);
return;
}
// コピー
System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(destPath));
System.IO.File.Copy(sourceCatalogPath, destPath, true);
Debug.Log("Copied catalog: " + sourceCatalogPath + " -> " + destPath);
}
catch (System.Exception e)
{
Debug.LogError(e);
}
AssetDatabase.Refresh();
}
}
設定ScriptableObject
コード
using System.Collections;
using System.Collections.Generic;
using UnityEditor;
using UnityEngine;
[CreateAssetMenu(fileName = "CopyBundleSettings", menuName = "CopyBundleSettings")]
public class CopyBundleSettings : ScriptableObject
{
[SerializeField] string sourceProjectPath;
[SerializeField] string catalogDirectoryName;
const string CATALOG_FILE_NAME = "catalog.json";
//------------------------------------------------------------
public bool IsNullValue => string.IsNullOrEmpty(sourceProjectPath) || string.IsNullOrEmpty(catalogDirectoryName);
//------------------------------------------------------------
/// <summary> コピー元のバンドルパスを取得 </summary>
public string GetSourceBundlePath(bool isAbsolutePath, bool checkExist = false)
{
var completelyPath = System.IO.Path.Combine(sourceProjectPath, UnityEngine.AddressableAssets.Addressables.RuntimePath, EditorUserBuildSettings.activeBuildTarget.ToString());
GetPathProcess(ref completelyPath, isAbsolutePath, checkExist);
return completelyPath;
}
/// <summary> コピー先のバンドルパスを取得 </summary>
public string GetDestinationBundlePath(bool isAbsolutePath, bool checkExist = false)
{
var completelyPath = System.IO.Path.Combine(UnityEngine.AddressableAssets.Addressables.RuntimePath, EditorUserBuildSettings.activeBuildTarget.ToString());
GetPathProcess(ref completelyPath, isAbsolutePath, checkExist);
return completelyPath;
}
/// <summary> コピー元のカタログパスを取得 </summary>
public string GetSourceCatalogPath(bool isAbsolutePath, bool checkExist = false)
{
var targetPath = System.IO.Path.Combine(sourceProjectPath, UnityEngine.AddressableAssets.Addressables.RuntimePath, CATALOG_FILE_NAME);
GetPathProcess(ref targetPath, isAbsolutePath, checkExist);
return targetPath;
}
/// <summary> コピー先のカタログパスを取得 </summary>
public string GetDestinationCatalogPath(bool isAbsolutePath, bool checkExist = false)
{
var targetPath = System.IO.Path.Combine(System.IO.Path.GetRelativePath(Application.dataPath, Application.streamingAssetsPath), catalogDirectoryName, CATALOG_FILE_NAME);
GetPathProcess(ref targetPath, isAbsolutePath, checkExist);
return targetPath;
}
//------------------------------------------------------------
void GetPathProcess(ref string path, bool isAbsolutePath, bool checkExist = false)
{
if (checkExist && !System.IO.Directory.Exists(path))
{
throw new System.Exception("Not Found Directory: " + path);
}
if (isAbsolutePath)
{
SetAbsolutePath(ref path);
}
}
void SetAbsolutePath(ref string path)
{
path = System.IO.Path.GetFullPath(path);
}
}
アセット読み込み処理
コード
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using UnityEngine.AddressableAssets;
using UnityEngine.UI;
public class AssetLoader : MonoBehaviour
{
[SerializeField] Image image;
[Header("Catalog")]
[SerializeField] string catalogPath = "Addressables/catalog.json";
[SerializeField] bool loadCatalog;
async void Start()
{
if (loadCatalog)
{
// カタログ追加
var path = Path.Combine(Application.streamingAssetsPath, catalogPath);
var catalog = Addressables.LoadContentCatalogAsync(path).Task;
await catalog;
if (catalog.Status == System.Threading.Tasks.TaskStatus.RanToCompletion)
{
Debug.Log("Catalog loaded successfully");
}
Addressables.AddResourceLocator(catalog.Result);
}
// アセット読み込み
var spriteHandler = Addressables.LoadAssetAsync<Sprite>("UnityLogo");
await spriteHandler.Task;
image.sprite = spriteHandler.Result;
}
}
説明
ビルド完了時にコピー処理を走らせる
// ビルド完了時の処理
static void OnBuildCompleted(AddressableAssetBuildResult result)
{
// 設定ファイルをAssetDatabaseで読み込む
var settings = AssetDatabase.LoadAssetAtPath<CopyBundleSettings>(SETTING_FILE_PATH);
if (settings == null || settings.IsNullValue)
{
throw new System.Exception("Not found settings.");
}
CopyBundles(settings.GetSourceBundlePath(false), settings.GetDestinationBundlePath(false));
CopyCatalog(settings.GetSourceCatalogPath(false), settings.GetDestinationCatalogPath(false));
Debug.Log("CopyBundleFromResourceProject: OnBuildCompleted");
}
void OnEnable()
{
BuildScript.buildCompleted = null;
BuildScript.buildCompleted += OnBuildCompleted;
}
-
AssetDatabase
でファイルパスなどが指定された設定用ScriptableObject(CopyBundleSettings)を直接読み込み、パスなどの設定を取得してます - 設定ファイルがなかったり、値が設定されていなかったら警告を出します
- 問題がなければバンドルとカタログをコピーします
- この処理を
OnEnable
のタイミングで、BuildScript.buildCompleted
イベントに登録します
利用側でカタログを登録して読み込む
カタログ追加
if (loadCatalog)
{
// カタログ追加
var path = Path.Combine(Application.streamingAssetsPath, catalogPath);
var catalog = Addressables.LoadContentCatalogAsync(path).Task;
await catalog;
if (catalog.Status == System.Threading.Tasks.TaskStatus.RanToCompletion)
{
Debug.Log("Catalog loaded successfully");
}
Addressables.AddResourceLocator(catalog.Result);
}
- StreamingAssetsに配置されたカタログを、
Addressables.LoadContentCatalogAsync()
で読み込みます - 読み込んだ後に、
Addressables.AddResourceLocator()
でカタログを本プロジェクトに追加します
アセット読み込み
// アセット読み込み
var spriteHandler = Addressables.LoadAssetAsync<Sprite>("UnityLogo");
await spriteHandler.Task;
image.sprite = spriteHandler.Result;
- 読み込み時のキーは、リソースプロジェクト側で設定されたものを指定する必要があります
さいごに
こんな感じでプロジェクトを分けて、エディタ上の無駄な待機時間などを短縮しましょう~
参考