블루프린트 없이 C++로 폰과 캐릭터 만들기(2)

유영준·2023년 1월 4일
0
post-thumbnail

어제에 이어 캐릭터의 이동을 완성하려고 한다
오늘은 이동에 따른 회전, TPS 시점과 쿼터뷰 시점으로 변경 등을 하려고 한다

ABCharacter.h 에 먼저 2가지 시점을 enum으로 선언해주고, 두 모드를 오갈 수 있게 함수를 선언해주었다

protected:
	// Called when the game starts or when spawned
	virtual void BeginPlay() override;

	enum class EControlMode
	{
		TPS,
		QUARTERVIEW
	};

	void SetControlMode(EControlMode NewControlMode);
    
    //UPROPERTY를 쓰지 않는 변수 초기화
	EControlMode CurrentControlMode = EControlMode::TPS;
	FVector DirectionToMove = FVector::ZeroVector;
    

enum에 비해 enum 클래스는 클래스명을 명시해줘야된다는 점이 있다
실수로 네이밍이 겹치더라도 클래스명을 명시해 중복문제에서 자유로운 enum 클래스가 선호된다

이후 ABCharacter.cpp 에 생성자에서 기본 모드를 Tps로 선언해준다


이후 각각 TPS모드일때와 QUARTERVIEW 시점일때 카메라가 어떻게 작동하는지 코드를 작성해준다

void AABCharacter::SetControlMode(EControlMode NewControlMode)
{
	CurrentControlMode = NewControlMode;
	switch (CurrentControlMode)
	{
	case EControlMode::TPS:
		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::QUARTERVIEW:
		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;
		bUseControllerRotationYaw		   = false;

		GetCharacterMovement()->bOrientRotationToMovement = false;
		GetCharacterMovement()->bUseControllerDesiredRotation = true;
		GetCharacterMovement()->RotationRate = FRotator(0.0f, 720.0f, 0.0f);
		break;
	}
}

각각의 코드가 무얼 뜻하는지 알아보도록 하겠다

SpringArm->TargetArmLength 스프링암의 길이를 제어함
SetRelativeRotation() 부모액터와의 상대적인 각도를 조절
(TPS의 경우 마우스로 제어를 받기때문에 0으로 초기화, 쿼터뷰는 45도 위에서 바라보게 설정)
bUsePawnControlRotation true면 대상(스프링암)이 입력에 따라 회전함
bInherit~~각각 pitch, yaw, roll 방향으로 회전이 가능한지 체크
bDoCollisionTest 카메라와 플레이어 사이에 물체가 있을때 카메라가 앞으로 오도록 설정
bUseControllerRotationYaw true면 카메라 시점에 따라 캐릭터움직이는 방향 바뀜
bOrientRotationToMovement true면 입력받는 방향으로 폰이 회전함(단 액터의 회전값을 받음)
bUseControllerDesiredRotation true면 입력받는 방향으로 폰이 회전함 (컨트롤러의 회전값을 받음)

여기서 bUsePawnControlRotation, bUsePawnControlRotation,
bOrientRotationToMovement, bUseControllerDesiredRotation 는 서로 비슷하면서 다르고, 또 유기적으로 엮여있기 때문에 정확한 이해가 필요하다


이번에는 Tick 함수에서 쿼터뷰일때 이동이 가능하도록 정의해주자

void AABCharacter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);


	switch (CurrentControlMode)
	{
	case EControlMode::QUARTERVIEW:
		if (DirectionToMove.SizeSquared() > 0.0f)
		{
			GetController()->SetControlRotation
            (FRotationMatrix::MakeFromX(DirectionToMove).Rotator());
			AddMovementInput(DirectionToMove);
		}
		break;
	}
}

if (DirectionToMove.SizeSquared() > 0.0f)를 조건으로 한 이유는 vector의 값이
음수값이 나왔을때도 제곱하게 되면 양수로 나오기 때문이다
FRotationMatrix::MakeFromX(DirectionToMove).Rotator() 를 통해
쿼터뷰 시점은 항상 정면을 바라보게 된다

이어서 UpDown 함수와 LeftRight 함수도 각각의 시점에 맡게 변경해준다

void AABCharacter::UpDown(float NewAxisValue)
{
	switch (CurrentControlMode)
	{
	case EControlMode::TPS:
		AddMovementInput(FRotationMatrix(FRotator(0.0f,
        GetControlRotation().Yaw, 0.0f)).GetUnitAxis(EAxis::X), NewAxisValue);
		break;
	case EControlMode::QUARTERVIEW:
		DirectionToMove.X = NewAxisValue;
		break;
	}
}


void AABCharacter::LeftRight(float NewAxisValue)
{
	switch (CurrentControlMode)
	{
	case EControlMode::TPS:
		AddMovementInput(FRotationMatrix(FRotator(0.0f, 
        GetControlRotation().Yaw, 0.0f)).GetUnitAxis(EAxis::Y), NewAxisValue);
		break;
	case EControlMode::QUARTERVIEW:
		DirectionToMove.Y = NewAxisValue;
		break;
	}
}

쿼터뷰일때의 이동은 Tick 함수 부분에서 해결해주기 때문에,
여기서는 기존의 TPS모드만 조금 수정을 해줬다


이제 시점 변경 키를 만들어줄 시간이다 키 바인딩은 어제와 같이 Input 설정에 들어가서 가능하다
다만 이번에는 Axis Mapping 이 아닌 Action Mapping을 해줄 것이다

Action Mapping 은 scale이 따로 존재하지 않아 토글형식에 알맞다


나는 shift + V 를 시점 변경으로 매핑해줬다

헤더에 ViewChange함수를 선언 후 cpp 창에 정의해줬다

void AABCharacter::ViewChange()
{
	switch (CurrentControlMode)
	{
	case EControlMode::TPS:
		GetController()->SetControlRotation(GetActorRotation());
		SetControlMode(EControlMode::QUARTERVIEW);
		break;
	case EControlMode::QUARTERVIEW:
		GetController()->SetControlRotation(SpringArm->GetRelativeRotation());
		SetControlMode(EControlMode::TPS);
		break;
	}
}

이때 SetupPlayerInputComponent에 바인딩을 하는것을 잊지말자


여기까지 왔다면, 시점변경까지 되는 것을 확인할 수 있다

마지막으로 디테일을 조금만 더 추가하기 위해 카메라 간의 이동을 부드럽게
이동하도록 선형보간을 이용해주도록 하겠다

헤더에 바뀔 각도, 바뀔 거리, 이동속도, 회전 속도 변수를 선언한다

생성자에서 LengthSpeed와 RotationSpeed를 선언하고,

SetControlMode에서 TargetArmLength와 SetRelativeRotation 부분 대신
새롭게 수정된 코드를 넣어주자

void AABCharacter::SetControlMode(EControlMode NewControlMode)
{
	CurrentControlMode = NewControlMode;
	switch (CurrentControlMode)
	{
	case EControlMode::TPS:
		//bUsePawnControlRotation와 bUseControllerRotationYaw 가 모두 true 라면, 카메라 방향으로 움직이게 됨
		//bUseControllerRotationYaw가 false 라면 카메라 방향과 상관없이 움직임
		
		//SpringArm->TargetArmLength         = 450.0f;
		//SpringArm->SetRelativeRotation(FRotator::ZeroRotator);
		ArmlengthTo						   = 450.0f;
		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::QUARTERVIEW:
		//SpringArm->TargetArmLength		   = 800.0f;
		//SpringArm->SetRelativeRotation(FRotator(-45.0f, 0.0f, 0.0f));
		ArmlengthTo						   = 800.0f;
		ArmRotationTo					   = FRotator(-45.0f, 0.0f, 0.0f);
		SpringArm->bUsePawnControlRotation = false;
		SpringArm->bInheritPitch		   = false;
		SpringArm->bInheritRoll			   = false;
		SpringArm->bInheritYaw			   = false;
		SpringArm->bDoCollisionTest		   = false;
		bUseControllerRotationYaw		   = false;

		GetCharacterMovement()->bOrientRotationToMovement = false;
		GetCharacterMovement()->bUseControllerDesiredRotation = true;
		GetCharacterMovement()->RotationRate = FRotator(0.0f, 720.0f, 0.0f);
		break;
	}
}

이제 모드를 바꾸게 되면 스프링 암 세팅이 바뀌는 대신
스프링 암의 목표 타겟 길이와 각도만 바뀌게 되고, 이를 선형보간으로 이동하는것을
Tick 함수에서 추가해주자

// Called every frame
void AABCharacter::Tick(float DeltaTime)
{
	Super::Tick(DeltaTime);

	SpringArm->TargetArmLength = FMath::FInterpTo(
    SpringArm->TargetArmLength, ArmlengthTo, DeltaTime, ArmLengthSpeed);

	switch (CurrentControlMode)
	{
	case EControlMode::QUARTERVIEW:
		SpringArm->SetRelativeRotation(FMath::RInterpTo(
        SpringArm->GetRelativeRotation(), ArmRotationTo, DeltaTime, ArmRotationSpeed));
		if (DirectionToMove.SizeSquared() > 0.0f)
		{
			GetController()->SetControlRotation(FRotationMatrix::MakeFromX(DirectionToMove).Rotator());
			AddMovementInput(DirectionToMove);
		}
		break;
	}
}

FInterpTo 는 거리, RInterpTo 는 각도의 선형보간을 담당하며, Unity의 Lerp와 동일하게 동작한다


마지막으로 ViewChange 함수에서 시점을 재지정해주면 완성된다

void AABCharacter::ViewChange()
{
	switch (CurrentControlMode)
	{
	case EControlMode::TPS:
		GetController()->SetControlRotation(GetActorRotation());
		SetControlMode(EControlMode::QUARTERVIEW);
		break;
	case EControlMode::QUARTERVIEW:
		GetController()->SetControlRotation(SpringArm->GetRelativeRotation());
		SetControlMode(EControlMode::TPS);
		break;
	}
}


시점변경까지 부드럽게 되는 모습


다음에는 애니메이션을 손봐 가만히 있을때 Idle 모션을 취하도록 변경을 할 예정이다

profile
토비폭스가 되고픈 게임 개발자

0개의 댓글