제대로 따라가기 (7) C++ 프로그래밍 튜토리얼 :: 일인칭 슈팅 C++ 튜토리얼 (2)
작성버전 :: 4.21.0
언리얼 엔진 튜토리얼인 일인칭 슈팅 C++ 튜토리얼에서는 C++ 코드 작업을 통해서 기본적인 일인칭 슈팅(FPS) 게임을 만드는 법을 배울 수 있다.
이번 튜토리얼은 각 하위 섹션들의 길이가 길어서 분할되어 작성된다.
튜토리얼대로 하면 문제가 발생해서 제대로 따라갈 수 없는 부분으로 동작이 가능하게 수정해야하는 부분은 빨간 블럭으로 표시되어 있다.
이번 튜토리얼에서 새로 배우게 되는 내용은 글 제일 끝에 "이번 섹션에서 배운 것"에 정리된다.
VisibleDefaultsOnly는 정상적으로 존재하는 UPROPERTY 지정자입니다. 제가 실수로 VisibleDefaultOnly로 오타를 내서 컴파일러가 지정자가 없다고 에러를 띄웠었습니다. 잘못된 정보로 혼동을 드린 점에 대해서 사과드립니다.
2. 캐릭터 임포트
이번 섹션의 목표는 일인칭 슈팅 캐릭터 구현법을 배우는 것이다.
2-1. 새 캐릭터 만들기(문서)
이번 단계에서는 엔진의 Character 베이스 클래스를 사용해서 새 캐릭터를 만들어보자. Character 클래스에는 걷기, 달리기, 점프와 같은 이족보행 동작이 기본 내장되어 있다. 1
캐릭터 클래스 추가
파일 드롭다운 메뉴에서 새로운 C++ 클래스... 를 선택해서 새 부모 클래스를 선택한다.
부모 클래스 선책 창이 열리면 Character 클래스를 부모로 선택하고 다음 버튼을 누른다.
새 클래스 이름을 "FPSCharacter"라 하고 클래스 생성을 클릭한다.
FPSCharacter 클래스의 생성이 완료되었다면 FPSCharacter.cpp의 BeginPlay() 함수에 FPSCharacter 클래스가 사용중인지 확인하는 다음 코드를 추가한다.
if (GEngine)
{
GEngine->AddOnScreenDebugMessage(-1, 5.0f, FColor::Red,
TEXT("We are using FPSCharacter!"));
}
이전 섹션에서도 언급했듯이 전역에 선언된 GEngine 변수를 사용하기 위해서는 "Engine.h"를 포함시켜야 한다. FPSCharacter.cpp의 전처리기 리스트에 "Engine.h" 포함 선언을 추가해주자.
#include "Engine.h"
만약 지난 섹션에서 언급한 다른 방식인 "FPSProject.h"에 선언을 추가해두었다면 "FPSProject.h"를 포함시켜주면 된다.
#include "FPSProject.h"
코드 작성이 끝났다면 변경사항을 저장하고 에디터로 돌아가 컴파일을 진행한다.
C++ FPS 캐릭터 클래스를 블루프린트로 확장
콘텐츠 브라우저에서 생성된 FPSCharacter 클래스를 찾아서 우클릭한 뒤 FPSCharacter 기반 블루프린트 클래스 생성을 클릭한다.
BP_FPSCharacter라고 블루프린트 이름을 정하고 Blueprints 폴더를 선택한 뒤, 블루프린트 생성 버튼을 클릭한다.
기본 폰 클래스 설정
새로운 캐릭터 클래스를 블루프린트로 확장했으니, 이제 이 BP_FPSCharacter를 기본 폰으로 사용하도록 설정할 차례다.
프로젝트 세칭 창을 열고 프로젝트 섹셩에 맵 & 모드에서 Default Pawn Class 항목을 BP_FPSCharacter로 설정한다.
세팅이 끝났다면 프로젝트 세팅 창을 닫고 레벨 에디터에서 플레이 버튼을 클릭해서 PIE 모드로 들어가보자. 뷰포트 좌상단에 원래의 로그 메시지와 함께 새로 추가한 "We are using FPSCharacter!"라는 문구가 빨간색으로 5초간 표시될 것이다.
아직 이동에 대한 기능을 전혀 만들지 않았기 때문에 WASD를 사용해도 움직이지 않는 것이 정상적이며 FPSCharacter가 제대로 적용된 것이다.
다음 단계로 넘어가기 전에 Esc키를 눌러 PIE 모드에서 빠져나오자.
2-2. 축 매핑 구성(문서)
일반적으로 축 매핑(Axis Mappings)을 통해서 키보드, 마우스, 컨트롤러 입력을 "친근한 이름"으로 매핑시킨뒤 나중에 이동 등의 게임 동작에 바인딩할 수 있다. 축 매핑은 지속적으로 폴링되어, 부드러운 전환 및 게임 동작이 가능하다. 컨트롤러의 조이스틱 같은 하드웨어 축은 "눌렸다", "안눌렸다" 같은 식의 구분되는 입력이 아닌 연속적인 입력 수치를 제공한다. 컨트롤러 조이스틱 입력 방법이 스케일식 동작 입력을 제공해 주기는 하지만, 축 매핑으로 WASD 처럼 지속적 폴링되는 게임 동작을 위한 일반 이동 키 매핑도 가능하다.
프로젝트 세팅 창을 열고 엔진 섹션의 입력을 선택한다. 그리고 입력 매핑 세팅을 다음처럼 구성한다.
2-3. 캐릭터 동작 함수 구현(문서)
이번 단계에서는 Player Input Component를 구성하고, FPSCharacter 클래스에 다음 함수들을 구현한다.
MoveForward();
MoveRight();
무브먼트 함수 인터페이스
에디터에서 축 매핑을 구성했으니, 매핑에 바인딩할 함수들을 구현하자.
FPSCharacter.h에서 클래스 하단부에 다음 함수들의 선언을 추가한다.
UFUNCTION()
void MoveForward(float AxisValue);
UFUNCTION()
void MoveRight(float AxisValue);
함수 위에 붙여준 UFUNCTION() 매크로는 엔진에게 이 함수들을 인식시켜 직렬화(Serialization), 최적화, 기타 엔진 함수성에 포함될 수 있도록 해준다.
동작 함수 구현
전형적인 FPS 조작법에서, 캐릭터의 동작 축은 카메라에 상대적이다. "전방"이란 "카메라가 향하는 방향"을, "우측"이란 "카메라가 향하는 방향의 우측"을 뜻한다. 캐릭터의 제어 방향을 구하는 데는 PlayerController를 사용할 것이다. 또한 MoveForward() 함수는 제어 회전의 피치 컴포넌트를 무시하고 입력을 XY 면으로 제한시켜 위아래를 쳐다보더라도 캐릭터는 땅과 평행으로 움직일 수 있도록 한다.
FPSCharacter.cpp에서 AFPSCharacter::SetupPlayerInputComponent() 함수의 하단에 다음 코드를
InputComponent->BindAxis("MoveForward", this, &AFPSCharacter::MoveForward);
InputComponent->BindAxis("MoveRight", this, &AFPSCharacter::MoveRight);
그리고 MoveForward()함수와 MoveRight()함수를 구현한다.
void AFPSCharacter::MoveForward(float AxisValue)
{
FVector Direction = FRotationMatrix(Controller->GetControlRotation()).GetScaledAxis(EAxis::X);
AddMovementInput(Direction, AxisValue);
}
void AFPSCharacter::MoveRight(float AxisValue)
{
FVector Direction = FRotationMatrix(Controller->GetControlRotation()).GetScaledAxis(EAxis::Y);
AddMovementInput(Direction, AxisValue);
}
캐릭터 동작 테스트하기
구현이 모두 끝났다면, 변경사항들을 저장하고 에디터로 넘어가서 컴파일을 한다.
컴파일이 끝나면 플레이 버튼을 눌러서 PIE 모드를 실행한 뒤, WASD 키를 눌러서 캐릭터가 전후좌우로 움직이는지 확인해보자.
성공적으로 움직인다면 Esc를 눌러서 PIE 모드를 빠져나오고 다음 파트로 넘어간다.
2-4. 마우스 카메라 컨트롤 구현(문서)
이번 단계에서는 캐릭터의 시야 및 이동 방향을 마우스로 조정하는 기능을 추가한다.
마우스 축 매핑 추가
프로젝트 세팅 창에서 입력 매핑을 다음과 같이 추가한다.
입력 처리 구현
회전과 쳐다보기에 대한 마우스 입력 처리를 하는 코드를 추가할 차례이다. Character 베이스 클래스는 카메라 회전 컨트롤에 대해서 다음과 같은 필수 함수 둘을 제공한다. 그렇기 때문에 FPSCharacter 클래스에 별도의 함수를 정의하고 구현할 필요없이 바로 AFPSCharacter::SetupPlayerInputComponent() 함수에 바인딩하는 코드를 추가하면 된다.
InputComponent->BindAxis("Turn", this, &AFPSCharacter::AddControllerYawInput);
InputComponent->BindAxis("LookUp", this, &AFPSCharacter::AddControllerPitchInput);
마우스 카메라 컨트롤 테스트
변경 사항들을 저장하고 에디터로 넘어가서 컴파일을 진행한 뒤, 플레이 버튼을 누르고 카메라를 움직여서 카메라가 회전하는 것을 확인해보자.
확인을 마쳤다면 Esc를 눌러서 PIE 모드에서 빠져나온다.
2-5. 캐릭터 점프 구현(문서)
액션 매핑은 별도의 이벤트에 대한 입력을 다루며, "친근한 이름"에 매핑시켜 나중에 이벤트 주도형 동작에 바인딩시킬 수 있도록 해준다. 최종 효과는 키나 마우스 버튼, 혹은 키패드 버튼에 대해서 누르기/떼기를 통해서 게임 동작을 실행시키도록 하는 거이다.
이번 단계에서는, 스페이스 바에 대한 액션 매핑을 구성하여 캐릭터에 점프 능력을 추가하는 것이다.
점프 액션 매핑
프로젝트 세팅 창을 열고 엔진 섹션에서 입력을 선택한다. 그리고 액션 매핑을 다음과 같이 추가한다.
입력 처리 구현
Character 베이스 클래스의 인터페이스(*.h) 파일 안을 보면, 캐릭터 점프에 대한 지원이 내장되어 있는 것을 볼 수 있다. 캐릭터 점프는 bPressedJump 변수에 묶여 있어서, 점프 키를 누르면, 이 변수를 true로, 떼면 false로 설정해주기만 하면 된다.
FPSChararcter.h에 다음 두 함수의 선언을 추가한다.
UFUNCTION()
void StartJump();
UFUNCTION()
void StopJump();
그리고 FPSCharacter.cpp에 함수의 구현을 추가해준다.
void AFPSCharacter::StartJump()
{
bPressedJump = true;
}
void AFPSCharacter::StopJump()
{
bPressedJump = false;
}
구현이 끝났으면 SetupPlayerInputComponent() 함수에 바인딩 코드를 추가한다.
InputComponent->BindAction("Jump", IE_Pressed, this, &AFPSCharacter::StartJump);
InputComponent->BindAction("Jump", IE_Released, this, &AFPSCharacter::StopJump);
캐릭터 점프 테스트
변경 사항을 저장하고, 에디터로 돌아가서 컴파일 한다. 그리고 플레이 버튼을 눌러서 PIE에 들어가서 스페이스 바를 눌러서 캐릭터의 점프가 정상적으로 동작하는지 확인해보자.
점프가 정상적으로 동작한다면 Esc키를 눌러서 PIE 모드에서 빠져나온다.
2-6. 캐릭터에 메시 추가(문서)
이번 단계에서는 캐릭터에 스켈레탈 메시를 추가한다. Character 클래스는 기본적으로 우리가 쓸 SkeletalMeshComponent를 생성해 주므로, 어떤 스켈레탈 메시를 사용할지 알려주기만 하면 된다.
이 튜토리얼에서 사용되는 기본 스켈레탈 메시는 파트 제목 옆에 문서 링크를 통해서 들어가면 샘플 메시라는 링크에서 받을 수 있다.
스켈레탈 메시 임포트
콘텐츠 브라우저의 콘텐츠 폴더에 들어가서 파일 창에 우클릭하여 /Game에 임포트... 를 선택해서 임포트 대화창을 연다.
아까 다운받아서 압축해제한 GenericMale.fbx 메시 파일을 찾아 선택하고 열기를 클릭하여 메시를 프로젝트에 임포트 시작한다.
콘텐츠 브라우저에서 FBX 임포트 옵션 대화창이 뜬다. 임포트를 클릭하면 프로젝트에 메시가 추가된다.
저장 버튼을 클릭해서 임포트된 메시를 저장한다.
삼인칭 메시 구성
BP_FPSCharacter 아이콘을 더블클릭해서 블루프린트 에디터를 열고 컴포넌트 탭에서 Mesh 컴포넌트를 선택한다.
디테일 패널의 메시 섹션으로 스크롤해 내려가서 없음이라고 되어있는 드롭다운 메뉴를 클릭해서 GenericMale 스켈레탈 메시를 선택해서 넣는다.
Z 위치를 -88.0으로 설정해서 SkeletalMeshComponent를 CapsuleComponent에 정렬시킨다.
SkeletalMeshComponent는 다음과 같은 상태일 것이다.
일반적으로 CapsuleComponent안에 SkeletalMeshComponent이 포함되고, ArrowComponent와의 방향이 일치해야 캐릭터가 월드에서 정상적으로 돌아다닐 수 있다.
스켈레탈 메시 설정이 끝났다면 BP_FPSCharacter 블루프린트를 컴파일하고 저장한 뒤에 블루프린트 에디터를 닫자.
PIE 모드에서 새 메시 확인
레벨 에디터에서 플레이 버튼을 클릭하면 캐릭터를 따라오는 메시의 그림자가 보일 것이다.
Esc를 눌러 PIE 모드를 빠져나오자.
2-7. 카메라 뷰 변경(문서)
이전 단계에서 살펴보았을 때, 별도의 설정을 하지 않은 기본 카메라의 위치는 메시의 목 안쪽에 위치했었다. 이번 단계에서는 카메라(위치나 시야같은) 프로퍼티 조정에 사용할 수 있는 적합한 FPS 카메라를 구성해보자.
카메라 컴포넌트 붙이기
FPSCharacter.h에 다음 코드를 추가한다.
UPROPERTY(VisibleAnywhere)
UCameraComponent* FPSCameraComponent;
그리고 FPSCharacter.cpp의 AFPSCharacter::AFPSCharacter() 생성자 함수에 다음 코드를 추가해준다.
FPSCameraComponent = CreateDefaultSubobject<UCameraComponent>(TEXT("FirstPersonCamera"));
FPSCameraComponent->SetupAttachment(GetCapsuleComponent());
FPSCameraComponent->SetRelativeLocation(FVector(0.0f, 0.0f, 50.0f + BaseEyeHeight));
FPSCameraComponent->bUsePawnControlRotation = true;
이 코드는 카메라의 위치를 캐릭터의 눈 살짝 위쪽으로 잡으면서 폰이 카메라 로테이션을 제어할 수 있도록 해준다.
이대로 빌드하면 Camera Component에서 에러가 발생해서 컴파일에 실패할 수 있다. 코드를 작성할 때는 에러가 뜨지 않아서 방심했지만 이 에러는 충분이 아는 에러일 것이다. 지금 비주얼 스튜디오가 한글 버전이라 로그가 깨져있지만 튜토리얼을 진행하면서 생긴 경험으로 미루어 짐작하건데, 헤더의 30라인에서 발생하는 에러는 UCameraComponent가 정의되지 않았다는 에러일 것이다. UCameraComponent 선언 앞에 class를 붙여주자.
UPROPERTY(VisibleAnywhere) class UCameraComponent* FPSCameraComponent;
새 카메라 테스트
변경사항을 저장하고, 에디터에서 컴파일한 후, 플레이 버튼을 눌러서 테스트 해보자.
2-8. 캐릭터에 일인칭 메시 추가(문서)
FPS에서 흔히 사용되는 방법은, 전신 바디 메시 하나, 무기와 손 메시하나, 이렇게 별개의 캐릭터 메시 두 개를 사용하는 것이다. 전신 메시는 삼인칭 시첨에서 캐릭터를 보거나 다른 캐릭터를 볼대 사용되고, 플레이어가 일인칭 시점에서 게임을 볼 때는 이 전신 메시를 숨긴다. 그리고 "무기와 손" 메시는 전형적으로 카메라에 붙여 플레이어가 일인칭 시점에서 맵을 볼 때 플레이어에게만 보이는 것이다. 이 파트에서는 캐릭터에 일인칭 메시를 추가해보자.
일인칭 캐릭터 메시 추가
비주얼 스튜디오로 이동해서 FPSCharacter.h를 열고 다음 코드를 추가한다.
UPROPERTY(VisibleDefaultsOnly, Category = "Mesh")
USkeletalMeshComponent* FPSMesh;
그리고 FPSCharacter.cpp의 AFPSCharacter::AFPSCharacter() 생성자 함수에 다음 코드를 추가하여 일인칭 메시를 생성하고 설정해준다.
FPSMesh = CreateDefaultSubobject<USkeletalMeshComponent>(TEXT("FirstPersonMesh"));
FPSMesh->SetOnlyOwnerSee(true);
FPSMesh->SetupAttachment(FPSCameraComponent);
FPSMesh->bCastDynamicShadow = false;
FPSMesh->CastShadow = false;
SetOnlyOwnerSee는 이 메시가 이 Character에 빙의(Possession)한 PlayerController에게만 보인다는 의미다. 이 코드는 메시를 카메라에 붙이고 배경 그림자를 끈다. 만약 카메라에 붙은 팔의 그림자가 보이면 기괴한 모습이 될 것이다.
그 아래쪽에 다음 코드를 추가하여 소유 캐릭터에서 기존 삼인칭 메시를 숨긴다.
GetMesh()->SetOwnerNoSee(true);
이제 코드의 변경 사항을 저장하고 에디터로 돌아가서 컴파일을 진행한다.
만약 에디터 컴파일로 곧바로 변경 사항이 반영되지 않는다면 에디터를 종료하고 솔루션 탐색기에서 FPSProject를 찾아서 우클릭하고 빌드(Build)를 선택해서 프로젝트를 새로 빌드하고 다시 실행하면 변경 사항이 반영된다.
빌드 완료 후, PIE 모드에서 살펴보면 캐릭터의 메시가 더 이상 보이지 않는 것을 알 수 있다.
메시 블루프린트 빌드
계속하기 전에, 다음 링크에서 샘플 메시를 다운로드해서 압축을 풀어야 한다. "일인칭 스켈레탈 메시"
콘텐츠 브라우저의 콘텐츠 파일창에서 우클릭해서 /Game에 임포트를 선택한다.
HeroFPP.fbx 메시 파일을 찾아 임포트 한다.
콘텐츠 브라우저에서 Blueprints 폴더로 들어가서 BP_FPSCharacter 아이콘을 더블클릭해서 블루프린트 에디터를 연다.
컴포넌트 탭에서 FPSMesh 컴포넌트를 찾는다.
FPSMesh 컴포넌트는 FPSCameraComponent의 자손이라, 카메라에 항상 붙어있다.
컴포넌트 탭에서 FPSMesh를 클릭한다.
디테일 탭에서 Mesh 섹션으로 스크롤해 내려가서 "없음"이라는 드롭다운 메뉴를 클릭해서 HeroFPP 스켈레탈 메시를 선택하여 뷰포트에 팔을 추가한다.
새로 추가된 HeroFPP 스켈레탈 메시는 뷰포트 안에서 다음과 같이 보일 것이다.
새로 추가된 메시의 트랜스폼이 카메라 앞에 오도록 조절한다. 위치를 로, 회전을 {-180, 50, -180}으로 설정한다.
블루프린트 에디터를 닫기 전에 BP_FPSCharacter 블루프린트를 반드시 컴파일하고 저장하자.
게임 내 새 메시 확인
레벨 에디터에서 플레이 버튼을 클릭해서 게임 내에서 새 메시를 확인한다.
Esc키를 눌러서 PIE 모드에서 빠져나오자.
다음 섹션에서는 발사체(Projectile) 구현법을 배워보자.
이번 섹션에서 배운 것
1. FRotatorMatrix
FRotatorMatrix RotatorMatrix;
행렬에서 이동 행렬을 뺀 순수하게 회전에 대한 행렬이다.
RotatorMatrix.GetScaledAxis(EAxis::X);
행렬의 크기에 의해서 스케일링된 행렬의 축을 가져오는 함수
2. ACharacter
Controller;
캐릭터를 제어하는 컨트롤러에 대한 변수
Controller->GetControlRotation();
컨트롤 회전을 가져오는 함수. 이것은 카메라 방향(예 : 3 인칭보기)과 다를 수 있는 완전한 목표 회전이며 제어된 Pawn (시각적으로 피치를 두거나 롤하지 않도록 선택할 수 있음)의 회전과 다를 수 있다.
AddControllerYawInput();
캐릭터 클래스에서 기본적으로 지원하는 Yaw 회전 처리 함수. 이 함수를 마우스 좌우 이동 입력과 바인딩하면 카메라 좌우 회전하는 기능을 만들 수 있다.
AddControllerPitchInput();
캐릭터 클래스에서 기본적으로 지원하는 Pitch 회전 처리 함수.
bPressedJump = true;
캐릭터 클래스에서 기본적으로 지원하는 점프 처리 변수. true가 되면 캐릭터 클래스에서 자동으로 점프를 처리해준다.
3. UCameraComponent
CameraComponent->bUsePawnControlRotation = true;
이 카메라 컴포넌트가 폰의 컨트롤 회전을 따라서 회전할 지를 결정하는 변수
4. USkeletalMeshComponent
USkeletalMeshComponent* SkeletalMeshComponent;
애니메이션을 사용할 수 있는 스켈레탈 메시 애셋의 인스턴스를 만드는데 사용되는 컴포넌트.
SkeletalMeshComponent->SetOnlyOwnerSee(true);
UPrimitiveComponent 클래스에서 상속받은 함수로 이 메시를 소유한 플레이어만 이 메시를 볼 수 있게 하는 함수
SkeletalMeshComponent->bCastDynamicShadow = false;
사전에 계산되지 않은 실시간 그림자를 그려야하는지에 대한 변수. CastShadow가 true일 때만 작동한다.
SkeletalMeshComponent->CastShadow = false;
그림자를 그려야하는지에 대한 변수
SkeletalMeshComponent->SetOwnerNoSee(true);
이 메시를 소유한 플레이어에게만 보이지 않도록 하는 함수
- Pawn 클래스에서 파생되었다. [본문으로]
[투네이션]
[Patreon]
[디스코드 채널]
'Unreal Engine4 > Tutorial' 카테고리의 다른 글
[UE4] 언리얼 엔진 4 설치하기 (0) | 2021.04.14 |
---|---|
[UE4] 제대로 따라가기 (8) C++ 프로그래밍 튜토리얼 :: 일인칭 슈팅 C++ 튜토리얼 (3) (8) | 2018.11.30 |
[UE4] 제대로 따라가기 (6) C++ 프로그래밍 튜토리얼 :: 일인칭 슈팅 C++ 튜토리얼 (1) (4) | 2018.11.29 |
[UE4] 제대로 따라가기 (5) C++ 프로그래밍 튜토리얼 :: UMG와 유저 인터페이스 (4) | 2018.11.28 |
[UE4] 제대로 따라가기 (4) C++ 프로그래밍 튜토리얼 :: 플레이어 제어 카메라 (2) | 2018.11.27 |