레벨 블루프린트와의 상호작용
레벨 블루프린트에서 배치 액터를 참조해 레벨 전용 이벤트를 연결하고 사용 범위와 결합도 한계를 이해합니다.
이 레벨의 이 스위치를 밟으면 저기 있는 저 문이 열리게 하고 싶다고 해 봅시다. 직접 참조를 통한 블루프린트 통신처럼 스위치 블루프린트에 문 참조 변수를 만들고 인스턴스를 지정해도 되지만, 이 연결이 이 레벨에만 있는 퍼즐이라면 레벨 자체에 로직을 두는 편이 자연스러울 수 있습니다. 언리얼 엔진은 레벨마다 그 레벨에만 속한 블루프린트를 하나씩 두는데, 이것이 레벨 블루프린트(Level Blueprint)입니다.
이 글에서는 레벨 블루프린트가 무엇이고 어떻게 여는지, 레벨에 배치된 액터의 참조와 이벤트를 어떻게 가져오는지 살펴봅니다. 그리고 재사용과 결합의 측면에서 레벨 블루프린트의 한계를 짚고, 어떤 일을 레벨 블루프린트에 맡기고 어떤 일을 다른 블루프린트로 나눌지 기준을 정리합니다.
레벨 하나에만 속한 블루프린트
레벨 블루프린트는 레벨(맵 파일)마다 하나씩 있는 블루프린트로, 그 레벨에 배치된 액터나 레벨 자체에서 일어나는 이벤트에 반응하는 로직을 만듭니다. 레벨 블루프린트는 해당 레벨 파일 안에 저장되므로 다른 레벨에서는 쓸 수 없습니다. 내부적으로는 ALevelScriptActor 계열의 클래스가 만들어지고, 그 인스턴스가 레벨에 속한 숨겨진 액터로 동작합니다. 그래서 그 레벨이 언로드되면 레벨 블루프린트도 플레이를 끝냅니다. 다만 레벨 스트리밍으로 다른 레벨이 추가되더라도 원래 레벨이 그대로 남아 있다면, 그 레벨의 블루프린트까지 사라지는 것은 아닙니다.
레벨 블루프린트의 가장 큰 특징은 현재 레벨에 배치된 특정 액터 인스턴스의 직접 참조를 아주 쉽게 얻을 수 있다는 점입니다. 레벨의 시작, 특정 영역 진입과 이탈, 시퀀스 재생처럼 레벨 전체에 걸친 사건을 처리하기에도 알맞으며, 공식 문서는 레벨 스트리밍과 시퀀서(Sequencer)를 제어하는 수단으로도 소개합니다. 반면 편집 화면은 일반 액터 블루프린트와 다릅니다. 공식 문서에 따르면 레벨 블루프린트 에디터는 그래프 에디터와 마이 블루프린트 패널, 디테일 패널만 쓰며, 컴포넌트 패널에서 외형을 조립하는 용도가 아닙니다. 또 에디터에서 레벨 블루프린트를 편집할 수는 있지만 새 레벨 블루프린트를 따로 만들 수는 없습니다.
레벨 블루프린트는 레벨 에디터 툴바에서 엽니다. 툴바의 Blueprints 버튼을 누르고 Open Level Blueprint를 고르면, 현재 레벨의 레벨 블루프린트가 블루프린트 에디터로 열립니다.
배치된 액터의 참조와 이벤트 가져오기
먼저 레벨에 통신할 액터, 예를 들어 BP_Door 인스턴스를 배치합니다. 그리고 레벨 뷰포트나 아웃라이너(Outliner)에서 그 문을 선택한 채 레벨 블루프린트 에디터로 돌아와, 이벤트 그래프의 빈 곳에서 마우스 오른쪽 버튼을 누릅니다. 메뉴 위쪽에 Create a Reference to BP_Door처럼 선택한 액터의 이름이 들어간 항목이 나타나며, 이것을 고르면 그 인스턴스를 가리키는 참조 노드가 생깁니다. 아웃라이너에서 액터를 레벨 블루프린트 그래프로 끌어다 놓아도 같은 참조 노드를 만들 수 있습니다.
일반 액터 블루프린트였다면 참조 변수를 만들고 Instance Editable을 켜고 레벨에서 인스턴스를 지정하는 과정을 거쳐야 했던 일을, 선택과 오른쪽 클릭만으로 끝낸 셈입니다. 생성된 참조 노드에서 드래그하면 그 액터의 함수나 변수 가운데 외부 접근이 허용된 것을 쓸 수 있습니다. 액터가 플레이 중에 제거될 수 있다면, 호출하기 전에 참조가 유효한지도 확인합니다.
배치된 액터의 이벤트도 같은 방식으로 가져옵니다. 액터를 선택한 채 레벨 블루프린트의 그래프에서 오른쪽 클릭하면 Add Event for BP_Switch처럼 그 액터의 이벤트를 추가하는 항목이 있고, 레벨 뷰포트에서 액터를 오른쪽 클릭했을 때 나오는 메뉴의 Level Blueprint 항목에서도 이벤트를 추가할 수 있습니다.
스위치로 특정 문 열기
이제 두 기능을 합쳐 스위치로 문을 여는 레벨 스크립트를 만들어 보겠습니다. 레벨에 BP_Door와 BP_Switch가 하나씩 배치되어 있다고 가정합니다. 레벨 블루프린트에서 두 인스턴스의 참조 노드를 각각 만듭니다.
레벨에서 BP_Switch를 선택하고 레벨 블루프린트의 Add Event for BP_Switch 메뉴에서 OnActorBeginOverlap 이벤트를 추가합니다. 이 이벤트가 오려면 스위치와 상대 컴포넌트 모두 쿼리 충돌과 Generate Overlap Events가 켜져 있어야 하고, 어느 쪽도 상대를 Ignore하지 않으면서 적어도 한쪽(보통 스위치)이 Overlap으로 응답해야 합니다. 이벤트의 실행 핀 뒤에는 Branch를 두고, 이벤트의 Other Actor가 Get Player Character의 결과와 같은지 비교한 값을 조건으로 넣어 겹친 액터가 플레이어 캐릭터인지 확인합니다.
Branch의 True 쪽에서는 BP_Door 참조를 Is Valid로 확인하고, 유효할 때만 그 참조를 대상으로 OpenDoor를 호출합니다. 이렇게 하면 플레이어가 스위치에 닿을 때 이 레벨의 그 문이 열립니다. 문과 스위치는 서로를 전혀 모르고, 둘의 연결은 레벨 블루프린트에만 있습니다.
반대 방향, 즉 액터가 레벨 블루프린트에 무언가를 알려야 할 때는 액터 쪽에서 레벨 블루프린트를 직접 찾아 부르는 대신 이벤트 디스패처의 기본 사용법의 디스패처를 씁니다. 액터는 디스패처를 호출하기만 하고, 레벨 블루프린트가 배치된 그 인스턴스의 참조로 디스패처에 등록합니다. 공식 문서도 레벨 블루프린트에서 디스패처 노드의 Target에는 레벨에 있는 특정 인스턴스의 참조를 연결해야 한다고 설명합니다.
레벨 블루프린트의 장점과 한계
레벨 블루프린트의 장점은 앞에서 본 대로 레벨에 배치된 인스턴스의 참조를 쉽게 얻을 수 있다는 것입니다. 그래서 특정 레벨에서만 필요한 간단한 이벤트나 시퀀스 로직을 빠르게 만들 수 있고, 레벨의 로딩과 언로딩, 특정 영역 진입처럼 레벨 자체와 관련된 사건을 다루기에도 좋습니다.
한계는 재사용입니다. 레벨 블루프린트의 로직은 그 레벨에만 있으므로, 다른 레벨에서 같은 기능이 필요하면 다시 만들어야 하고 같은 그래프를 여러 곳에서 고쳐야 하는 반복 작업이 생깁니다. 또 레벨 블루프린트는 특정 액터 인스턴스에 직접 묶이므로 강한 결합이 생깁니다. 참조하던 액터를 지우거나 바꾸면 레벨 블루프린트의 참조가 깨질 수 있습니다. 로직을 너무 많이 넣으면 그래프가 복잡해져 관리하기 어려워지며, 이 문제는 대규모 프로젝트일수록 커집니다.
레벨 블루프린트는 일반 Actor 블루프린트처럼 여러 맵에 배치해 재사용하는 구조가 아닙니다. 그래서 여러 곳에서 불러야 하는 공통 기능을 레벨 블루프린트에 두고 외부에서 호출하려 하기보다, 그 기능은 별도의 재사용할 수 있는 블루프린트로 나누는 편이 분명합니다.
언제 레벨 블루프린트를 쓸까
레벨 블루프린트는 특정 레벨에서만 일어나는 고유한 사건에 가장 잘 맞습니다. 레벨이 시작될 때 컷신을 재생하거나, 그 레벨에만 있는 퍼즐의 작동을 만들거나, 그 레벨에서만 등장하는 보스전의 단계를 바꾸는 일이 그 예입니다. "이 레벨의 이 스위치를 누르면 저 문이 열린다"처럼 명확하게 정해진 소수의 인스턴스 사이를 잇는 일이나, 레벨의 기능을 빠르게 시험해 보는 프로토타이핑에도 알맞습니다.
반대로 게임 전체에서 재사용해야 하는 기능, 플레이 중에 생기고 사라지는 액터의 로직, 유연한 통신이 필요한 복잡한 시스템은 액터 블루프린트와 게임 인스턴스, 블루프린트 인터페이스, 이벤트 디스패처로 만드는 편이 효율적이고 유지보수도 쉽습니다. 레벨 블루프린트는 어디까지나 현재 레벨의 스크립트라는 점을 기억해 두면 선택이 쉬워집니다.
정리
- 레벨 블루프린트는 레벨마다 하나씩 그 레벨 파일에 저장되는 블루프린트이며, 레벨이 언로드되면 함께 플레이를 끝냅니다.
- 툴바의
Blueprints버튼에서Open Level Blueprint로 엽니다. - 레벨에서 액터를 선택한 채 그래프를 오른쪽 클릭하면
Create a Reference to로 참조를,Add Event for로 그 액터의 이벤트를 만들 수 있습니다. - 액터가 레벨 블루프린트에 알릴 때는 디스패처를 호출하고, 레벨 블루프린트가 그 인스턴스에 등록합니다.
- 레벨 블루프린트는 그 레벨에만 있는 사건과 소수 인스턴스의 연결에 쓰고, 재사용할 기능은 별도의 블루프린트로 나눕니다.
연습 문제
1. 두 번째 레벨에도 같은 퍼즐
Level_1의 레벨 블루프린트에 "스위치를 밟으면 문이 열리는" 로직을 만들었습니다. 이제 Level_2와 Level_3에도 같은 퍼즐을 넣으려고 합니다. 각 레벨의 레벨 블루프린트에 같은 그래프를 복사해 넣는 방법에는 어떤 문제가 있고, 어떻게 바꾸면 좋을까요?
해설 보기
레벨 블루프린트의 로직은 그 레벨에만 있으므로, 복사하면 같은 그래프가 세 곳에 생깁니다. 나중에 "문이 열릴 때 소리를 낸다" 같은 규칙을 바꾸려면 세 레벨을 모두 열어 같은 수정을 반복해야 하고, 하나라도 빠뜨리면 레벨마다 동작이 달라집니다.
여러 레벨에서 쓰는 퍼즐이라면 스위치 액터 블루프린트에 Instance Editable인 문 참조 변수를 두고, 겹침 이벤트에서 그 문을 열게 만듭니다. 그러면 각 레벨에서는 스위치를 배치하고 어느 문을 열지만 지정하면 되고, 규칙은 스위치 클래스 한곳에서 고칠 수 있습니다.
2. 지워진 문
레벨 블루프린트가 참조하던 BP_Door 인스턴스를 레벨에서 지우고, 같은 자리에 새 BP_Door를 배치했습니다. 스위치를 밟아도 새 문은 열리지 않습니다. 왜 그럴까요?
해설 보기
레벨 블루프린트의 참조 노드는 클래스가 아니라 특정 인스턴스를 가리킵니다. 원래 문을 지우면 그 참조는 더 이상 유효한 문을 가리키지 못하고, 같은 자리에 같은 클래스의 문을 새로 놓았다고 참조가 새 문으로 옮겨 가지는 않습니다.
새 문을 선택하고 Create a Reference to로 참조 노드를 다시 만들어 연결해야 합니다. 이처럼 특정 인스턴스에 직접 묶이는 강한 결합 때문에, 레벨을 고칠 때마다 레벨 블루프린트도 함께 확인해야 합니다.
3. 액터에서 레벨 블루프린트에 알리기
보물 상자가 열리면 레벨 블루프린트가 레벨의 출구를 열어야 합니다. 상자 블루프린트에서 레벨 블루프린트의 함수를 직접 부르려고 했지만 방법을 찾지 못했습니다. 어떻게 구성하면 될까요?
해설 보기
상자 블루프린트에 OnChestOpened 같은 이벤트 디스패처를 만들고, 상자가 열릴 때 그 디스패처를 호출합니다. 상자는 누가 듣는지 알 필요가 없습니다.
레벨 블루프린트에서는 배치된 상자 인스턴스의 참조를 만들고, BeginPlay에서 그 참조를 Target으로 OnChestOpened에 커스텀 이벤트를 등록합니다. 그 커스텀 이벤트에서 출구를 열면 됩니다. 이렇게 하면 상자는 레벨 블루프린트를 모르고, 상자와 출구의 연결은 레벨 블루프린트에만 남습니다.
레벨 블루프린트를 열고 액터의 참조와 이벤트를 추가하는 방법은 언리얼 엔진 공식 문서의 레벨 블루프린트 설명에서 확인할 수 있습니다.
이것으로 4장을 마칩니다. 4장에서는 특정 인스턴스를 가리키는 직접 참조에서 시작해, 한 사건을 여러 수신자에게 알리는 이벤트 디스패처, 여러 클래스에 같은 요청을 보내는 인터페이스 통신, 레벨이 바뀌어도 데이터를 유지하는 게임 인스턴스, 그리고 레벨 하나에만 속한 레벨 블루프린트까지 블루프린트끼리 정보를 주고받는 방법과 각각을 고르는 기준을 익혔습니다. 다음 장의 구조체와 열거형의 기본 이해부터는 구조체와 열거형, 데이터 테이블, 저장과 불러오기, 에셋 레퍼런싱, 함수 라이브러리로 블루프린트의 데이터를 정리하고 관리하는 방법을 다룹니다.