8. UE5 RPG创建UI(上)

UI是显示角色的一部分属性玩家可以直接查看的界面,通过直观的形式在屏幕上显示角色的各种信息。如何使用一种可扩展,可维护的形式来制作,这不得不说到耳熟能详的MVC架构。
MVC(Model-View-Controller)是一种常见的软件架构模式,用于组织和设计应用程序。它将应用程序分为三个逻辑层:模型(Model)、视图(View)和控制器(Controller)。

  1. 模型(Model)层: 主要处理数据相关的内容,和数据库进行读取,写入,更新,删除等操作。并定义了操作和访问这些数据的方法。
  2. 视图(View)层: 视图层负责展示模型层的数据给用户,并接收用户的输入。它是用户界面的一部分,负责展示信息、呈现数据、收集用户输入等。视图层通常是根据模型层的数据来动态生成的,以便用户可以直观地与数据进行交互。
  3. 控制器(Controller)层: 控制器层接收用户的输入,并根据输入调度和处理请求。它负责处理用户与应用程序的交互逻辑,决定如何更新模型层数据和选择合适的视图层。控制器将用户的请求转发给模型层进行处理,并在完成后更新视图层以显示结果。

通过MVC的方式,我们将各部分模块化分工,将关注点分离,提高应用程序的可维护性,可扩展性和可测试性。模型层只需将相应的接口提供,不需要知道控制层如何如使用它,也不需要知道视图层如何显示它。控制器层去模型层交互获取数据,然后通过广播的形式将消息散发出去,视图层只需要接受广播消息更新数据。形成一种单向依赖关系。

创建基础类

理论的东西说完了,开始进入实际操作中,首先,我需要创建用于显示的基础ui部件类
8. UE5 RPG创建UI(上)_第1张图片
新创建一个UserWidget(用户控件)类
8. UE5 RPG创建UI(上)_第2张图片
接着创建一个基础的Object类,用于作为控制器层。
8. UE5 RPG创建UI(上)_第3张图片
这个类被用来作为用户控件的控制器层。

用户控件代码:

	UPROPERTY(BlueprintReadOnly) //蓝图可读取,但不能修改
	TObjectPtr<UObject> WidgetController;

首先增加了一个参数,用于获取存储控制器层对象。

protected:
	UFUNCTION(BlueprintImplementableEvent) //c++里不能定义,可以调用,蓝图中 无返回值可作为通知,有返回值还可以覆盖重写
	void WidgetControllerSet();

添加一个可以在蓝图里面使用的函数作为控制器对象修改后的回调。

	UFUNCTION(BlueprintCallable) //蓝图可调用
	void SetWidgetController(UObject* InWidgetController);

增加一个设置控制器层的方法,可以在蓝图调用去修改控制器。

void UMyUserWidget::SetWidgetController(UObject* InWidgetController)
{
	WidgetController = InWidgetController;
	WidgetControllerSet();
}

在cpp中函数的实现就是赋值,然后调用回调。具体设置会在蓝图中设置。

MyUserWidget.h

// 版权归暮志未晚所有。

#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "MyUserWidget.generated.h"

/**
 * 
 */
UCLASS()
class AURA_API UMyUserWidget : public UUserWidget
{
	GENERATED_BODY()

public:
	UFUNCTION(BlueprintCallable) //蓝图可调用
	void SetWidgetController(UObject* InWidgetController);

	UPROPERTY(BlueprintReadOnly) //蓝图可读取,但不能修改
	TObjectPtr<UObject> WidgetController;

protected:
	UFUNCTION(BlueprintImplementableEvent) //c++里不能定义,可以调用,蓝图中 无返回值可作为通知,有返回值还可以覆盖重写
	void WidgetControllerSet();
};

MyUserWidget.cpp

// 版权归暮志未晚所有。


#include "UI/Widget/MyUserWidget.h"

void UMyUserWidget::SetWidgetController(UObject* InWidgetController)
{
	WidgetController = InWidgetController;
	WidgetControllerSet();
}

在WidgetController里面,需要去获取model层的数据。数据在哪,在角色的playerController,playerState,ASC和AS身上。
MyWidgetController.h

// 版权归暮志未晚所有。

#pragma once

#include "CoreMinimal.h"
#include "UObject/NoExportTypes.h"
#include "MyWidgetController.generated.h"

class UAttributeSet;
class UAbilitySystemComponent;

/**
 * 
 */
UCLASS()
class AURA_API UMyWidgetController : public UObject
{
	GENERATED_BODY()

protected:

	UPROPERTY(BlueprintReadOnly, Category="WidgetController")
	TObjectPtr<APlayerController> PlayerController;

	UPROPERTY(BlueprintReadOnly, Category="WidgetController")
	TObjectPtr<APlayerState> PlayerState;

	UPROPERTY(BlueprintReadOnly, Category="WidgetController")
	TObjectPtr<UAbilitySystemComponent> AbilitySystemComponent;

	UPROPERTY(BlueprintReadOnly, Category="WidgetController")
	TObjectPtr<UAttributeSet> AttributeSet;
	
};

创建进度条

我们创建进度条用于显示角色的血量变化。
8. UE5 RPG创建UI(上)_第4张图片
创建目录ProgressBar,用于存放进度条蓝图
8. UE5 RPG创建UI(上)_第5张图片
文件夹内,选择创建控件蓝图
8. UE5 RPG创建UI(上)_第6张图片
基类选择创建的c++类
8. UE5 RPG创建UI(上)_第7张图片
将ui尺寸调整成所需(当前用户控件需要多少,大小就是多少)
8. UE5 RPG创建UI(上)_第8张图片
添加一个尺寸框
8. UE5 RPG创建UI(上)_第9张图片
为了可以自定义每个用户控件的大小,设置高度和宽度重载。并将其勾选是变量。
8. UE5 RPG创建UI(上)_第10张图片
打开图标,可以在左侧查看到尺寸框的变量,我们接着创建两个变量用来更新尺寸框的尺寸。编译后的尺寸框可以修改宽高。
8. UE5 RPG创建UI(上)_第11张图片
EventPreConstruct相当于构造函数,在构造时,我们只需要通过变量修改尺寸框的高和宽,实现对每个添加的用户控件进行尺寸修改。
8. UE5 RPG创建UI(上)_第12张图片
在尺寸框下面添加一个覆层,覆层下面的物体可以重叠在一起。然后覆层下面添加一个图像作为背景图片。
8. UE5 RPG创建UI(上)_第13张图片
修改图片的水平对齐和垂直对齐
8. UE5 RPG创建UI(上)_第14张图片
接下来,创建一个变量,用来在初始化时修改背景的图像,这里需要用到Slate笔刷。
8. UE5 RPG创建UI(上)_第15张图片
初始化后链接使用SetBrush节点,去使用变量修改背景图片的brush。
8. UE5 RPG创建UI(上)_第16张图片
设置变量的默认值。
8. UE5 RPG创建UI(上)_第17张图片
会发现图片被成功的渲染出来。
8. UE5 RPG创建UI(上)_第18张图片
我们可以选中右键,折叠到函数,将函数折叠起来。
8. UE5 RPG创建UI(上)_第19张图片
接着在覆层下面添加一个进度条,这个进度条用来表示
8. UE5 RPG创建UI(上)_第20张图片
样式这边,添加上对应的材质或者图像。
8. UE5 RPG创建UI(上)_第21张图片
修改进度的百分比,可以实现修改进度。
8. UE5 RPG创建UI(上)_第22张图片
8. UE5 RPG创建UI(上)_第23张图片

但是你会发现颜色不对,是纯黑,需要在外观这里,将所有值都修改为1,颜色表现就对了。
8. UE5 RPG创建UI(上)_第24张图片
但是颜色却是正方形,需要将填充图绘制为图像,就可以正确显示图像里面效果。
8. UE5 RPG创建UI(上)_第25张图片
将背景图的着色的A值设置为0,可以去掉背景。
8. UE5 RPG创建UI(上)_第26张图片
进度过渡我们将条填充类型改为底到顶,实现了从下往上填充。
8. UE5 RPG创建UI(上)_第27张图片
我们想通过变量去修改进度条的样式,所以将进度条设置为变量,并设置样式。
8. UE5 RPG创建UI(上)_第28张图片
将样式拖出,选择Make ProgressBarStyle创建一个新的样式。
8. UE5 RPG创建UI(上)_第29张图片

8. UE5 RPG创建UI(上)_第30张图片
创建的样式节点也就对应四个参数。
8. UE5 RPG创建UI(上)_第31张图片
前面设置,我们将背景颜色的着色的alpha设置为0, 那么我们也需要在节点这里修改透明度。
8. UE5 RPG创建UI(上)_第32张图片
将填充图像提升为变量,我们可以通过变量修改,来修改填充图像。
8. UE5 RPG创建UI(上)_第33张图片
我们需要修改填充让进度条大小小一些,防止遮挡住背景图像。通过修改覆层插槽的填充来实现。
8. UE5 RPG创建UI(上)_第34张图片
先将插槽转换成覆层的插槽,然后设置它的填充,并设置一个变量来可以修改当前的padding的值。
8. UE5 RPG创建UI(上)_第35张图片
接着在覆层下再添加一个图像,用于在血量图像上次覆盖一层玻璃瓶的效果。
8. UE5 RPG创建UI(上)_第36张图片
通过Brush去设置图像
8. UE5 RPG创建UI(上)_第37张图片
稍微调整一下填充,来实现晶莹剔透的效果。如果你感觉玻璃瓶的亮度太高,可以通过调整它的透明度,降低亮度。
8. UE5 RPG创建UI(上)_第38张图片
现在这个可以作为药瓶基类,创建血量和蓝量显示ui。
8. UE5 RPG创建UI(上)_第39张图片
在右侧属性这里,修改图像,即可实现了两个用户控件的创建。

添加到屏幕

8. UE5 RPG创建UI(上)_第40张图片
创建一个新的用户控件,控件基类为创建的UMyUserWidget 基类,这个控件会覆盖整个屏幕,作为ui的根。
8. UE5 RPG创建UI(上)_第41张图片
添加一个画布面板
8. UE5 RPG创建UI(上)_第42张图片
将血量显示控件拖入到场景内,并修改锚点到中间下方。
8. UE5 RPG创建UI(上)_第43张图片
打开关卡蓝图
8. UE5 RPG创建UI(上)_第44张图片
在事件开始时,输入create Widget,创建控件节点,类设置为创建的WBP_Overlay。
8. UE5 RPG创建UI(上)_第45张图片
使用Add Viewport添加到视口

可以看到已经能够在ui上面显示,但是有些大。
8. UE5 RPG创建UI(上)_第46张图片
将属性右侧的眼睛打开,可以将私有变量设置为公有变量。

给用户控件添加控制器层

当前角色血量和蓝量显示的用户控件我们已经制作完成,接下来要实现对用户控件显示的更新,基于MVC,我们需要实现控制器层,将View和Model链接起来,前面我们已经创建了控制器层类。接下来,我们将继续更新它能够使用。

USTRUCT(BlueprintType)
struct FWidgetControllerParams
{
	GENERATED_BODY()
	FWidgetControllerParams(){}
	FWidgetControllerParams(APlayerController* PC, APlayerState* PS, UAbilitySystemComponent* ASC, UAttributeSet* AS)
	: PlayerController(PC),
	PlayerState(PS),
	AbilitySystemComponent(ASC),
	AttributeSet(AS)
	{}

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	TObjectPtr<APlayerController> PlayerController = nullptr;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	TObjectPtr<APlayerState> PlayerState = nullptr;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	TObjectPtr<UAbilitySystemComponent> AbilitySystemComponent = nullptr;

	UPROPERTY(EditAnywhere, BlueprintReadWrite)
	TObjectPtr<UAttributeSet> AttributeSet = nullptr;
};

在控制器类里,增加了一个结构体,我们后续通过结构体设置控制器层的相关引用。结构体构造函数小括号后面加:可以直接对属性进行初始化。

public:
	UFUNCTION(BlueprintCallable)
	void SetWidgetControllerParams(const FWidgetControllerParams& WCParams);

控制器层类增加一个蓝图调用函数,通过蓝图调用去设置控制器层的相关参数。

void UMyWidgetController::SetWidgetControllerParams(const FWidgetControllerParams& WCParams)
{
	PlayerController = WCParams.PlayerController;
	PlayerState = WCParams.PlayerState;
	AbilitySystemComponent = WCParams.AbilitySystemComponent;
	AttributeSet = WCParams.AttributeSet;
}

函数实现是直接设置对应参数。
8. UE5 RPG创建UI(上)_第47张图片
然后创建基于此控制层类创建一个子类,后续作为HUD的控制器层,它可以作为一个单例使用。

创建HUD

HUD是管理UI的基类,我在类中将管理用户控件和用户控件的控制器层内容。
8. UE5 RPG创建UI(上)_第48张图片

之前直接在关卡蓝图里面将控件添加到视口的方式只是用来测试。正常我们需要赋值HUD类的方式去实现对UI的添加。
8. UE5 RPG创建UI(上)_第49张图片
父类选择HUD
8. UE5 RPG创建UI(上)_第50张图片
创建一个HUD类

public:

	UPROPERTY()
	TObjectPtr<UMyUserWidget> OverlayWidget;

在HUD代码中,创建一个公有属性用于存储实例化后的用户组件

private:

	UPROPERTY(EditAnywhere)
	TSubclassOf<UMyUserWidget> OverlayWidgetClass;

	UPROPERTY()
	TObjectPtr<UOverlayWidgetController> OverlayWidgetController;

	UPROPERTY(EditAnywhere)
	TSubclassOf<UOverlayWidgetController> OverlayWidgetControllerClass;

创建一个私有的用户组件类,用于存储需要实力的用户组件类。以及私有的覆层用户控件变量和它的类,用于创建和存储控制器层。

UOverlayWidgetController* GetOverlayWidgetController(const FWidgetControllerParams& WCParams);

创建一个函数,可以通过结构体去获取控制器层实例。

UOverlayWidgetController* AMyHUD::GetOverlayWidgetController(const FWidgetControllerParams& WCParams)
{
	if(OverlayWidgetController == nullptr)
	{
		OverlayWidgetController = NewObject<UOverlayWidgetController>(this, OverlayWidgetControllerClass);
		OverlayWidgetController->SetWidgetControllerParams(WCParams);
	}
	return OverlayWidgetController;
}

实现就是没有就创建,存在就直接返回。

void InitOverlay(APlayerController* PC, APlayerState* PS, UAbilitySystemComponent* ASC, UAttributeSet* AS);

创建一个能够HUD初始化并能够创建相应的用户控件和控制器层对象的函数

void AMyHUD::InitOverlay(APlayerController* PC, APlayerState* PS, UAbilitySystemComponent* ASC, UAttributeSet* AS)
{
	checkf(OverlayWidgetClass, TEXT("OverlayWidgetClass 未设置,请在HUD上面设置")); //会打印相关信息到log列表
	checkf(OverlayWidgetControllerClass, TEXT("OverlayWidgetControllerClass 未设置,请在HUD上面设置"));
	
	UMyUserWidget* Widget = CreateWidget<UMyUserWidget>(GetWorld(), OverlayWidgetClass); //创建Overlay用户控件
	OverlayWidget = Cast<UMyUserWidget>(Widget);

	const FWidgetControllerParams WidgetControllerParams(PC, PS, ASC, AS); //创建参数结构体
	OverlayWidgetController = GetOverlayWidgetController(WidgetControllerParams); //获取控制器层

	OverlayWidget->SetWidgetController(OverlayWidgetController); //设置用户控件的控制器层
	
	Widget->AddToViewport(); //添加到视口
}

如果没有设置对应的类,将抛出错误并给予提示,然后根据类创建对应的实例最后将用户控件添加到视口。

MyHUD.h

// 版权归暮志未晚所有。

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/HUD.h"
#include "MyHUD.generated.h"

class UAttributeSet;
class UAbilitySystemComponent;
struct FWidgetControllerParams;
class UOverlayWidgetController;
class UMyUserWidget;

/**
 * 
 */
UCLASS()
class AURA_API AMyHUD : public AHUD
{
	GENERATED_BODY()

public:

	UPROPERTY()
	TObjectPtr<UMyUserWidget> OverlayWidget;

	UOverlayWidgetController* GetOverlayWidgetController(const FWidgetControllerParams& WCParams);

	void InitOverlay(APlayerController* PC, APlayerState* PS, UAbilitySystemComponent* ASC, UAttributeSet* AS);

private:

	UPROPERTY(EditAnywhere)
	TSubclassOf<UMyUserWidget> OverlayWidgetClass;

	UPROPERTY()
	TObjectPtr<UOverlayWidgetController> OverlayWidgetController;

	UPROPERTY(EditAnywhere)
	TSubclassOf<UOverlayWidgetController> OverlayWidgetControllerClass;
};

MyHUD.cpp

// 版权归暮志未晚所有。


#include "UI/HUD/MyHUD.h"
#include "UI/Widget/MyUserWidget.h"
#include "UI/WidgetController/OverlayWidgetController.h"

UOverlayWidgetController* AMyHUD::GetOverlayWidgetController(const FWidgetControllerParams& WCParams)
{
	if(OverlayWidgetController == nullptr)
	{
		OverlayWidgetController = NewObject<UOverlayWidgetController>(this, OverlayWidgetControllerClass);
		OverlayWidgetController->SetWidgetControllerParams(WCParams);
	}
	return OverlayWidgetController;
}

void AMyHUD::InitOverlay(APlayerController* PC, APlayerState* PS, UAbilitySystemComponent* ASC, UAttributeSet* AS)
{
	checkf(OverlayWidgetClass, TEXT("OverlayWidgetClass 未设置,请在HUD上面设置")); //会打印相关信息到log列表
	checkf(OverlayWidgetControllerClass, TEXT("OverlayWidgetControllerClass 未设置,请在HUD上面设置"));
	
	UMyUserWidget* Widget = CreateWidget<UMyUserWidget>(GetWorld(), OverlayWidgetClass); //创建Overlay用户控件
	OverlayWidget = Cast<UMyUserWidget>(Widget);

	const FWidgetControllerParams WidgetControllerParams(PC, PS, ASC, AS); //创建参数结构体
	OverlayWidgetController = GetOverlayWidgetController(WidgetControllerParams); //获取控制器层

	OverlayWidget->SetWidgetController(OverlayWidgetController); //设置用户控件的控制器层
	
	Widget->AddToViewport(); //添加到视口
}

我们的初始化函数也创建完成了,接下里就是InitOverlay()函数何时调用,在哪里调用?
由于这个函数需要PC,PS,ASC,AS,这四个参数,所以我们需要在这四个值已经创建完成并初始化完成后的地方调用。而ASC初始化完成,需要在客户端和服务器都要初始化结束,我们是在对应的Character里面实现的,所以,我们也需要在对应的Character里面调用即可。
8. UE5 RPG创建UI(上)_第51张图片
在玩家角色里我们书写了InitAbilityActorInfo()函数,这里面可以获得ASC和AS还有PlayerState,从角色类身上我们也可以获得玩家控制器,那么参数就齐了。

void AHeroCharacter::InitAbilityActorInfo()
{
	APlayerStateBase* PlayerStateBase = GetPlayerState<APlayerStateBase>();
	check(PlayerStateBase); //检测是否有效,无限会暂停游戏
	//从playerState获取ASC和AS
	AbilitySystemComponent = PlayerStateBase->GetAbilitySystemComponent();
	AttributeSet = PlayerStateBase->GetAttributeSet();
	//初始化ASC
	AbilitySystemComponent->InitAbilityActorInfo(PlayerStateBase, this);

	//获取PC
	if(APlayerControllerBase* PlayerControllerBase = Cast<APlayerControllerBase>(GetController()))
	{
		if(AMyHUD* HUD = Cast<AMyHUD>(PlayerControllerBase->GetHUD()))
		{
			HUD->InitOverlay(PlayerControllerBase, PlayerStateBase, AbilitySystemComponent, AttributeSet);
		}
	}
	
}

我们可以先获取到PlayerController,可以通过PlayerController获取到HUD,并调用函数初始化。

8. UE5 RPG创建UI(上)_第52张图片
在UE里创建一个蓝图类,基类基于创建的c++的HUD
8. UE5 RPG创建UI(上)_第53张图片
设置Overlay用户控件类和Overlay控制器层类
在这里插入图片描述
打开基础游戏模式,设置HUD类为当前创建的蓝图HUD。
8. UE5 RPG创建UI(上)_第54张图片
世界场景设置这里的HUD类也会默认改成当前的设置。

总结

上面这些我们主要实现了制作用户控件,并且能够在用户控件里面直接获取到控制器层,后续可以通过控制器层直接获取数据进行数据更新。
具体实现过程:在创建玩家控制角色过程中,初始化完成技能系统后,去调用HUD的InitOverlay()函数,来实现用户组件控制器层的初始化。InitOverlay()中将实现对用户控件和用户控件控制器层的实例化,并将UI添加到屏幕中。

测试

我们在用户组件基类中创建了一个WidgetControllerSet()函数,函数会在控制器层被设置时触发。这个函数可以在C++中调用,在蓝图中如果没有返回值可以当做通知使用,我们可以针对这一点对于当前实现的内容进行测试,查看是否在用户控件中这个通知是否有回调。
8. UE5 RPG创建UI(上)_第55张图片
在WBP_Overlay的图标中,右键搜索WidgetControllerSet,会发现相应的通知函数
8. UE5 RPG创建UI(上)_第56张图片
拉出来
8. UE5 RPG创建UI(上)_第57张图片
获取到控制器层对象。
8. UE5 RPG创建UI(上)_第58张图片
在设置控制器层对象完成后,打印它的名称。
8. UE5 RPG创建UI(上)_第59张图片
如果在左上角能够显示当前控制器层的对象名称,证明实现了当前内容的功能。

8. UE5 RPG创建UI(上)_第60张图片
在debug模式下,我们还可以通过打断点的方式实现调试检测。在代码左侧数字那里,悬停会出现红点,通过点击添加断点。
接着重新运行游戏。
8. UE5 RPG创建UI(上)_第61张图片
会发现程序停止运行到此处,我们可以以此来检查停止运行之前的值是否有问题,并检查相应的变量设置的是否正确。

你可能感兴趣的:(unreal,UE5,RPG,ue5,ui)