【UE4】Camera Shake 震屏

2022-08-04,,

  本文使用的引擎版本是 UE 4.25.1,使用 “Third Person” 模板。

一、创建 Camera Shake

  步骤如下:

  1. 在 ThirdPersonBP 的 Blueprints 文件夹中创建一个 “Blueprint Class”,选择 Parent Class 为 “CameraShake”,命名为 “OnCamShake”,里边的参数设置如下,其中

"Oscillation Duartion" 是震动时间,
PitchYawRoll分别对应摄像机绕着自己的 Y、Z、X 轴的震动,X 轴的震动表现就是吃鸡里边的 左右探头,一般的镜头震动不会使用这个;镜头震动还有X、Y可以设置,这会使得镜头在世界坐标下发生坐标变化,所以没有使用;

"Ampplitude" 是振幅;
"Frequency" 是震动频率,越高震动越快,为了展示效果,设置的很低;
"Initial Offset" 是初始偏移,不用,设为 0;
"Waveform" 是波形,有两个选项:Sine Wave 和 Perlin Noise,对应有规律的正弦震荡以及没有规律的噪声震荡;

  1. "Project Settings" 里添加 Input,叫 “CameraShake”,绑定键盘 C 键;
  2. 打开 ThirdPersonCharacter 蓝图,右键添加 “CameraShake” 这个 Actions Event;
  3. 右键添加 “Get Player Controller”,从 Return Value 拖出 “Client Play Camera Shake”,从 “InputAction CameraShake” 的 “Pressed” 连接到 “Client Play Camera Shake”;“Shake” 中选择刚刚创建的 “OnCamShake”
  1. Compile & Save 之后就可以 Play 了,按 C 看到效果如下:

二、根据角色朝向调整 Camera Shake 方向

  从上图可以看到因为选择了 Yaw、Pitch方向的震动,且设的 “Ampplitude” 是正值,所以镜头先向右上方起振,且由于震动频率设的是一样的,所以不会出现震着震着乱了的情况;
  但是有的时候我们需要根据主角的朝向来调整起震方向,比如第三人称游戏,主角挥刀,主角右手持刀,从右上方砍到左下方,这是镜头先向左下方起震,然后在左下-右上之间震荡;这种情况在主角背对摄像机的时候是正确的,但是在主角正对镜头的时候,就不对了,因为这时候主角的右上,对应镜头的左上,主角从右上砍到左下不变,镜头的震动方式应该变为:向右下方起震,然后在右下-左上之间震荡,这样才能对应上主角的挥刀。
  可以看到上边的蓝图中,只用三个参数,一个是 target,一个是震动类,一个是震动大小Scale,代码中调用就是

PlayerController->ClientPlayCameraShake(CameraShake, ShakeScale);

  后两个参数用的是默认值,“Play Space” 是指播放空间,是个枚举类型,有三个值可选:1. “Camera Local”、2. “World”、3. “User Defined”
  默认 “Camera Local”,也就是按照摄像机的局部坐标进行CameraShake,所以我们现在的设置下,无论摄像机怎么转动,主角怎么移动怎么转动,镜头震动效果是不会变的,因为摄像机在自己的局部坐标下没有动;
  “World” 是根据镜头在世界坐标下的转动进行CameraShake,刚才的其他设置不变,选为 “World” 之后的效果如下:

  可以看到,在主角背对摄像机的时候,是右上–>左下;主角正对摄像机的时候,是右下–>左上;这两种情况都没有问题,但是,在主角侧身对着摄像机的时候,镜头震动就变成了左右摇头;这是因为摄像机在世界坐标下,绕着Z轴旋转了90度,原来配置的 Pitch,就变成了 Roll,这样的效果从逻辑上是没有错的,但是从效果上不太好,如果不想要摇头的效果,只想根据主角朝向调整CameraShake的起震方向,可以用第三个类型;
  “User Defined”,用户定义,这样自由度大了很多,但是理解起来会麻烦一些,首先,这里只有当选成自定义后,最后一个参数 "User Play Space Rot"才可用,从Camera中 GetWorldRotation 然后传给 “User Play Space Rot” 和直接使用 “CameraLocal” 效果是一模一样的,理解了这个后边的就好说了。

  当主角的 Forward 和摄像机的 Forward 的朝向相同时,就用 Camera 的 WorldRotation,当朝向相反时,用Camera 的 WorldRotation,将 Roll 和 Yaw 的转动都加上 180° 之后,才是想要的效果(判断朝向的时候,用的是向量投影到XY平面上之后的点乘):

  代码中C++实现如下:

const FVector CameraForward = PlayerController->PlayerCameraManager->GetActorForwardVector();
const FVector CharacterForward = Character->GetActorForwardVector();

FVector2D CameraForward2D = FVector2D(CameraForward.X, CameraForward.Y);
FVector2D CharacterForward2D = FVector2D(CharacterForward.X, CharacterForward.Y);
CameraForward2D = CameraForward2D.GetSafeNormal();
CharacterForward2D = CharacterForward2D.GetSafeNormal();

const float Cosine = FVector2D::DotProduct(CameraForward2D, CharacterForward2D);
FRotator CameraRotation = PlayerController->PlayerCameraManager->GetCameraRotation();
if (Cosine < 0)
{
	CameraRotation.Roll += 180;
	CameraRotation.Yaw += 180;
}
PlayerController->ClientPlayCameraShake(CameraShake, 1.0f, ECameraAnimPlaySpace::UserDefined, CameraRotation);

三、Camera Shake 开始、叠加、停止

  值得注意的是,PlayerController->ClientPlayCameraShake 这个函数传入的第一个参数是 TSubclassOf<class UCameraShake> Shake,也就是传入的是类,不是实例,进入函数中:

void APlayerController::ClientPlayCameraShake_Implementation( TSubclassOf<class UCameraShake> Shake, float Scale, ECameraAnimPlaySpace::Type PlaySpace, FRotator UserPlaySpaceRot )
{
	if (PlayerCameraManager != NULL)
	{
		PlayerCameraManager->PlayCameraShake(Shake, Scale, PlaySpace, UserPlaySpaceRot);
	}
}

  再往下:

UCameraShake* APlayerCameraManager::PlayCameraShake(TSubclassOf<UCameraShake> ShakeClass, float Scale, ECameraAnimPlaySpace::Type PlaySpace, FRotator UserPlaySpaceRot)
{
	if (ShakeClass && CachedCameraShakeMod && (Scale > 0.0f) )
	{
		return CachedCameraShakeMod->AddCameraShake(ShakeClass, FAddCameraShakeParams(Scale, PlaySpace, UserPlaySpaceRot));
	}
	return nullptr;
}

  AddCameraShake 代码比较长,里边就是对 CameraShake进行实例化,并且用 ActiveShakes.Emplace(ShakeInfo); 进行添加,CameraShake 有一个 array,来记录当前正在进行的震屏,可以通过 GetActiveCameraShakes(TArray<FActiveCameraShakeInfo>& ActiveCameraShakes) 来获取。
  值得注意的是,同一个类,在没有选择单例的情况下,是可以用时实例化很多个实例的,也就是可以叠加,如果震屏发生时间几乎同时,那么看起来的效果就是震动强度变大,如果发生的时间交错,但又在同一时间段内,效果就无法预期了,可能会非常混乱。且如果震动叠加,可能会因为震动过多而导致效果过强,看起来很晕。
  所以在 CameraShake的类中,有一个选项是 “Single Instance”,代码中的注释是:

/** 
	 *  If true to only allow a single instance of this shake class to play at any given time.
	 *  Subsequent attempts to play this shake will simply restart the timer.
*/

  也就是同一时间用一个 CameraShake 类只能有一个实例,勾选上这个就可以解决震动叠加导致的过强,过着混乱的问题了。
  这样的好处是直接,简单,但是问题是有的 CameraShake 类有时候想叠加,有时候不想叠加,那就不能勾选这个,但是可以在代码中,不想震屏的时候,先调用下边函数:

	PlayerController->ClientStopCameraShake(CameraShakeDefine.CameraShake, true);

  这个函数定义如下:

void APlayerController::ClientStopCameraShake_Implementation( TSubclassOf<class UCameraShake> Shake, bool bImmediately )
{
	if (PlayerCameraManager != NULL)
	{
		PlayerCameraManager->StopAllInstancesOfCameraShake(Shake, bImmediately);
	}
}

  这个函数会把当前这个 CameraShake 类的所有实例全部删掉,这样再调用 PlayCameraShake 就不会出现叠加的情况了,需要叠加的时候,不stop已经创建的实例就好了。

本文地址:https://blog.csdn.net/Bob__yuan/article/details/107322305

《【UE4】Camera Shake 震屏.doc》

下载本文的Word格式文档,以方便收藏与打印。