UE4 RTS 拉选提示框

前言

RTS 游戏中,当选取多个单位时会有一个操作,按住鼠标左键后,拖动鼠标,这时候屏幕上会出现一个矩形的范围提示框,当释放鼠标左键后,这个范围内的单位就会被选中,如图所示,本文在前篇 UE4 RTS 风格摄像机 的基础上,实现这个多选提示框

SelectInputInterface 接口

首先定义一个 SelectInputInterface 的接口类,包含鼠标按下和鼠标释放两个接口

SelectInputInterface.h

接口类不能直接在编辑器中直接添加,所以需要自己新建 .h/.cpp 文件,新建一个 SelectInputInterface.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
#pragma once

#include "SelectInputInterface.generated.h"

/**
*
*/

UINTERFACE()
class RTS_API USelectInputInterface : public UInterface
{
GENERATED_BODY()
};

/**
*
*/

class RTS_API ISelectInputInterface
{
GENERATED_BODY()

public:

virtual void OnSelectStart();
virtual void OnSelectEnd();
};
SelectInputInterface.cpp

新建一个 SelectInputInterface.cpp,默认实现两个空的接口函数,内容如下

1
2
3
4
5
6
7
8
9
10
11
12
#include "RTS.h"
#include "SelectInputInterface.h"

void ISelectInputInterface::OnSelectStart()
{

}

void ISelectInputInterface::OnSelectEnd()
{

}

修改 RTSPlayerController

绑定鼠标左键事件

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

PlayerController 中响应鼠标事件

RTSPlayerController.h 中重载 SetupInputComponent 并且增加两个鼠标事件响应函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
UCLASS()
class RTS_API ARTSPlayerController : public APlayerController
{
// ...

protected:

virtual void SetupInputComponent() override;

// 响应鼠标左键按下
void OnSelectStart();

// 响应鼠标左键释放
void OnSelectEnd();
};

RTSPlayerController.cpp 中实现新增的三个函数,目前 OnSelectStartOnSelectEnd 都是空的,之后会逐渐实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void ARTSPlayerController::SetupInputComponent()
{
Super::SetupInputComponent();

check(InputComponent);

InputComponent->BindAction(TEXT("Select"), IE_Pressed, this, &ARTSPlayerController::OnSelectStart);
InputComponent->BindAction(TEXT("Select"), IE_Released, this, &ARTSPlayerController::OnSelectEnd);
}

void ARTSPlayerController::OnSelectStart()
{

}

void ARTSPlayerController::OnSelectEnd()
{

}

修改 RTSCamera

当鼠标开始拖动选择单位时,即使鼠标移动到窗口边缘,也不喜欢触发窗口卷动,不然就会让单位选择更加困难

继承 ISelectInputInterface 接口

修改 RTSCamera 类,增加一个继承 ISelectInputInterface,实现两个接口,并增加一个成员函数 bSelecting

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
UCLASS()
class RTS_API ARTSCamera : public ASpectatorPawn, public ISelectInputInterface
{
GENERATED_BODY()

public:
ARTSCamera();

// 实现 ISelectInputInterface 接口

virtual void OnSelectStart() override;

virtual void OnSelectEnd() override;

// 其他成员函数...

private:

// 其他成员变量...

bool bSelecting;
};

RTSCamera.cpp 中实现新增的两个函数

1
2
3
4
5
6
7
8
9
void ARTSCamera::OnSelectStart()
{
bSelecting = true;
}

void ARTSCamera::OnSelectEnd()
{
bSelecting = false;
}
修改 UpdateCameraScroll 函数

UpdateCameraScroll 函数中判断新增的 bSelecting 标志,只有为 false 的时候才继续判断其他卷动条件

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
bool ARTSCamera::UpdateCameraScroll(float DeltaSeconds)
{
if (bSelecting)
{
return false;
}

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;
}
修改 RTSPlayerController

RTSCamera 中已经实现了 ISelectInputInterface 接口,还需要在鼠标按下和释放时候触发接口类的调用,这时候就要回到之前的 RTSPlayerController 中的两个鼠标响应函数中,在这里进行事件触发

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void ARTSPlayerController::OnSelectStart()
{
// Notify Camera
ISelectInputInterface* SelectInterfaceInstance = Cast<ISelectInputInterface>(GetPawn());
if (SelectInterfaceInstance)
{
SelectInterfaceInstance->OnSelectStart();
}
}

void ARTSPlayerController::OnSelectEnd()
{
// Notify Camera
ISelectInputInterface* SelectInterfaceInstance = Cast<ISelectInputInterface>(GetPawn());
if (SelectInterfaceInstance)
{
SelectInterfaceInstance->OnSelectEnd();
}
}
最终效果

修改完成后,编译代码,运行游戏,移动鼠标到窗口边缘,这时,按下鼠标左键,就会发现窗口停止卷动,当释放鼠标左键时,又会恢复卷动

在屏幕上绘制选择矩形提示框

在编辑器中新建一个 RTSHUD 的类,继承自 HUD

继承 ISelectInputInterface 接口

修改 RTSHUD.h,继承 ISelectInputInterface 接口

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
/**
*
*/

UCLASS()
class RTS_API ARTSHUD : public AHUD, public ISelectInputInterface
{
GENERATED_BODY()

public:
ARTSHUD();

// 矩形提示框填充颜色
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FLinearColor SelectionBoxFillColor;

// 矩形提示框边框颜色
UPROPERTY(EditAnywhere, BlueprintReadWrite)
FLinearColor SelectionBoxBorderColor;

// 矩形提示框边框粗细
UPROPERTY(EditAnywhere, BlueprintReadWrite)
float SelectionBoxBorderThickness;

// 实现 ISelectInputInterface 接口

virtual void OnSelectStart() override;
virtual void OnSelectEnd() override;

protected:

// 重载绘制接口
virtual void DrawHUD() override;

// 矩形提示框更新函数
void UpdateSelectionBox();

private:

// 鼠标左键按下时的窗口坐标
FVector2D StartSelectPosition;

// 当前鼠标窗口坐标
FVector2D CurrentSelectPosition;

// 是否正在绘制矩形提示框
bool bDrawingSelectionBox;
};

修改 RTSHUD.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
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
ARTSHUD::ARTSHUD()
{
// 初始化成员变量
SelectionBoxFillColor = FLinearColor(0.2f, 0.8f, 0.2f, 0.1f);
SelectionBoxBorderColor = FLinearColor(0.08f, 0.55f, 0.06f, 1.f);
SelectionBoxBorderThickness = 1.f;
}

void ARTSHUD::OnSelectStart()
{
// 在鼠标左键按下时,保存下鼠标位置,并置位bDrawingSelectionBox

if (!PlayerOwner->GetMousePosition(StartSelectPosition.X, StartSelectPosition.Y))
{
return;
}

CurrentSelectPosition = StartSelectPosition;
bDrawingSelectionBox = true;
}

void ARTSHUD::OnSelectEnd()
{
// 在鼠标左键释放时,清除鼠标位置,并复位bDrawingSelectionBox

CurrentSelectPosition = StartSelectPosition = FVector2D(0.f, 0.f);
bDrawingSelectionBox = false;
}

void ARTSHUD::DrawHUD()
{
// 在 HUD 绘制回调中绘制矩形提示框

UpdateSelectionBox();

Super::DrawHUD();
}

void ARTSHUD::UpdateSelectionBox()
{
// 先判断当前处在绘制状态,并且当前鼠标位置和起始位置不同

if (bDrawingSelectionBox &&
PlayerOwner->GetMousePosition(CurrentSelectPosition.X, CurrentSelectPosition.Y) &&
StartSelectPosition != CurrentSelectPosition)
{
// 绘制填充矩形
DrawRect(
SelectionBoxFillColor,
StartSelectPosition.X,
StartSelectPosition.Y,
CurrentSelectPosition.X - StartSelectPosition.X,
CurrentSelectPosition.Y - StartSelectPosition.Y);

// 绘制四条边框
DrawLine(StartSelectPosition.X, StartSelectPosition.Y, CurrentSelectPosition.X, StartSelectPosition.Y,
SelectionBoxBorderColor, SelectionBoxBorderThickness);

DrawLine(StartSelectPosition.X, StartSelectPosition.Y, StartSelectPosition.X, CurrentSelectPosition.Y,
SelectionBoxBorderColor, SelectionBoxBorderThickness);

DrawLine(StartSelectPosition.X, CurrentSelectPosition.Y, CurrentSelectPosition.X, CurrentSelectPosition.Y,
SelectionBoxBorderColor, SelectionBoxBorderThickness);

DrawLine(CurrentSelectPosition.X, StartSelectPosition.Y, CurrentSelectPosition.X, CurrentSelectPosition.Y,
SelectionBoxBorderColor, SelectionBoxBorderThickness);
}
}
修改 RTSPlayerController

同样需要再次修改 RTSPlayerController 类的鼠标响应事件,在其中调用 RTSHUD 类的 ISelectInputInterface 接口

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
void ARTSPlayerController::OnSelectStart()
{
// Notify Camera
ISelectInputInterface* SelectInterfaceInstance = Cast<ISelectInputInterface>(GetPawn());
if (SelectInterfaceInstance)
{
SelectInterfaceInstance->OnSelectStart();
}

// Notify HUD
SelectInterfaceInstance = Cast<ISelectInputInterface>(GetHUD());
if (SelectInterfaceInstance)
{
SelectInterfaceInstance->OnSelectStart();
}
}

void ARTSPlayerController::OnSelectEnd()
{
// Notify Camera
ISelectInputInterface* SelectInterfaceInstance = Cast<ISelectInputInterface>(GetPawn());
if (SelectInterfaceInstance)
{
SelectInterfaceInstance->OnSelectEnd();
}

// Notify HUD
SelectInterfaceInstance = Cast<ISelectInputInterface>(GetHUD());
if (SelectInterfaceInstance)
{
SelectInterfaceInstance->OnSelectEnd();
}
}
新建 RTSHUD 蓝图类

编译完成后,在编辑器中新建一个蓝图对象 BP_RTSHUD,继承自 RTSHUD,可以在其中自定义提示框填充颜色,边框颜色,和边框粗细,然后打开 BP_RTSGameMode 蓝图,修改 HUD 对象类为 BP_RTSHUD,如图

最终效果

运行游戏,在屏幕上按住鼠标左键并拖动,就会出现一个浅绿色的矩形提示框