반응형
여러 대상 간에 카메라를 전환하면서 사용자의 입력을 처리하는 기술은 게임에서만 필요한 것이 아닙니다. 이 기술은 갤러리, 전시회, 제품 매장 및 시뮬레이션과 같은 여러 유형의 응용 프로그램에서 사용할 수 있습니다.
예를 들어 갤러리 또는 제품 매장 앱은 유사한 시스템을 사용하여 사용자가 다양한 전시품, 예술품 또는 제품의 카테고리 와 개별 제품 사이를 탐색할 수 있도록 할 수 있습니다. 선택한 항목에 초점을 맞추도록 카메라를 배치하여 사용자가 작품을 자세히 볼 수 있도록 합니다.
차량 또는 항공기와 관련된 시뮬레이션 애플리케이션의 경우 사용자는 다른 비행기 사이를 전환하거나 다른 차량 사이를 운전할 수 있습니다. 카메라는 각 개체에 대한 자세한 보기를 제공하도록 배치되어 사용자가 다양한 관점에서 시뮬레이션을 경험하고 각 대상 모델의 기능을 더 잘 이해할 수 있도록 합니다.
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));
}
예제 프로젝트
이상의 과정을 하나의 프로젝트에 포함한 간단한 예제 프로젝트와 전체 소스 코드를 아래 링크에서 다운로드할 수 있습니다.
결론
여러 물체들 사이에서 포커스를 전환하고 카메라를 제어하는 것은 다양한 애플리케이션에서 사용할 수 있는 간단하지만 유용한 기술입니다. 약간의 창의성과 변형을 거치면 이 시스템은 다양한 산업 분야의 사용자에게 매력적이고 몰입감 있는 경험을 제공할 수 있습니다.
예제로 제공하는 간단한 코드와 기본 원리에 더해서 마음껏 상상력을 발휘해 자신의 프로젝트에 통합해서 활용해 보세요.
반응형
'Unreal Engine' 카테고리의 다른 글
FString과 std::string의 메모리 사용량 비교 (Unreal Engine, C++) (0) | 2023.04.22 |
---|---|
두께를 가진 Polyline을 Procedural Mesh로 생성하는 방법 (Unreal Engine, C++) (0) | 2023.04.01 |
DrawDebugLine()의 런타임 성능 (Unreal Engine, C++) (0) | 2023.03.22 |
Unreal Engine(C++)에서 Material을 로드하는 방법 (0) | 2023.03.15 |
정다각형(N-gon)을 Procedural Mesh로 생성하는 방법 (Unreal Engine, C++) (0) | 2023.03.14 |