[UE4]C++操作UMG Widget实例(demo, example,例子)

这里演示在UE4中UMG使用和C++代码操作Widget对象的示例。实现的功能有:

  1. 如何在widget蓝图中制作UI模版;
  2. 如何用C++动态加载资源(UTexture2D);
  3. 当触发widget组件的事件时如何调用C++函数;

 

例子的完成工程(包括所有代码)在文章末尾下载。

这个例子的部分代码参考自UE4官方项目的源码,有兴趣深入研究可以去看那些UI比较丰富的官方项目。

注:当前使用的是最新版本v4.10。下面的代码涉及的API可能在后续版本中变动,注意下。

 

具体步骤:

1,先新建一个空的C++工程


[UE4]C++操作UMG Widget实例(demo, example,例子)_第1张图片
 

 

2,然后我们新建一个自己的PlayerController:工程编辑器中的点击File -》 new C++ class -》 选择Player Controller -》 下一步 -》 起一个类名:MyPlayerController


[UE4]C++操作UMG Widget实例(demo, example,例子)_第2张图片
 

自定义PlayerController是为了添加角色控制的逻辑,每个游戏都有特定的角色(可以是3d人身角色,也可以是一个2d面板),所以一个正常的游戏都会需要添加自己的PlayerController,当前例子没有角色,这里添加PlayerController是为了将后面的UMG widget实例加入到PlayerController中。

 

我们先在PlayerController构造函数中添加一些配置代码,来实现鼠标显示,默认是不显示:

AMyPlayerController::AMyPlayerController()
{
	//显示鼠标
	bShowMouseCursor = true;
	DefaultMouseCursor = EMouseCursor::Crosshairs;

	//启用鼠标事件
	bEnableClickEvents = true;
	bEnableMouseOverEvents = true;
}
 

 

 

3,再GameMode类的构造函数中,设置默认的PlayerController为刚刚我们新建的PlayerController:

 UMGDemoGameMode.h

// Fill out your copyright notice in the Description page of Project Settings.

#include "UMGDemo.h"
#include "UMGDemoGameMode.h"
#include "MyPlayerController.h"


AUMGDemoGameMode::AUMGDemoGameMode()
{
	//使用自定义PlayerController类
	PlayerControllerClass = AMyPlayerController::StaticClass();
}

 

 

4,再新建一个自定义的UBlueprintFunctionLibrary,这个类是一个提供各种通用的静态函数的管理类,我们将在这个类中实现一个获取自定义PlayerController的静态函数。工程编辑器中的点击File -》 new C++ class -》 勾选Show All Classes -》 找到BlueprintFunctionLibrary并选中 -》 下一步 -》 类名我们起为:MyBlueprintFunctionLibrary


[UE4]C++操作UMG Widget实例(demo, example,例子)_第3张图片
 

添加完毕后会自动编译,这里会提示需要在编辑器中重新Compile编译一次,否则编辑器的内容视图中看不到这个文件,这个窗口我们先关掉,后面会统一编译


[UE4]C++操作UMG Widget实例(demo, example,例子)_第4张图片
 

 

5,在“工程名.h”头文件加入我们刚刚新建的BlueprintFunctionLibrary:

 

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "Engine.h"
#include "MyBlueprintFunctionLibrary.h"

 

 

并实现一个静态的函数用于获取自定义PlayerController:

MyBlueprintFunctionLibrary.h

public:
	/** 获取所有PlayerController列表,然后再找到本地客户端的第一个PlayerController并返回 */
	UFUNCTION(BlueprintCallable, Category = "TD_Test Gameplay")
		static APlayerController* GetLocalPlayerController(UObject* WorldContextObject);
	

 

  MyBlueprintFunctionLibrary.cpp

 

APlayerController* UMyBlueprintFunctionLibrary::GetLocalPlayerController(UObject* WorldContextObject)
{
	if (UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject))
	{
		for (FConstPlayerControllerIterator Iterator = World->GetPlayerControllerIterator(); Iterator; ++Iterator)
		{
			APlayerController* PlayerController = *Iterator;
			if (PlayerController->IsLocalController())
			{
				// For this project, we will only ever have one local player.
				return PlayerController;
			}
		}
	}
	return nullptr;
}

 

 

6,新建一个自定义UserWidget类,添加的目的是:将widget组件需要调用的C++逻辑都放到这个类中,且只能放到自定义UserWidger类中,如果你想在UMG蓝图中让widget组件调用UserWidget类之外的函数是行不通的。工程编辑器中的点击File -》 new C++ class -》 勾选Show All Classes -》 找到UserWidget并选中 -》 下一步 -》 类名我们起为:MyUserWidget


[UE4]C++操作UMG Widget实例(demo, example,例子)_第5张图片
 
 

添加完以后还无法编译,需求在“工程名.Build.cs”中加入配置,加入红色部分即可:

PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "UMG", "Slate", "SlateCore" });

 

7,添加Widget蓝图,并将此蓝图的父类指定为刚刚新建的MyUserWidget class。右击内容视图-》用户界面-》Widget蓝图,名字起为:MyWidgetBlueprint


[UE4]C++操作UMG Widget实例(demo, example,例子)_第6张图片
 

 

然后双击打开蓝图,点击Graph(图形)界面 -》 点击 Class settings -》 找到Parent Class -》 选择我们刚刚新建的自定义UserWidget类。(如果找不到自定义UserWidget,在编辑器中重新编译下代码,实在不行重启下编辑器)


[UE4]C++操作UMG Widget实例(demo, example,例子)_第7张图片
 


[UE4]C++操作UMG Widget实例(demo, example,例子)_第8张图片
 

 

8,再回到Designer界面,从左侧的组件列表中,我们拖拽几个组件进来,分别是:Button,Overlay,Image。层级关系是:Button 《 Overlay 《 Image


[UE4]C++操作UMG Widget实例(demo, example,例子)_第9张图片
 

后面会实现这样的一个功能:给定两张图片(Texture2d),每点击一次,button的图片变一次。这个功能其实也可以用存蓝图脚本实现,这里为了演示蓝图调用C++函数,我们把替换图片的逻辑放在C++代码中。官方文档说,对于调用非常频繁的逻辑,若要提高性能,建议用C++实现而不要在蓝图中实现。

 

先修改widget组件的一些参数:

设置设置button的大小,因为我们使用的图片大小是205X115,所以button的大小也这样设置


[UE4]C++操作UMG Widget实例(demo, example,例子)_第10张图片
 

 

属性Style中Normal、Hovered、Pressed中的Draw As都修改为None,因为按钮的图片我们准备设置在Image组件当中。之所以不直接在button中设置背景图片,是因为仅仅依靠一个button组件,无法只用一张图片就实现按钮下压的效果,这里我们想只用一张图片来实现按钮下压的效果,所以除了button,还用了Overlay和Image两种组件


[UE4]C++操作UMG Widget实例(demo, example,例子)_第11张图片
 

 

然后再设置下Image组建的size,设置为图片一样大小:205x115。


[UE4]C++操作UMG Widget实例(demo, example,例子)_第12张图片
 

 

控制button按下后图片浮动的上下左右距离,在Style下的Pressed Padding中设置,如果想要浮动的效果明显一点,根据需要设置大一下:


[UE4]C++操作UMG Widget实例(demo, example,例子)_第13张图片
 

 

到此我们把UMG widget蓝图模版绘制完成,接下来我们用C++代码操作这些widget组件。

 

9,首先我们在GameMode类中重载父类的BeginPlay()函数

/** Called when the game starts. */
virtual void BeginPlay() override;

 

同时在GameMode内定义了几个函数:

//创建widget对象并添加到Viewport

void CreateWidgetInstance();

//获取需要操作数据的widget对象

void FindImageComponents();

//动态加载icon所需Texture2D

void LoadAssetsDynamic();

 

其中CreateWidgetInstance()函数用来创建Widget实例对象(以之前的MyUserWidgetBlueprint为模版),并添加到游戏的Viewport中;

FindImageComponents()用来获取创建的widget实例中的Image组件的引用,因为我们后面要用代码操作这个Image组件,所以先把这个组件的指针存起来,不用每次操作时再查找;

LoadAssetsDynamic()用来加载Texture资源。我们对Image组件更换图片时,实际是对Texture的更换,所以,进入游戏时我们就先加载到内存中,等后面点击button的时候,直接修改Image组件中对这些Texture内存的引用,来实现图片切换。

具体代码如下:

UMGDemoGameMode.h

 

// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "GameFramework/GameMode.h"
#include "Blueprint/UserWidget.h"
#include "Runtime/UMG/Public/Components/Image.h"
#include "UMGDemoGameMode.generated.h"

/**
 * 
 */
UCLASS()
class UMGDEMO_API AUMGDemoGameMode : public AGameMode
{
	GENERATED_BODY()
	
	
public:

	AUMGDemoGameMode();
	
	/** Called when the game starts. */
	virtual void BeginPlay() override;

private:
	UImage* imageHero;

	UTexture2D* texHero1;
	UTexture2D* texHero2;

	UTexture2D* defaultTexHero;

protected:

	/** The widget class we will use as our game over screen when the player wins. */
	UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Game")
		TSubclassOf<UUserWidget> MyWidgetClass;

	/** The widget instance that we are using as our menu. */
	UPROPERTY()
		UUserWidget*  MyWidgetInstance;

	//创建widget对象并添加到Viewport
	void CreateWidgetInstance();

	//获取需要操作数据的widget对象
	void FindImageComponents();

	//动态加载icon所需Texture2D
	void LoadAssetsDynamic();

public:

	//修改icon图片:切换英雄时调用
	void ChangeHeroIcon();
};
 

 

UMGDemoGameMode.cpp

 

// Fill out your copyright notice in the Description page of Project Settings.

#include "UMGDemo.h"
#include "UMGDemoGameMode.h"
#include "MyPlayerController.h"


AUMGDemoGameMode::AUMGDemoGameMode()
{
	//使用自定义PlayerController类
	PlayerControllerClass = AMyPlayerController::StaticClass();

	imageHero = NULL;
	texHero1 = NULL;
	texHero2 = NULL;
	defaultTexHero = NULL;
}

void AUMGDemoGameMode::BeginPlay()
{
	Super::BeginPlay();

	UMyBlueprintFunctionLibrary::SetGameModeInstance(this);
	CreateWidgetInstance();
	FindImageComponents();
	LoadAssetsDynamic();

	ChangeHeroIcon();
}

//创建widget对象并添加到Viewport
void AUMGDemoGameMode::CreateWidgetInstance()
{
	if (MyWidgetInstance)
	{
		MyWidgetInstance->RemoveFromViewport();
		MyWidgetInstance = nullptr;
	}
	if (MyWidgetClass)
	{
		if (AMyPlayerController* PC = Cast<AMyPlayerController>(UMyBlueprintFunctionLibrary::GetLocalPlayerController(this)))
		{
			MyWidgetInstance = CreateWidget<UUserWidget>(PC, MyWidgetClass);
			if (MyWidgetInstance)
			{
				MyWidgetInstance->AddToViewport();
			}
		}
	}
}

//获取需要操作数据的widget对象
void AUMGDemoGameMode::FindImageComponents()
{
	if (UCanvasPanel* CanvasPanelWidget = Cast<UCanvasPanel>(MyWidgetInstance->GetRootWidget()))
	{
		//获取英雄头像的UImage指针
		if (UButton* btn = Cast<UButton>(CanvasPanelWidget->GetChildAt(0)))
		{
			if (UOverlay* overlay = Cast<UOverlay>(btn->GetChildAt(0)))
			{
				if (UImage* image = Cast<UImage>(overlay->GetChildAt(0)))
				{
					imageHero = image;
				}
			}
		}
	}
}

//动态加载icon所需Texture2D
void AUMGDemoGameMode::LoadAssetsDynamic()
{
	//加载英雄头像的Texture
	if (texHero1)
	{
		//如果已经加载过,则先销毁掉
		texHero1->ConditionalBeginDestroy();
		texHero1 = NULL;
		GetWorld()->ForceGarbageCollection(true);
	}
	texHero1 = Cast<UTexture2D>(StaticLoadObject(UTexture2D::StaticClass(), NULL, TEXT("Texture2D'/Game/Textures/hero1.hero1'")));

	if (texHero2)
	{
		texHero2->ConditionalBeginDestroy();
		texHero2 = NULL;
		GetWorld()->ForceGarbageCollection(true);
	}
	texHero2 = Cast<UTexture2D>(StaticLoadObject(UTexture2D::StaticClass(), NULL, TEXT("Texture2D'/Game/Textures/hero2.hero2'")));

	defaultTexHero = texHero2;
}

void AUMGDemoGameMode::ChangeHeroIcon()
{
	if (!defaultTexHero || !texHero1 || !texHero1 || !imageHero)
	{
		return;
	}

	defaultTexHero = defaultTexHero == texHero1 ? texHero2 : texHero1;

	imageHero->SetBrushFromTexture(defaultTexHero);
}
 

 

上面代码涉及到一些Widget组件的变量,需要在"工程名.h"中加入以下头文件(后五行)才能编译通过:
UMGDemo.h
// Fill out your copyright notice in the Description page of Project Settings.

#pragma once

#include "Engine.h"
#include "MyBlueprintFunctionLibrary.h"

#include "Runtime/UMG/Public/UMG.h"
#include "Runtime/UMG/Public/UMGStyle.h"
#include "Runtime/UMG/Public/Slate/SObjectWidget.h"
#include "Runtime/UMG/Public/IUMGModule.h"
#include "Runtime/UMG/Public/Blueprint/UserWidget.h"
 

 

10,在UMyUserWidget类中添加一个蓝图函数,在这个函数内调用GameMode的ChangeHeroIcon()函数。这里的宏定义关键字使用了“BlueprintCallable”,表示蓝图中可以调用的函数

MyUserWidget.h

 

public:

	//暴露给蓝图调用的函数,处理Button的OnClicked事件
	UFUNCTION(BlueprintCallable, Category = "WidgetFunction")
		void handleMyButtonClick();
 

 

 

MyUserWidget.cpp

 

void UMyUserWidget::handleMyButtonClick()
{
	AUMGDemoGameMode* GameModePtr = UMyBlueprintFunctionLibrary::GetGameModeInstance();
	GameModePtr->ChangeHeroIcon();
}
 

 

 

11,在Widget蓝图中添加鼠标点击事件OnClicked


[UE4]C++操作UMG Widget实例(demo, example,例子)_第14张图片
 

然后这OnClicked节点中拖出一个连线,输入函数名handleMyButtonClick,即可看到这个函数(如果看不到需要重新编译下,前提时上面第7个步骤执行过),并选择这个函数


[UE4]C++操作UMG Widget实例(demo, example,例子)_第15张图片
 

 

 12,建立一个GameMode蓝图。

因为我们再GameMode代码中定义了一个MyWidgetClass成员变量,这个变量类型是一个Widget蓝图模版,要对这个变量赋初值,需要再蓝图脚本中设置(可能也有代码方式来执行对蓝图模版类型的变量赋初值的方法,但我还没找到相关资料)。

操作方式是:右击GameMode class,选择创建GameMode蓝图,名字起为:MyUMGDemoGameMode_BP


[UE4]C++操作UMG Widget实例(demo, example,例子)_第16张图片
 

 

建好以后打开GameMode蓝图,然后找到C++代码里面定义的那个成员变量MyWidgetClass,然后选择我们之前创建的Widget蓝图MyWidgetBlueprint。(之前遇到一个很诡异的问题,在GameMode蓝图中设置好MyWidgetClass变量的初始值以后没有生效,需要这个初始值清空后重新设置一遍,或者在菜单Settings -》 World Settings下面设置下默认的GameMode


[UE4]C++操作UMG Widget实例(demo, example,例子)_第17张图片
 

 

13,最后,设置默认GameMode为这个GameMode蓝图,菜单Edit -》Project Settings -》 Project -> Maps & Modes -》 Default Modes -》 Default GameMode:


[UE4]C++操作UMG Widget实例(demo, example,例子)_第18张图片
 

 

 

最终效果如下图,每当点击一次按钮,按钮的图片就会切换一次


[UE4]C++操作UMG Widget实例(demo, example,例子)_第19张图片
 

 

此实例的完成工程下载为UMGDemo.rar附件

再给一个备用的站外链接:

http://yunpan.cn/cubaGg5rk8Xyv  访问密码 f72f

你可能感兴趣的:(UE4)