이번 튜토리얼에서는 플레이어를 탐지하고 추격하여 대미지를 입히는 적을 생성해 봅니다. 적은 대미지를 받을 수 있으며 장애물을 피하기 위해 내비게이션 메시로 주변 환경을 탐색할 수도 있습니다.
이전 튜토리얼과 마찬가지로, 먼저 블루프린트 비주얼 스크립팅 시스템을 사용해 적 로직을 생성한 다음 내비게이션 메시를 생성해 AI가 탐색할 수 있는 환경을 정의합니다. 내비게이션 메시는 맵에서 다양한 AI 캐릭터가 게임플레이 중에 탐색할 수 있는 특정 구역과 영역을 설정합니다.
적 생성하기
이전 튜토리얼의 블루프린트 작업에 더해, 이번에는 적을 시각적으로 나타내는 오브젝트를 추가하고 애니메이션 블루프린트를 사용해 애니메이션을 재생해 봅니다. 애니메이션 블루프린트는 프로젝트 파일로 제공되므로 처음부터 만들지 않아도 되지만, 튜토리얼에서 적 캐릭터의 블루프린트와 통합하는 방법을 배워야 합니다.
먼저 적 블루프린트를 생성합니다. 다음 단계를 따르면 됩니다.
콘텐츠 브라우저(Content Browser)로 이동하여 Content > AdventureGame > Blueprints > Characters로 이동합니다.
콘텐츠 브라우저의 빈 곳을 우클릭한 다음 블루프린트 클래스(Blueprint Class)를 클릭합니다.
새 창에서 캐릭터(Character)를 베이스 클래스로 선택합니다.
이 클래스를 'BP_Enemy'로 명명하고 블루프린트 에디터에서 더블클릭하여 엽니다.
블루프린트를 생성하면 블루프린트 에셋의 일부이며 블루프린트의 기능을 정의하는 블루프린트 클래스도 생성됩니다. 따라서 기능을 추가할 때 블루프린트 클래스를 편집하게 됩니다. 여기에서는 BP_Enemy로 명명된 블루프린트 에셋을 생성하게 되는데, 이 에셋은 블루프린트 클래스에도 동일한 이름을 사용합니다.
베이스 클래스는 새 블루프린트 클래스에서 활용할 기능이 사전 제작되어 있는 템플릿으로 생각할 수 있습니다. 여기에서 캐릭터 베이스 클래스는 게임 캐릭터와 관련된 기능(예: 이동, 해당 방향을 보기 위해 카메라 회전)을 갖추고 있습니다. 이러한 베이스 클래스를 활용하면 사전 제작된 기능을 사용할 수 있어 기능을 반복해 생성하지 않아도 됩니다.
플레이어 블루프린트 BP_AdventureCharacter 역시 캐릭터 베이스 클래스를 사용하므로, 플레이어와 적 캐릭터 모두 동일한 베이스 기능을 갖추고 있으며 크리에이터가 이를 재사용할 수 있습니다.
BP_Enemy 블루프린트 창이 열린 상태에서 뷰포트 탭으로 이동하여 컴포넌트(Components) 패널을 살펴봅니다. 그러면 목록 하단에 캐릭터 이동(CharMoveComp) 컴포넌트를 볼 수 있습니다. 이 컴포넌트는 최대 이동 속도와 같이 이동과 관련된 자체 함수와 변수를 갖고 있는데, 이는 캐릭터 베이스 클래스에 속하는 것입니다. 프로젝트에서 적에 대해 유사한 기능을 생성할 때 이 컴포넌트를 사용할 수 있습니다.
적에 3D 모델 추가하기
먼저 새 컴포넌트를 추가하는 것부터 시작하겠습니다. 다음 단계를 따르면 됩니다.
스켈레탈 메시 컴포넌트를 선택하고 창 오른쪽의 디테일(Details) 패널을 확인합니다. 메시(Mesh) 카테고리에서 스켈레탈 메시 에셋(Skeletal Mesh Asset) 옆의 드롭다운을 클릭합니다.
목록에서 SKM_Manny_Simple 모델을 찾아 선택합니다. 그러면 Manny 모델이 블루프린트에 할당됩니다.
다음으로 애니메이션(Animation) 카테고리를 살펴봅니다. 애님 클래스(Anim Class) 옆의 드롭다운을 클릭하고 ABP_Unarmed 애니메이션 블루프린트를 할당합니다. 여기서 ABP는 애니메이션 블루프린트(Animation Blueprint)를 의미합니다.
적 캐릭터에서 제대로 작동하도록 애니메이션 블루프린트를 수정합니다.
적 메시의 애님 클래스 프로퍼티에서 ABP_Unarmed 옆에 있는 콘텐츠 브라우저의 '에셋 탐색(Browse to Asset)'을 클릭합니다.
ABP_Unarmed 애니메이션 블루프린트를 더블클릭하여 엽니다.
시퀀스 노드의 Then 1 핀 뒤에 오는 로직 그룹으로 이동합니다. 적 캐릭터 이동을 막는 가속 확인이 적용되지 않도록 And 노드와 Set Should Move 노드 사이의 와이어를 제거합니다.
보다 큼(>) 노드의 출력 핀을 Set Should Move에 연결해 가속 확인을 우회합니다.
블루프린트를 저장 및 컴파일한 후 닫습니다.
이제 적 캐릭터가 뷰포트 탭에서 유휴 상태 애니메이션을 재생하게 됩니다.
뷰포트에서 적 캐릭터 근처에 캡슐 모양이 있는 것을 볼 수 있습니다. 캡슐 컴포넌트는 콜리전 콜라이더를 시각적으로 표현한 것으로, 벽, 지면, 플레이어 캐릭터 등 다른 오브젝트와의 콜리전을 탐지하는 데 사용할 수 있습니다.
뷰포트에서 캐릭터가 현재 지면으로부터 오프셋되어 있고 캡슐 컴포넌트와 맞지 않는 것을 볼 수 있는데, 이로 인해 게임플레이 중에 메시와 컴포넌트가 서로 어긋나게 될 수 있습니다. 캐릭터의 메시가 캡슐 컴포넌트와 제대로 맞도록 하려면, 발이 캡슐 컴포넌트의 하단 부분에 닿도록 캐릭터를 아래로 옮깁니다.
메시 컴포넌트를 다시 선택하여 3D 모델을 하이라이트합니다. 디테일 패널에서 트랜스폼(Transform) 카테고리의 위치(Location) 필드를 사용해 Z축(파란색으로 표시된 마지막 필드)을 변경합니다. 값을 -96으로 설정합니다. 이렇게 하면 메시가 캡슐 콜라이더 하단으로 옮겨져 게임에서 적이 지면 위로 뜨지 않게 됩니다.
캐릭터의 정면이 파란색 화살표 방향을 향하도록 합니다. 컴포넌트 패널에서는 화살표 컴포넌트(화살표)(Arrow Component (Arrow))로 나와 있습니다. 이 화살표는 어느 방향이 블루프린트의 정면인지를 보여줍니다.
컴포넌트 패널에서 화살표 컴포넌트를 볼 수 있는데, 이 화살표는 뷰포트 탭에서 하늘색 화살표로도 표시되어 있습니다.
화살표 컴포넌트는 캐릭터가 모든 방향으로 올바르게 움직이기 위해서 향해야 하는 방향을 알려줍니다. 화살표의 방향이 캐릭터의 전방 방향입니다.
이를 기억하고 메시 컴포넌트를 다시 선택합니다. 트랜스폼 아래 마지막 필드인 디테일 패널을 통해 캐릭터를 Z축에서 회전합니다. -90으로 설정하면 올바르게 회전합니다.
| 잘못된 방향 | 올바른 방향 |
|---|
3D 모델의 머티리얼 변경하기
기능을 생성하기 전에, 적 모델의 색상을 빨간색으로 변경해 보겠습니다. 현재의 흰색 대신 이렇게 변경하면 플레이어와 구분되고 더 적처럼 느껴지게 됩니다.
디테일 패널의 머티리얼 섹션에 두 개의 엘리먼트가 있는 것을 볼 수 있습니다. MI_Manny_01_New는 위팔, 다리, 머리와 같이, 적 모델의 다양한 위치에 영향을 미치는 머티리얼입니다. MI_Manny_02_New는 팔뚝과 몸통에 영향을 미치는 머티리얼입니다.
엘리먼트 0(Element 0) 프로퍼티 옆에 있는 폴더와 돋보기 모양의 콘텐츠 브라우저에서 탐색(Browse in Content Browser) 버튼을 클릭합니다. 그러면 콘텐츠 드로어가 열리고 선택한 머티리얼이 표시됩니다. 두 번째 머티리얼 MI_Manny_02_New도 여기에 있는 걸 볼 수 있습니다.
MI_Manny_01_New 에셋을 우클릭하고 머티리얼 인스턴스 생성(Create Material Instance)을 클릭합니다. 그러면 선택한 머티리얼의 인스턴스가 생성됩니다. 머티리얼 인스턴스는 부모 머티리얼의 모든 항목을 상속하며, 수정하려는 파라미터를 오버라이드할 수 있습니다. 예를 들어 색만 변경하고 머티리얼의 광택과 같은 다른 모든 세팅은 동일하게 유지할 수 있습니다.
새 머티리얼 인스턴스를 생성할 때 언리얼 엔진은 MI 접두사를 사용해 새 에셋을 자동으로 명명합니다. 여기서 MI는 머티리얼 인스턴스(Material Instance)를 의미합니다. 머티리얼이나 머티리얼 인스턴스에서 인스턴스를 생성하는 방식으로 기존 머티리얼에서 계속 분기할 수 있습니다.
머티리얼 인스턴스 에셋을 MI_Enemy_01로 명명하고 더블클릭하여 엽니다.
오른쪽 디테일 패널에서 01 - BaseColor를 펼치고 페인트 색조(Paint Tint) 프로퍼티를 토글합니다. 페인트 색조 프로퍼티 옆의 색상 견본을 클릭하고 색 선택 툴(Color Picker) 창을 사용해 빨간색을 선택합니다.
이 튜토리얼에서는 형광 빨간색을 선택했는데, 색 선택 툴 오른쪽 하단의 Hex sRGB 필드에 컬러 코드 F60E6EFF를 붙여 넣으면 됩니다.
머티리얼 창의 왼쪽 상단에 있는 저장(Save) 버튼을 클릭하고 창을 닫습니다.
다음으로 다시 콘텐츠 브라우저로 이동하여 이번에는 MI_Manny_02_New 에셋을 우클릭하고 머티리얼 인스턴스 생성을 클릭합니다.
이 머티리얼 인스턴스를 MI_Enemy_02로 명명하고 이 머티리얼에서도 앞서 진행한 단계를 반복합니다.
단계를 반복하고 나면 두 개의 새 머티리얼 인스턴스, MI_Enemy_01 및 MI_Enemy_02가 생성됩니다. 머티리얼 에디터 창에서 저장 버튼을 사용해 머티리얼 인스턴스 에셋을 모두 저장해야 합니다.
다음으로 이 머티리얼을 적 캐릭터에 할당합니다. 블루프린트 에디터 창에서 BP_Enemy 블루프린트를 열고 디테일 패널에서 머티리얼 섹션으로 이동합니다. 엘리먼트 0 드롭다운을 클릭하고 새로 생성한 머티리얼 MI_Enemy_01을 선택합니다. 엘리먼트 1(Element 1)에서 MI_Enemy_02 머티리얼을 선택합니다.
이제 적 캐릭터가 새 머티리얼 인스턴스 에셋을 사용하므로 빨간색으로 표시됩니다.
게임플레이 시작 시 적 구성하기
적의 시각적인 부분을 준비했으니 이제 게임플레이 기능을 만들어 보겠습니다. 먼저 적 생성 시 필요한 프로퍼티를 구성해야 합니다.
다음 단계를 따르면 됩니다.
BP_Enemy 블루프린트 에디터 창에서 뷰포트 탭 옆에 있는 이벤트 그래프(EventGraph) 탭으로 이동합니다. 기본적으로 이벤트 3개, Event BeginPlay, Event ActorBeginOverlap, Event Tick이 나와 있으며, 이벤트 위에 코멘트가 있습니다. 이 튜토리얼에서는 필요하지 않으므로 ActorBeginOverlap을 선택해 삭제합니다.
먼저 컴포넌트 패널을 사용해 캐릭터 캐릭터 이동(Character Movement) 컴포넌트를 이벤트 그래프로 드래그합니다. 그러면 Character Movement 라는 노드가 생성됩니다. 이 노드의 핀을 드래그해 Set Max Walk Speed 노드를 생성합니다.
Set 노드의 녹색 Max Walk Speed 핀을 우클릭하고 컨텍스트 메뉴에서 변수로 승격(Promote to Variable) 옵션을 선택합니다. 그러면 새 변수가 생성되며 블루프린트 에디터 왼쪽의 내 블루프린트(My Blueprint) 패널에서 변수(Variables) 목록 아래에 표시됩니다.
이 변수의 이름을 Max Speed로 변경합니다. 변수 옆의 눈아이콘을 클릭하여 이를 편집 가능한 퍼블릭 변수로 바꿉니다. 블루프린트를 컴파일(Compile)하고 디테일 패널에서 최대 속도의 디폴트 값을 200으로 설정합니다.
이 변수는 퍼블릭이므로 레벨에 적을 추가할 때 레벨에서 값을 설정할 수 있습니다. 하지만 값을 변경하는 것을 잊어버리면 디폴트 값이 0으로 설정되어 적이 움직일 수 없게 됩니다. 디폴트 값을 200으로 설정하면 적이 이동하기에 충분한 속도가 됩니다.
변수 목록에서 CurrentHP라는 플로트 타입의 새 변수를 추가합니다.
Eliminated라는 변수를 하나 더 추가하고 타입을 부울로 변경합니다.
변수 목록에서 CurrentHP 변수를 이벤트 그래프로 드래그하고 컨텍스트 메뉴에서 현재 HP 설정(Set Current HP) 옵션을 클릭합니다. Set Max Speed 노드의 Exec 핀을 드래그해 Set Current HP 노드의 Exec 핀에 연결합니다.
Set Current HP 노드에서 Current HP 플로트 핀을 우클릭하고 변수로 승격을 클릭합니다.
이 변수의 이름을 TotalHP로 변경합니다. 타입을 플로트(Float)로 설정하고 눈 아이콘을 클릭하여 편집 가능한 퍼블릭 변수로 만듭니다. 디테일 패널에서 카테고리를 구성(Setup)으로 설정합니다. 블루프린트를 컴파일하고 디폴트 값을 100으로 설정합니다.
적의 체력 값을 추적하는 변수가 두 개 있어야 하는데, 두 개 중 Total HP 변수는 적 캐릭터가 얼마의 체력으로 시작하는지를 저장하고, Current HP 변수는 어느 시점에서든(플레이어로부터 대미지를 받은 직후도) 적의 체력이 얼마인지를 추적합니다. 총 HP 변수를 퍼블릭으로 만들면 레벨에 있는 적 캐릭터 블루프린트의 각 인스턴스에서 이 값을 조정할 수 있습니다. 따라서 각각의 적이 얼마나 센지 조정할 수 있어 플레이어가 적을 물리치기 더 쉽게, 아니면 더 어렵게 만들 수 있습니다.
이 이벤트 그래프에서 플레이어 캐릭터로 많은 액션을 수행하므로, 이제 플레이어에 대한 레퍼런스를 변수로 저장해 보겠습니다. 변수 목록에서 PlayerRef라는 새 변수를 생성하고 타입을 캐릭터(오브젝트 레퍼런스)(Character (Object Reference))로 변경합니다.
플레이어를 참조해야 할 때마다 Get Player Character 함수를 호출하는 것보다, 캐릭터 오브젝트를 위한 레퍼런스 변수를 생성하는 것이 프로젝트 퍼포먼스에 더 좋습니다.
Set Current HP 노드 다음에 Set PlayerRef 노드를 연결합니다. Player Ref 입력 핀에 Get Player Character 노드를 연결합니다. 이 노드의 플레이어 인덱스(Player Index) 값을 0으로 설정합니다. 이 값이 레벨에 처음 생성되는 플레이어 캐릭터의 기본 인덱스가 됩니다.
만든 로직을 Event BeginPlay 함수 노드에 연결합니다. 아래와 같은 그래프가 되어야 합니다.
적이 플레이어를 추격하게 만들기
다음으로 적을 플레이어를 향해 이동하도록 하는 로직을 만들어야 합니다. 이를 위해서 커스텀 이벤트를 생성해야 하는데, BeginPlay 등의 다른 이벤트 노드와 비슷하지만 로직을 다시 생성할 필요 없이 블루프린트의 어디에서나 호출할 수 있는 이벤트입니다.
커스텀 태그를 생성하려면 다음 단계를 따릅니다.
이벤트 그래프 아무 곳이나 우클릭하고 검색창에 Custom Event를 입력합니다. 목록에서 커스텀 이벤트 추가(Add Custom Event) 옵션을 클릭하고 이 이벤트를 MoveToPlayer로 명명합니다.
새로 생성된 MoveToPlayer 이벤트 노드의 핀을 드래그하고 검색 필드에 AI MoveTo를 입력합니다. AI MoveTo 노드를 선택하여 생성합니다.
AI MoveTo 노드는 AI 컨트롤러가 있는 폰이 특정 위치로 이동하는 데 사용할 수 있는 이벤트입니다. 여기에서는 이를 사용해 적을 플레이어를 향해 이동하게 합니다.
AI MoveTo 노드에서 Pawn 핀을 드래그하고 검색창에 Self를 입력합니다. 셀프 레퍼런스 가져오기(Get reference to self) 옵션을 선택하여 게임에서 이 블루프린트의 액터를 참조하는 노드를 생성합니다.
다음으로 AI MoveTo 노드의 Target Actor 핀을 드래그해 Get PlayerRef 노드를 생성합니다.
여기에서는 어떤 액터가 움직일지를 정의하는 사용하는 Self 노드로 폰을 적으로 정의합니다. 그런 다음 폰이 이동할 타깃 액터를 레벨의 플레이어 캐릭터로 설정합니다.
AI MoveTo 노드에는 성공 시(On Success) 및 실패 시(On Fail) 이벤트가 포함되어 있습니다. 이를 사용해 적이 플레이어에게 다가가거나, 이에 실패할 경우 어떤 작업을 진행할지를 정의합니다.
On Success 핀을 드래그해 Delay 노드를 생성합니다.
Delay 노드에서 Duration 핀을 우클릭하고 변수로 승격을 선택합니다.
이 변수를 WaitAtDestination으로 명명합니다. 타입은 기본적으로 플로트로 설정됩니다.
블루프린트를 컴파일하고 WaitAtDestination 변수를 선택한 다음 디폴트 값을 5로 변경합니다. 눈 아이콘을 클릭하여 편집 가능한 상태로 만들고 카테고리를 구성으로 변경합니다.
다음으로 이벤트 그래프에서 Delay 노드의 Exec 핀을 드래그해 Move To Player 노드를 생성합니다. 이렇게 하면 적이 플레이어에게 다가가도 일정 시간이 지난 뒤에 생성한 Move To Player 이벤트가 실행됩니다.
이제 AI MoveTo 노드에서 On Fail 핀을 드래그해 새 Delay 노드를 생성합니다. 이 노드의 Completed 핀을 드래그해 Move To Player 노드를 생성합니다. Delay 노드의 지속 시간(Duration) 필드는 0.2로 그대로 두는데,
그래야 적이 플레이어에게 도달하지 않을 때도 계속 움직입니다.
이제 Event BeginPlay 노드로 돌아갑니다. 이 시퀀스의 마지막 부분에서 Set Player Ref 노드 다음에 Move To Player 노드를 연결해 이 이벤트를 트리거해야 합니다. 그래야 게임 시작 시 적이 움직이고 적의 이동을 테스트할 수 있습니다.
이벤트 그래프가 아래와 같은 모습이 되어야 합니다.
전체 위젯 블루프린트가 아래와 같은 모습이 되어야 합니다.
플레이어에게 대미지 주기
계속해서 적이 플레이어에게 다가가면 플레이어에게 대미지를 주도록 해보겠습니다. 이 튜토리얼에서는 플레이어와 접촉 시 스스로 소멸하는 적을 생성하려 하므로, 적이 플레이어에게 한 번 대미지를 준 후 제거되는 로직을 추가해야 합니다.
다음 단계를 따르면 됩니다.
컴포넌트 패널에서 캡슐 컴포넌트(CollisionCylinder)(Capsule Component (CollisionCylinder))를 선택합니다.
디테일 패널의 이벤트 섹션에서 컴포넌트 히트 시(On Component Hit) 옆의 추가(+)(Add (+))를 사용합니다.
이렇게 하면 이벤트 그래프에 컴포넌트(여기에서는 선택한 캡슐 컴포넌트)가 무언가를 히트할 때 실행되는 새 이벤트 노드가 생성됩니다.
On Component Hit 노드의 핀을 드래그해 Branch 노드를 생성합니다. Branch 노드의 Condition 핀을 On Component Hit 노드의 Other Actor 핀으로 드래그하면 새 노드를 생성하라는 메시지가 표시됩니다.
Equal 노드를 생성합니다. 이 노드는 On Component Hit 노드에서 탐지한 다른 액터가 의도한 액터(여기에서는 플레이어 캐릭터)인지 확인합니다. Equal 노드의 Select Asset 핀을 드래그해 PlayerRef 변수에 대한 레퍼런스를 추가합니다.
이 로직은 적의 캡슐 컴포넌트가 게임에서 무언가를 히트할 때 이벤트를 트리거하고, 이 이벤트는 캡슐 컴포넌트가 히트한 다른 오브젝트가 플레이어 캐릭터인지를 확인합니다. 이러한 정보에 따라 플레이어에게 대미지를 적용하게 됩니다.
이 튜토리얼에서는 플레이어를 한 번 히트하고 스스로 소멸하는 적을 생성합니다. 플레이어에게 한 번만 대미지를 입힐 수 있도록 Branch 노드의 True 핀을 드래그해 Do Once 노드를 생성합니다.
Do Once 노드의 Completed 핀을 드래그해 Apply Damage 노드를 생성합니다. Damaged Actor 핀을 드래그하고 레퍼런스를 PlayerRef 변수에 연결하여 대미지를 입힐 액터로 지정합니다.
그런 다음 Base Damage 핀을 우클릭하고 변수로 승격을 선택합니다. 변수 목록에서 기본 대미지의 눈 아이콘을 클릭하여 편집 가능한 상태로 만들고, 카테고리를 구성으로 변경합니다. 블루프린트를 컴파일하고 기본 대미지를 25로 설정합니다.
다음으로 변수 목록에서 Eliminated 변수를 이벤트 그래프로 드래그하고 설정(Set)을 선택합니다.
Apply Damage 노드의 Exec 핀을 Set Removed 노드에 연결합니다.
Set Eliminated 노드의 처치됨(Eliminated) 프로퍼티를 true로 토글합니다.
Set Eliminated 노드의 Exec 핀을 드래그해 Delay 노드를 생성합니다. 지속 시간을 2.0으로 설정합니다.
그런 다음 Delay 노드의 Exec 핀을 드래그해 Destroy Actor 노드를 생성합니다. 타깃(Target) 프로퍼티는 기본 셀프(self)로 설정되어 있어야 합니다.
적이 플레이어와 충돌하면 플레이어에게 대미지를 주고 2초 지연 후 적이 처치됩니다.
이 단계까지 구성하면 적이 플레이어를 향해 달려올 수 있으며, 플레이어와 충돌하면 대미지를 주고 스스로 소멸할 수 있습니다. 하지만 플레이어와 적 사이의 거리나 둘 사이에 벽과 같은 장애물이 있는지를 고려하지는 않으므로, 적은 환경에 관계없이 항상 플레이어를 추격하게 됩니다.
거리 및 장애물 추가 로직
이 문서의 다음 섹션에서는 플레이어가 적에 충분히 가까이 있을 때만 적이 플레이어를 탐지할 수 있도록 최대 탐지 거리를 설정하는 방법을 살펴봅니다. 그런 다음 적이 벽 너머 플레이어를 볼 수 없도록 하는 기능을 만들어 마무리해 보겠습니다. 그러면 플레이어가 적의 시야 안에 있어야 적이 플레이어를 볼 수 있게 됩니다. 이렇게 하려면 먼저 적과 플레이어 사이에서 라인 트레이스를 수행하는 새 함수를 블루프린트 라이브러리에 생성해야 합니다.
라인 트레이스로 시선 및 거리 계산하기
라인 트레이스는 게임 개발에서 흔히 사용되는 방식입니다. 라인 트레이스는 런타임 시 소스 액터로부터 다른 게임 오브젝트 및 액터와 교차하는 선을 그려 이들을 탐지합니다. 그러면 라인 트레이스로 탐지된 액터와 게임 오브젝트를 활용해 게임 로직을 활성화할 수 있습니다. 이 튜토리얼에서는 적 캐릭터의 라인 트레이스를 사용해 플레이어 캐릭터가 적의 시야 안에 있을 때 플레이어를 탐지하는 구성을 보여줍니다. 트레이싱에 대한 자세한 내용은 레이캐스트를 사용한 트레이스 문서를 참고하세요.
라인 트레이스를 사용해 플레이어까지의 거리와 시선을 테스트하는 함수를 구성하려면 다음 단계를 따릅니다.
콘텐츠 브라우저에서 Core 폴더로 이동하여 BPL_FPGame 블루프린트 라이브러리를 엽니다.
fnBPLFindPlayer라는 새 함수를 추가합니다.
새 함수의 디테일 패널에서 프로퍼티의 입력(Inputs) 섹션으로 이동하고 추가(+)를 사용해 다음 입력 타입을 추가합니다.
Player Reference(폰 오브젝트 레퍼런스)
Start Location(벡터),
Max Detection Distance(플로트),
Debug(부울)
이러한 입력을 추가하고 나면 fnBPLFindPlayer 함수 노드에 각 변수에 해당하는 핀이 있는 것을 볼 수 있습니다.
다음으로 함수의 디테일 패널에서 프로퍼티의 출력(Outputs) 섹션으로 이동하여 추가 (+)를 사용해 Player Found 부울 출력 변수를 생성합니다.
Player Found Output 변수를 추가하면 그래프에 새로운 Return 노드가 생성되고 Player Found Output 변수가 Entry 노드에 연결됩니다.
마지막으로 내 블루프린트 패널에서 로컬 변수(Local Variables) 섹션으로 이동한 후, 추가(+)를 사용해 로컬 PlayerFound(부울) 변수를 생성하고 디폴트 값이 False인지 확인합니다.
다음으로 플레이어 입력에 대한 오류 확인을 추가해야 합니다. 확인을 추가하려면 다음 단계를 따릅니다.
ALT 키를 누른 상태에서 Entry 또는 Return 노드의 Exec 핀을 클릭하여 Entry 노드와 Return 노드 사이의 와이어를 삭제합니다.
함수 엔트리 노드 뒤에 Is Valid 노드를 생성해 연결합니다. 노드를 생성한 후 Entry 노드에서 Player Reference 핀을 드래그해 Is Valid 노드의 Input Object 핀에 연결합니다. Is Valid는 모든 블루프린트에서 사용할 수 있는 라이브러리 함수입니다. 이 함수가 올바르게 실행되는 데 있어 플레이어 레퍼런스가 중요한 역할을 하므로, 유효하지 않은 플레이어 레퍼런스를 확인하는 조건을 추가하여 오류를 방지해야 합니다.
유효하지 Is Not Valid 실행 핀을 Return 노드에 연결합니다. Found 값에는 PlayerFound 변수에 대한 레퍼런스를 연결합니다. 이렇게 하면 함수가 종료되고 적의 이벤트 그래프로 PlayerFound=False를 반환합니다.
라인 트레이스를 구성하려면 다음 단계를 따릅니다.
플레이어가 유효한 경우 함수는 라인 트레이스 오브젝트를 실행한 다음 PlayerFound를 반환합니다. Sequence 노드를 추가하여 로직을 정리합니다.
Sequence 노드의 Then 0 핀을 드래그해 Line Trace By Channel 노드를 추가합니다. 참고: 여기에 이 노드에 대한 정보가 필요합니다. By Channel 노드란 무엇인가요? 이에 대한 문서 링크 무엇이든 가능합니다.
다음과 같이 Line Trace By Channel 노드를 구성합니다.
Entry 노드에서 Start Location 핀을 드래그해 Line Trace By Channel 노드의 Start 입력 핀에 연결합니다. 적 캐릭터의 위치를 라인 트레이스의 시작점으로 사용합니다.
다음으로 Entry 노드의 Player Reference 핀을 그래프의 아무 곳으로 드래그하면 블루프린트 노드 컨텍스트 메뉴가 열립니다. 컨텍스트 메뉴를 사용해 Get Actor Location 노드를 찾아 생성합니다. 그러면 플레이어 레퍼런스를 Target 입력으로 사용해 새 노드가 생성됩니다. 이렇게 하면 플레이어의 위치에서 라인 트레이스가 끝납니다.
Line Trace By Channel 노드에서 드롭다운 메뉴를 사용해 트레이스 채널(Trace Channel) 프로퍼티를 '카메라(Camera)'로 설정합니다.
이제 다음 단계를 따라 라인 트레이스 히트 결과를 사용해 플레이어와 히트 거리를 확인하는 기능을 만들어야 합니다.
라인 트레이스의 Out Hit 핀을 드래그해 Break Hit Result 노드를 추가합니다. 라인 트레이스가 오브젝트를 히트하면 많은 정보가 수집되는데, 이 로직에서는 몇 가지 특정 값만 사용하면 됩니다. 따라서 반환된 데이터를 개별 컴포넌트로 나누기 위해 Break Hit Result 노드가 필요합니다.
Break Hit Result 노드에서 하단의 화살표를 클릭하여 추가 옵션을 확인합니다. 이 함수는 플레이어가 시야 내에 있고 적과 일정 거리 안에 있는지 확인하는 데 사용됩니다. 따라서 히트 액터를 사용해 플레이어를 확인하고, 트레이스 시작 및 트레이스 끝을 사용해 히트한 오브젝트까지의 거리를 측정합니다.
Hit Actor 핀을 드래그해 등호 연산자 노드를 추가합니다. 하단 입력에는 레퍼런스를 Player Reference 함수 입력에 연결합니다.
Distance (Vector) 노드를 생성합니다. V1을 트레이스 시작에 연결하고 V2를 트레이스 끝에 연결합니다. 참고: 벡터는 3D 공간에서 오브젝트 또는 포인트의 위치를 나타내는 X, Y, Z 값 세트입니다. Distance 노드는 이러한 지점 사이의 거리를 계산하여 플로트 값으로 표시합니다.
Distance 노드의 Return Value를 드래그해 작음(<) 노드를 추가합니다. 하단 입력에는 레퍼런스를 Max Detection Distance 함수 입력에 연결합니다.
AND(부울) 노드를 생성하고 등호(==) 및 작음(<) 노드의 부울 출력 모두를 AND(부울) 노드의 입력 핀에 연결합니다. 이 로직에서는 라인 트레이스가 Player Reference와 동일한 액터를 히트하고, 트레이스 시작(적)과 끝(히트한 오브젝트) 사이의 거리가 Max Detection Distance보다 작은 경우에 AND 노드가 true를 반환합니다.
AND 노드의 출력을 드래그해 Branch 노드를 추가하고 노드를 구성합니다.
Line Trace By Channel 이후에 실행되도록 연결합니다.
Branch 노드의 True 실행 핀에서 Set 변수 노드를 생성해 로컬 PlayerFound 변수를 true로 설정합니다.
Branch 노드의 False 실행 핀에서 Set 변수 노드를 생성해 로컬 PlayerFound 변수를 false로 설정합니다.
라인 트레이스 로직을 마무리했으니 Sequence 노드로 돌아가 Then 1 핀을 Return 노드에 연결해 함수를 종료하고 PlayerFound 결과를 적 블루프린트로 다시 전송합니다.
레벨 에디터에서 라인 트레이스의 디버그 비주얼을 쉽게 켜고 끌 수 있어야 하는데, 라인 트레이스 노드는 디버그 드로 타입 목록에서 하나의 옵션만 선택할 수 있습니다. Select 노드를 사용하면 편집 가능한 디버그 부울 변수를 두 개의 디버그 드로 타입 옵션에 연결할 수 있습니다.
라인 트레이스에 커스터마이징 가능한 디버그 옵션을 구성하려면 다음 단계를 따릅니다.
Line Trace By Channel 노드의 Draw Debug Type 핀에서 Select 노드를 생성합니다.
Select 노드의 Index 핀을 Entry 노드의 디버그 함수 입력에 연결합니다. Select 노드의 인덱스는 와일드카드이므로 부울 타입 디버그 레퍼런스를 연결하면 옵션이 False 및 True로 변경됩니다.
False를 '없음(None)'으로, True를 '지속 시간 동안(For Duration)'으로 설정합니다.
블루프린트를 저장 및 컴파일합니다.
이 로직은 Debug 변수를 사용해 라인 트레이스 디버그 드로의 활성화 여부를 확인합니다. Debug 변수가 false인 경우 디버그 모드는 '없음'으로 설정되어 디버그 드로가 렌더링되지 않습니다. Debug 변수가 true로 설정된 경우 Select 노드는 지속 시간 동안 디버그 드로를 활성화하여, 활성 상태인 동안에 런타임 시 라인 트레이스를 렌더링합니다. 원하는 경우 다른 옵션을 사용할 수 있습니다. 예를 들어 단일 프레임 동안(For One Frame) 옵션의 경우 하나의 프레임에서만 라인 트레이스를 드로하거나, 지속(Persistant) 옵션의 경우 프로젝트가 실행 중인 동안에는 디버그 드로를 활성 상태로 유지할 수 있습니다. 여기에서는 디버깅 시 라인 트레이스를 드로하는 데 가장 유용한 방법은 '지속 시간 동안'입니다.
적이 플레이어를 추격하도록 만들기
이제 거리와 시야를 고려해 플레이어를 찾는 함수를 호출할 수 있게 되었으므로, 적이 플레이어가 시야에 들어왔을 때 플레이어를 찾아 추격하도록 하는 로직을 추가해야 합니다.
다음 단계를 따르면 됩니다.
콘텐츠 브라우저에서 Content > AdventureGame > Designer > Blueprints > Characters로 이동하여 'BP_Enemy'를 엽니다.
이벤트 그래프에서 Event Tick 노드의 Exec 핀을 드래그해 Branch 노드를 생성합니다. 참고: On Tick 이벤트는 게임이 플레이되는 동안 프레임마다 실행되는데, 이를 사용하여 적이 플레이어의 위치를 지속적으로 확인하고 추격 시작 여부를 판단하도록 만들 수 있습니다.
Branch 노드의 Condition 핀을 드래그해 부울 값의 반대값을 반환하는 NOT Boolean 노드를 생성합니다. 여기에서는 Eliminated 변수의 반대값을 반환받아야 하므로, NOT Boolean 노드의 핀을 드래그해 Get Eliminated를 찾습니다.
이를 통해 적이 처치되지 않았는지 확인하며, 처치된 경우 Branch 노드의 True 핀으로 진행합니다.
Branch 노드의 True 핀을 드래그해 FnBPLFFind Player 노드를 생성합니다. 이 노드는 라인 트레이스를 수행하여 월드에서 플레이어를 찾고 적과 플레이어 사이의 현재 거리를 확인합니다.
Fn BPFind Player 노드에서 핀을 사용해 다음과 같이 연결합니다.
Player Reference: 레퍼런스를 PlayerRef 변수에 연결합니다.
Start Location: 타깃 프로퍼티가 셀프로 설정된 Get Actor Location 노드를 생성합니다. 이렇게 하면 라인 트레이스의 시작 위치가 월드에서 적 자신의 위치로 계속 지정됩니다.
Max Detection Distance: '변수로 승격'을 선택하고 눈 아이콘을 클릭하여 편집 가능한 상태로 만든 다음 카테고리를 '구성'으로 변경합니다. 블루프린트를 컴파일한 후 디폴트 값을 20000(200미터)으로 설정합니다. 이 값이 작을수록 플레이어가 가까이 있어야 적이 플레이어를 탐지할 수 있습니다.
Debug: '변수로 승격'을 선택하고 DebugDetection으로 명명합니다. 이 변수 역시 편집 가능한 상태로 만들면 구성 카테고리에 나타납니다.
라인 트레이스로 플레이어를 발견한 경우 플레이어를 향해 적이 이동할 최대 이동 속도를 원하는 정도로 설정할 수 있습니다. 이동하기를 원하지 않는 경우 속도를 0으로 설정하면 적이 플레이어를 향해 이동하지 않습니다.
FnBPLFindPlayer 노드에서 Found 핀을 우클릭하고 변수로 승격합니다. 그러면 해당 새 변수를 위한 Set 노드가 자동으로 생성됩니다. 노드의 실행 핀도 연결합니다. 그러면 이 Found 결과를 그래프의 다른 곳에서 사용할 수 있습니다.
Set 노드 뒤에 Branch 노드를 연결하고, Branch 노드의 Condition 핀을 Set Found 노드의 출력 핀으로 드래그합니다.
컴포넌트 패널에서 캐릭터 이동(CharMoveComp)을 이벤트 그래프로 드래그합니다. 핀을 드래그해 Set Max Walk Speed 노드를 생성합니다.
Branch 노드의 True 핀을 드래그해 Set Max Walk Speed 노드에 연결합니다.
변수 패널에서 Max Speed 변수를 이벤트 그래프로 드래그하고 Get을 선택한 다음 Set 노드의 Max Walk Speed 핀에 연결합니다.
다음으로 Set Max Walk Speed 노드와 Character Movement 노드를 복제합니다. Branch의 False 핀을 새 Set 노드에 연결합니다.
최대 걷기 속도(Max Walk Speed) 프로퍼티를 0으로 설정합니다.
이렇게 하면 AI MoveTo 태스크를 취소하고 태스크를 다시 실행하지 않아도 적이 이동을 멈춥니다.
Branch 노드의 True 핀에 연결된 Set 노드를 뒤에 MoveToPlayer 노드를 연결하여 해당 이벤트를 호출하고 적이 플레이어를 찾은 경우에만 이동하게 만듭니다.
이제 적이 프레임마다 플레이어를 찾도록 만들었으니, Event BeginPlay 로직 끝에 추가한 Move to Player 노드를 삭제합니다. 이 노드는 부분적으로 완료된 적을 테스트하는 데 유용하게 사용했지만 이제는 필요하지 않습니다.
MoveToPlayer 이벤트 로직으로 이동합니다. 이제 플레이어가 발견된 경우에만 적이 이 명령을 실행하도록 해야 합니다.
이벤트 노드와 AI Move To 사이의 와이어를 삭제합니다. 두 노드 사이에서 Branch 노드의 True 핀을 사용해 두 노드를 연결합니다.
Branch의 Condition에서는 Found 변수에 대한 레퍼런스를 추가합니다.
플레이어가 적으로부터 지정된 거리 안에 있고 둘 사이에 장애물이 없는 경우, 적의 이동 속도는 MaxSpeed 변수로 설정되고 적이 MoveToPlayer 로직을 실행합니다. 이러한 조건 중 하나라도 충족되지 않으면 적의 이동 속도가 0으로 설정되어 적이 플레이어를 향해 이동하지 않습니다.
적이 대미지를 받도록 하기
이 블루프린트를 마무리하기 전에 적 역시 대미지를 받을 수 있도록 만들어야 합니다. 다음 단계를 따르면 됩니다.
이벤트 그래프 아무 곳이나 우클릭하고 Event AnyDamage 노드를 생성합니다.
CurrentHP 변수를 이벤트 그래프로 드래그하고 Set CurrentHP를 선택합니다.
현재 HP에서 받은 대미지를 빼야 합니다. 먼저 Event AnyDamage의 실행 핀을 드래그해 Set 노드에 연결합니다.
현재 HP 프로퍼티의 핀을 드래그해 Subtract 노드를 생성합니다.
Subtract 노드의 하단 핀을 드래그해 Event AnyDamage 노드의 대미지(Damage) 프로퍼티에 연결합니다.
그런 다음 Subtract 노드의 상단 핀을 드래그해 Get Current HP 노드를 생성합니다.
이제 이 액터가 대미지를 받으면 이 로직이 CurrentHP 변수의 값을 가져와 대미지 수치를 뺀 다음 CurrentHP 값을 새 값으로 설정합니다.
다음으로 CurrentHP가 0 이하인지 확인하여 적을 처치해야 합니다.
Set 노드의 핀을 드래그해 새 Branch 노드를 생성합니다.
Branch 노드의 Condition 핀을 드래그해 작거나 같음(<=) 노드를 생성합니다.
상단 핀을 드래그해 새 Get CurrentHP 노드를 생성합니다. 하단 핀의 프로퍼티가 0으로 설정되어 있는지 확인합니다.
Branch 노드의 True 핀을 드래그해 Do Once 노드를 생성합니다. Completed 핀을 드래그해 Set Eliminated 노드를 생성합니다.
Set 노드에서 Eliminated(처치됨) 프로퍼티를 true로 토글합니다.
Set 노드 뒤에 Delay 노드를 연결하고 지속 시간을 2로 설정합니다.
마지막으로 Delay 노드의 Completed 핀을 드래그해 Destroy Actor 노드를 생성하고 타깃(Target) 프로퍼티를 '셀프'로 설정합니다.
블루프린트를 저장 및 컴파일해야 합니다.
적 이동을 위한 내비메시 생성하기
레벨에 적을 추가하고 플레이어를 추격하게 하려면 먼저 적 AI가 레벨을 탐색하는 데 사용할 내비메시를 빌드해야 합니다.
내비메시(NavMesh)는 내비게이션 메시(Navigation Mesh)의 약어로, 레벨에서 AI가 탐색할 수 있는 영역을 정의하는 데 사용할 수 있습니다. 이 튜토리얼에서는 내비메시를 생성하여 적이 플레이어를 추격할 수 있는 영역을 정의합니다.
언리얼 엔진의 BP_Enemy 블루프린트 에디터에서 나와 레벨 에디터로 이동합니다. 메인 툴바에서 추가(Add) 버튼을 클릭하고 볼륨(Volumes)으로 이동하여 내비메시 바운드 볼륨(Nav Mesh Bounds Volume)을 생성합니다. 그러면 프로젝트에서 AI의 이동 가능 영역을 할당하는 데 사용할 수 있는 오브젝트가 생성됩니다.
녹색 구역이 보이지 않으면 키보드의 P 키를 눌러 디버그 뷰를 토글합니다. 이를 사용하면 레벨의 이동 가능 구역을 비롯하여 레벨 뷰포트에서 내비메시 디테일을 볼 수 있으며, 레벨 뷰포트 왼쪽에는 텍스트 기반 디버그 정보도 나와 있습니다.
트랜스폼 툴을 사용하면 내비메시를 이동하고 스케일을 조정하여(키보드의 W 및 R 키) 내비메시 바운드를 수정할 수 있습니다. 이때 원하는 만큼 볼륨의 스케일을 조정할 수 있습니다. 이 예시를 따라 하려면 NavMeshBoundsVolume 오브젝트를 선택하고 디테일 패널의 트랜스폼 섹션에 있는 X, Y, Z 필드를 6, 7, 1로 스케일을 조정해야 합니다.
내비메시의 스케일을 조정하고 움직이면, 큐브처럼 장애물로 간주되는 오브젝트가 없는 한 바운딩 박스의 크기에 따라 녹색 구역이 확장되는 것을 볼 수 있습니다.
빨간색으로 표시된 잘린 부분은 게임에서 AI가 이동할 수 없는 부분을 나타냅니다.
팁: 언리얼 에디터에서 내비메시에 있어서는 안 되는 오브젝트가 있는 경우, 해당 메시 레벨 오브젝트를 선택하고 디테일 패널에서 '내비게이션에 영향 주기 가능(Can Ever Affect Navigation)'을 비활성화합니다.
디버그 정보에 '내비메시 리빌드가 필요합니다' 메시지가 표시되면 메뉴 바에서 '빌드(Build)'로 이동하여 '경로 빌드(Build Paths)'를 선택합니다. 그러면 내비메시가 리빌드됩니다.
레벨에 적 추가하기
적 캐릭터의 내비메시 영역을 생성하고 정의하고 나면 적 캐릭터를 레벨에 추가할 수 있습니다.
콘텐츠 브라우저를 열고 적 블루프린트의 위치로 이동합니다. 적 블루프린트를 레벨 뷰포트로 드래그해 플레이어 앞에 배치합니다. 둘 모두 내비메시 바운드인 녹색 구역 안에 있어야 합니다.
레벨 뷰포트 또는 아웃라이너 패널에서 적 액터를 선택합니다. 디테일 패널의 디폴트 및 구성 프로퍼티 섹션에서 블루프린트에 추가한 변수를 볼 수 있습니다. 이 값을 프로젝트에서 필요한 대로 변경하거나 Debug Detection 변수를 켜서 라인 트레이스의 디버그 드로를 활성화할 수 있습니다.
이제 게임을 플레이해 보면 적과 플레이어 사이의 거리에 따라 적이 움직이지 않을 수 있는데, 적에게 가까이 다가가면 플레이어를 추격하기 시작합니다.
장애물이 있는 환경에서 이를 테스트해 보겠습니다. 플레이 모드를 종료하고 메인 툴바의 추가 버튼을 누르고 '셰이프(Shape)' > '큐브(Cube)'를 선택해 녹색 구역 가운데에 큐브를 추가합니다. 큐브는 자동으로 장애물로 간주되므로 차단이 적용됩니다.
플레이어가 이 큐브 뒤에 있으면, 라인 트레이스가 큐브에 의해 차단되어 적이 플레이어를 찾지 못하게 되고 적이 플레이어를 추격할 수 없게 됩니다. 플레이어가 큐브 주변을 걷고 있을 때 플레이어가 적의 시야에 바로 들어오면 적은 플레이어를 계속 추격합니다.
의도한 대로 작동하지 않는 부분이 있으면, 블루프린트에서 생성한 변수가 씬의 BP_Enemy에 올바른 값으로 설정되어 있는지 다시 확인합니다. Max Speed 또는 Max Detection Distance 값을 설정하지 않으면 적 AI는 움직이지 않으며, 값이 너무 낮은 경우에도 역시 움직이지 않을 수 있습니다.
프로젝트에서 적 위치 디자인하기
샘플 레벨에서는 Hallway 3과 Room 3에 몇 명의 적을 배치했습니다. 첫 번째 적은 Hallway 3에 홀로 배치되었는데, 이는 플레이어가 적을 처음 마주쳐 적 캐릭터를 익히고 적의 능력을 알아가기 위한 것입니다.
복도에서 적 한 명과 마주치고 나면, 플레이어에게 더 어려운 도전이 되도록 Room 3에는 두 명의 적 캐릭터를 추가로 배치했습니다. 적의 숫자와 버전을 활용하면 프로젝트 전반에서 다양한 수준의 난이도를 제시할 수 있습니다. 플레이어에게 새로운 적과 도전 요소를 서서히 드러내면 더 균형 잡힌 난이도 커브를 설정할 수 있어, 플에이어가 시간이 지남에 따라 게임을 익힐 수 있게 됩니다. 플레이어가 길을 찾고 적을 물리치는 방법을 배울 수 있게 한 다음에 어려운 적과 마주치게 해야 플레이어가 성취감을 느낄 수 있는 도전이 됩니다.
다음 순서
다음 모듈에서는 플레이어가 더 빠른 속도로 공간을 이동하고 도전을 헤쳐나가는 데 사용할 수 있는 전력질주 메커니즘을 만드는 방법을 알아봅니다. 이 메커니즘은 플레이어가 공간을 더 효율적으로 이동할 수 있게 해줄 뿐 아니라, 앞서 프로젝트에 추가한 적을 피하는 방법이 될 수 있습니다.
플레이어에 전력질주 메커니즘 추가하기
전력질주할 수 있는 추가 이동 방법을 사용할 수 있도록 플레이어 캐릭터에 입력 액션 에셋을 환경설정해 봅니다.