Code Less, Create More!

Simple but useful code snippets for 3D Graphic Developers

Unreal Engine

여러 객체를 대상으로 카메라 전환하는 방법 (Unreal Engine, C++)

데브엑스 2023. 4. 22. 08:31
반응형
여러 대상 간에 카메라를 전환하면서 사용자의 입력을 처리하는 ​​기술은 게임에서만 필요한 것이 아닙니다. 이 기술은 갤러리, 전시회, 제품 매장 및 시뮬레이션과 같은 여러 유형의 응용 프로그램에서 사용할 수 있습니다.
 
예를 들어 갤러리 또는 제품 매장 앱은 유사한 시스템을 사용하여 사용자가 다양한 전시품, 예술품 또는 제품의 카테고리 와 개별 제품 사이를 탐색할 수 있도록 할 수 있습니다. 선택한 항목에 초점을 맞추도록 카메라를 배치하여 사용자가 작품을 자세히 볼 수 있도록 합니다.
 
차량 또는 항공기와 관련된 시뮬레이션 애플리케이션의 경우 사용자는 다른 비행기 사이를 전환하거나 다른 차량 사이를 운전할 수 있습니다. 카메라는 각 개체에 대한 자세한 보기를 제공하도록 배치되어 사용자가 다양한 관점에서 시뮬레이션을 경험하고 각 대상 모델의 기능을 더 잘 이해할 수 있도록 합니다.
 
Unreal Engine에서는 PlayerController와 Pawn 클래스를 사용하여 이러한 카메라 전환 시스템을 만들 수 있습니다. 아래 이미지는 이 시스템의 간단한 예에 대한 스냅샷입니다.
간단한 카메라 전환 시스템의 스크린샷
 

PlayerController 및 Pawn을 사용하여 보기 대상 변경

이 시스템의 핵심은 현재 카메라를 지정된 Pawn의 Camera로 변경하는 PlayerController::Possess() 함수입니다. 여러 MyPawn 엔티티가 레벨에 배치되고 GetAllActorsOfClass()가 MyPawn 인스턴스를 수집한다고 가정해 보겠습니다. 그런 다음 엔티티의 인덱스를 사용해 카메라가 바라보는 대상 객체를 선택합니다.
여기서는 간단히 < > 키를 사용해서 이전, 이후의 객체를 선택하도록 합니다.
// MyProjectGameModeBase.cpp
void
AMyProjectGameModeBase::SetFollowTarget(bool PrevTarget)
{    
    TArray<AActor*> Actors;
    UGameplayStatics::GetAllActorsOfClass(GetWorld(), AMyPawn::StaticClass(), Actors);
    
    APlayerController* PlayerController = GetWorld()->GetFirstPlayerController();
    AMyPawn* CurrentActor = Cast<AMyPawn>(GetWorld()->GetFirstPlayerController()->GetViewTarget());
 
    int CurrentIndex = Actors.Find(CurrentActor);
    int TargetIndex = -1;
 
    if (PrevTarget)
    {
        TargetIndex = CurrentIndex > 0 ? (CurrentIndex - 1) % Actors.Num() : Actors.Num() - 1;
    }
    else
    {
        TargetIndex = CurrentIndex < 0 ? 0 : (CurrentIndex + 1) % Actors.Num();
    }
 
    AMyPawn* TargetActor = Cast<AMyPawn>(Actors[TargetIndex]);
    if (TargetActor)
    {
        PlayerController->Possess(TargetActor);
    }
 
    UE_LOG(LogTemp, Log, TEXT("SetFollowTarget %s"), *TargetActor->GetName());
}​
 

사용자의 제어 입력과 콜백 함수 연결

SetFollowTarget 함수를 실행하기 위해 Unreal Engine은 클래스의 멤버 함수에 사용자 입력의 Action 과 Axis를 바인딩을 하는 기능을 제공합니다.
// MyProjectGameModeBase.h
UCLASS()
class MYPROJECT_API AMyProjectGameModeBase : public AGameModeBase
{
    GENERATED_BODY()
    
public:
    AMyProjectGameModeBase();
    virtual void BeginPlay() override;
    void SwitchPrevTarget();
    void SwitchNextTarget();
    void ClearFollowTarget();
    void SetFollowTarget(bool Prev);
};
// MyProjectGameBase.cpp
void AMyProjectGameModeBase::BeginPlay()
{
    Super::BeginPlay();
 
    APlayerController* PlayerController = GetWorld()->GetFirstPlayerController();
 
    PlayerController->InputComponent->BindAction("SwitchPrevTarget", IE_Pressed, this, &AMyProjectGameModeBase::SwitchPrevTarget);
    PlayerController->InputComponent->BindAction("SwitchNextTarget", IE_Pressed, this, &AMyProjectGameModeBase::SwitchNextTarget);
    PlayerController->InputComponent->BindAction("ClearFollowTarget", IE_Pressed, this, &AMyProjectGameModeBase::ClearFollowTarget);
}
  
void AMyProjectGameModeBase::SwitchPrevTarget()
{
    SetFollowTarget(true);
}
 
void AMyProjectGameModeBase::SwitchNextTarget()
{
    SetFollowTarget(false);
}
 
void AMyProjectGameModeBase::ClearFollowTarget()
{
    APlayerController* PlayerController = GetWorld()->GetFirstPlayerController();
    
    // Set to default pawn
    APawn* DefaultPawn = Cast<APawn>(UGameplayStatics::GetActorOfClass(GetWorld(), DefaultPawnClass));
    PlayerController->Possess(DefaultPawn);
}

Unreal Engine에서 사용자의 입력은 Action과 Axis로 구분되는 데, 아래 스크린샷과 같이 프로젝트 설정 > 입력 탭에서 동작 및 축 이벤트를 설정할 수 있습니다.

 

 

Pawn 클래스를 사용해 대상 메시를 바라보는 카메라 구성

MyPawn에 대상 메시와 카메라를 배치하기 위해 UStaticMeshComponent, USpringArmComponent, UCameraComponent를 사용하여 멤버 컴포넌트를 생성하고 계층 관계를 설정합니다.
// MyPawn.h
UCLASS()
class MYPROJECT_API AMyPawn : public APawn
{
    GENERATED_BODY()
 
public:
    // Sets default values for this character's properties
    AMyPawn();
 
protected:
    // Called when the game starts or when spawned
    virtual void BeginPlay() override;
 
public:    
    // Called every frame
    virtual void Tick(float DeltaTime) override;
 
    // Called to bind functionality to input
    virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override;
 
    void LookPitch(float Value);
    void LookHeading(float Value);
    void LookZoom(float Value);
    
    
    UPROPERTY(EditAnywhere)
    class UStaticMeshComponent* StaticMesh{ nullptr };
    
    UPROPERTY(EditAnywhere)
    class USpringArmComponent* SpringArm{ nullptr };
    
    UPROPERTY(EditAnywhere)
    class UCameraComponent* Camera{ nullptr };
 
    FVector CenterLocation;
    float MoveRadius{ 100.0f };
    float MoveAngle{ 0.0f };
    float MoveSpeed{ 0.5f };
    float RotationSpeed{ 2.0f };
    float ZoomSpeed{ 5.0f };
};
   
// MyPawn.h
 
// Sets default values
AMyPawn::AMyPawn()
{
     // 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;
 
    StaticMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("StaticMesh"));
    RootComponent = StaticMesh;
 
    SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm"));
    SpringArm->SetupAttachment(StaticMesh);
    SpringArm->TargetArmLength = 400.0f;
    SpringArm->SetWorldRotation(FRotator(-15.0f, 0, 0));
    
    Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
    Camera->SetupAttachment(SpringArm, USpringArmComponent::SocketName);
}
 
// Called when the game starts or when spawned
void AMyPawn::BeginPlay()
{
    Super::BeginPlay();
    
    CenterLocation = GetActorLocation();
}
 
 

대상별 카메라를 별도로 제어

MyProjectGameModeBase와 달리 각 Pawn의 카메라 제어 입력은 MyPawn::SetupPlayerInputComponent()에서 바인딩할 수 있습니다. 이 바인딩은 사용자 입력 이벤트를 카메라를 소유한 현재 Pawn에만 전달하도록 합니다. 
// MyPawn.cpp
// Called to bind functionality to input
void AMyPawn::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)
{
    Super::SetupPlayerInputComponent(PlayerInputComponent);
 
    // BindAxis for all MyPawn instances, but only possesed pawn receives events
    PlayerInputComponent->BindAxis("LookPitch", this, &AMyPawn::LookPitch);
    PlayerInputComponent->BindAxis("LookHeading", this, &AMyPawn::LookHeading);
    PlayerInputComponent->BindAxis("LookZoom", this, &AMyPawn::LookZoom);
}
 
void AMyPawn::LookPitch(float Value)
{
    // Check receiving event input 
    UE_LOG(LogTemp, Log, TEXT("LookPitch %s"), *GetName());
 
    FRotator Rotation = SpringArm->GetComponentRotation();
    Rotation.Pitch += RotationSpeed * Value;
    SpringArm->SetWorldRotation(Rotation);
}
 
void AMyPawn::LookHeading(float Value)
{
    FRotator Rotation = SpringArm->GetComponentRotation();
    Rotation.Yaw += RotationSpeed * Value;
    SpringArm->SetWorldRotation(Rotation);
}
 
void AMyPawn::LookZoom(float Value)
{
    SpringArm->TargetArmLength -= ZoomSpeed * Value;
    SpringArm->TargetArmLength = FMath::Clamp(SpringArm->TargetArmLength, 100.0f, 1000.0f);
}

 

대상 물체의 반복적인 이동 효과

대상 물체가 움직이는 상황에서도 카메라가 일정한 위치와 거리를 유지하도록 하는 기능을 확인할 수 있도록 MyPawn이 자동으로 계속 움직이게 하는 아래의 간단한 수학 함수를 추가했습니다. 
// MyPawn.cpp
// #include "Math/UnrealMathUtility.h"
// Called when the game starts or when spawned
void AMyPawn::BeginPlay()
{
    Super::BeginPlay();
    
    CenterLocation = GetActorLocation();
}
 
// Called every frame
void AMyPawn::Tick(float DeltaTime)
{
    Super::Tick(DeltaTime);
 
    MoveAngle = FMath::Fmod(MoveAngle + MoveSpeed, 360.0f);
    float Rad = FMath::DegreesToRadians(MoveAngle);
    float X = FMath::Sin(Rad) * MoveRadius;
    float Y = FMath::Cos(Rad) * MoveRadius;
 
    SetActorLocation(CenterLocation + FVector(X, Y, 0.0f));
}
 

 

예제 프로젝트

이상의 과정을 하나의 프로젝트에 포함한 간단한 예제 프로젝트와 전체 소스 코드를 아래 링크에서 다운로드할 수 있습니다.
 

GitHub - odyssey2010/UE_switch_multiple_cameras: Example project for switching between multiple cameras

Example project for switching between multiple cameras - GitHub - odyssey2010/UE_switch_multiple_cameras: Example project for switching between multiple cameras

github.com

 

결론

여러 물체들 사이에서 포커스를 전환하고 카메라를 제어하는 ​​것은 다양한 애플리케이션에서 사용할 수 있는 간단하지만 유용한 기술입니다. 약간의 창의성과 변형을 거치면 이 시스템은 다양한 산업 분야의 사용자에게 매력적이고 몰입감 있는 경험을 제공할 수 있습니다.
예제로 제공하는 간단한 코드와 기본 원리에 더해서 마음껏 상상력을 발휘해 자신의 프로젝트에 통합해서 활용해 보세요.

 

반응형