关于如何通过定义自己的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 << ) | 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, .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 > )
{
float DurationPct = (BlendParams.BlendTime - BlendTimeToGo) / BlendParams.BlendTime; float BlendPct = .f;
switch (BlendParams.BlendFunction)
{
case VTBlend_Linear:
BlendPct = FMath::Lerp(.f, .f, DurationPct);
break;
case VTBlend_Cubic:
BlendPct = FMath::CubicInterp(.f, .f, .f, .f, DurationPct);
break;
case VTBlend_EaseIn:
BlendPct = FMath::Lerp(.f, .f, FMath::Pow(DurationPct, BlendParams.BlendExp));
break;
case VTBlend_EaseOut:
BlendPct = FMath::Lerp(.f, .f, FMath::Pow(DurationPct, .f / BlendParams.BlendExp));
break;
case VTBlend_EaseInOut:
BlendPct = FMath::InterpEaseInOut(.f, .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 = ; // 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 + ((.f - FadeTimeRemaining / FadeTime) * (FadeAlpha.Y - FadeAlpha.X));
} if ((bHoldFadeWhenFinished == false) && (FadeTimeRemaining <= .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(.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(, , ); 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(,,VolumeSize.Z/));
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;
}
关于如何通过定义自己的CameraManager来控制视角的更多相关文章
- struts自己定义拦截器--登录权限控制
说明:该自己定义的拦截器实现用户登录的权限控制. login.jsp--->LoginAction--重定向-->MainAction--->main.jsp 一.1.整体的步骤: ...
- VC中预处理指令与宏定义详解
刚接触到MFC编程的人往往会被MFC 向导生成的各种宏定义和预处理指令所吓倒,但是预处理和宏定义又是C语言的一个强大工具.使用它们可以进行简单的源代码控制,版本控制,预警或者完成一些特殊的功能. 一个 ...
- C 宏定义
C/C++中宏使用总结 .C/C++中宏总结C程序的源代码中可包括各种编译指令,这些指令称为预处理命令.虽然它们实际上不是C语言的一部分,但却扩展了C程序设计的环境.本节将介绍如何应用预处理程序和注释 ...
- linux C宏定义 转
写好C语言,漂亮的宏定义很重要,使用宏定义可以防止出错,提高可移植性,可读性,方便性等等.下面列举一些成熟软件中常用得宏定义...... 1,防止一个头文件被重复包含 #ifndef COMDEF_H ...
- c c++面试c工程开发之宏定义和条件编译
多数c语言的初学者对c工程开发过程各个阶段的作用理解不到位,而这方面的的知识又是实际开发过程中经常用到的技能点,所以就成为面试考察中一个重要的考察方面.例如:头文件的作用.头文件的内容:链接的作用和意 ...
- 交换机VLAN的定义、意义以及划分方式
什么是VLAN 虚拟网技术(VLAN,Virtual Local Area Network)的诞生主要源于广播.广播在网络中起着非常重要的作用,如发现新设备.调整网络路径.IP地址租赁等等,许多网络协 ...
- C语言预处理命令总结大全 :宏定义
C程序的源代码中可包括各种编译指令,这些指令称为预处理命令.虽然它们实际上不是C语言的一部分,但却扩展了C程序设计的环境.本节将介绍如何应用预处理程序和注释简化程序开发过程,并提高程序的可读性.ANS ...
- Java中常见方法详解合集(方法的定义及语法结构)
Java的方法定义 1.方法的定义 方法是控制对象的动作行为方式与准则,在Java中方法位于类体下又有另一种含义. 普通的方法在类中称为"实例方法",因为方法的调用需要创建对象,而 ...
- JavaScript性能优化
如今主流浏览器都在比拼JavaScript引擎的执行速度,但最终都会达到一个理论极限,即无限接近编译后程序执行速度. 这种情况下决定程序速度的另一个重要因素就是代码本身. 在这里我们会分门别类的介绍J ...
随机推荐
- Web jquery表格组件 JQGrid 的使用 - 11.问题研究
系列索引 Web jquery表格组件 JQGrid 的使用 - 从入门到精通 开篇及索引 Web jquery表格组件 JQGrid 的使用 - 4.JQGrid参数.ColModel API.事件 ...
- Python学习笔记——字典
1.创建字典和给字典赋值,可以使用工厂方法dict()来创建字典,也可以使用fromkeys()来创建一个元素具有相同值的字典 >>> dict = {'name':'XiaoMin ...
- [Nhibernate]一级缓存
目录 写在前面 文档与系列文章 一级缓存 一个例子 一级缓存管理 总结 写在前面 上篇文章介绍了nhibernate中对象的三种状态,通过对象的三种状态,很容易想到缓存. 什麽是缓存? 有时候,某些数 ...
- [Redis]发布/订阅
摘要 有这样的一个场景,管理员需要发布一条消息,所有的客户端都要受到通知.然后想到了发布订阅模式.使用redis的发布与订阅实现起来更简单一些,说做就做,这里弄个简单的demo,先模拟下. 核心代码 ...
- javascript 高级程序设计 -有感
本来我想写一个高级程序设计总结的,结果发现我进入了一扇门,里面所有的字都要逐字逐句的理解,所有描述已经是非常精炼了,我最初的想法无异于老鼠吃大象. 我现在记录的是我在看这本时的感想. 2015.4月9 ...
- C#中Trim()、TrimStart()、TrimEnd()的用法
string s = " from dual union all "; s = s.Trim().TrimEnd("union all".To ...
- vtkMapper
本文只是整理了该网页的内容:http://www.cnblogs.com/lizhengjin/archive/2009/08/16/1547340.html vtkMapper是一个抽象类,指定了几 ...
- ML 基础知识
A computer program is said to learn from experience E with respect to some task T and some performan ...
- C语言运算符优先级 详细列表
首先还是插入原博客的地址http://blog.csdn.net/huangblog/article/details/8271791 另外还有一个参考博客http://www.slyar.com/bl ...
- C#常用类库(100多个)
http://download.csdn.net/download/dxf1213cs/8238153