关于如何通过定义自己的CameraManager来控制视角

[复制链接]
查看5082 | 回复7 | 2016-8-20 18:37:19 | 显示全部楼层 |阅读模式
本帖最后由 bluerose 于 2016-8-20 18:39 编辑

最近看了几天PlayerCameraManager的代码,大致看明白了,本着分享的原则,在此分享一些经验。
PlayerCameraManager,顾名思义就是管理角色摄像仪与视角的,你可以通过继承的方式,编写自己的PlayerCameraManager,之后在你角色的控制类中指定即可。
下面说一下主要的运行过程:
这里可以参考一下ShooterGame(ShooterPlayerCameraManager),从函数UpdateCamera开始看
[mw_shl_code=cpp,true]{
        // Make sure view target is valid
        if( NewTarget == NULL )
        {
                NewTarget = PCOwner;
        }

        // Update current ViewTargets
        ViewTarget.CheckViewTarget(PCOwner);
        if( PendingViewTarget.Target )
        {
                PendingViewTarget.CheckViewTarget(PCOwner);
        }

        // If we're already transitioning to this new target, don't interrupt.
        if( PendingViewTarget.Target != NULL && NewTarget == PendingViewTarget.Target )
        {
                return;
        }[/mw_shl_code]
这里并没有视角的相关逻辑,所以进一步看父类的UpdateCamera函数。
[mw_shl_code=cpp,true]void APlayerCameraManager::UpdateCamera(float DeltaTime)
{
    if ((PCOwner->layer && PCOwner->IsLocalPlayerController()) || !bUseClientSideCameraUpdates || bDebugClientSideCamera)
    {
        DoUpdateCamera(DeltaTime);

        if (GetNetMode() == NM_Client && bShouldSendClientSideCameraUpdate)
        {
            // compress the rotation down to 4 bytes
            int32 const ShortYaw = FRotator::CompressAxisToShort(CameraCache.POV.Rotation.Yaw);
            int32 const ShortPitch = FRotator::CompressAxisToShort(CameraCache.POV.Rotation.Pitch);
            int32 const CompressedRotation = (ShortYaw << 16) | ShortPitch;

            PCOwner->ServerUpdateCamera(CameraCache.POV.Location, CompressedRotation);
            bShouldSendClientSideCameraUpdate = false;
        }
    }
}[/mw_shl_code]
这里是一些网络的逻辑,继续看DoUpdateCamera函数
[mw_shl_code=cpp,true]这里是一些网络的逻辑,继续看DoUpdateCamera函数[/mw_shl_code]
接下来看UpdateViewTarget函数,计算视角的逻辑都在里面
[mw_shl_code=cpp,true]void APlayerCameraManager::UpdateViewTarget(FTViewTarget& OutVT, float DeltaTime)
{
    // Don't update outgoing viewtarget during an interpolation
    if ((PendingViewTarget.Target != NULL) && BlendParams.bLockOutgoing && OutVT.Equal(ViewTarget))
    {
        return;
    }
  //设置默认属性的摄像机
    // store previous POV, in case we need it later
    FMinimalViewInfo OrigPOV = OutVT.POV;
  
    //@TODO: CAMERA: Should probably reset the view target POV fully here
    OutVT.POV.FOV = DefaultFOV;
    OutVT.POV.OrthoWidth = DefaultOrthoWidth;
    OutVT.POV.bConstrainAspectRatio = false;
    OutVT.POV.bUseFieldOfViewForLOD = true;
    OutVT.POV.ProjectionMode = bIsOrthographic ? ECameraProjectionMode::Orthographic : ECameraProjectionMode:erspective;
    OutVT.POV.PostProcessSettings.SetBaseValues();
    OutVT.POV.PostProcessBlendWeight = 1.0f;


    bool bDoNotApplyModifiers = false;
  //如果ViewTarget是个摄像机的话,就直接获取摄像机的视角
    if (ACameraActor* CamActor = Cast<ACameraActor>(OutVT.Target))
    {
        // Viewing through a camera actor.
        CamActor->GetCameraComponent()->GetCameraView(DeltaTime, OutVT.POV);
    }
    else
    {
    //下面都是一些不同模式的摄像机的逻辑,你可以在游戏中按下~,输入Camera XXXX的方式切换这个摄像机
    //默认的摄像机是Default

        static const FName NAME_Fixed = FName(TEXT("Fixed"));
        static const FName NAME_ThirdPerson = FName(TEXT("ThirdPerson"));
        static const FName NAME_FreeCam = FName(TEXT("FreeCam"));
        static const FName NAME_FreeCam_Default = FName(TEXT("FreeCam_Default"));
        static const FName NAME_FirstPerson = FName(TEXT("FirstPerson"));

        if (CameraStyle == NAME_Fixed)
        {
            // do not update, keep previous camera position by restoring
            // saved POV, in case CalcCamera changes it but still returns false
            OutVT.POV = OrigPOV;

            // don't apply modifiers when using this debug camera mode
            bDoNotApplyModifiers = true;
        }
        else if (CameraStyle == NAME_ThirdPerson || CameraStyle == NAME_FreeCam || CameraStyle == NAME_FreeCam_Default)
        {
            // Simple third person view implementation
            FVector Loc = OutVT.Target->GetActorLocation();
            FRotator Rotator = OutVT.Target->GetActorRotation();

            if (OutVT.Target == PCOwner)
            {
                Loc = PCOwner->GetFocalLocation();
            }

            // Take into account Mesh Translation so it takes into account the PostProcessing we do there.
            // @fixme, can crash in certain BP cases where default mesh is null
//            APawn* TPawn = Cast<APawn>(OutVT.Target);
//             if ((TPawn != NULL) && (TPawn->Mesh != NULL))
//             {
//                 Loc += FQuatRotationMatrix(OutVT.Target->GetActorQuat()).TransformVector(TPawn->Mesh->RelativeLocation - GetDefault<APawn>(TPawn->GetClass())->Mesh->RelativeLocation);
//             }

            //OutVT.Target.GetActorEyesViewPoint(Loc, Rot);
            if( CameraStyle == NAME_FreeCam || CameraStyle == NAME_FreeCam_Default )
            {
                Rotator = PCOwner->GetControlRotation();
            }

            FVector Pos = Loc + ViewTargetOffset + FRotationMatrix(Rotator).TransformVector(FreeCamOffset) - Rotator.Vector() * FreeCamDistance;
            FCollisionQueryParams BoxParams(NAME_FreeCam, false, this);
            BoxParams.AddIgnoredActor(OutVT.Target);
            FHitResult Result;

            GetWorld()->SweepSingleByChannel(Result, Loc, Pos, FQuat::Identity, ECC_Camera, FCollisionShape::MakeBox(FVector(12.f)), BoxParams);
            OutVT.POV.Location = !Result.bBlockingHit ? Pos : Result.Location;
            OutVT.POV.Rotation = Rotator;

            // don't apply modifiers when using this debug camera mode
            bDoNotApplyModifiers = true;
        }
        else if (CameraStyle == NAME_FirstPerson)
        {
            // Simple first person, view through viewtarget's 'eyes'
            OutVT.Target->GetActorEyesViewPoint(OutVT.POV.Location, OutVT.POV.Rotation);
   
            // don't apply modifiers when using this debug camera mode
            bDoNotApplyModifiers = true;
        }
        else
        {
       //默认摄像机会执行这个函数
            UpdateViewTargetInternal(OutVT, DeltaTime);
        }
    }
  //这个应该是执行CameraShakes的逻辑
    if (!bDoNotApplyModifiers || bAlwaysApplyModifiers)
    {
        // Apply camera modifiers at the end (view shakes for example)
        ApplyCameraModifiers(DeltaTime, OutVT.POV);
    }
  //头戴设备的视角逻辑
    if (bFollowHmdOrientation)
    {
        if (GEngine->HMDDevice.IsValid() && GEngine->HMDDevice->IsHeadTrackingAllowed())
        {
            GEngine->HMDDevice->UpdatePlayerCameraRotation(this, OutVT.POV);
        }
    }

    // Synchronize the actor with the view target results
    SetActorLocationAndRotation(OutVT.POV.Location, OutVT.POV.Rotation, false);
  
    UpdateCameraLensEffects(OutVT);
}[/mw_shl_code]
综上所述,你应该把摄像机逻辑写在这,如果想了解得更加清楚,可以继续从UpdateViewTargetInternal函数看下去,一直看到CalcCamera为止。
下面大致说一下编写思路:
在使用编辑器新建完自己的CameraManager后,在控制类中制定,例如:
PlayerCameraManagerClass = ADemoCameraManager::StaticClass();
然后我贴一下自己写的代码,这里我实现了一个固定视角:
[mw_shl_code=cpp,true]// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "Camera/PlayerCameraManager.h"
#include "DemoCameraManager.generated.h"

/**
*
*/
UCLASS()
class DEMO_API ADemoCameraManager : public APlayerCameraManager
{
    GENERATED_BODY()
protected:
    virtual void UpdateViewTarget(FTViewTarget& OutVT, float DeltaTime) override;
   
    //UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = TViewTarget)
    //struct FMinimalViewInfo SceneFixedPOV;
   
};[/mw_shl_code]
[mw_shl_code=cpp,true]// Fill out your copyright notice in the Description page of Project Settings.

#include "Demo.h"
#include "DemoCameraManager.h"
#include "DemoCharacter.h"

void ADemoCameraManager::UpdateViewTarget(FTViewTarget& OutVT, float DeltaTime)
{
    if (bFollowHmdOrientation)
    {
        //如果有VR设备就直接运行引擎原始函数
        Super::UpdateViewTarget(OutVT, DeltaTime);
        return;
    }
    // Don't update outgoing viewtarget during an interpolation
    if ((PendingViewTarget.Target != NULL) && BlendParams.bLockOutgoing && OutVT.Equal(ViewTarget))
    {
        return;
    }
    bool bDoNotApplyModifiers = false;

    if (ACameraActor* CamActor = Cast<ACameraActor>(OutVT.Target))
    {
        // Viewing through a camera actor.
        CamActor->GetCameraComponent()->GetCameraView(DeltaTime, OutVT.POV);
    }
    else
    {
        if (CameraStyle == FName("SceneFixed"))
        {
       //这里我感觉可能用PendingViewTarget来传递摄像机坐标和旋转比较好,但是还没测试过
            //自己定义一个场景固定视角
            ADemoCharacter *Character = Cast<ADemoCharacter>(GetOwningPlayerController()->GetPawn());
            OutVT.POV.Location = Character->ViewTargetLocation;
            OutVT.POV.Rotation = Character->ViewTargetRotator;
            //DesiredView.FOV = FieldOfView;
            //DesiredView.AspectRatio = AspectRatio;
            // don't apply modifiers when using this debug camera mode
            bDoNotApplyModifiers = true;
        }else if (CameraStyle==FName("Default"))
        {
            //默认方式是直接取得摄像机的参数来设置FTViewTarget.pov,而摄像机被控制类、SpringArm控制。
            UpdateViewTargetInternal(OutVT, DeltaTime);
        }
        else
        {
            Super::UpdateViewTarget(OutVT, DeltaTime);
        }
    }
    if (!bDoNotApplyModifiers || bAlwaysApplyModifiers)
    {
        // Apply camera modifiers at the end (view shakes for example)
        ApplyCameraModifiers(DeltaTime, OutVT.POV);
    }
    // Synchronize the actor with the view target results
    SetActorLocationAndRotation(OutVT.POV.Location, OutVT.POV.Rotation, false);
    UpdateCameraLensEffects(OutVT);
}[/mw_shl_code]
后面是固定视角VolumeActor的代码:
[mw_shl_code=cpp,true]// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "GameFramework/Actor.h"
#include "Character/DemoCharacter.h"
#include "Character/DemoCameraManager.h"
#include "Character/DemoPlayerController.h"
#include "Runtime/Engine/Classes/Kismet/KismetMathLibrary.h"
#include "BaseSceneFixedViewVolume.generated.h"

UCLASS()
class DEMO_API ABaseSceneFixedViewVolume : public AActor
{
    GENERATED_BODY()
   
public:   
    // Sets default values for this actor's properties
    ABaseSceneFixedViewVolume();

    // Called when the game starts or when spawned
    virtual void BeginPlay() override;
   
    // Called every frame
    virtual void Tick( float DeltaSeconds ) override;

    FVector Lcation;
    FRotator Rotator;

    UPROPERTY(EditDefaultsOnly, Category = "SceneFixedView", meta = (AllowPrivateAccess = "true"))
    UBoxComponent* Triggers;

    UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "SceneFixedView", meta = (AllowPrivateAccess = "true"))
    UCameraComponent* Camera;


    UFUNCTION()
    virtual void OnBeginOverlap(class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweeoResult);

    UFUNCTION()
    virtual void OnEndOverlap(class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex);

    UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = "SceneFixedView")
    bool bCalView=false;

    UPROPERTY(EditDefaultsOnly,Category="SceneFixedView")
    FVector VolumeSize = FVector(500, 500, 200);

    ADemoCharacter* Character;

    ADemoCameraManager* CameraManage;

    FName OriginMode;
};[/mw_shl_code]
[mw_shl_code=cpp,true]// Fill out your copyright notice in the Description page of Project Settings.

#include "Demo.h"
#include "BaseSceneFixedViewVolume.h"



// Sets default values
ABaseSceneFixedViewVolume::ABaseSceneFixedViewVolume()
{
     // Set this actor to call Tick() every frame.  You can turn this off to improve performance if you don't need it.
    PrimaryActorTick.bCanEverTick = true;

    Triggers = CreateDefaultSubobject<UBoxComponent>(TEXT("Triggers"));
    Triggers->SetBoxExtent(VolumeSize);
    Triggers->SetRelativeLocation(FVector(0,0,VolumeSize.Z/2));
    RootComponent = Triggers;

    Camera= CreateDefaultSubobject<UCameraComponent>(TEXT("Camera"));
    Camera->SetRelativeLocation(FVector(VolumeSize.X,VolumeSize.Y,VolumeSize.Z));
    Camera->AttachTo(RootComponent);

    //FollowCamera = CreateDefaultSubobject<UCameraComponent>(TEXT("FollowCamera"));
    //FollowCamera->AttachTo(CameraBoom, USpringArmComponent::SocketName); // Attach the camera to the end of the boom and let the boom adjust to match the controller orientation
    //FollowCamera->bUsePawnControlRotation = false; // Camera does not rotate relative to arm


    Triggers->OnComponentBeginOverlap.AddDynamic(this, &ABaseSceneFixedViewVolume::OnBeginOverlap);
    Triggers->OnComponentEndOverlap.AddDynamic(this, &ABaseSceneFixedViewVolume::OnEndOverlap);
}

// Called when the game starts or when spawned
void ABaseSceneFixedViewVolume::BeginPlay()
{
    Super::BeginPlay();
   
}

// Called every frame
void ABaseSceneFixedViewVolume::Tick( float DeltaTime )
{
    Super::Tick( DeltaTime );
    if (bCalView)
    {
        if (Character)
        {
            Character->ViewTargetLocation = Camera->K2_GetComponentLocation();
            Character->ViewTargetRotator = UKismetMathLibrary::FindLookAtRotation(Camera->K2_GetComponentLocation(), Character->GetActorLocation());
        }
    }
}
void ABaseSceneFixedViewVolume::OnBeginOverlap(class AActor* OtherActor, class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweeoResult)
{
    Character = Cast<ADemoCharacter>(OtherActor);
    if (Character)
    {
        bCalView = true;
        ADemoPlayerController* Controller = Cast<ADemoPlayerController>(Character->GetController());
        if (Controller)
        {
            OriginMode=Controller->layerCameraManager->CameraStyle;
            Controller->SetCameraMode(FName("SceneFixed"));
            //因为我使用控制类来管理视角,为了解决一些视角问题,所以在进入时,必须重新设置视角,屏蔽鼠标修改视角
            Controller->SetControlRotation(FRotator(0.0f,90.0f,0.0f));
            Controller->SetIgnoreLookInput(true);
        }
    }
}

void ABaseSceneFixedViewVolume::OnEndOverlap(class AActor* OtherActor ,class UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
    if (OtherActor==Character)
    {
        bCalView = false;
        ADemoPlayerController* Controller = Cast<ADemoPlayerController>(Character->GetController());
        if (Controller)
        {
            if (OriginMode!=FName(""))
            {
                Controller->SetCameraMode(OriginMode);
            }
            else
            {
                Controller->SetCameraMode(FName("Default"));
            }
            Controller->SetIgnoreLookInput(false);
        }
    }
    Character= nullptr;
}[/mw_shl_code]

bluerose | 2016-8-20 18:40:20 | 显示全部楼层
因为代码太多了,DoUpdateCamera的代码补上
[mw_shl_code=cpp,true]void APlayerCameraManager:oUpdateCamera(float DeltaTime)
{
  //一些后期效果融合
    // update color scale interpolation
    if (bEnableColorScaleInterp)
    {
        float BlendPct = FMath::Clamp((GetWorld()->TimeSeconds - ColorScaleInterpStartTime) / ColorScaleInterpDuration, 0.f, 1.0f);
        ColorScale = FMath:erp(OriginalColorScale, DesiredColorScale, BlendPct);
        // if we've maxed
        if (BlendPct == 1.0f)
        {
            // disable further interpolation
            bEnableColorScaleInterp = false;
        }
    }
  
  
  //是否停止工作,不然就执行UpdateViewTarget
    // Don't update outgoing viewtarget during an interpolation when bLockOutgoing is set.
    if ((PendingViewTarget.Target == NULL) || !BlendParams.bLockOutgoing)
    {
        // Update current view target
        ViewTarget.CheckViewTarget(PCOwner);
        UpdateViewTarget(ViewTarget, DeltaTime);
    }

  //下面都是一些视角更新的循环逻辑,看到最后你会发现他们一直都是用的引用,设置新的视角的逻辑并不在这
    // our camera is now viewing there
    FMinimalViewInfo NewPOV = ViewTarget.POV;

    // if we have a pending view target, perform transition from one to another.
    if (PendingViewTarget.Target != NULL)
    {
        BlendTimeToGo -= DeltaTime;

        // Update pending view target
        PendingViewTarget.CheckViewTarget(PCOwner);
        UpdateViewTarget(PendingViewTarget, DeltaTime);

        // blend....
        if (BlendTimeToGo > 0)
        {
            float DurationPct = (BlendParams.BlendTime - BlendTimeToGo) / BlendParams.BlendTime;

            float BlendPct = 0.f;
            switch (BlendParams.BlendFunction)
            {
            case VTBlend_Linear:
                BlendPct = FMath:erp(0.f, 1.f, DurationPct);
                break;
            case VTBlend_Cubic:
                BlendPct = FMath::CubicInterp(0.f, 0.f, 1.f, 0.f, DurationPct);
                break;
            case VTBlend_EaseIn:
                BlendPct = FMath:erp(0.f, 1.f, FMath:ow(DurationPct, BlendParams.BlendExp));
                break;
            case VTBlend_EaseOut:
                BlendPct = FMath:erp(0.f, 1.f, FMath:ow(DurationPct, 1.f / BlendParams.BlendExp));
                break;
            case VTBlend_EaseInOut:
                BlendPct = FMath::InterpEaseInOut(0.f, 1.f, DurationPct, BlendParams.BlendExp);
                break;
            default:
                break;
            }

            // Update pending view target blend
            NewPOV = ViewTarget.POV;
            NewPOV.BlendViewInfo(PendingViewTarget.POV, BlendPct);//@TODO: CAMERA: Make sure the sense is correct!  BlendViewTargets(ViewTarget, PendingViewTarget, BlendPct);
        }
        else
        {
            // we're done blending, set new view target
            ViewTarget = PendingViewTarget;

            // clear pending view target
            PendingViewTarget.Target = NULL;

            BlendTimeToGo = 0;

            // our camera is now viewing there
            NewPOV = PendingViewTarget.POV;
        }
    }

    // Cache results
    FillCameraCache(NewPOV);

    if (bEnableFading)
    {
        if (bAutoAnimateFade)
        {
            FadeTimeRemaining = FMath::Max(FadeTimeRemaining - DeltaTime, 0.0f);
            if (FadeTime > 0.0f)
            {
                FadeAmount = FadeAlpha.X + ((1.f - FadeTimeRemaining / FadeTime) * (FadeAlpha.Y - FadeAlpha.X));
            }

            if ((bHoldFadeWhenFinished == false) && (FadeTimeRemaining <= 0.f))
            {
                // done
                StopCameraFade();
            }
        }

        if (bFadeAudio)
        {
            ApplyAudioFade();
        }
    }
}[/mw_shl_code]
回复 支持 反对

使用道具 举报

challengerhawk | 2016-8-21 11:13:01 | 显示全部楼层
牛人啊。 看到FMath忍不住笑了
回复 支持 反对

使用道具 举报

crackertoo | 2016-8-31 12:24:45 | 显示全部楼层
看看~ 这个不是大事~
回复 支持 反对

使用道具 举报

kiben4 | 2016-11-13 00:04:53 | 显示全部楼层
好东西!不错
回复 支持 反对

使用道具 举报

ajhonson4 | 2016-12-30 21:41:13 | 显示全部楼层
路过,看一看。
回复 支持 反对

使用道具 举报

我会为何弃疗 | 2019-6-6 10:05:39 | 显示全部楼层
草,好东西,先看为敬,最近遇到需要切换控制器和镜头的问题,控制器和镜头管理器要分开了,正好有用
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

4

主题

23

回帖

182

积分

初阶编码师

积分
182