'ecs 스크립트'에 해당되는 글 1건

  1. 2019.07.17 Unity ECS (Entity Component System) Study - 3. Hybrid ECS 스크립팅

Unity ECS - Hybrid 에 대한 스크립팅 및 사용방법 기록 포스팅입니다.

 

ECS에 대한 설명및 기본세팅은 이전 포스팅으로 - https://zprooo915.tistory.com/66

아래 포스팅은 Unity ECS 샘플 프로젝트의 스크립트를 기준으로 작성하였습니다.

 

이번 포스팅은 아래순서로 진행됩니다.

  1. Hybrid ECS란?
  2. 기본적인 사용방법
  3. 사용 Tip & 오류 및 해결방법

 

1. Hybrid ECS란?

  • 간단하게 기존 Unity의 컴포넌트 방식과 ECS 방식을 융합된 방법입니다.
    • Unity Editor 상에서는 기존 Unity와 동일하게 하이러키와 컴포넌트를 사용하고, 스크립트는 ECS 방식입니다.
  • ECS 방법 자체는 기존 사용자가 바로 사용하기에는 무리가 있을 정도로 에디팅 방식이 많이 바뀌지만, Hybrid 방식으로 둘을 적당히 섞음으로써 ECS 방식을 좀 더 쉽게 접근할 수 있습니다.
  • 장점
    • 진입장벽이 낮다    
    • 기존의 유니티에서 제공하는 시스템을 함께 사용할 수 있다. (ECS만 사용한다면, 새로 만들어야 할 수도 있습니다.) 
  • 단점

 

2. 기본적인 사용방법

 편의상 component - Entity - System 순으로 진행하겠습니다.

 순서는 간단한 설명 후 플레이어블 캐릭터를 예시로 드는 식으로 진행합니다. 

 

Component

 - 하나의 정의에 필요한 데이터 집합

 

  • 아래는 플레이어블 캐릭터의 playerInput 데이터의 집합(컴포넌트) 입니다. 일반 클래스 생성하듯 만듭니다.
public class PlayerInput : MonoBehaviour 
{ 
    public float2 move; 

    public bool jump; 
    public bool seat; 

    public bool attack; 
}

 

  • 그리고 밑은 캐릭터/ 몬스터 누구나 가지고 있을법한 hp에 대한 데이터 집합(컴포넌트) 입니다.
public class Health : MonoBehaviour 
{ 
    public float value;

    public bool isDead;
}

 

  • 위와 같이 하나의 정의에 필요한 데이터 집합들이 component입니다.
    • input에 관련된 요소, Health에 관련된 요소 hp값과 죽었는지 여부입니다.

 

Entity

 - 컴포넌트들의 집합

  •  위의 컴포넌트들을 조합해서 만들어집니다. 기존의 게임오브젝트와 유사하게 생각하시면 됩니다.
  •  위에서 만든 컴포넌트들을 에디터창에서 원하는 오브젝트에 붙이면 됩니다.
    • 각 오브젝트에 필요한 특성과 정의를 부여한다고 생각할 수 있겠네요.
  •  반드시 지켜야 할 것이 ECS의 관리를 받는 오브젝트를 만들려면 Game Object Entity라는 클래스도 오브젝트에 추가해야합니다. (이것을 안하면 System에서 아예 인지를 하지 못합니다.)
  • 아래는 플레이어블 캐릭터의 인스펙터 창입니다.

  • 아래는 체력만 가진 몬스터의 인스펙터 창입니다. ( PlayerInput 빼고는 위와 동일합니다.)

 

 

 System

 - Entity들의 행동로직 

  • 데이터들을 가공하는 곳으로 일반 객체지향의 method와 비슷합니다.
  • 시스템은 방식이 두가지가 있습니다.
    • 두 방식이 어떨 때 좋을지는 정확히는 잘 모르겠네요.
    • 차이점이라면 두번째는 개체수를 얻을 수 있다는 점과 개인적으로 사용했을 때는 여러 엔티티를 가져와 로직을 돌려야 할 경우 두번째 방식이 더 편하고, 하나만 사용할 경우에는 첫번째가 편하더군요.
  • 기본 템플릿

1. 

using UnityEngine; 
using Unity.Entities; 

public class ClassName : ComponentSystem 
{ 
    public struct StructName
    { 
        public ComponentClass name; 
         
    } 
    protected override void OnUpdate() 
    { 
        foreach(var entity in GetEntities<StructName>()) 
        {

            // 로직 진행
            // ex ) entity.name.property = 0;
        } 
    } 
}

 

2.

using Unity.Entities; 
using UnityEngine; 

public class ClassName : ComponentSystem 
{ 
    public struct StructName
    { 
        public readonly int Length; 
        public ComponentArray<ComponentClass > name; 
    } 
    [Inject] 
    private StructName m_data; 

    protected override void OnUpdate() 
    {         
        for(int i = 0; i < m_data.Length; i++) 
        {

            // 로직 진행
            // ex ) m_data.name[i].property = 0; 
             
        } 
    } 
}

 

  • 사용예시

  • 아래는 인풋제어 컴포넌트를 가진 엔티티들을 불러와 키맵핑을 해주는 시스템입니다.
    • 다른 컴포넌트를 가지고 있든말든 인풋제어만 있으면 다 로직이 돕니다.
using UnityEngine; 
using Unity.Entities; 

//사용자의 입력을 input component에 배정하는 시스템 
// 이동 점프 앉기 공격 등 키입력 맵핑 
public class PlayerInputSystem : ComponentSystem 
{ 
    public struct PlayerData 
    { 
        public PlayerInput input; 
         
    } 
    protected override void OnUpdate() 
    { 
        foreach(var entity in GetEntities<PlayerData>()) 
        { 
            entity.input.move.x = Input.GetAxis("Horizontal"); 

            entity.input.seat = false; 
            entity.input.jump = false; 

            if (Input.GetKey(KeyCode.DownArrow)) entity.input.seat = true; 
            if (Input.GetKey(KeyCode.UpArrow)) entity.input.jump = true; 

        } 
         
    } 
}

 

 

  • 아래는 플레이어블 캐릭터의 컴포넌트들을 가진 엔티티들을 불러와 이동시켜주는 시스템입니다.
    • 마찬가지로 PlayerData에 있는 컴포넌트를 가진 모든 엔티티를 불러옵니다.
using Unity.Entities; 
using UnityEngine; 

// 입력된 input에 맞춰 캐릭터 이동  
public class PlayerMoveSystem : ComponentSystem 
{ 
    public struct PlayerData 
    { 
        public readonly int Length; 
        public ComponentArray<PlayerInput> input; 
        public ComponentArray<Health> health; 
        public ComponentArray<Rigibody2D> rigid; 
    } 
    [Inject] 
    private PlayerData m_data; 

    protected override void OnUpdate() 
    { 
        // 병렬로 전체 캐릭터들이 모두 별도로 이동하고, 나중에 한꺼번에 적용 
        float dt = Time.deltaTime; 
        for(int i = 0; i < m_data.Length; i++) 
        { 
            m_data.rigid[i].AddForce(new Vector2(m_data.input[i].move.x * 4, 0)); 
             
        } 
    } 
}

 

  • 아래는 Health를 가진 모든 오브젝트에 대해 죽음 판별하는 시스템입니다.
    • 캐릭터와 몬스터 모두 Health를 가졌으니 모두를 대상으로 돌겠죠
using UnityEngine; 
using Unity.Entities; 

public class HealthCheckSystem : ComponentSystem 
{ 
    public struct PlayerData 
    { 
        public Health health; 
         
    } 
    protected override void OnUpdate() 
    { 
        foreach(var entity in GetEntities<PlayerData>()) 
        { 
            if(entity.health.value <=0) entity.health.isDead = true;

        } 
         
    } 
} 

 

  • 그래서 필요 기능별로 컴포넌트들을 구분지어야 나중에 시스템을 돌릴때 편해지고 직관적이게 됩니다. 오브젝트를 사용할 때도 컴포넌트만으로 제어하기 편합니다.
    • 예를들어 이 캐릭터는 무적인 특성이 있다 했을 때, 예외처리를 하는게 아니라 단지 그 캐릭터에게 Health 컴포넌트만 제거하면 Health에 관련된 모든 로직을 무시할 수 있게되죠.
    • 마찬가지로, 스킬이나 특성들도 컴포넌트만으로 제어할 수 있겠죠.

 

 

3. 사용 Tip & 오류 및 해결방법

  • 시스템 로직 도는 순서 변경하는 방법

    •  기능별로 시스템이 돌기 때문에 간혹 순서상에 문제가 발생할 수도 있습니다.
      •  물론 이런경우를 발생 안시키는게 베스트이긴 하지만, 부득이할 경우 로직 도는 순서를 변경 시킬 수 있습니다.
      • (디폴트로는 생성 순으로 진행되는 것으로 압니다.)
    • 참고 사이트 - https://docs.unity3d.com/Packages/com.unity.entities@0.0/manual/system_update_order.html
    • 방법 
// 타겟 시스템이 SystemClass 이후에 실행되도록 설정
// 같은 시스템 그룹 내에서 동작
[UpdateAfter(typeof(SystemClass))]

// 타겟 시스템이 SystemClass 이전에 실행되도록 설정
// 같은 시스템 그룹 내에서 동작.
[UpdateBefore(typeof(SystemClass))]

// 타겟 시스템을 SystemGroup에 귀속시킴.
// 디폴트로 개발자가 만든 System은 모두 SimulationSystemGroup에 속함.
[UpdateInGroup(typeof(SystemGroup))]


// 사용 예시
// PlayerInputSystem 실행 이전에 TestSystem (타켓시스템)을 실행하도록 설정.
[UpdateBefore(typeof(PlayerInputSystem))]
public class TestSystem : ComponentSystem
{
    public struct PlayerData
    {
        public PlayerInput input;
    }
    protected override void OnUpdate()
    {
        foreach (var entity in GetEntities<PlayerData>())
        {
        }
    }
}

 

  • 여러 엔티티를 하나의 시스템에서 써야할 때

    • 그냥 두개 inject 해오면 된다.
    public struct PlayerData 
    { 
        public readonly int Length; 
        public ComponentArray<PlayerInput> input; 
        public ComponentArray<Transform> transform; 

    } 
    [Inject] 
    private PlayerData p_data; 

    public struct MinimapData 
    { 
        public readonly int Length; 
        public ComponentArray<Minimap> minimap; 

    } 
    [Inject] 
    private MinimapData m_data;

 

  • Tag처럼 쓰기

    • Tag도 하나의 컴포넌트로써 사용하면 된다.
      • ex) Component로 Human클래스를 만들고 아무 내용없이 오브젝트에 붙여만 놓으면, 시스템에서 Human으로 걸러낼 수 있죠.

 

 

 

 

 

 

 

 

Posted by 검은거북
이전버튼 1 이전버튼

블로그 이미지
프로그래밍 공부 요약 및 공부하며 발생한 궁금증과 해결과정을 포스팅합니다.
검은거북

공지사항

Yesterday
Today
Total

달력

 « |  » 2025.1
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31

최근에 올라온 글

최근에 달린 댓글

글 보관함