相机技术指南
概述
虚幻引擎 3 中供玩家使用的相机系统由三个主要类组成:Camera、Pawn 和 PlayerController。这些类都会交互以控制位置、旋转量以及任何其他在游戏过程中应该用于玩家相机的特效。
PlayerController 有一个对被使用的 Camera 以及被控制的 Pawn 的引用。PlayerController 会接收玩家的输入数据并将其用于更新它正在控制的 Pawn 的位置和旋转。默认情况下,Camera 会将其更新传递给 Pawn,它反过来会更新相机的位置和旋转。
通过修改这些类中的一个或多个及其交互方式,可以将玩家的相机设置为使用任何适合于您正在制作的游戏类型的视角向玩家显示这个游戏世界。默认情况下,玩家的相机会使用第一人称,并提供可以将其触发为一个第三人称肩后镜头视角的选项。可以轻松地将它改为自上而下视角、等角视图、横向卷轴视角或任何其他您的游戏可能需要的视图。
相机
camera类代表玩家进入世界的视角。玩家相机的位置和旋转量可以确定场景显示在屏幕上时进行渲染的视点。Camera 类还包含一些可以控制通过相机看到世界的方式的属性,例如,设置视图字段、屏幕长宽比等等。相机还具有可以被应用于它们的特效,例如后期处理、镜头特效、相机修改器、相机动画等等。
注意:所有 FOV 变量都是全角,用度数表示。
相机属性
- 通用
- PCOwner – 引用拥有这个相机的 PlayerController。
- CameraStyle – 该相机的当前相机模式。在视角没有被视图目标覆盖时,用于确定相机的类型(例如,第一人称、第三人称、自由等等)。
- ViewTarget – 定义视图目标的当前数据。类型是 TViewTarget。
- TViewTarget
- Target – 相机是“以下”的目标 actor。
- Controller – 如果是 Pawn,Target 的 Controller。
- POV – Target 的理想视角。类型是 TPOV。
- TPOV
- Location – 视角的位置。
- Rotation – 视点的旋转量。
- FOV – 视点的全视角角度。
- AspectRatio – 用于 Target 的屏幕长宽比。
- PRI – 通过 pawn 平移可以用于跟踪同一个玩家的玩家函数复制信息。
- TPOV
- FOV
- DefaultFOV – 相机的默认视角。
- bLockedFOV – 如果为真,相机的视角将会锁定为 LockedFOV 值。
- LockedFOV – 锁定 FOV 时使用的视角。
- 屏幕长宽比
- DefaultAspectRatio – 相机的默认长宽比。
- bConstrainAspectRatio – 如果为真,相机的屏幕长宽比将被限制为 ConstrainedAspectRatio 的值。
- ConstrainedAspectRatio – 在相机的屏幕长宽比受到限制时使用的长宽比。
相机函数
- 通用
- UpdateCamera [DeltaTime] – 每帧调用一次对相机进行一次更新。
- DeltaTime – 自从上一次更新发生开始的时间长度。
- GetCameraViewPoint [OutCamLoc] [OutCamRot] – 会检索相机的位置和旋转。不可直接调用该函数。相反,应该调用 PCOwner 的 GetPlayerViewPoint() 函数。
- OutCamLoc – 会输出相机的位置。
- OutCamRot – 会输出相机的旋转量。
- ProcessViewRotation [DeltaTime] [OutViewRotation] [OutDeltaRot] – 由 PCOwner 调用,为相机提供一个可以改变这一帧的视图旋转变化量的机会。
- DeltaTime – 自从上一次更新发生开始的时间长度。
- OutViewRotation – 会输出相机的已调整视图旋转量。
- OutDeltaRot – 输出相机的已调整 delta 旋转量。
- UpdateCamera [DeltaTime] – 每帧调用一次对相机进行一次更新。
- FOV
- GetFOVAngle – 会返回相机的当前视角。
- SetFOV [NewFOV] – 会将相机的视角设置为 NewFOV 值。
- NewFOV – 为相机的视角设置的值。
- 视图目标
- SetViewTarget [NewViewTarget] [TransitionParams] – 设置相机的视图目标。
- NewViewTarget – 设置为相机新的视图目标的 Actor。
- TransitionParams – 混合参数以便可以在平移到新的视图目标时使用。
- UpdateViewTarget [OutVT] [DeltaTime] – 根据帧进行调用将视图目标更新为它的新位置、旋转量和 fov。 如果使用自定义相机(与视图目标 actor 中覆盖 CalcCamera() 相对),这是覆盖以便实现您希望的行为的关键函数。
- OutVT – 会输出一个含有相机的视图目标和视点的数据结构。
- DeltaTime – 自从上一次更新发生开始的时间长度。
- SetViewTarget [NewViewTarget] [TransitionParams] – 设置相机的视图目标。
后期特效
后期特效是在向玩家展示之前应用于已渲染的场景的效果。每个相机都可以使用它自己的后期处理设置的集合,可以覆盖世界、音量、或默认后期特效设置。
要了解更多有关后期特效的信息,请参阅 PostProcessEditorUserGuide(后期编辑器用户指南)、PostProcessTechnicalGuide(后期技术指南)以及 PostProcessMaterials(后期材质)。
后期特效属性
- CameOverridePostProcessAlpha – 设置与世界、体积或默认后期特效设置相关的相机的后期特效设置的作用范围。值 0.0 表示世界、体积或默认后期特效都具有全部作用。值 1.0 表示相机的后期特效具有全部作用范围。
- CamPostProcessSettings – 当相机覆盖世界、体积或默认后期特效的时候使用的后期特效设置。
- bEnableColorScaling – 如果为真,最终图像中的颜色通道将会使用 ColorScale 值进行调整。
- ColorScale – 在最终图像中各个颜色通道调整向量。
- bEnableColorScaleInterp – 如果为真,相机将会在通过 SetDesiredColorScale() 函数设置的新颜色调整值时插入到颜色调整值之间。
- bEnableFading – 如果为真,相机会将 FadeColor 的 FadeAmount 应用到画面中。
- FadeColor – 相机淡入淡出时画面使用的颜色。
- FadeAmount – 使用的淡入淡出量。实质上就是淡入淡出的滤镜。
后期特效函数
- SetDesiredColorScale [NewColorScale] [InterpTime] – 根据 bEnableColorScaleInterp 的值设置新的颜色调整值并有选择地插入。
- NewColorScale – 可以用于颜色调整的新值。
- InterpTime – 进入到新颜色调整值所需要的时间长度。
镜头特效
镜头特效是粒子特效,可以将其用于玩家的相机的镜头。这些镜头特效可以用于创建诸如相机镜头中下的雨滴、血溅、镜头上的污物或灰尘等等这一类的效果。这个 camera 类会包含可以使用这些特效类型的函数。
要了解有关粒子系统和特效的更多信息,请参阅ParticleSystemReference(粒子系统参考)。
镜头特效属性
-
- CameraLensEffects – 当前应用于相机的所有粒子特效的数组。
镜头特效函数
- FindCameraLensEffect [LensEffectEmitterClass] – 会搜索当前应用于相机的镜头特效并返回任何与之匹配的类型。
- LensEffectEmitterType – 要搜索的镜头特效的类。
- AddCameraLensEffect [LensEffectEmitterClass] – 会将指定类型的新镜头特效应用于相机中。
- LensEffectEmitterClass – 要应用到相机中的镜头特效的类。
- RemoveCameraLensEffect [Emitter] – 从相机中删除一个镜头特效。
- Emitter – 要从相机中删除的镜头特效。
- ClearCameraLensEffects – 删除当前应用于相机中的所有镜头特效。
相机动画
相机动画是那些可以在 Matinee 中创建的动画(或者可以选择在一个外部动画编辑器中创建,然后将其导入),它在游戏的过程中使用动画的平移和旋转信息来偏置相机。它们还提供模拟功能,通常可以在 Matinee 中模拟相机的任何其他属性,例如,FOV 或后期特效。它对于创建诸如相机振动、手持式相机的手惯性摆动的特效或者任何其他动画特效都十分有效。
要了解有关创建相机动画的更多信息,请参阅SettingUpCameras(设置相机).
相机动画函数
- PlayCameraAnim [CameraAnim] [Rate] [Scale] [BlendInTime] [BlendOutTime] [bLoop] [bRandomStartTime] [Duration] [bSingleInstance] – 在相机上播放一个相机动画。
- CameraAnim – 要播放的相机动画。
- Rate – 可选项。播放相机动画的速度。
- Scale – 可选项。应用于相机动画变换控件的强度加强器。
- BlendInTime – 可选项。淡入相机动画所需的时间长度。
- BlendOutTime – 可选项。淡出相机动画所需的时间长度。
- bLoop – 可选项。如果是真,相机动画将继续循环直到明确要求它停止。
- bRandomStartTime – 可选项。如果为真,相机动画将会在动画的时间轴上任意一个时间点开始播放。
- Duration – 可选项。播放动画的时间长度。如果没有设置。将会播放整个动画。
- bSingleInstance – 可选项。如果是真,同一时间只允许存在一个相机振动实例。
- StopAllCameraAnims [bImmediate] – 停止所有当前播放的相机动画。
- bImmediate – 可选项。如果是真,动画将会立即停止,忽略不计任何混合延时设置。
- StopAllCameraAnimsByType [Anim] [bImmediate] – 会停止相机动画的某个特定类型的所有实例。
- Anim – 要停止的相机动画的类型。
- bImmediate – 可选项。如果是真,动画将会立即停止,忽略不计任何混合延时设置。
- StopCameraAnim [AnimInst] [bImmediate] – 会停止某个相机动画的某个特定实例。
- AnimInst – 要停止的相机动画实例。
- bImmediate – 可选项。如果是真,动画将会立即停止,忽略不计任何混合延时设置。
相机修改器
相机修改器是应用于相机时可以修改相机的属性的物体。CameraModifier 类是这些效果的基类。通过建这个类的子类并覆盖其中的函数,完全自定义可以创建的修改器。CameraModifier_CameraShake 类是一个可以通过使用相机修改器完成的很恰当的示例。
相机修改器属性
-
- ModifierList – 当前应用于相机上的所有相机修改器的数列
- CameraShakeModClass – 用于减振型相机振动的类。例如,Kismet 中非相机动画画面振动。
相机修改器函数
- PlayCameraShake [Shake] [Scale] [PlaySpace] [UserPlaySpaceRot] – 会在相机上播放相机振动效果。
- Shake – 用于相机振动效果的 CamerShake 设置。
- Scale – 可以放大相机振动设置的缩放因素。
- PlaySpace – 可选项。可以用于相机振动的播放空间。
- UserPlaySpaceRot – 可选项。可以用于用户定义的播放空间的旋转量。
- StopCameraShake [Shake] – 会停止在相机上播放相机振动效果。
- Shake – 要停止播放的相机振动效果。
- CalcRadialCameraShake [Cam] [Epicenter] [InnerRadius] [OuterRadius] [Falloff] – 会计算并为一个具有径裂的特定相机返回强度。
- Cam – 要计算密度的相机。
- Epicenter – 产生相机振动的起源位置。
- InnerRadius – 距离衰减开始的 epicenter 的距离。
- OuterRadius – 距离相机振动效果结束的 epicenter 的距离。
- Falloff – 用于计算强度衰减的指数。
- PlayWorldCameraShake [Shake] [ShakeInstigator] [Epicenter] [InnerRadius] [OuterRadius] [Falloff] [bTrForceFeedback] [bOrientShakeTowardEpicenter] – 播放会影响所有附近相机的世界相机振动。
- Shake – 要播放的相机振动。
- ShakeInstigator – 发起相机振动的 Actor。
- Epicenter – 产生相机振动的起源位置。
- InnerRadius – 距离衰减开始的 epicenter 的距离。
- OuterRadius – 距离相机振动效果结束的 epicenter 的距离。
- Falloff – 用于计算强度衰减的指数。
- bTryForceFeedback – 如果是真,强制反馈将会尝试被应用于任何受影响的控制器。
- bOrientShakeTowardEpicenter – 可选项。如果是真,相机振动中的任何偏移都将相对面向 epicenter 进行使用,X 轴正方向朝向 epicenter。
- ClearAllCameraShakes – 会删除当前应用于相机的所有相机振动。
Player Controller(玩家控制器)
PlayerController 主要负责将玩家输入平移到游戏动作中,例如移动一个 Pawn 或控制相机。PlayerController 的旋转量使相机旋转很常用,尽管这不是严格必需。 创建新的相机视角时,可能必需更新或覆盖一些 PlayerController 类中的功能,因为每个不同的相机类型将玩家的输入平移到 Pawn 中的运动和方向都有所不同。下面将会介绍一些与运动和相机有关的属性和函数。
玩家控制器属性
- PlayerCamera – 引用玩家的相机。
- CameraClass – 要对玩家使用的相机的类。
- ViewTarget – 玩家相机的当前视图目标。
- RealViewTarget – 玩家的相机的视图目标的玩家函数复制信息。
- FOVangle – 玩家的相机的视角。
- DefaultFOV – 用于玩家的相机的默认视角。
玩家控制器函数
- GetPlayerViewPoint [out_Location] [out_Rotation] – 它会返回 Controller 的 Pawn 的视点。对于人类玩家来说,这是指相机的视角。对于AI-控制的玩家来说,这个函数是Pawn眼中的视角。在这个基本的实现过程中,它只是 Controller 自己的位置和旋转量。
- out_Location – 会输出玩家的视点位置。
- out_Rotation – 会输出玩家的视点旋转量。
- GetActorEyesViewPoint [out_Location] [out_Rotation] – 它会返回 Controller 或者其 Pawn(如果存在一个)的视点。基本上,它会返回玩家从哪个位置和方向观看。
- out_Location – 会输出玩家的眼睛的位置。
- out_Rotation – 会输出玩家的眼睛的旋转量。
- UpdateRotation [DeltaTime] – 它会更新基于玩家的输入的 Controller 及该 Controller 的 Pawn 的旋转量。
- DeltaTime – 自从上一次更新发生开始的时间长度。
- ProcessViewRotation [DeltaTime] [out_ViewRotation] [DeltaRot] – 调用它可以允许对控制器的视图旋转量进行任何修改(例如,固定)。 从 UpdateRotation() 中调用。
- DeltaTime – 自从上一次更新发生开始的时间长度。
- out_ViewRotation – 会输出玩家的视图旋转量。
- DeltaRot – 由于玩家输入使旋转量发生改变。
- PlayerMove [DeltaTime] – 它会为当前的移动计算新的加速度和旋转量值,然后调用 ProcessMove()(针对单个玩家或监听服务器)或 ReplicateMove()(针对网络客户端)。这只是基本 PlayerController 类中的一个存根,但是它在与运动有关的特定状态中被覆盖,例如 PlayerWalking 状态。每个循环都会从 PlayerTick() 函数中调用这个函数。
- ProcessMove [DeltaTime] [newAccel] [DoubleClickMove] [DeltaRot] – 它会处理客户端上的当前移动。该函数被覆盖在运动需要特殊功能才能运动的特定状态内部。
Pawn
Pawn 不只是玩家在世界中肉体的代表,还可以负责控制玩家的相机的位置和旋转量。它包含可以被覆盖的函数,创建完全新的相机视角。如下所示是一些与相机相关的函数。
Pawn 函数
- CalcCamera [DeltaTime] [out_CamLoc] [out_CamRot] [out_FOV] – 它会在从 Pawn 角度浏览时计算相机的视点。这是玩家的主要相机计算。
- DeltaTime – 自从上一次更新发生开始的时间长度。
- out_CamLoc – 会输出相机的位置。
- out_CamRot – 会输出相机的旋转量。
- out_FOV – 会输出相机的视角。
- GetDefaultCameraMode [RequestedBy] – 它会返回应该被用于这个 Pawn 的默认相机模式,例如一个名称。通常在具有 Pawn 的时候由控制器进行调用。
- RequestedBy - 控制器请求默认相机模式。
- ProcessViewRotation [deltaTime] [out_ViewRotation] [out_DeltaRot] – 它会为 pawn 提供影响玩家的视图旋转量的机会,然后返回最终视图旋转量作为 out_ViewRotation 参数。可以从 PlayerController 的 UpdateRotation() 函数中调用它。
- deltaTime – 自从上一次更新发生开始的时间长度。
- out_ViewRotation – 会输出 Pawn 的视角的旋转量。
- out_DeltaRot – 会输出 delta 旋转量。
- SetViewRotation [NewRotation] – 如果存在一个Controller则设置该Controller的旋转值,如果不存在Controller ,则设置Pawn本身的旋转值。
- NewRotation – 要为 Pawn 的视图设置的新旋转量。
- GetActorEyesViewPoint [out_Location] [out_Rotation] – 这返回了Pawn的眼睛的位置及方位或者玩家的视角。对于第一人称视角来说,这和相机的位置及朝向一样。它也是会从这里开始执行大多数跟踪的视点。
- out_Location – 会输出 Pawn 的眼睛的位置。
- out_Rotation – 会输出 Pawn 的眼睛的旋转量。
定制相机行为
有2种主要的方法可以实现自定义您的相机。 视图目标 Pawn 可以执行 CalcCamera(),或者您可以创建一个自定义 Camera 扩展类。
执行 Pawn.CalcCamera() 对于简单易懂的相机模式非常有效。 通过这种方法有些功能可能不完全有作用,包括后期特效或相机动画。
创建一个自定义相机类可以消耗多一点进行设置,但是会得到更加全面功能化。 GameFramework.GamePlayerCamera 是这种方法的一个示例。
示例 - CalcCamera
下面是一些使用 Pawn 的 CalcCamera() 函数修改玩家相机视角以及在某些实例中如何处理玩家输入的基本示例。这并不表示所有您的相机需求都可以采用完全即插即用的解决方法。它们只是开始创建您自己的自定义相机设置的起始点。
很明显,建立这些正确可发行相机类型需要进行大量其他可以并应该进行的修改。您可能想要实现在任何非第一人称模式中通过滚动鼠标滚轮调整相机与玩家的距离这个功能。如果您不打算为避免这样的事情而专门设计您的关卡,那么用添加代码的方法避免相机在某些模式中侵占世界几何体,这个主意相当不错。此外,在某些模式中改变绘制瞄准线的方式而不是将其完全删除可能会更好。
所有这些示例都需要一个新的自定义游戏类型类,它可以通知游戏使用这个新的 Pawn 和 PlayerController 类。
UDNGame.uc
class UDNGame extends UTDeathMatch; defaultproperties { DefaultPawnClass=class‘UDNExamples.UDNPawn‘ PlayerControllerClass=class‘UDNExamples.UDNPlayerController‘ MapPrefixes[0]="UDN" }
将需要对 DefaultGame.ini 进行修改,以通知引擎同时将这个新的游戏类型作为默认值使用。
DefaultGame.ini
[Engine.GameInfo] DefaultGame=UDNExamples.UDNGame DefaultServerGame=UDNExamples.UDNGame
注意: 为了要使用这个新的游戏类型,您将需要确保您的地图的前缀正确。我们会在我们的游戏类型中将这个前缀设置为"UDN",这样所有地图都将需要以"UDN-"为前缀进行命名。还可以使用编辑器中的地图快速测试这个新的游戏类型,将这个地图的 World Properties(世界属性)中的 Game Type PIE 属性设置为这个新的游戏类型。
第一人称相机示例
第一人称视角是从 UTPawn 扩展而来的所有 pawn 的默认相机类型。该示例会从每个相关的类中抽取组成相机类型的主要部分,并将其放置在新的子类中,以便可以更好地演示有关创建一个基本的第一人称相机的流程。
UDNPawn.uc
class UDNPawn extends UTPawn; simulated function bool CalcCamera( float fDeltaTime, out vector out_CamLoc, out rotator out_CamRot, out float out_FOV ) { // 计算第一人称相机位置和旋转量 GetActorEyesViewPoint( out_CamLoc, out_CamRot ); return true; } defaultproperties { }
UDNPlayerController.uc
class UDNPlayerController extends UTPlayerController; state PlayerWalking { ignores SeePlayer, HearNoise, Bump; function ProcessMove(float DeltaTime, vector NewAccel, eDoubleClickDir DoubleClickMove, rotator DeltaRot) { if( Pawn == None ) { return; } if (Role == ROLE_Authority) { // 为远程客户端更新 ViewPitch Pawn.SetRemoteViewPitch( Rotation.Pitch ); } Pawn.Acceleration = NewAccel; CheckJumpOrDuck(); } } function UpdateRotation( float DeltaTime ) { local Rotator DeltaRot, newRotation, ViewRotation; ViewRotation = Rotation; if (Pawn!=none) { Pawn.SetDesiredRotation(ViewRotation); } // 计算将会应用在 ViewRotation 上的 Delta DeltaRot.Yaw = PlayerInput.aTurn; DeltaRot.Pitch = PlayerInput.aLookUp; ProcessViewRotation( DeltaTime, ViewRotation, DeltaRot ); SetRotation(ViewRotation); NewRotation = ViewRotation; NewRotation.Roll = Rotation.Roll; if ( Pawn != None ) Pawn.FaceRotation(NewRotation, deltatime); } defaultproperties { }
第三人称相机示例
第三人称相机设置同时被列为 UTPawn 的所有子类的备选相机类型。该示例会抽取主要部分并将默认相机覆盖为第三人称相机。
UDNPawn.uc
class UDNPawn extends UTPawn; //覆盖使默认情况下的玩家网格物体可见 simulated event BecomeViewTarget( PlayerController PC ) { local UTPlayerController UTPC; Super.BecomeViewTarget(PC); if (LocalPlayer(PC.Player) != None) { UTPC = UTPlayerController(PC); if (UTPC != None) { //将玩家控制器设置在视图后面,并使网格物体可见 UTPC.SetBehindView(true); SetMeshVisibility(UTPC.bBehindView); } } } simulated function bool CalcCamera( float fDeltaTime, out vector out_CamLoc, out rotator out_CamRot, out float out_FOV ) { local vector CamStart, HitLocation, HitNormal, CamDirX, CamDirY, CamDirZ, CurrentCamOffset; local float DesiredCameraZOffset; CamStart = Location; CurrentCamOffset = CamOffset; DesiredCameraZOffset = (Health > 0) ? 1.2 * GetCollisionHeight() + Mesh.Translation.Z : 0.f; CameraZOffset = (fDeltaTime < 0.2) ? DesiredCameraZOffset * 5 * fDeltaTime + (1 - 5*fDeltaTime) * CameraZOffset : DesiredCameraZOffset; if ( Health <= 0 ) { CurrentCamOffset = vect(0,0,0); CurrentCamOffset.X = GetCollisionRadius(); } CamStart.Z += CameraZOffset; GetAxes(out_CamRot, CamDirX, CamDirY, CamDirZ); CamDirX *= CurrentCameraScale; if ( (Health <= 0) || bFeigningDeath ) { // 调整相机位置,确保它没有剪切到世界中 // @todo fixmesteve. 注意:如果 FindSpot 失败,您仍然可以获得剪切(很少发生) FindSpot(GetCollisionExtent(),CamStart); } if (CurrentCameraScale < CameraScale) { CurrentCameraScale = FMin(CameraScale, CurrentCameraScale + 5 * FMax(CameraScale - CurrentCameraScale, 0.3)*fDeltaTime); } else if (CurrentCameraScale > CameraScale) { CurrentCameraScale = FMax(CameraScale, CurrentCameraScale - 5 * FMax(CameraScale - CurrentCameraScale, 0.3)*fDeltaTime); } if (CamDirX.Z > GetCollisionHeight()) { CamDirX *= square(cos(out_CamRot.Pitch * 0.0000958738)); // 0.0000958738 = 2*PI/65536 } out_CamLoc = CamStart - CamDirX*CurrentCamOffset.X + CurrentCamOffset.Y*CamDirY + CurrentCamOffset.Z*CamDirZ; if (Trace(HitLocation, HitNormal, out_CamLoc, CamStart, false, vect(12,12,12)) != None) { out_CamLoc = HitLocation; } return true; } defaultproperties { }
UDNPlayerController.uc
class UDNPlayerController extends UTPlayerController; state PlayerWalking { ignores SeePlayer, HearNoise, Bump; function ProcessMove(float DeltaTime, vector NewAccel, eDoubleClickDir DoubleClickMove, rotator DeltaRot) { if( Pawn == None ) { return; } if (Role == ROLE_Authority) { // 为远程客户端更新 ViewPitch Pawn.SetRemoteViewPitch( Rotation.Pitch ); } Pawn.Acceleration = NewAccel; CheckJumpOrDuck(); } } function UpdateRotation( float DeltaTime ) { local Rotator DeltaRot, newRotation, ViewRotation; ViewRotation = Rotation; if (Pawn!=none) { Pawn.SetDesiredRotation(ViewRotation); } // 计算将会应用在 ViewRotation 上的 Delta DeltaRot.Yaw = PlayerInput.aTurn; DeltaRot.Pitch = PlayerInput.aLookUp; ProcessViewRotation( DeltaTime, ViewRotation, DeltaRot ); SetRotation(ViewRotation); NewRotation = ViewRotation; NewRotation.Roll = Rotation.Roll; if ( Pawn != None ) Pawn.FaceRotation(NewRotation, deltatime); } defaultproperties { }
自上而下相机示例
可以通过进行某些额外的修改创建自上而下相机。它与第三人称相机设置相似,但是它还需要限制 Pawn 的旋转量,特别是不允许它的 pitch 向上或向下瞄准。
UDNPawn.uc
class UDNPawn extends UTPawn; var float CamOffsetDistance; //相机在玩家上方偏移的距离 var bool bFollowPlayerRotation; //如果是真,相机会与玩家一起旋转 //覆盖使默认情况下的玩家网格物体可见 simulated event BecomeViewTarget( PlayerController PC ) { local UTPlayerController UTPC; Super.BecomeViewTarget(PC); if (LocalPlayer(PC.Player) != None) { UTPC = UTPlayerController(PC); if (UTPC != None) { //将玩家控制器设置在视图后面,并使网格物体可见 UTPC.SetBehindView(true); SetMeshVisibility(UTPC.bBehindView); UTPC.bNoCrosshair = true; } } } simulated function bool CalcCamera( float fDeltaTime, out vector out_CamLoc, out rotator out_CamRot, out float out_FOV ) { out_CamLoc = Location; out_CamLoc.Z += CamOffsetDistance; if(!bFollowPlayerRotation) { out_CamRot.Pitch = -16384; out_CamRot.Yaw = 0; out_CamRot.Roll = 0; } else { out_CamRot.Pitch = -16384; out_CamRot.Yaw = Rotation.Yaw; out_CamRot.Roll = 0; } return true; } simulated singular event Rotator GetBaseAimRotation() { local rotator POVRot, tempRot; tempRot = Rotation; tempRot.Pitch = 0; SetRotation(tempRot); POVRot = Rotation; POVRot.Pitch = 0; return POVRot; } defaultproperties { bFollowPlayerRotation = false; CamOffsetDistance=384.0 }
UDNPlayerController.uc
class UDNPlayerController extends UTPlayerController; state PlayerWalking { ignores SeePlayer, HearNoise, Bump; function ProcessMove(float DeltaTime, vector NewAccel, eDoubleClickDir DoubleClickMove, rotator DeltaRot) { if( Pawn == None ) { return; } if (Role == ROLE_Authority) { // 为远程客户端更新 ViewPitch Pawn.SetRemoteViewPitch( Rotation.Pitch ); } Pawn.Acceleration = NewAccel; CheckJumpOrDuck(); } } function UpdateRotation( float DeltaTime ) { local Rotator DeltaRot, newRotation, ViewRotation; ViewRotation = Rotation; if (Pawn!=none) { Pawn.SetDesiredRotation(ViewRotation); } // 计算将会应用在 ViewRotation 上的 Delta DeltaRot.Yaw = PlayerInput.aTurn; DeltaRot.Pitch = 0; ProcessViewRotation( DeltaTime, ViewRotation, DeltaRot ); SetRotation(ViewRotation); NewRotation = ViewRotation; NewRotation.Roll = Rotation.Roll; if ( Pawn != None ) Pawn.FaceRotation(NewRotation, deltatime); } defaultproperties { }
等角相机示例
简单的等角型相机与上面介绍的自上而下相机相似。沿着 X 和 Z 两个轴偏移相机,然后向下旋转该 pitch 聚焦于玩家。
UDNPawn.uc
class UDNPawn extends UTPawn; var float CamOffsetDistance; //相机从玩家处偏移的距离 var int IsoCamAngle; //覆盖使默认情况下的玩家网格物体可见 simulated event BecomeViewTarget( PlayerController PC ) { local UTPlayerController UTPC; Super.BecomeViewTarget(PC); if (LocalPlayer(PC.Player) != None) { UTPC = UTPlayerController(PC); if (UTPC != None) { //将玩家控制器设置在视图后面,并使网格物体可见 UTPC.SetBehindView(true); SetMeshVisibility(UTPC.bBehindView); UTPC.bNoCrosshair = true; } } } simulated function bool CalcCamera( float fDeltaTime, out vector out_CamLoc, out rotator out_CamRot, out float out_FOV ) { out_CamLoc = Location; out_CamLoc.X -= Cos(IsoCamAngle * UnrRotToRad) * CamOffsetDistance; out_CamLoc.Z += Sin(IsoCamAngle * UnrRotToRad) * CamOffsetDistance; out_CamRot.Pitch = -1 * IsoCamAngle; out_CamRot.Yaw = 0; out_CamRot.Roll = 0; return true; } simulated singular event Rotator GetBaseAimRotation() { local rotator POVRot, tempRot; tempRot = Rotation; tempRot.Pitch = 0; SetRotation(tempRot); POVRot = Rotation; POVRot.Pitch = 0; return POVRot; } defaultproperties { IsoCamAngle=6420 //35.264 度 CamOffsetDistance=384.0 }
UDNPlayerController.uc
class UDNPlayerController extends UTPlayerController; state PlayerWalking { ignores SeePlayer, HearNoise, Bump; function ProcessMove(float DeltaTime, vector NewAccel, eDoubleClickDir DoubleClickMove, rotator DeltaRot) { if( Pawn == None ) { return; } if (Role == ROLE_Authority) { // 为远程客户端更新 ViewPitch Pawn.SetRemoteViewPitch( Rotation.Pitch ); } Pawn.Acceleration = NewAccel; CheckJumpOrDuck(); } } function UpdateRotation( float DeltaTime ) { local Rotator DeltaRot, newRotation, ViewRotation; ViewRotation = Rotation; if (Pawn!=none) { Pawn.SetDesiredRotation(ViewRotation); } // 计算将会应用在 ViewRotation 上的 Delta DeltaRot.Yaw = PlayerInput.aTurn; DeltaRot.Pitch = 0; ProcessViewRotation( DeltaTime, ViewRotation, DeltaRot ); SetRotation(ViewRotation); NewRotation = ViewRotation; NewRotation.Roll = Rotation.Roll; if ( Pawn != None ) Pawn.FaceRotation(NewRotation, deltatime); } defaultproperties { }
横向卷轴相机示例
一个简单的横版相机不只需要控制相机的视点,还需要修改处理玩家输入数据的方式。只允许玩家在画面上向左和向右移动,并且始终面向他们移动的方向。需要输入数据,这样 A 和 D 按键才可以向前后向后移动玩家。
UDNPawn.uc
class UDNPawn extends UTPawn; var float CamOffsetDistance; //Y-轴上要锁定相机的位置 //覆盖使默认情况下的玩家网格物体可见 simulated event BecomeViewTarget( PlayerController PC ) { local UTPlayerController UTPC; Super.BecomeViewTarget(PC); if (LocalPlayer(PC.Player) != None) { UTPC = UTPlayerController(PC); if (UTPC != None) { //将玩家控制器设置在视图后面,并使网格物体可见 UTPC.SetBehindView(true); SetMeshVisibility(UTPC.bBehindView); UTPC.bNoCrosshair = true; } } } simulated function bool CalcCamera( float fDeltaTime, out vector out_CamLoc, out rotator out_CamRot, out float out_FOV ) { out_CamLoc = Location; out_CamLoc.Y = CamOffsetDistance; out_CamRot.Pitch = 0; out_CamRot.Yaw = 16384; out_CamRot.Roll = 0; return true; } simulated singular event Rotator GetBaseAimRotation() { local rotator POVRot; POVRot = Rotation; if( (Rotation.Yaw % 65535 > 16384 && Rotation.Yaw % 65535 < 49560) || (Rotation.Yaw % 65535 < -16384 && Rotation.Yaw % 65535 > -49560) ) { POVRot.Yaw = 32768; } else { POVRot.Yaw = 0; } if( POVRot.Pitch == 0 ) { POVRot.Pitch = RemoteViewPitch << 8; } return POVRot; } defaultproperties { CamOffsetDistance=0.0 }
UDNPlayerController.uc
class UDNPlayerController extends UTPlayerController; state PlayerWalking { ignores SeePlayer, HearNoise, Bump; function ProcessMove(float DeltaTime, vector NewAccel, eDoubleClickDir DoubleClickMove, rotator DeltaRot) { local Rotator tempRot; if( Pawn == None ) { return; } if (Role == ROLE_Authority) { // 为远程客户端更新 ViewPitch Pawn.SetRemoteViewPitch( Rotation.Pitch ); } Pawn.Acceleration.X = -1 * PlayerInput.aStrafe * DeltaTime * 100 * PlayerInput.MoveForwardSpeed; Pawn.Acceleration.Y = 0; Pawn.Acceleration.Z = 0; tempRot.Pitch = Pawn.Rotation.Pitch; tempRot.Roll = 0; if(Normal(Pawn.Acceleration) Dot Vect(1,0,0) > 0) { tempRot.Yaw = 0; Pawn.SetRotation(tempRot); } else if(Normal(Pawn.Acceleration) Dot Vect(1,0,0) < 0) { tempRot.Yaw = 32768; Pawn.SetRotation(tempRot); } CheckJumpOrDuck(); } } function UpdateRotation( float DeltaTime ) { local Rotator DeltaRot, ViewRotation; ViewRotation = Rotation; // 计算将会应用在 ViewRotation 上的 Delta DeltaRot.Yaw = Pawn.Rotation.Yaw; DeltaRot.Pitch = PlayerInput.aLookUp; ProcessViewRotation( DeltaTime, ViewRotation, DeltaRot ); SetRotation(ViewRotation); } defaultproperties { }
一体化相机示例
该示例会将所有其他示例合成为一个单独的执行过程,它允许玩家在任何相机类型之间进行切换并通过使用可执行函数调整它们。
UDNPawn.uc
class UDNPawn extends UTPawn; Enum CameraPerspective { CAM_FirstPerson, CAM_ThirdPerson, CAM_TopDown, CAM_SideScroller, CAM_Isometric }; var bool bFollowPlayerRotation; var CameraPerspective CameraType; var float CamOffsetDistance; var int IsoCamAngle; exec function CameraMode(CameraPerspective mode) { local UTPlayerController UTPC; CameraType = mode; UTPC = UTPlayerController(Controller); if (UTPC != None) { if(CameraType != CAM_FirstPerson) { UTPC.SetBehindView(true); if(CameraType != CAM_ThirdPerson) { UTPC.bNoCrosshair = true; } else { UTPC.bNoCrosshair = false; } } else { UTPC.bNoCrosshair = false; UTPC.SetBehindView(false); } SetMeshVisibility(UTPC.bBehindView); } } exec function IsoAngle(int angle) { IsoCamAngle = angle; } /* BecomeViewTarget 在这个 actor 变为它的 ViewTarget 时会被 Camera 调用 */ simulated event BecomeViewTarget( PlayerController PC ) { local UTPlayerController UTPC; Super.BecomeViewTarget(PC); if (LocalPlayer(PC.Player) != None) { UTPC = UTPlayerController(PC); if (UTPC != None) { if(CameraType != CAM_FirstPerson) { UTPC.SetBehindView(true); if(CameraType != CAM_ThirdPerson) { UTPC.bNoCrosshair = true; } else { UTPC.bNoCrosshair = false; } } else { UTPC.bNoCrosshair = false; UTPC.SetBehindView(false); } SetMeshVisibility(UTPC.bBehindView); } } } /** * 从这个 pawn 观看时计算相机视点。 * * @param fDeltaTime 自从上次更新开始的 delta 时间(秒) * @param out_CamLoc 计算机位置 * @param out_CamRot 相机旋转量 * @param out_FOV 视角 * * @返回 如果 Pawn 应该提供相机视点,那么返回真。 */ simulated function bool CalcCamera( float fDeltaTime, out vector out_CamLoc, out rotator out_CamRot, out float out_FOV ) { // 处理固定的相机 if (bFixedView) { out_CamLoc = FixedViewLoc; out_CamRot = FixedViewRot; } else { if ( CameraType == CAM_ThirdPerson ) // 处理后视图 { CalcThirdPersonCam(fDeltaTime, out_CamLoc, out_CamRot, out_FOV); } else if ( CameraType == CAM_TopDown ) // 处理后视图 { CalcTopDownCam(fDeltaTime, out_CamLoc, out_CamRot, out_FOV); } else if ( CameraType == CAM_SideScroller ) // 处理后视图 { CalcSideScrollerCam(fDeltaTime, out_CamLoc, out_CamRot, out_FOV); } else if ( CameraType == CAM_Isometric ) // 处理后视图 { CalcIsometricCam(fDeltaTime, out_CamLoc, out_CamRot, out_FOV); } else { // 默认情况下,我们通过 Pawn 的眼睛浏览。 GetActorEyesViewPoint( out_CamLoc, out_CamRot ); } if ( UTWeapon(Weapon) != none) { UTWeapon(Weapon).WeaponCalcCamera(fDeltaTime, out_CamLoc, out_CamRot); } } return true; } simulated function bool CalcTopDownCam( float fDeltaTime, out vector out_CamLoc, out rotator out_CamRot, out float out_FOV ) { out_CamLoc = Location; out_CamLoc.Z += CamOffsetDistance; if(!bFollowPlayerRotation) { out_CamRot.Pitch = -16384; out_CamRot.Yaw = 0; out_CamRot.Roll = 0; } else { out_CamRot.Pitch = -16384; out_CamRot.Yaw = Rotation.Yaw; out_CamRot.Roll = 0; } return true; } simulated function bool CalcSideScrollerCam( float fDeltaTime, out vector out_CamLoc, out rotator out_CamRot, out float out_FOV ) { out_CamLoc = Location; out_CamLoc.Y = CamOffsetDistance; out_CamRot.Pitch = 0; out_CamRot.Yaw = 16384; out_CamRot.Roll = 0; return true; } simulated function bool CalcIsometricCam( float fDeltaTime, out vector out_CamLoc, out rotator out_CamRot, out float out_FOV ) { out_CamLoc = Location; out_CamLoc.X -= Cos(IsoCamAngle * UnrRotToRad) * CamOffsetDistance; out_CamLoc.Z += Sin(IsoCamAngle * UnrRotToRad) * CamOffsetDistance; out_CamRot.Pitch = -1 * IsoCamAngle; out_CamRot.Yaw = 0; out_CamRot.Roll = 0; return true; } /** * 会返回没有经过任何调整的基础“瞄准旋转量?”(没有瞄准误差、没有自动锁定、没有附着.. 就是没有用过的初始瞄准旋转量!) * * @返回 基本 Aim 旋转量。 */ simulated singular event Rotator GetBaseAimRotation() { local vector POVLoc; local rotator POVRot, tempRot; if(CameraType == CAM_TopDown || CameraType == CAM_Isometric) { tempRot = Rotation; tempRot.Pitch = 0; SetRotation(tempRot); POVRot = Rotation; POVRot.Pitch = 0; } else if(CameraType == CAM_SideScroller) { POVRot = Rotation; if( (Rotation.Yaw % 65535 > 16384 && Rotation.Yaw % 65535 < 49560) || (Rotation.Yaw % 65535 < -16384 && Rotation.Yaw % 65535 > -49560) ) { POVRot.Yaw = 32768; } else { POVRot.Yaw = 0; } if( POVRot.Pitch == 0 ) { POVRot.Pitch = RemoteViewPitch << 8; } } else { if( Controller != None && !InFreeCam() ) { Controller.GetPlayerViewPoint(POVLoc, POVRot); return POVRot; } else { POVRot = Rotation; if( POVRot.Pitch == 0 ) { POVRot.Pitch = RemoteViewPitch << 8; } } } return POVRot; } defaultproperties { CameraType=CAM_FirstPerson; bFollowPlayerRotation = false; CamOffsetDistance=384.0 IsoCamAngle=6420 //35.264 度 }
UDNPlayerController.uc
class UDNPlayerController extends UTPlayerController; state PlayerWalking { function ProcessMove(float DeltaTime, vector NewAccel, eDoubleClickDir DoubleClickMove, rotator DeltaRot) { local UDNPawn P; local Rotator tempRot; if( (Pawn != None) ) { P = UDNPawn(Pawn); if(P != none) { if(P.CameraType == CAM_SideScroller) { Pawn.Acceleration.X = -1 * PlayerInput.aStrafe * DeltaTime * 100 * PlayerInput.MoveForwardSpeed; Pawn.Acceleration.Y = 0; Pawn.Acceleration.Z = 0; tempRot.Pitch = P.Rotation.Pitch; tempRot.Roll = 0; if(Normal(Pawn.Acceleration) Dot Vect(1,0,0) > 0) { tempRot.Yaw = 0; P.SetRotation(tempRot); } else if(Normal(Pawn.Acceleration) Dot Vect(1,0,0) < 0) { tempRot.Yaw = 32768; P.SetRotation(tempRot); } } else { if ( (DoubleClickMove == DCLICK_Active) && (Pawn.Physics == PHYS_Falling) ) DoubleClickDir = DCLICK_Active; else if ( (DoubleClickMove != DCLICK_None) && (DoubleClickMove < DCLICK_Active) ) { if ( UTPawn(Pawn).Dodge(DoubleClickMove) ) DoubleClickDir = DCLICK_Active; } Pawn.Acceleration = newAccel; } if (Role == ROLE_Authority) { // 为远程客户端更新 ViewPitch Pawn.SetRemoteViewPitch( Rotation.Pitch ); } } CheckJumpOrDuck(); } } } function UpdateRotation( float DeltaTime ) { local UDNPawn P; local Rotator DeltaRot, newRotation, ViewRotation; P = UDNPawn(Pawn); ViewRotation = Rotation; if (p != none && P.CameraType != CAM_SideScroller) { Pawn.SetDesiredRotation(ViewRotation); } // 计算将会应用在 ViewRotation 上的 Delta if( P != none && P.CameraType == CAM_SideScroller ) { DeltaRot.Yaw = Pawn.Rotation.Yaw; } else { DeltaRot.Yaw = PlayerInput.aTurn; } DeltaRot.Pitch = PlayerInput.aLookUp; ProcessViewRotation( DeltaTime, ViewRotation, DeltaRot ); SetRotation(ViewRotation); ViewShake( deltaTime ); NewRotation = ViewRotation; NewRotation.Roll = Rotation.Roll; if (P != None && P.CameraType != CAM_SideScroller ) Pawn.FaceRotation(NewRotation, deltatime); } defaultproperties { }
示例 - 自定义相机
该示例会采用与之前集合不同的方法。这些方法都使用了 Pawn 类中的 CalcCamera() 函数修改相机。在这个示例中,将会创建自定义相机框架,这样可以轻松地插入不同的相机模型以便快速修改相机操作。重要的是,我们具有所有相机功能的全部访问权限,例如后期特效、相机动画和特效等等。此外之外,玩家控制器类将添加该功能,使用玩家控制模块覆盖某些特定角度的玩家运动和瞄准。
该示例的设置比相对简单的覆盖一些函数更复杂一些,正如其他示例中所示的情况。该示例需要覆盖很多在前面的示例中提到的函数,但是不用直接在这些函数中直接添加或更改功能,这些函数将只会负责自定义相机和控制系统,提供它们通常执行的功能。
新的相机类实质上将会充当相机模块的界面。它包含一些函数,它们可以创建新相机模块以及处理相机具有除玩家的 Pawn 之外的视图目标的情况。它的职责除了这些以外,还可以将函数调用从各种不同 Engine 类中传递给当前相机模块以便进行管理。同样地,这个新的 PlayerController 类会引用一个用来处理 PlayerController 和 Pawn 类中特定运动和瞄准函数的控制模块。它允许针对相机和控制类型的功能全部包含在紧凑的自包含类中,这些自包含类可以随意进行替换,快速并轻松地实现新的相机类型。
基础相机模块
基础相机模块类通过 Object 类扩展而来,并且定义了所有相机模块将会共有的属性和运转状态。它的一个属性是对拥有它的相机的一个引用。定义了一些初始化和取消初始化函数,但是这个类中的主要函数功能由可以计算玩家相机的新位置和旋转量的 UpdateCamera() 函数执行,而且可以应用任何其他想要的效果或修改。
使用 config(Camera) 修饰符定义这个类,这样应该可以配置或在特定相机模块中永远存在的所有属性将会在 *Camera.ini 文件中找到。它还会被定义为 abstract ,这样实际上就不能够使用它。它更像是一个可以进行特定相机模块构建的模板,不是实际使用自己本身的类。
UDNCameraModule.uc
class UDNCameraModule extends Object abstract config(Camera); //具有相机控制权 var transient UDNPlayerCamera PlayerCamera; //针对模式的初始化 function Init(); /** 在相机变为 active 的时候调用 */ function OnBecomeActive( UDNCameraModule OldCamera ); /** 在相机变为 inactive 的时候调用 */ function OnBecomeInActive( UDNCameraModule NewCamera ); //计算新的相机位置和旋转量 function UpdateCamera(Pawn P, UDNPlayerCamera CameraActor, float DeltaTime, out TViewTarget OutVT); //初始化新的视图目标 simulated function BecomeViewTarget( UDNPlayerController PC ); //处理镜头推近 function ZoomIn(); //处理镜头推近 function ZoomOut(); defaultproperties { }
自定义相机
这个新的相机类由 Camera 基类扩展而来,覆盖一些函数并添加新的函数功能处理相机模块。在这个系统中相机的主要任务是担任相机模块的中间人,它现在处理大部分计算工作。
UDNPlayerCamera.uc
class UDNPlayerCamera extends Camera config(Camera); var UDNPlayerController PlayerOwner; //玩家控制器具有这个相机控制权 var UDNCameraModule CurrentCamera; //正在使用的当前相机模式 var config string DefaultCameraClass; //默认相机模式??类 function PostBeginPlay() { local class<UDNCameraModule> NewClass; Super.PostBeginPlay(); // 设置相机模式 if ( (CurrentCamera == None) && (DefaultCameraClass != "") ) { //获取要使用的默认相机类 NewClass = class<UDNCameraModule>( DynamicLoadObject( DefaultCameraClass, class‘Class‘ ) ); //创建默认相机 CurrentCamera = CreateCamera(NewClass); } } //初始化 PlayerCamera 使其拥有 PlayerController function InitializeFor(PlayerController PC) { //进行父代初始化 Super.InitializeFor(PC); //将 PlayerOwner 设置为玩家控制器 PlayerOwner = UDNPlayerController(PC); } /** * 内部。创建并初始化一个新的相机指定类,返回这个对象参数。 */ function UDNCameraModule CreateCamera(class<UDNCameraModule> CameraClass) { local UDNCameraModule NewCam; //创建新的相机并进行初始化 NewCam = new(Outer) CameraClass; NewCam.PlayerCamera = self; NewCam.Init(); //在新/旧的相机上调用 active/inactive 函数 if(CurrentCamera != none) { CurrentCamera.OnBecomeInactive(NewCam); NewCam.OnBecomeActive(CurrentCamera); } else { NewCam.OnBecomeActive(None); } //将新相机设置为当前值 CurrentCamera = NewCam; return NewCam; } /** * 查询 ViewTarget(视图目标)并输出 Point Of View(视角)。 * * @param OutVT 要使用的 ViewTarget。 * @param DeltaTime 自从上次相机更新后的 Delta Time(以秒为单位)。 */ function UpdateViewTarget(out TViewTarget OutVT, float DeltaTime) { local CameraActor CamActor; local TPOV OrigPOV; local Vector Loc, Pos, HitLocation, HitNormal; local Rotator Rot; local Actor HitActor; // 不要在插值的过程中更新输出观察目标 if( PendingViewTarget.Target != None && OutVT == ViewTarget && BlendParams.bLockOutgoing ) { return; } OrigPOV = OutVT.POV; // 视图目标上的默认 FOV OutVT.POV.FOV = DefaultFOV; // 浏览相机 actor。 CamActor = CameraActor(OutVT.Target); if( CamActor != None ) { CamActor.GetCameraView(DeltaTime, OutVT.POV); // 通过 CameraActor 获取长宽比。 bConstrainAspectRatio = bConstrainAspectRatio || CamActor.bConstrainAspectRatio; OutVT.AspectRatio = CamActor.AspectRatio; // 查看 CameraActor 是否需要覆盖使用的 PostProcess 设置。 CamOverridePostProcessAlpha = CamActor.CamOverridePostProcessAlpha; CamPostProcessSettings = CamActor.CamOverridePostProcess; } else { // 为 Pawn Viewtarget 提供了一个指定相机位置的机会。 // 如果 Pawn 没有覆盖相机视图,那么我们将会继续使用我们自己的默认设置 if( Pawn(OutVT.Target) == None || !Pawn(OutVT.Target).CalcCamera(DeltaTime, OutVT.POV.Location, OutVT.POV.Rotation, OutVT.POV.FOV) ) { //Pawn 不需要控制,我们有一个自定义模式 if(CurrentCamera != none) { //允许模式处理相机更新 CurrentCamera.UpdateCamera(Pawn(OutVT.Target), self, DeltaTime, OutVT); } //没有自定义模式 - 使用默认相机类型 else { switch( CameraStyle ) { case ‘Fixed‘ : // 没有更新,保留以前的相机位置 // 通过恢复保存的 POV,如果 CalcCamera 更改它,但是仍然会返回 false OutVT.POV = OrigPOV; break; case ‘ThirdPerson‘ : // 简单的第三人称视角实现 case ‘FreeCam‘ : case ‘FreeCam_Default‘: Loc = OutVT.Target.Location; Rot = OutVT.Target.Rotation; //OutVT.Target.GetActorEyesViewPoint(Loc, Rot); if( CameraStyle == ‘FreeCam‘ || CameraStyle == ‘FreeCam_Default‘ ) { Rot = PCOwner.Rotation; } Loc += FreeCamOffset >> Rot; Pos = Loc - Vector(Rot) * FreeCamDistance; // @fixme, 各个 BlockingVolume.bBlockCamera=false HitActor = Trace(HitLocation, HitNormal, Pos, Loc, FALSE, vect(12,12,12)); OutVT.POV.Location = (HitActor == None) ? Pos : HitLocation; OutVT.POV.Rotation = Rot; break; case ‘FirstPerson‘ : // 简单的第一人称,通过视图目标的‘眼睛’观看 default : OutVT.Target.GetActorEyesViewPoint(OutVT.POV.Location, OutVT.POV.Rotation); break; } } } } ApplyCameraModifiers(DeltaTime, OutVT.POV); // 设置相机的位置和旋转量,处理我们没有被锁定到视图目标的情况 SetRotation(OutVT.POV.Rotation); SetLocation(OutVT.POV.Location); } //将视图目标初始化传递到相机模式 simulated function BecomeViewTarget( PlayerController PC ) { CurrentCamera.BecomeViewTarget(UDNPlayerController(PC)); } //将放大传递给相机模式 function ZoomIn() { CurrentCamera.ZoomIn(); } //将缩小传递给相机模式 function ZoomOut() { CurrentCamera.ZoomOut(); } defaultproperties { }
基础控制模块
基础控制模块类通过 Object 类扩展而来,并且定义了所有控制模块将会共有的属性和运转状态。它包含一个对拥有它同时控制当前鼠标光标位置的控制的引用。像基础相机模块一样,定义了初始化和取消初始化函数,可以针对任何类型进行可能需要的设置或清除。这个类的其他部分由将会同时控制玩家运动和瞄准的函数构成。
使用 config(Control) 修饰符定义这个类,这样应该可以配置或在特定控制模块中永远存在的所有属性将会在 *Control.ini 文件中找到。它还会被定义为 abstract ,这样实际上就不能够使用它。它只不过是一个用来进行构建的特定控制模块的模板,而不是一个始终实际会使用自己本身的类。
UDNControlModule.uc
class UDNControlModule extends Object abstract config(Control); //引用具有控制权的控制器 var UDNPlayerController Controller; //针对模式的初始化 function Init(); /** 在相机变为 active 的时候调用 */ function OnBecomeActive( UDNControlModule OldModule ); /** 在相机变为 inactive 的时候调用 */ function OnBecomeInActive( UDNControlModule NewModule ); //计算 Pawn 瞄准目标旋转量 simulated singular function Rotator GetBaseAimRotation(); //控制自定义玩家运动 function ProcessMove(float DeltaTime, vector NewAccel, eDoubleClickDir DoubleClickMove, rotator DeltaRot); //计算控制器旋转 function UpdateRotation(float DeltaTime); defaultproperties { }
Engine 类重载
一些引擎类需要扩展才能与新的相机和控制系统接口,其中主要的是 PlayerController、Pawn 和 HUD 类。还会利用这些新的类创建一个新的游戏类型
PlayerController
这个新的 PlayerController 类会添加可以更改同时作为缩小或扩大使用的相机模块类型的可执行函数(缩放函数的工作方式由当前相机模块实现它们的方式决定)。覆盖 PlayerWalking 状态的 ProcessMove() 函数和 UpdateRotation() 函数向控制模块添加调用。最后,覆盖并修改 GetPlayerViewPoint() 函数防止相机被损坏,同时强制 PlayerController 在存在新自定义相机的情况下使用它。
UDNPlayerController.uc
class UDNPlayerController extends UTPlayerController; var UDNControlModule ControlModule; //要使用的玩家控制模块 var config string DefaultControlModuleClass; //玩家控制模块的默认类 //通过类切换到其他相机的可执行函数 exec function ChangeControls( string ClassName ) { local class<UDNControlModule> ControlClass; local UDNControlModule NewControlModule; ControlClass = class<UDNControlModule>( DynamicLoadObject( DefaultControlModuleClass, class‘Class‘ ) ); if(ControlClass != none) { // 将模块与 PlayerController 联系起来 NewControlModule = new(Outer) ControlClass; NewControlModule.Controller = self; NewControlModule.Init(); //在新/旧模块上调用 active/inactive 函数 if(ControlModule != none) { ControlModule.OnBecomeInactive(NewControlModule); NewControlModule.OnBecomeActive(ControlModule); } else { NewControlModule.OnBecomeActive(None); } ControlModule = NewControlModule; } else { `log("Couldn‘t get control module class!"); // 没有一个 Control Class 的情况很好。 PlayerController 将使用默认控制。 } } //通过类切换到其他相机的可执行函数 exec function ChangeCamera( string ClassName ) { local class<UDNCameraModule> NewClass; NewClass = class<UDNCameraModule>( DynamicLoadObject( ClassName, class‘Class‘ ) ); if(NewClass != none && UDNPlayerCamera(PlayerCamera) != none) { UDNPlayerCamera(PlayerCamera).CreateCamera(NewClass); } } //镜头推近可执行函数 exec function ZoomIn() { if(UDNPlayerCamera(PlayerCamera) != none) { UDNPlayerCamera(PlayerCamera).ZoomIn(); } } //镜头拉远可执行函数 exec function ZoomOut() { if(UDNPlayerCamera(PlayerCamera) != none) { UDNPlayerCamera(PlayerCamera).ZoomOut(); } } simulated function PostBeginPlay() { local class<UDNControlModule> ControlClass; local UDNControlModule NewControlModule; Super.PostBeginPlay(); ControlClass = class<UDNControlModule>( DynamicLoadObject( DefaultControlModuleClass, class‘Class‘ ) ); if(ControlClass != none) { // 将模块与 PlayerController 联系起来 NewControlModule = new(Outer) ControlClass; NewControlModule.Controller = self; NewControlModule.Init(); //在新/旧模块上调用 active/inactive 函数 if(ControlModule != none) { ControlModule.OnBecomeInactive(NewControlModule); NewControlModule.OnBecomeActive(ControlModule); } else { NewControlModule.OnBecomeActive(None); } ControlModule = NewControlModule; } else { `log("Couldn‘t get control module class!"); // 没有一个 Control Class 的情况很好。 PlayerController 将使用默认控制。 } } state PlayerWalking { function ProcessMove(float DeltaTime, vector NewAccel, eDoubleClickDir DoubleClickMove, rotator DeltaRot) { //控制器有一个 UDNPlayerCamera if(ControlModule != none) { //允许自定义相机覆盖玩家运动 ControlModule.ProcessMove(DeltaTime, NewAccel, DoubleClickMove, DeltaRot); } else { Super.ProcessMove(DeltaTime, NewAccel, DoubleClickMove, DeltaRot); } } } function UpdateRotation( float DeltaTime ) { //控制器有一个 UDNPlayerCamera if(ControlModule != none) { //允许自定义相机更新我们的旋转量 ControlModule.UpdateRotation(DeltaTime); } else { Super.UpdateRotation(DeltaTime); } } /* GetPlayerViewPoint: 会返回 Player 的 Point of View(视角) 对于 AI,它代表的是 Pawn 的 Eyes ViewPoint(眼睛视角) 对于 Human 玩家,它代表的是 Camera 的 ViewPoint(视角) */ simulated event GetPlayerViewPoint( out vector POVLocation, out Rotator POVRotation ) { local float DeltaTime; local UTPawn P; P = IsLocalPlayerController() ? UTPawn(CalcViewActor) : None; DeltaTime = WorldInfo.TimeSeconds - LastCameraTimeStamp; LastCameraTimeStamp = WorldInfo.TimeSeconds; // 支持使用 CameraActor 视角 if ( CameraActor(ViewTarget) != None ) { if ( PlayerCamera == None ) { super.ResetCameraMode(); SpawnCamera(); } super.GetPlayerViewPoint( POVLocation, POVRotation ); } else { //没有损坏我们的相机!!! /* if ( PlayerCamera != None ) { PlayerCamera.Destroy(); PlayerCamera = None; } */ //没有相机,我们有视图目标 - 使视图目标在控制中 if ( PlayerCamera == None && ViewTarget != None ) { POVRotation = Rotation; if ( (PlayerReplicationInfo != None) && PlayerReplicationInfo.bOnlySpectator && (UTVehicle(ViewTarget) != None) ) { UTVehicle(ViewTarget).bSpectatedView = true; ViewTarget.CalcCamera( DeltaTime, POVLocation, POVRotation, FOVAngle ); UTVehicle(ViewTarget).bSpectatedView = false; } else { ViewTarget.CalcCamera( DeltaTime, POVLocation, POVRotation, FOVAngle ); } if ( bFreeCamera ) { POVRotation = Rotation; } } //没有相机,没有视图目标 - 由我们负责 else if(PlayerCamera == None) { CalcCamera( DeltaTime, POVLocation, POVRotation, FOVAngle ); return; } //我们有一个相机 - 让相机在控制中 else { POVLocation = PlayerCamera.ViewTarget.POV.Location; POVRotation = PlayerCamera.ViewTarget.POV.Rotation; FOVAngle = PlayerCamera.ViewTarget.POV.FOV; } } // 应用视图振动 POVRotation = Normalize(POVRotation + ShakeRot); POVLocation += ShakeOffset >> Rotation; if( CameraEffect != none ) { CameraEffect.UpdateLocation(POVLocation, POVRotation, GetFOVAngle()); } // 缓存结果 CalcViewActor = ViewTarget; CalcViewActorLocation = ViewTarget.Location; CalcViewActorRotation = ViewTarget.Rotation; CalcViewLocation = POVLocation; CalcViewRotation = POVRotation; if ( P != None ) { CalcEyeHeight = P.EyeHeight; CalcWalkBob = P.WalkBob; } } defaultproperties { CameraClass=class‘UDNExamples.UDNPlayerCamera‘ MatineeCameraClass=class‘UDNExamples.UDNPlayerCamera‘ }
Pawn 类
这个新的 Pawn 类会重载 CalcCamera() 函数,只返回 false,使新的相机系统始终控制相机方位和位置。重载 BecomeViewTarget() 和 GetBaseAimRotation() 函数将它们的功能处理分别传递给相机和控制系统。
UDNPawn.uc
class UDNPawn extends UTPawn; /* BecomeViewTarget 在这个 actor 变为它的 ViewTarget 时会被 Camera 调用 */ simulated event BecomeViewTarget( PlayerController PC ) { local UDNPlayerController UDNPC; UDNPC = UDNPlayerController(PC); //Pawn 由 UDNPlayerController 进行控制,而且有一个 UDNPlayerCamera if(UDNPC != none && UDNPlayerCamera(UDNPC.PlayerCamera) != none) { //允许相机控制网格物体可视性等等。 UDNPlayerCamera(UDNPC.PlayerCamera).BecomeViewTarget(UDNPC); } else { Super.BecomeViewTarget(PC); } } /** * 从这个 pawn 观看时计算相机视点。 * * @param fDeltaTime 自从上次更新开始的 delta 时间(秒) * @param out_CamLoc 相机方位 * @param out_CamRot 相机旋转量 * @param out_FOV 视角 * * @如果 Pawn 应该提供相机视点,那么返回真。 */ simulated function bool CalcCamera( float fDeltaTime, out vector out_CamLoc, out rotator out_CamRot, out float out_FOV ) { //返回 false,允许自定义相机控制它的方位和旋转量 return false; } /** * 会返回没有经过任何调整的基础“瞄准旋转量?”(没有瞄准误差、没有自动锁定、没有附着.. 就是没有用过的初始瞄准旋转量!) * * @返回基本 Aim 旋转量。 */ simulated singular event Rotator GetBaseAimRotation() { local vector POVLoc; local rotator POVRot; local UDNPlayerController PC; PC = UDNPlayerController(Controller); //Pawn 由 UDNPlayerController 进行控制,而且有一个 UDNPlayerCamera if(PC != none && PC.ControlModule != none) { //允许自定义相机控制目标旋转量 return PC.ControlModule.GetBaseAimRotation(); } else { if( Controller != None && !InFreeCam() ) { Controller.GetPlayerViewPoint(POVLoc, POVRot); return POVRot; } else { POVRot = Rotation; if( POVRot.Pitch == 0 ) { POVRot.Pitch = RemoteViewPitch << 8; } return POVRot; } } } defaultproperties { }
GameInfo 类
这个新的 gametype 类是 UTDeathMatch 类的一个基础扩展类,UTDeathMatch 类可以设置要使用的新 HUD、Pawn 和 PlayerController 类。它还可以将 bUseClassicHUD 设置为 True,这样将会使用在这里指定的 HUD 类替换 UTGFxHUDWrapper,在没有设置这个布尔变量的情况下将会硬编码使用 UTGFxHUDWrapper。
UDNGame.uc
class UDNGame extends UTDeathMatch; defaultproperties { DefaultPawnClass=class‘UDNExamples.UDNPawn‘ PlayerControllerClass=class‘UDNExamples.UDNPlayerController‘ MapPrefixes[0]="UDN" }
示例相机模块
在使用新相机框架的示例中,将会安装一个 Top-Down(自上而下)相机。生成一个新的相机模块是实现基础相机模块类中定义的函数的主要问题。如果您已经看过上面的 CalcCamera() 示例,将会觉得很多内容非常熟悉。
UDNCameraModule_TopDown.uc
class UDNCameraModule_TopDown extends UDNCameraModule; var float CamAltitude; //相机偏离玩家的实际高度 var float DesiredCamAltitude; //要将相机移动到的新高度偏移量 var float MaxCamAltitude; //相机可以距离玩家的最大偏移量 var float MinCamAltitude; //相机可以距离玩家的最小偏移量 var float CamZoomIncrement; //每次点击鼠标滑缩放单元格的数量 //计算新的相机位置和旋转量 function UpdateCamera(Pawn P, UDNPlayerCamera CameraActor, float DeltaTime, out TViewTarget OutVT) { //如果不是想要得到的高度,那么插入新的相机偏移量 if(CamAltitude != DesiredCamAltitude) { CamAltitude += (DesiredCamAltitude - CamAltitude) * DeltaTime * 3; } //在高度 (Z) 偏移量上将相机与玩家对齐 OutVT.POV.Location = OutVT.Target.Location; OutVT.POV.Location.Z += CamAltitude; //将相机旋转量设置为向下 OutVT.POV.Rotation.Pitch = -16384; OutVT.POV.Rotation.Yaw = 0; OutVT.POV.Rotation.Roll = 0; } //初始化新的视图目标 simulated function BecomeViewTarget( UDNPlayerController PC ) { if (LocalPlayer(PC.Player) != None) { //将玩家网格物体设置为可视 PC.SetBehindView(true); UDNPawn(PC.Pawn).SetMeshVisibility(PC.bBehindView); PC.bNoCrosshair = true; } } function ZoomIn() { //减少相机高度 DesiredCamAltitude -= CamZoomIncrement; //将相机高度锁定在限制范围内 DesiredCamAltitude = FMin(MaxCamAltitude, FMax(MinCamAltitude, DesiredCamAltitude)); } function ZoomOut() { //增加相机高度 DesiredCamAltitude += CamZoomIncrement; //将相机高度锁定在限制范围内 DesiredCamAltitude = FMin(MaxCamAltitude, FMax(MinCamAltitude, DesiredCamAltitude)); } defaultproperties { CamAltitude=384.0 DesiredCamAltitude=384.0 MaxCamAltitude=1024.0 MinCamAltitude=160.0 CamZoomIncrement=96.0 }
示例控制模块
UDNControlModule_TopDown.uc
class UDNControlModule_TopDown extends UDNControlModule; //计算 Pawn 瞄准目标旋转量 simulated singular function Rotator GetBaseAimRotation() { local rotator POVRot; //Pawn 所面向的目标 - 锁定跨度 POVRot = Controller.Pawn.Rotation; POVRot.Pitch = 0; return POVRot; } //控制自定义玩家运动 function ProcessMove(float DeltaTime, vector NewAccel, eDoubleClickDir DoubleClickMove, rotator DeltaRot) { if( Controller.Pawn == None ) { return; } if (Controller.Role == ROLE_Authority) { // 为远程客户端更新 ViewPitch Controller.Pawn.SetRemoteViewPitch( Controller.Rotation.Pitch ); } Controller.Pawn.Acceleration = NewAccel; Controller.CheckJumpOrDuck(); } //计算控制器旋转 function UpdateRotation(float DeltaTime) { local Rotator DeltaRot, NewRotation, ViewRotation; ViewRotation = Controller.Rotation; //旋转 pawn 面向光标 if (Controller.Pawn!=none) Controller.Pawn.SetDesiredRotation(ViewRotation); DeltaRot.Yaw = Controller.PlayerInput.aTurn; DeltaRot.Pitch = 0; Controller.ProcessViewRotation( DeltaTime, ViewRotation, DeltaRot ); Controller.SetRotation(ViewRotation); NewRotation = ViewRotation; NewRotation.Roll = Controller.Rotation.Roll; if ( Controller.Pawn != None ) Controller.Pawn.FaceRotation(NewRotation, DeltaTime); } defaultproperties { }
配置文件
所有下面的这些文件都应该防止在 UDKgame/Config 目录中。由于某些可能是新添加的目录,所以您需要自己进行创建。其他可以简单地修改使它们可以包括这些新的配置设置。
DefaultCamera.ini 文件应该含有在新相机类中出现的各种配置变量的值。在这个示例中,它只包含设置一个默认的相机模块类。
DefaultCamera.ini
[UDNExamples.UDNPlayerCamera] DefaultCameraClass=UDNExamples.UDNCameraModule_TopDown
DefaultGame.ini 文件应该将 [Engine.GameInfo] 项修改为指向这个新游戏类型,而且它还需要在底部添加一个项来指定要使用的默认控制模块类。
DefaultGame.ini
... [Engine.GameInfo] DefaultGame=UDNExamples.UDNGame DefaultServerGame=UDNExamples.UDNGame ... [UDNExamples.UDNPlayerController] DefaultControlModuleClass=UDNExamples.UDNControlModule_TopDown
只要创建了这些配置和/或使用希望得到的配置设置填充或修改了这些配置后,下次运行游戏或编辑器的时候将会创建 UDKCamera.ini 和 UDKGame.ini 文件。
注意: 为了要使用这个新的游戏类型,您将需要确保您的地图的前缀正确。我们会在我们的游戏类型中将这个前缀设置为"UDN",这样所有地图都将需要以"UDN-"为前缀进行命名。还可以使用编辑器中的地图快速测试这个新的游戏类型,将这个地图的 World Properties(世界属性)中的 Game Type PIE 属性设置为这个新的游戏类型。