환경과 상호작용하는 오브젝트 설정
입력·트리거·참조·상태 변화를 연결해 문 열기 예제를 구현하고 재사용 가능한 환경 상호작용 구조를 익힙니다.
이전 챕터들에서 조명, 날씨, 시간 같은 시각 요소와 물리 엔진, 충돌 같은 물리 요소를 다듬어 레벨의 기반을 만들었습니다.
이제는 그 기반 위에 플레이어의 행동이 실제로 세계를 바꾸는 환경 상호작용(Environmental Interaction)을 올릴 차례입니다.
문을 열고, 스위치를 누르고, 아이템을 줍는 순간처럼 월드가 즉시 반응할 때 게임은 훨씬 더 살아 있게 느껴집니다.
이번 절에서는 언리얼 엔진에서 이런 상호작용을 안정적으로 구성하는 방법을 단계적으로 다룹니다.
이 흐름을 익히고 나면 플레이어에게 내가 세계에 영향을 주고 있다는 감각을 자연스럽게 전달할 수 있습니다.
환경 상호작용의 중요성
환경 상호작용은 게임 플레이의 핵심이자 레벨 디자인의 중요한 부분입니다.
- 몰입감 증대: 플레이어가 주변 환경에 영향을 미치고, 환경이 플레이어의 행동에 반응할 때 게임 세계는 더욱 현실적이고 몰입감 있게 느껴집니다.
- 게임 플레이 다양성: 스위치 조작, 문 열기, 퍼즐 풀이, 아이템 획득 등 다양한 상호작용은 게임 플레이에 깊이와 다양성을 더합니다.
- 내러티브 강화: 환경과의 상호작용은 스토리를 진행하거나, 세계관에 대한 정보를 제공하는 중요한 수단이 될 수 있습니다.
- 문제 해결 및 탐험 유도: 잠긴 문을 열거나 숨겨진 스위치를 찾는 등의 상호작용은 플레이어에게 문제 해결과 탐험의 동기를 부여합니다.
기본적인 환경 상호작용 구현 요소
언리얼 엔진에서 환경 상호작용을 구현하기 위해 주로 사용되는 액터, 컴포넌트, 그리고 개념들은 다음과 같습니다.
트리거 (Triggers)
트리거는 플레이어(또는 다른 액터)가 특정 영역에 진입하거나 이탈했을 때 이벤트를 발생시키는 투명한 볼륨입니다.
상호작용의 시작을 감지하는 가장 기본적인 방법입니다.
-
종류
- Box Collision (박스 충돌): 가장 흔히 사용되는 사각형 형태의 트리거.
- Sphere Collision (구 충돌): 원형 또는 구 형태의 트리거.
- Capsule Collision (캡슐 충돌): 캐릭터 충돌에 주로 사용되는 캡슐 형태의 트리거.
-
생성 및 설정
레벨 에디터의 액터 배치(Place Actors) 검색에서
Trigger Box나Trigger Sphere를 찾아 배치합니다.또는 기존 액터에
Box Collision등의 컴포넌트를 추가할 수도 있습니다.Details패널에서Collision섹션을 찾습니다.Collision PresetsOverlapOnlyPawn: 기본 프리셋은 Pawn·Camera·Vehicle에 Overlap 응답을 둡니다. 플레이어 전용 필터는 별도로 필요합니다.OverlapAll: 기본 채널에 Overlap 응답을 둡니다. 상대 채널 응답과 이벤트 생성 옵션도 맞아야 합니다.Custom...: 특정 오브젝트 타입과만 오버랩하도록 세부 설정할 수 있습니다.
Generate Overlap Events: 트리거와 상대 컴포넌트 양쪽에서 활성화하고 query 충돌도 켜야 합니다. -
블루프린트 활용
- Actor Blueprint에 추가한 충돌 컴포넌트를 선택하고
OnComponentBeginOverlap과OnComponentEndOverlap이벤트를 추가합니다. 레벨의 Trigger Actor는 Level Blueprint의 Actor overlap 이벤트로도 연결할 수 있습니다. - 이벤트 그래프에서
Other Actor핀을Cast To PlayerCharacter(또는 여러분의 플레이어 클래스)로 연결하여, 오직 플레이어에 의해서만 이벤트가 발생하도록 필터링합니다.
- Actor Blueprint에 추가한 충돌 컴포넌트를 선택하고
상호작용 가능한 오브젝트
문, 스위치, 아이템 등 플레이어가 직접 행동하여 조작하는 오브젝트입니다.
-
기본 구조
Static Mesh Component: 오브젝트의 시각적인 형태를 제공합니다. (예: 문의 뼈대, 스위치의 버튼 모델)
Collision Component: 플레이어가 상호작용 영역에 들어왔는지 감지하거나, 오브젝트와 물리적으로 충돌하는 역할을 합니다. (자세한 내용은 7장 2절 충돌 참고)
블루프린트 로직: 상호작용 시 실행될 행동을 정의합니다. (예: 문 열기 애니메이션 재생, 스위치 상태 변경)
-
상호작용 방식
- 트리거 기반: 플레이어가 오브젝트 근처의 특정 트리거 영역에 진입했을 때 상호작용 메시지(예: E 키를 눌러 상호작용)를 표시하고, 플레이어가 키를 누르면 상호작용이 발생합니다.
- 시선 기반 (Line Trace): 플레이어의 카메라 시선이 오브젝트에 닿았을 때 상호작용 가능 여부를 판단하고, 키를 누르면 상호작용합니다. (자세한 내용은 8장 2절에서 다룸)
- 충돌 기반 (Hit Events): 오브젝트에 직접 부딪혔을 때 상호작용이 발생하는 경우. (예: 발로 차서 문 열기)
블루프린트 (Blueprints)
환경 상호작용 로직의 대부분은 블루프린트를 통해 구현됩니다.
- 액터 블루프린트: 문, 스위치, 아이템 등 각 상호작용 가능한 오브젝트를 개별적인 액터 블루프린트(
BP_Door,BP_Switch,BP_Item)로 만듭니다. - 이벤트 그래프:
OnComponentBeginOverlap,OnComponentEndOverlap, 그리고 플레이어 입력 이벤트(예:Input Action Interact)를 사용하여 상호작용 로직을 구현합니다. - 컴포넌트 조작:
Static Mesh컴포넌트의 가시성/위치/회전 변경,Timeline을 이용한 애니메이션,Play Sound at Location등의 노드를 사용하여 상호작용 결과를 시각적/청각적으로 표현합니다.
상호작용 가능한 오브젝트 구현 예시 (문 열기)
상호작용 오브젝트는 배치만으로 동작하지 않고 트리거, 상태, 피드백이 함께 준비되어야 합니다.
가장 기본적인 환경 상호작용인 문 오브젝트를 만들어 봅시다.
먼저 캐릭터 준비 안내를 확인합니다. 이미 조작할 수 있는 Character가 있다면 그대로 사용하고, Blank에서 시작해 아직 없다면 이동·카메라·입력을 준비한 뒤 이 예제로 돌아옵니다. 여기서도 PlayerCharacter는 실제 플레이어 Character 블루프린트의 예시 이름입니다.
Interact는 이름만 적으면 생기는 입력 이벤트가 아닙니다. 입력 설정에 맞춰 키를 매핑하고, Character 또는 Controller 중 한 곳에서 이벤트를 받도록 연결합니다. 한 번 누를 때 한 번만 요청하도록 준비하며, 같은 키를 두 곳에서 받아 문을 중복 호출하지 않습니다.
- 콘텐츠 브라우저에서 마우스 오른쪽 버튼을 클릭한 뒤
블루프린트 클래스(Blueprint Class)선택. - 부모 클래스는
Actor로, 이름은BP_Door로 지정합니다.
컴포넌트 추가: BP_Door 블루프린트를 열고 컴포넌트(Components) 패널에서 다음을 추가합니다.
Static Mesh (DoorFrame): 문틀이 될 메시를 할당합니다. (예:SM_DoorFrame)Scene Component (Hinge)와 자식Static Mesh (Door): Hinge를 경첩 위치에 놓고 Door 메시를 자식으로 붙여 오프셋을 맞춥니다. 회전할 Hinge와 Door는 Movable로 두며 Simulate Physics는 끕니다.Box Collision (InteractionVolume): 문 앞에 놓일 트리거 볼륨입니다. 문이 열리는 영역이 됩니다.Details > Collision Presets의 Custom에서 Pawn만 Overlap, 나머지는 Ignore로 두고 Generate Overlap Events를 켭니다. 플레이어 캡슐 쪽 설정도 맞춥니다.
-
플레이어 쪽 후보 문 참조
- 플레이어 Character 블루프린트에
CandidateDoor변수를 만들고, 타입을BP_Door의Object Reference로 정합니다. 초기값은None입니다. 이 변수는 레벨에 놓인 문 인스턴스를 저장하며,Class Reference로 정하지 않습니다.
- 플레이어 Character 블루프린트에
-
변수 추가
bIsOpen(Boolean, 초기 False): 완전히 열린 상태인지 저장합니다.bIsAnimating(Boolean, 초기 False): 이동 중 재입력을 막습니다.ClosedRotation/OpenRotation(Rotator): BeginPlay에서 Hinge의 상대 회전을 ClosedRotation으로 저장하고, 로컬 Z축 Yaw에 90도를 더해 OpenRotation을 한 번 정합니다.
-
오버랩 이벤트
InteractionVolume컴포넌트를 선택하고OnComponentBeginOverlap (InteractionVolume)이벤트를 추가합니다.OnComponentEndOverlap (InteractionVolume)이벤트를 추가합니다.- 두 이벤트에서
Other Actor를 실제PlayerCharacter클래스로 캐스팅하고,Other Comp가 그 캐릭터의Capsule Component인지 확인합니다. - BeginOverlap에서
As PlayerCharacter의Set CandidateDoor값에 문의Self를 연결합니다. - EndOverlap에서는 같은 플레이어·캡슐 필터를 통과한 뒤 그 캐릭터의
CandidateDoor와 문의Self를 객체 비교(==)합니다. 이 문이 현재 후보일 때만CandidateDoor를None으로 비웁니다. 다른 문으로 바뀐 후보를 함께 지우지 않습니다.
-
상호작용 UI 표시/숨김 (선택 사항)
OnComponentBeginOverlap시 플레이어 HUD에 E 키를 눌러 문 열기 같은 텍스트 위젯을 표시하는 로직을 추가합니다. (이때 위젯 블루프린트와 연결해야 합니다.)OnComponentEndOverlap시 해당 위젯을 제거합니다.
-
문 열림/닫힘 로직 (Input Event)
BP_Door에ToggleDoor커스텀 이벤트를 만들고, 입력 인자RequestingPlayer를Character Object Reference로 추가합니다. 이 인자는 문 열기를 요청한 캐릭터 인스턴스입니다.- Character에서
Interact를 받으면CandidateDoor를Is Valid로 확인한 뒤, 그 문을Target으로ToggleDoor를 호출합니다.RequestingPlayer에는 Character의Self를 전달합니다. - Controller에서 입력을 받는다면 매 호출 시
Get Controlled Pawn을 실제PlayerCharacter클래스로 캐스팅합니다. 캐스트가 성공한 뒤 그 캐릭터의CandidateDoor도Is Valid로 확인하고, 같은 방식으로 그 문을Target으로 호출합니다.RequestingPlayer에도 그 캐릭터를 전달합니다. 일반 문 Actor에 입력 노드를 추가하는 것만으로 입력이 활성화되지는 않습니다. - 문의
ToggleDoor시작에서RequestingPlayer를Is Valid로 확인한 뒤, 그 캐릭터의Capsule Component를InteractionVolume의Is Overlapping Component에 연결해 범위 안인지 확인합니다. 이 검사는 요청할 때마다 다시 진행합니다. 범위 밖이거나bIsAnimating이 True라면 요청을 무시합니다. - 요청이 유효하면 먼저
bIsAnimating=True로 둡니다.bIsOpen=False이면 Play, True이면 Reverse로 분기합니다. 상태와 별도의 FlipFlop을 중복 관리하지 않습니다. -
문 회전 (Timeline 사용)
Timeline노드를 추가하고 이름을DoorAnimation으로 지정합니다.DoorAnimation을 더블클릭하여 타임라인 에디터를 엽니다.Track에Add Float Track을 추가하고 이름을Alpha로 지정합니다.Alpha트랙에 키를 추가합니다. (예: 시간 0.0, 값 0.0)두 번째 키를 추가합니다. (예: 시간 1.0, 값 1.0)
키의 보간을 조절해 부드러운 커브를 만듭니다. Alpha의 범위는 0~1, Timeline 길이는 1초로 맞춥니다.
Timeline의 Update에서 Set Relative Rotation을 실행하고, 순수 노드 Lerp (Rotator)의 출력을 연결합니다.A핀: 시작 시 저장한ClosedRotationB핀: 시작 시 정한OpenRotationAlpha핀: 타임라인의Alpha출력 핀 연결
Lerp (Rotator)의 결과를Set Relative Rotation (Hinge)노드의New Rotation핀에 연결합니다.DoorAnimation타임라인의Play핀을 문 여는 로직에,Reverse핀을 문 닫는 로직에 연결합니다.Finished에서 Direction이 Forward이면
bIsOpen=True, Backward이면 False로 확정하고bIsAnimating=False로 풉니다. Update 중의 현재 회전을 보간 시작값으로 다시 읽지 않습니다.
- 사운드 추가:
Play Sound at Location노드를 사용하여 문이 열리거나 닫힐 때 적절한 사운드를 재생합니다.
초기 상태가 닫힘이고 진행 중 입력을 무시하는 이 예제의 전환은 다음과 같습니다.
재입력을 막는 문 열림·닫힘 상태
닫힘→여는 중→열림→닫는 중→닫힘과 bIsAnimating 입력 잠금/Finished 확정의 실제 상태 기계이다.
DOOR · STATE TRANSITIONS
문이 움직이는 동안은 재입력을 무시합니다
닫힘에서 시작하고 유효한 입력에 Play·Reverse로 반응합니다. Timeline이 끝나면 bIsOpen을 확정하고 입력 잠금을 풉니다.
입력 잠금과 완료 상태
여는 중·닫는 중은 bIsAnimating = true입니다. Finished에서 Direction에 따라 bIsOpen을 정하고 bIsAnimating = false로 바꿉니다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 닫힘·요청 닫힌 문에 열기 입력이 들어온다. |
|
|
| 여는 중·완료 Timeline 완료에서 열린 상태를 확정한다. |
|
|
| 닫기 전환 다음 유효 입력이 역방향 전환을 시작한다. |
|
|
재입력을 막는 문 열림·닫힘 상태
닫힘→여는 중→열림→닫는 중→닫힘과 bIsAnimating 입력 잠금/Finished 확정의 실제 상태 기계이다.
DOOR · STATE TRANSITIONS
문이 움직이는 동안은 재입력을 무시합니다
닫힘에서 시작하고 유효한 입력에 Play·Reverse로 반응합니다. Timeline이 끝나면 bIsOpen을 확정하고 입력 잠금을 풉니다.
입력 잠금과 완료 상태
여는 중·닫는 중은 bIsAnimating = true입니다. Finished에서 Direction에 따라 bIsOpen을 정하고 bIsAnimating = false로 바꿉니다.
이 예의 조건
- 본문과 원 도표에 제시된 사례의 입력·조건을 사용합니다. 설명용 수치나 독립 비교 경로는 각 단계에 표시합니다.
- 현재 상태·값과 전환 근거의 위치는 모든 단계에서 같습니다. 조건이 맞지 않거나 실행되지 않은 경로를 성공한 동작으로 해석하지 않습니다.
| 관찰·상황 | 값·보충 정보 | 변화·이유 |
|---|---|---|
| 닫힘·요청 닫힌 문에 열기 입력이 들어온다. |
|
|
| 여는 중·완료 Timeline 완료에서 열린 상태를 확정한다. |
|
|
| 닫기 전환 다음 유효 입력이 역방향 전환을 시작한다. |
|
|
문에 가까이 갔는데 CandidateDoor가 계속 None이면 양쪽 Overlap 설정, 플레이어 클래스 캐스트, Other Comp의 캡슐 필터를 먼저 확인합니다.
닫힘 상대 Yaw를 0도, 열림을 90도로 정한 경우 Alpha가 0.5이면 보간 회전은 45도입니다. 여기서 Alpha는 열린 각도가 아니라 두 끝점 사이의 비율입니다. 현재 회전을 매 Update마다 A로 다시 넣으면 시작점도 계속 바뀌므로 같은 커브를 재생해도 정한 0→90도 전환과 달라집니다.
배치한 문 하나에서 닫힘→열기→완료→닫기→완료를 먼저 확인합니다. 여는 중 재입력과 범위 밖 요청은 각각 무시되어야 합니다. 선택 UI를 함께 구현했다면 EndOverlap에서 현재 후보가 이 문인지 확인한 분기 안에서만 후보와 해당 프롬프트를 함께 비웁니다. 다른 문으로 선택이 바뀐 뒤 이전 문의 이탈 이벤트가 새 안내까지 지우지 않게 합니다.
확인 문제: 여는 중 두 번째 입력을 받으면 방향이 뒤집힐까요?
이 예제는 bIsAnimating이 True일 때 요청을 무시하므로 계속 열립니다. 정방향 Finished에서 bIsOpen=True와 bIsAnimating=False를 확정한 다음 유효 입력을 받아야 Reverse로 닫습니다. 즉시 역전하려면 재입력 정책과 상태 처리를 별도로 바꿔야 하며, 현재 규칙에 FlipFlop을 추가하는 것만으로 해결하지 않습니다.
참조는 있는데 문이 반응하지 않는다면 입력이 한 곳에서 전달되는지, ToggleDoor의 Target과 RequestingPlayer가 각각 문과 캐릭터인지, 현재 범위 검사와 bIsAnimating 상태를 차례로 확인합니다.
상호작용 구현의 최적화 및 고려 사항
- 블루프린트 vs C++: 복잡하고 성능에 민감한 상호작용은 C++로 구현하는 것이 유리할 수 있지만, 대부분의 기본적인 상호작용은 블루프린트로 충분합니다.
- 충돌 설정의 중요성:
Trigger Volume과 상호작용 오브젝트의 충돌 설정이 정확해야 합니다. 불필요한Block설정은 피하고, 필요한 곳에만Overlap을 사용합니다. - 컴포넌트 구조화: 상호작용 로직이 복잡해질수록 블루프린트 내의 컴포넌트들을 논리적으로 그룹화하고 이름을 명확하게 지정합니다.
- 입력 매핑: Legacy Input은 Project Settings의 Action Mappings를 사용합니다. Enhanced Input은 Input Action·Input Mapping Context를 만들고 로컬 플레이어에 Context를 등록한 뒤 입력 이벤트를 연결합니다.
- 피드백: 상호작용이 발생했을 때 플레이어에게 시각적(UI, 이펙트), 청각적(사운드), 또는 햅틱(진동) 피드백을 제공하여 몰입도를 높입니다.
- 상호작용 우선순위: 여러 상호작용 가능한 오브젝트가 겹쳐 있을 때 어떤 오브젝트와 상호작용할지 결정하는 로직을 고려합니다. (예: 가장 가까운 오브젝트, 시선에 있는 오브젝트)
- 네트워크 상호작용: 위 문 호출은 단일 로컬 플레이어 예제입니다. 멀티플레이에서 필요한 서버 권한 확인, 요청 전달과 문 상태 복제는 구현하지 않았습니다.
후보가 여러 개면 유효 조건을 먼저 확인한 뒤 시선·거리 같은 프로젝트 규칙으로 하나만 선택합니다. 선택이 바뀌면 이전 프롬프트와 하이라이트를 정리합니다.
환경 상호작용은 트리거, 상호작용 가능한 오브젝트, 블루프린트 로직을 연결해 플레이어 입력에 반응하는 레벨 요소를 만드는 작업입니다.
상호작용 범위, 피드백, 상태 변화를 분리해 관리해야 합니다.