
이번에는 언리얼 엔진에서 클릭한 위치로 캐릭터를 이동시키는 기능을 구현해 보도록 할 것이다
흔히 AOS게임, 대표적으로 '리그오브레전드', '도타' 등이 있을 것이다
클릭한 위치로 이동하는 기능은 이미 언리얼 엔진에 구현되어 있지만, 이 프로젝트를 진행하면서 여러 지식들을 공부해 보자
먼저 언리얼 엔진을 실행하고, C++ 타입으로 해서 ClickMove라는 이름으로 새 프로젝트를 생성한다
그다음에는 언리얼5-46 C++ <탑 다운 게임 기능 구현>에서 했듯이 에픽게임즈 런처의 마켓 플레이스에서 'MCO Mocap Basics'를 검색해서 나오는 에셋들을 다운로드하여 프로젝트에 추가해 준다
이미 다운로드 받아져 있는 경우, 라이브러리 탭에서 아래쪽 보관함에 있는 애셋을 프로젝트에 추가하면 된다
캐릭터 메시와 애니메이션을 모두 추가한 다음에는 언리얼 에디터의 상단 메뉴 바에서 [Tools > Open Visual Studio] 항목을 선택해서 비주얼 스튜디오를 열어주고, 솔루션 탐색기에서 "ClickMove.Build.cs"파일을 찾아서 열어준다
이 Build.cs 파일에서는 게임을 개발하면서 사용할 모듈을 추가하거나 뺄 수 있는데, 지금은 두 개의 모듈을 추가해 줄 것이다
이렇게 PublicDependencyModuleNames에 "NavigationSystem"과 "AIModule"을 추가해 준다
[NavigationSystem은 내비게이션 메시와 관련된 기능 모듈이고, AIModule은 이름 그대로 AI 기능에 관련된 모듈이다]
클릭된 위치로 캐릭터를 이동시킬 때, 처리하는 코드를 일일이 만드는 대신 이 두 모듈의 기능을 이용해서 찾아진 경로를 따라서 움직이도록 만들 예정이다
프로젝트 세팅이 끝난 다음에는 캐릭터가 움직일 맵을 구성 해보자
맵 세팅 작업에 들어가기 전에 맵에 배치할 기둥이나 벽을 만들어보자
레벨의 빈 평면에 액터들을 이용해서 캐릭터의 이동을 방해할 수 있게 적절하게 배치해 준다[각자 편한 대로 배치해도 된다]
장애물을 모두 배치한 다음에는 액터 배치 패널에서 Volumes 탭에 있는 Nav Mesh Bounds Volume을 찾은 뒤
레벨의 한가운데에 배치하고 맵의 오브젝트들을 전부 덮도록 Details 패널에 Scale값들을 조정해 준다
[Nav Mesh Bounds Volume은 볼륨의 영역 안에 있는 오브젝트들을 찾아서 계산한 뒤에 이동 경로를 찾아줄 내비 메시를 만들어내는 역할을 한다]
내비 메시 바운드 볼륨 설치를 마친 다음 단축키 [P]를 누르면 캐릭터가 이동할 수 있는 범위가 초록색으로 표시된다
[주의: PlayerStart가 내비 메시 바운드 볼륨 범위 안에 있어야 정상적으로 움직일 수 있다]
이렇게 맵 세팅을 마치면 이 맵을 기본 맵으로 설정하기 위해서 콘텐츠 브라우저에서 Maps 폴더를 만들고
상단 메뉴 바에서 [File > Save Current Level] 항목을 선택한 뒤, 현재 레벨을 Maps 폴더 안에 ClickMoveTestMap이라는 이름으로 저장한다
마지막으로 프로젝트 세팅 창을 연 다음 Project 섹션의 Maps & Modes 항목을 선택하고
Default Maps에서 Editor Startup Map과 Game Default Map을 방금 저장한 ClickMoveTestMap으로 설정해 주면, 다음에 프로젝트를 열었을 때 설정해 준 맵이 제일 먼저 열리게 된다
이다음 작업으로는 플레이어 컨트롤러로 클릭 입력을 받아서 컨트롤러가 소유한 폰을 클릭한 위치로 이동시키는 코드를 작성해 보자
그전에 [Engine > Input] 항목에서클릭한 지점으로 캐릭터를 이동시키기 위해 RightClick이라는 이름으로 오른쪽 마우스 버튼만 액션 매핑에 추가해 준다
입력 세팅을 마쳤다면, 상단 메뉴바에서 [Tools > New C++ Class..] 항목을 선택해서 C++ 클래스 추가 창을 띄우고,
부모 클래스로는 Player Controller를 선택해서 ClickMovePlayerController라는 이름으로 C++ 클래스를 생성한다
클래스가 생성되고 비주얼 스튜디오가 열리면, 먼저 헤더 파일에 생성자를 선언한 다음,
소스 파일로 이동해서 생성자를 구현해 준다
이때 bShowMouseCursor를 true로 변경해 주는데, 이전 글에서도 사용했듯이 이 프로퍼티는 게임 내에서 마우스 커서를 보이게 만들지 아니면 숨길 지를 결정하는 프로퍼티이다
우리는 마우스로 캐릭터를 이동시킬 계획이기 때문에 이 프로퍼티를 true로 변경해서 마우스를 보이게 만들어줘야 한다
이다음에는 다시 헤더 파일로 이동해서 bool 타입으로 bClickLeftMouse 변수를 추가한다
이 변수는 오른쪽 마우스 버튼을 누를 때 true가 되고 뗄 떼 false로 값이 바뀌도록 동작하게 만들 예정이다
그 아래에는 InputLeftMouseButtonPressed 함수와 InputLeftMouseButtonReleased 함수의 선언을 추가한다
이 함수들은 입력 매핑과 바인딩해서 입력을 받으면 bClickLeftMouse 변수의 값을 바꿔주는 역할을 한다
이제 소스 파일로 이동해서 두 함수를 구현하는데
InputLeftMouseButtonPressed 함수에서는 bClickLeftMouse를 true로 변경하고, InputLeftMouseButtonReleased 함수에서는 false로 변경한다
그다음은 SetupInputComponent 함수를 덮어씌워서 만들 차례이다
헤더 파일로 이동해서 SetupInputComponent 함수의 선언을 추가해 주고
다시 소스 파일로 돌아가서 SetupInputComponent 함수를 구현하고 RightClick 입력 매핑과 함수들을 바인딩해 준다
이제 오른쪽 마우스 버튼을 누를 때 InputLeftMouseButtonPressed 함수가 호출돼서 bClickRightMouse 변수가 true가 되고,
오른쪽 마우스 버튼을 떼면 InputRightMouseButtonReleased 함수가 호출되서 bClickRightMouse 변수가 false가 된다
이다음 작업으로는 마우스를 클릭하면 클릭한 위치로 캐릭터를 이동시키는 코드를 작성할 차례이다
먼저 헤더 파일로 이동해서 SetNewDestination 함수를 선언한다
[이 함수는 새로운 목표 위치를 받아서 컨트롤러가 소유한 폰을 그 위치로 이동시키는 역할을 하도록 만들 것이다]
그다음에는 소스 파일로 이동해서 내비 메시 위에서 캐릭터를 이동시키기 위해 #include "Blueprint/AIBlueprintHelperLibrary.h"를 선언해 준다
그리고 SetNewDestination 함수를 구현해 주는데,이 함수에서는 GetPawn( ) 함수를 이용해서 컨트롤러가 소유하고 있는 폰을 가져와서 MyPawn에 저장해 주고
만약 폰이 존재할 경우 FVector 클래스에 정의된 Dist( ) 함수를 사용해서 Destination과 MyPawn->GetActorLocation( )과의 거리를 계산하여 Distance 변수에 저장한다. 그렇게 계산된 거리인 Distance가 120.0f보다 크다면 UAIBlueprintHelperLibrary 클래스의 SimpleMoveToLocation 함수를 호출해서 this(컨트롤러)가 관리하는 Pawn을 Destination으로 이동시키는 간단한 이동 명령을 수행한다.
즉, 이 함수는 프로그래머가 목적지로 폰을 이동시키기 위한 코드를 일일이 작성하는 대신에 간단한 함수 호출로 그 모든 일을 할 수 있도록 도와준다 (이 기능을 사용하기 위해서 앞의 프로젝트 세팅 단계에서 모듈을 추가했다)
헤더 파일로 돌아가서 MoveToMouseCursor 함수를 선언한다
그리고 소스 파일로 이동해서 함수를 구현하는데, 먼저 FHitResult 구조체를 사용해서 충돌 정보를 저장할 Hit 변수를 선언하고
GetHitResultUnderCursor( ) 함수로 마우스 커서 아래의 충돌 정보를 얻는 함수를 호출한다. (ECC_Visibility는 충돌을 검출하거나 무시할 때 사용되며, false는 여러 충돌 결과 중 하나만을 가져오도록 하고 이렇게 얻은 충돌 정보를 Hit에 저장한다)
만약 Hit 구조체의 bBlockingHit 멤버 변수가 true이면(충돌이 발생했다면) SetNewDestination( ) 함수를 호출해서 플레이어 캐릭터의 목적지를 마우스 커서 아래의 충돌 지점인 Hit.ImpactPoint로 설정한다. 이때, ImpactPoint는 충돌 지점의 위치를 나타내는 FVector이다
이제 MoveToMouseCursor 함수를 실시간으로 호출하기 위해서 헤더 파일로 돌아가서 PlayerTick 함수를 선언하고
다시 소스 파일로 돌아와서 bClickLeftMouse 변수의 상태에 따라서 만들었던 MoveToMouseCursor 함수를 호출하도록 PlayerTick 함수를 구현해 준다
컨트롤러 코드 작성이 끝나면 [Ctrl + Shift + 'S']를 눌러 코드를 모두 저장하고 에디터로 돌아가서 컴파일 버튼을 눌러준다
캐릭터가 움직일 맵과 캐릭터를 컨트롤할 플레이어 컨트롤러를 모두 만들었으니 이제 맵 위에서 움직일 캐릭터를 만들 차례이다
이를 위해 상단 메뉴 바에서 [Tools > New C++ Class...] 항목을 선택해서 C++클래스 추가 창을 띄우고 부모 클래스로는 Character 클래스를 선택해서 ClickMoveCharacter라는 이름의 클래스를 생성한다
클래스 생성이 완료되면, 헤더 파일에 UCameraComponent 타입과 USpringArmComponent 타입으로 멤버 변수를 선언해 준다 [이 변수들은 카메라의 위치를 클릭 이동을 주로 하는 RPG 게임처럼 시점을 맞추는 데 사용할 것이다]
이제 카메라 컴포넌트와 스프링 암 컴포넌트를 초기화시켜주기 전에, 필요한 컴포넌트들을 사용하기 위한 전처리기를 추가해야 한다
소스 파일로 가서 #include "Components/CapsuleComponent.h", #include "Camera/CameraComponent.h",
#include "GameFramework/CharacterMovementComponent.h", #include "GameFramework/SpringArmComponent.h"
를 선언해 주면 된다
그리고 생성자 함수에 캐릭터를 초기화하는 코드를 작성한다
먼저 GetCapsuleComponent 함수로 캐릭터 클래스에 있는 기본 콜라이더를 가져와서 InitCapsuleSize 함수로 캡슐 콜라이더의 크기를 정하고, bUseControllerRotationPitch, bUseControllerRotationYaw, bUseControllerRotationRoll 프로터티의 값을 false로 변경해서캐릭터가 카메라의 회전을 따라서 회전하지 않도록 해준다
그다음에는 GetCharacterMovement 함수로 캐릭터 무브먼트를 가져온 다음 bOrientRotationToMovement를 true로 변경해서 캐릭터가 이동하는 방향으로 카메라를 회전하고,
RotationRate를 (0.0f, 640.0f, 0.0f)로 설정해서 캐릭터를 이동시키기 전에 이동 방향과 현재 캐릭터가 바라보는 방향이 다르면 Yaw 축을 기준으로 캐릭터를 초당 640도의 속도로 회전시키도록 만들어준다
그리고 bConstrainToPlane을 true로 변경해서 캐릭터를 특정 평면에만 이동하도록 제한하고, bSnapToPlaneAtStart를 true로 변경해서 캐릭터의 위치가 평면을 벗어난 상태라면 초기위치를 지정된 평면에 자동으로 정렬하도록 한다
그다음으로는 SpringArmComponent를 컴포넌트로 생성해서 이를 루트 컴포넌트로 만든다
그리고 SetUsingAbsoluteRotation을 true로 설정함으로써 스프링 암이 절대 회전 값을 사용하도록 하고, TargetArmLength를 이용해서 카메라와 캐릭터를 800.0f만큼 떨어뜨리도록 한다. 또한 SetRelativeRotation( ) 함수로 카메라의 상대적인 각도를 조절한 다음, bDoCollision 프로퍼티를 false로 설정하여 카메라가 벽에 닿을 때 카메라와 캐릭터의 거리를 좁혀 카메라가 벽을 뚫지 못하게 만들어 주는 기능을 false로 설정한다 (RPG 게임처럼 위에서 내려다보는 시점에는 사용하지 않는다)
그다음에 CameraComponent도 하위 컴포넌트로 생성해서 루트 컴포넌트인 SpringArmComponent 아래에 붙여주고, bUsePawnControlRotation을 false로 변경해서 캐릭터가 회전할 때 카메라도 같이 회전하지 않도록 설정한다
마지막으로 PrimaryActorTick의 bStartWithTickEnabled를 true로 변경해서 BeginPlay 함수가 동작한 직후에 Tick 함수가 계속 동작하도록 만들어준다
코드 작업이 모드 끝난 다음에는 코드를 저장하고 에디터로 돌아가서 컴파일 버튼을 눌러준다
컴파일이 끝나면 콘텐츠 브라우저 패널에서 Blueprints 폴더를 만든 뒤에
ClickMoveCharacter 클래스를 찾아서 우클릭하고 [ClickMoveCharacter 기반 블루프린트 클래스 생성] 항목을 선택해서 캐릭터 블루프린트 클래스를 생성한다
블루프린트를 더블클릭하여 블루프린트 에디터가 열리면 컴포넌트 패널에서 Mesh 컴포넌트를 선택하고, 디테일 패널의 Mesh섹션에서 Skeletal Mesh Asset을 SK_Mannequin으로 설정한다
그리고 뷰포트 패널을 보면 캐릭터의 메시가 캡슐 콜라이더를 벗어나고 캐릭터의 방향 역시 캡슐 콜라이더에 표시된 화살표와 다른 방향을 바라보고 있는 것을 알 수 있다
위치와 방향을 맞춰주기 위해서 메시 컴포넌트의 Location를 {0, 0, -90}으로 Rotation을 {0, 0, -90}으로 수정해 준다
작업이 끝나면 블루프린트를 컴파일하고 저장한 뒤 블루프린트 에디터를 닫아준다
이제 플레이어 컨트롤러와 캐릭터의 설정이 모두 끝났으니 게임이 우리가 만든 플레이어 컨트롤러와 캐릭터를 사용하도록 만들 차례이다
상단 메뉴바에 [Tools > New C++ Class..]를 선택하고 Game Mode Base를 부모 클래스로 상속받은 ClickMoveGameModeBase 이름의 C++ 클래스를 생성해 준다
그리고 콘텐츠 브라우저 패널에서 게임의 기본 게임 모드 C++ 클래스를 찾아서 우클릭한 뒤 BP_ClickMoveGameModeBase라는 이름으로 블루프린트 클래스를 생성해 준다
게임 모드 블루프린트 에디터가 열리면 디테일 패널에서 Player Controller Class를 ClickMovePlayerController로, Default Pawn Class를 BP_ClickMoveCharacter로 교체해주고 나서 블루프린트를 컴파일하고 저장한다
그다음에는 툴바에서 [Settings > World Settings] 항목을 선택해서 월드 세팅 패널을 열고 Game Mode 섹션에 있는
GameMode Override를 방금 만든 BP_ClickMoveGameModeBase로 변경한다
모든 과정을 마친 뒤 플레이 버튼을 눌러서 PIE 모드로 들어가면
캐릭터가 마우스 클릭 지점으로 이동하고, 그 과정에서 적절하게 장애물을 회피하는 모습을 볼 수 있다
VR게임 개발을 위한 언리얼엔진/C++ 공부한 내용 끄적이기...
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!