Unity DOTS/따라하며 배우기

UNITY DOTS - State Machine(상태 머신) 만들기 #6 색상 변경

개양반 2022. 8. 8.

오늘 알아볼 내용

Guard의 상태(순찰, 대기, 추격)에 따라 Material이 변경되는 방법에 대해 다룹니다.


Component

1. GuardAuthoring.cs 수정

색상이 변경되어야 할때 추가할 IsInTransitionTag를 GuardAuthoring.cs에 추가합니다.

public struct IsInTransitionTag : IComponentData
{ }

 

2. MaterialSingletonAuthoring.cs 작성

MaterialSingletonAuthoring.cs 파일을 만들고 아래의 코드를 작성합니다.

using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;


class StateTransitionMaterials : IComponentData
{
    public Material m_IdleMaterial;
    public Material m_PatrollingMaterial;
    public Material m_ChasingMaterial;
}

#코드 설명

Material은 관리되는 데이터이므로 클래스타입의 인스턴스로 만들어야 합니다. IComponentData에 대한 메뉴얼은 아래의 링크를 참고합니다.

[Unity DOTS/Dots Custom Manual] - UNITY DOTS - ICompoentData 에 대해 알아보자.

 


Authoring

1. MaterialSingletonAuthoring.cs 수정

위에서 만든 MaterialSingletonAuthoring.cs에 아래의 코드를 추가합니다.

[DisallowMultipleComponent]
public class MaterialSingletonAuthoring : MonoBehaviour, IConvertGameObjectToEntity
{
    public Material m_IdleMaterial;
    public Material m_PatrollingMaterial;
    public Material m_ChasingMaterial;


    public void Convert(Entity entity, EntityManager dstManager, GameObjectConversionSystem conversionSystem)
    {
        dstManager.AddComponentData(entity, new StateTransitionMaterials
        {
            m_ChasingMaterial = m_ChasingMaterial,
            m_IdleMaterial = m_IdleMaterial,
            m_PatrollingMaterial = m_PatrollingMaterial
        });
    }
}

 


GuardAIUtility

상태가 변경될때마다 IsInTransitionTag를 추가하여 Guard의 Material이 변경되어야 한다고 알릴 겁니다. GuardAIUtility.cs에 아래의 코드를 수정합니다.

    public static void TransitionToIdle(EntityCommandBuffer.ParallelWriter ecb, Entity e, int index)
    {
        ecb.AddComponent(index, e, new IdleTimer { Value = 0.0f });
        ecb.AddComponent<IsInTransitionTag>(index, e);
    }
    
    public static void TransitionToPatrolling(EntityCommandBuffer.ParallelWriter ecb, Entity e, int index, float3 waypointPosition)
    {
        ecb.AddComponent(index, e, new TargetPosition { Value = waypointPosition });
        ecb.AddComponent<IsInTransitionTag>(index, e);
    }
    
    public static void TransitionToChasing(EntityCommandBuffer.ParallelWriter ecb, Entity e, int index, float3 playerPosition)
    {
        ecb.AddComponent<IsChasingTag>(index, e);
        ecb.AddComponent(index, e, new TargetPosition { Value = playerPosition });
        ecb.AddComponent<IsInTransitionTag>(index, e);
    }

#코드 설명

상태가 변경될때마다 IsInTransitionTag가 추가되는 코드가 추가되었습니다.


System

1. StateTransitionVisualizationSystem

StateTransitionVisualizationSystem.cs를 만들고 아래의 코드를 작성합니다.

using Unity.Entities;
using Unity.Jobs;
using Unity.Rendering;


[UpdateInGroup(typeof(PresentationSystemGroup))]
[UpdateBefore(typeof(HybridRendererSystem))]
public partial class StateTransitionVisualizationSystem : SystemBase
{
    private EntityQuery m_StateTransitionMaterialSingletonQuery;

    protected override void OnCreate()
    {
        m_StateTransitionMaterialSingletonQuery = GetEntityQuery(typeof(StateTransitionMaterials));
    }


    protected override void OnUpdate()
    {
        var materials = EntityManager.GetComponentData<StateTransitionMaterials>(
            m_StateTransitionMaterialSingletonQuery.GetSingletonEntity());

        Entities
            .WithAll<IsInTransitionTag>()
            .ForEach((Entity e) => {

                var meshRenderer = EntityManager.GetSharedComponentData<RenderMesh>(e);

                if (HasComponent<IdleTimer>(e))
                {
                    meshRenderer.material = materials.m_IdleMaterial;
                }

                else if (HasComponent<IsChasingTag>(e))
                {
                    meshRenderer.material = materials.m_ChasingMaterial;
                }

                else
                {
                    meshRenderer.material = materials.m_PatrollingMaterial;
                }

                EntityManager.SetSharedComponentData(e, meshRenderer);
                EntityManager.RemoveComponent<IsInTransitionTag>(e);

            }).WithStructuralChanges().Run();
    }
}

#코드 설명

1-1. [UpdateInGroup(typeof(PresentationSystemGroup))]

UpdateInGroup는 매개변수 안의 시스템그룹에 해당 시스템이 포함되어야 한다는 의미입니다. PresentationSystemGroup는 랜더링 데이터가 렌더러에 전달되고 변경되는 그룹입니다.

 

1-2. [UpdateBefore(typeof(HybridRendererSystem))]

이것에 대한 정보가 부족하여 잘은 모릅니다. 시스템에서 Material을 변경할때는 해당 시스템이 [UpdateInGroup(typeof(PresentationSystemGroup))]에 속해야 하고  [UpdateBefore(typeof(HybridRendererSystem))] 전에 동작해야 한다 정도로 외워두고 있습니다.

 

1-3. var materials = EntityManager.GetComponentData<StateTransitionMaterials>(m_StateTransitionMaterialSingletonQuery.GetSingletonEntity());

m_StateTransitionMaterialSingletonQuery로 조회된 Entity 중에 GetSingletonEntity(0번째 Entity)의  StateTransitionMaterials를 var  materials로 전달한다는 의미입니다. 만약 GetSingletonEntity로 호출하려는 Entity가 두개 이상이면 InvalidOperationException: GetSingletonEntity() requires that exactly one entity exist that match this query, but there are 2. 가 발생합니다.

 

1-4. var meshRenderer = EntityManager.GetSharedComponentData<RenderMesh>(e);

GetSharedComponentData는 IComponentData와 다르게  EntityManager가 동일한 SharedComponentData 값을 가진 모든 엔티티를 동일한 청크에 배치합니다. 공유 구성 요소를 사용하면 시스템이 같은 엔티티를 함께 처리할 수 있습니다.

 

1-5. WithStructuralChanges().Run();

Material은 관리되는 데이터타입으로 클래스 인스턴스입니다. 클래스 인스턴스는 멀티스레드에서 동작하지 않으므로 메인스레드에서 동작하도록 만들었습니다. 


Material

Materal 3개(Red, Blue, Green)를 만듭니다. Shader는 Universal Render Pipeline 이어야 합니다.


GameObject

빈게임오브젝트를 만들고 아래와 같이 설정합니다.

 

유니티 재생버튼을 눌러 테스트 해보세요. 

 

댓글

💲 추천 글