はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【Unity】Physics.RaycastAllではなくPhysics.RaycastNonAllocを利用してGC.Allocを防ぐ(実験結果付き)

はじめに

今回はPhysics.RaycastAllPhysics.RaycastNonAllocの違いについて紹介したいと思います。

docs.unity3d.com

docs.unity3d.com

概要

Physics.RaycastAllPhysics.RaycastNonAllocはどちらもRayを飛ばしてぶつかったゲームオブジェクトを返します

// オーバーロードが複数あるが、その内一個
public static RaycastHit[] RaycastAll (Vector3 origin, Vector3 direction, float maxDistance= Mathf.Infinity, int layerMask= DefaultRaycastLayers, QueryTriggerInteraction queryTriggerInteraction= QueryTriggerInteraction.UseGlobal);
public static int RaycastNonAlloc (Vector3 origin, Vector3 direction, RaycastHit[] results, float maxDistance= Mathf.Infinity, int layerMask= DefaultRaycastLayers, QueryTriggerInteraction queryTriggerInteraction= QueryTriggerInteraction.UseGlobal);

それぞれの説明は以下の通り。

Casts a ray through the Scene and returns all hits. Note that order of the results is undefined.

Physics-RaycastAll - Unity スクリプトリファレンス

// DeepL翻訳
Scene を通して光線を投げ、ヒットしたものをすべて返します。結果の順序は不定であることに注意してください。

Cast a ray through the Scene and store the hits into the buffer.

Physics-RaycastNonAlloc - Unity スクリプトリファレンス

// DeepL翻訳
Sceneに光線を照射し、ヒットした光線をバッファに格納します。

使用例は以下の通り。

// Physics.RaycastAllの使用例

// Rayの原点がorigin, Rayの方向がdirectoin, maxDistanceがヒットが発生する最大距離
RaycastHit[] hits = Physics.RaycastAll(origin: Vector3.zero, direction: Vector3.forward, maxDistance: 100);
// Physics.RaycastNonAllocの使用例

// 配列を用意しておき、そこに結果が格納する
RaycastHit[] hits = new RaycastHit[100];

// Rayの原点がorigin, Rayの方向がdirectoin, maxDistanceがヒットが発生する最大距離
Physics.RaycastNonAlloc(origin: Vector3.zero, direction: Vector3.forward, results: hits, maxDistance: 100);

違い

上記サンプルだとほぼ違いがない(Physics.RaycastNonAllocは配列に入りきらない結果が破棄される,結果のサイズが小さいと余分にメモリを利用する可能性アリ)ですが、Physics.RaycastNonAllocRaycastHit[]のインスタンスを使いまわすことができるというメリットがあります。

public class Test : MonoBehaviour
{
    private RaycastHit[] _hits = new RaycastHit[100];

    private void Update()
    {
        // Rayの原点がorigin, Rayの方向がdirectoin, maxDistanceがヒットが発生する最大距離
        Physics.RaycastNonAlloc(origin: Vector3.zero, direction: Vector3.forward, results: _hits, maxDistance: 100);
    }
}

こうすることでPhysics.RaycastAllの度に配列が確保される(GC.Alloc発生)のを、最初の一回だけしか配列が確保されないようにできます。

ただキャッシュするということはメモリを常に使用するという意味でもあるので、使い分けてください。

実験

適当にUpdate10000回メソッド呼び出ししてみます。

// 適宜特定箇所をコメントアウトして利用
public class Test : MonoBehaviour
{
    private RaycastHit[] _hits = new RaycastHit[100];

    private void Update()
    {
        for (var i = 0; i < 10000; i++)
        {
            // RaycastNonAllocの場合
            Physics.RaycastNonAlloc(origin: Vector3.zero, direction: Vector3.forward, results: _hits, maxDistance: 100);
            
            // RaycastAllの場合
            Physics.RaycastAll(Vector3.zero, Vector3.forward, 100);
        }
    }
}
←RaycastNonAlloc, RaycastAll→

Physics.RaycastNonAllocは名前の通りGC.Alloc0ですね。処理時間も1/6くらいになってます。

補足ですが、ヒットするゲームオブジェクトは一個もない状況での実験結果です。

注意点

たまに勘違いされている方がいますが、Physics.RaycastAllPhysics.RaycastNonAllocのどちらも順序が保証されているわけではないです。

またresultsの配列がnullだった場合は例外を投げるわけではなく、結果も返されないよう。