zoukankan      html  css  js  c++  java
  • 关于如何通过定义自己的CameraManager来控制视角

    2016.8.30

    发现了这个函数,可以直接获得摄像机的位置和旋转。

    Controller->GetPlayerViewPoint(CamLoc, CamRot);

    最近看了几天PlayerCameraManager的代码,大致看明白了,本着分享的原则,在此分享一些经验。

    PlayerCameraManager,顾名思义就是管理角色摄像仪与视角的,你可以通过继承的方式,编写自己的PlayerCameraManager,之后在你角色的控制类中指定即可。

    下面说一下主要的运行过程:

    这里可以参考一下ShooterGame(ShooterPlayerCameraManager),从函数UpdateCamera开始看

    void AShooterPlayerCameraManager::UpdateCamera(float DeltaTime)
    {
      //尝试获取角色类指针,如果成功就执行一些第一人称视角相关的修正 AShooterCharacter
    * MyPawn = PCOwner ? Cast<AShooterCharacter>(PCOwner->GetPawn()) : NULL; if (MyPawn && MyPawn->IsFirstPerson()) {
        //Fov切换的相关逻辑,(右键的微瞄切换)
    const float TargetFOV = MyPawn->IsTargeting() ? TargetingFOV : NormalFOV; DefaultFOV = FMath::FInterpTo(DefaultFOV, TargetFOV, DeltaTime, 20.0f); }   //执行父类函数 Super::UpdateCamera(DeltaTime);   //对角色类的模型进行变换 if (MyPawn && MyPawn->IsFirstPerson()) { MyPawn->OnCameraUpdate(GetCameraLocation(), GetCameraRotation()); } }

      这里并没有视角的相关逻辑,所以进一步看父类的UpdateCamera函数。

    void APlayerCameraManager::UpdateCamera(float DeltaTime)
    {
        if ((PCOwner->Player && 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;
            }
        }
    }

      这里是一些网络的逻辑,继续看DoUpdateCamera函数

    void APlayerCameraManager::DoUpdateCamera(float DeltaTime)
    {
      //一些后期效果融合
    // update color scale interpolation if (bEnableColorScaleInterp) { float BlendPct = FMath::Clamp((GetWorld()->TimeSeconds - ColorScaleInterpStartTime) / ColorScaleInterpDuration, 0.f, 1.0f); ColorScale = FMath::Lerp(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::Lerp(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::Lerp(0.f, 1.f, FMath::Pow(DurationPct, BlendParams.BlendExp)); break; case VTBlend_EaseOut: BlendPct = FMath::Lerp(0.f, 1.f, FMath::Pow(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(); } } }

      接下来看UpdateViewTarget函数,计算视角的逻辑都在里面

    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::Perspective;
        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); }

      综上所述,你应该把摄像机逻辑写在这,如果想了解得更加清楚,可以继续从UpdateViewTargetInternal函数看下去,一直看到CalcCamera为止。

    下面大致说一下编写思路:

    在使用编辑器新建完自己的CameraManager后,在控制类中制定,例如:

    PlayerCameraManagerClass = ADemoCameraManager::StaticClass();

    然后我贴一下自己写的代码,这里我实现了一个固定视角:

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

    后面是固定视角VolumeActor的代码:

    // 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;
    };
    // 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->PlayerCameraManager->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;
    }
  • 相关阅读:
    MyBatisPartA
    概念:漏洞扫描技术
    概念:防火墙技术
    概念:认证技术与访问控制
    概念:为什么要引进密钥管理技术
    概念:数字签名、传统签名和认证
    概念:简述对称密码算法和公钥密码算法的区别
    概念:单向散列函数部分知识点
    Redis单线程QPS效率高的原因,以及redis6.0升级后的变化
    Mydql数据库缓存池Buffer Pool 冷热数据分离
  • 原文地址:https://www.cnblogs.com/blueroses/p/5790879.html
Copyright © 2011-2022 走看看