안녕하세요. 알비언 MetaverseLAB의 David입니다. 또 뵙게 되었네요!
지난 콘텐츠(컴포넌트 기반 개발 #1/4)에 이은 두 번째 이야기입니다. 지난 번에는 CBD의 소개와 장단점에 대해 다뤄보았다면 오늘은 컴포넌트 간 통신하는 방법에 대해 이야기해 보고자 합니다.
궁금하시죠? 그럼 바로 시작할게요!

소통, 소통, 소통! 소통합시다~!
컴포넌트 끼리는 어떻게 통신할까?
시작에 앞서 지난 이야기를 간략하게 리뷰해보겠습니다.
지난 이야기
컴포넌트 기반 개발이라(이하 CBD)함은 부품(컴포넌트)를 만들어 이들의 조합으로 객체를 만들고, 소프트웨어를 변경하는 방법론입니다. 이를 통해 얻을 수 있는 우수성과 불편한 점으로는
우수성
- 부품을 늘림으로 다양한 객체를 만들 수 있다
- 비교적 에디터를 만드는 것이 수월하다
- 컴파일 할 일이 줄어든다
불편한 점
- 컴포넌트 설계 난이도가 높다.
- 컴포넌트를 이용하지 않는 경우보다 느리다
- 보다 많은 테스트가 요구된다
부품(컴포넌트)들간의 의사소통
컴포넌트들은 CBD의 폐쇄된 환경 안에서도 동작하기 때문에 매우 매우 독립적입니다. 그렇다고는 하지만 모든 일이 항상 뜻대로 흘러가지는 않죠. 분명 언젠가는 다른 컴포넌트들과 소통(통신)해야 하는 경우가 발생하는데 이 경우 어떻게 해야 할까요? 바로 이벤트를 통해 소통합니다.
앞으로 소개해드릴 이벤트를 통한 소통을 이용하면 컴포넌트들이 다른 컴포넌트들과 소통할 때 낮은 의존성과 높은 결합도를 거의 해치지 않는 방법으로 소통할 수 있음을 알 수 있습니다.

[ 그림1. 모든 컴포넌트들이 EventContainer(이름은 다를 수 있음)에 자신을 이벤트 구독자로 등록 ]

[ 그림2. ComponentA에서 일이 완료되었다는 이벤트를 EventContainer에 전달.
이 때 몇 가지 매개변수를 함께 넘겨주는 것도 가능 ]

[ 그림3. 이벤트 컨테이너는 구독 등록한 모든 컴포넌트들에게 이벤트가 발생되었음을 알림 ]

[ 그림4. 컴포넌트들은 해당 이벤트에 맞는 작업을 하고 이후 그림2 ~ 4의 일을 반복하며 소프트웨어가 동작하게 됨 ]
이를 간단한 예제로 구현해 보겠습니다.

- Object Manager
- 프로그램에 생성되는 모든 객체를 관리하기 위한 클래스
- Component
- 프로그램에 존재하는 모든 컴포넌트들의 부모클래스
- ConcreteComponentA, ConcreteComponentB, ConcreteComponentC
: 예를 들기 위해 간단한 기능만 구현되어 있음 - 개념적으로는 Entity가 컴포넌트들 가지고 있어야 하지만, id만으로 entity를 가르켜도 문제 없음
ex ) 귀 컴포넌트, 다리 컴포넌트, 털 컴포넌트
- Entity :
- 컴포넌트들이 자신이 어디에 소속되는지 설명하는 클래스, 사실 큰 기능이 없다면 정수형으로 대체 가능
- IEventParam
- 모든 이벤트 매개변수 클래스 혹은 구조체는 이 인터페이스를 상속받아야 함
- EventType
- DefaultEventParam
- 이벤트 매개변수 IEventParam 인터페이스를 상속 받음
[ Source code ]
namespace ComponentWithEvent
{
public class Component
{
public int EntityId { get; set; }
public virtual void OnEvent(EventType eventType, IEventParam param)
{
//code here what todo
}
}
}
namespace ComponentWithEvent
{
public class ConcreteComponentA : Component
{
public override void OnEvent(EventType eventType, IEventParam param)
{
base.OnEvent(eventType, param);
switch (eventType)
{
case EventType.Event1:
Console.WriteLine($"componentName : ConcreteComponentA, entityId : {EntityId} , eventType : {eventType}");
break;
default:
break;
}
}
}
}
namespace ComponentWithEvent
{
public class ConcreteComponentB : Component
{
public override void OnEvent(EventType eventType, IEventParam param)
{
base.OnEvent(eventType, param);
switch (eventType)
{
case EventType.Event2:
Console.WriteLine($"componentName : ConcreteComponentB, entityId : {EntityId} , eventType : {eventType}");
break;
default:
//nothing todo
break;
}
}
}
}
namespace ComponentWithEvent
{
public class ConcreteComponentC : Component
{
public override void OnEvent(EventType eventType, IEventParam param)
{
base.OnEvent(eventType, param);
switch (eventType)
{
case EventType.Event3:
var convertedParam = (DefaultEventParam)param;
//이러한 예시로 이벤트가 무시될 수 있습니다.
if (convertedParam.target != this.EntityId)
return;
Console.WriteLine($"componentName : ConcreteComponentC, entityId : {EntityId} , eventType : {eventType}"); break;
default:
break;
}
}
}
}
namespace ComponentWithEvent
{
public static class ObjectManager
{
static HashSet<Entity> = new HashSet<Entity>();
static List components<Entity> = new List<Entity>();
static int objectCount = int.MinValue;
public static Entity GenerateEntity()
{
var newEntity = new Entity(objectCount++);
entities.Add(newEntity);
return newEntity;
}
public static void AddComponent<T>(Entity entity)
where T : Component, new()
{
T newComponent = new T();
newComponent.EntityId = entity.Id;
components.Add(newComponent);
}
public static void Emit(EventType eventType, IEventParam param)
{
for (int i = 0; i < components.Count; i++)
{
components[i].OnEvent(eventType, param);
}
}
}
}
namespace ComponentWithEvent
{
public interface IEventParam
{
}
}
namespace ComponentWithEvent
{
public struct DefaultEventParam : IEventParam
{
public int target;
public DefaultEventParam(int target)
{
this.target = target;
}
}
}
namespace ComponentWithEvent
{
public class Entity
{
public int Id { get; private set }
public Entity(int id)
{
this.Id = id;
}
}
}
[ Main Program ]
using ComponentWithEvent;
//엔티티 A를 생성
Entity entityA = ObjectManager.GenerateEntity();
//컴포넌트 A와 컴포넌트 B가 엔티티 A를 가르키도록하면서 생성
ObjectManager.AddComponent<ConcreteComponentA>(entityA);
ObjectManager.AddComponent<ConcreteComponentB>(entityA);
//엔티티 B를 생성
Entity entityB = ObjectManager.GenerateEntity();
//컴포넌트 C를 하나 더 생성하고 B를 가르키도록 생성
ObjectManager.AddComponent<ConcreteComponentC>(entityB);
Entity entityC = ObjectManager.GenerateEntity();
ObjectManager.AddComponent<ConcreteComponentA>(entityC);
ObjectManager.AddComponent<ConcreteComponentC>(entityC);
DefaultEventParam eventParam = new DefaultEventParam(entityB.Id);
//이벤트를 만들어냄
ObjectManager.Emit(EventType.Event1, eventParam);
Console.WriteLine("============================================");
eventParam = new DefaultEventParam(entityB.Id);
ObjectManager.Emit(EventType.Event2, eventParam);
Console.WriteLine("============================================");
eventParam = new DefaultEventParam(entityC.Id);
ObjectManager.Emit(EventType.Event3, eventParam);
[ Result ]
componentName : ConcreteComponentA, entityId : -2147483648 , eventType : Event1
componentName : ConcreteComponentA, entityId : -2147483646 , eventType : Event1
============================================
componentName : ConcreteComponentB, entityId : -2147483648 , eventType : Event2
============================================
componentName : ConcreteComponentC, entityId : -2147483646 , eventType : Event3
오늘은 컴포넌트들이 어떻게 소통하는지에 대해 다뤄보았는데요, 이벤트를 통해 각 컴포넌트들의 OnEvent를 호출하기 때문에 컴포넌트 기반 개발을 사용하지 않은 경우보다 대체로 느립니다. 성능을 올리기 위해서 이벤트의 필터링을 다양한 방법으로 할 수 있습니다.
다음 콘텐츠에서는 컴포넌트 기반 개발을 Unity상에서 어떻게 사용하는지 알아보겠습니다. 이번 글 역시 조금이나마 유익하셨길 바랍니다. :)
Ref.
[1] https://ko.wikipedia.org/wiki/컴포넌트_기반_소프트웨어_공학
[2] https://docs.unity3d.com/kr/2018.4/Manual/Components.html
[3] https://docs.unrealengine.com/4.26/en-US/Basics/Components/
[4] https://hrcak.srce.hr/file/69311
[5] https://www.youtube.com/watch?v=cxyG_REKD4Y
[6] http://gameprogrammingpatterns.com/component.html
[7] Game Programming Gems 5 - 정보문화사
안녕하세요. 알비언 MetaverseLAB의 David입니다. 또 뵙게 되었네요!
지난 콘텐츠(컴포넌트 기반 개발 #1/4)에 이은 두 번째 이야기입니다. 지난 번에는 CBD의 소개와 장단점에 대해 다뤄보았다면 오늘은 컴포넌트 간 통신하는 방법에 대해 이야기해 보고자 합니다.
궁금하시죠? 그럼 바로 시작할게요!
소통, 소통, 소통! 소통합시다~!
컴포넌트 끼리는 어떻게 통신할까?
시작에 앞서 지난 이야기를 간략하게 리뷰해보겠습니다.
지난 이야기
컴포넌트 기반 개발이라(이하 CBD)함은 부품(컴포넌트)를 만들어 이들의 조합으로 객체를 만들고, 소프트웨어를 변경하는 방법론입니다. 이를 통해 얻을 수 있는 우수성과 불편한 점으로는
우수성
불편한 점
부품(컴포넌트)들간의 의사소통
컴포넌트들은 CBD의 폐쇄된 환경 안에서도 동작하기 때문에 매우 매우 독립적입니다. 그렇다고는 하지만 모든 일이 항상 뜻대로 흘러가지는 않죠. 분명 언젠가는 다른 컴포넌트들과 소통(통신)해야 하는 경우가 발생하는데 이 경우 어떻게 해야 할까요? 바로 이벤트를 통해 소통합니다.
앞으로 소개해드릴 이벤트를 통한 소통을 이용하면 컴포넌트들이 다른 컴포넌트들과 소통할 때 낮은 의존성과 높은 결합도를 거의 해치지 않는 방법으로 소통할 수 있음을 알 수 있습니다.
[ 그림1. 모든 컴포넌트들이 EventContainer(이름은 다를 수 있음)에 자신을 이벤트 구독자로 등록 ]
[ 그림2. ComponentA에서 일이 완료되었다는 이벤트를 EventContainer에 전달.
이 때 몇 가지 매개변수를 함께 넘겨주는 것도 가능 ]
[ 그림3. 이벤트 컨테이너는 구독 등록한 모든 컴포넌트들에게 이벤트가 발생되었음을 알림 ]
[ 그림4. 컴포넌트들은 해당 이벤트에 맞는 작업을 하고 이후 그림2 ~ 4의 일을 반복하며 소프트웨어가 동작하게 됨 ]
이를 간단한 예제로 구현해 보겠습니다.
: 예를 들기 위해 간단한 기능만 구현되어 있음
ex ) 귀 컴포넌트, 다리 컴포넌트, 털 컴포넌트
[ Source code ]
[ Main Program ]
[ Result ]
오늘은 컴포넌트들이 어떻게 소통하는지에 대해 다뤄보았는데요, 이벤트를 통해 각 컴포넌트들의 OnEvent를 호출하기 때문에 컴포넌트 기반 개발을 사용하지 않은 경우보다 대체로 느립니다. 성능을 올리기 위해서 이벤트의 필터링을 다양한 방법으로 할 수 있습니다.
다음 콘텐츠에서는 컴포넌트 기반 개발을 Unity상에서 어떻게 사용하는지 알아보겠습니다. 이번 글 역시 조금이나마 유익하셨길 바랍니다. :)
Ref.
[1] https://ko.wikipedia.org/wiki/컴포넌트_기반_소프트웨어_공학
[2] https://docs.unity3d.com/kr/2018.4/Manual/Components.html
[3] https://docs.unrealengine.com/4.26/en-US/Basics/Components/
[4] https://hrcak.srce.hr/file/69311
[5] https://www.youtube.com/watch?v=cxyG_REKD4Y
[6] http://gameprogrammingpatterns.com/component.html
[7] Game Programming Gems 5 - 정보문화사