前言

嗯,好久没更新文章了,是时候水一波了。

–qswwltn


放假后突然变成咸鱼了一段时间没更新了,不过现在就来讲解下游戏中比较重要的部分:对话系统吧,这边使用的是Json保存对话内容然后使用UMG技术来实现UI界面。

UMG简单的介绍


这个UMG的话其实就是蓝图编辑器,在UE4中实现UI的方式有slate和UMG这2种,slate是完全使用C++来实现的,UMG是被封装过后的slate也就是蓝图了,但是这个被封装的库是可以通过C++来直接调用的,也就是说可以避免单纯使用C++导致的调整UI界面的不方便同时也可以降低封装后导致的性能下降。

实现对话


因为我们的对话内容是保存在Json文件中的,读取Json的步骤在前面的文章已经提过了这边就不再赘述了有需要的话可以自己翻下我前面的文章,本文将重点讲调用UMG的操作。

在使用UMG之前需要在VS里配置要编译的库,这个文件其实就是一个.cs文件

其名称是你的项目名称+.Build.cs,将里面改成这样

using UnrealBuildTool;

public class CPP_GameProject : ModuleRules
{
	public CPP_GameProject(ReadOnlyTargetRules Target) : base(Target)
	{
		PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
	
		PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" , "AIModule", "GamePlayTasks", "UMG", "Slate", "SlateCore", "Json", "JsonUtilities"});

		PrivateDependencyModuleNames.AddRange(new string[] { });
	}
}

这边稍微讲下这个PublicDependencyModuleNames里面的东西,前面3个是自动配置的,意思是使用UE4C++的代码库来编译,这个是使用C++编程必须添加的;InputCore是添加按键输入的响应库,AIModule和GamePlayTasks是如果你要使用C++来编写AI的行为的话要添加的;Json和JsonUtilites是使用读取Json文件的配置,之前在前面那几章忘记说要添加这个配置了。

而UMG,slate,slateCore这3个就是用来实现本文功能必须添加的配置了,在这里啰嗦几句,一定要记住蓝图是C++的封装,在编辑器中修改蓝图其实就是在可视的修改C++代码。同时在UE4的游戏进程中基本所有的内容都是指针;搞清楚这些的话对于这个引擎使用C++来编程就会比较舒服点。

好了,配置完成后,就可以在编辑器里自定义一个控件蓝图了,这边的话因为演示方便就先添加点文字内容算了注意,一定要修改控件名称,因为接下来要使用这些名称来找到指针。

这边说点小技巧,如果你不能确定你添加的控件是什么类型的话可以点旁边的打开源文件来看看这个控件是什么类型的好在官方的文档里看到这个的使用办法,比如文本框就是UTextBlock

现在搞清楚了这些控件的什么类型后使用起来就会方便多了。在编辑器里创建一个C++类,继承于UserWidget这个类型,然后在头文件中添加你刚才创建的控件类型的指针变量,比如我刚才创建了5个文本框,那就是5个UTextBlock指针

UCLASS()
class CPP_GAMEPROJECT_API UPlayingUI : public UUserWidget
{
	GENERATED_BODY()
		
public:
	UPlayingUI(const FObjectInitializer& ObjectInitializer);

	virtual bool Initialize() override;
	
public:
	AUear_Player* Player = nullptr;

	//UI元素
	UTextBlock* HPText = nullptr;
	UTextBlock* StaminaText = nullptr;
	UTextBlock* BoomText = nullptr;
	UTextBlock* SpareBulletsText = nullptr;
	UTextBlock* NowBulletsText = nullptr;
};

大概像这样,注意,因为是要改变的是玩家的界面所以要创建一个玩家的指针。

接下来就是要考虑界面的更新问题,这边的话我是使用定时器来进行更新的,因为这样可以不使用每帧更新而是使用固定的时间更新来减少性能的损失,比如你帧数是60帧,每秒更新60次界面和你每0.05秒更新一次,每秒更新20次哪个消耗高点?毕竟人的反应时间大概在0.2秒左右,所以减少刷新也不会出现什么问题。

使用定时器的话其实很简单的,只要声明一个FTimerHandle变量和一个更新用的函数就行了,比如这样

//注意添加#include "Engine/EngineTypes.h"
//定时器
	FTimerHandle UpDateAllDataTimeHandle;
	void UpDateAllData();

那么头文件的内容讲完了,该说下具体怎么实现了。

#include "PlayingUI.h"

UPlayingUI::UPlayingUI(const FObjectInitializer& ObjectInitializer):Super(ObjectInitializer){

	//获取玩家角色
	ACharacter* PlayerCharacter = UGameplayStatics::GetPlayerCharacter(this, 0);
	Player = Cast(PlayerCharacter);
}

//控件初始化
bool UPlayingUI::Initialize()
{
	Super::Initialize();
	HPText = Cast(GetWidgetFromName(TEXT("HPText")));
	StaminaText = Cast(GetWidgetFromName(TEXT("StaminaText")));
	BoomText = Cast(GetWidgetFromName(TEXT("BoomText")));
	SpareBulletsText = Cast(GetWidgetFromName(TEXT("SpareBulletsText")));
	NowBulletsText = Cast(GetWidgetFromName(TEXT("NowBulletsText")));

	//添加定时器(这个我觉得应该可以不用讲了吧?)
	GetWorld()->GetTimerManager().SetTimer(UpDateAllDataTimeHandle, this, &UPlayingUI::UpDateAllData, 0.05f, true);

	return true;
}

一定要在构建函数前面加上:Super(ObjectInitializer),这个是用来调用父函数的构建函数的,获取到控件的方法就是使用GetWidgetFromName函数来获取已经被添加到视口的用户控件中的控件然后转换成对应的变量类型然后赋值到你声明的变量中就可以更改了。

因为讲解方便,读取Json数据的过程就直接省略了,就假设我们已经获取到了对话的内容了,在UE4中比较常见的字符串类型除了FString外还有一个FText类型,我们拿到的对话内容应该是FString类型的这里必须转换成FText才能使用,比如这样

FText::FromString(FString::FromInt(Player->Dialogue))

现在就可以实现把Json内容读取出来当成对话了。

具体的触发办法可以自己想想,这边就不讲解了。那么,下个文章再见。