Unreal Engine开发:虚拟摄像机开发_虚拟摄像机的物理模拟

虚拟摄像机的物理模拟

引言

在动作游戏中,虚拟摄像机的物理模拟是提升玩家沉浸感和游戏真实感的重要手段。通过模拟摄像机的物理特性,如惯性、震动、平滑移动等,可以为玩家提供更加真实和动态的游戏体验。本节将详细介绍如何在Unreal Engine中实现虚拟摄像机的物理模拟,包括基础的物理概念、摄像机组件的设置、以及具体的代码实现。

物理模拟的基础概念

在开始实现虚拟摄像机的物理模拟之前,我们需要了解一些基本的物理概念,这些概念将帮助我们更好地理解和设计摄像机的行为。

惯性

惯性是物体保持当前运动状态的性质。在虚拟摄像机中,惯性可以用来模拟摄像机在快速移动或停止时的自然反应。例如,当角色突然加速时,摄像机会有一个短暂的滞后,这种滞后可以增加游戏的真实感。

震动

震动是物体在受力时发生的快速、短促的运动。在虚拟摄像机中,震动可以用来模拟碰撞、爆炸等事件的影响。通过在这些事件中引入摄像机震动,可以增强玩家的视觉和听觉体验。

平滑移动

平滑移动是指摄像机在移动过程中逐渐加速和减速,而不是瞬间完成移动。这种平滑的过渡可以减少玩家的眩晕感,提升游戏的舒适度。

摄像机组件的设置

在Unreal Engine中,虚拟摄像机通常由CameraComponentSpringArmComponent组成。SpringArmComponent用于管理摄像机与角色之间的距离和角度,而CameraComponent则负责实际的摄像机视角。

SpringArmComponent

SpringArmComponent是一个常用的组件,用于模拟摄像机的物理特性。它具有以下关键属性:

  • Target Arm Length:摄像机与角色之间的目标距离。

  • Socket Offset:摄像机相对于角色的偏移。

  • bUsePawnControlRotation:是否使用角色的控制旋转。

  • bDoCollisionTest:是否进行碰撞检测。

  • bEnableCameraLag:是否启用摄像机滞后。

  • CameraLagSpeed:摄像机滞后的速度。

CameraComponent

CameraComponent负责渲染游戏视角。它具有以下关键属性:

  • Field of View (FOV):摄像机的视场角。

  • AspectRatio:摄像机的宽高比。

  • Projection Mode:摄像机的投影模式(透视或正交)。

实现惯性效果

理论基础

惯性效果可以通过在摄像机移动时引入平滑过渡来实现。具体来说,当角色快速移动或改变方向时,摄像机不会立即跟随,而是逐渐调整到新的位置和方向。

代码实现

在Unreal Engine中,我们可以使用SpringArmComponentbEnableCameraLag属性来启用摄像机滞后。此外,我们还可以通过自定义代码来实现更复杂的惯性效果。

以下是一个示例代码,展示如何在角色移动时引入摄像机的平滑移动:


// MyCharacter.h

#pragma once



#include "CoreMinimal.h"

#include "GameFramework/Character.h"

#include "MyCharacter.generated.h"



UCLASS()

class MYGAME_API AMyCharacter : public ACharacter

{
    
    

    GENERATED_BODY()



public:

    // Sets default values for this character's properties

    AMyCharacter();



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;



private:

    // SpringArmComponent for camera

    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))

    USpringArmComponent* CameraBoom;



    // CameraComponent for rendering

    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))

    UCameraComponent* FollowCamera;



    // Smooth camera lag

    float CameraLagSpeed = 10.0f;



    // Current and target camera locations

    FVector CurrentCameraLocation;

    FVector TargetCameraLocation;



    // Current and target camera rotations

    FRotator CurrentCameraRotation;

    FRotator TargetCameraRotation;

};



// MyCharacter.cpp

#include "MyCharacter.h"

#include "Components/SpringArmComponent.h"

#include "Components/CameraComponent.h"

#include "GameFramework/CharacterMovementComponent.h"



AMyCharacter::AMyCharacter()

{
    
    

    // 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;



    // Create the spring arm component

    CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));

    CameraBoom->SetupAttachment(RootComponent);

    CameraBoom->TargetArmLength = 300.0f; // The camera follows at this distance behind the character

    CameraBoom->bUsePawnControlRotation = true; // Rotate the arm based on the controller



    // Create the camera component

    FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));

    FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); // Attach the camera to the end of the boom

    FollowCamera->bUsePawnControlRotation = false; // Camera does not rotate relative to arm



    // Set initial camera location and rotation

    CurrentCameraLocation = FollowCamera->GetComponentLocation();

    TargetCameraLocation = FollowCamera->GetComponentLocation();

    CurrentCameraRotation = FollowCamera->GetComponentRotation();

    TargetCameraRotation = FollowCamera->GetComponentRotation();

}



void AMyCharacter::BeginPlay()

{
    
    

    Super::BeginPlay();

}



void AMyCharacter::Tick(float DeltaTime)

{
    
    

    Super::Tick(DeltaTime);



    // Smoothly move the camera to the target location and rotation

    CurrentCameraLocation = FMath::VInterpTo(CurrentCameraLocation, TargetCameraLocation, DeltaTime, CameraLagSpeed);

    CurrentCameraRotation = FMath::RInterpTo(CurrentCameraRotation, TargetCameraRotation, DeltaTime, CameraLagSpeed);



    // Set the camera to the new location and rotation

    FollowCamera->SetWorldLocation(CurrentCameraLocation);

    FollowCamera->SetWorldRotation(CurrentCameraRotation);

}



void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)

{
    
    

    Super::SetupPlayerInputComponent(PlayerInputComponent);



    // Bind input to move the character

    PlayerInputComponent->BindAxis("MoveForward", this, &AMyCharacter::MoveForward);

    PlayerInputComponent->BindAxis("MoveRight", this, &AMyCharacter::MoveRight);

    PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput);

    PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput);

}



void AMyCharacter::MoveForward(float Value)

{
    
    

    if ((Controller != nullptr) && (Value != 0.0f))

    {
    
    

        // Find out which way is forward

        const FRotator Rotation = Controller->GetControlRotation();

        const FVector Direction = FRotationMatrix(Rotation).GetUnitAxis(EAxis::X);



        // Add movement in that direction

        AddMovementInput(Direction, Value);



        // Update target camera location

        TargetCameraLocation = CameraBoom->GetTargetLocation();

    }

}



void AMyCharacter::MoveRight(float Value)

{
    
    

    if (Value != 0.0f)

    {
    
    

        // Find out which way is right

        const FRotator Rotation = Controller->GetControlRotation();

        const FVector Direction = FRotationMatrix(Rotation).GetUnitAxis(EAxis::Y);



        // Add movement in that direction

        AddMovementInput(Direction, Value);



        // Update target camera location

        TargetCameraLocation = CameraBoom->GetTargetLocation();

    }

}

实现震动效果

理论基础

震动效果可以通过在摄像机上施加随机或预定的移动和旋转来实现。这种效果通常用于模拟碰撞、爆炸等事件的影响。

代码实现

在Unreal Engine中,我们可以使用CameraComponentAddCamera Shake方法来实现摄像机震动。以下是一个示例代码,展示如何在角色受到碰撞时引入摄像机震动:


// MyCharacter.h

#pragma once



#include "CoreMinimal.h"

#include "GameFramework/Character.h"

#include "MyCharacter.generated.h"



UCLASS()

class MYGAME_API AMyCharacter : public ACharacter

{
    
    

    GENERATED_BODY()



public:

    // Sets default values for this character's properties

    AMyCharacter();



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;



    // Handle collision with other actors

    virtual void NotifyHit(class UPrimitiveComponent* MyComp, AActor* Other, class UPrimitiveComponent* OtherComp, bool bSelfMoved, FVector HitLocation, FVector HitNormal, FVector NormalImpulse, const FHitResult& Hit) override;



private:

    // SpringArmComponent for camera

    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))

    USpringArmComponent* CameraBoom;



    // CameraComponent for rendering

    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))

    UCameraComponent* FollowCamera;



    // Camera shake class

    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))

    TSubclassOf<UCameraShakeBase> CameraShakeClass;



};



// MyCharacter.cpp

#include "MyCharacter.h"

#include "Components/SpringArmComponent.h"

#include "Components/CameraComponent.h"

#include "Camera/CameraShakeBase.h"

#include "GameFramework/CharacterMovementComponent.h"



AMyCharacter::AMyCharacter()

{
    
    

    // 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;



    // Create the spring arm component

    CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));

    CameraBoom->SetupAttachment(RootComponent);

    CameraBoom->TargetArmLength = 300.0f; // The camera follows at this distance behind the character

    CameraBoom->bUsePawnControlRotation = true; // Rotate the arm based on the controller



    // Create the camera component

    FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));

    FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); // Attach the camera to the end of the boom

    FollowCamera->bUsePawnControlRotation = false; // Camera does not rotate relative to arm



    // Set the camera shake class

    CameraShakeClass = StaticLoadObject(UCameraShakeBase::StaticClass(), NULL, TEXT("Blueprint'/Game/Blueprints/CameraShakes/CameraShake_BP.CameraShake_BP_C'"));

}



void AMyCharacter::BeginPlay()

{
    
    

    Super::BeginPlay();

}



void AMyCharacter::Tick(float DeltaTime)

{
    
    

    Super::Tick(DeltaTime);

}



void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)

{
    
    

    Super::SetupPlayerInputComponent(PlayerInputComponent);



    // Bind input to move the character

    PlayerInputComponent->BindAxis("MoveForward", this, &AMyCharacter::MoveForward);

    PlayerInputComponent->BindAxis("MoveRight", this, &AMyCharacter::MoveRight);

    PlayerInputComponent->BindAxis("Turn", this, &APawn::AddControllerYawInput);

    PlayerInputComponent->BindAxis("LookUp", this, &APawn::AddControllerPitchInput);

}



void AMyCharacter::NotifyHit(class UPrimitiveComponent* MyComp, AActor* Other, class UPrimitiveComponent* OtherComp, bool bSelfMoved, FVector HitLocation, FVector HitNormal, FVector NormalImpulse, const FHitResult& Hit)

{
    
    

    Super::NotifyHit(MyComp, Other, OtherComp, bSelfMoved, HitLocation, HitNormal, NormalImpulse, Hit);



    // Apply camera shake when the character hits something

    if (CameraShakeClass)

    {
    
    

        GetWorld()->GetFirstPlayerController()->ClientStartCameraShake(CameraShakeClass);

    }

}



void AMyCharacter::MoveForward(float Value)

{
    
    

    if ((Controller != nullptr) && (Value != 0.0f))

    {
    
    

        // Find out which way is forward

        const FRotator Rotation = Controller->GetControlRotation();

        const FVector Direction = FRotationMatrix(Rotation).GetUnitAxis(EAxis::X);



        // Add movement in that direction

        AddMovementInput(Direction, Value);

    }

}



void AMyCharacter::MoveRight(float Value)

{
    
    

    if (Value != 0.0f)

    {
    
    

        // Find out which way is right

        const FRotator Rotation = Controller->GetControlRotation();

        const FVector Direction = FRotationMatrix(Rotation).GetUnitAxis(EAxis::Y);



        // Add movement in that direction

        AddMovementInput(Direction, Value);

    }

}

在这个示例中,我们使用NotifyHit方法来检测角色受到的碰撞,并在碰撞发生时调用ClientStartCameraShake方法来应用摄像机震动。

摄像机震动蓝图

你也可以使用蓝图来实现摄像机震动。创建一个摄像机震动蓝图类(例如CameraShake_BP),并在其中定义震动的参数和效果。然后在代码中引用这个蓝图类,如上面的CameraShakeClass所示。

实现平滑移动

理论基础

平滑移动可以通过在摄像机移动时引入插值来实现。插值是一种数学方法,用于在两个值之间平滑过渡。在Unreal Engine中,我们可以使用FMath::VInterpToFMath::RInterpTo函数来实现位置和旋转的平滑插值。

代码实现

在前面的示例代码中,我们已经展示了如何在角色移动时引入摄像机的平滑移动。这里再详细解释一下这两个函数的用法:

  • FMath::VInterpTo:用于在两个向量之间进行平滑插值。

    • 参数

      • Current:当前值。

      • Target:目标值。

      • DeltaTime:帧时间。

      • InterpSpeed:插值速度。

  • FMath::RInterpTo:用于在两个旋转之间进行平滑插值。

    • 参数

      • Current:当前值。

      • Target:目标值。

      • DeltaTime:帧时间。

      • InterpSpeed:插值速度。

完整示例

以下是一个完整的示例,展示如何在角色移动时引入摄像机的平滑移动和旋转:


// MyCharacter.h

#pragma once



#include "CoreMinimal.h"

#include "GameFramework/Character.h"

#include "MyCharacter.generated.h"



UCLASS()

class MYGAME_API AMyCharacter : public ACharacter

{
    
    

    GENERATED_BODY()



public:

    // Sets default values for this character's properties

    AMyCharacter();



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;



private:

    // SpringArmComponent for camera

    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))

    USpringArmComponent* CameraBoom;



    // CameraComponent for rendering

    UPROPERTY(VisibleAnywhere, BlueprintReadOnly, Category = Camera, meta = (AllowPrivateAccess = "true"))

    UCameraComponent* FollowCamera;



    // Smooth camera lag

    float CameraLagSpeed = 10.0f;



    // Current and target camera locations

    FVector CurrentCameraLocation;

    FVector TargetCameraLocation;



    // Current and target camera rotations

    FRotator CurrentCameraRotation;

    FRotator TargetCameraRotation;



    // Functions to update target camera location and rotation

    void UpdateTargetCameraLocation();

    void UpdateTargetCameraRotation();

};



// MyCharacter.cpp

#include "MyCharacter.h"

#include "Components/SpringArmComponent.h"

#include "Components/CameraComponent.h"

#include "GameFramework/CharacterMovementComponent.h"



AMyCharacter::AMyCharacter()

{
    
    

    // 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;



    // Create the spring arm component

    CameraBoom = CreateDefaultSubobject<USpringArmComponent>(TEXT("CameraBoom"));

    CameraBoom->SetupAttachment(RootComponent);

    CameraBoom->TargetArmLength = 300.0f; // The camera follows at this distance behind the character

    CameraBoom->bUsePawnControlRotation = true; // Rotate the arm based on the controller



    // Create the camera component

    FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));

    FollowCamera->SetupAttachment(CameraBoom, USpringArmComponent::SocketName); // Attach the camera to the end of the boom

    FollowCamera->bUsePawnControlRotation = false; // Camera does not rotate relative to arm



    // Set initial camera location and rotation

    CurrentCameraLocation = FollowCamera->GetComponentLocation();

    TargetCameraLocation = FollowCamera->GetComponentLocation();

    CurrentCameraRotation = FollowCamera->GetComponentRotation();

    TargetCameraRotation = FollowCamera->GetComponentRotation();

}



void AMyCharacter::BeginPlay()

{
    
    

    Super::BeginPlay();

}



void AMyCharacter::Tick(float DeltaTime)

{
    
    

    Super::Tick(DeltaTime);



    // Smoothly move the camera to the target location and rotation

    CurrentCameraLocation = FMath::VInterpTo(CurrentCameraLocation, TargetCameraLocation, DeltaTime, CameraLagSpeed);

    CurrentCameraRotation = FMath::RInterpTo(CurrentCameraRotation, TargetCameraRotation, DeltaTime, CameraLagSpeed);



    // Set the camera to the new location and rotation

    FollowCamera->SetWorldLocation(CurrentCameraLocation);

    FollowCamera->SetWorldRotation(CurrentCameraRotation);

}



void AMyCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent)

{
    
    

    Super::SetupPlayerInputComponent(PlayerInputComponent);



    // Bind input to move the character

    PlayerInputComponent->BindAxis("MoveForward", this, &AMyCharacter::MoveForward);

    PlayerInputComponent->BindAxis("MoveRight", this, &AMyCharacter::MoveRight);

    PlayerInputComponent->BindAxis("Turn", this, &AMyCharacter::Turn);

    PlayerInputComponent->BindAxis("LookUp", this, &AMyCharacter::LookUp);

}



void AMyCharacter::MoveForward(float Value)

{
    
    

    if ((Controller != nullptr) && (Value != 0.0f))

    {
    
    

        // Find out which way is forward

        const FRotator Rotation = Controller->GetControlRotation();

        const FVector Direction = FRotationMatrix(Rotation).GetUnitAxis(EAxis::X);



        // Add movement in that direction

        AddMovementInput(Direction, Value);



        // Update target camera location

        UpdateTargetCameraLocation();

    }

}



void AMyCharacter::MoveRight(float Value)

{
    
    

    if (Value != 0.0f)

    {
    
    

        // Find out which way is right

        const FRotator Rotation = Controller->GetControlRotation();

        const FVector Direction = FRotationMatrix(Rotation).GetUnitAxis(EAxis::Y);



        // Add movement in that direction

        AddMovementInput(Direction, Value);



        // Update target camera location

        UpdateTargetCameraLocation();

    }

}



void AMyCharacter::Turn(float Value)

{
    
    

    if (Value != 0.0f)

    {
    
    

        // Add yaw rotation to the controller

        AddControllerYawInput(Value);



        // Update target camera rotation

        UpdateTargetCameraRotation();

    }

}



void AMyCharacter::LookUp(float Value)

{
    
    

    if (Value != 0.0f)

    {
    
    

        // Add pitch rotation to the controller

        AddControllerPitchInput(Value);



        // Update target camera rotation

        UpdateTargetCameraRotation();

    }

}



void AMyCharacter::UpdateTargetCameraLocation()

{
    
    

    // Set the target camera location to the current spring arm target location

    TargetCameraLocation = CameraBoom->GetTargetLocation();

}



void AMyCharacter::UpdateTargetCameraRotation()

{
    
    

    // Set the target camera rotation to the current spring arm target rotation

    TargetCameraRotation = CameraBoom->GetTargetRotation();

}

详细解释

  1. SpringArmComponent 和 CameraComponent 的设置

    • CameraBoom:用于管理摄像机与角色之间的距离和角度。

    • FollowCamera:负责实际的摄像机视角。

  2. 平滑插值

    • FMath::VInterpTo:在每帧中,将摄像机的当前位置逐渐插值到目标位置。

    • FMath::RInterpTo:在每帧中,将摄像机的当前旋转逐渐插值到目标旋转。

  3. 输入绑定

    • MoveForwardMoveRight:根据输入值更新角色的移动,并调用 UpdateTargetCameraLocation 来更新摄像机的目标位置。

    • TurnLookUp:根据输入值更新角色的旋转,并调用 UpdateTargetCameraRotation 来更新摄像机的目标旋转。

  4. 目标位置和旋转的更新

    • UpdateTargetCameraLocation:将摄像机的目标位置设置为 CameraBoom 的目标位置。

    • UpdateTargetCameraRotation:将摄像机的目标旋转设置为 CameraBoom 的目标旋转。

通过这种方式,我们可以实现摄像机的平滑移动和旋转,从而提升游戏的真实感和玩家的沉浸感。

总结

在Unreal Engine中实现虚拟摄像机的物理模拟,可以通过以下几个步骤来完成:

  1. 了解基本物理概念:惯性、震动、平滑移动。

  2. 设置摄像机组件:使用SpringArmComponentCameraComponent来管理摄像机的位置和旋转。

  3. 实现惯性效果:通过启用bEnableCameraLag属性或自定义插值代码来实现摄像机的滞后效果。

  4. 实现震动效果:使用AddCameraShake方法在碰撞或爆炸等事件中引入摄像机震动。

  5. 实现平滑移动:通过FMath::VInterpToFMath::RInterpTo函数来实现在角色移动时摄像机的平滑过渡。

这些技术的结合使用,可以为玩家提供更加真实和动态的游戏体验。希望本节的内容对你在Unreal Engine中实现虚拟摄像机的物理模拟有所帮助。
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/chenlz2007/article/details/147031705