본문 바로가기
언리얼(Unreal)/엔진

25.02.05 언리얼 드론만들기(하)

by alwaysyoung2 2025. 2. 5.
728x90
반응형

시작하기전 반드시 알아야할 개념이 있다.

로컬 좌표계(Local Coordinate System)

  • 객체 자체를 기준으로 한 좌표계
  • 객체가 회전하거나 이동해도 **자신의 원점(0,0,0)**을 기준으로 좌표가 유지됨
  • 예: 비행기가 기울어진 상태에서 "앞으로" 이동하면, 비행기의 앞 방향을 따라 이동

월드 좌표계(World Coordinate System)

  • **씬 전체(또는 절대적인 기준점)**을 기준으로 한 좌표계
  • 객체가 회전하더라도 월드 기준의 방향은 변하지 않음
  • 예: 지구 위의 좌표계에서 사람이 방향을 바꿔도 북쪽(월드 좌표 기준)은 변하지 않음

차이점

기준로컬 좌표월드 좌표

기준점 개별 객체의 원점 씬 전체의 원점
이동 방향 객체의 방향에 따라 변함 항상 같은 방향
회전 영향 회전하면 축도 함께 변함 회전해도 축은 고정

활용 예시

  • 로컬 좌표: 로봇 팔 관절 회전, 캐릭터의 상대적 이동
  • 월드 좌표: 맵 배치, 전역적인 위치 계산

로컬 좌표는 개별 객체의 동작을 쉽게 제어하는 데 유용하고, 월드 좌표는 전체적인 배치를 관리하는 데 적합하다.

 

드론을 구현하기 위해서는 마우스가 움직일때 매쉬와 함께 카메라가 움직일 것인지도 고려해야한다 이를 위한 개념을 살펴보자

 

1. 컨트롤러와 폰(Pawn) 관계

  • bUseControllerRotation: 컨트롤러의 회전을 폰의 회전과 동기화할지 여부를 결정하는 속성
  • Character 클래스는 기본적으로 bUseControllerRotationYaw = true
    → 컨트롤러가 회전하면 즉시 폰도 같은 방향으로 회전

2. 폰(Pawn)과 SpringArmComponent 관계

  • bUsePawnControlRotation: 폰이 회전할 때 SpringArm도 회전할지 여부
    • false: 폰이 회전해도 SpringArm은 움직이지 않음
    • 예: 컷씬에서 플레이어가 움직이지 않는 경우에도 카메라 위치를 유지 가능
  • bDoCollisionTest: 카메라가 벽을 통과하는지 여부를 결정
    • 벽과 충돌하면 카메라를 앞으로 당김
  • bInherit: Root Component의 회전을 따라갈지 여부 결정

3. 폰과 CameraComponent 관계

  • CameraComponent는 SpringArmComponent의 자식
    → bUsePawnControlRotation이 CameraComponent에 적용되지 않고 SpringArmComponent에 따라감

정리

  1. 컨트롤러 → 폰: bUseControllerRotation이 true이면 컨트롤러 회전에 따라 폰이 회전
  2. 폰 → SpringArm: bUsePawnControlRotation이 false이면 폰이 회전해도 SpringArm은 회전하지 않음
  3. SpringArm → 카메라: SpringArm이 움직이면 카메라도 함께 움직임

즉, 컨트롤러, 폰, SpringArm, 카메라의 관계를 조절하는 여러 속성이 있으며,
각 속성을 조정하면 카메라의 움직임과 캐릭터의 회전 방식을 원하는 대로 변경할 수 있다.

 

이를 앞서 드론의 구현파일은 다음과 같다

#include "Drone.h"
#include "DroneController.h"
#include "EnhancedInputComponent.h"
#include "Components/StaticMeshComponent.h"
#include "Components/CapsuleComponent.h"
#include "Camera/CameraComponent.h"
#include "GameFramework/SpringArmComponent.h"


const FVector ADrone::Gravity = FVector(0.f, 0.f, -980.f);

ADrone::ADrone()
    : bMoveInputIgnored(false)
{
    PrimaryActorTick.bCanEverTick = true;

    bUseControllerRotationPitch = true;
    bUseControllerRotationYaw = true;
    bUseControllerRotationRoll = true;

    CapsuleComp = CreateDefaultSubobject<UCapsuleComponent>(TEXT("CapsuleComp"));
    CapsuleComp->InitCapsuleSize(25.f, 25.f);
    CapsuleComp->SetCollisionProfileName(UCollisionProfile::Pawn_ProfileName);
    CapsuleComp->CanCharacterStepUpOn = ECB_No;
    CapsuleComp->SetShouldUpdatePhysicsVolume(true);
    CapsuleComp->SetCanEverAffectNavigation(false);
    CapsuleComp->bDynamicObstacle = true;
    SetRootComponent(CapsuleComp);

    DroneMeshComp = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("DroneMeshComp"));
    DroneMeshComp->SetupAttachment(RootComponent);

    SpringArmComp = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArmComp"));
    SpringArmComp->SetupAttachment(RootComponent);
    SpringArmComp->TargetArmLength = 300.f;
    SpringArmComp->bUsePawnControlRotation = false;
    SpringArmComp->bInheritPitch = false;
    SpringArmComp->bInheritYaw = false;
    SpringArmComp->bInheritRoll = false;

    CameraComp = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComp"));
    CameraComp->SetupAttachment(SpringArmComp, USpringArmComponent::SocketName);
    CameraComp->bUsePawnControlRotation = false;

    NormalSpeed = 500.f;
   
 

}

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

    FVector GravityDisplacement = Gravity * DeltaTime;
    FHitResult HitResult;
    AddActorWorldOffset(GravityDisplacement * DeltaTime, true, &HitResult);
    if (HitResult.IsValidBlockingHit() == true)
    {
        bMoveInputIgnored = true;
    }
    else
    {
        bMoveInputIgnored = false;
    }
}

void ADrone::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    Super::SetupPlayerInputComponent(PlayerInputComponent);

    if (UEnhancedInputComponent* EnhancedInput = Cast<UEnhancedInputComponent>(PlayerInputComponent))
    {
        if (ADroneController* PlayerController = Cast<ADroneController>(GetController()))
        {
            if (PlayerController->DroneMoveAction)
            {
                EnhancedInput->BindAction(PlayerController->DroneMoveAction, ETriggerEvent::Triggered, this, &ThisClass::DroneMove);
            }


            if (PlayerController->DroneLookAction)
            {
                EnhancedInput->BindAction(PlayerController->DroneLookAction, ETriggerEvent::Triggered, this, &ThisClass::DroneLook);
            }


            if (PlayerController->MoveupAction)
            {
                EnhancedInput->BindAction(PlayerController->MoveupAction, ETriggerEvent::Triggered, this, &ThisClass::MoveUp);
            }

			if (PlayerController->RollAction)
			{
				EnhancedInput->BindAction(PlayerController->RollAction, ETriggerEvent::Triggered, this, &ThisClass::Roll);
			}
        }
    }
}

void ADrone::DroneMove(const FInputActionValue& InValue)
{
	if (IsValid(Controller) == false)
	{
		return;
	}
	const FVector2D MoveInput = InValue.Get<FVector2D>();
	if (FMath::IsNearlyZero(MoveInput.X) == false)
	{
		AddActorWorldOffset(GetActorForwardVector() * MoveInput.X);
	}
	if (FMath::IsNearlyZero(MoveInput.Y) == false)
	{
		AddActorWorldOffset(GetActorRightVector() * MoveInput.Y);
	}
}


void ADrone::DroneLook(const FInputActionValue& InValue)
{
	if (IsValid(Controller) == false)
	{
		return;
	}
	const FVector2D LookInput = InValue.Get<FVector2D>();
	if (FMath::IsNearlyZero(LookInput.X) == false)
	{
		AddActorWorldRotation(FRotator(0.f, LookInput.X, 0.f));
	}
	if (FMath::IsNearlyZero(LookInput.Y) == false)
	{
		SpringArmComp->AddRelativeRotation(FRotator(LookInput.Y, 0.f, 0.f));
	}
}



void ADrone::MoveUp(const FInputActionValue& InValue)
{
    if (IsValid(Controller) == false)
    {
        return;
    }

    const float UpDownInput = InValue.Get<float>();

    if (UpDownInput < 0.f && bMoveInputIgnored == true)
    {
        return;
    }

    if (FMath::IsNearlyZero(UpDownInput) == false)
    {
        // 드론의 메쉬(StaticMeshComp)의 회전 기준으로 이동 방향 설정
        FRotator DroneRotation = DroneMeshComp->GetComponentRotation();
        FVector LocalUpVector = DroneRotation.RotateVector(FVector::UpVector);

        AddActorWorldOffset(LocalUpVector * UpDownInput);
    }
}



void ADrone::Roll(const FInputActionValue& InValue)
{
    const float RollInput = InValue.Get<float>();

    if (FMath::IsNearlyZero(RollInput) == false)
    {
        DroneMeshComp->AddLocalRotation(FRotator(0, 0, RollInput * 2.f));  // Roll 적용
    }
}

다소 아쉬운부분: 매쉬의 기즈모기준이 다른곳에 가있어서 그런지 회전축도 다른모습이다...

게다가 look 함수가 제대로 작용하지 않는듯 하여 수정필요

728x90
반응형