블루프린트 인터페이스 활용
인터페이스로 기능 계약을 정의하고 서로 다른 액터에 구현해 상속 없이 느슨하게 상호작용하는 구조를 만듭니다.
지금까지 액터, 컴포넌트, 상속을 통해 블루프린트에서 객체 지향의 기본 구조를 다뤘습니다.
이번 절에서는 다음 단계인 블루프린트 인터페이스(Blueprint Interface)를 살펴봅니다.
인터페이스는 상속과 다른 방식으로 블루프린트 간 통신을 구성하며, 느슨한 결합(Loose Coupling)으로 확장성과 유지보수성을 높입니다.
호출자는 문, 레버, 버튼의 구체 클래스를 몰라도 BPI_Interactable의 Interact 메시지만 보낼 수 있다.
- Player
Line Trace로 대상 Actor를 찾고 Interact(Message)를 호출
- BP_Door
문 열기
- BP_Lever
스위치 전환
- BP_Button
버튼 눌림 처리
| 역할 | 의미 |
|---|---|
| 계약 | 인터페이스에는 변수나 구현 로직이 아니라 함수 이름과 핀만 둔다. |
| 구현 | 각 액터가 같은 메시지에 자기 방식으로 반응한다. |
| 효과 | 새 상호작용 액터를 추가해도 호출자는 바뀌지 않는다. |
인터페이스란 무엇인가?
일상생활의 비유로 설명해 보겠습니다.
여러분이 스마트폰 충전기(인터페이스)를 가지고 있다고 생각해봅시다.
이 충전기는 어떤 종류의 스마트폰이든 상관없이 특정 포트(USB-C, 라이트닝 등)만 가지고 있다면 충전할 수 있습니다.
충전기는 충전한다는 기능만 정의할 뿐, 그 기능을 어떤 스마트폰이 어떻게 구현하는지는 신경 쓰지 않습니다.
블루프린트 인터페이스도 이와 유사합니다.
인터페이스는 특정 기능의 이름만 정의하고, 그 기능을 어떻게 구현할지는 해당 인터페이스를 구현(Implement)하는 블루프린트 클래스에 맡깁니다.
인터페이스 자체는 어떠한 로직이나 변수도 포함하지 않습니다.
오직 이러한 기능이 존재한다는 약속만을 담고 있습니다.
인터페이스가 왜 필요한가?
상속은 A는 B이다 (A is a B)라는 부모-자식 관계를 형성합니다.
예를 들어, BP_SportsCar는 BP_Car이다.
이는 매우 강력하지만, 한 가지 제약이 있습니다.
블루프린트는 단일 상속(Single Inheritance) 만을 지원합니다.
즉, 하나의 블루프린트는 오직 하나의 부모 블루프린트만 가질 수 있습니다.
하지만 실제 게임에서는 서로 다른 블루프린트가 같은 기능을 공유해야 하는 경우가 많습니다.
예를 들어 BP_Door(문), BP_Lever(레버), BP_Button(버튼)이 모두 상호작용 기능을 가져야 할 수 있습니다.
이들을 하나의 부모 클래스로 강제로 묶으면 설계가 부자연스러워집니다.
문은 레버가 아니고, 레버는 버튼이 아니기 때문입니다.
이럴 때 인터페이스가 해결책이 됩니다.
이 세 가지 액터(문, 레버, 버튼)는 모두 BPI_Interactable (상호작용 가능 인터페이스)를 구현할 수 있습니다.
각 액터는 BPI_Interactable이 정의한 Interact (상호작용) 기능을 자신만의 방식으로 구현합니다.
문은 열리고, 레버는 작동하고, 버튼은 눌리는 식으로 말이죠.
인터페이스는 A는 B를 할 수 있다 (A can do B)라는 능력 관계를 정의합니다.
어떤 블루프린트가 특정 인터페이스를 구현했는지만 확인하면, 해당 기능을 안전하게 호출할 수 있습니다.
이 방식은 블루프린트 간 결합도를 낮추고, 나중에 새 상호작용 오브젝트를 추가할 때도 기존 로직 수정 없이 인터페이스 구현만으로 확장할 수 있게 해 줍니다.
블루프린트 인터페이스 생성 및 정의
- 콘텐츠 브라우저(Content Browser)에서 마우스 오른쪽 버튼을 클릭합니다.
블루프린트(Blueprints)>블루프린트 인터페이스(Blueprint Interface)를 선택합니다.- 이름을 지정합니다. (예:
BPI_Interactable- 일반적으로 BPI_ 접두사를 사용합니다.) - 생성된 인터페이스 에셋을 더블클릭하여 인터페이스 에디터를 엽니다.
- 인터페이스 에디터의 좌측
함수(Functions)패널에서+버튼을 클릭하여 새로운 함수를 추가합니다. - 함수 이름을 지정합니다. (예:
Interact) - 이 함수에 필요한 입력(Inputs) 및 출력(Outputs) 매개변수를 디테일 패널에서 추가합니다. (예:
Interact함수는InteractingActor(Actor Reference 타입)이라는 입력 핀을 가질 수 있습니다.) - 참고: 인터페이스 함수에는 로직을 구현할 수 있는 그래프가 없습니다. 오직 함수의 이름과 입출력 매개변수만 정의합니다.
컴파일 및 저장: 인터페이스를 정의한 후에는 반드시 컴파일하고 저장해야 합니다.
인터페이스 구현 및 활용
호출자는 클래스 타입 대신 인터페이스 구현 여부를 보고 메시지를 보내며, 각 액터는 자신의 Event Interact를 실행한다.
- Caller
Player가 대상 Actor 참조를 얻음 Trace Overlap
- Does Implement?
BPI_Interactable 여부 확인 Guard
- Interact Message
계약 이름으로 호출 Target pin
- Implement Event
Door/Lever/Button별 처리 Event Interact
| 점검 | 기준 |
|---|---|
| 느슨한 결합 | 호출자는 BP_Door 같은 구체 클래스를 몰라도 된다. |
| 실패 처리 | 인터페이스 미구현 대상이면 아무 일도 안 일어날 수 있어 검사를 둔다. |
| 계약 관리 | 입력 핀을 바꾸면 모든 구현 액터를 함께 확인한다. |
이제 생성한 인터페이스를 실제 액터 블루프린트에서 구현하고 호출해 봅시다.
- 인터페이스 기능을 추가하고 싶은 액터 블루프린트(예:
BP_Door)를 엽니다. - 블루프린트 에디터의 상단 툴바에서
클래스 세팅(Class Settings)버튼을 클릭합니다. - 디테일 패널의
인터페이스(Interfaces)섹션에서Implemented Interfaces옆의+ 추가(+ Add)버튼을 클릭합니다. - 목록에서 여러분이 만든 인터페이스(예:
BPI_Interactable)를 검색하여 선택합니다. - 이제 블루프린트를 컴파일하면 좌측 함수(Functions) 패널에
인터페이스(Interfaces)섹션이 새로 생기고, 그 아래에Interact함수가 나타난 것을 볼 수 있습니다.
- 좌측 함수 패널의
인터페이스섹션 아래Interact함수 위에서 마우스 오른쪽 버튼을 클릭하고이벤트 구현(Implement Event)을 선택합니다. - 이벤트 그래프에
Event Interact노드가 생성됩니다. 이제 이 이벤트 노드의 실행 핀 뒤에 이 액터가Interact되었을 때 수행할 실제 로직을 구현합니다. - 예시:
Print String노드를 연결하여 문이 상호작용되었습니다!라고 출력하도록 설정하고, 문을 열리는 애니메이션을 재생하는 로직을 추가할 수 있습니다. - 블루프린트를 컴파일하고 저장합니다.
- 이제 다른 블루프린트(예: 플레이어 캐릭터 블루프린트)에서 이
Interact기능을 호출해봅시다. - 플레이어 캐릭터 블루프린트의 이벤트 그래프에서, 예를 들어
E키를 눌렀을 때 특정 액터와 상호작용하는 로직을 만든다고 가정합니다. Event E키 입력 노드를 추가합니다.- 플레이어가 보고 있는 액터나 겹쳐진 액터를 가져오는 로직을 만듭니다. (예:
Line Trace By Channel또는Get Overlapping Actors사용) - 가져온 액터 레퍼런스 핀에서 드래그하여 마우스 오른쪽 버튼을 클릭하고, 여러분의 인터페이스 함수 이름(예:
Interact)을 검색합니다. 이때,Message가 붙은 노드를 선택해야 합니다. (예:Interact (Message)) Interact (Message)노드는 대상 액터가BPI_Interactable인터페이스를 구현했는지 자동으로 확인하고, 구현했다면 해당 액터의Event Interact로직을 실행시킵니다.Target핀에는 상호작용할 액터 레퍼런스를 연결하고, 필요한 경우 입력 매개변수를 연결합니다.
이 방식의 장점은, Interact (Message) 노드를 연결할 때 대상 액터가 BP_Door인지, BP_Lever인지, BP_Button인지 명시적으로 알 필요가 없다는 것입니다.
그저 BPI_Interactable 인터페이스를 구현했는지 여부만 중요합니다.
인터페이스 호출을 실제 그래프에 배치할 때는 대상 확인, 메시지 호출, 구현별 처리를 분리해서 보면 실수를 줄일 수 있습니다.
플레이어 블루프린트는 대상 액터의 구체 클래스를 몰라도 됩니다. BPI_Interactable 구현 여부와 메시지 호출 계약만 맞추면 각 액터가 자기 이벤트에서 반응합니다.
- BP_Door
문 열림 애니메이션을 실행합니다.
- BP_Lever
상태 토글과 연결 장치를 처리합니다.
- BP_Button
눌림 효과와 연결 이벤트를 처리합니다.
| 순서 | 검사 지점 | 블루프린트 신호 | 설계 효과 |
|---|---|---|---|
| 01 | 후보 액터 | Hit Actor | 바라본 대상을 상호작용 후보로만 다루고 클래스 이름은 보지 않습니다. |
| 02 | 구현 여부 | Does Implement Interface | 부모 클래스보다 “상호작용 가능”이라는 능력을 기준으로 분기합니다. |
| 03 | 메시지 호출 | Interact (Message) | 필요한 입력만 전달하고 문, 레버, 버튼 같은 구체 타입 캐스팅을 피합니다. |
| 04 | 구현 액터 | Event Interact | 각 액터가 자기 그래프에서 문 열기, 레버 전환, 버튼 실행을 처리합니다. |
| 05 | 미구현 액터 | no implementation | 실패 처리는 최소화하고 안내 숨김이나 디버그 기록 정도만 남깁니다. |
인터페이스 사용의 이점 정리
- 다형성(Polymorphism) 구현: 하나의 인터페이스 메시지 호출로, 다양한 종류의 블루프린트가 각자 자신만의 방식으로 기능을 수행할 수 있게 합니다.
- 느슨한 결합(Loose Coupling): 블루프린트 간의 의존성을 줄여줍니다. 호출하는 쪽은 특정 클래스에 묶이지 않고, 인터페이스만 알면 되므로 시스템 변경에 유연하게 대처할 수 있습니다.
- 확장성: 새로운 기능이 필요할 때, 기존 인터페이스에 함수를 추가하거나, 새로운 클래스가 기존 인터페이스를 구현하기만 하면 됩니다.
- 협업 효율성: 팀원 간에 기능의 계약을 명확히 정의할 수 있어, 각자 독립적으로 개발하고 나중에 쉽게 통합할 수 있습니다.
플레이어는 상호작용 가능한 대상인지 확인하고 메시지만 보냅니다. 실제 문 열림, 레버 작동, 버튼 입력은 각 액터의 구현에 맡깁니다.
- 상호작용 대상 확보
Find Target Line Trace나 Overlap으로 플레이어가 바라보는 액터 레퍼런스를 얻습니다.
- BPI 구현 여부 확인
점검 Does Implement Interface로 메시지를 받을 수 있는 대상인지 걸러냅니다.
- Interact 메시지 호출
Message Target에 Interact Message를 보내고, 구체 클래스 캐스팅은 피합니다.
- 액터별 반응 실행
Implement 문은 열리고, 레버는 상태를 바꾸고, 버튼은 연결된 장치를 호출합니다.
- 플레이어 블루프린트
Caller 대상 탐색, 거리 제한, 입력 타이밍만 책임집니다. 대상 종류는 몰라도 됩니다.
- BPI 구현 액터
Receiver Interact 이벤트 안에서 자신에게 맞는 애니메이션, 소리, 상태 변경을 실행합니다.
블루프린트 인터페이스는 복잡한 게임 시스템을 설계하고, 여러 블루프린트가 서로 유기적으로 상호작용하게 만들 때 매우 유용한 고급 도구입니다.
처음에는 어렵게 느껴질 수 있지만, 인터페이스를 활용하기 시작하면 블루프린트 코드의 유지보수성과 확장성이 크게 향상되는 것을 경험하실 수 있을 것입니다.
이번 절에서는 블루프린트 인터페이스의 개념, 생성 및 활용 방법, 그리고 상속과의 차이점에 대해 알아보았습니다.
인터페이스는 블루프린트의 객체 지향적 설계를 한 단계 더 발전시키는 중요한 요소입니다.
BPI_Interactable 은 Interact 함수의 이름과 핀만 정하고, 문과 레버와 버튼의 실제 반응은 각 블루프린트가 구현합니다.
- 상속 대신 인터페이스를 고르는 순간
can do B Door Event Interact 에서 문 열림 애니메이션이나 메시지를 실행합니다. Lever 문과 부모가 같지 않아도 같은 Interact 계약을 구현할 수 있습니다. Button 호출자는 구체 타입을 몰라도 Interact (Message) 로 공통 기능을 요청합니다.
- 문은 열립니다
Door Event Interact 에서 문 열림 애니메이션이나 메시지를 실행합니다.
- 레버는 작동합니다
Lever 문과 부모가 같지 않아도 같은 Interact 계약을 구현할 수 있습니다.
- 버튼은 눌립니다
Button 호출자는 구체 타입을 몰라도 Interact (Message) 로 공통 기능을 요청합니다.
- 정의에서 메시지 호출까지
BPI_Interactable 1 인터페이스 에디터에서 Interact 함수와 InteractingActor 같은 입력 핀을 만듭니다. 2 액터 블루프린트의 Implemented Interfaces에 BPI_Interactable 을 등록합니다. 3 함수 패널의 인터페이스 항목에서 이벤트 구현을 선택하고 실제 로직을 작성합니다. Call 플레이어가 바라본 액터에 Interact (Message) 를 보내고 대상 구현 여부를 확인합니다.
- 함수 선언
인터페이스 에디터에서 Interact 함수와 InteractingActor 같은 입력 핀을 만듭니다.
- 클래스 세팅에 추가
액터 블루프린트의 Implemented Interfaces에 BPI_Interactable 을 등록합니다.
- 이벤트 구현
함수 패널의 인터페이스 항목에서 이벤트 구현을 선택하고 실제 로직을 작성합니다.
- 메시지로 호출
Call 플레이어가 바라본 액터에 Interact (Message) 를 보내고 대상 구현 여부를 확인합니다.
인터페이스를 약속 중심으로 점검하는 다이어그램을 추가합니다.
Interactable처럼 여러 클래스가 같은 기능에 응답해야 할 때 부모 상속보다 인터페이스가 자연스럽다.
| 질문 | 판정 |
|---|---|
| 서로 다른 클래스가 같은 요청을 받아야 하나? | 인터페이스 후보. Door, Lever, NPC가 같은 Interact를 구현한다. |
| 공통 변수나 기본 로직까지 공유해야 하나? | 상속 또는 컴포넌트가 더 맞을 수 있다. |
| 호출자가 대상 타입을 몰라도 되나? | Message Call로 구체 클래스 의존을 줄인다. |
| 응답이 액터마다 달라야 하나? | 각 구현 이벤트에서 자기 역할에 맞게 처리한다. |
| 신호 | 해석 |
|---|---|
| 좋은 계약 | BPI_Interactable.Interact처럼 기능 이름이 행동을 설명한다. |
| 나쁜 계약 | 내부 변수나 구현 순서를 호출자에게 드러낸다. |
인터페이스는 클래스 이름을 직접 알기보다 어떤 메시지를 보낼지 약속하는 구조이므로 함수 계약과 구현 위치를 함께 봅니다.
호출자는 메시지를 보낼 뿐이고, 실제 문 열림·아이템 획득·NPC 대화는 각 블루프린트 내부에서 결정된다.
- BPI_Interactable
함수 이름 Interact, Use, Damage 같은 메시지. 입력 핀 InteractingActor 같은 호출 문맥. 구현 없음 계약만 있고 내부 로직은 없다.
- 구현 블루프린트
Door Interact → 문 열기. Item Interact → 획득 후 Destroy. NPC Interact → 대화 UI 열기.
| 검수 | 의미 |
|---|---|
| 핵심 | 인터페이스는 “무엇을 할 수 있는가”를 말하고, 클래스는 “어떻게 할 것인가”를 정한다. |
| 검수 | 호출자가 구체 클래스 Cast 없이 메시지를 보낼 수 있는지 본다. |