UE4 RTS 风格摄像机

前言

修改历史

  • 2017-4-26 : 简化 UpdateCameraDistanceUpdateCameraPitchDegree 函数计算方式

C++

Camera 基类

在虚幻4中,要实现一个 RTS 风格的摄像机,比较好的方式是从 SpectatorPawn 继承一个类。

新建一个类 ARTSCamera,继承自 ASpectatorPawn,如图

1. 添加 SpringArm 组件

增加一个 USpringArmComponent 成员对象,作为摄像机的绑定对象

1
2
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Camera)
USpringArmComponent* CameraBoom;
2. 添加 Camera 组件

增加一个 UCameraComponent 成员对象

1
2
UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Camera)
UCameraComponent* Camera;
3. 在构造函数中初始化组件成员
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ARTSCamera::ARTSCamera()
{
CameraBoom = CreateDefaultSubobject<USpringArmComponent>("CameraBoom");
CameraBoom->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepRelativeTransform);

// 默认角度
CameraBoom->SetRelativeRotation(FRotator(-60.f, 0, 0));

// 禁止碰撞
CameraBoom->bDoCollisionTest = false;

// 默认距离
CameraBoom->TargetArmLength = 1134.f;

Camera = CreateDefaultSubobject<UCameraComponent>("Camera");
Camera->AttachToComponent(CameraBoom, FAttachmentTransformRules::KeepRelativeTransform);
}
GameMode 基类

新建一个 ARTSGameMode 继承自 AGameModeBase,用来修改游戏中各种对象的类类型参数,添加基础通用代码,目前不需要添加什么代码,如图

PlayerController 基类

新建一个 ARTSPlayerController 继承自 APlayerController,用来修改游戏中控制逻辑,如图

在构造函数中默认打开鼠标的显示

1
2
3
4
ARTSPlayerController::ARTSPlayerController()
{
bShowMouseCursor = true;
}

蓝图类

Camera 蓝图类

新建一个蓝图类 BP_RTSCamera,继承自基类 ARTSCamera,可以在这里自定义摄像机参数,如角度,距离等

PlayerController 蓝图类

新建一个蓝图类 BP_RTSPlayerController,继承自基类 ARTSPlayerController,可以在这里自定义控制逻辑,比如隐藏鼠标指针

GameMode 蓝图类

新建一个蓝图类 BP_RTSGameMode 继承自 ARTSGameMode,用来做自定义的修改

应用蓝图类

双击打开 BP_RTSGameMode,将 Classes->Default Pawn Class 修改为 BP_RTSCamera。 将 Classes -> Player Controller Class 改为 BP_RTSPlayerController。这样在使用这个 GameMode 的地图运行时,就会用 BP_RTSCamera 来创建默认的 Pawn 对象。 并且将 BP_RTSPlayerController 对象作为玩家控制器。如图

在地图编辑器中,选择 Settings -> World Settings,在界面右边的 World Settings 标签中,将 Game Mode -> GameMode Override 修改为 BP_RTSGameMode。这样,当这个地图开始运行时,就会使用 BP_RTSGameMode 对象作为当前的游戏模式,如图

修改完成后,点击 Play 按钮,就会以第三人称视角观察游戏世界,如图

摄像机控制

屏幕卷动控制
窗口卷动方向枚举

RTSCamera.h 头文件中增加窗口卷动方向的枚举类型

1
2
3
4
5
6
7
8
9
UENUM()
enum class ECameraScrollDirection
{
None,
Top,
Bottom,
Left,
Right,
};
增加控制窗口是否卷动的摄像机和窗口边缘距离值变量

RTSCamera.h 中增加成员变量,当鼠标和窗口边缘的距离小于这个值的时候,窗口就开始卷动

1
2
3
// 触发窗口卷动的鼠标和窗口边缘距离值
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Camera)
int32 ScrollingSize;

在构造函数中初始化

1
2
3
4
5
6
7
ARTSCamera::ARTSCamera()
{
// 其他代码...

// 默认距离为20像素
ScrollingSize = 20.f;
}
增加获取窗口卷动方向的接口

RTSCamera.h 中增加成员函数

1
2
UFUNCTION(BlueprintCallable, Category = Camera)
ECameraScrollDirection GetCameraScrollDirection();

RTSCamera.cpp 中实现

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
ECameraScrollDirection ARTSCamera::GetCameraScrollDirection()
{
APlayerController* PlayerController = Cast<APlayerController>(Controller);
if (!PlayerController)
{
return ECameraScrollDirection::None;
}

// 获取当前鼠标位置
float MouseX = 0.f, MouseY = 0.f;
if (!PlayerController->GetMousePosition(MouseX, MouseY))
{
return ECameraScrollDirection::None;
}

// 获取当前窗口分辨率
int32 SizeX = 0, SizeY = 0;
PlayerController->GetViewportSize(SizeX, SizeY);

// 根据鼠标位置判断窗口的卷动方向
if (MouseY < ScrollingSize)
{
return ECameraScrollDirection::Top;
}

if (MouseY > SizeY - ScrollingSize)
{
return ECameraScrollDirection::Bottom;
}

if (MouseX < ScrollingSize)
{
return ECameraScrollDirection::Left;
}

if (MouseX > SizeX - ScrollingSize)
{
return ECameraScrollDirection::Right;
}

return ECameraScrollDirection::None;
}
增加更新窗口卷动的接口

RTSCamera.h 中增加成员函数来更新窗口卷动

1
2
protected:
bool UpdateCameraScroll(float DeltaSeconds);

RTSCamera.cpp 中实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
bool ARTSCamera::UpdateCameraScroll(float DeltaSeconds)
{
const ECameraScrollDirection ScrollDirection = GetCameraScrollDirection();

switch (ScrollDirection)
{
case ECameraScrollDirection::Top:
AddMovementInput(FVector::ForwardVector);
break;
case ECameraScrollDirection::Bottom:
AddMovementInput(-FVector::ForwardVector);
break;
case ECameraScrollDirection::Left:
AddMovementInput(-FVector::RightVector);
break;
case ECameraScrollDirection::Right:
AddMovementInput(FVector::RightVector);
break;
default:
break;
}

return ScrollDirection != ECameraScrollDirection::None;
}
重载帧更新接口

重载 ARTSCamera 的帧更新接口,在其中调用 UpdateCameraScroll 函数

1
2
protected:
virtual void Tick(float DeltaSeconds) override;

实现

1
2
3
4
5
6
void ARTSCamera::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);

UpdateCameraScroll(DeltaSeconds);
}
运行效果

编译程序,运行游戏,当鼠标和窗口边缘间的距离小于预设值后,窗口就会朝着响应的方向卷动,这个预设值可以在 BP_RTSCamera 蓝图中修改 ScrollingSize 属性来调整

鼠标滚轮放大/缩小摄像机距离
增加成员变量

ARTSCamera.h 中增加以下成员变量,并重载 BeginPlay

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
public:

// 最小距离
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Distance")
float MinDistance;

// 最大距离
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Distance")
float MaxDistance;

// 距离变化速度
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Distance")
float DistanceChangeSpeed;

// 每次鼠标滚轮事件修改的距离值
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Distance")
float DistanceChangeDelta;

protected:

virtual void BeginPlay() override;

private:

// 当前距离
float CurrentDistance;

// 最终距离
float DesiredDistance;

在构造函数和 BeginPlay 中初始化成员变量

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
ARTSCamera::ARTSCamera()
{
// 默认最小距离为800
MinDistance = 800.f;

// 默认最大距离为1134
MaxDistance = 1134.f;

// 默认距离变化速度为每秒334
DistanceChangeSpeed = 334.f;

// 默认每次修改距离为167,167 / 334 = 0.5s,每滚动一下滚轮,从当前距离变化到最终距离要花0.5秒
DistanceChangeDelta = 167.f;
CurrentDistance = MaxDistance;
DesiredDistance = CurrentDistance;

// 其他代码...

CameraBoom->TargetArmLength = CurrentDistance;
}

void ARTSCamera::BeginPlay()
{
Super::BeginPlay();

CurrentDistance = MaxDistance;
DesiredDistance = CurrentDistance;

CameraBoom->TargetArmLength = CurrentDistance;
}
增加距离帧更新函数
1
2
protected:
void UpdateCameraDistance(float DeltaSeconds);

RTSCamera.cpp 中实现,并在帧更新函数中调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void ARTSCamera::UpdateCameraDistance(float DeltaSeconds)
{
if (CurrentDistance != DesiredDistance)
{
CurrentDistance = FMath::FInterpConstantTo(CurrentDistance, DesiredDistance, DeltaSeconds, DistanceChangeSpeed);

// 设置摄像机距离
CameraBoom->TargetArmLength = CurrentDistance;
}
}

void ARTSCamera::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);

UpdateCameraScroll(DeltaSeconds);
UpdateCameraDistance(DeltaSeconds);
}
鼠标滚轮放大/缩小摄像机角度
增加成员变量

ARTSCamera.h 中增加以下成员变量

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
public:

// 最小角度
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Degree")
float MinPitchDegree;

// 最大角度
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Degree")
float MaxPitchDegree;

// 角度变化速度
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Degree")
float PitchDegreeChangeSpeed;

// 每次鼠标滚轮事件角度变化量
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera|Degree")
float PitchDegreeChangeDelta;

private:

// 当前角度
float CurrentPitchDegree;

// 最终角度
float DesiredPitchDegree;

在构造函数和 BeginPlay 中初始化成员变量

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
ARTSCamera::ARTSCamera()
{
MinPitchDegree = -60.f;
MaxPitchDegree = -38.f;
PitchDegreeChangeSpeed = 22.f;

// 默认每次修改角度11度,11 / 22 = 0.5s,每滚动一下滚轮,从当前角度变化到最终角度要花0.5秒
PitchDegreeChangeDelta = 11.f;
CurrentPitchDegree = MinPitchDegree;
DesiredPitchDegree = CurrentPitchDegree;

// 其他代码...
CameraBoom->SetRelativeRotation(FRotator(CurrentPitchDegree, 0.f, 0.f));
}

void ARTSCamera::BeginPlay()
{
Super::BeginPlay();

CurrentDistance = MaxDistance;
DesiredDistance = CurrentDistance;

CurrentPitchDegree = MinPitchDegree;
DesiredPitchDegree = CurrentPitchDegree;

CameraBoom->SetRelativeRotation(FRotator(CurrentPitchDegree, 0.f, 0.f));
CameraBoom->TargetArmLength = CurrentDistance;
}
增加角度帧更新函数
1
2
protected:
void UpdateCameraPitchDegree(float DeltaSeconds);

RTSCamera.cpp 中实现,并在帧更新函数中调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void ARTSCamera::UpdateCameraPitchDegree(float DeltaSeconds)
{
if (CurrentPitchDegree != DesiredPitchDegree)
{
CurrentPitchDegree = FMath::FInterpConstantTo(CurrentPitchDegree, DesiredPitchDegree, DeltaSeconds, PitchDegreeChangeSpeed);

// 设置摄像机角度
CameraBoom->SetRelativeRotation(FRotator(CurrentPitchDegree, 0.f, 0.f));
}
}

void ARTSCamera::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);

UpdateCameraScroll(DeltaSeconds);
UpdateCameraDistance(DeltaSeconds);
UpdateCameraPitchDegree(DeltaSeconds);
}
滚轮输入事件响应
增加滚轮输入事件

打开 Edit -> Project Settings... -> Engine -> Input,增加两个事件,如图

绑定事件

RTSCamera.h 中重载 SetupPlayerInputComponent 并增加两个事件响应函数

1
2
3
4
5
6
protected:

void OnZoomIn();
void OnZoomOut();

virtual void SetupPlayerInputComponent(UInputComponent* InInputComponent) override;

RTSCamera.cpp 中实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void ARTSCamera::OnZoomIn()
{
DesiredDistance = FMath::Clamp(CurrentDistance - DistanceChangeDelta, MinDistance, MaxDistance);
DesiredPitchDegree = FMath::Clamp(CurrentPitchDegree + PitchDegreeChangeDelta, MinPitchDegree, MaxPitchDegree);
}

void ARTSCamera::OnZoomOut()
{
DesiredDistance = FMath::Clamp(CurrentDistance + DistanceChangeDelta, MinDistance, MaxDistance);
DesiredPitchDegree = FMath::Clamp(CurrentPitchDegree - PitchDegreeChangeDelta, MinPitchDegree, MaxPitchDegree);
}

void ARTSCamera::SetupPlayerInputComponent(UInputComponent* InInputComponent)
{
check(InInputComponent);

InInputComponent->BindAction(TEXT("ZoomIn"), IE_Pressed, this, &ARTSCamera::OnZoomIn);
InInputComponent->BindAction(TEXT("ZoomOut"), IE_Pressed, this, &ARTSCamera::OnZoomOut);
}

结束

最后运行游戏,会得到一个典型的 RTS 风格的第三人称视角的摄像机,可以放大缩小视野,可以在窗口边缘卷动窗口

附件