UnrealEngine摄像机系统简析

1. 摄像机系统的主要功能

在游戏中,摄像机是玩家观察游戏世界的眼睛。

区别一下玩家和角色的概念,玩家是指通过输入设备体验游戏的真实个体,而角色则是指游戏世界中被操纵的虚拟个体。

摄像机的位置和朝向分别决定了玩家观察的位置和方向,而摄像机的视野、远近裁剪面等属性则决定了玩家观察的范围。UnrealEngine使用POV(Point of View)来代指这些会影响玩家观察游戏世界的属性。

POV原是一种电影拍摄的模式,指的是镜头相当于叙事主体的眼睛,所有被摄的内容看上去都是在以主体人的眼睛所看到的内容。

如何管理摄像机的切换、更新摄像机的视点、朝向和视野等属性,让玩家可以平滑流畅地观察游戏世界、欣赏游戏内容,是摄像机系统最主要的功能。


2. UE的摄像机系统框架

如下所示,不难看出,APlayerCameraManager是整个摄像机系统的核心,其他相关的类负责向APlayerCameraManager提供数据,或者从APlayerCameraManager获取数据。

UE的摄像机系统类图

APlayerCameraManager所管理的最重要的数据是FTViewTarget,它记录了摄像机的跟随对象Target以及摄像机的属性数据POV

总的来说,UE的摄像机系统框架的主要流程如下:

  1. 每个APlayerController对象(包括服务器上的)都会在PostInitializeComponents方法里创建APlayerCameraManager对象,并初始化ViewTarget
  2. UWorld在Tick时会调用APlayerController的UpdateCameraManager方法,最终驱动APlayerCameraManager在UpdateCamera方法里不断更新ViewTarget
  3. 更新完游戏逻辑之后,在调用UGameViewportClient的Draw方法渲染画面时,会通过ULocalPlayer的CalcSceneView方法,最终从APlayerCameraManager中获取ViewTarget中的POV数据用于渲染

3. ViewTarget的管理

3.1 初始化ViewTarget

用于渲染画面的POV需要依赖Target才能计算,所以初始化ViewTarget的主要目的是确认初始Target。

在创建APlayerCameraManager时,APlayerController会先调用APlayerCameraManager的SetViewTarget方法将Target设置为自己。

随后,在APlayerController创建并在创建自己所控制的Pawn之后,则会在OnPossess方法中通过AutoManageActiveCameraTarget方法将Target设置为APlayerController所控制的APawn。

正因如此,在Play之后,游戏默认展示的画面是角色身上所挂摄像机的渲染内容,而不是其他摄像机。

3.2 切换Target

APlayerCameraManager允许通过调用SetViewTarget方法来切换Target。如果需要在不同的Target之间平滑切换,只需要在传入新Target的同时传入BlendTime不为0的FViewTargetTransitionParams即可。

值得注意的是,如果设置了平滑切换,那么APlayerCameraManager并不会直接更新ViewTarget.Target,而是将其设置到PendingViewTarget.Target中,等待平滑切换结束之后再将其设置为ViewTarget.Target。

虽然平滑切换结束前,新Target不会被设置到ViewTarget.Target,但逻辑上APlayerCameraManager认为Target切换已经完成了。此时,通过APlayerCameraManager的GetViewTarget方法,将会直接返回PendingViewTarget.Target。

另外一个值得注意的点是,每次更新Target时,都会调用FTViewTarget的CheckViewTarget方法来记录PlayerState。这主要有两个目的:

  1. 校验Target是否合法,只允许是APawn、APlayerController或者是APlayerState;
  2. 确保玩家在切换不同控制的角色时(如观战其他玩家),可以将Target切换到当前玩家控制的角色身上;

    APawn在被AController调用Possess方法控制时,会调用PossessedBy方法,将自身的PlayerState设置成Controller的PlayerState
    因此,无论玩家切换到哪个控制的角色,都可以通过PlayerState快速找到当前控制的APawn。

3.3 更新POV

APlayerCameraManager在UpdateCamera方法里完成POV的更新,主要流程如下:

其中,UpdateViewTarget方法是计算POV数据的主要方法,具体步骤如下:

  1. 首先判断Target是否为CameraActor,如果是则直接找到挂载的UCameraComponent,并调用UCameraComponent的GetCameraView方法;

    从这里可以看到,UCameraComponent里并没有渲染逻辑,其主要功能是为APlayerCameraManager提供POV数据;

  2. 如果Target不为CameraActor,那么允许通过修改CameraStyle的值执行自定义POV数据的计算逻辑;

    UE默认提供了以下五种,可以根据需要额外新增,并添加相应的POV数据计算逻辑;

  3. 如果CameraStyle为默认值NAME_NONE,或者没有与其对应的计算逻辑,将会调用默认的UpdateViewTargetInternal方法

    BlueprintUpdateCamera方法是可以在蓝图中自定义实现的接口,可以直接覆盖相机的表现;

  4. 如果没有在蓝图中自定义实现BlueprintUpdateCamera方法,那么将会调用Target的CalcCamera方法

    (1)默认情况下,将会遍历所有Attach在AActor上的UCameraComponent,然后找到第一个激活的UCameraComponent调用其GetCameraView方法获取POV数据。如果找不到符合条件的UCameraComponent,那么将会直接调GetActorEyesViewPoint方法提供玩家观察的位置的朝向

    (2)因此,如果AActor上有多个UCameraComponent,可以通过设置UCameraComponent激活(不激活)来实现切换摄像机的功能;

  5. 最后,判断是否需要调用ApplyCameraModifiers方法对POV数据进行修正,得到最终的POV数据;

    APlayerCameraManager允许动态增加、删除UCameraModifier,且多个UCameraModifier可以同时生效;

3.4 小结

总的来说,APlayerCameraManager负责管理用于渲染的ViewTarget数据,而诸如UCameraComponent、APawn和UCameModifier等相关的类都是为了更加方便地管理ViewTarget数据而被设计出来分别承担不容职责的类。

理解了这一点,在阅读UE的摄像机系统源码时往往会起到事半功倍的效果。


4. 摄像机的控制

4.1 角色的旋转控制

在讨论摄像机的控制之前,需要先简单了解一下角色的旋转控制。

以UE的FirstPersonProject为例,在其示例Character的SetupPlayerInputComponent方法中,需要完成BindAction操作,玩家才可以使用鼠标控制其旋转。

查阅其处理鼠标输入逻辑的代码,发现并没有直接修改角色的Rotation,而是将鼠标输入直接转发给控制该角色的APlayerController,由APlayerController将其存到RotationInput字段中。

那玩家是如何使用鼠标来控制角色的旋转的呢?

通过查找RotationInput字段的引用,可以发现APlayerController在TickActor时,会调用到UpdateRotation方法,使用RotationInput计算出ControlRotation,并调用其控制角色的FaceRotation方法。

在APawn的FaceRotation方法中,可以看到角色会根据配置是否使用ControllerRotation来修改自身的Rotation,这便是角色旋转控制的主要流程。

4.2 摄像机的旋转控制

用于观察角色的摄像机通常都会直接Attach在该角色身上,摄像机的旋转受该角色的控制,这是最简单的情况。

但大部分情况下,摄像机的旋转与角色的旋转并不完全一致,例如在FPS中,摄像机跟随角色移动,但角色不可以上下旋转,而摄像机则可以跟随玩家输入的控制上下旋转。

此时,简单的Attach显然无法满足要求。UE作为一个从FPS游戏中诞生的游戏引擎,自然在底层提供了相应的支持。

从UCameraComponent的GetCameraView方法可以看到,UCameraComponent允许通过设置bUsePawnControlRotation字段来修正其Rotation。

又因为APawn的GetViewRotation方法默认返回APlayerController的ControlRotation,从而确保UCameraComponent的旋转受输入控制,而非受角色控制。

此时,只需要通过配置让角色的水平旋转也受输入控制,便可以实现摄像机与角色在水平方向上的旋转保持一致。

小Tips
  如果还记得3.2 更新POV的内容,那么就知道UWorld的每次Tick最终都会调用当前正在使用的UCameraComponent的GetCameraView方法。
  因此,如果勾选了UCameraComponent的bUsePawnControlRotation,那么所有针对该UCameraComponent的Rotation修改逻辑都不会生效,因为GetCameraView方法会强制将其Rotation覆盖为APlayerController的ControlRotation。

4.3 更加复杂的旋转控制

在使用第三人称视角的游戏中,摄像机的旋转逻辑常常更为复杂,如镜头需要在旋转的过程中拉近或者拉远等,简单地用玩家输入直接控制摄像机的旋转显然是满足不了需求的。

此时,则需要自定义更为复杂的逻辑来实现摄像机的旋转控制,如UE提供的弹簧臂组件(USpringArmComponent)

4.3.1 弹簧臂组件的使用

弹簧臂组件(USpringArmComponent)的主要功能控制其子对象的旋转,并确保其子对象与自身保持一个固定距离。如果发生碰撞,将缩短子对象与自身的距离,否则将回弹至固定距离。

因此,在使用USpringArmComponent时,需要确保UCameraComponent挂在USpringArmComponent上,并将USpringArmComponent挂在角色身上。

如果需要使用USpringArmComponent控制UCameraComponent的旋转,那么一定要确保将UCameraComponent的bUsePawnControlRotation设置为false,原因可以见4.2 摄像机的旋转控制

值得注意的是,USpringArmComponent同样提供了bUsePawnControlRotation字段来判断是否使用APlayerController的ControlRotation作为其子对象的Rotation,否则将默认使用自己的ComponentRotation(因为Attach在角色上,所以实际上这就是角色的朝向)

4.3.2 其他的自定义逻辑

当然,对于其他复杂的需求,可以自定义相应的控制逻辑实现。

总的来说,可以添加自定义逻辑的地方有三处。虽然它们都可以修改POV数据,达到控制摄像机的效果,但各自推荐使用的场景却略有区别:

  1. 在APlayerCameraManger的UpdateViewTarget方法中自定义新的CameraStyle,并添加相应的POV处理逻辑;

    影响范围最大的方式。通常推荐用于切换不同的镜头表现逻辑时使用,如从移动时的跟随镜头切换到处决时的特写镜头等;

  2. 创建新的UActorComponent,用于修改UCameraComponent的位置和朝向

    影响范围最小的方式。通常推荐于UCameraComponent的位置和朝向有额外修改需求时使用,如在加速时缩短角色与摄像机的距离等;

  3. 自定义UCameraModifier,并将其添加到APlayerCameraManager的ModifierList中修改POV数据;

    影响范围较大的方式。与修改UCameraComponent的位置和朝向最大的区别在于,修改UCameraComponent的位置和朝向通常只会针对某些特定的角色生效,而自定义UCameraModifier则在所有情况下都会生效;


参考文档

  1. UE4 里的 Camera 系统
  2. UE4 Camera系统使用与源码分析
  3. UE4摄像机系统解析
  4. 摄像机管理器 - APlayerCameraManager

UnrealEngine摄像机系统简析
https://asancai.github.io/posts/c6576a9/
作者
RainbowCyan
发布于
2024年2月29日
许可协议