5.1 캐릭터 모델
ACharacter (캐릭터) 클래스 생성
인간형 폰을 좀 더 효과적으로 제작 할 수 있는 캐릭터 사용
새로운 C++ 클래스 ▷ Character 부모 클래스 선택
ACharacter 클래스 구성
앞에 만든 Pawn을 Character로 변경
Capule, SkeletalMesh, CharacterMovement를 사용해 움직임 제어
각 클래스를 참조 할수 있게 Get 함수 보유
class ENGINE_API ACharacter : public APawn
{
private:
/** The main skeletal mesh associated with this Character (optional sub-object). */
UPROPERTY(Category=Character, VisibleAnywhere, BlueprintReadOnly, meta=(AllowPrivateAccess = "true"))
USkeletalMeshComponent* Mesh;
/** Movement component used for movement logic in various movement modes (walking, falling, etc), containing relevant settings and functions to control movement. */
UPROPERTY(Category=Character, VisibleAnywhere, BlueprintReadOnly, meta=(AllowPrivateAccess = "true"))
UCharacterMovementComponent* CharacterMovement;
/** The CapsuleComponent being used for movement collision (by CharacterMovement). Always treated as being vertically aligned in simple collision check functions. */
UPROPERTY(Category=Character, VisibleAnywhere, BlueprintReadOnly, meta=(AllowPrivateAccess = "true"))
UCapsuleComponent* CapsuleComponent;
}
AABCharacter.h 구성
이전에 제작한 폰과 동일한 동작 할 수 있게 함수 생성
#include "ArenaBattle.h"
#include "GameFramework/Character.h"
#include "ABCharacter.generated.h"
UCLASS()
class ARENABATTLE_API AABCharacter : public ACharacter
{
GENERATED_BODY()
//...
public:
UPROPERTY(VisibleAnywhere, Category = Camera)
USpringArmComponent* SpringArm;
UPROPERTY(VisibleAnywhere, Category = Camera)
UCameraComponent* Camera;
private:
void UpDown(float NewAxisValue);
void LeftRight(float NewAxisValue);
//...
}
AABCharacter.cpp 구성
AABCharacter::AABCharacter()
{
// Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SPRINGARM"));
Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("CAMERA"));
//Character 클래스 내부에 있는 Capsule 컴포넌트 참조
SpringArm->SetupAttachment(GetCapsuleComponent());
Camera->SetupAttachment(SpringArm);
//Character 클래스 내부에 있는 Mesh 컴포넌트 참조
GetMesh()->SetRelativeLocationAndRotation(FVector(0.0f, 0.0f, -88.0f), FRotator(0.0f, -90.0f, 0.0f));
SpringArm->TargetArmLength = 400.0f;
SpringArm->SetRelativeRotation(FRotator(-15.0f, 0.0f, 0.0f));
static ConstructorHelpers::FObjectFinder<USkeletalMesh> SK_CARDBOARD
(TEXT("/Game/InfinityBladeWarriors/Character/CompleteCharacters/SK_CharM_Cardboard.SK_CharM_Cardboard"));
if (SK_CARDBOARD.Succeeded())
{
//Character 클래스 내부에 있는 Mesh 컴포넌트 참조
GetMesh()->SetSkeletalMesh(SK_CARDBOARD.Object);
}
GetMesh()->SetAnimationMode(EAnimationMode::AnimationBlueprint);
static ConstructorHelpers::FClassFinder<UAnimInstance> WARRIOR_ANIM
(TEXT("/Game/Book/Animations/WarriorAnimBlueprint.WarriorAnimBlueprint_C"));
if (WARRIOR_ANIM.Succeeded())
{
//Character 클래스 내부에 있는 Mesh 컴포넌트 참조
GetMesh()->SetAnimInstanceClass(WARRIOR_ANIM.Class);
}
}
ABGameMode.cpp 에서 기본 폰 변경
#include "ABCharacter.h"
#include "ABPlayerController.h"
AABGameMode::AABGameMode()
{
DefaultPawnClass = AABCharacter::StaticClass();
//...
}
ABCharacter 와 ABPawn 차이점
FloatingPawnMovement 와 CharacterMovement 차이점
1. 점프와 같은 중력을 반영한 움직임 제공
2. 기어가기 및 날아가기 다양한 이동 모드 설정
3. 멀티 플레이 네트워크 환경에서 캐릭터들의 움직임을 자동으로 동기화
컨트롤 회전 활용
플레이어가 입장 시 생성 되는 컨트롤러 와 폰
컨트롤러는 물리적인 요소를 고려하지 않고 플레이어의 의지에 관련된 데이터를 관리
폰은 게임 세계의 물리적인 제약을 가지고 현재 캐릭터의 처한 물리적인 상황을 관리
폰에서 velocity 속도 값 제공, 컨트롤러의 컨트롤 회전(Control Rotation 속성을 제공
컨트롤 회전을 알아 보기 위해 예제 제작
1. 마우스 움직임에 따라 폰이 회전 하는 회전 값 설정
2. 폰이 일정한 속도로 회전하는 기능 제작
마우스의 상하 좌우에 움직임을 프로젝트 설정 ▷ 입력 창
Turn 과 LookUp를 마우스 축(Axis) 입력 설정
해당 Turn, Lookup 값을 캐릭터가 회전 할 수있게 AddControllerInputYaw, Roll, Pitch 제공
Turn 은 Z축 회전, Look up 은 Y축 회전으로 대응
ABCharacter.h
private:
void LookUp(float NewAxisValue);
void Turn(float NewAxisValue);
ABCharacter.cpp
void AABCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
PlayerInputComponent->BindAxis(TEXT("UpDown"), this, &AABCharacter::UpDown);
PlayerInputComponent->BindAxis(TEXT("LeftRight"), this, &AABCharacter::LeftRight);
PlayerInputComponent->BindAxis(TEXT("Turn"), this, &AABCharacter::Turn);
PlayerInputComponent->BindAxis(TEXT("LookUp"), this, &AABCharacter::LookUp);
}
void AABCharacter::LookUp(float NewAxisValue)
{
AddControllerPitchInput(NewAxisValue);
}
void AABCharacter::Turn(float NewAxisValue)
{
AddControllerYawInput(NewAxisValue);
}
PlayController ControlRotation 값을 화면 표시
실행 후 뷰포트 클릭하고 ~ 눌러 콘솔 명령어 입력
displayall PlayerController ControlRotation 입력
현재 Playcontroller에 입력되는 ControlRotation 갑을 실시간로 확인
캐릭터의 회전 연동
UseControllerRotationYaw
컨트롤 회전 Yaw 회전 값과 폰의 Yaw 회전 연동이 기본 값 true
마우스 좌우로 움직이면 Z 축으로 회전
UseControllerRotation (Roll, Yaw, Pitch) false 경우
각축의 회전은 영향을 받지 않음
플레이어 컨트롤러 컨트롤 스케일 값 조정
PlayerController 에는 Input Yaw, Roll, Pitch Scale 값을 조정 하면 컨트롤 회전 값의 변화
삼인칭 컨트롤러 개발 (GTA 방식)
캐릭터 이동 : 보는 시점 기준으로 상하 좌우 이동
캐릭터 회전 : 캐릭터가 이동하는 방향
카메라 지지대 길이 : 450
카메라 회전 : 마우스 상하 좌우 이동에 따라 카메라 회전
카메라 줌 : 카메라의 시선과 캐릭터 사이 장애물 감지 시 캐릭터가 보이도록 카메라 줌
SpringArm 컴포넌트로 삼인칭 시점 카메라 설정
void AAABCharacter::UpDown(float NewAxisValue)
{
//현재 컨트롤러의 회전 값을 매트릭스로 변환 후 X축 방향 벡터 추출
FVector Rotationvector = FRotationMatrix(GetControlRotation()).GetUnitAxis(EAxis::X);
ABLOG(Warning, TEXT("%f, %f, %f"), Rotationvector.X, Rotationvector.Y, Rotationvector.Z);
AddMovementInput(Rotationvector, NewAxisValue);
//현재 엑터의 정면 방향 벡터로 회전
//AddMovementInput(GetActorForwardVector(), NewAxisValue);
}
void AAABCharacter::LeftRight(float NewAxisValue)
{
//현재 컨트롤러의 회전 값을 매트릭스로 변환 후 Y축 방향 벡터 추출
AddMovementInput(FRotationMatrix(GetControlRotation()).GetUnitAxis(EAxis::Y), NewAxisValue);
//현재 엑터의 right 방향 벡터
//AddMovementInput(GetActorRightVector(), NewAxisValue);
}
void AAABCharacter::SetControlMode(int32 ControlMode)
{
if (ControlMode == 0)
{
SpringArm->TargetArmLength = 450.0f;
SpringArm->SetRelativeRotation(FRotator::ZeroRotator);
SpringArm->bUsePawnControlRotation = true;
SpringArm->bInheritPitch = true;
SpringArm->bInheritRoll = true;
SpringArm->bInheritYaw = true;
SpringArm->bDoCollisionTest = true;
bUseControllerRotationYaw = false;
//캐릭터가 움직이는 방향으로 자동으로 회전
GetCharacterMovement()->bOrientRotationToMovement = true;
//회전 속도 지정 Y축으로 초당 720
GetCharacterMovement()->RotationRate = FRotator(0.0f, 720.f, 0.0f);
}
}
삼인칭 컨트롤 구현(디아블로 방식)
카메라 길이 : 800
카메라 회전 : 회전 없이 항상 고정 시선 45도로 내려다봄
카메라 줌 : 없음, 카메라와 캐릭터 사이에 장애물이 있는 경우 외관석으로 처리
AABCharacter.h 예제
class 키워드로 열거형 선언 방식과 class 없이 선언 하는 방식 중에
묵시적 변환을 허용 하지 않는 class 키워드를 사용
열겨형 선언 및 UPROPERTY 사용하지 않는 변수
UPROERTY 사용하지 않는 변수는 생성 후 바로 초기화 하는것이 안전
enum class EControlMode
{
GTA,
DIABLO
};
void SetControlMode(EControlMode NewControlMode);
EControlMode CurrentControlMode = EControlMode::GTA;
FVector DirectionToMove = FVector::ZeroVector;
입력의 Axis 이벤트와 Tick 이벤트 모두 매 프레임 처리 되는데
Axis 이벤트 후 Tick 이벤트 진행, 입력에 따라서 Tick 처리가 달라 질 수 있다
AABCharacter.cpp 예제
각 열거형 모드 마다 SpringArm 지정
디아블로 모드에서는 컨트롤러 회전 값이 카메라 회전은 적용 되지 않고
카메라 시선만 밑으로 고정
부드럽게 회전 적용
GTA : 캐릭터가 이동하는 방향으로 회전
DIABLO : 컨트롤러 방향에 맞게 캐릭터 회전
캐릭터의 이동 방향에 맞게 캐릭터 회전
GetCharacterMovement()->bOrientRotationToMovement = true;
컨트롤러의 회전 값으로 캐릭터 회전
GetCharacterMovement()->bUseControllerDesiredRotation = false;
AABCharacter::AABCharacter()
{
//...
SetControlMode(EControlMode::GTA);
}
void AABCharacter::SetControlMode(EControlMode NewControlMode)
{
CurrentControlMode = NewControlMode;
switch (CurrentControlMode)
{
case EControlMode::GTA:
SpringArm->TargetArmLength = 450.0f;
SpringArm->SetRelativeRotation(FRotator::ZeroRotator);
SpringArm->bUsePawnControlRotation = true;
SpringArm->bInheritPitch = true;
SpringArm->bInheritRoll = true;
SpringArm->bInheritYaw = true;
SpringArm->bDoCollisionTest = true;
bUseControllerRotationYaw = false;
GetCharacterMovement()->bOrientRotationToMovement = true;
GetCharacterMovement()->bUseControllerDesiredRotation = false;
GetCharacterMovement()->RotationRate = FRotator(0.0f, 720.0f, 0.0f);
break;
case EControlMode::DIABLO:
SpringArm->TargetArmLength = 800.0f;
SpringArm->SetRelativeRotation(FRotator(-45.0f, 0.0f, 0.0f));
SpringArm->bUsePawnControlRotation = false;
SpringArm->bInheritPitch = false;
SpringArm->bInheritRoll = false;
SpringArm->bInheritYaw = false;
SpringArm->bDoCollisionTest = false;
//컨트롤러의 Yaw 값을 자동으로 갱신 안함
bUseControllerRotationYaw = false;
GetCharacterMovement()->bOrientRotationToMovement = false;
//컨트롤러의 회전 값을 가지고 캐릭터 회전
GetCharacterMovement()->bUseControllerDesiredRotation = true;
//1초에 Yaw 720.0f 회전
GetCharacterMovement()->RotationRate = FRotator(0.0f, 720.0f, 0.0f);
break;
}
}
Tick, updown, Leftright, Lookup, Turn 열거형 동작에 맞게 수정
언리얼에서 기존 X축 (0,0,0)을 (0,1,0)로 변경 시 오른쪽으로 회전 값은 Yaw 90 생성FRotator yaw = FRotationMatrix::MakeFromX(DirectionToMove).Rotator();//yaw 90
Y축 기준(0,1,0) 으로 회전 행렬 생성 시 0,1,0 일 경우 아래로 회전 Pitch 90 생성
FRotator pitch = FRotationMatrix::MakeFromY(DirectionToMove).Rotator();//pitch 90
void AABCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
switch (CurrentControlMode)
{
case EControlMode::DIABLO:
//키보드 입력에서 DirectionToMove 값 갱신
if (DirectionToMove.SizeSquared() > 0.0f)
{
//월드 좌표계 X축 기준
//정면을 0,0,0 이라고 하면 0,1,0 일경우 오른쪽으로 회전 값은 Yaw 90 생성
FRotator yaw = FRotationMatrix::MakeFromX(DirectionToMove).Rotator();//yaw 90
//Y축 기준으로 회전 행렬 생성 시 0,1,0 일 경우 아래로 회전 Pitch 90 생성
FRotator pitch = FRotationMatrix::MakeFromY(DirectionToMove).Rotator();//pitch 90
//컨트롤러의 회전 값을 지정
GetController()->SetControlRotation(yaw);
AddMovementInput(DirectionToMove);
}
break;
}
}
void AABCharacter::UpDown(float NewAxisValue)
{
switch (CurrentControlMode)
{
case EControlMode::GTA:
//현재 컨트롤러의 Yaw 기준 으로 정면 방향
AddMovementInput(FRotationMatrix(FRotator(0.0f,
GetControlRotation().Yaw, 0.0f)).GetUnitAxis(EAxis::X), NewAxisValue);
break;
case EControlMode::DIABLO:
DirectionToMove.X = NewAxisValue;
break;
}
}
void AABCharacter::LeftRight(float NewAxisValue)
{
switch (CurrentControlMode)
{
case EControlMode::GTA:
//현재 컨트롤러의 Yaw 기준 으로 우 방향
AddMovementInput(FRotationMatrix(FRotator(0.0f,
GetControlRotation().Yaw, 0.0f)).GetUnitAxis(EAxis::Y), NewAxisValue);
break;
case EControlMode::DIABLO:
DirectionToMove.Y = NewAxisValue;
break;
}
}
void AABCharacter::LookUp(float NewAxisValue)
{
switch (CurrentControlMode)
{
case EControlMode::GTA:
AddControllerPitchInput(NewAxisValue);
break;
}
}
void AABCharacter::Turn(float NewAxisValue)
{
switch (CurrentControlMode)
{
case EControlMode::GTA:
AddControllerYawInput(NewAxisValue);
break;
}
}
컨트롤 설정의 변경
프로젝트 설정 ▷ 입력 에서 ViewChange 액션 매핑, shfit + V 키 설정
GTA, DIAVLO View 변경
액션 매핑 입력 설정 과 연동
EInputEvent::IE_Pressed 누르면 발생, EInputEvent::IE_Released 버튼 업일때 발생
void AABCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
Super::SetupPlayerInputComponent(PlayerInputComponent);
//ViewChange 키를 누른 시점에 ViewChange 함수 실행
PlayerInputComponent->BindAction(TEXT("ViewChange"), EInputEvent::IE_Pressed,
this, &AABCharacter::ViewChange);
//...
}
void AABCharacter::ViewChange()
{
switch (CurrentControlMode)
{
case EControlMode::GTA:
GetController()->SetControlRotation(GetActorRotation());
SetControlMode(EControlMode::DIABLO);
break;
case EControlMode::DIABLO:
GetController()->SetControlRotation(SpringArm->RelativeRotation);
SetControlMode(EControlMode::GTA);
break;
}
}
캐릭터 View 변경 시 부드럽게 전환
SpringArm 카메라의 길이 및 회전에 시간에 따라 변경 될 수 있게 코드 수정
FInterTo는 float형 값, VInterTo는 Vector형 값, RInterTo Rotator형 값 등을 선형 보간
선형 보간에 필요한 데이터 지정
GTA는 ArmLegthTo : 450.0f으로 선형 보간
DIABLO는 ArmLegthTo : 800.0f, ArmRotationTo : (-45.0f, 0.0f, 0.0f) 회전 값으로 선형 보간
ArmLegthTo : 길이 값
ArmRotationTo : 회전 값
ArmLengthSpeed : ArmLegthTo이 결과 값으로 변환 되는 속도 값
ArmRotationSpeed : ArmRotationTo가 결과 값으로 변환 되는 회전 속도 값
UCLASS()
class ARENABATTLE_API AABCharacter : public ACharacter
{
GENERATED_BODY()
private:
float ArmLengthTo = 0.0f;
FRotator ArmRotationTo = FRotator::ZeroRotator;
float ArmLengthSpeed = 0.0f;
float ArmRotationSpeed = 0.0f;
}
AABCharacter::AABCharacter()
{
//속도 값 지정
ArmLengthSpeed = 3.0f;
ArmRotationSpeed = 10.0f;
}
void AABCharacter::SetControlMode(EControlMode NewControlMode)
{
CurrentControlMode = NewControlMode;
switch (CurrentControlMode)
{
case EControlMode::GTA:
//SpringArm->TargetArmLength = 450.0f;
//SpringArm->SetRelativeRotation(FRotator::ZeroRotator);
ArmLengthTo = 450.0f;
//....
case EControlMode::DIABLO:
//SpringArm->TargetArmLength = 800.0f;
//SpringArm->SetRelativeRotation(FRotator(-45.0f, 0.0f, 0.0f));
ArmLengthTo = 800.0f;
ArmRotationTo = FRotator(-45.0f, 0.0f, 0.0f);
//...
}
void AABCharacter::Tick(float DeltaTime)
{
Super::Tick(DeltaTime);
//TargetarmLength 선형 보간
SpringArm->TargetArmLength = FMath::FInterpTo(SpringArm->TargetArmLength, ArmLengthTo, DeltaTime, ArmLengthSpeed);
switch (CurrentControlMode)
{
case EControlMode::DIABLO:
//RelativeRotation Rotator 값 선형 보간
SpringArm->RelativeRotation = FMath::RInterpTo(SpringArm->RelativeRotation, ArmRotationTo, DeltaTime, ArmRotationSpeed);
break;
}
}
void AABCharacter::ViewChange()
{
switch (CurrentControlMode)
{
case EControlMode::GTA:
//시점 변경 시 컨트롤러의 회전 값을 캐릭터 회전 값으로 초기화
GetController()->SetControlRotation(GetActorRotation());
SetControlMode(EControlMode::DIABLO);
break;
case EControlMode::DIABLO:
//시점 변경 시 컨트롤러의 회전 값을 SpringArm->RelativeRotation으로 초기화
//(-45.0,0,0) 고정
GetController()->SetControlRotation(SpringArm->RelativeRotation);
SetControlMode(EControlMode::GTA);
break;
}
}