블루프린트 인터페이스를 통한 통신
인터페이스 메시지로 서로 다른 블루프린트에 공통 기능을 요청하고 직접 참조·디스패처와의 차이를 비교합니다.
이전 절에서 이벤트 디스패처로 느슨한 결합을 구현했다면, 이번에는 인터페이스 방식으로 통신 구조를 확장해 봅니다.
여기서는 블루프린트 인터페이스(Blueprint Interface)를 통신에 적용하는 방법과, 직접 참조/이벤트 디스패처 대비 장단점을 비교합니다.
특히 다형성(Polymorphism) 기반 설계에서 인터페이스가 왜 강력한지에 집중합니다.
블루프린트 인터페이스를 통한 통신이란?
블루프린트 인터페이스를 이용한 통신은 특정 기능의 '계약'을 기반으로 합니다.
인터페이스는 어떤 클래스들이 구현해야 할 함수의 선언(Declaration) 만을 포함하며, 실제 구현(Definition)은 각 클래스에 맡깁니다.
즉, 나는 이런 기능을 제공할 수 있다는 약속을 하는 것이죠.
다른 블루프린트에서 이 인터페이스의 기능을 호출할 때는, 호출 대상이 실제로 이 인터페이스를 구현했는지 여부만 확인합니다.
구현했다면, 그 클래스가 어떻게 구현했는지와 상관없이 해당 인터페이스 함수를 호출할 수 있습니다.
이는 마치 어떤 스마트폰이든 충전기 규격만 맞으면 충전할 수 있는 것과 같습니다.
충전기는 어떤 브랜드의 폰인지 알 필요 없이, 충전 규격만 지키면 됩니다.
인터페이스 통신 과정
발사체는 대상의 구체 클래스 대신 BPI_Damageable 계약만 확인하고 같은 메시지를 보낸다.
- Event Hit
Other Actor 를 메시지 대상 후보로 확보
- Does Implement Interface?
BPI_Damageable 계약만 검사
- ApplyDamage (Message)
True일 때 Target과 DamageAmount 전달
블루프린트 인터페이스를 활용한 통신은 크게 세 단계로 이루어집니다.
블루프린트 인터페이스 정의: 통신에 사용될 함수(기능)를 인터페이스에 선언합니다. (3장 4절 복습)
- 콘텐츠 브라우저에서 마우스 우클릭 >
블루프린트(Blueprints)>블루프린트 인터페이스(Blueprint Interface)선택. (예:BPI_Damageable) - 인터페이스 에디터에서 함수를 추가하고 입출력 핀을 정의합니다. (예:
ApplyDamage함수에DamageAmount(Float) 입력 핀 추가) - 주의: 인터페이스 함수에는 로직을 구현하지 않습니다.
클래스에서 인터페이스 구현 및 함수 정의: 해당 기능을 제공할 블루프린트 클래스(예: BP_Enemy, BP_PlayerCharacter)에서 인터페이스를 구현하고, 인터페이스 함수를 정의합니다.
- 인터페이스를 구현할 액터 블루프린트(예:
BP_Enemy)를 엽니다. - 클래스 세팅(Class Settings)을 클릭합니다.
- 디테일 패널의
Implemented Interfaces섹션에서BPI_Damageable을 추가합니다. - 블루프린트를 컴파일하면 좌측 함수(Functions) 패널의
인터페이스섹션에ApplyDamage함수가 나타납니다. ApplyDamage함수 위에서 마우스 우클릭 >이벤트 구현(Implement Event)을 선택합니다.- 이벤트 그래프에 생성된
Event ApplyDamage노드 뒤에 실제 대미지 처리 로직을 구현합니다. (예:CurrentHealth변수에서DamageAmount를 빼는 로직) BP_PlayerCharacter에도 동일하게BPI_Damageable인터페이스를 구현하고Event ApplyDamage로직을 정의합니다.
인터페이스 메시지 호출: 다른 블루프린트(예: BP_Projectile - 발사체)에서 이 인터페이스 함수를 메시지 형태로 호출합니다.
BP_Projectile블루프린트 에디터를 엽니다.Projectile이 다른 액터와 충돌했을 때 대미지를 주고 싶다고 가정합니다.Event Hit노드 뒤에Other Actor출력 핀에서 드래그하여Does Implement Interface노드를 검색하여 연결합니다.- 이 노드는 연결된 액터 레퍼런스가 특정 인터페이스를 구현했는지 불리언 값을 반환합니다.
Interface핀에BPI_Damageable을 선택합니다.
- 이 노드는 연결된 액터 레퍼런스가 특정 인터페이스를 구현했는지 불리언 값을 반환합니다.
Does Implement Interface의Return Value출력 핀을Branch노드의Condition핀에 연결합니다.- 이
Branch노드는 충돌한 액터가BPI_Damageable인터페이스를 구현했는지 여부에 따라 실행 흐름을 나눕니다.
- 이
Branch노드의True출력 핀에서 드래그하여ApplyDamage를 검색합니다. 이때,ApplyDamage (Message)노드를 선택합니다.Message노드는 대상이 인터페이스를 구현했는지 자동으로 확인하고, 구현했을 경우 해당 인터페이스 함수를 호출해줍니다.Does Implement Interface와Branch노드를 사용하지 않고 바로Message노드를 연결해도 되지만,Does Implement Interface를 통해 명시적으로 확인하는 것이 안정성 확보에 도움이 됩니다.
ApplyDamage (Message)노드의Target핀에는Event Hit의Other Actor핀을 연결합니다.DamageAmount입력 핀에는 발사체가 줄 대미지 값을 입력합니다. (예:20.0)- 블루프린트를 컴파일하고 저장합니다.
이 흐름을 실제 호출 관점에서 보면, 발사체는 하나의 메시지만 보내지만 대상 클래스별 구현이 각각 실행됩니다.
발사체는 대상의 클래스 이름을 모르고, BPI_Damageable 계약을 구현했는지만 확인한 뒤 같은 Message를 보낸다.
- CallerBP_Projectile
Caller Event Hit에서 Other Actor를 얻고 인터페이스 구현 여부를 검사한다.
- Does Implement Interface
검사 Other Actor가 BPI_Damageable을 구현했는지 확인한다.
- Branch True
분기 구현한 대상만 다음 호출 경로로 보낸다.
- ApplyDamage Message
호출 같은 메시지가 실제 대상의 구현으로 전달된다.
- Polymorphism각 클래스 구현 실행
Polymorphism 호출 코드는 하나지만 실행되는 로직은 대상 클래스마다 달라진다.
- BP_Enemy
Implementer 체력에서 대미지를 빼고 사망 상태를 검사한다. 계약 BPI_Damageable 구현 실행 Enemy 전용 피격 로직
- BP_PlayerCharacter
Implementer 체력을 줄이고 UI 갱신, 무적 시간 같은 플레이어 규칙을 적용한다. 계약 BPI_Damageable 구현 실행 Player 전용 피격 로직
- BP_DestructibleBox
Implementer 내구도를 줄이고 0이 되면 파괴 이펙트와 아이템 드롭을 실행한다. 계약 BPI_Damageable 구현 실행 Object 전용 파괴 로직
인터페이스 통신의 장점
-
강력한 다형성(Polymorphism):
ApplyDamage (Message)노드는BP_Enemy,BP_PlayerCharacter모두에 동일하게 동작합니다. 발사체 블루프린트는 대상이 적인지 플레이어인지 알 필요 없이,BPI_Damageable을 구현한 객체인지 여부만 확인하면 됩니다.이 구조는 확장성을 크게 높여 줍니다.
-
느슨한 결합(Loose Coupling): 호출하는 쪽(발사체)은 호출되는 쪽(적, 플레이어)의 구체적인 클래스 타입을 몰라도 됩니다. 오직 인터페이스만 알면 됩니다. 이는 나중에 새로운
BPI_Damageable을 구현하는 오브젝트(예:BP_DestructibleBox)가 추가되어도 발사체 블루프린트를 전혀 수정할 필요가 없다는 것을 의미합니다. -
유지보수 용이성: 특정 기능을 변경하고 싶다면, 해당 기능을 구현한 클래스만 수정하면 됩니다. 인터페이스 자체나 인터페이스를 호출하는 다른 블루프린트에는 영향을 주지 않습니다.
-
명확한 계약: 인터페이스는 이 기능을 구현하는 모든 클래스는 이 함수를 가져야 한다는 명확한 계약을 제시합니다. 이는 팀 작업 시 각 클래스의 책임과 기능을 명확히 정의하는 데 큰 도움이 됩니다.
직접 참조, 이벤트 디스패처, 인터페이스 비교
| 특징 | 직접 참조 (Direct Reference) | 이벤트 디스패처 (Event Dispatcher) | 블루프린트 인터페이스 (Blueprint Interface) |
|---|---|---|---|
| 결합도 | 강한 결합 (Tight Coupling) | 느슨한 결합 (Loose Coupling) | 느슨한 결합 (Loose Coupling) |
| 통신 방향 | 일대일 (정확한 대상 지정) | 일대다 (여러 대상에게 알림) | 다대다 (다양한 클래스에서 구현 가능, 다양한 클래스에서 호출 가능) |
| 주요 용도 | 특정 액터 인스턴스에 직접 접근, 변수 값 확인 | 특정 사건 발생 시 여러 대상에게 알림 | 특정 능력을 가진 객체에게 공통 명령 전달, 다형성 구현 |
| 안정성 | Access None 발생 가능성 높음 (유효성 검사 필수) | 바인드된 대상이 없으면 아무 일도 일어나지 않음 | 대상이 인터페이스를 구현했는지 확인 필요 |
| 복잡도 | 가장 간단 | 중간 | 중간 ~ 높음 (객체 지향 개념 이해 필요) |
직접 참조, 이벤트 디스패처, 인터페이스는 모두 액터 사이를 잇지만 대상 수와 결합도가 다르다.
- Direct Reference직접 참조
Direct Reference 특정 액터 인스턴스를 알고 있을 때 변수나 함수를 바로 호출한다. 방향 일대일 결합 강함 통신 결합도 Access None 검사
- Event Dispatcher이벤트 디스패처
Event Dispatcher 한 객체의 사건을 바인딩된 여러 구독자에게 알린다. 방향 일대다 결합 느슨함 통신 결합도 바인딩 상태 확인
- Blueprint Interface인터페이스
Blueprint Interface 대상의 구체 클래스 대신 같은 기능 계약을 구현했는지 본다. 방향 다대다 결합 느슨함 통신 결합도 구현 여부 확인
- BPI 정의
함수 이름과 입력 핀만 선언하고 로직은 넣지 않는다.
- 클래스 구현
각 클래스가 계약을 추가하고 자기 방식의 로직을 둔다.
- Message 호출
발신자는 구현 여부를 확인한 뒤 같은 메시지를 보낸다.
인터페이스는 특히 다형성을 기반으로 하는 설계에 가장 적합한 통신 기법입니다.
게임 내에 다양한 종류의 오브젝트들이 공통적인 특정 행동(예: 상호작용, 대미지 받기, 활성화/비활성화)을 해야 할 때, 인터페이스를 사용하면 매우 유연하고 확장 가능한 시스템을 구축할 수 있습니다.
마지막으로 직접 참조, 디스패처, 인터페이스를 어떤 기준으로 선택할지 한 번에 비교해 보겠습니다.
대상이 명확하면 직접 참조, 사건을 알릴 때는 디스패처, 능력을 호출할 때는 인터페이스가 기준점입니다.
- 결합직접 참조
하나의 특정 인스턴스를 정확히 알고 있을 때 빠르게 호출합니다. 결합 범위
- 결합이벤트 디스패처
발생한 사건을 여러 구독자에게 알릴 때 자연스럽습니다. 결합 범위
- 결합인터페이스
클래스가 달라도 같은 능력을 구현했다면 메시지로 호출합니다. 결합 확장
이번 절에서는 블루프린트 인터페이스를 통한 통신 방법을 심도 있게 다루었습니다.
직접 참조, 이벤트 디스패처, 그리고 인터페이스는 각각 다른 상황과 요구사항에 적합한 통신 기법이므로, 각 방법의 장단점을 이해하고 적절하게 선택하여 사용하는 것이 중요합니다.
인터페이스를 설계할 때는 호출자가 클래스보다 능력을 바라보는지부터 확인합니다.
발사체는 적, 상자, 플레이어의 구체 타입을 몰라도 BPI_Damageable 계약만 확인하고 ApplyDamage 메시지를 보낼 수 있습니다.
- 선언은 하나, 구현은 각자
계약 인터페이스에는 함수 형태만 두고 실제 반응은 구현한 블루프린트가 맡습니다. Define 함수 이름과 입력 핀을 정하되 내부 로직은 넣지 않습니다. Implement BP_Enemy, BP_Box처럼 반응할 클래스가 인터페이스를 구현합니다. 점검 Does Implement Interface로 메시지를 받을 수 있는 대상인지 분기합니다. Message ApplyDamage Message를 보내면 각 클래스의 구현 로직이 실행됩니다.
- 선언은 하나, 구현은 각자
계약 인터페이스에는 함수 형태만 두고 실제 반응은 구현한 블루프린트가 맡습니다.
- BPI_Damageable 선언
Define 함수 이름과 입력 핀을 정하되 내부 로직은 넣지 않습니다.
- 대상 클래스에 추가
Implement BP_Enemy, BP_Box처럼 반응할 클래스가 인터페이스를 구현합니다.
- 구현 여부 확인
점검 Does Implement Interface로 메시지를 받을 수 있는 대상인지 분기합니다.
- 같은 호출, 다른 실행
Message ApplyDamage Message를 보내면 각 클래스의 구현 로직이 실행됩니다.
세 가지 통신 방식을 비교해 선택하는 기준을 추가합니다.
직접 참조, 디스패처, 인터페이스는 모두 통신이지만 결합 방향과 수신자 수가 다르다.
- Direct
대상 하나를 알고 호출 레벨의 특정 문, 현재 플레이어
- Dispatcher
한 발행자가 여러 청취자에게 알림 문 열림 알림, 체력 변화 이벤트
- Interface
대상의 구체 클래스를 숨김 상호작용 가능한 문, 레버, NPC
| 선택 기준 | 의미 |
|---|---|
| 대상 하나 | 직접 참조가 가장 단순하지만 IsValid가 필요하다. |
| 청취자 여러 명 | 디스패처로 발행자와 반응자를 분리한다. |
| 클래스가 자주 바뀜 | 인터페이스 메시지로 구현 위치를 숨긴다. |
블루프린트 통신은 한 가지 방식으로 밀어붙이지 말고 대상 수, 호출 방향, 결합도를 기준으로 직접 참조·디스패처·인터페이스를 고릅니다.
Cast, 직접 참조, 디스패처, 인터페이스는 같은 문제를 풀지만 호출자가 부담하는 정보가 다르다.
| 질문 | 판정 |
|---|---|
| 정확한 클래스가 필요하다 | Cast To. 성공/실패 경로를 함께 둔다. |
| 이미 대상 변수를 갖고 있다 | Direct Reference. 할당 지점과 유효성 검사를 표시한다. |
| 여러 객체가 들어야 한다 | Event Dispatcher. 발행자와 구독자를 분리한다. |
| 행동 이름만 맞으면 된다 | Blueprint Interface. 구체 클래스 의존을 줄인다. |