スプリングアームとカメラをポーンに取り付けると、カメラが世界のポーンにどのように追従するかを調整できます。
1.カメラをポーンにアタッチする
まず、Unreal Engine を起動してブランクのテンプレートを新規作成します。
新しい Game Mode を選択します。[Project Settings (プロジェクト設定)] ウィンドウで、デフォルト コードを Blueprint から C++ に変更し、スターターコンテンツ有りを選択します。Unreal プロジェクトの保存先とするディレクトリを選択し、プロジェクト名を「HowTo_PlayerCamera」とし、[Create Project (プロジェクトを作成)] をクリックします。
ソース パネルから「C++ Classes」フォルダへ移動します。単一クラスに Game モードの基本クラスが含まれてることが分かります。隣のグレイのスペースを右クリックして、ドロップダウン ウィンドウから [New C++ class] を選択します。
ポーンを親クラスとして選択し、[Next] をクリックします。新しいポーンに「PawnWithCamera」と名前を付けます。
Visual Studio で「
PawnWithCamera.h
」を開き、クラス定義の保護された名前空間に以下のコードを追加します。protected: UPROPERTY(EditAnywhere) class USpringArmComponent* SpringArmComp; UPROPERTY(EditAnywhere) class UCameraComponent* CameraComp; UPROPERTY(EditAnywhere) UStaticMeshComponent* StaticMeshComp;
こうした変数を使って、端に CameraComponent をアタッチした SpringArmComponent を作成します。スプリングアームは、カメラ (または他のコンポーネント) をアタッチする単純な方法であり、観察するオブジェクトと一緒に動くとき、ある程度の柔軟性を持って、滑らかな動きをすることができます。
PawnWithCamera.cpp
で、以下の include を宣言します。#include "GameFramework/SpringArmComponent.h" #include "Camera/Component.h"
次に、スタティックメッシュ、スプリングアーム、カメラ コンポーネントを作成する必要があります。クラス コンストラクタ APawnWithCamera::APawnWithCamera 内から以下のコードを PawnWithCamera.cpp に追加します。次のコードを追加します。
//Create our components RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootSceneComponent")); StaticMeshComp = CreateDefaultSubobject <UStaticMeshComponent>(TEXT("StaticMeshComponent")); SpringArmComp = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArmComponent")); CameraComp = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComponent")); //Attach our components StaticMeshComp->SetupAttachment(RootComponent); SpringArmComp->SetupAttachment(StaticMeshComp); CameraComp->SetupAttachment(SpringArmComp,USpringArmComponent::SocketName); //Assign SpringArm class variables. SpringArmComp->SetRelativeLocationAndRotation(FVector(0.0f, 0.0f, 50.0f), FRotator(-60.0f, 0.0f, 0.0f)); SpringArmComp->TargetArmLength = 400.f; SpringArmComp->bEnableCameraLag = true; SpringArmComp->CameraLagSpeed = 3.0f;
これにより、SceneComponent がコンポーネント階層のルートとして作成されます。次に、StaticMeshComponent を作成してアタッチします。その後、SpringArmComponen tと Camera Component の両方が作成され、CameraComponent が SpringArmComponent のソケット長の端に接続されます。スプリングアームは、デフォルトのピッチ -60 度、ルートから 50 ユニット上の位置に設定されています。SpringArmComponent クラス特有の値もいくつか設定します。こうした値でその長さと動きの滑らかさを決めます。
最後に、コンストラクターからのポーンを所有するように、デフォルトのプレーヤー コントローラーを設定します。
//Take control of the default Player AutoPossessPlayer = EAutoReceiveInput::Player0;
これでカメラを快適に制御可能にするシンプルなポーンが用意できました。次に Unreal Engine エディタで入力を設定し、それに反応するコードを作成します。
Work-In-Progress Code
PawnWithCamera.h
// Copyright 1998-2021 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "PawnWithCamera.generated.h"
UCLASS()
class HOWTO_PLAYERCAMERA_API APawnWithCamera : public APawn
{
GENERATED_BODY()
public:
// Sets default values for this pawn's properties
APawnWithCamera();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick( float DeltaSeconds ) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) override;
protected:
UPROPERTY(EditAnywhere)
class USpringArmComponent* SpringArmComp;
UPROPERTY(EditAnywhere)
class UCameraComponent* CameraComp;
UPROPERTY(EditAnywhere)
UStaticMeshComponent* StaticMeshComp;
};
PawnWithCamera.cpp
// Copyright 1998-2021 Epic Games, Inc. All Rights Reserved.
#include "HowTo_PlayerCamera.h"
#include "PawnWithCamera.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
// Sets default values
APawnWithCamera::APawnWithCamera()
{
// Set this pawn to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
//Create our components
RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootSceneComponent"));
StaticMeshComp = CreateDefaultSubobject <UStaticMeshComponent>(TEXT("StaticMeshComponent"));
SpringArmComp = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArmComponent"));
CameraComp = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComponent"));
//Attach our components
StaticMeshComp->SetupAttachment(RootComponent);
SpringArmComp->SetupAttachment(StaticMeshComp);
CameraComp->SetupAttachment(SpringArmComp,USpringArmComponent::SocketName);
//Assign SpringArm class variables.
SpringArmComp->SetRelativeLocationAndRotation(FVector(0.0f, 0.0f, 50.0f), FRotator(-60.0f, 0.0f, 0.0f));
SpringArmComp->TargetArmLength = 400.f;
SpringArmComp->bEnableCameraLag = true;
SpringArmComp->CameraLagSpeed = 3.0f;
//Take control of the default Player
AutoPossessPlayer = EAutoReceiveInput::Player0;
}
// Called when the game starts or when spawned
void APawnWithCamera::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void APawnWithCamera::Tick( float DeltaTime )
{
Super::Tick( DeltaTime );
}
// Called to bind functionality to input
void APawnWithCamera::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
Super::SetupPlayerInputComponent(InputComponent);
}
2. カメラを制御する入力の設定
カメラとポーンを制御する入力キーを決定し、それに応じて入力を設定する必要があります。このプロジェクトサンプルでは、マウスの右ボタンを押したときに、追跡距離を短くし、FOV を拡大できるようにします。
これに加えてマウス操作による視野角の操作と、 WASD キー操作で ポーン の動作もコントロールしましょう。設定を行うために、 Unreal Engine エディタの [Edit (編集)] ドロップダウンメニューから [Project Settings (プロジェクト設定)] を開きます。
[Project Settings (プロジェクト設定)] ウィンドウで [Engine] > [Input] > [Bindings (バインディング)] を選択します。そこから、Action Mapping と Axis Mapping の横にある + 記号をクリックして、新規キーマップを追加します。
以下の入力 Action Mapping と Axis Mapping を定義する必要があります。これらは入力ロジックを制御するために使用されます。
Action Mapping:
ZoomIn
マウスの右ボタン
Axis Mapping:
MoveForward
W
1.0
S
-1.0
MoveRight
A
-1.0
D
1.0
CameraPitch
MouseY
1.0
CameraYaw
MouseX
1.0
入力マッピングの仕組みについて理解したい場合、プレイヤー入力と Pawn クラス チュートリアルを参照してください。
入力を定義したら、Visual Studio に戻って次のステップへ進みます。
3. 入力に反応する C++ コードを記述する
受け取る入力データを格納するメンバー変数を設定して、入力マッピングを利用するために必要なコードを記述します。 Event Tick の更新中に、移動とマウスルックの軸の値 (それぞれの 2D ベクター値) が必要です。
ズームインまたはズームアウトのカメラ ビューのどちらへ移動させるべきか、またカメラがこの 2 つのステートから現時点でどれくらい離れているのかについても知る必要があります。
PawnWithCamera.h のクラス定義の保護された名前空間に次のコードを追加することから始めます。
//Input variables FVector2D MovementInput; FVector2D CameraInput; float ZoomFactor; bool bZoomingIn;
入力を追跡するための関数を作成する必要があります。まず以下のコードを PawnWithCamera.h のクラス定義に追加します。
//Input functions void MoveForward(float AxisValue); void MoveRight(float AxisValue); void PitchCamera(float AxisValue); void YawCamera(float AxisValue); void ZoomIn(); void ZoomOut();
ここで、PawnWithCamera.Cpp ファイルに移動します。ここで、次のコードを追加して関数ロジックの実装を開始します。
//Input functions void APawnWithCamera::MoveForward(float AxisValue) { MovementInput.X = FMath::Clamp<float>(AxisValue, -1.0f, 1.0f); } void APawnWithCamera::MoveRight(float AxisValue) { MovementInput.Y = FMath::Clamp<float>(AxisValue, -1.0f, 1.0f); } void APawnWithCamera::PitchCamera(float AxisValue) { CameraInput.Y = AxisValue; } void APawnWithCamera::YawCamera(float AxisValue) { CameraInput.X = AxisValue; } void APawnWithCamera::ZoomIn() { bZoomingIn = true; } void APawnWithCamera::ZoomOut() { bZoomingIn = false; }
ZoomFactor については、まだ何もしていません。この変数はボタンのステートに基づいて時間の経過と共に常に変化するため、ポーン の Tick 関数実行時に更新されます。
入力データを格納するために必要なコードを作成したので、次はコードをいつ呼び出すかをエンジンに指示します。ポーンの入力イベントに関数をバインドするには、以下のように APawnWithCamera::SetupPlayerInputComponent にバインディングコードを追加します。
//Hook up events for "ZoomIn" InputComponent->BindAction("ZoomIn", IE_Pressed, this, &APawnWithCamera::ZoomIn); InputComponent->BindAction("ZoomIn", IE_Released, this, &APawnWithCamera::ZoomOut); //Hook up every-frame handling for our four axes InputComponent->BindAxis("MoveForward", this, &APawnWithCamera::MoveForward); InputComponent->BindAxis("MoveRight", this, &APawnWithCamera::MoveRight); InputComponent->BindAxis("CameraPitch", this, &APawnWithCamera::PitchCamera); InputComponent->BindAxis("CameraYaw", this, &APawnWithCamera::YawCamera);
Tick 関数でこれらの値を使用して、ポーンと カメラ をフレーム毎に更新することができます。以下のコードブロックのすべてを「PawnWithCamera.cpp」ファイルの APawnWithCamera::Tick に追加します。
//Zoom in if ZoomIn button is down, zoom back out if it's not { if (bZoomingIn) { ZoomFactor += DeltaTime / 0.5f; //Zoom in over half a second } else { ZoomFactor -= DeltaTime / 0.25f; //Zoom out over a quarter of a second } ZoomFactor = FMath::Clamp<float>(ZoomFactor, 0.0f, 1.0f); //Blend our camera's FOV and our SpringArm's length based on ZoomFactor CameraComp->FieldOfView = FMath::Lerp<float>(90.0f, 60.0f, ZoomFactor); SpringArmComp->TargetArmLength = FMath::Lerp<float>(400.0f, 300.0f, ZoomFactor); }
このコードはハードコードされた値をいくつか使用します。使用する値は、0.5 秒や 0.25 秒のズーム時間、90 度でズームアウト、60 度でズームインする FOV 値、またはカメラ距離の 400 ズームアウト、300 ズームインの値などです。このような値は、通常、UPROPERTY(EditAnywhere) でタグ付けされた変数としてエディタに公開する必要があります。この例では、プログラマーでなくても値を変更できる追加機能を提供する UPROPERTY(EditAnywhere) 指定子タグを使用しました。これにより、プログラマーはコードを再コンパイルする必要もありませんし、エディタでゲームをプレイしながら動的に変更することもできます。
//Rotate our actor's yaw, which will turn our camera because we're attached to it { FRotator NewRotation = GetActorRotation(); NewRotation.Yaw += CameraInput.X; SetActorRotation(NewRotation); } //Rotate our camera's pitch, but limit it so we're always looking downward { FRotator NewRotation = OurCameraSpringArm->GetComponentRotation(); NewRotation.Pitch = FMath::Clamp(NewRotation.Pitch + CameraInput.Y, -80.0f, -15.0f); SpringArmComp->SetWorldRotation(NewRotation); }
このコードブロックはマウスの X 軸でポーンのヨーを直接回転させますが、カメラシステムのみが、マウスの Y 軸からのピッチ変更に反応します。アクタ または アクタ のサブクラスを回転させると、実質的にルートレベルの コンポーネント を回転させます。これが間接的にコンポーネントに親子付けされているものすべてに影響を与えます。
//Handle movement based on our "MoveX" and "MoveY" axes { if (!MovementInput.IsZero()) { //Scale our movement input axis values by 100 units per second MovementInput = MovementInput.SafeNormal() * 100.0f; FVector NewLocation = GetActorLocation(); NewLocation += GetActorForwardVector() * MovementInput.X * DeltaTime; NewLocation += GetActorRightVector() * MovementInput.Y * DeltaTime; SetActorLocation(NewLocation); } }
GetActorForwardVector と GetActorRightVector を使用すると、アクタが向いている方向と相対的な動きになります。カメラはアクタと同じ方向を向いていいるため、これでフォワード キーはプレイヤーが見ているものに対して常に相対的に前方向になります。
これで終了です。これでコーディングが終わりました。コードをコンパイルして、コンテンツ ブラウザ から新規クラスを Unreal Engine Editor の レベルエディタ ウィンドウにドラッグできるようになりました。
スタティック メッシュ やその他のビジュアル コンポーネントを自由に追加したり、または何も追加しないでプレイをお楽しみください。カメラが操作に追随してレベル内で滑らかに加速、減速の動きをするのがわかりますが、回転はぎこちなく瞬間的に起こります。Camera Rotation Lag を追加したり Camera Lag を増減させるなど SpringArmComponent のプロパティをいくつか変更して、コントロールの操作性の違いを感じてみてください。
提供されたコードによる機能全体は以下のようになります。
カメラ ピッチ/ヨー
前方/右方向に移動
ズームイン/アウト
完成コード
PawnWithCamera.h
// Copyright 1998-2021 Epic Games, Inc.All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/Pawn.h"
#include "PawnWithCamera.generated.h"
UCLASS()
class HOWTO_PLAYERCAMERA_API APawnWithCamera : public APawn
{
GENERATED_BODY()
public:
// Sets default values for this pawn's properties
APawnWithCamera();
protected:
// Called when the game starts or when spawned
virtual void BeginPlay() override;
public:
// Called every frame
virtual void Tick( float DeltaSeconds ) override;
// Called to bind functionality to input
virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) override;
protected:
UPROPERTY(EditAnywhere)
class USpringArmComponent* OurCameraSpringArm;
UPROPERTY(EditAnywhere)
class UCameraComponent* OurCamera;
UPROPERTY(EditAnywhere)
UStaticMeshComponent* StaticMeshComp;
//Input variables
FVector2D MovementInput;
FVector2D CameraInput;
float ZoomFactor;
bool bZoomingIn;
//Input functions
void MoveForward(float AxisValue);
void MoveRight(float AxisValue);
void PitchCamera(float AxisValue);
void YawCamera(float AxisValue);
void ZoomIn();
void ZoomOut();
};
PawnWithCamera.cpp
// Copyright 1998-2021 Epic Games, Inc.All Rights Reserved.
#include "PawnWithCamera.h"
#include "GameFramework/SpringArmComponent.h"
#include "Camera/CameraComponent.h"
// Sets default values
APawnWithCamera::APawnWithCamera()
{
// Set this pawn to call Tick() every frame.You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
//Create our components
RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootComponent"));
StaticMeshComp = CreateDefaultSubobject <UStaticMeshComponent>(TEXT("StaticMeshComponent"));
SpringArmComp = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArmComponent"));
CameraComp = CreateDefaultSubobject<UCameraComponent>(TEXT("CameraComponent"));
StaticMeshComp->SetupAttachment(RootComponent);
SpringArmComp->SetupAttachment(StaticMeshComp);
CameraComp->SetupAttachment(SpringArmComp,USpringArmComponent::SocketName);
//Setting Default Variables and Behavior of the SpringArmComponent
SpringArmComp->SetRelativeLocationAndRotation(FVector(0.0f, 0.0f, 50.0f), FRotator(-60.0f, 0.0f, 0.0f));
SpringArmComp->TargetArmLength = 400.f;
SpringArmComp->bEnableCameraLag = true;
SpringArmComp->CameraLagSpeed = 3.0f;
//Take control of the default Player
AutoPossessPlayer = EAutoReceiveInput::Player0;
}
// Called when the game starts or when spawned
void APawnWithCamera::BeginPlay()
{
Super::BeginPlay();
}
// Called every frame
void APawnWithCamera::Tick( float DeltaTime )
{
Super::Tick(DeltaTime);
//Zoom in if ZoomIn button is down, zoom back out if it's not
{
if (bZoomingIn)
{
ZoomFactor += DeltaTime / 0.5f; //Zoom in over half a second
}
else
{
ZoomFactor -= DeltaTime / 0.25f; //Zoom out over a quarter of a second
}
ZoomFactor = FMath::Clamp<float>(ZoomFactor, 0.0f, 1.0f);
//Blend our camera's FOV and our SpringArm's length based on ZoomFactor
CameraComp->FieldOfView = FMath::Lerp<float>(90.0f, 60.0f, ZoomFactor);
SpringArmComp->TargetArmLength = FMath::Lerp<float>(400.0f, 300.0f, ZoomFactor);
}
//Rotate our actor's yaw, which will turn our camera because we're attached to it
{
FRotator NewRotation = GetActorRotation();
NewRotation.Yaw += CameraInput.X;
SetActorRotation(NewRotation);
}
//Rotate our camera's pitch, but limit it so we're always looking downward
{
FRotator NewRotation = SpringArmComp->GetComponentRotation();
NewRotation.Pitch = FMath::Clamp(NewRotation.Pitch + CameraInput.Y, -80.0f, -15.0f);
SpringArmComp->SetWorldRotation(NewRotation);
}
//Handle movement based on our "MoveX" and "MoveY" axes
{
if (!MovementInput.IsZero())
{
//Scale our movement input axis values by 100 units per second
MovementInput = MovementInput.SafeNormal() * 100.0f;
FVector NewLocation = GetActorLocation();
NewLocation += GetActorForwardVector() * MovementInput.X * DeltaTime;
NewLocation += GetActorRightVector() * MovementInput.Y * DeltaTime;
SetActorLocation(NewLocation);
}
}
}
// Called to bind functionality to input
void APawnWithCamera::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
Super::SetupPlayerInputComponent(InputComponent);
//Hook up events for "ZoomIn"
InputComponent->BindAction("ZoomIn", IE_Pressed, this, &APawnWithCamera::ZoomIn);
InputComponent->BindAction("ZoomIn", IE_Released, this, &APawnWithCamera::ZoomOut);
//Hook up every-frame handling for our four axes
InputComponent->BindAxis("MoveForward", this, &APawnWithCamera::MoveForward);
InputComponent->BindAxis("MoveRight", this, &APawnWithCamera::MoveRight);
InputComponent->BindAxis("CameraPitch", this, &APawnWithCamera::PitchCamera);
InputComponent->BindAxis("CameraYaw", this, &APawnWithCamera::YawCamera);
}
//Input functions
void APawnWithCamera::MoveForward(float AxisValue)
{
MovementInput.X = FMath::Clamp<float>(AxisValue, -1.0f, 1.0f);
}
void APawnWithCamera::MoveRight(float AxisValue)
{
MovementInput.Y = FMath::Clamp<float>(AxisValue, -1.0f, 1.0f);
}
void APawnWithCamera::PitchCamera(float AxisValue)
{
CameraInput.Y = AxisValue;
}
void APawnWithCamera::YawCamera(float AxisValue)
{
CameraInput.X = AxisValue;
}
void APawnWithCamera::ZoomIn()
{
bZoomingIn = true;
}
void APawnWithCamera::ZoomOut()
{
bZoomingIn = false;
}
4. 応用編
ここまでで学んだ知識を活かして、以下を行ってみましょう。
ポーンの 移動速度を速めるためにクリックする「run」キーをプレイヤーに提供します。
自動および入力駆動のカメラ移動をミックスするために、さまざまな方法を実験します。これはゲーム開発における非常に奥深い分野であり、試すべきことが沢山あります。
カメラ全体の雰囲気にラグがどのくらい影響を与えるかの理解を深めるために Spring コンポーネント からラグを増加、削減、または取り除きます。
カメラに「手持ち」感覚を与えるために多少ランダム化させて、または Curve アセットを使用して、少量の定期的な動きを実装します。
移動しているプレイヤー オブジェクトの背後へ徐々に移動して、プレイヤーの移動方向を向くように、カメラ を自動的にある程度回転させます。
以下はこのチュートリアルの内容の詳しい情報のリンク先です。
カメラとその操作に関する詳細は カメラ のフレームワーク ページを参照してください。
コンポーネント の詳細については、
[コンポーネント](programming-and-scripting\programming-language-implementation\cpp-in-unreal-engine\unreal-engine-cpp-tutorials\Components)チュートリアルを参照してください。