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