玩家控制的相机

学习使用玩家输入同时操控相机和Pawn的方法。

Windows
MacOS
Linux

本教程将介绍激活相机并将活跃相机变更另一相机的方法。

1. 将相机连接到Pawn

  1. 首先使用初学者内容包新建基础代码项目,并命名为HowTo_PlayerInput。需创建自定义 Pawn 类,因此先进行此操作。本教程中将使用PawnWithCamera命名新的Pawn类。

    NamePawnClass.png

  2. 接下来,在 Visual Studio 中,打开PawnWithCamera.h并将以下代码添加到类定义的底部:

    protected:
        UPROPERTY(EditAnywhere)
        USpringArmComponent* OurCameraSpringArm;
        UCameraComponent* OurCamera;

    使用此类变量创建 SpringArmComponent,并在末尾附加 CameraComponent。弹簧臂是连接相机(或其他组件)的简便方式,因此其不会过于僵硬,移动时也更加流畅。

  3. 之后,实际需在构造函数中创建组件。将以下代码添加到PawnWithCamera.cpp的**APawnWithCamera::APawnWithCamera**中:

    //创建组件
    RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootComponent"));
    OurCameraSpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraSpringArm"));
    OurCameraSpringArm->SetupAttachment(RootComponent);
    OurCameraSpringArm->SetRelativeLocationAndRotation(FVector(0.0f, 0.0f, 50.0f), FRotator(-60.0f, 0.0f, 0.0f));
    OurCameraSpringArm->TargetArmLength = 400.f;
    OurCameraSpringArm->bEnableCameraLag = true;
    OurCameraSpringArm->CameraLagSpeed = 3.0f;

    此操作将创建空白基本 SceneComponent 作为组件层级的根,然后创建SpringArmComponent并将其附加到根。之后将 弹簧臂 设为默认角度-60度(即向下60度),位置位于根之上50单位。同时设置部分SpringArmComponent类的特定值,此类值将决定其移动的长度和平滑度。完成以上操作后,只需创建CameraComponent并将其附加到SpringArmComponent末端的插槽即可,如下所示:

    OurCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("GameCamera"));
    OurCamera->SetupAttachment(OurCameraSpringArm, USpringArmComponent::SocketName);
  4. 最后,添加以下这段代码,将Pawn设为产生时由默认的本地玩家自动控制:

    //控制默认玩家
    AutoPossessPlayer = EAutoReceiveInput::Player0;

现在有了一个简单的Pawn,可自如地控制相机。接下来,将在 虚幻引擎 编辑器中配置输入,并创建对输入做出反应的代码。

半成品代码

PawnWithCamera.h

// Copyright 1998-2018 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:
    // 设置此Pawn属性的默认值
    APawnWithCamera();

protected:
    // 游戏开始或生成时调用
    virtual void BeginPlay() override;

public:
    // 逐帧调用
    virtual void Tick( float DeltaSeconds ) override;

    // 调用以将功能绑定至输入
    virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) override;

protected:
    UPROPERTY(EditAnywhere)
    USpringArmComponent* OurCameraSpringArm;
    UCameraComponent* OurCamera;
};

PawnWithCamera.cpp

// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.

#include "HowTo_PlayerCamera.h"
#include "PawnWithCamera.h"

// 设置默认值
APawnWithCamera::APawnWithCamera()
{
    // 设置该Pawn以逐帧调用Tick()。You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;

    //创建组件
    RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootComponent"));
    OurCameraSpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraSpringArm"));
    OurCameraSpringArm->SetupAttachment(RootComponent);
    OurCameraSpringArm->SetRelativeLocationAndRotation(FVector(0.0f, 0.0f, 50.0f), FRotator(-60.0f, 0.0f, 0.0f));
    OurCameraSpringArm->TargetArmLength = 400.f;
    OurCameraSpringArm->bEnableCameraLag = true;
    OurCameraSpringArm->CameraLagSpeed = 3.0f;
    OurCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("GameCamera"));
    OurCamera->SetupAttachment(OurCameraSpringArm, USpringArmComponent::SocketName);

    //控制默认玩家
    AutoPossessPlayer = EAutoReceiveInput::Player0;
}

// 游戏开始或生成时调用
void APawnWithCamera::BeginPlay()
{
    Super::BeginPlay();

}

// 逐帧调用
void APawnWithCamera::Tick( float DeltaTime )
{
    Super::Tick( DeltaTime );

}

// 调用以将功能与输入绑定
void APawnWithCamera::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
    Super::SetupPlayerInputComponent(InputComponent);

}

2.配置输入以控制相机

  1. 需决定相机控制的操作,然后设置相应输入。本项目中要实现的是长按鼠标右键时,跟踪距离缩短,而视野放大。同时鼠标可控制视角,WASD键可控制 Pawn 的运动。为此,将在 虚幻引擎 编辑器的 编辑 下拉菜单中打开 项目设置

    EditProjectSettings.png

  2. 需定义 操作映射 和四个 轴映射,如下所示:

    操作映射

    ZoomIn

    鼠标右键

    轴映射

    MoveForward

    W

    1.0

    S

    -1.0

    MoveRight

    A

    -1.0

    D

    1.0

    CameraPitch

    MouseY

    1.0

    CameraYaw

    MouseX

    1.0

    InputConfig.png

    如要sheng深入了解输入映射的工作方式,建议参考玩家输入和Pawn教程。

现在已定义输入,接下来需编写代码以对输入做出反应。返回 Visual Studio 完成此步骤。

3.编写C++代码响应输入

  1. 游戏现已拥有可用输入映射,接下来设置部分成员变量来存储收到的数据。更新期间需知道移动和鼠标朝向轴的值,所有值均为二维向量。同时需知道是朝放大还是缩小视觉移动,及当前处于这两个状态间的位置。为此,应在PawnWithCamera.h中的类定义中添加以下代码:

    //输入变量
    FVector2D MovementInput;
    FVector2D CameraInput;
    float ZoomFactor;
    bool bZoomingIn;
  2. 需创建函数追踪输入,因此在PawnWithCamera.h的类定义中添加以下代码:

    //输入函数
    void MoveForward(float AxisValue);
    void MoveRight(float AxisValue);
    void PitchCamera(float AxisValue);
    void YawCamera(float AxisValue);
    void ZoomIn();
    void ZoomOut();

    现在使用以下代码在PawnWithCamera.cpp中填充以下函数:

    //输入函数
    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。此变量的值会随着时间进行基于按钮状态的持续变化,因此该变量将在 PawnTick 函数期间更新。

  3. 现在已拥有用于存储输入数据的代码,只需告知 虚幻引擎 调用该代码的时间即可。将函数绑定到Pawn输入事件的操作十分简单,只需将绑定代码添加到 APawnWithCamera::SetupPlayerInputComponent 即可,如下所示:

    //连接“ZoomIn”的事件
    InputComponent->BindAction("ZoomIn", IE_Pressed, this, &APawnWithCamera::ZoomIn);
    InputComponent->BindAction("ZoomIn", IE_Released, this, &APawnWithCamera::ZoomOut);
    
    //连接四个轴的逐帧处理
    InputComponent->BindAxis("MoveForward", this, &APawnWithCamera::MoveForward);
    InputComponent->BindAxis("MoveRight", this, &APawnWithCamera::MoveRight);
    InputComponent->BindAxis("CameraPitch", this, &APawnWithCamera::PitchCamera);
    InputComponent->BindAxis("CameraYaw", this, &APawnWithCamera::YawCamera);
  4. 最后可在Tick函数中使用此类值逐帧更新Pawn和 相机。应将以下代码块添加到PawnWithCamera.cpp中的 APawnWithCamera::Tick

    //按下ZoomIn按钮时放大,松开时恢复
    {
        if (bZoomingIn)
        {
            ZoomFactor += DeltaTime / 0.5f;         //Zoom in over half a second
        }
        else
        {
            ZoomFactor -= DeltaTime / 0.25f;        //缩小四分之一秒
        }
        ZoomFactor = FMath::Clamp<float>(ZoomFactor, 0.0f, 1.0f);
        //基于ZoomFactor混合相机的FOV和SpringArm长度
        OurCamera->FieldOfView = FMath::Lerp<float>(90.0f, 60.0f, ZoomFactor);
        OurCameraSpringArm->TargetArmLength = FMath::Lerp<float>(400.0f, 300.0f, ZoomFactor);
    }

    该代码使用多个硬编码值,例如半秒和四分之一秒的缩放时间、90度缩小和60度放大视野值,及400度缩小和300度放大相机距离。此类值通常应作为标有 UPROPERTY(EditAnywhere) 的变量公开到编辑器,以便非程序员进行修改,或程序员无需重新编译代码或正在编辑器中运行游戏时,即可直接进行修改。

    //旋转Actor的Yaw,由于已与其附加,因此将转动相机
    {
        FRotator NewRotation = GetActorRotation();
        NewRotation.Yaw += CameraInput.X;
        SetActorRotation(NewRotation);
    }
    
    //旋转相机的Pitch,但进行限制,因此视觉固定朝下
    {
        FRotator NewRotation = OurCameraSpringArm->GetComponentRotation();
        NewRotation.Pitch = FMath::Clamp(NewRotation.Pitch + CameraInput.Y, -80.0f, -15.0f);
        OurCameraSpringArm->SetWorldRotation(NewRotation);
    }

    这段代码使用鼠标X轴直接旋转Pawn的Yaw,但仅有相机系统会响应鼠标Y轴的Pitch变化。旋转 Actor 或Actor子类实际上会旋转根级别 组件,从而间接影响与之附加的事物。

    //根据“MoveX”和“MoveY”轴处理移动
    {
        if (!MovementInput.IsZero())
        {
            //以100单位/秒缩放移动输入轴值
            MovementInput = MovementInput.SafeNormal() * 100.0f;
            FVector NewLocation = GetActorLocation();
            NewLocation += GetActorForwardVector() * MovementInput.X * DeltaTime;
            NewLocation += GetActorRightVector() * MovementInput.Y * DeltaTime;
            SetActorLocation(NewLocation);
        }
    }

    使用 GetActorForwardVectorGetActorRightVector,可相对Actor的朝向进行移动。由于相机与Actor朝向相同的方向,因此此操作可确保向前键固定相对玩家视线的方向向前移动。

  5. 现已完成编码。现在可编译代码并将新类的实例从 内容浏览器 拖入 虚幻引擎 编辑器的 关卡编辑器 窗口中。

    ClassInContentBrowser.png

    可添加 静态网格体 或其他视觉组件,或者不添加直接运行。现在相机会跟随角色在关卡中移动,因此其的移动加速和减速将十分平滑,但旋转会略显卡顿。尝试修改 SpringArmComponent 的部分属性,以查看其对控制感的影响,例如添加“相机旋转延迟”或减小/增大相机延迟。

    SpringArmValues.png

  6. 使用转动的椎体网格体后,最终成品应类似以下范例:

    FinalScreen.png

成品代码

PawnWithCamera.h

// Copyright 1998-2018 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:
    // 设置此Pawn属性的默认值
    APawnWithCamera();

protected:
    // 游戏开始或生成时调用
    virtual void BeginPlay() override;

public:
    // 逐帧调用
    virtual void Tick( float DeltaSeconds ) override;

    // 调用以将功能与输入绑定
    virtual void SetupPlayerInputComponent(class UInputComponent* InputComponent) override;

protected:
    UPROPERTY(EditAnywhere)
    USpringArmComponent* OurCameraSpringArm;
    UCameraComponent* OurCamera;

    //输入变量
    FVector2D MovementInput;
    FVector2D CameraInput;
    float ZoomFactor;
    bool bZoomingIn;

    //输入函数
    void MoveForward(float AxisValue);
    void MoveRight(float AxisValue);
    void PitchCamera(float AxisValue);
    void YawCamera(float AxisValue);
    void ZoomIn();
    void ZoomOut();
};

PawnWithCamera.cpp

// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved.

#include "HowTo_PlayerCamera.h"
#include "PawnWithCamera.h"

// 设置默认值
APawnWithCamera::APawnWithCamera()
{
    // 设置该Pawn以逐帧调用Tick()。You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;

    //创建组件
    RootComponent = CreateDefaultSubobject<USceneComponent>(TEXT("RootComponent"));
    OurCameraSpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraSpringArm"));
    OurCameraSpringArm->SetupAttachment(RootComponent);
    OurCameraSpringArm->SetRelativeLocationAndRotation(FVector(0.0f, 0.0f, 50.0f), FRotator(-60.0f, 0.0f, 0.0f));
    OurCameraSpringArm->TargetArmLength = 400.f;
    OurCameraSpringArm->bEnableCameraLag = true;
    OurCameraSpringArm->CameraLagSpeed = 3.0f;
    OurCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("GameCamera"));
    OurCamera->SetupAttachment(OurCameraSpringArm, USpringArmComponent::SocketName);

    //控制默认玩家
    AutoPossessPlayer = EAutoReceiveInput::Player0;
}

// 游戏开始或生成时调用
void APawnWithCamera::BeginPlay()
{
    Super::BeginPlay();

}

// 逐帧调用
void APawnWithCamera::Tick( float DeltaTime )
{
    Super::Tick(DeltaTime);

    //按下ZoomIn按钮时放大,松开时恢复
    {
        if (bZoomingIn)
        {
            ZoomFactor += DeltaTime / 0.5f;         //Zoom in over half a second
        }
        else
        {
            ZoomFactor -= DeltaTime / 0.25f;        //缩小四分之一秒
        }
        ZoomFactor = FMath::Clamp<float>(ZoomFactor, 0.0f, 1.0f);
        //基于ZoomFactor混合相机的FOV和SpringArm长度
        OurCamera->FieldOfView = FMath::Lerp<float>(90.0f, 60.0f, ZoomFactor);
        OurCameraSpringArm->TargetArmLength = FMath::Lerp<float>(400.0f, 300.0f, ZoomFactor);
    }

    //旋转Actor的Yaw,由于已与其附加,因此将转动相机
    {
        FRotator NewRotation = GetActorRotation();
        NewRotation.Yaw += CameraInput.X;
        SetActorRotation(NewRotation);
    }

    //旋转相机的Pitch,但进行限制,因此视觉固定朝下
    {
        FRotator NewRotation = OurCameraSpringArm->GetComponentRotation();
        NewRotation.Pitch = FMath::Clamp(NewRotation.Pitch + CameraInput.Y, -80.0f, -15.0f);
        OurCameraSpringArm->SetWorldRotation(NewRotation);
    }

    //根据“MoveX”和“MoveY”轴处理移动
    {
        if (!MovementInput.IsZero())
        {
            //以100单位/秒缩放移动输入轴值
            MovementInput = MovementInput.SafeNormal() * 100.0f;
            FVector NewLocation = GetActorLocation();
            NewLocation += GetActorForwardVector() * MovementInput.X * DeltaTime;
            NewLocation += GetActorRightVector() * MovementInput.Y * DeltaTime;
            SetActorLocation(NewLocation);
        }
    }
}

// 调用以将功能与输入绑定
void APawnWithCamera::SetupPlayerInputComponent(class UInputComponent* InputComponent)
{
    Super::SetupPlayerInputComponent(InputComponent);

    //连接“ZoomIn”的事件
    InputComponent->BindAction("ZoomIn", IE_Pressed, this, &APawnWithCamera::ZoomIn);
    InputComponent->BindAction("ZoomIn", IE_Released, this, &APawnWithCamera::ZoomOut);

    //连接四个轴的逐帧处理
    InputComponent->BindAxis("MoveForward", this, &APawnWithCamera::MoveForward);
    InputComponent->BindAxis("MoveRight", this, &APawnWithCamera::MoveRight);
    InputComponent->BindAxis("CameraPitch", this, &APawnWithCamera::PitchCamera);
    InputComponent->BindAxis("CameraYaw", this, &APawnWithCamera::YawCamera);
}

//输入函数
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.自行尝试!

利用所学内容,尝试以下操作:

  • 给予玩家运行键,长按将增大 Pawn 的移动速度。

  • 试验不同方法,混合自动和输入驱动的相机移动。此操作是游戏开发中较难部分,还有许多内容有待探索!

  • 增大、减小或移除 弹簧组件 的延迟,以更好地理解延迟对相机政体感觉的影响。

  • 实现少量周期性移动,可稍带随机性,或使用 曲线 资源创建相机手持感。

  • 给予 相机 一定的自动转动,以便相机逐渐落后于移动玩家物体,并朝向玩家的移动方向。

本教程中讲解的相关细节:

  • 欲了解相机和相机控制方法的更多相关信息,参阅相机框架页面。

  • 欲了解 **组件**的更多详情,查看组件和碰撞教程。

  • 欲学习更多教程,参见C++ 编程教程页面。

欢迎来到全新虚幻引擎4文档站!

我们正在努力开发新功能,包括反馈系统,以便您能对我们的工作作出评价。但它目前还未正式上线。如果您对此页面有任何意见与在使用中遭遇任何问题,请前往文档反馈论坛告知我们。

新系统上线运行后,我们会及时通知您的。

发表反馈意见