虚幻引擎(UE4) UMG实例
UI的重要性
游戏中UI系统具有以下特点:
- 庞大而复杂
一个游戏会包含多个系统,包括任务系统,进度系统,统计系统等等,而每个系统都有自己的可视化界面。 - 善变
在游戏开发中,经常会因为某些原因修改用户界面。 -
详细
为了向用户传递更加清晰的信息,UI通常会做得比较仔细。
有了以上的信息,强烈建议我们多花一点时间去创建一个UI系统,其中包括以下内容:
- 分离逻辑与可视化UI
- 允许快速迭代布局和视觉效果
- 逻辑调试可视化反馈
- 性能优化
案例
下面我将用一个真实的例子来展示一些非常有用的UI案例。
假如你希望游戏中的物品商店是这样的:
我们将利用下面这些内容来实现这个商店:
- Class 属性
- C++代码
- 蓝图
在大多数情况,我会提前写出伪代码,列出会使用哪些核心类和包含的内容:
MyData:这是一个C++类并继承自UObject,这样做的目的是创建一个类来管理UI要与用户交互的所有信息。继承自UObject的目的是方便使用Public/Private控制访问类属性setter和getter和一些有用的API。这种方法将数据的生命周期从UI中分离出来,这是非常理想的。而且多个Widget使用的数据能同时从同一份数据中获取。在游戏项目开发中,这些类很可能已经被其他开发者创建并存在,你可以继承自这些已经存在的类来开发,在很多时候我们需要创建自己的类来管理自己的任游戏模块。
UMyWidget:这是一个C++类并继承自UUserWidget。这个类用来定义特定给蓝图使用的API和蓝图事件和约定,以便与底层系统进行交互。
MyBlueprint:这是一个Widget类,继承自UMyWidget。在Widdget蓝图中,你需要创建所有可见的UI和布局,并使用UMyData和UMyWidget中提供的数据或图片进行UI更新。你也可以监听UMyWidget派发的事件,来响应一些UI的刷新。在实际交互中,你可能会调用UMyData和UMyWidget提供的API来响应一个按钮点击事件。
详细代码
数据类:
首先,将商品数据放到一起,以便商店里显示。
UOfferInfo派生自UObject,我想要将其暴露给蓝图类并且可以提供一些函数来让蓝图获取数据。我们建立一个简单的关系如(价格-图片-其他)
-
UENUM(BlueprintType)
-
enum class EOfferType:uint8
-
{
-
Normal,
-
Featured
-
};
-
-
UCLASS(BlueprintType)
-
class UOfferInfo : public UObject
-
{
-
GENERATED_UCLASS_BODY();
-
-
public:
-
UFUNCTION(BlueprintCallable, Category = "OfferInfo");
-
FText GetName() const;
-
-
UFUNCTION(BlueprintCallable, Category = "OfferInfo");
-
FText GetDescription() const;
-
-
UFUNCTION(BlueprintCallable, Category = "OfferInfo");
-
EOfferType GetOfferType() const;
-
-
private:
-
//其他属性数据
-
}
商店类:
负责显示所有商品
-
//抽象类,因为我们希望用蓝图来继承此类,但是又不希望用户直接实例化此类。
-
class UOfferShopWidgetBase : public UUserWidget
-
{
-
GENERATED_UCLASS_BODY()
-
-
public:
-
//此函数告诉用户开始读取商品信息,蓝图中最好采用文字提示的方式告诉用户正在处理数据
-
UFUNCTION(BlueprintImplementableEvent, Category = "OfferShopWidget")
-
void OnStartReadingOffers();
-
-
//告诉蓝图我们希望生成一些指定的商品。
-
//在多数情况下,蓝图会创建一个商品控件,并使用其UOfferInfo*指针来处理自己的逻辑。
-
//建议让蓝图完整控制API界面布局的逻辑,因为可能设计需要一个竖直列表,
-
//然后下次又要一个横向的滚动列表、或者一个网格、或者有其他组合,让蓝图控制更加灵活。
-
UFUNCTION(BlueprintImplementableEvent, Category = "OfferShopWidget")
-
void GenerateOffer(UOfferInfo* OfferData);
-
-
//这个函数提示已经完整地生成了所有的商品,蓝图中应该隐藏文字提示,并更新屏幕其他的显示。
-
UFUNCTION(BlueprintImplementableEvent, Category = "OfferShopWidget")
-
void OnOfferGenerationCompleted();
-
-
//蓝图获取数据的方法
-
UFUNCTION(BlueprintCallable, Category = "OfferShopWidget")
-
FDateTime GetStoreRefreshDate() const;
-
-
protected:
-
//内部函数,关键字的含义请查阅c++语法。
-
private:
-
//隐私级别更高的内部函数
-
-
//商品列表
-
TArray<UOfferInfo*> CurrentOffers;
-
}
商品类:
再来看用于显示单个商品的UOfferWidgetBase类。
我们将从该基类派生两个不同的布局视图来实现两个不同的商品。
-
//抽象类,因为我们希望用蓝图来继承此类,但是又不希望用户直接实例化此类。
-
UCLASS(Abstract, Blueprint, BlueType, ClassGroup = UI)
-
class UOfferWidgetBase : public UUserWidget
-
{
-
GENERATED_UCLASS_BODY()
-
public:
-
-
//这是你创建了一个新的商品实例后必须调用的函数。
-
//在这样的方式下,当你想显示其他不同的商品,直接再次调用SetupOffer,
-
//而不用再次创建一个全新的商品Widget,这对于性能优化格外重要。
-
//尤其是这种清单类条目,采用这种方式创建更少的widget控件,实现更高效UI。
-
UFUNCTIONO(BlueprintCallable, Category = "OfferWidget")
-
void SetupOffer(UOfferInfo* InOfferData)
-
{
-
//这里用一个小小的实现来解释我为什么不倾向于使用BlueprintNativeEvents
-
//主要的原因是这可能导致用户调用一个父类不存在的函数而报错。
-
//围绕BlueprintImplementableEvent的UX会更加单一些。
-
OfferData = InOfferData;
-
OnOfferSet();
-
}
-
-
//告诉蓝图已经获取了数据
-
//蓝图通常会调用GetOfferInfo(),然后调用GetName(),然后GetDescription()和更多的数据来填充文本,图片和其他
-
UFUNCTION(BlueprintImplementableEvent, Category = "OfferWidget")
-
void OnOfferSet();
-
-
//获取商品数据
-
UFUNCTION(BlueprintCallable, Category = "OfferWidget")
-
UOfferInfo* GetOfferInfo() const;
-
-
private:
-
UPROPERTY(transient)
-
UOfferInfo* OfferData;
-
}
蓝图类:
现在创建一个派生自上面的UOfferShopWidgetBase的UMG Widget蓝图类,并构建一个布局来接收商品数据。
- FeaturedOffers_HorBox
- 特殊商品放在商店左边
- NormalOffers_WrapBox
- 普通商品放在商店右边
- RefreshTime_TextBlock
- 文本计时器刷新商品信息
下面是图表:
让我们使用蓝图创建了widget组件并将其添加到了合适的容器中,所以我们就可以:
- 轻松地修改布局
- 不用情况下交替使用控件
- 修改从“add child to…”调用返回的插槽的属性
现在我们开始创建商品控件,我们将创建两种UMG控件,并继承自UOfferWidgetBase类。命名为OfferTileSmallWidget,OfferTileLargeWidget。上图中GenerateOffer事件驱动下,使用商品数据的商品类型创建了两种widget控件。
在我们将small widget和large widget布局设置为与最上面的实例图片相同后,就可以在图表中添加显示逻辑,最简单的形式如下所示:
到此,我们创建了OfferTileSmallWidget和OfferTileLargeWidget来适应商店屏幕,但是我们的体系结构使这一过程更加容易。想象一下,在我们游戏的另一个屏幕上,我们想展示一个热卖的商品。
这需要2步走:
- 在c++中,添加一个函数用于热卖商品:UOfferInfo* MyLibrary::GetTheHotOffer(),返回OfferInfo实例对象。
- 在屏幕上显示一个商品控件,并调用SetupOffer(),将GetTheHotOffer返回值作为参数,或者我创建一个带有火热特效“HotOfferWidget”蓝图并调用SetupOffer()。
结束语
这种方法处理c++业务逻辑,更加便于调试,修改和扩展。暴露一个基于事件的API在蓝图,让开发人员更加灵活根据需求修改UI。
要注意的问题是蓝图Tick、属性绑定,碎片太多,过多的动画会影响设备的性能。这种方法是利用事件来驱动,这自然会阻碍蓝图 Ticks和属性绑定,因为在事件存在的情况下,蓝图的Tick效率会降低。
动画的性能开销是很大的,当然是有用的,但你应该权衡利弊的做一些取舍,特别是当性能要求高的情况下,比如在HUD中,UI对性能开销预算几乎是为0。
文章来源: blog.csdn.net,作者:呦呦鹿鸣.,版权归原作者所有,如需转载,请联系作者。
原文链接:blog.csdn.net/zhang1461376499/article/details/113558077
- 点赞
- 收藏
- 关注作者
评论(0)