🏊

【Udon Sharp】VRCObjectPoolについて

2024/08/09に公開

はじめに

初めまして、VRChatユーザーの留奈と申します。Udon Sharpに興味を持っており、Boothでいくつかのギミックを出品しています( https://weloveyou-runa.booth.pm
VRCObjectPoolは、GameObjectのgameObject.activeSelfを簡単に同期できるという点で、オブジェクト数に上限がある場合に、VRCInstantiate(GameObject object)gameObject.SetActive(bool state)よりも優れた選択肢になります。しかし、ドキュメントが少なく、解説記事も多くはないため、導入が難しいのが現状です。また、公式ドキュメントには書かれていない注意事項も多く存在します。この記事では、いくつかのサンプルコードを通して、VRCObjectPoolの総合的な解説を行います。

VRCObjectPoolとは

公式ドキュメント( https://udonsharp.docs.vrchat.com/vrchat-api/#vrcobjectpool )によれば、

VRC Object Pool provides a lightweight method of managing an array of game objects. The pool will manage and synchronize the active state of each object it holds.

意訳:VRC Object Poolは、ゲームオブジェクトの配列を管理するための軽量な方法です。プールは、保持するゲームオブジェクトの有効・無効状態を管理し、同期します。

と紹介されています。つまり、語弊を恐れずに言えば、VRCObjectPoolは便利な機能があるGameObject[]配列であるといえるでしょう。

VRCObjectPoolコンポーネント

通常のGameObject[]配列であれば、フィールド変数として[SerializeField] GameObject[] array;のように記述し、InspectorからGameObjectを登録します。しかし、VRCObjectPoolでは、VRCObjectPoolコンポーネントを用いてGameObjectを登録します。

VRCObjectPoolコンポーネントでは、VRCObjectPoolで管理するGameObjectの数と、GameObjectを指定します。通常のGameObject[]配列を登録するときと同じです。

従って、VRCObjectPoolをUdonから利用する際は、このコンポーネントを操作する形で記述をします。以下のような形で登録しておくと後々便利でしょう。

using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.SDK3.Components;
using VRC.Udon;

public class Sample : UdonSharpBehaviour
{
    [SerializeField] private VRCObjectPool pool;

    void Start()
    {
        return;
    }
}

VRCObjectPool.Pool

VRCObjectPool.Poolは、Poolに登録されているオブジェクトを、GameObject[]配列で返すプロパティです。Return(GameObject object)メソッドで役立ちます。

VRCObjectPool.TryToSpawn()

説明とサンプル

VRCObjectPool.TryToSpawn()は、Poolに登録されている非アクティブなGameObjectを上から順番にアクティブにするメソッドです。このメソッドは、オブジェクトオーナーしか実行することができません。

では、上記の画像のように、Lengthが3で、初期状態が非アクティブなSphereを3つ登録したPoolから、オブジェクトをSpawnさせる処理を書いてみます。なお、VRCObjectPool poolには、Inspectorから、先ほどの画像のVRCObjectPoolコンポーネントを登録しているものとします。

using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.SDK3.Components;
using VRC.Udon;

public class Sample : UdonSharpBehaviour
{
    [SerializeField] private VRCObjectPool pool;

    public override void Interact()
    {
        SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.Owner, nameof(SpawnObject));
    }

    public void SpawnObject()
    {
        pool.TryToSpawn();
    }
}

これで、Interact時に、Poolのオブジェクトが有効になる処理が書けました。1回目のInteractでは「Sphere」が、2回目のInteractでは「Sphere (1)」が、3回目のInteractでは「Sphere (3)」が出現します。

注意事項

上記の例で、4回目のInteractでは、nullが返ってきてエラーになります。従って、以下のようなコードにするのが安全でしょう。

using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.SDK3.Components;
using VRC.Udon;

public class Sample : UdonSharpBehaviour
{
    [SerializeField] private VRCObjectPool pool;

    public override void Interact()
    {
        SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.Owner, nameof(SpawnObject));
    }

    public void SpawnObject()
    {
        if (pool.TryToSpawn == null) Debug.Log("Error."); 
    }
}

VRCObjectPool.Return(GameObject object)

説明とサンプル

VRCObjectPool.Return(GameObject object)は、アクティブになっている指定されたオブジェクトを非アクティブにし、その位置を初期位置に戻す機能があります。これもオブジェクトオーナーしか実行できません。

using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.SDK3.Components;
using VRC.Udon;

public class Sample : UdonSharpBehaviour
{
    [SerializeField] private VRCObjectPool pool;

    public override void Interact()
    {
        SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.Owner, nameof(ReturnObject0));
    }

    public void ReturnObject0()
    {
        pool.Return(pool.Pool[0]);
    }
}

これで、Interact時に、「Sphere」をReturnする処理が書けました。

注意事項

前述の通り、VRCObjectPool.Return(GameObject object)は、Return時に位置を初期位置に戻してくれますが、オブジェクトにVRCPickupコンポーネントがついている場合、Drop処理をしてくれません。つまり、Pickup中にReturnがかかった場合、デスクトップでは、右クリックをするまで、オブジェクトをPickupできなくなります。これを避けるため、以下のようなコードにするのが良いでしょう。

using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.SDK3.Components;
using VRC.Udon;

public class Sample : UdonSharpBehaviour
{
    [SerializeField] private VRCObjectPool pool;

    public override void Interact()
    {
        SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.All, nameof(DropObject0));
        SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.Owner, nameof(ReturnObject0));
    }

    public void DropObject0()
    {
        if (pool.Pool[0].activeSelf) pool.Pool[0].GetComponent<VRCPickup>().Drop();
    }

    public void ReturnObject0()
    {
        if (pool.Pool[0].activeSelf) pool.Return(pool.Pool[0]);
    }
}

全てのオブジェクトをReturnする

Pool内の全てのオブジェクトを一括してReturnする場合、専用のメソッドがないため、foreachなどを使って実装する必要があります。

using UdonSharp;
using UnityEngine;
using VRC.SDKBase;
using VRC.SDK3.Components;
using VRC.Udon;

public class Sample : UdonSharpBehaviour
{
    [SerializeField] private VRCObjectPool pool;

    public override void Interact()
    {
        SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.All, nameof(DropAllObject));
        SendCustomNetworkEvent(VRC.Udon.Common.Interfaces.NetworkEventTarget.Owner, nameof(ReturnObject0));
    }

    public void DropAllObject()
    {
        foreach (GameObject e in pool.Pool) {
            if (e.activeSelf && e.GetComponent<VRCPickup>().IsHeld) e.GetComponent<VRCPickup>().Drop();
        }
    }

    public void ReturnObject0()
    {
        foreach (GameObject e in pool.Pool) {
            if (e.activeSelf) pool.Return(e);
        }
    }
}

Discussion