UE4 RTS 拉选提示框

前言

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

result.png

SelectInputInterface 接口

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

SelectInputInterface.h

接口类不能直接在编辑器中直接添加,所以需要自己新建 .h/.cpp 文件,新建一个 SelectInputInterface.h,内容如下

#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,默认实现两个空的接口函数,内容如下

#include "RTS.h"
#include "SelectInputInterface.h"

void ISelectInputInterface::OnSelectStart()
{

}

void ISelectInputInterface::OnSelectEnd()
{

}

修改 RTSPlayerController

绑定鼠标左键事件

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

bind_select_action.png

PlayerController 中响应鼠标事件

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

UCLASS()
class RTS_API ARTSPlayerController : public APlayerController
{
	// ...

protected:

	virtual void SetupInputComponent() override;

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

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

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

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

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 中实现新增的两个函数

void ARTSCamera::OnSelectStart()
{
	bSelecting = true;
}

void ARTSCamera::OnSelectEnd()
{
	bSelecting = false;
}

修改 UpdateCameraScroll 函数

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

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 中的两个鼠标响应函数中,在这里进行事件触发

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 接口

/**
 * 
 */
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,实现各个接口

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 接口

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,如图

custom_hud.png

最终效果

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