블루프린트의 개념과 역할
블루프린트의 구조와 C++과의 역할 분담을 살펴보고 게임 로직을 비주얼 스크립팅으로 설계하는 기준을 익힙니다.
블루프린트를 처음 열면 상자 모양의 노드가 화면을 채우고, 그 사이를 흰 선과 색깔 있는 선이 얽혀 지나갑니다. 코드를 쓰지 않는 대신 오히려 더 복잡해진 것 같고, 어느 선이 실행 순서이고 어느 선이 값을 넘기는지도 헷갈리기 쉽습니다. 그래도 블루프린트는 게임 로직이 어떤 순서로 실행되는지 눈으로 따라가며 배울 수 있게 해 주므로, 언리얼 엔진에서 게임플레이를 처음 만들 때 좋은 출발점이 됩니다.
이 글에서는 블루프린트가 무엇인지, 언리얼 엔진에서 어떤 일을 맡는지, 그리고 C++과 일을 어떻게 나누는지를 현실적인 범위에서 살펴봅니다. 블루프린트가 맡는 역할을 먼저 알아 두면, 다음 글부터 노드를 연결할 때 왜 그 순서가 필요한지 더 빨리 이해할 수 있습니다. 이 교재의 메뉴와 노드 이름은 언리얼 엔진 5.8을 기준으로 합니다.
노드와 선으로 만드는 프로그램
블루프린트 비주얼 스크립팅(Blueprint Visual Scripting)은 프로그램의 동작을 노드(node)라는 상자로 나타내고, 노드 사이를 선(wire)으로 이어 흐름을 만드는 시각적 프로그래밍 방식입니다. 노드 하나는 이벤트, 함수 호출, 변수 읽기처럼 한 가지 일을 나타내며, 노드를 어떤 순서로 잇느냐에 따라 프로그램이 하는 일이 정해집니다. 언리얼 엔진 공식 문서는 이 시스템과, 이 시스템으로 정의한 객체를 모두 블루프린트라고 부릅니다. 그래서 "블루프린트를 만든다"는 말은 문맥에 따라 그래프를 작성한다는 뜻일 수도 있고, 콘텐츠 브라우저에 블루프린트 에셋을 하나 만든다는 뜻일 수도 있습니다.
텍스트 코드와 비교해 보면 차이가 분명해집니다. 캐릭터의 점프를 텍스트 코드로 쓴다면 if (JumpButtonIsPressed) { Character.Jump(); }처럼 조건문의 문법과 함수를 호출하는 방법을 정확히 알고 입력해야 합니다. 블루프린트에서는 "점프 입력이 들어왔다"는 이벤트 노드를 "캐릭터가 점프한다"는 노드에 선으로 잇는 것으로 같은 로직을 나타냅니다. 문법 오류로 막힐 일이 줄고 실행 흐름이 선을 따라 화면에 그대로 보이므로, 처음 배우는 사람도 무엇이 먼저 일어나는지 확인하며 배울 수 있습니다.
블록 장난감을 조립하는 일에 비유할 수도 있습니다. 각 노드가 가진 기능을 조합해 원하는 결과를 만든다는 점은 비슷하지만, 블루프린트의 노드는 실행 순서와 값의 종류(타입)가 맞아야만 연결된다는 점에서 장난감 블록과 다릅니다. 이 규칙은 다음 글에서 실행 선과 데이터 선을 나누어 자세히 봅니다.
앞의 점프 예시는 두 방식을 비교하려는 개념 설명입니다. 실제로 Jump 노드가 캐릭터를 뛰게 하려면 플레이어가 조작하는 Character와 그 입력 연결이 있어야 하며, 빈 Actor 블루프린트에 Jump 노드를 하나 넣는다고 캐릭터가 생기지는 않습니다.
블루프린트가 맡는 일
블루프린트는 캐릭터의 움직임만 다루는 도구가 아닙니다. 언리얼 엔진에서 블루프린트로 만드는 일은 크게 다음과 같습니다.
- 게임 로직: 규칙, 목표, 승패 조건처럼 게임 전체의 흐름을 정하고 제어합니다.
- 상호작용: 문을 열거나 아이템을 줍는 것처럼 플레이어와 환경, 오브젝트 사이의 상호작용을 처리합니다.
- UI: 화면에 점수를 표시하거나 메뉴를 여는 등 사용자 인터페이스(UI)의 동작을 만듭니다.
- 애니메이션 제어: 캐릭터의 애니메이션 상태를 바꾸거나, 특정 상황에서 특정 애니메이션을 재생합니다.
- 이벤트 처리: 게임 안에서 일어나는 사건(이벤트)을 받아 반응을 정합니다. 적에게 맞았을 때 체력을 줄이는 것이 한 예입니다.
- 에디터 도구: 반복되는 개발 작업을 자동화하거나, 에디터 안에서 쓸 맞춤 도구를 만듭니다.
이 교재에서는 UI를 6장의 위젯 블루프린트로, 애니메이션 제어를 7장의 애니메이션 블루프린트로 다룹니다. 블루프린트의 종류도 맡는 일에 따라 나뉩니다. 공식 문서는 레벨 하나에만 속한 이벤트를 다루는 레벨 블루프린트(Level Blueprint)와, 레벨에 여러 번 배치할 수 있는 새 액터 종류를 정의하는 블루프린트 클래스(Blueprint Class)를 구분합니다. 블루프린트 클래스를 고치면 그 클래스로 배치한 모든 인스턴스에 변경이 반영되므로, 여러 곳에서 쓰일 동작은 클래스로 만드는 편이 관리하기 쉽습니다.
블루프린트와 C++에 일을 나누는 기준
블루프린트로 많은 것을 만들 수 있지만, 모든 일을 블루프린트에 두는 것이 좋은 설계는 아닙니다. 언리얼 엔진은 C++ 코드에 블루프린트용 표시를 붙여, 프로그래머가 만든 기반 시스템을 디자이너가 블루프린트로 확장할 수 있게 설계되어 있습니다. 그래서 실제 프로젝트에서는 둘 중 하나만 고르기보다 일을 나누는 경우가 많습니다. C++ 클래스에 이 표시를 붙여 블루프린트로 확장하는 과정은 언리얼 C++ 교재의 C++로 Blueprint 클래스 확장에서 직접 해 봅니다.
승패 조건, 상호작용, UI 반응처럼 자주 조정하고 바로 확인해 보고 싶은 게임플레이는 블루프린트로 조합하기 좋습니다. 값을 바꾸고 다시 플레이해 보는 과정이 빠르고, 그래프를 보면 이벤트에서 결과까지의 흐름이 드러나기 때문입니다. 같은 세부 처리가 여러 곳에서 되풀이된다면 함수나 컴포넌트로 묶어서, 이벤트 그래프에는 무엇을 하는지만 읽히게 합니다.
반대로 매 프레임 실행되는 복잡한 계산이나 엔진 자체를 확장하는 기능은 C++로 옮길지 검토합니다. 다만 "느릴 것 같다"는 느낌만으로 옮기지는 않습니다. 프로파일링으로 실제 비용을 측정하고, 옮긴 뒤 누가 어디까지 유지보수할지를 함께 따져 결정합니다. 측정하는 방법은 블루프린트 성능 최적화 기법에서 다룹니다.
첫 그래프를 만들 때 확인할 것
처음 그래프를 만들 때는 이벤트 하나와 결과 하나만 연결하고, 동작을 확인한 뒤에 조건 분기와 상태 변경을 더합니다. 한 번에 여러 노드를 붙이면 결과가 예상과 다를 때 어느 연결이 문제인지 찾기 어렵기 때문입니다. 이때 다음 네 가지를 차례로 확인하는 습관을 들이면 좋습니다.
먼저 입력 이벤트 노드에서 실행 노드로 선이 올바른 방향으로 이어졌는지 봅니다. 다음으로 상태를 바꾸는 노드가 어느 실행 경로에서 호출되는지 확인합니다. 여러 이벤트의 선을 한 노드에 모으면 그 노드는 이벤트가 일어날 때마다 실행되며, 선을 합쳤다고 중복 호출이 막히지는 않습니다. 세 번째로, 매 프레임 실행되는 Tick 이벤트에 로직을 넣기 전에 특정 사건이 일어날 때만 실행되는 이벤트로 바꿀 수 있는지 판단합니다. 마지막으로 기능을 넓히기 전에 Print String 노드로 화면에 짧은 문구를 출력해, 실행 순서가 예상과 같은지 확인합니다.
에셋, 인스턴스, 실행 시점
블루프린트를 처음 다룰 때 자주 섞이는 세 가지가 있습니다. 에셋을 만드는 일, 그 에셋으로 월드에 인스턴스를 놓는 일, 그리고 인스턴스에서 이벤트가 실행되는 시점입니다. 첫 번째 블루프린트 생성 및 실행에서는 Actor 클래스를 부모로 하는 BP_WelcomeMessage를 만들고, 레벨에 배치한 인스턴스의 BeginPlay 이벤트에서 환영 문구를 출력합니다. 콘텐츠 브라우저에 에셋을 만든 것만으로는 아무것도 실행되지 않고, 레벨에 배치한 인스턴스가 플레이를 시작해야 BeginPlay가 실행됩니다.
마찬가지로 그래프에 노드가 있다고 그 노드가 매 프레임 실행되는 것도 아닙니다. 노드는 자신에게 이어진 이벤트가 일어날 때만 실행됩니다. 첫 메시지 실습은 플레이어 입력 없이 시작할 수 있으므로, 앞에서 본 점프처럼 조작 가능한 캐릭터가 필요한 목표와 섞지 않고 따로 연습합니다.
정리
- 블루프린트는 노드를 선으로 이어 프로그램의 흐름을 만드는 시각적 스크립팅 시스템이며, 그 시스템으로 만든 에셋도 블루프린트라고 부릅니다.
- 게임 로직, 상호작용, UI, 애니메이션 제어, 이벤트 처리, 에디터 도구까지 블루프린트로 만들 수 있습니다.
- 자주 조정하는 게임플레이는 블루프린트로, 측정해서 비용이 확인된 계산이나 엔진 확장은 C++로 나누는 것이 일반적인 기준입니다.
- 처음에는 이벤트 하나와 결과 하나를 연결해
Print String으로 확인한 뒤 기능을 넓힙니다. - 에셋을 만드는 일, 인스턴스를 배치하는 일, 이벤트가 실행되는 시점은 서로 다릅니다.
연습 문제
1. 실행을 시작할 이벤트 고르기
게임이 시작될 때 안내 문구를 한 번 출력하는 기능과, 체력이 바뀔 때마다 UI 문구를 갱신하는 기능을 만들려고 합니다. 두 기능 모두 매 Tick 전체 상태를 확인해야 할까요? 각 기능이 언제 실행을 시작해야 하는지 먼저 적어 보세요.
해설 보기
첫 기능은 문구를 출력할 액터의 BeginPlay에서 시작하면 됩니다. 게임을 시작할 때 한 번만 필요하므로 매 프레임 확인할 이유가 없습니다.
둘째 기능은 체력이 바뀌는 순간이 출발점입니다. 체력을 바꾸는 경로에서 UI 갱신을 직접 호출하거나, 체력이 바뀌었다는 이벤트를 알리는 구조를 먼저 검토합니다. 다만 움직임처럼 매 프레임 변화가 필요한 기능도 있으므로 Tick 자체를 금지할 이유는 없습니다. 실행 빈도와 데이터를 가진 객체를 먼저 정하고, 실제 비용이 문제인지는 측정해서 판단합니다.
2. 빈 액터에 넣은 Jump 노드
빈 Actor 블루프린트를 만들고 BeginPlay 뒤에 Jump 노드를 이으려고 합니다. 이렇게 하면 이 액터가 캐릭터처럼 점프하게 될까요?
해설 보기
그렇지 않습니다. Jump는 Character 클래스가 가진 기능이라서 점프할 Character를 대상으로 지정해야 하고, 빈 Actor에는 캐릭터의 이동 기능이 없습니다.
플레이어가 점프하는 기능을 만들려면 조작 가능한 Character와 점프 입력의 연결이 먼저 있어야 합니다. 그래서 이 교재는 첫 실습을 플레이어 입력이 필요 없는 BeginPlay 메시지로 시작하고, 캐릭터 조작은 9장에서 플레이어 컨트롤러와 폰을 다룰 때 따로 연결합니다.
3. C++로 옮길지 정하기
팀원이 "블루프린트는 느리니까 적 AI의 거리 계산을 전부 C++로 옮기자"고 제안했습니다. 이 제안을 받아들이기 전에 무엇을 확인해야 할까요?
해설 보기
먼저 그 계산이 얼마나 자주 실행되는지와, 프로파일링에서 실제로 비용이 크게 나타나는지를 확인합니다. 적 몇 마리가 가끔 계산하는 정도라면 옮겨도 체감되는 차이가 없을 수 있고, 수십 마리가 매 Tick 계산하는 것이 측정으로 확인된다면 C++로 옮기거나 실행 빈도를 줄이는 방법을 검토할 만합니다.
또 옮긴 뒤 이 계산을 누가 조정할지도 따져야 합니다. 디자이너가 자주 값을 바꾸는 부분이라면 계산 핵심만 C++에 두고, 조정할 값과 호출 시점은 블루프린트에 남기는 식으로 나눌 수 있습니다.
블루프린트의 종류와 전체 구성은 언리얼 엔진 공식 문서의 블루프린트 소개에서 확인할 수 있습니다.
다음 글 비주얼 스크립팅 기본 개념에서는 실행 선과 데이터 선을 구분하고, 노드가 어떤 순서로 실행되는지 살펴봅니다.