캐릭터 컨트롤

  • 일반적으로 컨트롤러, 폰, 카메라, 스프링암, 캐릭터 무브먼트 요소를 사용해 설정
  • 컨트롤러 : 입력자의 목표 지점을 지정할 때 사용, ControlRotation 속성
  • 폰 : 폰의 트랜스폼 지정
  • 카메라 : 화면 구도를 설정하기 위해 사용, 주로 1인칭 시점에서 사용
  • 스프링 암 : 화면 구도를 설정하기 위해 사용, 주로 3인칭 시점에서 사용
  • 캐릭터 무브먼트 : 캐릭터의 이동과 회전을 조정하는 용도로 사용

Rotation(현재 상태)에서 Desired Rotation(목표 상태)로 한번에 회전하면 부자연스러우므로 각속도로 회전해야 함

 

폰의 이동 함수

  • Look : 마우스 입력으로부터 컨트롤러의 컨트롤 회전을 설정
  • Move : 컨트롤러의 컨트롤 회전으로부터 Yaw 값을 참고해 이동 방향을 설정
  • 콘솔 커맨드 창(단축키 ~)에서 Control Rotation 값을 확인할 수 있음
    • 콘솔 창에 Display 클래스이름 속성이름을 입력하면 속성 값을 확인할 수 있음

폰의 컨트롤 옵션

  • Use Controller Rotation (Pitch/Yaw/Roll)
  • 폰의 회전을 컨트롤러의 Control Rotation과 동기화됨
    • 일정 속도로 회전하는 것이 아닌 바로 동기화

스프링암의 컨트롤 옵션

  • Use Pawn Control Rotation : 스프링 암의 회전을 컨트롤러의 Control Rotation과 동기화됨
  • Inherit (Yaw/Roll/Pitch) : 부모 컴포넌트의 회전 값을 상속받아 최종 회전을 구함
  • Do Collision Test : 카메라와 캐릭터 사이를 지탱해주는 스프링 암 중간에 장애물이 생겼을 때 장애물 앞으로 카메라를 당기는 옵션

카메라의 컨트롤 옵션

  • Use Pawn Control Rotation : 스프링 암에 달린 카메라의 회전을 컨트롤러의 Control Rotation과 동기화, 주로 1인칭

캐릭터 무브먼트 옵션

  • MovementMode : 각 모드별로 이동이 다름, 이동 기능을 끄고 싶으면 None
  • MaxWalkSpeed : 이동 모드에서의 이동 수치
  • JumpZVelocity : 점프의 속도
  • Rotation Rate : 회전 속도
  • Use Controller Desired Rotation : 컨트롤 회전을 목표 회전으로 삼고 지정한 속도로 돌리기
  • Orient Rotation To Movement : 캐릭터 이동 방향에 회전을 일치시키기
  • 폰의 회전 옵션과 충돌나지 않도록 주의

Look 함수의 AddControllerYaw를 살펴보자

// ABCharacterPlayer.cpp
void AABCharacterPlayer::Look(const FInputActionValue& Value)
{
	FVector2D LookAxisVector = Value.Get<FVector2D>();

	AddControllerYawInput(LookAxisVector.X);
	AddControllerPitchInput(LookAxisVector.Y);
}

// Pawn.cpp
void APawn::AddControllerYawInput(float Val)
{
	if (Val != 0.f && Controller && Controller->IsLocalPlayerController())
	{
		APlayerController* const PC = CastChecked<APlayerController>(Controller);
		PC->AddYawInput(Val);
	}
}

// PlayerController.cpp
// Input으로 들어온 Yaw 값을 RotationInput에 저장
void APlayerController::AddYawInput(float Val)
{
	RotationInput.Yaw += !IsLookInputIgnored() ? Val * (GetDefault<UInputSettings>()->bEnableLegacyInputScales ? InputYawScale_DEPRECATED : 1.0f) : 0.0f;
}

// 저장된 RotationInput로 Delta Rotation을 구하고, 변화한 Rotation 값을 설정
void APlayerController::UpdateRotation( float DeltaTime )
{
	// Calculate Delta to be applied on ViewRotation
	FRotator DeltaRot(RotationInput);

	FRotator ViewRotation = GetControlRotation();

	if (PlayerCameraManager)
	{
		PlayerCameraManager->ProcessViewRotation(DeltaTime, ViewRotation, DeltaRot);
	}

	...
    
	SetControlRotation(ViewRotation);
	
    ...
}

 

 

데이터 에셋

  • UDataAsset을 상속받은 언리얼 오브젝트 클래스
  • 주요 옵션을 모아 에디터에서 에셋 형태로 편리하게 데이터를 관리
  • 런타임에 에셋을 교체하여 설정을 변경할 수 있음
  • 각 섹션(Pawn, Character Movement, Input, SpirngArm) 별로 데이터를 저장

데이터 에셋으로 카메라 뷰 변경하는 방법

  • 데이터 에셋 생성 후 뷰 타입(Quater, Shoulder)에 따른 데이터 에셋을 저장
  • 쿼터 뷰일 때 화면을 회전할 필요는 없으므로 Input Mapping Context를 분리
  • 키를 입력했을 때 뷰가 변경되어 데이터 에셋에 따른 설정, Input Mapping Context 변경
// UABCharacterControllData.h
// 해당 클래스로 데이터 에셋을 생성 후 뷰에 맞게 설정 변경
class ARENABATTLE_API UABCharacterControllData : public UPrimaryDataAsset
{
	GENERATED_BODY()
	
public:
	UPROPERTY(EditAnywhere, Category = Pawn)
	uint32 bUseControllerRotationYaw : 1;

	UPROPERTY(EditAnywhere, Category = CharacterMovement)
	uint32 bOrientRotationToMovement : 1;

	UPROPERTY(EditAnywhere, Category = CharacterMovement)
	uint32 bUseControllerDesiredRotation : 1;

	UPROPERTY(EditAnywhere, Category = CharacterMovement)
	FRotator RotationRate;

	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = Input)
	TObjectPtr<class UInputMappingContext> InputMappingContext;

	UPROPERTY(EditAnywhere, Category = SpringArm)
	float TargetArmLength = 400.0f;

	UPROPERTY(EditAnywhere, Category = SpringArm)
	FRotator RelativeRotation;

	UPROPERTY(EditAnywhere, Category = SpringArm)
	uint32 bUsePawnControlRotation : 1;

	UPROPERTY(EditAnywhere, Category = SpringArm)
	uint32 bInheritPitch : 1;

	UPROPERTY(EditAnywhere, Category = SpringArm)
	uint32 bInheritYaw : 1;

	UPROPERTY(EditAnywhere, Category = SpringArm)
	uint32 bInheritRoll : 1;

	UPROPERTY(EditAnywhere, Category = SpringArm)
	uint32 bDoCollisionTest : 1;
};

// ABCharacterBase.cpp
void AABCharacterPlayer::BeginPlay()
{
	Super::BeginPlay();

	SetCharacterControl(CurrentCharacterControlType);
}

void AABCharacterPlayer::SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent)
{
	Super::SetupPlayerInputComponent(PlayerInputComponent);

	// EnhancedInput을 사용하지 않을 경우 에러를 발생하도록 CastChecked 사용
	UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(PlayerInputComponent);

	EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Triggered, this, &ACharacter::Jump);
	EnhancedInputComponent->BindAction(JumpAction, ETriggerEvent::Completed, this, &ACharacter::StopJumping);
	EnhancedInputComponent->BindAction(ChangeControlAction, ETriggerEvent::Triggered, this, &AABCharacterPlayer::ChangeCharacterControl);
	EnhancedInputComponent->BindAction(ShoulderMoveAction, ETriggerEvent::Triggered, this, &AABCharacterPlayer::ShoulderMove);
	EnhancedInputComponent->BindAction(ShoulderLookAction, ETriggerEvent::Triggered, this, &AABCharacterPlayer::ShoulderLook);
	EnhancedInputComponent->BindAction(QuaterMoveAction, ETriggerEvent::Triggered, this, &AABCharacterPlayer::QuaterMove);
}

void AABCharacterPlayer::QuaterMove(const FInputActionValue& Value)
{
	FVector2D MovementVector = Value.Get<FVector2D>();

	float MovementVectorSize = 1.0f;
	float MovementVectorSizeSqaured = MovementVector.SquaredLength();
	
	if (MovementVectorSizeSqaured > 1.0f)
	{
		MovementVector.Normalize();
		MovementVectorSizeSqaured = 1.0f;
	}
	else
	{
		MovementVectorSize = FMath::Sqrt(MovementVectorSizeSqaured);
	}

	const FVector MoveDirection = FVector(MovementVector.X, MovementVector.Y, 0.0f);
	GetController()->SetControlRotation(FRotationMatrix::MakeFromX(MoveDirection).Rotator());
	AddMovementInput(MoveDirection, MovementVectorSize);
}

void AABCharacterPlayer::ChangeCharacterControl()
{
	if (CurrentCharacterControlType == ECharacterControlType::Quater)
	{
		SetCharacterControl(ECharacterControlType::Shoulder);
	}
	else if (CurrentCharacterControlType == ECharacterControlType::Shoulder)
	{
		SetCharacterControl(ECharacterControlType::Quater);
	}
}

void AABCharacterPlayer::SetCharacterControl(ECharacterControlType NewCharacterControlType)
{
	UABCharacterControllData* NewCharacterControlData = CharacterControlManager[NewCharacterControlType];
	check(NewCharacterControlData);

	SetCharacterControlData(NewCharacterControlData);

	APlayerController* PlayerController = CastChecked<APlayerController>(GetController());
	if (UEnhancedInputLocalPlayerSubsystem* Subsystem = ULocalPlayer::GetSubsystem<UEnhancedInputLocalPlayerSubsystem>(PlayerController->GetLocalPlayer()))
	{
		Subsystem->ClearAllMappings();
		UInputMappingContext* NewMappingContext = NewCharacterControlData->InputMappingContext;
		if (NewMappingContext)
		{
			Subsystem->AddMappingContext(NewMappingContext, 0); // 두 번째 매개변수는 우선순위로 다양한 입력이 겹칠 때 우선 순위 높은 것이 작동
		}
	}

	CurrentCharacterControlType = NewCharacterControlType;
}

Roll : X축을 기준으로 회전반경

Yaw : Z축을 기준으로 회전반경

Pitch : Y축을 기준으로 회전반경

+ Recent posts

목차