プレハブ
概要
Any prefab with NetworkObject
script on its root GameObject
and with the Is Spawnable
checkbox ticked can be spawned at runtime.
Fusion は可能な限り NetworkObject
プレハブへのハードリファレンスを作成しないようにします。プレハブがアドレス指定可能な場合やリソースである場合、Fusion はそれを直接参照せず、代わりにアドレスやリソースのパスを格納します。それ以外の場合は、プレハブへのハードリファレンスが設定に保存されます。
ユーザーコードが同様にハードリファレンスを形成しないように、プレハブの参照には NetworkPrefabRef
型を使用します。NetworkPrefabRef
はプレハブの GUID に基づいているので、プロジェクト内でプレハブをリネームしたり移動したりしても大丈夫です。
プレハブの登録
Fusionはネットワークスポーンを行う前に、プレハブを登録する必要があります。プレハブのGUIDとロードに必要なデータ(アドレス、リソースパス、静的参照)は、NetworkPrefabTable
プロパティでアクセス可能な場所に保存されます。
www.DeepL.com/Translator(無料版)で翻訳しました。
ビルド時に存在する、プロジェクト内のすべてのスポーン可能な NetworkObject
プレハブは、自動的に検出され登録されます。 これは、NetworkProjectConfig
のPrefabs
プロパティを検査することで確認することができます。
[A]
は A addressable プレハブを表します。[R]
は R esources プレハブの略です。[S]
は S tatically-referenced プレハブの略です。
Is Spawnable
チェックボックスを使用してください。
Alternatively, you can inspect Prefab Source
label field of a prefab.
テーブルが不完全だったり、Prefab Source
が無効な値を表示している場合は、Fusion > Rebuild Prefab Table
メニューアイテムか、設定インスペクタの Rebuild Prefab Table
ボタンを使用します。
全てのスポーン可能なプレハブには、FusionPrefab
というラベルが付けられます。つまり、l:FusionPrefab
をプロジェクトウィンドウの検索ボックスの用語として使用することができます。
ビルド時に存在しないプレハブ(例えば、プロシージャルで生成されたものや、ビルドのライフタイム中にAddressableで追加された新しいもの)は、実行時に NetworkPrefabTable.TryAdd
で登録することが可能です。
C#
// this will register an Addressable prefab with a know guid without having to load it first
NetworkPrefabId RegisterAddressablePrefab(NetworkProjectConfig config, NetworkObjectGuid guid)
{
var source = ScriptableObject.CreateInstance<NetworkPrefabSourceUnityAddressable>();
source.Address = new AssetReferenceGameObject(guid.ToUnityGuidString());
if (config.PrefabTable.TryAdd(guid, source, out var id)) {
return id;
}
throw new ArgumentException($"Failed to register addressable guid: {guid}");
}
// this will register any prefab, but it needs to be loaded first (from whatever the source)
NetworkPrefabId RegisterPrefab(NetworkProjectConfig config, NetworkObject prefab)
{
var source = new NetworkPrefabSourceStatic {
PrefabReference = prefab
};
if (config.PrefabTable.TryAdd(prefab.NetworkGuid, source, out var id)) {
return id;
}
throw new ArgumentException($"Failed to register prefab with guid: {prefab.NetworkGuid}");
}
NetworkPrefabId
の割り当ては順次行われます)。プレハブを起動しようとする前に、すべてのクライアントでプレハブが登録されていることを確認します(例:ランナーを起動する前に登録する)。プレハブのスポーン
NetworkPrefabRef を使用する
プレハブを生成するには、NetworkRunner.Spawn
メソッドのいずれかを使用します。推奨される方法は、プレハブの参照とスポーンに NetworkPrefabRef
フィールドを使用することです。
C#
public class SpawnWithNetworkPrefabRef : NetworkBehaviour {
public NetworkPrefabRef PrefabRef;
public NetworkObject SpawnPrefab() {
return Runner.Spawn(PrefabRef);
}
}
プレハブがアドレス指定可能なもの、またはリソースである場合、Fusion は Spawn
から戻る前に実際のプレハブを読み込みます。Addressable の場合は、AsyncOperationHandle.WaitForCompletion
を使用して、プレハブが読み込まれたことを確認します。リソースの場合は Resources.Load
が使用されます。もし、大きな遅延が発生するようであれば、プレハブのプリロードを検討してみてください。
プレハブリファレンスの使用
また、NetworkObject
や GameObject
、SimulationBehaviour
のサブクラスへの参照を使用することもできます。Spawn
は、渡された参照がスポーン可能なプレハブのルート GameObject
に属していない場合、 InvalidOperationException
をスローします。NetworkObject
と GameObject
を参照する場合、 [NetworkPrefab]
属性を使用すると、インスペクタの選択をスポーン可能なプレハブのみに制限することができます。
C#
public class SpawnWithReferences : NetworkBehaviour {
[NetworkPrefab]
public GameObject GameObjectRef;
[NetworkPrefab]
public NetworkObject NetworkObjectRef;
public NetworkTransform SimulationBehaviourSubclassRef;
public NetworkObject SpawnWithGameObjectRef() {
return Runner.Spawn(GameObjectRef);
}
public NetworkObject SpawnWithNetworkObjectRef() {
return Runner.Spawn(NetworkObjectRef);
}
public NetworkTransform SpawnWithSimulationBehaviourSubclassRef() {
return Runner.Spawn(SimulationBehaviourSubclassRef);
}
}
NetworkPrefabId の使用
プレハブが NetworkPrefabTable
に登録されると、 NetworkPrefabId
が返されます。この型はサイズとネットワークに最適化されており、プロジェクトに別のプレハブを追加 / 削除すると、プレハブの値が異なる可能性があるため、実行時にのみ使用することを意図しています。特に、 NetworkPrefabTable.TryAdd
を用いて手動でプレハブを追加登録する際に便利です。
C#
public class SpawnWithNetworkPrefabId : NetworkBehaviour {
[NonSerialized]
public NetworkPrefabId PrefabId;
public UnityEngine.AddressableAssets.AssetReferenceGameObject PrefabRef;
public void RegisterPrefab() {
var source = ScriptableObject.CreateInstance<NetworkPrefabSourceUnityAddressable>();
source.Address = PrefabRef;
if (!Runner.Config.PrefabTable.TryAdd(NetworkObjectGuid.Parse(PrefabRef.AssetGUID), source, out PrefabId)) {
Debug.LogError($"Failed to register {PrefabRef}");
}
}
public NetworkObject SpawnPrefab() {
Debug.Assert(PrefabId.IsValid);
return Runner.Spawn(PrefabId);
}
}
動的に登録されたプレハブは、そのGUIDまたはオブジェクトリファレンス(すでに読み込まれた場合)を使って生成することも可能です。
カスタム
プレハブの参照に他の手段を使用することも可能です。Addressables と AssetReferenceGameObject
フィールドをすでに使用している既存のプロジェクトでは、代わりにまずプレハブを読み込み、その結果得られる参照を使用してプレハブをスポーンすることができます。
C#
public class SpawnWithAddressables : NetworkBehaviour {
public UnityEngine.AddressableAssets.AssetReferenceGameObject AddressablePrefabRef;
public void SpawnAsync() {
AddressablePrefabRef.LoadAssetAsync().Completed += asyncOpHandle => {
Runner.Spawn(asyncOpHandle.Result); // asyncOpHandle.Result is of GameObject type
};
}
}
同様の手法は、すでにリソースパスを使ってプレハブを参照しているプロジェクトにも利用できます。
カスタムコンテンツ配信
プロジェクトがカスタムコンテンツ配信システムに依存している場合、カスタムプレファブソースの実装を検討する必要があります。これにより、実行時にどのようにプレハブが読み込まれるかにかかわらず、ユーザーは NetworkPrefabRef
やアセット参照、エディタプレハブ自動検出を使用し続けることができます。以下は、アセットバンドルに基づくカスタムプレファブソースをどのように実装するかの簡略化されたサンプルです。
まず、INetworkPrefabSource
を実装した実行時クラスが必要です。このインターフェースは、単一のプレハブを読み込むために必要なデータとロジックを表します。INetworkPrefabSource
を直接実装する代わりに、次のスニペットでは NetworkPrefabSourceUnityBase
- 基本的な実装を提供する ScriptableObject
ベースのベースクラス - を使用します。
C#
public class NetworkPrefabSourceUnityAssetBundles : NetworkPrefabSourceUnityBase {
// name of the asset bundle
public string AssetBundleName;
// name of the asset in the asset bundle
public string AssetName;
private GameObject _prefab;
// this is what gets displayed in prefab's Prefab Source label field
public override string EditorSummary => $"[AssetBundle: {AssetBundleName}[{AssetName}]]";
public override void Load(in NetworkPrefabLoadContext context) {
// load the bundle, if not yet loaded
var bundle = AssetBundle.GetAllLoadedAssetBundles()
.FirstOrDefault(x => x.name == AssetBundleName);
if (bundle == null) {
var bundlePath = System.IO.Path.Combine(Application.streamingAssetsPath, AssetBundleName);
bundle = AssetBundle.LoadFromFile(bundlePath);
}
if (bundle == null) {
context.Error(new InvalidOperationException($"Unable to load asset bundle {AssetBundleName}"));
} else {
// actually load the prefab
_prefab = bundle.LoadAsset<GameObject>(AssetName);
context.Loaded(_prefab);
}
}
public override void Unload() {
if (_prefab) {
Destroy(_prefab);
_prefab = null;
}
}
}
次に、エディタ専用の INetworkPrefabSourceFactory
の実装が必要です。このクラスは、assetPath
で生成されたプレハブがカスタムアセットローディングを使用するかどうかを検出し、使用する場合は NetworkPrefabSourceUnityAssetBundles
を初期化したインスタンスを返します。Fusion エディタスクリプトによって自動的に検出されるため、ファクトリー自体を登録する必要はありません。
C#
public class NetworkPrefabAssetFactoryAssetBundles : INetworkPrefabSourceFactory {
int INetworkPrefabSourceFactory.Order => 0;
Type INetworkPrefabSourceFactory.SourceType => typeof(NetworkPrefabSourceUnityAssetBundles);
NetworkPrefabSourceUnityBase INetworkPrefabSourceFactory.TryCreate(string assetPath) {
var prefabBundle = AssetDatabase.GetImplicitAssetBundleName(assetPath);
if (!string.IsNullOrEmpty(prefabBundle)) {
var result = ScriptableObject.CreateInstance<NetworkPrefabSourceUnityAssetBundles>();
result.AssetBundleName = prefabBundle;
result.AssetName = System.IO.Path.GetFileName(assetPath);
return result;
}
// returning null instructs Fusion to use other factories with greater Order
return null;
}
}
これらを配置すると、Asset Bundle に割り当てられた生成可能なプレハブは、その Prefab Source
が Asset Bundle と表示され、設定インスペクタの Prefabs
プロパティの項目には [AB]
が付くはずです。もし、すぐにそうならない場合は、Fusion > Rebuild Prefab Table
メニューを使用することを検討してください。
Rebuild Prefab Table
を繰り返し使用することを避けるために、Asset Bundleの割り当て変更を聞くためにいくつかのエディタ統合コードが必要です(NetworkPrefabAssetFactoryAddressable
を確認してください)。