사용자 인터페이스와 환경 연동
시선과 상호작용 범위를 판별해 안내 위젯을 표시하고 월드 상태와 UI 피드백이 일치하도록 연결합니다.
트리거로 상호작용을 만들었다면, 이제 플레이어가 그것을 바로 알아차리게 할 차례입니다. 문 앞에 섰는데 아무 안내가 없으면 플레이어는 이 문이 열리는지, 어떤 키를 눌러야 하는지 몰라 이것저것 눌러 보게 됩니다. 반대로 바라보는 순간 "E 키를 눌러 문 열기"가 뜨면 다음 행동이 자연스러워지고 헤매는 시간도 줄어듭니다.
이 글에서는 위젯 블루프린트로 UI를 만들고 화면에 띄우는 기본을 정리한 뒤, 카메라의 시선으로 상호작용할 대상을 찾고 안내를 띄우는 방법을 만들어 봅니다. 특히 시선이 벽에 닿았을 때 이전 안내를 어떻게 지우는지, 안내가 떠 있다는 것과 실제로 상호작용해도 된다는 것이 왜 다른지를 그림과 함께 짚습니다. 마지막으로 텍스트 말고도 쓸 수 있는 피드백과 성능을 위한 주의점을 정리합니다.
UI를 환경과 연결하는 이유
환경 상호작용에 UI를 연결하면 플레이어 경험이 여러모로 좋아집니다. 무엇과 상호작용할 수 있고 어떻게 해야 하는지를 바로 알려 주고, 세계 안에서 일어나는 일에 시각적인 반응을 주어 몰입을 돕습니다. 다음에 무엇을 해야 할지를 간접적으로 안내하기도 하고, 상호작용할 수 없는 오브젝트에 쓸데없이 시도하는 일도 막아 줍니다.
위젯 블루프린트의 기본
언리얼 엔진에서는 주로 위젯 블루프린트(Widget Blueprint)로 UI를 만들고 화면에 띄웁니다. 위젯 블루프린트는 버튼, 텍스트, 이미지, 진행 막대 같은 UI 요소를 배치하고 기능을 붙이는 에셋입니다. 콘텐츠 브라우저를 오른쪽 클릭해 User Interface의 Widget Blueprint를 골라 만들며, 보통 부모 클래스로 User Widget을 고르고 WBP_InteractionPrompt처럼 WBP_ 접두사를 붙입니다.
위젯 디자이너(Widget Designer)에서는 팔레트(Palette)의 Text, Image, Button 같은 요소를 캔버스로 끌어다 놓고 크기와 위치, 색을 조절합니다. Hierarchy 패널에서 위젯의 계층 구조를 관리하고, Details 패널에서 글자 내용, 이미지, 글꼴, 크기 같은 속성을 정합니다.
그래프(Graph) 탭에서는 위젯의 로직을 블루프린트로 만듭니다. 위젯이 화면 계층에 들어가고 나갈 때 생기는 Construct와 Destruct, 버튼의 OnClicked 같은 이벤트를 쓰며, 글자를 바꾸거나 이미지를 교체하는 것 같은 변화를 여기서 다룹니다. 이때 Construct는 같은 위젯이 화면에 다시 추가될 때 또 호출될 수 있으므로, 한 번만 해야 하는 초기화와는 구분합니다.
위젯을 화면에 띄우려면 블루프린트에서 Create Widget 노드로 위젯 블루프린트의 인스턴스를 만들고, 그 참조로 Add to Viewport를 호출합니다. 화면에서 지울 때는 그 참조로 Remove from Parent를 호출합니다.
시선으로 상호작용 대상 찾기
시선 방식은 플레이어 카메라의 시선이 특정 오브젝트에 닿았을 때 그것을 감지하고 UI를 띄우는 방식입니다. 트리거 방식보다 정교해서, 여러 오브젝트가 가까이 있을 때 바라보는 것 하나만 고르기에 알맞습니다.
라인 트레이스
시선은 라인 트레이스(line trace)로 검사합니다. 라인 트레이스는 시작점(Start)과 끝점(End) 사이의 선분을 따라, 고른 채널에 Block으로 응답하는 첫 번째 물체를 찾는 물리 검사입니다. 주로 Line Trace By Channel 노드를 씁니다.
이 노드의 Start에는 보통 카메라의 월드 위치를, End에는 시작점에서 카메라의 앞 방향으로 일정 거리만큼 간 위치를 넣습니다. Trace Channel은 어떤 충돌 채널로 검사할지로, Visibility나 Camera 같은 채널을 고릅니다. Draw Debug Type은 디버깅을 위해 트레이스를 화면에 그릴지로, None, For One Frame, For Duration, Persistent 가운데 고릅니다. 무언가에 맞았다면 Out Hit으로 맞은 액터와 위치, 법선, 맞은 컴포넌트 같은 정보가 나옵니다.
끝점 계산하기
끝점은 시작점에 방향과 거리를 더해 계산합니다. 예를 들어 시작점이 (100, 0, 100) cm이고, 길이가 1인 앞 방향 벡터가 (1, 0, 0)이며, 상호작용 거리가 250 cm라면, 끝점은 (100, 0, 100) + (1, 0, 0) × 250 = (350, 0, 100) cm입니다. 여기서 250은 월드 원점에서의 좌표가 아니라, 시작점에서 나아갈 거리라는 점에 주의합니다.
시선 안내 만들기
준비
먼저 조종할 수 있는 캐릭터와 카메라를 준비합니다. 아래에 나오는 BP_InteractableBase는 엔진이 기본으로 주는 클래스가 아니라, 이 예에서 쓰는 상호작용 오브젝트의 공통 부모 액터 클래스 이름입니다. 아직 만들지 않았다면, 앞 글의 문 하나만 대상으로 Cast To BP_Door를 써서 먼저 확인해도 됩니다. 이때 Hit Actor가 실제 문 인스턴스인지 확인하고, 상호작용 오브젝트의 종류가 늘어나면 공통 부모 클래스나 인터페이스로 넓혀 갑니다.
안내 위젯도 준비합니다. WBP_InteractionPrompt의 디자이너에 Text 위젯을 놓고 이름을 Text_Prompt로 정한 뒤, 그래프에서 참조할 수 있게 Is Variable을 켭니다. 그리고 위젯 안에 Text 인자를 받는 UpdatePrompt 함수를 만들어 Text_Prompt의 SetText에 연결합니다. 플레이어 쪽에서는 저장한 위젯을 Target으로 UpdatePrompt를 호출해 글자를 바꿉니다. 이때 Text 속성을 바인딩하는 방식과, 매번 SetText로 값을 넣는 방식을 함께 쓰지 않습니다. 둘이 서로 덮어써 글자가 깜빡일 수 있기 때문입니다. 로컬 플레이어 한 명으로 연습한다면 Create Widget의 Class에는 WBP_InteractionPrompt를, Owning Player에는 그 플레이어의 Player Controller를 지정합니다.
마지막으로 PlayerCharacter 블루프린트에 두 변수를 만듭니다. InteractionPromptWidget은 WBP_InteractionPrompt 타입의 오브젝트 참조로, 만든 위젯을 기억합니다. PrevHitActor는 Actor의 오브젝트 참조로, 지금 안내하고 있는 대상을 기억합니다.
매 프레임 시선 검사하기
Event Tick에서 시선을 검사합니다. Get Player Camera Manager로 카메라 매니저를 얻고, 그것에서 Get Camera Location으로 시작점을, Get Camera Rotation으로 카메라의 회전을 얻습니다. 회전을 Get Forward Vector에 넣어 카메라가 바라보는 방향 벡터를 얻고, Vector * Float로 상호작용 거리, 예를 들어 250.0 cm를 곱한 뒤, Vector + Vector로 카메라 위치에 더해 끝점을 만듭니다.
그다음 Line Trace By Channel을 호출해 Start와 End에 계산한 값을 연결하고, Trace Channel은 Visibility로, Draw Debug Type은 디버깅을 위해 For Duration으로 정합니다. 이때 캐릭터 자신의 메시나 캡슐이 첫 번째로 맞는 물체가 되지 않도록 Ignore Self와, 필요하다면 Actors to Ignore도 확인합니다. 트레이스의 Return Value는 무언가에 맞았는지를 알려 주므로 Branch에 연결합니다.
맞은 대상 처리하기
맞았다면 Out Hit을 Break Hit Result로 풀고, Hit Actor를 Cast To BP_InteractableBase에 연결해 지금 바라보는 액터가 상호작용할 수 있는 액터인지 확인합니다.
캐스트에 성공하면 안내를 띄웁니다. InteractionPromptWidget이 유효하지 않다면 Create Widget으로 만들어 참조를 저장하고, 화면에 없다면 Add to Viewport를 호출합니다. 여기서 Remove from Parent로 화면에서 지운 뒤에도 참조 자체는 유효할 수 있으므로, 참조가 유효한지와 화면에 추가되어 있는지를 따로 확인합니다. 그다음 "E 키를 눌러 [오브젝트 이름] 상호작용" 같은 글자로 UpdatePrompt를 호출하고, Set Visibility로 위젯을 보이게 합니다.
대상이 바뀌는 경우도 처리합니다. 지금의 후보가 PrevHitActor와 다르다면, 이전 대상의 강조 표시를 먼저 지우고, 새 대상의 글자를 바꾼 뒤, 새 대상을 PrevHitActor에 저장합니다.
반대로 트레이스가 아무것에도 맞지 않았다면, InteractionPromptWidget이 유효한지 Is Valid로 확인한 뒤 Set Visibility로 위젯을 숨기고, PrevHitActor도 비웁니다. 그리고 여기서 중요한 점은, 캐스트에 실패했거나 거리나 잠금 같은 조건을 통과하지 못한 경우에도 이 정리를 똑같이 해야 한다는 것입니다. 이 정리를 하나의 함수로 만들어 두고 두 곳에서 같이 부르면 빠뜨리지 않습니다.
맞혔다고 다 후보는 아니다
벽을 맞힌 경우에도 이전 상호작용 안내를 지웁니다
시선 첫 Hit 검사 후 유효 후보 갱신 또는 벽/무효/거리 실패에서 이전 안내·하이라이트 정리로 상태가 변한다. 표시와 Interact 재검증은 별도 단계이다.
FIRST BLOCKING HIT · CANDIDATE · PROMPT
벽을 맞힌 경우에도 이전 상호작용 안내를 지웁니다
Trace가 무언가를 맞혔다는 사실과 상호작용 가능한 대상이라는 사실은 다릅니다. UI는 현재 후보를 표현하며, Interact 입력 시에는 실행 조건을 다시 검증합니다.
Blocking Hit가 없거나 벽 같은 비상호작용 Actor를 맞힌 경우, 대상 무효화·거리·잠금 조건 실패는 같은 정리 경로를 호출합니다. Trace=True라도 유효 후보가 아닐 수 있으므로 True 경로만 표시 로직으로 연결하지 않습니다.
표시와 정리는 서로 다른 판단 결과입니다. 별도 Interact 입력에서는 대상 유효성·범위·실행 조건을 다시 확인합니다. Widget 참조가 유효한지와 화면에 추가돼 있는지도 별도로 확인합니다. 전용 Trace Channel을 사용하더라도 벽의 차단을 유지합니다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 관계·조건 설명 | 현재 시선의 첫 Blocking Hit가 없거나 타입·거리·잠금 조건을 만족하지 못하면 같은 정리 경로로 간다. 조건을 모두 만족하면 이전 하이라이트를 정리하고 새 대상의 후보·텍스트를 갱신해 안내를 표시한다. 안내 표시가 실행 권한은 아니며 별도 Interact 입력에서 조건을 재검증한다. |
| 노드·연결선 라벨 | 아니요 |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 첫 Hit 검사 시선 trace의 첫 Hit를 후보로 검증한다. |
|
|
| 후보 표시 유효 후보가 바뀌면 안내·하이라이트를 갱신한다. |
|
|
| 무효·실행 경계 후보가 없어지면 표시를 정리하고 실행 전 다시 검사한다. |
|
|
벽을 맞힌 경우에도 이전 상호작용 안내를 지웁니다
시선 첫 Hit 검사 후 유효 후보 갱신 또는 벽/무효/거리 실패에서 이전 안내·하이라이트 정리로 상태가 변한다. 표시와 Interact 재검증은 별도 단계이다.
FIRST BLOCKING HIT · CANDIDATE · PROMPT
벽을 맞힌 경우에도 이전 상호작용 안내를 지웁니다
Trace가 무언가를 맞혔다는 사실과 상호작용 가능한 대상이라는 사실은 다릅니다. UI는 현재 후보를 표현하며, Interact 입력 시에는 실행 조건을 다시 검증합니다.
Blocking Hit가 없거나 벽 같은 비상호작용 Actor를 맞힌 경우, 대상 무효화·거리·잠금 조건 실패는 같은 정리 경로를 호출합니다. Trace=True라도 유효 후보가 아닐 수 있으므로 True 경로만 표시 로직으로 연결하지 않습니다.
표시와 정리는 서로 다른 판단 결과입니다. 별도 Interact 입력에서는 대상 유효성·범위·실행 조건을 다시 확인합니다. Widget 참조가 유효한지와 화면에 추가돼 있는지도 별도로 확인합니다. 전용 Trace Channel을 사용하더라도 벽의 차단을 유지합니다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
관계도의 보충 설명
| 범위 | 원문 |
|---|---|
| 관계·조건 설명 | 현재 시선의 첫 Blocking Hit가 없거나 타입·거리·잠금 조건을 만족하지 못하면 같은 정리 경로로 간다. 조건을 모두 만족하면 이전 하이라이트를 정리하고 새 대상의 후보·텍스트를 갱신해 안내를 표시한다. 안내 표시가 실행 권한은 아니며 별도 Interact 입력에서 조건을 재검증한다. |
| 노드·연결선 라벨 | 아니요 |
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 첫 Hit 검사 시선 trace의 첫 Hit를 후보로 검증한다. |
|
|
| 후보 표시 유효 후보가 바뀌면 안내·하이라이트를 갱신한다. |
|
|
| 무효·실행 경계 후보가 없어지면 표시를 정리하고 실행 전 다시 검사한다. |
|
|
위 그림은 트레이스의 결과가 어떻게 두 갈래로 나뉘는지 보여 줍니다. 왼쪽에서 현재 시선으로 트레이스해 첫 번째로 막히는 물체를 찾고, 가운데에서 그것이 후보의 조건을 만족하는지 판단합니다. 조건은 첫 Hit의 타입이 상호작용 오브젝트인지, 그리고 거리와 잠금 조건을 통과하는지입니다. 조건을 모두 만족하면 위쪽 갈래로 가서, 이전 강조 표시를 정리하고, 새 대상과 글자를 저장한 뒤 안내를 표시합니다. 조건을 만족하지 못하면 아래쪽 갈래의 같은 정리 경로로 가서, 안내와 강조 표시를 지우고 PrevHitActor를 None으로 비웁니다.
그림의 아래 설명이 강조하듯이, 트레이스가 True라고 해서 유효한 후보인 것은 아닙니다. 시선이 벽처럼 상호작용할 수 없는 물체에 맞아도 트레이스는 True가 되지만, 그것은 후보가 아니므로 정리 경로로 가야 합니다. 그래서 True 쪽만 표시 로직에 연결하면, 벽을 바라본 순간에도 이전 문의 안내가 남는 문제가 생깁니다. 맞은 물체가 없는 경우, 벽 같은 액터에 맞은 경우, 대상이 사라졌거나 거리나 잠금 조건을 통과하지 못한 경우는 모두 같은 정리 경로를 부릅니다.
단계 그림은 첫 Hit를 후보로 검사하는 장면과, 유효한 후보가 바뀌어 안내와 강조 표시를 갱신하는 장면을 차례로 보여 줍니다. 그림의 오른쪽 아래 카드는 세 가지를 덧붙입니다. 안내를 띄우는 것과 지우는 것은 서로 다른 판단의 결과이고, 따로 들어오는 Interact 입력에서는 대상의 유효성과 범위, 실행 조건을 다시 확인해야 합니다. 위젯의 참조가 유효한지와 화면에 추가되어 있는지도 따로 확인하고, 전용 트레이스 채널을 쓰더라도 벽은 시선을 막도록 유지합니다.
입력할 때 다시 확인하기
상호작용 키가 눌린 순간에도 저장해 둔 대상이 여전히 유효한지, 거리 안에 있는지, 실행 조건을 만족하는지 다시 확인합니다. 한 프레임 전에 안내가 떠 있었다고 해서 지금 실행을 허용하지는 않습니다. 그 사이에 대상이 사라졌거나, 다른 플레이어가 먼저 써서 잠겼을 수 있기 때문입니다.
텍스트 말고도 쓸 수 있는 피드백
상호작용의 피드백은 텍스트 말고도 여러 가지로 줄 수 있습니다. "E 키를 눌러 상호작용"이나 "문 열기" 같은 텍스트 안내는 가장 흔하고 분명한 방법입니다. 상호작용할 수 있는 오브젝트에 마우스나 시선이 닿으면 커서 아이콘을 바꾸거나 전용 아이콘을 띄울 수도 있습니다.
오브젝트 자체를 강조할 수도 있습니다. 외곽선이 빛나거나 색이 바뀌게 하면 플레이어의 시선이 그 물체로 갑니다. 오브젝트의 재질에 강조 기능을 넣고 블루프린트에서 Set Scalar Parameter Value로 강조의 세기를 바꾸거나, 커스텀 뎁스(Custom Depth) 렌더링과 후처리 재질(Post Process Material)을 함께 써서 외곽선 효과를 만듭니다.
소리와 움직임도 단서가 됩니다. 상호작용할 수 있는 범위에 들어가거나 오브젝트를 바라볼 때 작은 띵 소리나 UI 효과음을 내면 귀로도 알 수 있고, 스위치가 살짝 튀어나오는 것처럼 오브젝트에 짧은 애니메이션을 주면 상호작용할 수 있다는 것을 암시할 수 있습니다.
성능과 일관성
Event Tick에서 매 프레임 라인 트레이스를 하면 성능에 부담이 될 수 있습니다. 그래서 Set Timer by Event로 0.1초나 0.2초마다 트레이스하게 주기를 조절하되, 그만큼 반응이 늦어지는 정도를 비교합니다. 이때 플레이어가 멈춰 있어도 카메라의 회전이나 대상의 이동, 잠금 상태의 변화는 반영되어야 하므로, 주기를 너무 길게 잡지 않습니다.
전용 트레이스 채널을 만들어 상호작용 대상의 규칙을 따로 관리할 수도 있습니다. 다만 벽도 그 채널을 막아야 벽 너머의 대상을 고르지 않습니다. 상호작용 대상만 Block하도록 만드는 설정과, 실제로 보이는지를 판단하는 시야 검사는 서로 다르다는 점을 혼동하지 않습니다.
UI 피드백은 분명하고 간결해야 하며, 너무 많은 정보나 어지러운 효과는 피합니다. 게임 안의 모든 상호작용 오브젝트에 같은 방식의 피드백을 쓰면 플레이어가 빨리 익숙해집니다. 상호작용 키는 입력 액션으로 정의해 두면 플레이어가 키를 바꿀 수 있습니다.
정리
- UI는 위젯 블루프린트로 만들고,
Create Widget과Add to Viewport로 띄우고Remove from Parent로 지우며,Construct는 다시 호출될 수 있습니다. - 시선은
Line Trace By Channel로 검사하며, 끝점은 시작점 + 앞 방향 × 거리이고, 캐릭터 자신이 맞지 않게 Ignore Self를 확인합니다. - 트레이스가 True라고 유효한 후보는 아니므로, 맞은 것이 없을 때와 비상호작용 물체에 맞았을 때, 조건을 통과하지 못했을 때 모두 같은 정리 경로를 부릅니다.
- 후보가 바뀌면 이전 강조를 지우고 새 대상을 저장하며, 위젯의 참조가 유효한지와 화면에 있는지를 따로 확인합니다.
- 안내가 떠 있다는 것은 실행 권한이 아니므로, 입력할 때 대상의 유효성과 거리, 조건을 다시 확인합니다.
- 텍스트, 아이콘, 강조, 소리, 애니메이션으로 피드백을 주고, 트레이스 주기와 전용 채널로 비용을 줄이되 벽의 차단은 유지합니다.
연습 문제
1. 끝점은 어디일까
카메라 위치가 (0, 200, 150) cm이고, 길이가 1인 앞 방향 벡터가 (0, 1, 0)이며, 상호작용 거리가 300 cm입니다. 라인 트레이스의 끝점은 어디일까요?
해설 보기
끝점은 시작점 + 앞 방향 × 거리이므로 (0, 200, 150) + (0, 1, 0) × 300 = (0, 500, 150) cm입니다.
300은 월드 원점에서의 좌표가 아니라 시작점에서 나아갈 거리이므로, 끝점의 Y가 300이 아니라 500이 됩니다.
2. 벽을 봐도 남는 안내
문을 바라보면 안내가 뜨는데, 고개를 돌려 옆의 벽을 바라봐도 문의 안내가 그대로 남아 있습니다. 트레이스가 아무것에도 맞지 않을 때는 안내가 잘 사라집니다. 무엇이 문제일까요?
해설 보기
벽을 바라볼 때도 트레이스는 벽에 맞으므로 Return Value가 True가 됩니다. 그런데 True 쪽에서는 캐스트에 실패해도 아무것도 하지 않고, 정리는 False 쪽에서만 하고 있기 때문입니다.
캐스트에 실패하거나 거리, 잠금 조건을 통과하지 못한 경우에도 False 쪽과 같은 정리 함수를 불러 안내와 강조를 지우고 PrevHitActor를 비웁니다. 트레이스가 맞았다는 것과 유효한 후보라는 것은 다른 판단입니다.
3. 안내는 떠 있는데 실행해도 될까
플레이어가 상자를 바라봐 "E 키를 눌러 열기" 안내가 떠 있는 상태에서 E를 눌렀습니다. 그 사이에 상자가 다른 이벤트로 잠겼습니다. 안내가 떠 있었으니 상자를 열어도 될까요?
해설 보기
안 됩니다. 안내는 지난 검사의 결과를 보여 줄 뿐, 지금 실행해도 된다는 권한이 아닙니다. 그 사이에 상자가 잠겼으므로 실행 조건을 만족하지 않습니다.
그래서 입력이 들어온 순간에 저장해 둔 대상이 여전히 유효한지, 거리 안에 있는지, 잠금 같은 실행 조건을 만족하는지 다시 확인하고, 조건을 통과할 때만 상자를 엽니다.
트레이스의 종류와 설정은 Epic의 트레이스 안내에서 확인할 수 있습니다.
다음 글 동적 오브젝트 및 상호작용에서는 움직이는 발판이나 밀 수 있는 상자처럼 스스로 움직이거나 물리로 반응하는 오브젝트를 만들고 상호작용에 연결합니다.