【Unity ECS】ECSのEntityを画面クリックで選択できるようにする - 土鍋で雑多煮

土鍋で雑多煮

UnityでXR・ゲーム開発をしています。学んだことや備忘録、趣味の記録などを書いていきます。

【Unity ECS】ECSのEntityを画面クリックで選択できるようにする

はじめに

どうも、土鍋です。

Unity ECS (DOTS)では従来のようにカメラからRayを飛ばして、オブジェクトの取得をすることはできません。というのもMonoBehaviourであるカメラからのRaycastとECSで生成されたオブジェクトや移動したオブジェクトは直接参照することができないのです。

そこで今回は、Playerの画面クリック情報を座標データとしてECSのSystemに渡して、ECSのRaycastをしてあげることで解決しました。

実装していく

画面クリック情報を取得

using R3;
using UnityEngine;

public class PlayerClick : MonoBehaviour
{
    public Camera camera;
    private RaycastHit hit;

    public Observable<(Vector3, Vector3)> OnClick => clickSubject;
    private Subject<(Vector3,Vector3)> clickSubject = new Subject<(Vector3, Vector3)>();

    void Update () {
        if (Input.GetMouseButtonDown(0))
        {
            var ray = camera.ScreenPointToRay(Input.mousePosition);
            
            clickSubject.OnNext((ray.origin,ray.direction));
        }
    }
}

このコードでは画面クリック時にクリックした場所のカメラ座標と方向情報を3次元座標に変換して、値を渡しています。

※R3を使っていますので、R3を導入していない場合はEventやUniRxのもので代用してください。

ECSに渡す

using R3;
using Unity.Entities;
using UnityEngine;

public class WorldManager : MonoBehaviour
{
    [SerializeField]
    private PlayerClick _playerClick;
    private ECSPlayerInput ecsPlayerInput;
    
    void Start()
    {
        var world = World.DefaultGameObjectInjectionWorld;
        ecsPlayerInput = world.GetExistingSystemManaged<ECSPlayerInput>();

        _playerClick.OnClick.Subscribe(click =>
        {
            ecsPlayerInput.ClickRaycast(click.Item1,click.Item2);
        }).AddTo(this);
    }
}

World

WorldとはECSのあらゆる情報を保有するものです。

デフォルトで1つ目のWorldは作成され、World.DefaultGameObjectInjectionWorldで取得することが可能です。

※2つ目以降のWorldもMonoBehaviourから作成できます。

world.GetExistingSystemManagedでSystemBase継承クラスを取得できます。

クリックイベントの購読

PlayerClickのOnClickを購読しています。
画面クリックが発生すると次のECSPlayerInputにクリック情報を送ってRaycast用のメソッドを実行しています。

ECS PhysicsによるRaycast

using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Physics.Systems;
using UnityEngine;

[UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
[UpdateAfter(typeof(PhysicsSystemGroup))]
[BurstCompile]
public partial class ECSPlayerInput : SystemBase
{
    RaycastInput input;
    PhysicsWorldSingleton physics;
    
    protected override void OnCreate()
    {
        var filter = new CollisionFilter()
        {
            BelongsTo = ~0u,
            CollidesWith =  ~0u
        };

        input = new RaycastInput
        {
            Start = new float3(0, 0, 0),
            Filter = filter,
            End = new float3(0, 0, 0)
        };
    }

    protected override void OnUpdate()
    {
    }

    public void ClickRaycast(Vector3 inputOrigin, Vector3 inputDirection)
    {
        physics = SystemAPI.GetSingleton<PhysicsWorldSingleton>();

        input.Start = inputOrigin;
        
        var distance = 100;
        var goal = inputDirection * distance;
        input.End = goal + inputOrigin;
        
        if (physics.CastRay(input, out var hit))
        {
            var name = this.EntityManager.GetName(hit.Entity);
            Debug.Log(name);
        }
    }
}

MonoBehaviourから実行する必要があるのでSystemBase継承クラスで作りました。

RaycastInput

Raycastのスタート地点とゴール地点を設定します。
通常のRaycastと違い、スタートと方向ではなくゴール地点を指定してあげる必要があります。

CollisionFilterはUInt32型で自分自身のレイヤーや衝突対象のレイヤーを指定することができます。 今回は全てに衝突する「~0u」を指定しています。

physics.CastRay

if (physics.CastRay(input, out var hit))
{
    var name = this.EntityManager.GetName(hit.Entity);
    Debug.Log(name);
}

inputの条件でRaycastして衝突したオブジェクトがあったらtrueを返します。

SystemBase継承クラスなのでthis.EntityManagerを使用してヒットしたEntityの名前を取得し、Debug.Logで出力しています。

完成

参考

qiita.com

qiita.com

qiita.com

qiita.com

docs.unity3d.com

https://discussions.unity.com/t/safely-using-entitymanager-in-systembase/805590

https://discussions.unity.com/t/safely-using-entitymanager-in-systembase/805590