一、问题描述

在开发类似飞行类游戏的时候往往需要将相机的旋转范围设置到[-180~180],但引擎默认会将pitch和roll两个轴向限定到[-90~90],这并不是一个bug,是引擎为了适配FPS游戏所做的限制,你可以垂直向上或者向下看,超过这个角度就没有意义了且会导致万向节锁的现象,为了避免这种现象我们可以采用四元数的方法。

二、解决步骤

使用的是飞行模板,创建自己的GameMode和Controller和pawn并在worldSettings中设置为我们的GameMode。如图示1.1(注意Pawn类中要加入Camera组件)

ue_gimbal_lock_image1

1
2

图示1.1

项目设置中添加输入,如图示1.2

ue_gimbal_lock_image2

1
2

图示1.2

接下来是主要的一步,使用四元数的方法解决万向节锁的现象。为了方便使用,我这里写成了一个插件。如图1.3所示,选择模板“BlueprintLibrary”,创建一个插件。

ue_gimbal_lock_image3

1
2

图示1.3

打开VS,将如下代码加入到.h和.cpp中。注意:在cpp中将函数名称“UMyFunctionName”替换为自己的类名称。如图1.4所示

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
public:

// 旋转公式:将欧拉角的度数转换为四元数
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Euler To Quaternion", Keywords = "rotation, quaterion"), Category = "Quaternion Rotation")
static FQuat Euler_To_Quaternion(FRotator Current_Rotation);

// 根据输入的四元数设置组件的世界坐标的旋转
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set World Rotation (Quaterion)", Keywords = "rotation, quaternion"), Category = "Quaternion Rotation")
static void SetWorldRotationQuat(USceneComponent* SceneComponent, const FQuat& Desired_Rotation);

// 根据输入的四元数设置组件的相对旋转
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set Relative Rotation (Quaterion)", Keywords = "rotation, quaternion"), Category = "Quaternion Rotation")
static void SetRelativeRotationQuat(USceneComponent* SceneComponent, const FQuat& Desired_Rotation);

// 添加本地旋转量
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Local Rotation (Quaterion)", Keywords = "rotation, quaternion"), Category = "Quaternion Rotation")
static void AddLocalRotationQuat(USceneComponent* SceneComponent, const FQuat& Delta_Rotation);

// 根据输入的四元数设置Actor的世界坐标的旋转
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set Actor World Rotation (Quaternion)", Keywords = "rotation, quaternion"), Category = "Quaternion Rotation")
static void SetActorWorldRotationQuat(AActor* Actor, const FQuat& Desired_Rotation);

//根据输入的四元数设置Actor的相对旋转
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set Actor Relative Rotation (Quaternion)", Keywords = "rotation, quaternion"), Category = "Quaternion Rotation")
static void SetActorRelativeRotationQuat(AActor* Actor, const FQuat& Desired_Rotation);

// 添加本地旋转量
UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Actor Local Rotation (Quaternion)", Keywords = "rotation, quaternion"), Category = "Quaternion Rotation")
static void AddActorLocalRotationQuat(AActor* Actor, const FQuat& Delta_Rotation);

FQuat UQuaternoinsBPLibrary::Euler_To_Quaternion(FRotator Current_Rotation)
{
//声明输出四元数
FQuat q;

//将度数转换为弧度
float yaw = Current_Rotation.Yaw * PI / 180;
float roll = Current_Rotation.Roll * PI / 180;
float pitch = Current_Rotation.Pitch * PI / 180;

double cy = cos(yaw * 0.5);
double sy = sin(yaw * 0.5);
double cr = cos(roll * 0.5);
double sr = sin(roll * 0.5);
double cp = cos(pitch * 0.5);
double sp = sin(pitch * 0.5);

q.W = cy * cr * cp + sy * sr * sp;
q.X = cy * sr * cp - sy * cr * sp;
q.Y = cy * cr * sp + sy * sr * cp;
q.Z = sy * cr * cp - cy * sr * sp;

return q;
}

void UQuaternoinsBPLibrary::SetWorldRotationQuat(USceneComponent* SceneComponent, const FQuat& Desired_Rotation)
{
if (SceneComponent)
{
SceneComponent->SetWorldRotation(Desired_Rotation);
}
}

void UQuaternoinsBPLibrary::SetRelativeRotationQuat(USceneComponent* SceneComponent, const FQuat& Desired_Rotation)
{
if (SceneComponent)
{
SceneComponent->SetRelativeRotation(Desired_Rotation);
}
}

void UQuaternoinsBPLibrary::AddLocalRotationQuat(USceneComponent* SceneComponent, const FQuat& Delta_Rotation)
{
if (SceneComponent)
{
SceneComponent->AddLocalRotation(Delta_Rotation);
}
}

void UQuaternoinsBPLibrary::SetActorWorldRotationQuat(AActor* Actor, const FQuat& Desired_Rotation)
{
if (Actor)
{
Actor->SetActorRotation(Desired_Rotation);
}
}

void UQuaternoinsBPLibrary::SetActorRelativeRotationQuat(AActor* Actor, const FQuat& Desired_Rotation)
{
if (Actor)
{
Actor->SetActorRelativeRotation(Desired_Rotation);
}
}

void UQuaternoinsBPLibrary::AddActorLocalRotationQuat(AActor* Actor, const FQuat& Delta_Rotation)
{
if (Actor)
{
Actor->AddActorLocalRotation(Delta_Rotation);
}
}
1
2

图示1.4

编译并打开工程,在Controller里面写入如下逻辑,如图1.5所示。这样就可以解决万向节锁的问题。

ue_gimbal_lock_image5

1
2

图示1.5

参考链接 https://www.youtube.com/watch?v=KqbqZ3IY1II&list=PLyu-W38DvZhqOdaCkB4hGWYQzuKH2gALZ&index=2