玩家控制的摄像机

学习如何使用玩家输入同时操控摄像机和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

    如果要深入了解输入映射的工作方式,建议参考玩家输入和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++ 编程教程 页面。

Select Skin
Light
Dark

Welcome to the new Unreal Engine 4 Documentation site!

We're working on lots of new features including a feedback system so you can tell us how we are doing. It's not quite ready for use in the wild yet, so head over to the Documentation Feedback forum to tell us about this page or call out any issues you are encountering in the meantime.

We'll be sure to let you know when the new system is up and running.

Post Feedback