ECS – MouseHoverAuthoring

Physics Scene Basic – Query
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.EntityRaycastHit 결과에서 얻은 충돌한 물리 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가 존재하는지 확인

MaterialMeshInfoRenderMeshArray 컴포넌트가 있는 경우, 원래 상태를 저장:

  • OriginalMeshInfo: 원래 MaterialMeshInfo 값을 저장
  • OriginalRenderMeshes: 원래 RenderMeshArray 값을 저장
10> 마우스 Hover 머티리얼 적용

mouseHover.HoverEntity는 마우스 Hover 효과를 표시하는 데 사용할 프리팹(Material 정보 포함)

hoverMeshInfohoverRenderMeshes를 가져와서 현재 마우스가 가리키는 Entity에 적용

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

위로 스크롤