UnrealEngine摄像机系统简析
1. 摄像机系统的主要功能
在游戏中,摄像机是玩家观察游戏世界的眼睛。
区别一下玩家和角色的概念,玩家是指通过输入设备体验游戏的真实个体,而角色则是指游戏世界中被操纵的虚拟个体。
摄像机的位置和朝向分别决定了玩家观察的位置和方向,而摄像机的视野、远近裁剪面等属性则决定了玩家观察的范围。UnrealEngine使用POV(Point of View)
来代指这些会影响玩家观察游戏世界的属性。
POV原是一种电影拍摄的模式,指的是镜头相当于叙事主体的眼睛,所有被摄的内容看上去都是在以主体人的眼睛所看到的内容。
如何管理摄像机的切换、更新摄像机的视点、朝向和视野等属性,让玩家可以平滑流畅地观察游戏世界、欣赏游戏内容,是摄像机系统最主要的功能。
2. UE的摄像机系统框架
如下所示,不难看出,APlayerCameraManager
是整个摄像机系统的核心,其他相关的类负责向APlayerCameraManager
提供数据,或者从APlayerCameraManager
获取数据。
APlayerCameraManager所管理的最重要的数据是FTViewTarget
,它记录了摄像机的跟随对象Target
以及摄像机的属性数据POV
。
总的来说,UE的摄像机系统框架的主要流程如下:
- 每个APlayerController对象(包括服务器上的)都会在PostInitializeComponents方法里创建APlayerCameraManager对象,并
初始化ViewTarget
; - UWorld在Tick时会调用APlayerController的UpdateCameraManager方法,最终驱动APlayerCameraManager在UpdateCamera方法里不断
更新ViewTarget
; - 更新完游戏逻辑之后,在调用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。这主要有两个目的:
- 校验Target是否合法,只允许是APawn、APlayerController或者是APlayerState;
- 确保玩家在切换不同控制的角色时
(如观战其他玩家)
,可以将Target切换到当前玩家控制的角色身上;APawn在被AController调用
Possess方法
控制时,会调用PossessedBy方法
,将自身的PlayerState
设置成Controller的PlayerState
。
因此,无论玩家切换到哪个控制的角色,都可以通过PlayerState快速找到当前控制的APawn。
3.3 更新POV
APlayerCameraManager在UpdateCamera方法里完成POV的更新,主要流程如下:
其中,UpdateViewTarget方法是计算POV数据的主要方法,具体步骤如下:
首先判断Target是否为
CameraActor
,如果是则直接找到挂载的UCameraComponent,并调用UCameraComponent的GetCameraView方法;从这里可以看到,UCameraComponent里并没有渲染逻辑,其主要功能是为APlayerCameraManager提供POV数据;
如果Target不为CameraActor,那么允许通过
修改CameraStyle的值
执行自定义POV数据的计算逻辑;UE默认提供了以下五种,可以根据需要额外新增,并添加相应的POV数据计算逻辑;
如果CameraStyle为默认值
NAME_NONE
,或者没有与其对应的计算逻辑,将会调用默认的UpdateViewTargetInternal方法
;BlueprintUpdateCamera方法是可以在蓝图中自定义实现的接口,可以直接覆盖相机的表现;
如果没有在蓝图中自定义实现BlueprintUpdateCamera方法,那么将会调用Target的
CalcCamera方法
;(1)默认情况下,将会遍历所有Attach在AActor上的UCameraComponent,然后找到第一个激活的UCameraComponent调用其GetCameraView方法获取POV数据。如果找不到符合条件的UCameraComponent,那么将会直接调GetActorEyesViewPoint方法提供
玩家观察的位置的朝向
;(2)因此,如果AActor上有多个UCameraComponent,可以通过设置UCameraComponent
激活(不激活)
来实现切换摄像机的功能;最后,判断是否需要调用
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数据,达到控制摄像机的效果,但各自推荐使用的场景却略有区别:
在APlayerCameraManger的UpdateViewTarget方法中
自定义新的CameraStyle
,并添加相应的POV处理逻辑;影响范围最大的方式。通常推荐用于切换不同的镜头表现逻辑时使用,如从
移动时的跟随镜头
切换到处决时的特写镜头
等;创建新的UActorComponent,用于
修改UCameraComponent的位置和朝向
;影响范围最小的方式。通常推荐于UCameraComponent的位置和朝向有额外修改需求时使用,如在加速时缩短角色与摄像机的距离等;
自定义UCameraModifier
,并将其添加到APlayerCameraManager的ModifierList中修改POV数据;影响范围较大的方式。与
修改UCameraComponent的位置和朝向
最大的区别在于,修改UCameraComponent的位置和朝向
通常只会针对某些特定的角色生效,而自定义UCameraModifier
则在所有情况下都会生效;