Version: 2021.2
言語: 日本語
ゲームオブジェクトのスポーン (spawn)
ネットワーク権限

カスタムのスポーン関数

重要: UNet は非推奨のソリューションになり、現在、新しい Multiplayer とネットワーキングソリューション (Netcode for GameObjects) が開発中です。詳細は、GameObjects Web サイトの Unity Netcode を参照してください。

クライアント上でプレハブからゲームオブジェクトをスポーンするときに、スポーンハンドラー関数を使用してデフォルトの挙動をカスタマイズできます。スポーンハンドラー関数を使うと、ゲームオブジェクトの破棄だけでなく、スポーンの方法を完全に制御することができます。

ClientScene.RegisterSpawnHandler を使って、クライアントゲームオブジェクトをスポーン、および破棄する関数を登録します。サーバーは直接ゲームオブジェクトを作成し、この機能を通じてクライアント上にスポーンします。この関数は、ゲームブジェクトのアセット ID を取り、2 つの関数にデリゲートします。1 つはクライアント上でのゲームオブジェクトの生成を処理し、1 つはクライアント上でのゲームオブジェクトの破棄を扱います。アセット ID は動的か、(もしあるなら) スポーンしたいプレハブゲームオブジェクトのアセット ID にすることができます。

スポーン/アンスポーン (un-spawn) する関数は、ゲームオブジェクトのシグネチャを持つ必要があり、高レベルAPI で定義されています。


// クライアント上でオブジェクトをスポーンするリクエストを処理します
public delegate GameObject SpawnDelegate(Vector3 position, NetworkHash128 assetId);

// クライアント上でオブジェクトをアンスポーンするリクエストを処理します
public delegate void UnSpawnDelegate(GameObject spawned);

スポーン関数に渡すアセット ID は、プレハブの場合は NetworkIdentity.assetId を使って見つけることができます。これは、自動的に表示されます。動的なアセット ID は次のように処理します。


// 一意の新しい assetId を生成
NetworkHash128 creatureAssetId = NetworkHash128.Parse("e2656f");

// 新しい assetId のハンドラーを設定
ClientScene.RegisterSpawnHandler(creatureAssetId, SpawnCreature, UnSpawnCreature);

// 既存のプレハブ上で assetId を取得
NetworkHash128 coinAssetId = coinPrefab.GetComponent<NetworkIdentity>().assetId;

// カスタムでスポーンしたい既存のプレハブのハンドラーを設定
ClientScene.RegisterSpawnHandler(coinAssetId, SpawnCoin, UnSpawnCoin);

// コインをスポーン - SpawnCoin がクライアント上で呼び出されます
NetworkServer.Spawn(gameObject, coinAssetId);

スポーン関数自身は、デリゲートのシグネチャで実装されています。これはコインのスポーンを行う関数 (coin spawner) です。SpawnCreature は同じように見えますが、スポーンのロジックは異なります。


public GameObject SpawnCoin(Vector3 position, NetworkHash128 assetId)
{
    return (GameObject)Instantiate(m_CoinPrefab, position, Quaternion.identity);
}
public void UnSpawnCoin(GameObject spawned)
{
    Destroy(spawned);
}

カスタムのスポーン関数を使用するときに、ゲームオブジェクトを破棄せずにゲームオブジェクトのアンスポーン (スポーンの破棄) ができるのは便利なことがあります。これは、NetworkServer.UnSpawn を呼び出すことによって行います。この関数によってメッセージがクライアントに送信され、ゲームオブジェクトをアンスポーンし、それで、カスタムのアンスポーンの関数がクライアント上で呼び出されます。この関数が呼び出されたときにオブジェクトは破棄されません。

ホスト上では、ゲームオブジェクトはローカルクライアントのためにスポーンされません。なぜなら、すでにサーバー上に存在するからです。つまり、スポーンハンドラー関数は、ホスト上では一切呼び出されません。

カスタムスポーンハンドラーのゲームオブジェクトプールを設定

ここでは、カスタムのスポーンハンドラーを持つ、とてもシンプルなゲームオブジェクトプールの設定例を紹介します。スポーン/アンスポーンしてから、ゲームオブジェクトをプールに出し入れすることができます。


using UnityEngine;
using UnityEngine.Networking;
using System.Collections;

public class SpawnManager : MonoBehaviour
{
    public int m_ObjectPoolSize = 5;
    public GameObject m_Prefab;
    public GameObject[] m_Pool;

    public NetworkHash128 assetId { get; set; }
    
    public delegate GameObject SpawnDelegate(Vector3 position, NetworkHash128 assetId);
    public delegate void UnSpawnDelegate(GameObject spawned);

    void Start()
    {
        assetId = m_Prefab.GetComponent<NetworkIdentity> ().assetId;
        m_Pool = new GameObject[m_ObjectPoolSize];
        for (int i = 0; i < m_ObjectPoolSize; ++i)
        {
            m_Pool[i] = (GameObject)Instantiate(m_Prefab, Vector3.zero, Quaternion.identity);
            m_Pool[i].name = "PoolObject" + i;
            m_Pool[i].SetActive(false);
        }
        
        ClientScene.RegisterSpawnHandler(assetId, SpawnObject, UnSpawnObject);
    }

    public GameObject GetFromPool(Vector3 position)
    {
        foreach (var obj in m_Pool)
        {
            if (!obj.activeInHierarchy)
            {
                Debug.Log("Activating GameObject " + obj.name + " at " + position);
                obj.transform.position = position;
                obj.SetActive (true);
                return obj;
            }
        }
        Debug.LogError ("Could not grab GameObject from pool, nothing available");
        return null;
    }
    
    public GameObject SpawnObject(Vector3 position, NetworkHash128 assetId)
    {
        return GetFromPool(position);
    }
    
    public void UnSpawnObject(GameObject spawned)
    {
        Debug.Log ("Re-pooling GameObject " + spawned.name);
        spawned.SetActive (false);
    }
}

このマネージャーを使うために、新しい空のゲームオブジェクトを作成し「SpawnManager」と名付けます。SpawnManager という新しいスクリプトを作成し、上のコードサンプルにコピーします。そして、それを新しい SpawnManager ゲームオブジェクトにアタッチします。次に、複数回スポーンしたいプレハブを Prefab フィールドへドラッグし、 Object Pool Size を設定します (デフォルトは 5)。

最後に、プレイヤーの移動に使用するスクリプトで SpawnManager への参照を設定します。


SpawnManager spawnManager;

void Start()
{
    spawnManager = GameObject.Find("SpawnManager").GetComponent<SpawnManager> ();
}

プレイヤーのロジックは、例えば、以下のようなものを含んでいます。ここでは、コインを移動し発射します。


void Update()
{
    if (!isLocalPlayer)
        return;
    
    var x = Input.GetAxis("Horizontal")*0.1f;
    var z = Input.GetAxis("Vertical")*0.1f;
    
    transform.Translate(x, 0, z);

    if (Input.GetKeyDown(KeyCode.Space))
    {
        // コマンド関数はクライアント上で呼び出されますがサーバー上で発生します
        CmdFire();
    }
}

プレイヤーの発射ロジックでは、オブジェクトプールを使用するとよいでしょう。


[Command]
void CmdFire()
{
    // サーバー上でコインを設定
    var coin = spawnManager.GetFromPool(transform.position + transform.forward);  
    coin.GetComponent<Rigidbody>().velocity = transform.forward*4;
    
    // クライアント上でコインをスポーン。カスタムのスポーンハンドラーが呼び出されます。
    NetworkServer.Spawn(coin, spawnManager.assetId);
    
    // コインがサーバーで破棄されるとき、クライアント側でも自動的に破棄されます。
    StartCoroutine (Destroy (coin, 2.0f));
}

public IEnumerator Destroy(GameObject go, float timer)
{
    yield return new WaitForSeconds (timer);
    spawnManager.UnSpawnObject(go);
    NetworkServer.UnSpawn(go);
}

自動的な破棄によって、ゲームオブジェクトがプールに戻され、再度コインを発射するときに再使用する方法を表しています。

ゲームオブジェクトのスポーン (spawn)
ネットワーク権限