RPG :: 마우스 입력 이동 구현하기
영상 기준 버전 : 4.27
작성 기준 버전 :: 4.21
이전 섹션에서는 C++ 내려보기 템플릿을 참고해서 일반적인 RPG처럼 마우스 클릭을 통해 캐릭터를 이동시키는 방법을 구현해보고 코드를 분석해본다.
프로젝트 세팅
RpgProject 라는 이름으로 새 프로젝트를 하나 만든다.
RpgProject를 생성한 뒤에는 내려보기 템플릿으로도 새 프로젝트를 하나 만드는데, 이것은 캐릭터의 메시와 애니메이션을 가져오기 위함이다.
제일 먼저 할 일은 내려보기 프로젝트에서 캐릭터의 메시와 애니메이션을 가져올 것이다. 방금 만든 내려보기 프로젝트의 콘텐츠 패널의 콘텐츠 폴더 하위에 Mannequin 폴더가 보일 것이다. 이 안에 캐릭터의 메시와 애니메이션이 들어있다. 이 폴더의 내용물들을 RpgProject로 옮겨야 한다. 이렇게 프로젝트에 포함된 애셋들을 다른 프로젝트로 옮기는 작업을 이주(Migrate)라고 한다. 직접 파일을 옮기지 않아도 언리얼 엔진에서는 이것을 도와주는 기능을 제공한다.
Mannequin 폴더에 우클릭을 하고, 이주... 를 선택한다. 애셋 리포트 창이 뜨면 리스트를 체크하고 확인 버튼을 누른다.
그 다음 대상 콘텐츠 폴더 선택 대화상자가 열리면 RpgProject 프로젝트의 Content 폴더를 찾아서 폴더 선택을 한다.
이주 작업이 끝난 뒤에 RpgProject로 가서 콘텐츠 브라우저 패널을 확인하면 내려보기 프로젝트에 있던 Mannequin 폴더와 그 안의 애셋들이 RpgProject에 성공적으로 옮겨진 것을 확인할 수 있다.
캐릭터의 메시와 애니메이션을 모두 이주시켰으면 그 다음은, 비주얼 스튜티오를 열고 솔루션 탐색기에서 RpgProject.Build.cs를 찾아서 소스파일을 연다.
Build.cs에서는 게임을 개발하면서 사용할 모듈을 추가하거나 뺄 수 있는데, 여기서는 두 가지 모듈을 추가할 것이다. 아래의 예시 코드와 같이 PublicDependencyModuleNames에 "NavigationSystem"과 "AIModule"을 추가해주자. NavigationSystem 모듈은 네비게이션 메시와 관련된 기능에 도움을 주는 모듈이고 AIModule 은 이름 그대로 AI 기능에 관련된 모듈이다. 클릭된 위치로 캐릭터를 이동시킬 때, 처리하는 코드를 일일이 만드는 대신, 이 두 모듈의 기능의 도움으로 내비게이션된 경로를 따라서 움직이도록 만들 예정이다.
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "NavigationSystem", "AIModule" });
맵 세팅하기
프로젝트에 대한 세팅이 끝났다면, 캐릭터가 움직일 맵을 구성해보자.
맵을 세팅하기 이전에 맵에 배치할 기둥이나 벽을 대신할 메시 파일을 다운받는다.
그리고 콘텐츠 브라우저 패널에서 Props 폴더를 생성하고 그 폴더에 방금 다운받은 Box.fbx를 임포트(Import)한다.
그 다음 RpgProject의 레벨 에디터를 보면 빈 평면만 있는 것을 볼 수 있다. 여기에 방금 전에 받은 박스를 이용해서 유닛의 이동을 방해할 수 있게 적절하게 배치해주도록 하자. 아래의 예시와 같이 배치하여도 되고 원하는 대로 편하게 배치해도 된다.
맵에 장애물들을 모두 배치했다면, 모드 패널의 볼륨에서 내비 메시 바운드 볼륨을 선택해서 레벨의 정중앙에 배치한다. 이 내비 매시 바운드 볼륨은 볼륨의 영역 내에 있는 오브젝트들을 찾아서 계산한 뒤 이동 경로를 찾아줄 내비 메시를 만들어내는 역할을 한다.
내비 메시 바운드 볼륨을 맵 중앙에 배치한 뒤, 이것의 스케일을 맵을 충분히 덮을 만큼 키워준 다음, P를 눌러보면 캐릭터가 이동할 수 있는 범위가 초록색으로 표시되는 것을 볼 수 있다. 1
모든 맵 세팅을 마쳤다면 콘텐츠 브라우저에서 Maps 폴더를 만들고 Ctrl + S를 눌러서 Maps 폴더에 지금 만든 맵을 RpgTestMap 이라는 이름으로 저장한다.
맵을 저장한 뒤에는 프로젝트 세팅 창을 열고 맵 & 모드에서 Editor Startup Map을 방금 저장한 RpgTestMap으로 설정해준다. 이렇게 하면 다음에 프로젝트를 열었을 때, 지정한 멥이 제일 먼저 열릴 것이다.
Player Controller로 마우스 입력 받기
이 다음 작업은 플레이어 컨트롤러로 클릭 입력을 받아서 컨트롤러가 소유한 폰을 클릭한 위치로 이동시키는 코드를 작성한다.
코드 작성 이전에 프로젝트 세팅 창의 입력에서 다음과 같이 입력 매핑을 세팅해준다. 이번 섹션에서는 클릭 지점으로 캐릭터를 이동시키는 것만을 목표로 할 것이기 때문에, 입력 매핑은 InputClick을 왼쪽 마우스 버튼으로 하는 것으로 충분하다.
입력 환경설정을 마쳤다면, 콘텐츠 브라우저 패널에서 신규 추가 버튼을 눌러서 새 C++ 클래스를 추가한다. 부모 클래스로는 Player Controller를 선택하고 다음 버튼을 누른다.
클래스의 이름은 RpgPlayerController로 하고 클래스 생성 버튼을 누른다.
클래스가 생성되고 비주얼 스튜디오가 열리면, 우선 다음 코드를 추가해서 생성자를 선언한다.
public:
ARpgPlayerController();
RpgPlayerController.cpp에 생성자를 구현한다.
ARpgPlayerController::ARpgPlayerController()
{
bShowMouseCursor = true;
}
bShowMouseCursor 프로퍼티를 true로 설정하면 게임 내에서 마우스 커서가 보이도록 만들어준다. 우리는 마우스로 캐릭터를 이동시킬 계획이기 때문에 반드시 필요한 코드이다.
RpgPlayerController.h에 다음 변수를 추가한다.
protected:
bool bClickMouse;
이 변수는 왼쪽 마우스 버튼을 누를 때 true가 되고, 뗄 때 false가 될 것이다.
그 아래에 다음 함수들을 선언을 추가한다.
void InputClickPressed();
void InputClickReleased();
이 함수들은 입력 매핑과 바인딩되어서 입력을 받으면 bClickMouse 변수의 값을 바꿔주는 역할을 한다. RpgPlayerController.cpp에 위 두 함수를 구현한다.
void ARpgPlayerController::InputClickPressed()
{
bClickMouse = true;
}
void ARpgPlayerController::InputClickReleased()
{
bClickMouse = false;
}
SetupInputComponent() 함수를 덮어씌워서 구현할 차례다. 헤더에 다음 선언을 추가한다.
virtual void SetupInputComponent() override;
다시 RpgPlayerController.cpp로 가서 SetupInputComponent() 함수를 구현한다.
void ARpgPlayerController::SetupInputComponent()
{
Super::SetupInputComponent();
InputComponent->BindAction("InputClick", IE_Pressed, this, &ARpgPlayerController::InputClickPressed);
InputComponent->BindAction("InputClick", IE_Released, this, &ARpgPlayerController::InputClickReleased);
}
왼쪽 마우스 버튼을 누르면 InputClickPressed() 함수가 호출되서 bClickMouse 변수가 true가 되고, 왼쪽 마우스 버튼을 떼면 InputClickReleased() 함수가 호출되서 bClickMouse 변수가 false가 되도록 세팅되었다.
이 다음 작업은 마우스를 클릭하면 클릭한 위치로 캐릭터를 이동시키는 코드를 작성하는 작업이다.
먼저 RpgPlayerController.h에 다음 함수를 정의한다.
void SetNewDestination(const FVector DestLocation);
이 함수의 역할은 새로운 목표 위치를 받아서 컨트롤러가 소유한 폰을 그 위치로 이동시키는 역할을 할 것이다.
SetNewDestination() 함수에서 내비 메시 위에서 움직이기 위한 작업을 처리하기 위해 다음 전처리기를 추가한다.
#include "Blueprint/AIBlueprintHelperLibrary.h"
이제 RpgPlayerController.cpp에 SetNewDestination() 함수를 구현해보자.
void ARpgPlayerController::SetNewDestination(const FVector DestLocation)
{
APawn* const MyPawn = GetPawn();
if (MyPawn)
{
float const Distance = FVector::Dist(DestLocation, MyPawn->GetActorLocation());
if (Distance > 120.0f)
{
UAIBlueprintHelperLibrary::SimpleMoveToLocation(this, DestLocation);
}
}
}
이 함수에서는 우선 컨트롤러가 소유하고 있는 폰을 가져와서 폰과 목적지 사이의 거리를 측정해서, 그 거리가 120 언리얼 유닛보다 크면 폰을 목적지로 이동시킨다. UAIBlueprintHelperLibrary클래스의 SimpleMoveToLocation() 함수는 프로그래머가 목적지로 폰을 이동시키기 위한 처리를 하는 모든 코드를 일일이 작성하는 대신에 간단한 함수 호출로 그 모든 일을 할 수 있도록 도와준다. 아까 전 프로젝트 세팅 단계에 모듈을 추가한 것은 이 기능을 사용하기 위해서 였다.
헤더 파일로 돌아가서 다음 함수를 정의한다.
void MoveToMouseCursor();
그리고 cpp파일에 MoveToMouseCursor() 함수를 구현한다.
void ARpgPlayerController::MoveToMouseCursor()
{
FHitResult Hit;
GetHitResultUnderCursor(ECC_Visibility, false, Hit);
if (Hit.bBlockingHit)
{
SetNewDestination(Hit.ImpactPoint);
}
}
MoveToMouseCursor() 함수는 GetHitResultUnderCursor() 함수를 통해 마우스 커서 아래에 레이 트레이스를 쏴서 그 위치를 SetNewDestination() 함수에 전달하는 역할을 한다.
PlayerTick() 함수를 덮어쓸 차례이다. 헤더에 다음 함수 선언을 추가한다.
virtual void PlayerTick(float DeltaTime) override;
Cpp 파일에 함수 구현 코드를 추가한다.
void ARpgPlayerController::PlayerTick(float DeltaTime)
{
Super::PlayerTick(DeltaTime);
if (bClickMouse)
{
MoveToMouseCursor();
}
}
코드 추가가 끝났다면 솔루션 탐색기에서 RpgProject를 우클릭 해서 빌드하고 에디터로 돌아간다.
캐릭터 구현
캐릭터가 움직일 맵과 캐릭터를 컨트롤할 플레이어 컨트롤러를 모두 만들었으니 이제 맵 위에서 움직일 캐릭터를 만들 차례이다.
콘텐츠 브라우저 패널에서 신규 추가 버튼을 누르고 새 C++ 클래스를 추가한다. 부모 클래스로는 Character 클래스를 선택한다.
클래스의 이름은 RpgCharacter로 한다.
RpgCharacter 클래스가 생성되면 RpgCharacter.h로 가서 다음 변수들을 추가한다.
private:
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
class UCameraComponent* RpgCameraComponent;
UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))
class USpringArmComponent* RpgCameraSpringArmComponent;
이 변수들은 카메라의 위치를 Rpg 게임에 알맞은 위치로 맞춰주는 역할을 할 것이다. 카메라 컴포너트와 스프링 암 컴포넌트를 초기화 시켜주기 전에, 필요한 컴포넌트들을 사용하기 위한 헤더들을 포함시키는 전처리기들을 RpgCharacter.cpp에 추가해주자.
#include "Engine/Classes/Components/CapsuleComponent.h"
#include "Engine/Classes/Camera/CameraComponent.h"#include "Engine/Classes/GameFramework/CharacterMovementComponent.h"
#include "Engine/Classes/GameFramework/SpringArmComponent.h"
그리고 ARpgCharacter::ARpgCharacter() 생성자 함수로 가서 다음 코드들을 차례로 추가한다.
GetCapsuleComponent()->InitCapsuleSize(42.0f, 96.0f);
이 코드는 캐릭터 클래스가 기본적으로 가지고 있는 캡슐 콜라이더의 크기를 초기화한다.
bUseControllerRotationPitch = false;
bUseControllerRotationYaw = false;
bUseControllerRotationRoll = false;
이 코드는 캐릭터가 카메라의 회전을 따라서 회전하지 않도록 한다.
GetCharacterMovement()->bOrientRotationToMovement = true;
GetCharacterMovement()->RotationRate = FRotator(0.0f, 640.0f, 0.0f);
GetCharacterMovement()->bConstrainToPlane = true;
GetCharacterMovement()->bSnapToPlaneAtStart = true;
이 코드는 캐릭터의 무브먼트를 규정하는 코드로, 캐릭터를 이동시키기 전에 이동 방향과 현재 캐릭터의 방향이 다르면 캐릭터를 이동 방향으로 초당 640도의 회전 속도로 회전시킨다음 이동시킨다. 그리고 캐릭터의 이동을 평면으로 제한하고, 시작할 때 캐릭터의 위치가 평면을 벗어난 상태라면 가까운 평면으로 붙여서 시작되도록 한다. 여기서 평면이란 내비게이션 메시를 의미한다.
RpgCameraSpringArmComponent = CreateDefaultSubobject<USpringArmComponent>(TEXT("RpgCameraSpringArm"));
RpgCameraSpringArmComponent->SetupAttachment(RootComponent);
RpgCameraSpringArmComponent->SetUsingAbsoluteRotation(false);
RpgCameraSpringArmComponent->TargetArmLength = 800.0f;
RpgCameraSpringArmComponent->RelativeRotation = FRotator(-60.0f, 45.0f, 0.0f);
RpgCameraSpringArmComponent->bDoCollisionTest = false;
이 코드는 카메라를 캐릭터에게서 적절한 위지를 잡도록 도와주는 스프링 암 컴포넌트를 생성하고 설정한다.
bAbsoluteRotation은 스프링 암의 회전이 루트 컴포넌트와 상위 컴포넌트를 따르지 않고 월드 좌표계의 회전을 따르도록 한다.
TargetArmLength는 카메라와 캐릭터의 거리를 800으로 설정하고 ReleativeRotation은 스프링 암을 회전시켜 위에서 캐릭터를 내려다보도록 설정한다.
bDoCollisionTest는 카메라가 벽에 닿으면 충돌 계산을 통해 카메라와 캐릭터의 거리를 좁혀 카메라가 벽을 뚫지 않게 만들어주는 프로퍼티이지만, Rpg게임에서는 사용되지 않는 옵션이기 때문에 false로 설정한다.
RpgCameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("RpgCamera"));
RpgCameraComponent->SetupAttachment(RpgCameraSpringArmComponent, USpringArmComponent::SocketName);
RpgCameraComponent->bUsePawnControlRotation = false;
이 코드는 카메라 컴포넌트를 생성하고 스프링 암 컴포넌트에 붙이는 작업을 한다.
PrimaryActorTick.bCanEverTick = true;
PrimaryActorTick.bStartWithTickEnabled = true;
그리고 마지막으로 틱 함수가 동작하도록 설정한다.
모든 코드 작업이 끝났다면 솔루션 탐색기에서 RpgProject를 우클릭해서 프로젝트를 빌드하고 에디터로 돌아간다.
에디터로 돌아왔다면, 콘텐츠 브라우저 패널에서 Bluprints 폴더를 생성한 뒤, RpgCharacter 클래스를 찾아서 우클릭하고 RpgCharacter 기반 블루프린트 클래스 생성을 선택한다.
그리고 Blueprints 폴더에 BP_RpgCharacter라는 이름으로 블루프린트 클래스를 생성한다.
블루프린트가 생성되면, 생성된 블루프린트 클래스를 더블클릭해서 블루프린트 에디터를 열고 컴포넌트 패널에서 Mesh 컴포넌트를 선택한다.
그리고 디테일 패널에서 Mesh 카테고리를 찾아 Skeletal Mesh 프로퍼티를 내려보기 프로젝트에서 이주시킨 SK_Mannequin으로 설정한다.
애니메이션 역시 내려보기 프로젝트에서 가져온 ThirdPerson_AnimBP로 설정한다.
위 작업을 하고 나서 블루프린트 에디터의 뷰포트 패널을 보면 캐릭터의 메시가 캡슐 콜라이더를 벗어나고 방향 역시 다르게 되어 있을 것이다.
이를 일치시키기 위해서 메시 컴포넌트의 위치를 {0.0, 0.0, -90.0}으로 회전을 {0.0, 0.0, -90.0}으로 수정해주자.
세팅이 모두 끝났다면 블루프린트 클래스를 컴파일하고 저장한다.
게임 모드 설정
플레이어 컨트롤러와 캐릭터의 설정이 모두 끝났으니, 이제 게임이 우리가 만든 플레이어 컨트롤러와 캐릭터를 사용하도록 할 차례이다.
콘텐츠 브라우저 패널에서 RpgProjectGameModeBase 클래스를 찾아서 우클릭하여 RpgProjectGameModeBase 기반 블루프린트를 생성한다.
BP_RpgProjectGameModeBase라는 이름으로 Blueprints 폴더에 블루프린트 클래스를 생성한다.
게임 모드 블루프린트가 생성되면 더블클릭하여 블루프린트 에디터를 열고, 디테일 패널에서 Player Controller Class를 RpgPlayerController로, Default Pawn Class를 BP_RpgCharacter로 설정한다. 그리고 블루프린트를 컴파일하고 저장한 뒤 블루프린트 에디터를 닫는다.
레벨 에디터 상단 메뉴바에서 세팅>월드 세팅을 선택하면 월드 세팅 패널이 열린다.
월드 세팅 패널에서 Game Mode Override를 방금 만든 BP_RpgProjectGameMode로 설정한다.
모든 과정을 마친 뒤 레벨 에디터에서 플레이 버튼을 눌러서 PIE 모드로 들어가면 캐릭터가 마우스 클릭 지점으로 이동하고, 그 과정에서 적절하게 장애물을 회피하는 것을 볼 수 있다.
- 언리얼 엔진에서 생성된 내비 메시를 보여주는 단축키이다. [본문으로]
[유니티 어필리에이트 프로그램]
아래의 링크를 통해 에셋을 구매하시거나 유니티를 구독하시면 수익의 일부가 베르에게 수수료로 지급되어 채널의 운영에 도움이 됩니다.
[투네이션]
[Patreon]
[디스코드 채널]
'Unreal Engine4 > Programming' 카테고리의 다른 글
[UE4] Programming - 캐스팅(Casting) (2) | 2018.12.21 |
---|---|
[UE4] Programming - RPG :: 캐릭터를 가리는 벽 투명하게 만들기 (0) | 2018.12.20 |
[UE4] Programming - Player Controller (0) | 2018.12.11 |
[UE4] Programming - TSubclassOf<T> (1) | 2018.12.01 |
[UE4] Programming - 필요없어진 C++ 클래스 삭제하기 (1) | 2018.11.25 |