직접 참조를 통한 블루프린트 통신
대상 액터 참조를 얻어 변수와 함수를 직접 호출하고 캐스트·수명 관리·결합도 측면의 장단점을 판단합니다.
블루프린트로 게임을 만들다 보면 한 블루프린트가 다른 블루프린트에 일을 시켜야 하는 순간이 계속 생깁니다. 플레이어 캐릭터가 문에게 "열려라"라고 명령하거나, 폭발이 주변의 적들에게 "대미지를 받아라"라고 알리는 식입니다. 이렇게 블루프린트끼리 정보를 주고받거나 기능을 호출하는 것을 블루프린트 통신(Blueprint Communication)이라고 합니다.
4장에서는 블루프린트 통신의 여러 방법을 차례로 다룹니다. 이 글에서는 가장 기본이 되는 직접 참조(direct reference)를 살펴봅니다. 참조 변수를 만들고, 그 변수에 실제 인스턴스를 넣는 여러 방법을 비교한 뒤, 참조로 함수를 호출할 때 지켜야 할 유효성 확인과 이 방법의 장단점을 정리합니다.
특정 인스턴스를 가리키는 변수
직접 참조는 한 블루프린트가 다른 특정 블루프린트 인스턴스를 가리키는 연결을 직접 가지는 것입니다. 친구의 집 주소를 정확히 알고 있으면 그 집에 바로 찾아가 부탁할 수 있는 것과 비슷합니다. 다만 주소를 적을 종이를 마련하는 일과 그 종이에 실제 주소를 적는 일이 따로 있듯이, 참조도 변수를 만드는 일과 그 변수에 인스턴스를 넣는 일이 따로 있습니다.
언리얼 엔진에서 직접 참조는 주로 변수로 만듭니다. 어떤 블루프린트가 다른 블루프린트 타입의 변수를 가지면, 그 변수에 특정 인스턴스를 넣어 연결을 만들 수 있습니다. 공식 문서도 직접 통신을 스위치가 특정 문을 여는 것처럼 한 액터가 다른 특정 액터를 제어하는 일대일 연결로 설명하며, 이때 대상 액터의 참조가 반드시 필요하다고 말합니다.
참조 변수 만들기
통신을 시작할 블루프린트, 예를 들어 BP_PlayerCharacter를 엽니다. 마이 블루프린트(My Blueprint) 패널에서 변수를 하나 추가하고 이름을 TargetDoor로 정한 뒤, 변수 타입(Variable Type)을 참조하려는 블루프린트 클래스인 BP_Door의 Object Reference로 바꿉니다. 이제 이 변수는 BP_Door 타입의 액터 인스턴스를 담을 수 있습니다.
여기서 가장 많이 놓치는 부분은 변수를 만든 것만으로는 아무 문과도 연결되지 않는다는 점입니다. 새로 만든 참조 변수의 값은 비어 있는 None이며, 월드에 있는 특정 문 인스턴스를 이 변수에 넣어 주어야 비로소 그 문을 가리킵니다. 인스턴스를 넣는 방법은 크게 에디터에서 손으로 지정하는 방법과 블루프린트 로직으로 찾아 넣는 방법이 있습니다.
에디터에서 인스턴스 지정하기
가장 간단한 방법은 디테일 패널에서 직접 지정하는 것입니다. BP_PlayerCharacter 블루프린트 에디터에서 TargetDoor 변수를 선택하고, 디테일 패널에서 인스턴스 편집 가능(Instance Editable)을 켭니다. 마이 블루프린트 패널의 변수 옆 눈 아이콘을 눌러 열린 눈 모양으로 만들어도 같습니다. 이렇게 하면 레벨에 배치된 BP_PlayerCharacter 인스턴스마다 이 변수를 따로 편집할 수 있습니다.
컴파일한 뒤 레벨 에디터로 돌아와 레벨에 배치된 BP_PlayerCharacter를 선택하면, 디테일 패널에 TargetDoor가 보입니다. 옆의 드롭다운에서 레벨의 BP_Door 인스턴스를 고르거나, 스포이드 아이콘으로 뷰포트의 문을 직접 찍거나, 아웃라이너(Outliner)에서 문 액터를 끌어다 칸에 놓으면 됩니다.
이 방법은 가장 단순하고 어느 인스턴스와 연결되었는지 분명합니다. 그 대신 연결을 하나하나 손으로 해야 하므로 오브젝트가 많거나 플레이 중에 생성되는 경우에는 쓰기 어렵습니다. 또 이 방법은 레벨에 배치한 바로 그 BP_PlayerCharacter 인스턴스를 플레이에서 실제로 쓴다는 조건에서만 의미가 있습니다. 게임 모드가 새 캐릭터를 스폰해 조종하는 프로젝트라면, 레벨에 따로 놓은 캐릭터의 TargetDoor를 아무리 지정해도 실제로 조종하는 캐릭터의 값은 바뀌지 않습니다. 그래서 먼저 플레이 중에 실행되는 인스턴스가 어느 것인지 확인하고, 스폰되는 경우에는 로직으로 값을 준비합니다.
로직으로 인스턴스 찾아 넣기
게임이 시작될 때 찾아 넣을 수도 있습니다. Event BeginPlay 뒤에 Get All Actors Of Class 노드를 두고 Actor Class에 BP_Door를 고르면, 이 노드는 현재 월드에 있는 BP_Door 액터들의 배열을 돌려줍니다. 문이 하나뿐이라고 가정한다면 배열 길이가 1인지 확인한 뒤 첫 요소를 TargetDoor에 넣고, 문이 여럿이라면 태그나 거리처럼 분명한 기준으로 대상을 고릅니다. 배열에 담긴 순서는 특정 문을 고르는 기준이 될 수 없으므로 "첫 번째가 정문일 것"이라고 가정하지 않습니다. 이 방법은 손으로 설정할 필요가 없는 대신, 찾는 시점에 아직 생성되지 않은 액터는 찾지 못합니다. 문을 직접 스폰한다면 Spawn Actor From Class가 성공했을 때 돌려주는 반환값을 그대로 저장하는 편이 더 확실합니다.
플레이어가 문에 다가갔을 때 넣는 방법도 있습니다. 플레이어 캐릭터에 Sphere Collision 컴포넌트를 추가하고, 양쪽 컴포넌트의 겹침 이벤트 생성과 콜리전 응답을 설정합니다. 그리고 Sphere Collision의 Begin Overlap 이벤트를 만든 뒤, 이벤트의 Other Actor 핀을 Cast To BP_Door 노드에 연결해 겹친 액터가 BP_Door인지 확인합니다. 캐스트가 성공하는 실행 핀에서만 Set TargetDoor를 실행하고, 그 값으로 Cast To BP_Door의 As BP Door 출력을 연결합니다.
이 방법은 플레이어가 지금 상호작용할 수 있는 대상을 그때그때 가리킬 수 있습니다. 하지만 겹침이 끝나도 변수가 저절로 비워지지는 않습니다. 그래서 End Overlap 이벤트에서 겹침이 끝난 액터가 현재 TargetDoor와 같을 때만 참조를 비우고, 여러 문과 동시에 겹칠 수 있다면 어느 문을 고를지 기준을 따로 정합니다. 또 참조가 유효하다는 사실만으로 플레이어가 아직 상호작용할 거리 안에 있다는 것까지 확인되지는 않습니다.
참조로 함수를 호출하고 유효성 확인하기
TargetDoor에 BP_Door 인스턴스가 들어 있다면, 이 변수를 통해 BP_Door의 함수나 변수 가운데 외부 접근이 허용된 것을 쓸 수 있습니다. 플레이어가 상호작용 키를 눌렀을 때 그 문을 열고 싶다면, 상호작용 입력 이벤트에서 출발합니다. 이 예는 캐릭터가 입력을 받도록 설정되어 있다고 가정하며, 향상된 입력(Enhanced Input)을 쓰는 프로젝트라면 상호작용용 Input Action의 이벤트를 씁니다.
그래프에 TargetDoor의 Get 노드를 놓고, 출력 핀에서 드래그해 BP_Door에 정의된 OpenDoor 함수를 검색해 호출 노드를 만듭니다. 그다음 입력 이벤트의 실행 핀을 실행 분기가 있는 Is Valid 노드에 연결하고, 검사할 대상에 TargetDoor를 넣습니다. 유효한 쪽(Is Valid) 실행 핀에서만 OpenDoor를 실행하고, 유효하지 않은 쪽(Is Not Valid)은 아무것도 호출하지 않고 끝내거나 "가까이에 문이 없습니다" 같은 안내를 보여 줍니다.
이 확인이 중요한 이유는 참조가 비어 있을 때 일어나는 일 때문입니다. TargetDoor에 유효한 액터가 없는 상태에서 함수를 호출하면 Accessed None 런타임 오류가 나고, 의도한 처리는 실행되지 않습니다. 이 오류는 직접 참조에서 가장 흔한 문제이므로, 참조로 호출하기 전에는 항상 유효성을 확인하는 습관을 들입니다.
직접 참조의 장점과 단점
직접 참조는 직관적이고 간단합니다. 특정 오브젝트에 바로 접근하므로 그래프를 읽는 사람도 무엇이 무엇을 부르는지 바로 이해할 수 있습니다. 또 한 번 얻은 참조를 저장해 다시 쓰면 호출할 때마다 월드의 액터를 다시 찾을 필요가 없습니다. 실제 비용은 참조를 어떻게 얻었는지와 호출하는 로직에 따라 달라집니다.
단점은 강한 결합(tight coupling)입니다. 호출하는 블루프린트(플레이어)가 호출받는 블루프린트(문)의 구체적인 클래스 타입 BP_Door를 알아야 하므로, 나중에 문 대신 레버 같은 다른 상호작용 오브젝트를 추가하면 플레이어 블루프린트의 로직을 고쳐야 할 수 있습니다. 또 동적으로 생기고 사라지는 오브젝트나 여러 종류의 오브젝트에 공통으로 적용하기 어렵고, 앞에서 본 것처럼 참조가 비어 있으면 Accessed None 오류가 납니다.
그래서 직접 참조는 특정 인스턴스와, 그 구체 타입만 가진 기능이 필요할 때 알맞습니다. 여러 클래스에 같은 능력을 요청한다면 블루프린트 인터페이스를 통한 통신을, 한 사건을 여러 구독자에게 알린다면 이벤트 디스패처의 기본 사용법을 함께 검토합니다. 다만 인터페이스로 호출할 때도 메시지를 보낼 대상 객체의 참조는 여전히 필요합니다.
정리
- 직접 참조는 특정 인스턴스를 가리키는 변수로 만들며, 변수를 만든 것만으로는 None이므로 실제 인스턴스를 넣어 주어야 합니다.
- Instance Editable을 켜면 레벨의 인스턴스마다 디테일 패널에서 참조를 지정할 수 있지만, 그 인스턴스가 플레이에서 실제로 쓰이는지 먼저 확인해야 합니다.
Get All Actors Of Class의 배열 순서로 대상을 고르지 않고, 스폰한 액터는Spawn Actor From Class의 반환값을 저장합니다.- 겹침으로 넣은 참조는 저절로 비워지지 않으므로, End Overlap의 대상이 현재 참조와 같을 때만 비웁니다.
- 참조로 호출하기 전에는
Is Valid로 확인해Accessed None오류를 막습니다.
연습 문제
1. 겹침이 끝난 문과 현재 대상
문 A와 겹쳐 TargetDoor에 A를 저장한 뒤, 선택 규칙에 따라 문 B를 새 대상으로 저장했습니다. 이후 A의 End Overlap이 발생했고 B는 아직 범위 안에 있습니다. 모든 End Overlap에서 무조건 TargetDoor를 None으로 바꾸면 B의 참조도 사라질까요?
해설 보기
사라집니다. 현재 값은 B인데, 이미 지나간 A의 겹침 종료 이벤트가 그 값을 지우기 때문입니다. 플레이어는 B 앞에 서 있는데도 상호작용 키를 누르면 "문이 없다"는 결과가 나옵니다.
End Overlap에서는 겹침이 끝난 액터가 현재 TargetDoor와 같은 경우에만 참조를 비웁니다. 대상이 여러 개일 수 있다면 남은 후보 가운데 무엇을 고를지 규칙도 필요합니다. 참조가 유효한지, 선택된 인스턴스가 무엇인지, 그 대상이 지금 범위 안에 있는지는 각각 따로 확인해야 하는 조건입니다.
2. 지정했는데 바뀌지 않는 참조
레벨에 BP_PlayerCharacter를 하나 배치하고 디테일 패널에서 TargetDoor에 정문을 지정했습니다. 그런데 플레이해서 상호작용 키를 누르면 Is Valid의 유효하지 않은 쪽으로만 흐릅니다. 게임 모드는 시작할 때 새 캐릭터를 스폰해 조종하도록 설정되어 있습니다. 원인은 무엇일까요?
해설 보기
플레이어가 조종하는 캐릭터는 게임 모드가 새로 스폰한 인스턴스이고, 디테일 패널에서 값을 지정한 것은 레벨에 따로 놓인 다른 인스턴스이기 때문입니다. 두 인스턴스는 같은 클래스일 뿐 변수 값을 공유하지 않으므로, 조종 중인 캐릭터의 TargetDoor는 여전히 None입니다.
이럴 때는 플레이 중에 실행되는 인스턴스를 먼저 확인합니다. 스폰되는 캐릭터라면 BeginPlay에서 대상을 찾거나 겹침으로 넣는 등 로직으로 값을 준비해야 합니다.
3. 유효성 확인을 빼면
Is Valid 없이 상호작용 입력에서 바로 TargetDoor의 OpenDoor를 호출하게 바꾸었습니다. 문에서 멀리 떨어진 곳에서 키를 누르면 어떤 일이 생길까요?
해설 보기
겹침으로 참조를 넣는 방식이라면, 아직 어떤 문과도 겹친 적이 없을 때 TargetDoor는 None입니다. 이 상태에서 함수를 호출하면 Accessed None 런타임 오류가 나고 OpenDoor는 실행되지 않습니다.
반대로 예전에 겹쳤던 문이 End Overlap 처리 없이 남아 있다면, 멀리 있는데도 그 문이 열리는 문제가 생길 수 있습니다. 그래서 호출 전에 Is Valid로 참조를 확인하고, 거리 같은 상호작용 조건은 겹침 처리나 별도의 검사로 따로 확인합니다.
블루프린트 통신 방법의 종류와 각각을 쓰는 상황은 언리얼 엔진 공식 문서의 블루프린트 통신 사용법에서 확인할 수 있습니다.
다음 글 이벤트 디스패처의 기본 사용법에서는 한 블루프린트에서 일어난 사건을 여러 블루프린트에 한꺼번에 알리는 이벤트 디스패처를 살펴봅니다.