2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

EntityとEntityの接触判定

Last updated at Posted at 2024-03-03

EntityとEntityの接触判定の取り方について備忘録です。
基本的にはunity physicsのサンプルプロジェクトの6. Events /Collisionsを参考にしていますが、他のプロジェクトに適用しようとすると結構面倒なので、メモを残します。

オブジェクト指向でいうOn Trigger Enter,On Trigger Stayに相当する機能を再現します。

OnTriggerStay的な機能(TriggerEvent)を再現

必要なscriptの一覧は以下
DisplayCollisionTextAuthoring.cs ※空のtextオブジェクトにアタッチするスクリプト
DisplayCollisionTextSystem.cs ※Authoringと対
StatefulCollisionEventAuthoring.cs ※地面側にアタッチするスクリプト
StatefulCollisionEventSystem.cs ※Authoringと対
EntitySender.cs ※キャラクター側、地面側、双方にアタッチするスクリプト
IStatefulSimulationEvent.cs
StatefulEventCollectionJobs.cs 
StatefulSimulationEventBuffers.cs 
StatefulTriggerEvent.cs 

詳細は公式サンプルを参照。
Physics Shape > Collision ResponseをCollide Raise Collision Eventsに変更するの忘れないように注意。

【説明】

DisplayCollisionTextAuthoring.csをアタッチしたTextオブジェクトが、キャラクターの上に移動して接触中の時間経過を表示します。

OnTriggerEnter的な機能(CollisionEvent)を再現

TriggerVolumePortalAuthoring.cs ※静的Entity側にアタッチするスクリプト
TriggerVolumePortalSystem.cs ※Authoringと対
TriggerVolumeChangeMaterialAuthoring ※静的Entity側にアタッチするスクリプト
TriggerVolumeChangeMaterialSystem ※Authoringと対
StatefulTriggerEventAuthoring ※静的Entity側にアタッチするスクリプト
StatefulTriggerEventSystem ※Authoringと対
IStatefulSimulationEvent.cs
StatefulEventCollectionJobs.cs 
StatefulSimulationEventBuffers.cs 
StatefulTriggerEvent.cs 

【説明】

TriggerVolumeChangeMaterialAuthoringにより、オブジェクトの色や移動方向を変更します

以下3つのスクリプトは、OnTriggerStay的な機能とOnTriggerEnter的な機能の両方に必要です。
IStatefulSimulationEvent.cs
StatefulEventCollectionJobs.cs 
StatefulSimulationEventBuffers.cs 

IStatefulSimulationEvent.csでは、以下4つの状態を作成します。
・Undefined,
・Enter,
・Stay,
・Exit

StatefulEventCollectionJobs.csには、[BurstCompile]で実行するJobが羅列されている。
TriggerEventとCollisionEvent、どちらも定義されていて、それぞれ関連する〇〇Eventを実行する流れに繋がる。

StatefulSimulationEventBuffers.csについては、バッファーについての記事が参考になりそうです。

試しにTriggerVolumePortalSystem.csの中身を見てみます。

using Unity.Assertions;
using Unity.Burst;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Physics.GraphicsIntegration;
using Unity.Physics.Stateful;
using Unity.Physics.Systems;
using Unity.Transforms;

namespace Events
{
    [RequireMatchingQueriesForUpdate]
    [UpdateInGroup(typeof(FixedStepSimulationSystemGroup))]
    [UpdateAfter(typeof(PhysicsSystemGroup))]
    public partial struct TriggerVolumePortalSystem : ISystem
    {
        [BurstCompile]
        public void OnCreate(ref SystemState state)
        {
            state.RequireForUpdate<TriggerVolumePortal>();
        }

        [BurstCompile]
        public void OnUpdate(ref SystemState state)
        {
            var hierarchyChildQuery = SystemAPI.QueryBuilder().WithAll<Parent, LocalToWorld>().Build();
            Assert.IsFalse(hierarchyChildQuery.HasFilter(),
                "The use of EntityQueryMask in this system will not respect the query's active filter settings.");

            var nonTriggerDynamicBodyQuery = SystemAPI.QueryBuilder()
                .WithAll<LocalTransform, PhysicsVelocity>()
                .WithNone<StatefulTriggerEvent>().Build();
            Assert.IsFalse(nonTriggerDynamicBodyQuery.HasFilter(),
                "The use of EntityQueryMask in this system will not respect the query's active filter settings.");

            state.Dependency = new TriggerVolumePortalJob()
            {
                LocalToWorldLookup = SystemAPI.GetComponentLookup<LocalToWorld>(),
                LocalTransformLookup = SystemAPI.GetComponentLookup<LocalTransform>(),
                TriggerVolumePortalLookup = SystemAPI.GetComponentLookup<TriggerVolumePortal>(),
                PhysicsVelocityLookup = SystemAPI.GetComponentLookup<PhysicsVelocity>(),
                PhysicsGraphicalSmoothingLookup = SystemAPI.GetComponentLookup<PhysicsGraphicalSmoothing>(),
                HierarchyChildMask = hierarchyChildQuery.GetEntityQueryMask(),
                NonTriggerDynamicBodyMask = nonTriggerDynamicBodyQuery.GetEntityQueryMask()
            }.Schedule(state.Dependency);
        }

        [BurstCompile]
        partial struct TriggerVolumePortalJob : IJobEntity
        {
            public EntityQueryMask HierarchyChildMask;
            public EntityQueryMask NonTriggerDynamicBodyMask;
            public ComponentLookup<LocalTransform> LocalTransformLookup;
            public ComponentLookup<LocalToWorld> LocalToWorldLookup;
            public ComponentLookup<TriggerVolumePortal> TriggerVolumePortalLookup;
            public ComponentLookup<PhysicsVelocity> PhysicsVelocityLookup;
            public ComponentLookup<PhysicsGraphicalSmoothing> PhysicsGraphicalSmoothingLookup;

            public void Execute(Entity portalEntity, ref DynamicBuffer<StatefulTriggerEvent> triggerBuffer) //
            {
                if (!TriggerVolumePortalLookup.HasComponent(portalEntity))
                {
                    return;
                }

                var triggerVolumePortal = TriggerVolumePortalLookup[portalEntity];
                var companionEntity = triggerVolumePortal.Companion; //もう一つのポータルのこと?
                var companionTriggerVolumePortal = TriggerVolumePortalLookup[companionEntity];

                for (int i = 0; i < triggerBuffer.Length; i++)
                {
                    var triggerEvent = triggerBuffer[i];
                    var otherEntity = triggerEvent.GetOtherEntity(portalEntity); //otheretityも、portalentityも、接触して移動する側の動的entytyを指す。

                    // exclude other triggers, static bodies and processed events
                    if (triggerEvent.State != StatefulEventState.Enter ||
                        !NonTriggerDynamicBodyMask.MatchesIgnoreFilter(otherEntity))
                    {
                        continue;
                    }

                    // Check if entity just teleported to this portal,
                    // and if it did, decrement TransferCount
                    if (triggerVolumePortal.TransferCount != 0)
                    {
                        triggerVolumePortal.TransferCount--;
                        continue;
                    }

                    // a static body may be in a hierarchy, in which case Translation and Rotation may not be in world space
                    var portalTransform = HierarchyChildMask.MatchesIgnoreFilter(portalEntity)
                        ? Math.DecomposeRigidBodyTransform(LocalToWorldLookup[portalEntity].Value)
                        : new RigidTransform(LocalTransformLookup[portalEntity].Rotation,
                        LocalTransformLookup[portalEntity].Position);

                    var companionTransform = HierarchyChildMask.MatchesIgnoreFilter(companionEntity)
                        ? Math.DecomposeRigidBodyTransform(LocalToWorldLookup[companionEntity].Value)
                        : new RigidTransform(LocalTransformLookup[companionEntity].Rotation,
                        LocalTransformLookup[companionEntity].Position);


                    var portalPositionOffset = companionTransform.pos - portalTransform.pos;
                    var portalRotationOffset = math.mul(companionTransform.rot, math.inverse(portalTransform.rot));

                    var entityLocalTransformComponent = LocalTransformLookup[otherEntity];
                    var entityVelocityComponent = PhysicsVelocityLookup[otherEntity];

                    entityVelocityComponent.Linear = math.rotate(portalRotationOffset, entityVelocityComponent.Linear);
                    entityLocalTransformComponent.Position += portalPositionOffset;
                    entityLocalTransformComponent.Rotation =
                        math.mul(entityLocalTransformComponent.Rotation, portalRotationOffset);

                    LocalTransformLookup[otherEntity] = entityLocalTransformComponent;
                    PhysicsVelocityLookup[otherEntity] = entityVelocityComponent;

                    if (PhysicsGraphicalSmoothingLookup.HasComponent(otherEntity))
                    {
                        var entitySmoothingComponent = PhysicsGraphicalSmoothingLookup[otherEntity];
                        entitySmoothingComponent.ApplySmoothing = 0;
                        PhysicsGraphicalSmoothingLookup[otherEntity] = entitySmoothingComponent;
                    }

                    companionTriggerVolumePortal.TransferCount++;
                }

                TriggerVolumePortalLookup[portalEntity] = triggerVolumePortal;
                TriggerVolumePortalLookup[companionEntity] = companionTriggerVolumePortal;
            }
        }
    }
}

60行目の
public void Execute(Entity portalEntity, ref DynamicBuffer triggerBuffer)
では、Buffernに格納された動的Entityの情報(接触時にBufferに格納される)を参照し、以後portalEntityとして取り扱っています。
そして、
entityLocalTransformComponent.Position += portalPositionOffset;
の部分で、球に対して移動したい二つのポータルボックス間のワールド座標の差分を与えて、位置情報を変化させています。

この辺の記述を色々書き換えれば、色々な接触時のアクションを再現できそうです。

Entityを接触時に消滅させる

例えばEntity×Entityの接触で片方を消滅させるには、
OnUpdateの中に、

state.Dependency = new TriggerVanishJob()
{
    _ecb = state.World.GetOrCreateSystemManaged<BeginSimulationEntityCommandBufferSystem>().CreateCommandBuffer()
}.Schedule(state.Dependency);

を追記して、partial struct TriggerVanishJob : IJobEntity内に

{
    public EntityCommandBuffer _ecb;
    public void Execute(Entity portalEntity, ref DynamicBuffer<StatefulTriggerPortalEvent> triggerBuffer)
    {
        for (int i = 0; i < triggerBuffer.Length; i++)
        {
            var triggerEvent = triggerBuffer[i];
            _ecb.DestroyEntity(otherEntity);

のように追記してあげれば、以下動画のように再現できます。

ただこのままだと、玉の移動と、消滅を両方同時に再現できません。
本当は右の玉だけ消滅させたかったのですが、上記動画でも3つとも消滅してしまいました。
理由は、衝突した玉の情報を全て同じBufferに格納しているためです。

そこで、衝突と消滅のBufferを別々に作ってあげれば解決します。

玉の消滅を別のBufferに格納するために、StatefulTriggerVanishEventという別のイベントを作成します。
この例の場合、機能別にわかりやすく管理するために、
・StatefulTriggerEvent
・TriggerVolumePortalSystem
・StatefulTriggerEventAuthoring
・StatefulTriggerEventSystem
・StatefulEventCollectionJobs
の中を、
StatefulTriggerEvent → StatefulTriggerVanishEvent

TriggerVolumePortalAuthoring
TriggerVolumePortalSystem
の中を、
Events → EventVanish

とかに変更するのが良さそうです。

以下についても変更が必要です。

・TriggerVolumePortalAuthoringの中に、AddBuffer(entity);を追加
・StatefulTriggerEventSystemの中で、各job名を変更
 例:TriggerVanishEventsなど、StatefulEventCollectionJobsで自分で定義した名前に合わせる。

沢山の接触Triggerイベントを作るには、同じことを繰り返せばOKです。

Entityベースの機能追加は、通常のオブジェクト指向で作っていく何倍も難しいですね。
コードが複雑だし読みづらい...

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?