プレハブ
概要
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.
プレハブのソース(Resource ではないし、アドレス指定もできない)。
テーブルが不完全だったり、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を確認してください)。