본문으로 건너뛰기

안동민 개발노트

본문 시작

블루프린트 인터페이스를 통한 통신

인터페이스 메시지로 서로 다른 블루프린트에 공통 기능을 요청하고 직접 참조·디스패처와의 차이를 비교합니다.

이전 절에서 이벤트 디스패처로 느슨한 결합을 구현했다면, 이번에는 인터페이스 방식으로 통신 구조를 확장해 봅니다.

여기서는 블루프린트 인터페이스(Blueprint Interface)를 통신에 적용하는 방법과, 직접 참조/이벤트 디스패처 대비 장단점을 비교합니다.

특히 다형성(Polymorphism) 기반 설계에서 인터페이스가 왜 강력한지에 집중합니다.


블루프린트 인터페이스를 통한 통신이란?

블루프린트 인터페이스를 이용한 통신은 특정 기능의 '계약'을 기반으로 합니다.

인터페이스는 어떤 클래스들이 구현해야 할 함수의 선언(Declaration) 만을 포함하며, 실제 구현(Definition)은 각 클래스에 맡깁니다.

즉, 나는 이런 기능을 제공할 수 있다는 약속을 하는 것이죠.

다른 블루프린트에서 이 인터페이스의 기능을 호출할 때는, 호출 대상이 실제로 이 인터페이스를 구현했는지 여부만 확인합니다.

구현했다면, 그 클래스가 어떻게 구현했는지와 상관없이 해당 인터페이스 함수를 호출할 수 있습니다.

이는 마치 어떤 스마트폰이든 충전기 규격만 맞으면 충전할 수 있는 것과 같습니다.

충전기는 어떤 브랜드의 폰인지 알 필요 없이, 충전 규격만 지키면 됩니다.


인터페이스 통신 과정

호출자는 클래스가 아니라 구현한 능력을 향한다

발사체는 대상의 구체 클래스 대신 BPI_Damageable 계약만 확인하고 같은 메시지를 보낸다.

  1. Event Hit

    Other Actor 를 메시지 대상 후보로 확보

  2. Does Implement Interface?

    BPI_Damageable 계약만 검사

  3. 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 InterfaceReturn Value 출력 핀을 Branch 노드의 Condition 핀에 연결합니다.
    • Branch 노드는 충돌한 액터가 BPI_Damageable 인터페이스를 구현했는지 여부에 따라 실행 흐름을 나눕니다.
  • Branch 노드의 True 출력 핀에서 드래그하여 ApplyDamage를 검색합니다. 이때, ApplyDamage (Message) 노드를 선택합니다.
    • Message 노드는 대상이 인터페이스를 구현했는지 자동으로 확인하고, 구현했을 경우 해당 인터페이스 함수를 호출해줍니다. Does Implement InterfaceBranch 노드를 사용하지 않고 바로 Message 노드를 연결해도 되지만, Does Implement Interface를 통해 명시적으로 확인하는 것이 안정성 확보에 도움이 됩니다.
  • ApplyDamage (Message) 노드의 Target 핀에는 Event HitOther Actor 핀을 연결합니다.
  • DamageAmount 입력 핀에는 발사체가 줄 대미지 값을 입력합니다. (예: 20.0)
  • 블루프린트를 컴파일하고 저장합니다.

이 흐름을 실제 호출 관점에서 보면, 발사체는 하나의 메시지만 보내지만 대상 클래스별 구현이 각각 실행됩니다.

계약 하나로 여러 클래스의 구현을 호출한다

발사체는 대상의 클래스 이름을 모르고, BPI_Damageable 계약을 구현했는지만 확인한 뒤 같은 Message를 보낸다.

  1. Caller
    BP_Projectile

    Caller Event Hit에서 Other Actor를 얻고 인터페이스 구현 여부를 검사한다.

  2. Does Implement Interface

    검사 Other Actor가 BPI_Damageable을 구현했는지 확인한다.

  3. Branch True

    분기 구현한 대상만 다음 호출 경로로 보낸다.

  4. ApplyDamage Message

    호출 같은 메시지가 실제 대상의 구현으로 전달된다.

  5. Polymorphism
    각 클래스 구현 실행

    Polymorphism 호출 코드는 하나지만 실행되는 로직은 대상 클래스마다 달라진다.

  6. BP_Enemy

    Implementer 체력에서 대미지를 빼고 사망 상태를 검사한다. 계약 BPI_Damageable 구현 실행 Enemy 전용 피격 로직

  7. BP_PlayerCharacter

    Implementer 체력을 줄이고 UI 갱신, 무적 시간 같은 플레이어 규칙을 적용한다. 계약 BPI_Damageable 구현 실행 Player 전용 피격 로직

  8. 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 발생 가능성 높음 (유효성 검사 필수)바인드된 대상이 없으면 아무 일도 일어나지 않음대상이 인터페이스를 구현했는지 확인 필요
복잡도가장 간단중간중간 ~ 높음 (객체 지향 개념 이해 필요)
통신 방식은 대상 수와 결합도로 선택한다

직접 참조, 이벤트 디스패처, 인터페이스는 모두 액터 사이를 잇지만 대상 수와 결합도가 다르다.

  1. Direct Reference
    직접 참조

    Direct Reference 특정 액터 인스턴스를 알고 있을 때 변수나 함수를 바로 호출한다. 방향 일대일 결합 강함 통신 결합도 Access None 검사

  2. Event Dispatcher
    이벤트 디스패처

    Event Dispatcher 한 객체의 사건을 바인딩된 여러 구독자에게 알린다. 방향 일대다 결합 느슨함 통신 결합도 바인딩 상태 확인

  3. Blueprint Interface
    인터페이스

    Blueprint Interface 대상의 구체 클래스 대신 같은 기능 계약을 구현했는지 본다. 방향 다대다 결합 느슨함 통신 결합도 구현 여부 확인

  4. BPI 정의

    함수 이름과 입력 핀만 선언하고 로직은 넣지 않는다.

  5. 클래스 구현

    각 클래스가 계약을 추가하고 자기 방식의 로직을 둔다.

  6. Message 호출

    발신자는 구현 여부를 확인한 뒤 같은 메시지를 보낸다.

인터페이스는 특히 다형성을 기반으로 하는 설계에 가장 적합한 통신 기법입니다.

게임 내에 다양한 종류의 오브젝트들이 공통적인 특정 행동(예: 상호작용, 대미지 받기, 활성화/비활성화)을 해야 할 때, 인터페이스를 사용하면 매우 유연하고 확장 가능한 시스템을 구축할 수 있습니다.

마지막으로 직접 참조, 디스패처, 인터페이스를 어떤 기준으로 선택할지 한 번에 비교해 보겠습니다.

참조 방식은 관계의 모양에 맞춰 고른다

대상이 명확하면 직접 참조, 사건을 알릴 때는 디스패처, 능력을 호출할 때는 인터페이스가 기준점입니다.

  1. 결합
    직접 참조

    하나의 특정 인스턴스를 정확히 알고 있을 때 빠르게 호출합니다. 결합 범위

  2. 결합
    이벤트 디스패처

    발생한 사건을 여러 구독자에게 알릴 때 자연스럽습니다. 결합 범위

  3. 결합
    인터페이스

    클래스가 달라도 같은 능력을 구현했다면 메시지로 호출합니다. 결합 확장


이번 절에서는 블루프린트 인터페이스를 통한 통신 방법을 심도 있게 다루었습니다.

직접 참조, 이벤트 디스패처, 그리고 인터페이스는 각각 다른 상황과 요구사항에 적합한 통신 기법이므로, 각 방법의 장단점을 이해하고 적절하게 선택하여 사용하는 것이 중요합니다.

인터페이스를 설계할 때는 호출자가 클래스보다 능력을 바라보는지부터 확인합니다.

인터페이스 메시지 호출

발사체는 적, 상자, 플레이어의 구체 타입을 몰라도 BPI_Damageable 계약만 확인하고 ApplyDamage 메시지를 보낼 수 있습니다.

  1. 선언은 하나, 구현은 각자

    계약 인터페이스에는 함수 형태만 두고 실제 반응은 구현한 블루프린트가 맡습니다. Define 함수 이름과 입력 핀을 정하되 내부 로직은 넣지 않습니다. Implement BP_Enemy, BP_Box처럼 반응할 클래스가 인터페이스를 구현합니다. 점검 Does Implement Interface로 메시지를 받을 수 있는 대상인지 분기합니다. Message ApplyDamage Message를 보내면 각 클래스의 구현 로직이 실행됩니다.

  2. 선언은 하나, 구현은 각자

    계약 인터페이스에는 함수 형태만 두고 실제 반응은 구현한 블루프린트가 맡습니다.

  3. BPI_Damageable 선언

    Define 함수 이름과 입력 핀을 정하되 내부 로직은 넣지 않습니다.

  4. 대상 클래스에 추가

    Implement BP_Enemy, BP_Box처럼 반응할 클래스가 인터페이스를 구현합니다.

  5. 구현 여부 확인

    점검 Does Implement Interface로 메시지를 받을 수 있는 대상인지 분기합니다.

  6. 같은 호출, 다른 실행

    Message ApplyDamage Message를 보내면 각 클래스의 구현 로직이 실행됩니다.

세 가지 통신 방식을 비교해 선택하는 기준을 추가합니다.

통신 방식은 대상 수와 의존도에 따라 달라진다

직접 참조, 디스패처, 인터페이스는 모두 통신이지만 결합 방향과 수신자 수가 다르다.

  1. Direct

    대상 하나를 알고 호출 레벨의 특정 문, 현재 플레이어

  2. Dispatcher

    한 발행자가 여러 청취자에게 알림 문 열림 알림, 체력 변화 이벤트

  3. Interface

    대상의 구체 클래스를 숨김 상호작용 가능한 문, 레버, NPC

선택 기준의미
대상 하나직접 참조가 가장 단순하지만 IsValid가 필요하다.
청취자 여러 명디스패처로 발행자와 반응자를 분리한다.
클래스가 자주 바뀜인터페이스 메시지로 구현 위치를 숨긴다.

블루프린트 통신은 한 가지 방식으로 밀어붙이지 말고 대상 수, 호출 방향, 결합도를 기준으로 직접 참조·디스패처·인터페이스를 고릅니다.

Blueprint 통신은 호출자가 알아야 하는 정보량으로 고른다

Cast, 직접 참조, 디스패처, 인터페이스는 같은 문제를 풀지만 호출자가 부담하는 정보가 다르다.

질문판정
정확한 클래스가 필요하다Cast To. 성공/실패 경로를 함께 둔다.
이미 대상 변수를 갖고 있다Direct Reference. 할당 지점과 유효성 검사를 표시한다.
여러 객체가 들어야 한다Event Dispatcher. 발행자와 구독자를 분리한다.
행동 이름만 맞으면 된다Blueprint Interface. 구체 클래스 의존을 줄인다.