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
는 마우스 오버 이벤트를 관리하는IComponentData
SystemAPI.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에 적용