UE4 RTS 风格摄像机

前言

修改历史

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

C++

Camera 基类

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

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

new_spectator_class.png

1. 添加 SpringArm 组件

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

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

增加一个 UCameraComponent 成员对象

UPROPERTY(VisibleAnywhere, BlueprintReadWrite, Category = Camera)
UCameraComponent* Camera;
3. 在构造函数中初始化组件成员
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,用来修改游戏中各种对象的类类型参数,添加基础通用代码,目前不需要添加什么代码,如图

new_gamemodebase_class.png

PlayerController 基类

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

new_player_controller_class.png

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

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 对象作为玩家控制器。如图

change_default_pawn_class.png

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

change_game_mode_class.png

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

third_person_view.png

摄像机控制

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

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

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

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

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

在构造函数中初始化

ARTSCamera::ARTSCamera()
{
	// 其他代码...

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

RTSCamera.h 中增加成员函数

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

RTSCamera.cpp 中实现

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 中增加成员函数来更新窗口卷动

protected:
	bool UpdateCameraScroll(float DeltaSeconds);

RTSCamera.cpp 中实现

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 函数

protected:
	virtual void Tick(float DeltaSeconds) override;

实现

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

	UpdateCameraScroll(DeltaSeconds);
}
运行效果

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

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

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

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 中初始化成员变量

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;
}

增加距离帧更新函数
protected:
	void UpdateCameraDistance(float DeltaSeconds);

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

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 中增加以下成员变量

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 中初始化成员变量

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;
}

增加角度帧更新函数
protected:
	void UpdateCameraPitchDegree(float DeltaSeconds);

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

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,增加两个事件,如图

add_action.png

绑定事件

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

protected:

	void OnZoomIn();
	void OnZoomOut();

	virtual void SetupPlayerInputComponent(UInputComponent* InInputComponent) override;

RTSCamera.cpp 中实现

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 风格的第三人称视角的摄像机,可以放大缩小视野,可以在窗口边缘卷动窗口

附件