using System;
using Unity.Burst;
using Unity.Collections;
using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Physics.GraphicsIntegration;
using Unity.Rendering;
using Unity.Transforms;
using UnityEngine;
using static Unity.Physics.Extensions.PhysicsSamplesExtensions;
namespace Unity.Physics.Extensions
{
public class MouseHover : IComponentData
{
public bool IgnoreTriggers;
public bool IgnoreStatic;
public Entity PreviousEntity;
public Entity CurrentEntity;
public Entity HoverEntity;
public MaterialMeshInfo OriginalMeshInfo;
public RenderMeshArray OriginalRenderMeshes;
}
[DisallowMultipleComponent]
public class MouseHoverAuthoring : MonoBehaviour
{
public GameObject HoverPrefab;
public bool IgnoreTriggers = true;
public bool IgnoreStatic = true;
// Note: override OnEnable to be able to disable the component in the editor
protected void OnEnable() {}
}
class MouseHoverBaker : Baker<MouseHoverAuthoring>
{
public override void Bake(MouseHoverAuthoring authoring)
{
var entity = GetEntity(TransformUsageFlags.None);
AddComponentObject(entity, new MouseHover()
{
PreviousEntity = Entity.Null,
CurrentEntity = Entity.Null,
IgnoreTriggers = authoring.IgnoreTriggers,
IgnoreStatic = authoring.IgnoreStatic,
HoverEntity = GetEntity(authoring.HoverPrefab, TransformUsageFlags.Dynamic),
});
}
}
// Applies any mouse spring as a change in velocity on the entity's motion component
// Limitations: works only if the physics objects in the scene come from the same subscene as MouseHoverAuthoring
// Will be fixable if there is a Unity.Rendering API that lets you get the UnityEngine.Mesh that an entity is using for renderin.
[UpdateInGroup(typeof(InitializationSystemGroup))]
public partial class MouseHoverSystem : SystemBase
{
[BurstCompile]
public struct WorldRaycastJob : IJob
{
public RaycastInput RayInput;
[ReadOnly] public CollisionWorld CollisionWorld;
[ReadOnly] public bool IgnoreTriggers;
[ReadOnly] public bool IgnoreStatic;
public NativeReference<RaycastHit> RaycastHitRef;
public void Execute()
{
var mousePickCollector = new MousePickCollector(CollisionWorld.NumDynamicBodies)
{
IgnoreTriggers = IgnoreTriggers,
IgnoreStatic = IgnoreStatic
};
if (CollisionWorld.CastRay(RayInput, ref mousePickCollector))
{
RaycastHitRef.Value = mousePickCollector.Hit;
}
}
}
protected override void OnCreate()
{
base.OnCreate();
RequireForUpdate<MouseHover>();
}
// Find the Entity holding the Graphical representation of a Physics Shape.
// It may be that the Physics and Graphics representation are on the same Entity.
public Entity FindGraphicsEntityFromPhysics(Entity bodyEntity) => FindGraphicsEntityFromPhysics(bodyEntity, ColliderKey.Empty);
public Entity FindGraphicsEntityFromPhysics(Entity bodyEntity, ColliderKey leafColliderKey)
{
if (bodyEntity.Equals(Entity.Null))
{
// No Physics so no Graphics
return Entity.Null;
}
// Set the Graphics Entity to the supplied Physics Entity
var renderEntity = bodyEntity;
// Check if we have hit a leaf node
if (!leafColliderKey.Equals(ColliderKey.Empty))
{
// Get the Physics Collider
var rootCollider = EntityManager.GetComponentData<PhysicsCollider>(bodyEntity).Value;
// If we hit a CompoundCollider we need to find the original Entity associated
// the actual leaf Collider that was hit.
if (rootCollider.Value.Type == ColliderType.Compound)
{
#region Find a Leaf Entity and ColliderKey
var leafEntity = Entity.Null;
unsafe
{
var rootColliderPtr = rootCollider.AsPtr();
// Get the leaf Collider and check if we hit was a PolygonCollider (i.e. a Triangle or a Quad)
rootColliderPtr->GetLeaf(leafColliderKey, out var childCollider);
leafEntity = childCollider.Entity;
// PolygonColliders are likely to not have an original Entity associated with them
// So if we have a Polygon and it has no Entity then we really need to check for
// the higher level Mesh or Terrain Collider instead.
var childColliderType = childCollider.Collider->Type;
var childColliderIsPolygon = childColliderType == ColliderType.Triangle || childColliderType == ColliderType.Quad;
if (childColliderIsPolygon && childCollider.Entity.Equals(Entity.Null))
{
// Get the ColliderKey of the Polygon's parent
if (TryGetParentColliderKey(rootColliderPtr, leafColliderKey, out leafColliderKey))
{
// Get the Mesh or Terrain Collider of the Polygon
TryGetChildInHierarchy(rootColliderPtr, leafColliderKey, out childCollider);
leafEntity = childCollider.Entity;
}
}
}
#endregion
// The Entities recorded in the leaves of a CompoundCollider may have been correct
// at the time of conversion. However, if the Collider blob is shared, or came up
// through a sub scene, we cannot assume that the baked Entities in the
// CompoundCollider are still valid.
// On conversion Entities using a CompoundCollider have an extra dynamic buffer added
// which holds a list of Entity/ColliderKey pairs. This buffer should be patched up
// automatically and be valid with each instance, at least until you start messing
// with the Entity hierarchy yourself e.g. by deleting Entities.
#region Check the Leaf Entity is valid
// If the leafEntity was never assigned in the first place
// there is no point in looking up any Buffers.
if (!leafEntity.Equals(Entity.Null))
{
// Check for an Key/Entity pair buffer first.
// This should exist if the Physics conversion pipeline was invoked.
var colliderKeyEntityPairBuffers = GetBufferLookup<PhysicsColliderKeyEntityPair>(true);
if (colliderKeyEntityPairBuffers.HasBuffer(bodyEntity))
{
var colliderKeyEntityBuffer = colliderKeyEntityPairBuffers[bodyEntity];
for (int i = 0; i < colliderKeyEntityBuffer.Length; i++)
{
var bufferColliderKey = colliderKeyEntityBuffer[i].Key;
if (leafColliderKey.Equals(bufferColliderKey))
{
renderEntity = colliderKeyEntityBuffer[i].Entity;
break;
}
}
}
else
{
// We haven't found a Key/Entity pair buffer so the compound collider
// may have been created in code.
// We'll assume the Entity in the CompoundCollider is valid
renderEntity = leafEntity;
// If this CompoundCollider was instanced from a prefab then the entities
// in the compound children would actually reference the original prefab hierarchy.
var rootEntityFromLeaf = leafEntity;
while (SystemAPI.HasComponent<Parent>(rootEntityFromLeaf))
{
rootEntityFromLeaf = SystemAPI.GetComponent<Parent>(rootEntityFromLeaf).Value;
}
// If the root Entity found from the leaf does not match the body Entity
// then we have hit an instance using the same CompoundCollider.
// This means we can try and remap the leaf Entity to the new hierarchy.
if (!rootEntityFromLeaf.Equals(bodyEntity))
{
// This assumes there is a LinkedEntityGroup Buffer on original and instance Entity.
// No doubt there is a more optimal way of doing this remap with more specific
// knowledge of the final application.
var linkedEntityGroupBuffers = GetBufferLookup<LinkedEntityGroup>(true);
// Only remap if the buffers exist, have been created and are of equal length.
bool hasBufferRootEntity = linkedEntityGroupBuffers.HasBuffer(rootEntityFromLeaf);
bool hasBufferBodyEntity = linkedEntityGroupBuffers.HasBuffer(bodyEntity);
if (hasBufferRootEntity && hasBufferBodyEntity)
{
var prefabEntityGroupBuffer = linkedEntityGroupBuffers[rootEntityFromLeaf];
var instanceEntityGroupBuffer = linkedEntityGroupBuffers[bodyEntity];
if (prefabEntityGroupBuffer.IsCreated && instanceEntityGroupBuffer.IsCreated
&& (prefabEntityGroupBuffer.Length == instanceEntityGroupBuffer.Length))
{
var prefabEntityGroup = prefabEntityGroupBuffer.AsNativeArray();
var instanceEntityGroup = instanceEntityGroupBuffer.AsNativeArray();
for (int i = 0; i < prefabEntityGroup.Length; i++)
{
// If we've found the renderEntity index in the prefab hierarchy,
// set the renderEntity to the equivalent Entity in the instance
if (prefabEntityGroup[i].Value.Equals(renderEntity))
{
renderEntity = instanceEntityGroup[i].Value;
break;
}
}
}
}
}
}
}
#endregion
}
}
// Finally check to see if we have a graphics redirection on the shape Entity.
if (SystemAPI.HasComponent<PhysicsRenderEntity>(renderEntity))
{
renderEntity = SystemAPI.GetComponent<PhysicsRenderEntity>(renderEntity).Entity;
}
// If no render info is found on the located render entity, we try to find any child which has render info
if (renderEntity != Entity.Null && !EntityManager.HasComponent<MaterialMeshInfo>(renderEntity))
{
// No render information on this entity. Try to find the actual render entity in the hierarchy.
if (EntityManager.HasBuffer<Child>(renderEntity))
{
var children = EntityManager.GetBuffer<Child>(renderEntity);
foreach (var childElement in children)
{
// find the first child with render info
if (EntityManager.HasComponent<MaterialMeshInfo>(childElement.Value))
{
renderEntity = childElement.Value;
}
}
}
}
return renderEntity;
}
protected override void OnUpdate()
{
var collisionWorld = SystemAPI.GetSingleton<PhysicsWorldSingleton>().CollisionWorld;
Vector2 mousePosition = Input.mousePosition;
UnityEngine.Ray unityRay = Camera.main.ScreenPointToRay(mousePosition);
var rayInput = new RaycastInput
{
Start = unityRay.origin,
End = unityRay.origin + unityRay.direction * MousePickSystem.k_MaxDistance,
Filter = CollisionFilter.Default,
};
var mouseHover = SystemAPI.ManagedAPI.GetSingleton<MouseHover>();
RaycastHit hit;
using (var raycastHitRef = new NativeReference<RaycastHit>(Allocator.TempJob))
{
var rcj = new WorldRaycastJob()
{
CollisionWorld = collisionWorld,
RayInput = rayInput,
IgnoreTriggers = mouseHover.IgnoreTriggers,
IgnoreStatic = mouseHover.IgnoreStatic,
RaycastHitRef = raycastHitRef
};
rcj.Run();
hit = raycastHitRef.Value;
}
var graphicsEntity = FindGraphicsEntityFromPhysics(hit.Entity, hit.ColliderKey);
// If still hovering over the same entity then do nothing.
if (mouseHover.CurrentEntity.Equals(graphicsEntity)) return;
mouseHover.PreviousEntity = mouseHover.CurrentEntity;
mouseHover.CurrentEntity = graphicsEntity;
bool hasPreviousEntity = !mouseHover.PreviousEntity.Equals(Entity.Null);
bool hasCurrentEntity = !mouseHover.CurrentEntity.Equals(Entity.Null);
if (hasPreviousEntity && EntityManager.HasComponent<MaterialMeshInfo>(mouseHover.PreviousEntity))
{
// restore render info to original in the last entity we were hovering over
EntityManager.SetComponentData(mouseHover.PreviousEntity, mouseHover.OriginalMeshInfo);
EntityManager.SetSharedComponentManaged(mouseHover.PreviousEntity, mouseHover.OriginalRenderMeshes);
}
if (hasCurrentEntity && EntityManager.HasComponent<MaterialMeshInfo>(mouseHover.CurrentEntity) && EntityManager.HasComponent<RenderMeshArray>(mouseHover.CurrentEntity))
{
mouseHover.PreviousEntity = mouseHover.CurrentEntity;
mouseHover.CurrentEntity = graphicsEntity;
mouseHover.OriginalMeshInfo = EntityManager.GetComponentData<MaterialMeshInfo>(mouseHover.CurrentEntity);
mouseHover.OriginalRenderMeshes = EntityManager.GetSharedComponentManaged<RenderMeshArray>(mouseHover.CurrentEntity);
// get render info from the hover entity
var hoverMeshInfo = EntityManager.GetComponentData<MaterialMeshInfo>(mouseHover.HoverEntity);
var hoverRenderMeshes = EntityManager.GetSharedComponentManaged<RenderMeshArray>(mouseHover.HoverEntity);
// create new render info for the current entity that we hover over:
// use the materials from the hover entity, but the meshes from the current entity
var newRenderMeshes = new RenderMeshArray(hoverRenderMeshes.MaterialReferences, mouseHover.OriginalRenderMeshes.MeshReferences);
// use the material id from the hover entity, but the mesh id from the current entity
var newMeshInfo = MaterialMeshInfo.FromRenderMeshArrayIndices(hoverMeshInfo.Material, mouseHover.OriginalMeshInfo.Mesh);
// apply the new render info to the current entity
EntityManager.SetComponentData(mouseHover.CurrentEntity, newMeshInfo);
EntityManager.SetSharedComponentManaged(mouseHover.CurrentEntity, newRenderMeshes);
}
}
}
}
1. MouseHover (IComponentData)
// 관리되는 객체이므로 Class를 사용
public class MouseHover : IComponentData
{
public bool IgnoreTriggers;
public bool IgnoreStatic;
public Entity PreviousEntity; // 이전 프레임에서 Hover 상태의 Entity
public Entity CurrentEntity; // 현재 프레임에서 Hover 상태의 Entity
public Entity HoverEntity; // Hover 상태일 때 적용할 메쉬/머터리얼이 있는 Entity
public MaterialMeshInfo OriginalMeshInfo; // 원래의 렌더링 정보를 저장
public RenderMeshArray OriginalRenderMeshes; // "
}
2. MouseHoverBaker (Baker)
[DisallowMultipleComponent]
public class MouseHoverAuthoring : MonoBehaviour
{
public GameObject HoverPrefab;
public bool IgnoreTriggers = true;
public bool IgnoreStatic = true;
// 참고: 편집기에서 구성 요소를 비활성화하려면 OnEnable을 재정의합니다
protected void OnEnable() { }
}
// MouseHover 컴포넌트를 Entity에 추가
class MouseHoverBaker : Baker<MouseHoverAuthoring>
{
public override void Bake(MouseHoverAuthoring authoring)
{
var entity = GetEntity(TransformUsageFlags.None);
AddComponentObject(entity, new MouseHover()
{
PreviousEntity = Entity.Null,
CurrentEntity = Entity.Null,
IgnoreTriggers = authoring.IgnoreTriggers,
IgnoreStatic = authoring.IgnoreStatic,
HoverEntity = GetEntity(authoring.HoverPrefab, TransformUsageFlags.Dynamic), // HoverPrefab을 HoverEntity로 변환하여 저장
});
}
}
3. MouseHoverSystem (SystemBase)
// Applies any mouse spring as a change in velocity on the entity's motion component
// Limitations: works only if the physics objects in the scene come from the same subscene as MouseHoverAuthoring
// Will be fixable if there is a Unity.Rendering API that lets you get the UnityEngine.Mesh that an entity is using for renderin.
[UpdateInGroup(typeof(InitializationSystemGroup))]
public partial class MouseHoverSystem : SystemBase
{
[BurstCompile]
public struct WorldRaycastJob : IJob
{
public RaycastInput RayInput;
[ReadOnly] public CollisionWorld CollisionWorld;
[ReadOnly] public bool IgnoreTriggers;
[ReadOnly] public bool IgnoreStatic;
public NativeReference<RaycastHit> RaycastHitRef;
public void Execute()
{
var mousePickCollector = new MousePickCollector(CollisionWorld.NumDynamicBodies)
{
IgnoreTriggers = IgnoreTriggers,
IgnoreStatic = IgnoreStatic
};
if (CollisionWorld.CastRay(RayInput, ref mousePickCollector))
{
RaycastHitRef.Value = mousePickCollector.Hit;
}
}
}
protected override void OnCreate()
{
base.OnCreate();
RequireForUpdate<MouseHover>();
}
// Find the Entity holding the Graphical representation of a Physics Shape.
// It may be that the Physics and Graphics representation are on the same Entity.
public Entity FindGraphicsEntityFromPhysics(Entity bodyEntity) => FindGraphicsEntityFromPhysics(bodyEntity, ColliderKey.Empty);
public Entity FindGraphicsEntityFromPhysics(Entity bodyEntity, ColliderKey leafColliderKey)
{
if (bodyEntity.Equals(Entity.Null))
{
// No Physics so no Graphics
return Entity.Null;
}
// Set the Graphics Entity to the supplied Physics Entity
var renderEntity = bodyEntity;
// Check if we have hit a leaf node
if (!leafColliderKey.Equals(ColliderKey.Empty))
{
// Get the Physics Collider
var rootCollider = EntityManager.GetComponentData<PhysicsCollider>(bodyEntity).Value;
// If we hit a CompoundCollider we need to find the original Entity associated
// the actual leaf Collider that was hit.
if (rootCollider.Value.Type == ColliderType.Compound)
{
#region Find a Leaf Entity and ColliderKey
var leafEntity = Entity.Null;
unsafe
{
var rootColliderPtr = rootCollider.AsPtr();
// Get the leaf Collider and check if we hit was a PolygonCollider (i.e. a Triangle or a Quad)
rootColliderPtr->GetLeaf(leafColliderKey, out var childCollider);
leafEntity = childCollider.Entity;
// PolygonColliders are likely to not have an original Entity associated with them
// So if we have a Polygon and it has no Entity then we really need to check for
// the higher level Mesh or Terrain Collider instead.
var childColliderType = childCollider.Collider->Type;
var childColliderIsPolygon = childColliderType == ColliderType.Triangle || childColliderType == ColliderType.Quad;
if (childColliderIsPolygon && childCollider.Entity.Equals(Entity.Null))
{
// Get the ColliderKey of the Polygon's parent
if (TryGetParentColliderKey(rootColliderPtr, leafColliderKey, out leafColliderKey))
{
// Get the Mesh or Terrain Collider of the Polygon
TryGetChildInHierarchy(rootColliderPtr, leafColliderKey, out childCollider);
leafEntity = childCollider.Entity;
}
}
}
#endregion
// The Entities recorded in the leaves of a CompoundCollider may have been correct
// at the time of conversion. However, if the Collider blob is shared, or came up
// through a sub scene, we cannot assume that the baked Entities in the
// CompoundCollider are still valid.
// On conversion Entities using a CompoundCollider have an extra dynamic buffer added
// which holds a list of Entity/ColliderKey pairs. This buffer should be patched up
// automatically and be valid with each instance, at least until you start messing
// with the Entity hierarchy yourself e.g. by deleting Entities.
#region Check the Leaf Entity is valid
// If the leafEntity was never assigned in the first place
// there is no point in looking up any Buffers.
if (!leafEntity.Equals(Entity.Null))
{
// Check for an Key/Entity pair buffer first.
// This should exist if the Physics conversion pipeline was invoked.
var colliderKeyEntityPairBuffers = GetBufferLookup<PhysicsColliderKeyEntityPair>(true);
if (colliderKeyEntityPairBuffers.HasBuffer(bodyEntity))
{
var colliderKeyEntityBuffer = colliderKeyEntityPairBuffers[bodyEntity];
for (int i = 0; i < colliderKeyEntityBuffer.Length; i++)
{
var bufferColliderKey = colliderKeyEntityBuffer[i].Key;
if (leafColliderKey.Equals(bufferColliderKey))
{
renderEntity = colliderKeyEntityBuffer[i].Entity;
break;
}
}
}
else
{
// We haven't found a Key/Entity pair buffer so the compound collider
// may have been created in code.
// We'll assume the Entity in the CompoundCollider is valid
renderEntity = leafEntity;
// If this CompoundCollider was instanced from a prefab then the entities
// in the compound children would actually reference the original prefab hierarchy.
var rootEntityFromLeaf = leafEntity;
while (SystemAPI.HasComponent<Parent>(rootEntityFromLeaf))
{
rootEntityFromLeaf = SystemAPI.GetComponent<Parent>(rootEntityFromLeaf).Value;
}
// If the root Entity found from the leaf does not match the body Entity
// then we have hit an instance using the same CompoundCollider.
// This means we can try and remap the leaf Entity to the new hierarchy.
if (!rootEntityFromLeaf.Equals(bodyEntity))
{
// This assumes there is a LinkedEntityGroup Buffer on original and instance Entity.
// No doubt there is a more optimal way of doing this remap with more specific
// knowledge of the final application.
var linkedEntityGroupBuffers = GetBufferLookup<LinkedEntityGroup>(true);
// Only remap if the buffers exist, have been created and are of equal length.
bool hasBufferRootEntity = linkedEntityGroupBuffers.HasBuffer(rootEntityFromLeaf);
bool hasBufferBodyEntity = linkedEntityGroupBuffers.HasBuffer(bodyEntity);
if (hasBufferRootEntity && hasBufferBodyEntity)
{
var prefabEntityGroupBuffer = linkedEntityGroupBuffers[rootEntityFromLeaf];
var instanceEntityGroupBuffer = linkedEntityGroupBuffers[bodyEntity];
if (prefabEntityGroupBuffer.IsCreated && instanceEntityGroupBuffer.IsCreated
&& (prefabEntityGroupBuffer.Length == instanceEntityGroupBuffer.Length))
{
var prefabEntityGroup = prefabEntityGroupBuffer.AsNativeArray();
var instanceEntityGroup = instanceEntityGroupBuffer.AsNativeArray();
for (int i = 0; i < prefabEntityGroup.Length; i++)
{
// If we've found the renderEntity index in the prefab hierarchy,
// set the renderEntity to the equivalent Entity in the instance
if (prefabEntityGroup[i].Value.Equals(renderEntity))
{
renderEntity = instanceEntityGroup[i].Value;
break;
}
}
}
}
}
}
}
#endregion
}
}
// Finally check to see if we have a graphics redirection on the shape Entity.
if (SystemAPI.HasComponent<PhysicsRenderEntity>(renderEntity))
{
renderEntity = SystemAPI.GetComponent<PhysicsRenderEntity>(renderEntity).Entity;
}
// If no render info is found on the located render entity, we try to find any child which has render info
if (renderEntity != Entity.Null && !EntityManager.HasComponent<MaterialMeshInfo>(renderEntity))
{
// No render information on this entity. Try to find the actual render entity in the hierarchy.
if (EntityManager.HasBuffer<Child>(renderEntity))
{
var children = EntityManager.GetBuffer<Child>(renderEntity);
foreach (var childElement in children)
{
// find the first child with render info
if (EntityManager.HasComponent<MaterialMeshInfo>(childElement.Value))
{
renderEntity = childElement.Value;
}
}
}
}
return renderEntity;
}
protected override void OnUpdate()
{
var collisionWorld = SystemAPI.GetSingleton<PhysicsWorldSingleton>().CollisionWorld;
Vector2 mousePosition = Input.mousePosition;
UnityEngine.Ray unityRay = Camera.main.ScreenPointToRay(mousePosition);
var rayInput = new RaycastInput
{
Start = unityRay.origin,
End = unityRay.origin + unityRay.direction * MousePickSystem.k_MaxDistance,
Filter = CollisionFilter.Default,
};
var mouseHover = SystemAPI.ManagedAPI.GetSingleton<MouseHover>();
RaycastHit hit;
using (var raycastHitRef = new NativeReference<RaycastHit>(Allocator.TempJob))
{
var rcj = new WorldRaycastJob()
{
CollisionWorld = collisionWorld,
RayInput = rayInput,
IgnoreTriggers = mouseHover.IgnoreTriggers,
IgnoreStatic = mouseHover.IgnoreStatic,
RaycastHitRef = raycastHitRef
};
rcj.Run();
hit = raycastHitRef.Value;
}
var graphicsEntity = FindGraphicsEntityFromPhysics(hit.Entity, hit.ColliderKey);
// If still hovering over the same entity then do nothing.
if (mouseHover.CurrentEntity.Equals(graphicsEntity)) return;
mouseHover.PreviousEntity = mouseHover.CurrentEntity;
mouseHover.CurrentEntity = graphicsEntity;
bool hasPreviousEntity = !mouseHover.PreviousEntity.Equals(Entity.Null);
bool hasCurrentEntity = !mouseHover.CurrentEntity.Equals(Entity.Null);
if (hasPreviousEntity && EntityManager.HasComponent<MaterialMeshInfo>(mouseHover.PreviousEntity))
{
// restore render info to original in the last entity we were hovering over
EntityManager.SetComponentData(mouseHover.PreviousEntity, mouseHover.OriginalMeshInfo);
EntityManager.SetSharedComponentManaged(mouseHover.PreviousEntity, mouseHover.OriginalRenderMeshes);
}
if (hasCurrentEntity && EntityManager.HasComponent<MaterialMeshInfo>(mouseHover.CurrentEntity) && EntityManager.HasComponent<RenderMeshArray>(mouseHover.CurrentEntity))
{
mouseHover.PreviousEntity = mouseHover.CurrentEntity;
mouseHover.CurrentEntity = graphicsEntity;
mouseHover.OriginalMeshInfo = EntityManager.GetComponentData<MaterialMeshInfo>(mouseHover.CurrentEntity);
mouseHover.OriginalRenderMeshes = EntityManager.GetSharedComponentManaged<RenderMeshArray>(mouseHover.CurrentEntity);
// get render info from the hover entity
var hoverMeshInfo = EntityManager.GetComponentData<MaterialMeshInfo>(mouseHover.HoverEntity);
var hoverRenderMeshes = EntityManager.GetSharedComponentManaged<RenderMeshArray>(mouseHover.HoverEntity);
// create new render info for the current entity that we hover over:
// use the materials from the hover entity, but the meshes from the current entity
var newRenderMeshes = new RenderMeshArray(hoverRenderMeshes.MaterialReferences, mouseHover.OriginalRenderMeshes.MeshReferences);
// use the material id from the hover entity, but the mesh id from the current entity
var newMeshInfo = MaterialMeshInfo.FromRenderMeshArrayIndices(hoverMeshInfo.Material, mouseHover.OriginalMeshInfo.Mesh);
// apply the new render info to the current entity
EntityManager.SetComponentData(mouseHover.CurrentEntity, newMeshInfo);
EntityManager.SetSharedComponentManaged(mouseHover.CurrentEntity, newRenderMeshes);
}
}
}
1) InitializationSystemGroup

초기화 단계에서 실행됨
2) WorldRaycastJob (IJob)

마우스 위치에서 Raycast를 실행하는 IJob
CollisionWorld.CastRay()를 사용하여 마우스가 클릭한 지점의 물리 객체를 찾음mousePickCollector를 사용하여 필터링 (IgnoreTriggers,IgnoreStatic)
3) OnUpdate()

1> CollisionWorld 가져오기

PhysicsWorldSingleton은 Unity Physics 시스템에서 현재의 물리 월드(CollisionWorld)를 관리하는 싱글톤CollisionWorld는 물리적인 충돌을 검사하는 데 사용
2> 마우스 위치에서 레이(Ray) 생성

Input.mousePosition을 사용하여 현재 마우스 커서의 화면 좌표를 가져오기Camera.main.ScreenPointToRay(mousePosition)을 통해 이 화면 좌표를 기준으로 3D 월드에서의Ray를 생성
3> RaycastInput 설정

RaycastInput은 Unity Physics에서 사용되는 구조체로, 물리 엔진이 사용할 레이캐스트 정보를 포함
Start: 레이의 시작점 (unityRay.origin)End: 레이의 끝점 (unityRay.origin + unityRay.direction * MousePickSystem.k_MaxDistance)MousePickSystem.k_MaxDistance는 레이의 최대 탐색 거리로, 너무 멀리까지 체크하지 않도록 제한
Filter:CollisionFilter.Default는 기본적인 충돌 필터로, 충돌 검사를 수행할 물리 레이어를 정의
4> MouseHover 컴포넌트 가져오기

MouseHover는 마우스 오버 이벤트를 관리하는IComponentDataSystemAPI.ManagedAPI.GetSingleton<MouseHover>()을 사용하여 현재 씬에 존재하는MouseHover컴포넌트를 가져옴
5> 레이캐스트 실행

NativeReference<RaycastHit>을 사용하여 레이캐스트 결과(RaycastHit)를 저장할 공간을 확보
WorldRaycastJob을 생성하여 레이캐스트를 수행
CollisionWorld을 사용하여 실제 물리 충돌을 검사합니다.IgnoreTriggers,IgnoreStatic값을 기반으로 충돌 조건을 설정합니다.
rcj.Run();을 실행하여 Job을 즉시 수행한 후 결과를 hit에 저장합니다.
5> 물리 Entity에서 그래픽 Entity 찾기

hit.Entity는 RaycastHit 결과에서 얻은 충돌한 물리 Entity
FindGraphicsEntityFromPhysics(hit.Entity, hit.ColliderKey)를 호출하여 실제 렌더링을 담당하는 Entity를 찾습니다.
- 물리 Entity 와 그래픽 Entity 는 다를 수 있기 때문에 이 과정을 거칩니다.
6> 동일한 엔터티를 계속 가리키면 무시

현재 마우스가 가리키는 graphicsEntity가 이전 프레임과 동일하면 추가 작업을 하지 않고 바로 반환합니다.
7> 이전 및 현재 마우스 오버 Entity 업데이트

PreviousEntity에 이전 프레임에서 마우스가 가리키던 Entity를 저장합니다.CurrentEntity에 이번 프레임에서 새롭게 감지된 Entity를 저장합니다.
8> 이전 Entity의 원래 상태 복구

hasPreviousEntity: 이전 프레임에서 마우스가 가리키던 Entity가 존재하는지 확인
EntityManager.HasComponent<MaterialMeshInfo>(mouseHover.PreviousEntity): 이전 Entity가 MaterialMeshInfo 컴포넌트를 가지고 있는지 확인
이전 Entity의 렌더링 데이터를 원래 값으로 복원:
MaterialMeshInfo: 이전에 저장해 둔 원래의 메쉬 정보를 복원RenderMeshArray: 이전에 저장해 둔 원래의Material배열을 복원
9> 새로운 Entity의 마우스 Hover 효과 적용

현재 프레임에서 감지된 CurrentEntity가 존재하는지 확인
MaterialMeshInfo 및 RenderMeshArray 컴포넌트가 있는 경우, 원래 상태를 저장:
OriginalMeshInfo: 원래MaterialMeshInfo값을 저장OriginalRenderMeshes: 원래RenderMeshArray값을 저장
10> 마우스 Hover 머티리얼 적용

mouseHover.HoverEntity는 마우스 Hover 효과를 표시하는 데 사용할 프리팹(Material 정보 포함)
hoverMeshInfo와 hoverRenderMeshes를 가져와서 현재 마우스가 가리키는 Entity에 적용



