WangShuXian6 / blog

FE-BLOG
https://wangshuxian6.github.io/blog/
MIT License
46 stars 10 forks source link

Unreal Engine 虚幻引擎5 Lyra 项目 #182

Open WangShuXian6 opened 9 months ago

WangShuXian6 commented 9 months ago

Unreal Engine 虚幻引擎5 Lyra 项目

https://docs.unrealengine.com/5.3/zh-CN/lyra-sample-game-in-unreal-engine/ https://www.unrealengine.com/marketplace/zh-CN/product/lyra

Procedural game Development 程序化游戏开发

模块化 image 游戏技能系统引入模块化方法来进行游戏开发。 在游戏开发中使用模块化方法,将完整的游戏开发为单独的模块, 这些模块遵循即插即用的方式,这 些模块可以通过消息轻松地相互通信, 角色移动动画、操作武器、视觉特效、声音系统都是单独构建的可以轻松地在系统中添加或删除的模块。

例如,可以通过一组单独的动画将新的弓箭手技能添加到角色中,并且功能构建为单独的模块,只需插入系统即可。

WangShuXian6 commented 9 months ago

2. Lyra and Gameplay Ability System Concepts Lyra和游戏技能系统概念

1. Introduction to Gameplay Ability System 游戏技能系统简介

image

gas 是一个插件。 使游戏开发变得更加容易,并加快了 RPG 类型游戏的开发速度。 支持多人游戏。

技能 是可以围绕角色或角色持有的武器甚至游戏本身构建的技能.

例如,角色可以具有跳跃冲刺和投掷手榴弹等技能。 角色持有的武器手枪可以具有开火、装弹等技能。 游戏本身可以具有诸如角色死亡后自动重生之类的功能。

属性 主要用于存储角色不同方面的值

角色健康状况是属性的一个很好的例子. 生命值在整个游戏中用于不同的功能。 例如,屏幕中显示的健康栏显示健康属性值。 当生命值为零时,角色就会死亡。

游戏效果

游戏效果用于改变属性值.

例如: 伤害效果降低生命属性。 治疗效果会增加健康属性。

游戏性CUE显示 主要用于游戏中特定事件的视觉和声音效果

例如,引爆手榴弹涉及爆炸、烟雾和相机抖动。

游戏标签 是游戏技能系统的标识

每个GAS功能都由游戏标签来标识,这是一个分层名称。

例如,游戏技能英雄跳跃被识别为 技能.类型.动作.跳跃。

同样,武器手枪射击的 游戏性CUE显示被识别为 游戏提示.武器.手枪.火。

游戏标签用于技能、事件、消息 HUD 插槽、输入提示和效果。

游戏消息和事件

游戏消息是不同组件或模块之间通信的一种方式。

一个很好的例子是当角色死亡然后重生时屏幕上显示的重生计时器. 屏幕上显示的小部件接收游戏消息以及来自游戏技能的持续时间数据。

2. Introduction to Lyra

Lyra 采用现代游戏开发方法,使用游戏功能插件和模块化游戏玩法. 在 Lyra 中,游戏功能被开发为相互连接的单独模块。 这些功能作为插件安装。 作为即插即用系统,它带来了很大的灵活性,可以轻松添加或删除游戏中的功能不而影响现有游戏。 Lyra 建立在游戏技能系统之上,具有大量预先配置的帮助功能和示例。 image

Lyra 预先构建了功能齐全、可扩展的射击游戏框架。 使用 Lyra 作为游戏基础的最大优势是它可以用作低代码平台。

Lyra 通过构建用于模块化游戏开发的子系统和组件,使游戏开发变得更加容易。

Lyra Quick bar 组件是一个屏幕上的库存组件,用于维护装备的武器。 image

Lyra 库存管理器组件用于添加和删除库存中的物品。

UI 扩展子系统可以轻松地将动态小部件加载到 UI HUD 的相应插槽中。 image

Lyra 游戏消息子系统用于组件,模块之间的通信和发送数据。 image

Lyra 体验是一种模块化游戏设计方法。用于在视频游戏中构建功能。 使用这种方法,可以将视频游戏的功能开发为即插即用模块,该模块可以在游戏开发一段时间后,可以很容易地添加和删除。

Lyra 面向用户的体验是模块化游戏设计,在加载 Lyra 体验(包括加载屏幕)之前以特定顺序组装 UI 屏幕。 image

Lyra团队子系统用于将游戏中的角色分成不同的团队并管理不同的团队相关功能。

Lyra装备管理器用于为游戏中的角色装备武器。

Lyra 指示器管理器组件用于在游戏中显示队友的铭牌。

音乐管理器组件用于处理游戏的音响系统和音乐。

Lyra 针对不同的角色技能使用动画层为单独的模块,从而更轻松地管理动画作。 image

Lyra 支持使用游戏技能系统构建的多人游戏开发。

3. Lyra Installation 安装

https://www.unrealengine.com/marketplace/zh-CN/product/lyra

生成C++项目

右键 Lyra初学者游戏包.uproject - Generate Visual Studio project image

image

使用 VS 打开 Lyra.sln 或 VSCode 加载项目目录 或 Rider 打开 Lyra.uproject

Lyra 设为启动项目 image image

Lyra -右键-生成

4. Lyra Experience Walkthrough Lyra体验演练

启动Lyra时,DefaultEditorOverview 关卡将会加载为 默认地图(Default Map) 。 在编辑器中,点击 在编辑器中运行(Play In Editor) (PIE) 可以启动默认关卡。

5. Lyra Character Features Lyra角色功能

https://docs.unrealengine.com/5.3/zh-CN/animation-in-lyra-sample-game-in-unreal-engine/

角色模块配有内置运动系统和多线程动画支持以获得最佳性能。

Locomotion系统支持以下动画。 走。跳。蹲。

Lyra 支持步幅变形,距离匹配等先进技术。 这给我们的角色在跑步开始和停止时带来了真实的感觉。 Lyra 支持方向扭曲,这是一项非常重要的技术,用于提供自然感觉的身体下部的弯曲。

游戏过程中使用两种类型的动画: 运动动画: 其中包括行走、奔跑、蹲伏和跳跃。这些是全身连续动画。

另一种类型的动画是动画蒙太奇。 在动画蒙太奇中,在运动动画之上的事件期间播放短动画。 在大多数情况下,它是上半身蒙太奇动画与运动动画的混合。 例如,跑步时的步枪射击动画或跑步时的近战武器动画。 动画蒙太奇还用于其他动作,例如角色冲刺、角色滚动等

6. Enhanced Input System in Lyra 增强输入系统

image image

WangShuXian6 commented 9 months ago

游戏功能和模块化Gameplay / gameplay abilities game feature actions 插件

https://docs.unrealengine.com/5.3/zh-CN/game-features-and-modular-gameplay-in-unreal-engine/ 游戏功能和模块化Gameplay构建你可以轻松激活、停用或在项目之间共享的独立功能。

游戏功能(Game Features) 和 模块化Gameplay(Modular Gameplay) 插件可以帮助开发人员为项目创建独立功能。使用插件来构建功能有多种优势:

保持你的项目代码库的清洁且可读。

避免不相关的功能之间意外交互或依赖。在开发那些需要经常改动功能的已上线产品时,这尤为重要。

设置

以下步骤将使你的项目处于一种状态,可以在其中添加一个或多个基于插件的独立功能。首先,激活游戏功能和模块化Gameplay插件。

在虚幻引擎中,找到 编辑(Edit)> 插件(Plugins)。打开 插件** 窗口后,找到并启用游戏功能和模块化Gameplay插件。

image

你可以在 Gameplay 类别中找到游戏功能和模块化Gameplay插件。

在启用这些插件后,编辑器会提醒你将它重启。点击 立即重启。

image

当编辑器重启后,打开插件窗口,然后点击 添加(Add) 来启动 新的插件 窗口。

image

创建一个包含你正在构建的功能的新插件。选择 游戏功能(仅内容),然后命名这个插件。

image

使用 内容浏览器(Content Browser) 来找到插件的上级内容文件夹。确保你的插件与你的项目一同保存在 /Plugins/GameFeatures/ 目录中。

image

在我们的例子中,这个插件命名为 MyStandAloneFeature。

在 资产面板(Asset Panel) 中点击右键,在上下文菜单中展开 杂项(Miscellaneous),并选择 数据资产(Data Asset) 来创建一个新的 数据(Data) 资产。从类的列表中,选择 GameFeatureData ,并将新的资产命名为你的插件的名称。

当你完成这些步骤后,你的独立功能将被设置为在引擎(或编辑器)启动时加载。现在你可以开始开发功能本身,并添加可以实现该功能的 操作(Actions)。有四种可供使用的操作。

游戏功能动作

你的独立功能现在被设置为在引擎启动时加载。现在你可以开始开发这个功能,为其添加可以实现该功能的 操作。

操作 说明
添加作弊(Add Cheats) 操作将会扩展 作弊管理器(Cheat Manager) ,创建新的"作弊代码"或扩展现有作弊代码。作弊代码对于调试很有帮助,可以自动从发行版中删除。~(波浪号)键可以打开控制台,你可以在运行项目时在这里输入作弊代码。
添加组件(Add Components) 操作将会获取Actor子类的列表,并通过选择加入的方式将一组组件添加到子类中。这是使用游戏功能和模块化Gameplay插件的最常见方式,因为组件非常适合用来封装各种类型的行为。
添加数据注册表(Add Data Registry) 操作会将一个或多个数据注册表添加到项目中。数据注册表可以用来高效存储和检索全局注册数据。
添加数据注册表源(Add Data Registry Source) 操作会将一个或多个数据表添加到现有数据注册表
添加世界分区内容(Add World Partition Content) 为这个功能添加世界分区内容。

添加操作

要添加操作,请打开你刚创建的数据资产。展开 操作(ACTIONS) 类别,显示名为 操作(Actions) 的数组。将元素添加到该数组中,并将其设置为合适的操作类型。你可以在下方小节中找到每种操作类型的进一步指导。 image 对于任何插件,你可以添加任意多个操作。

在添加操作后,如果你的游戏功能看起来没有生效,那么可能需要重启一次编辑器。这是因为,GameFeatureData资产在编辑器启动期间加载,这意味着,创建GameFeatureData资产之后若不重新启动,将会导致该资产无法运行。你只需要在最初创建资产时重新启动;在后续的会话中更新资产时则不需要重新启动。

添加作弊

"添加作弊"操作将会在游戏中注册 作弊管理器扩展(Cheat Manager Extension) 。这让开发人员可以在独立功能中创建自己的调试命令(或"作弊代码"),并让这些命令在功能生效后随时可用。

作弊管理器和作弊管理器扩展都是调试工具,因此不会在发行版中实例化。

添加组件

"添加组件"操作会遍历Actor和组件对,并尝试将每个组件的一个实例添加到配对的Actor中。你可以指定在客户端还是服务器上添加组件,还是两者都添加;默认情况下,客户端和服务器都会添加组件。

为了保证你的功能完全封装在插件中,你添加的组件将来自插件自身,而Actor类将是内置的引擎类,例如 Pawn 或项目中继承自引擎类的子类,例如常见的 MyPawn 。组件应该能处理与你的功能相关的所有程序逻辑和数据存储。

当你能最大程度降低与项目的Actor子类所需的交互保持在最低限度时,在别的项目中实现你设计的功能就会比较容易。

在添加组件操作中不支持使用基础Actor类。系统将忽略这部分的操作。相反,我们建议你找到所需组件的Actor子类的最小子集,并指定该类。如果有多个类需要接收该组件,且它们没有一个共同的父类,那么你可以设置多个添加组件操作来覆盖所有适当的基类。

蓝图版本:

要接收"添加组件"操作中的组件,你的Actor必须在 游戏框架组件管理器(Game Framework Component Manager) 中注册;通常是在其 开始播放(Begin Play) 事件中注册。方法是,获取全局游戏框架组件管理器,调用其 添加接收器(Add Receiver) 函数,将Actor作为 接收器(Receiver) 参数传入。无论激活了多少游戏功能,Actor仅需要使用 添加接收器(Add Receiver) 一次即可。要从Actor中删除与游戏功能关联的所有组件,请使用游戏框架组件管理器的 删除接收器(Remove Receiver) 函数。 image 在Actor的 开始播放(Begin Play) 事件中注册以便接收组件。

C++ 版本:

要接收"添加组件"操作中的组件,你的Actor必须使用 UGameFrameworkComponentManager 单例注册,并将自身传入 AddReceiver 函数。这步通常在 BeginPlay 中完成。代码如下:

    if (UGameFrameworkComponentManager* ComponentManager = GetGameInstance()->GetSubsystem<UGameFrameworkComponentManager>())
    {
        ComponentManager->AddReceiver(this);
    }

添加数据注册表

"添加数据注册表"操作可以将整个数据注册表添加到你的项目中。使用你要添加的数据注册表资产的路径来配置操作。如需了解数据注册表的更多信息,请参见数据注册表页面。

添加数据注册表源

假如要添加一个数据源并流送到数据注册表中,你需要使用"添加数据注册表源(Add Data Registry Source)"操作。你必须为每个数据源配置路径,配置将加载数据源的数据注册表的名称,以及相关的优先级和使用标记。如果游戏功能在启动时加载,则相应的标识符将填充下拉列表。如需更多信息,请参见"数据注册表"页面的数据注册表源部分。

WangShuXian6 commented 9 months ago

Common UI 插件

https://docs.unrealengine.com/5.3/zh-CN/common-ui-plugin-for-advanced-user-interfaces-in-unreal-engine/ 使用Common UI插件创建复杂、多层级的用户界面。

https://www.bilibili.com/video/BV1Ma411o7ZY/?spm_id_from=333.337.search-card.all.click&vd_source=f259d77343588259f8e1b4ae567b1d34

启用插件 Common UI plugin image

Common UI提供了一个丰富的工具箱,可用于创建丰富的、多层级的并且支持跨平台的用户界面,例如《堡垒之夜》中的界面。它的工具包括;

全新的 Common UI控件库,可提供常用的游戏功能。

全新的 风格数据资产(style data asset),将风格信息与UI元素分开,使之更容易在多个UI中共享风格/造型。

全新的 输入路由(Input Routing) 系统,允许UI控件实现选择性的交互,使之更容易在多层UI中管理聚焦。

支持主机特定的UI元素,例如与特定控制器相关的按钮图标。

适用于游戏手柄的方向导航。

Common UI概览

了解Common UI的运作方式和能够解决的难题

Common UI快速指南

对Common UI初次使用者介绍其核心功能。

设置你的视口来使其支持 输入路由(Input Routing)。

如何创建 输入动作数据表(Input Action Data Tables),将控制器按键映射到UI中的动作。

如何设置 默认导航动作(Default Navigation Actions),支持全局的点击和返回按钮功能。

如何创建 控制器数据资产(Controller Data Assets) 并且将其分配到特定平台上的特定控制器类型。

1. 视口输入路由设置

视口(Viewport) 是Common UI全部输入路由的基础。当Common UI捕获输入时,会将其发送至视口,然后视口会将其发送至顶部的节点。要让视口支持这些功能,请执行以下设置步骤:

打开 编辑(Edit) > 项目设置(Project Settings) > 引擎(Engine) > 通用设置(General Settings)。

将你的 游戏视口客户端类(Game Viewport Client Class) 设置为 CommonGameViewportClient。 image

如果你需要自己的自定义游戏视口类,你需要将其从CommonGameViewportClient扩展来使用Common UI。

2. 创建输入动作数据表

Common UI使用输入动作数据表来创建能够与各种平台的输入所关联的动作。比如,查看Common UI内容文件夹中的 GenericInputActionDataTable,或者内容示例项目中的 NavigationInputActionDataTable。

Common UI的输入动作数据表与项目设置中的输入动作或者高级输入系统无关。它们仅用于管理UI输入。

1 在 内容浏览器(Content Browser) 中右键点击,然后点击 杂项(Miscellaneous) > 数据表资产(Data Table Asset)。

2 选择 CommonInputActionDataBase 作为你的行结构,然后点击 OK 来创建一个新的输入动作数据表。

image

3 要添加一个新的输入动作行,点击顶部栏中的 添加(Add)。 image

4 填充输入动作,输入名称和哪个按键会激活它的信息。 输入动作由以下几个参数组成: image

参数(Parameter) 描述(Description)
显示名称(Display Name) 输入动作的名称。如果有导航栏,会显示在导航栏中。
按住显示名称(Hold Display Name) 输入动作的名称,如果其需要用户按住按钮的话。
导航栏优先级(Nav Bar Priority) 在导航栏中从左到右排列动作时作为依据的优先级顺序。
键盘输入类型信息(Keyboard Input Type Info) 使用鼠标键盘时执行该动作的按键。
默认游戏手柄输入类型信息(Default Gamepad Input Type Info) 使用游戏手柄时执行该动作的按键。
游戏手柄输入覆盖(Gamepad Input Overrides) 使用 特定 游戏手柄时执行该动作的按键。用于特定平台的按钮覆盖,比如在任天堂Switch的手柄上切换前进和后退按钮。

image

触控输入类型信息(Touch Input Type Info ) | 使用触控时执行该动作的按键。

Common UI控件将这些抽象的动作映射到实际的输入。比如,你可以将数据表和行名称参考添加到 CommonButtonBase 控件中的 触发输入动作(Triggering Input Action)。之后,按下该动作所关联的按钮会触发Common UI按钮。 image

为了避免版本控制冲突,建议将相关的动作组收归至其自己的数据表。比如,将所有的菜单导航动作放入一个表,然后将特定菜单的动作放入其自己的表。之后,创建一个合成的数据表来引用这些数据表。

3. 默认导航动作设置

虚幻引擎原生支持指向导航。然而,Common UI使用 Common UI输入数据 资产来定义所有平台通用的 点击(Click) 和 返回(Back) 输入动作。

在内容浏览器中创建一个 蓝图类(Blueprint Class)。

找到 CommonUIInputData 并点击 选择(Select) 来创建一个新的蓝图。

指定适当的包含你的默认点击(Click) 和 返回(Back) 动作的数据表。

image

将该资产分配到 项目设置(Project Settings) > 游戏(Game) > 通用输入设置(Common Input Settings) > 输入数据(Input Data).

image

上述指定的资产会被装载进Common UI并用于默认导航。指定的在高光选中按钮或者其它可互动元素时使用的点击按钮用于替换鼠标点击,指定的返回按钮用于从当前菜单返回至上一个菜单。

4. 控制器数据绑定 (特定平台的UI元素)

控制器数据资产(Controller Data Asset) 将按键动作与UI元素关联。每个控制器数据资产都与一个输入类型、游戏手柄或者平台关联。Common UI使用该信息来根据当前平台和输入类型自动使用特定平台的正确UI元素。除此以外,对于支持多种输入类型或者特殊游戏手柄的平台,它还可以使用用户的输入来找到正确的游戏手柄并在运行时切换UI元素。

在 内容浏览器(Content Browser) 中右键点击,然后创建一个新的 蓝图类(Blueprint Class)。

找到 CommonInputBaseControllerData 并点击 选择(Select) 来创建一个新的控制器数据资产。

用资产和你计划支持的一个控制器的相关信息来填充控制器数据资产。

image

参数(Parameter) 描述(Description)
输入类型(Input Type) Set this to Gamepad, Mouse and Keyboard, or Touch.
游戏手柄名称(Gamepad Name) If the controller is a Gamepad, this will be the platform this gamepad corresponds to. The default Gamepad is called Generic.
输入刷数据映射(Input Brush Data Map) 从按键到UI元素和图标的映射。
输入刷按键组(Input Brush Key Sets) 从多个按键映射到单个UI元素。用于十字键或者其它可能映射到多个轴的输入。

为所有计划支持的输入创建好控制器数据后,这些类都必须添加到它们关联的平台,位于 项目设置(Project Settings) > 游戏(Game) > 常见输入设置(Common Input Settings) > 平台输入(Platform Input)。

image

默认游戏手柄名称(Default Gamepad Name) 必须完全匹配控制器数据资产的 游戏手柄名称(Gamepad Name) 域中的内容,否则将无法识别,无法显示图标。

将每个游戏手柄的数据根据对应的平台分配到 控制器数据(Controller Data) 数列。你可以将多个游戏手柄关联到一个平台。举个例子,PC游戏通常支持键鼠控制器数据以及一个通常的游戏手柄。然而,你也可以为特定的游戏手柄型号添加控制器数据。

5. Common UI控件库和控件样式

Common UI有一个控件库,位于UMG 调色板(Palette) 的 Common UI插件 部分之下。这里很多都是UI功能的一些部分,在很多游戏和应用中都很常见,包括:

特殊处理过的 日期/时间 和 数字 数值文本框。

导航和可视性辅助,比如 轮播 和 有动画的切换器。

平台辅助,比如 加载防护 和 硬件可视性边界。

提供基本功能的控件,比如按钮和文本,但是需要样式数据资产来调整样式。

这些控件不像它们的UMG对等控件,它们没有风格选项。它们需要引用 Common样式资产,这样可以向所有菜单和HUD应用一致的风格。对样式资产做出的任何更高都会应用到使用该资产的所有Common UI控件上。

要创建一个Common样式资产:

在 内容浏览器(Content Browser) 中右键点击然后创建一个 蓝图(Blueprint),然后选择其中一个Common样式类来作为基础。 image

用样式信息填充其 细节(Details),这样可以将其应用到Common UI控件。这些信息通常和标准的UMG控件的样式选项一样。 image

将资产分配到适当类型的Common UI控件。比如,如果你创建了一个 Common文本样式 资产,那就将其分配到Common文本控件的 样式 域中。

image

你还可以将这些分配到 模板样式(Template Styles),位于 项目设置(Project Settings) > 插件(Plugins) > Common UI编辑器(Common UI Editor)

image

任何没有手动分配样式的Common UI控件都会使用适当的模板样式。这样一来为应用创建全局的默认样式就变得更加简单。

项目设置(Project Settings) > 插件(Plugins) > Common UI框架(Common UI Framework) 菜单包含更多全局资产,包括一个用于加载界面的 默认加载图标材质(Default Throbber Material) 和一个 默认图片资源对象(Default Image Resource Object),可以作为占位图片在UI资产没有完成加载时显示。

image

设计指南

在项目中集成并使用CommonUI的指南。

输入技术指南

了解CommonUI的输入路由系统运作方式的详情。

输入基础知识

了解标准UI输入系统与CommonUI的关联。

输入调试和故障排除

在CommonUI中调试输入代码的提示和技巧

通用绑定操作栏

使用通用绑定操作栏自动显示UI的按钮提示列表。

将CommonUI与增强输入结合使用

了解如何通过CommonUI的输入系统纳入增强输入。

Common UI 实战

将你的 游戏视口客户端类(Game Viewport Client Class) 设置为 CommonGameViewportClient。

image 重启编辑器

新建前端关卡 FrontendMap

创建输入动作数据表 InputActionTable

右键-其他-数据表格-选择 CommonInputActionDataBase 作为你的行结构 image 保存可以在UI识别的所有输入操作

不同的UI可以单独新建输入动作数据表 没有通过操作映射实现,而是通过键实现输入操作绑定。 当有一个输入操作绑定到控件上时,将找到对应的键。

添加一行:确定 绑定键盘 回车 键 keyboard input type info-key-enter

绑定手柄键 gamepad face button bottom 游戏手柄正面按钮下 default gamepad input type info-key-gamepad face button bottom 游戏手柄正面按钮下 image

添加一行:取消 绑定键盘 回退键 键 keyboard input type info-key-回格键

绑定手柄键 gamepad face button right 游戏手柄正面按钮右 default gamepad input type info-key-gamepad face button right 游戏手柄正面按钮右 image

添加一行:TabLeft 绑定键盘Q键 keyboard input type info-key-Q

绑定手柄键 gamepad shoulder left游戏手柄左肩按钮 default gamepad input type info-key-gamepad shoulder left 游戏手柄左肩按钮 image

添加一行: TabRight 绑定键盘R键 keyboard input type info-key-R

绑定手柄键 gamepad shoulder right游戏手柄右肩按钮 default gamepad input type info-key-gamepad shoulder right 游戏手柄右肩按钮 image

gamepad input override 用于重载特殊的手柄,需要有相关的源码才可以选择手柄平台, 默认 generic image

设置图标纹理

texture group-UI 用于UI显示。

基于 CommonInputBaseControllerData 创建 键盘控制器数据绑定 (特定平台的UI元素) 蓝图 ControllerData_PC_Keyboard

image 用来包含按钮图表信息

这是数据蓝图,不需要事件。

类默认值-Input type-mouse and keyboard image

input brush data map-添加4项 对应之前的4个键盘按键输入 image

key-回车键 key brush-图像-对应的按钮纹理 image

key-回格键 key brush-图像-对应的按钮纹理

key-Q key brush-图像-对应的按钮纹理

key-R key brush-图像-对应的按钮纹理

基于 CommonInputBaseControllerData 创建 手柄控制器数据绑定 (特定平台的UI元素) 蓝图 ControllerData_PC_Gamepad

这是数据蓝图,不需要事件。

类默认值-Input type-gamepad gamepad name-generic gamepad display name-gamepad gamepad platform name-windows 可以为mac也设置一份数据 image

input brush data map-添加4项 对应之前的4个手柄按键输入 image

key-gamepad face button bottom 游戏手柄正面按钮下 key brush-图像-对应的按钮纹理 image

key-gamepad face button right 游戏手柄正面按钮右 key brush-图像-对应的按钮纹理

key-gamepad shoulder left 游戏手柄左肩按钮 key brush-图像-对应的按钮纹理

key-gamepad shoulder right 游戏手柄右肩按钮 key brush-图像-对应的按钮纹理

为所有计划支持的输入创建好控制器数据后,这些类都必须添加到它们关联的平台,

位于 项目设置(Project Settings) > 游戏(Game) > 常见输入设置(Common Input Settings) > 平台输入(Platform Input) windows-default-controller data-添加2组

ControllerData_PC_Keyboard ControllerData_PC_Gamepad

image

基于 CommonUIInputData 创建默认导航动作蓝图 InputData

用于设置通用点击和返回按钮操作

类默认值- default click action-数据表格-InputActionTable 行命名-Confirm

default back action-数据表格-InputActionTable 行命名-Cancel

image

将该资产分配到 项目设置(Project Settings) > 游戏(Game) > 通用输入设置(Common Input Settings) > 输入数据(Input Data)-InputData image

现在数据资产全部就绪。开始处理样式资产。

基于 CommonBorderStyle 创建通用边框样式蓝图 GenericBorder

image 不需要事件,只需要样式

设置通用的样式 image

基于 CommonButtonStyle 创建通用边框样式蓝图 GenericButton

设置按钮的悬停,激活,点击,禁用等样式

基于 CommonTextScrollStyle 创建通用边框样式蓝图 GenericTextScroll

基于 CommonTextStyle 创建通用边框样式蓝图 GenericText

基于 UserWidget 新建控件蓝图 TextUserWidget 用以测试

添加 common text 控件 image image image

common text-style-GenericText 使用通用样式资产 image image

将这些通用样式资产分配到 模板样式(Template Styles),

位于 项目设置(Project Settings) > 插件(Plugins) > Common UI编辑器(Common UI Editor)

image 通用控件将默认使用对应的样式,也可以自由覆盖。 可以使项目样式统一。

基于 CommonActivatableWidget 创建通用可激活控件蓝图 UI_Base 作为用户界面容器

image

设计器:

【最好不使用画布,CPU性能消耗太高】

添加覆层控件:Overlay_Root
添加 common activatable widget stack 控件:MenuStack

ActivatableWidget元素的显示堆栈: 只显示和激活堆栈顶部的小部件。其他的都失效了。 当最上面显示的小部件停用时,它会自动移除,并显示/激活前面的条目。 .如果提供了RootContent,则无论处于何种激活状态,都不能将其删除。

这是一种堆栈控件,可以向其中添加层层控件。 并存最上层开始移除控件。先进后出. 用来存放菜单。

填充屏幕, 水平填充,垂直填充。 image

image

与之相反,common activatable widget queue 从最下层开始移除控件,先进先出。

添加 common activatable widget stack 控件:PromptStack

存放所有提示控件 该堆栈在菜单堆栈之上,优先级更高。 image

事件图表:

添加自定义事件: PushMenu PushPrompt

将PushMenu事件的激活的控件类参数推送到菜单堆栈中MenuStack MenuStack PushWidget 将 PushWidget 的 activatable widget class 拖至 PushMenu,为 PushMenu 自动创建参数 activatable widget class 可选:PushMenu事件可以进一步限制参数类型

将PushPrompt事件的激活的控件类参数推送到弹窗堆栈中PromptStack PromptStack PushWidget 将 PushWidget 的 activatable widget class 拖至 PushPrompt,为 PushPrompt自动创建参数 activatable widget class

PromptStack

image

UI_Base 将作为UI容器,为其中推送菜单和弹窗控件。

基于 CommonActivatableWidget 创建主菜单可激活控件蓝图 UI_MainMenu

CommonActivatableWidget 可以激活和停用。 可以为其自定义功能,显示隐藏。 基础UMG控件不具备该功能。

基于 CommonButtonBase 创建按钮控件蓝图 UI_Generic_Button

设计器: 自定义宽高:250 * 60 image image image

UI_Generic_Button-样式-style-GenericButton 之前创建的通用按钮样式资产 image

selectable-取消 按钮是否增加复选框

添加覆层控件:Overlay_Root

添加覆层控件:Common Text : DisplayedText 水平居中对齐,垂直居中对齐 默认使用了通用样式 GenericText image

事件图表:

添加Text类型变量 ButtonText 公开 表示按钮文本,TEXT类型用于本地化。

event pre construct DisplayedText set text(text) ButtonText image

现在设计器中可以设置 ButtonText 属性 image

image

UI_MainMenu 主菜单

设计器:

添加覆层控件:Overlay_Root

添加垂直框控件: 水平向左对齐,垂直填充 image

添加 UI_Generic_Button 控件 * 4

添加 间隔区 控件 * 4 最后一个按钮之前的间隔区设为填充模式,将最后一个按钮挤到底部 image

设置每个按钮的 ButtonText 属性: 新游戏,继续,选项,退出

image image

创建特殊的前端显示玩家控制器蓝图 FrontEndPlayerController 用以跟踪UI

也可以在其他地方跟踪UI ,例如游戏模式。

事件图表: 创建UI容器控件

event beginPlay create widget-UI_Base self

UI容器添加到屏幕上 add to viewport

推入主菜单控件到UI容器的菜单栈中 push menu-UI_MainMenu

BPGraphScreenshot_2024Y-02M-09D-18h-04m-39s-123_00

创建游戏模式基础蓝图 FrontEndGameMode 使用自定义前端显示玩家控制器

类默认值-玩家控制器-FrontEndPlayerController image

FrontendMap 关卡 使用 FrontEndGameMode

打开 FrontendMap 关卡-世界场景设置-游戏模式重载-FrontEndGameMode

image

现在运行游戏将显示主菜单控件

image 鼠标可以选择按钮 image

UI_MainMenu 主菜单

事件图表: 当前控件激活时。【当 UI_MainMenu 主菜单 被推到菜单堆栈上时,将自动激活,这是可激活控件的功能】 event on activated set focus

Get Desired Focus Target 当这个小部件激活时,返回需要关注的小部件。 目标是通用可激活部件. image

必须重载 Get Desired Focus Target 函数 决定如何确定聚焦的目标

当前主菜单激活时,聚焦NewGame_Button按钮 NewGame_Button 控件变量 BPGraphScreenshot_2024Y-02M-09D-18h-18m-31s-542_00 现在运行游戏将聚焦 NewGame_Button按钮 但由于项目默认使用键鼠输入模式,所以 新游戏按钮不会高亮。【而是有轮廓显示】 渲染聚焦规则-从不 将不显示蓝色轮廓

只有设为手柄模式,才会高亮。即悬停状态。

基于 CommonActivatableWidget 创建弹窗可激活控件蓝图 UI_GenericPrompt

激活-is modal-启用 image true 则此小部件被视为输入路由的根节点,而不管其实际父级如何。应该很少需要,但在子部件应该阻止父部件的所有操作处理的情况下很有用,即使它们仍然处于活动状态(例如模态弹出菜单)。 防止弹窗后,仍可以操作其他控件。

设计器: 添加覆层控件:Overlay_Root

添加垂直框控件: 水平居中对齐,垂直填充

添加垂直框控件: 填充模式 水平居中对齐,垂直居中对齐

添加 common border 控件: 添加 common border 控件:

添加 common text 控件:PromptText

添加水平框控件: 填充模式 水平居中对齐,垂直居中对齐

添加 UI_Generic_Button控件

添加 UI_Generic_Button控件

image

image

事件图表

必须重载 Get Desired Focus Target 函数 决定如何确定聚焦的目标 当前弹窗激活时,聚焦是按钮 Yes_Button BPGraphScreenshot_2024Y-02M-09D-19h-25m-11s-563_00

主图表: event on activated set focus

Get Desired Focus Target 当这个小部件激活时,返回需要关注的小部件。

现在,打开从弹窗是,将聚焦按钮 是。

BPGraphScreenshot_2024Y-02M-09D-19h-26m-57s-147_00

设置提示信息 此处仅作测试,应当在C++试过事件广播改变弹窗信息。用来将主菜单点击的按钮的信息传递给弹窗控件使用。 应当使用接口或创建基本菜单类来实现此功能。 添加自定义事件 SetPromptInfo 输入参数1 Text 类型: InPromptText 输入参数2 UI_MainMenu 对象引用类型: PromptOwner 输入参数 2 整数类型 :PromptIndex

添加整数类型变量 :PromptIndex 添加变量 UI_MainMenu 对象引用类型: PromptOwner 表示点击的主菜单按钮,作为弹窗的拥有者

set PromptIndex set PromptOwner PromptText set text

Yes_Button 点击事件 image on button base clicked(yes_Button)

PromptOwner

添加自定义事件 OnPromptConfirm 输入参数 整数 PromptIndex switch on int quit game

OnPromptConfirm PromptIndex BPGraphScreenshot_2024Y-02M-09D-20h-05m-29s-548_00

重载 on handle back action

deactivate widget image

UI_Base

PushPrompt 添加参数 InPromptText InPromptOwner InPromptIndex

PushPrompt-UI_GenericPrompt

set Prompt info

image

FrontEndPlayerController

UI_Base PushPrompt

UI_MainMenu

激活-Auto Restore Focus-启用

关闭弹窗时主菜单之前的按钮重新恢复聚焦 image

WangShuXian6 commented 9 months ago

UMG Viewmodel 视图模型 插件

https://docs.unrealengine.com/5.3/zh-CN/umg-viewmodel/ UI开发人员通常会将后端数据和视觉设计分解成独立的系统。这样做可以在构建用户界面(UI)的过程中减少破坏性,提高构建效率,因为设计人员可以在不破坏UI底层代码的情况下更改视觉呈现,程序员则可以专注于数据和系统,不需要完整的前端。Viewmodel 插件通过引入Viewmodel资产和 视图绑定(View Bindings) 为该工作流程提供了实现途径。

启用 插件(Plugins) 菜单中的 UMG Viewmodel 插件

image

项目设置中禁止低效的旧版绑定系统

防止每一帧都监测设置控件的值。值在控件值变化时更新值。

项目设置-插件-视图模型-Allow Binding from Detail View-取消 image

编辑器-控件设计器(队伍)-编译器-默认编译器选项-属性绑定规则-预防和警告 image

这将禁用控件蓝图中变量的旧版bind选项。

禁止在tick中设置GI中的Viewmodel变量。 不再使用GI

实战

基本项目配置

基于 HUD 类 创建 HUD_Main 蓝图

image

基于角色类创建角色蓝图 BP_CharacterViewmodel

image

基于 GameModeBase 创建游戏模式蓝图 BP_GameMode

image

配置 BP_GameMode 使用 BP_CharacterViewmodel,HUD_Main

image

配置当前地图使用 BP_GameMode image

配当前项目使用游戏实例 GI_GameInstance

项目设置-项目-地图和模式-游戏实例-游戏实例类-点击+号新建 游戏实例 GI_GameInstance image

Viewmodel

Viewmodel的主要用途有两个:

维护你的UI所需变量的清单。

提供你的UI与应用程序其余部分之间的通信途径。

需要让你的UI感知某个变量时,可以将它添加到Viewmodel,然后将该Viewmodel添加到你的控件,并将字段绑定到它。需要更新变量时,你可以获取对该Viewmodel的引用,并从该处设置它们。然后,它们会将更改通知绑定到这些变量的控件并更新控件。

在蓝图中创建 Viewmodel : VM_Stats

在蓝图中通过扩展 MVVMViewModelBase 类来创建Viewmodel image

VM_Stats 中添加浮点类型变量 Health,MaxHealth

image

启用字段通知

要广播对绑定到你的参数的控件的更改,你需要将变量或函数标记为 FieldNotifies 。点击函数或变量旁边的钟形图标,将其设为FieldNotify。 image image

基于 UserWidget 新建控件蓝图 WBP_MainHUD

image

向控件添加Viewmodel VM_Stats

UMG设计器(UMG Designer)选项卡的 窗口(Window) > Viewmodels 视图模型 点击 + Viewmodel 按钮,选择你的项目的其中一个Viewmodel,然后点击 选择(Select) VM_Stats image

image

初始化Viewmodel

窗口-视图模型-VM_Stats-细节面板-创建类型(Creation Type) image

Viewmodel创建类型 说明
创建实例(Create Instance) 该控件会自动创建它自己的Viewmodel实例。
手动(Manual) 该控件在初始化时Viewmodel为null,你需要手动创建一个实例并为其赋值。
全局Viewmodel集合(Global Viewmodel Collection) 指你的项目中的所有控件均可使用的全局可用Viewmodel。需要 全局Viewmodel标识符 。
属性路径(Property Path) 在初始化时,执行一个函数来查找Viewmodel。Viewmodel属性路径 将使用句点分隔的成员名称。例如:GetPlayerController.Vehicle.ViewModel。属性路径始终是相对于控件的路径。

Viewmodel与控件之间不一定存在一一对应的关系。你可用多种方法设置它们,并将它们赋值给控件,而且多个控件可以从单个Viewmodel获取信息。下文详述了每种创建类型方法。

创建实例

创建实例(Create Instance) 创建方法会自动为控件的每个唯一实例创建一个新的Viewmodel实例。这意味着,如果你在视口中有同一控件的数个副本,并且你更改了其中一个副本的Viewmodel变量,则只有该控件会更新,所有其他副本将保持不变。同理,如果你创建多个使用同一Viewmodel的不同控件,这些控件都不会感知到彼此信息的变化。下面介绍的其他方法适用于你希望多个控件引用相同数据的情况。你还可以选择在创建后设置Viewmodel。

你可以在C++中的初始化回调后或在蓝图中的初始化回调期间为Viewmodel赋值。系统只会在Viewmodel未设置时创建新实例。Viewmodel会在PreConstruct和Construct事件之间创建。

这代表控件只关注自身,不关注数据持久性。

手动

手动(Manual) 创建方法需要你在应用程序代码的某个位置自行创建一个Viewmodel实例,然后手动将其赋值给控件。控件具有Viewmodel对象引用,但在你为它赋值Viewmodel之前,它将具有空值。你还可以在Create Widget节点中创建时分配Viewmodel。 image 你赋值Viewmodel后,就可以在想要更新UI时更新它,不用获取对控件的引用。这样就有机会将Actor类中的相同Viewmodel分配给UI中的多个不同控件。

属性路径

属性路径(Property Path) 创建方法提供的替代方案可能更简洁,需要的代码支持也更少。控件并不是让其他类访问其内部来设置其Viewmodel引用,而是通过一系列函数调用和引用向外访问来获取Viewmodel。编辑器中的"属性路径(Property Path)"字段要求一系列句点分隔的成员名称,并且它假定调用这些函数的起始点是 Self 。换言之,它的起始点始终是你正在编辑的控件。

不要在你的属性路径中手动指定 Self ,因为"属性路径(Property Path)"字段已经假定你的起始点是对 Self 的引用。

例如,以下字段会获取控件的所属玩家控制器,然后获取它当前控制的载具上的Viewmodel:

GetPlayerController.Vehicle.ViewModel

你还可以调用在蓝图中定义的函数,这有助于精简属性路径的逻辑并提高灵活性。例如,以下函数会从拥有该控件的角色获取角色生命值Viewmodel:

image 然后你可以使用以下函数的名称作为属性路径:

GetHealthViewModel
全局Viewmodel集合

这是全局唯一的,切换关卡也存在。

全局Viewmodel集合(Global Viewmodel Collection) 是 MVVM游戏子系统(MVVM Game Subsystem) 中可全局访问的Viewmodel列表。这些Viewmodel非常适合用于处理可能需要在你的整个UI中访问的变量,例如你的游戏选项菜单的设置。如需将Viewmodel添加到蓝图中的全局Viewmodel集合,请执行以下步骤:

添加对MVVM游戏子系统的引用。

从MVVM Game Subsystem节点拖移引脚,然后命名为 Get Viewmodel Collection 。

从全局Viewmodel集合拖出一个引脚,然后调用 Add Viewmodel Instance 。

然后你可以构建一个Viewmodel实例,并通过此节点将其添加到集合中。你可在 游戏实例(Game Instance) 类中方便地初始化这些实例。

image

选择"全局Viewmodel集合(Global Viewmodel Collection)"作为初始化模式时,请通过 全局Viewmodel标识符(Global Viewmodel Identifier) 中的 Add Viewmodel Instance 节点提供 上下文名称(Context Name) 。此名称必须与Viewmodel的类名一致。例如,如果你的Viewmodel叫做VM_GraphicsOptions,你需要将其同时用作上下文名称和全局Viewmodel标识符。

WBP_MainHUD 的 VM_Stats 使用手动模式

image

游戏实例 GI_GameInstance 中手动创建视图模型

事件图表 event init 从VM_Stats类创建视图模型 construct object from class construct object from class-class-VM_Stats construct VM_Stats 提升为变量 VM_Stats 这将健康视图模型 VM_Stats 作为变量持久存储在了全局游戏实例中,之后可以从游戏实例直接获取

获取MVVM游戏实例子系统(MVVM Game Subsystem) get MVVM Game Subsystem 从 MVVM游戏子系统 获取视图模型集合 Get View Model Collection

将 健康视图模型 VM_Stats 添加到 MVVM游戏子系统 获取的视图模型集合 中 Add View Model instance Add View Model instance-View Model - VM_Stats Add View Model instance-context-分割结构体引脚

设置添加的视图模型情景为 VM_Stats 必须一致 Add View Model instance-Context Context Class-VM_Stats Add View Model instance-Context Context Name-VM_Stats 此名称必须与Viewmodel的类名一致。例如,如果你的Viewmodel叫做VM_GraphicsOptions,你需要将其同时用作上下文名称和全局Viewmodel标识符。

现在 视图模型 VM_Stats 被持久存储在 游戏实例的MVVM游戏子系统 中 BPGraphScreenshot_2024Y-02M-14D-18h-08m-51s-992_00

之后可以在 游戏实例的MVVM游戏子系统上获取该视图模型 Find View Model Instance 或 Find First View Model Instance of Type BPGraphScreenshot_2024Y-02M-14D-18h-13m-36s-379_00

HUD_Main 中添加 WBP_MainHUD

事件图表: event beginplay

创建 WBP_MainHUD 控件 creat widget creat widget-class-WBP_MainHUD

WBP_MainHUD 提升为变量 MainWIdget

为 WBP_MainHUD 设置 视图模型 VM Stats

获取游戏实例 GI_GameInstance get game instance cast to GI_GameInstance 从 游戏实例 GI_GameInstance 获取 VM Stats get VM Stats 赋值给 WBP_MainHUD -VM Stats

现在 WBP_MainHUD 健康控件将具由视图模型 VM Stats

将创建的控件添加到视图。 add to viewport

BPGraphScreenshot_2024Y-02M-14D-18h-24m-05s-897_00

WBP_MainHUD 将健康值控件绑定到视图模型中的变量

窗口-视图绑定 添加控件 Text_HealthValue image 绑定该控件的文本 image image

选择要绑定的视图模型变量 Health image image image

选择绑定方向 单向到控件 image

勾选 Immediate 表示检测到模型变量更新立刻更新值到控件 image

设置 视图模型到控件的类型转换方式。点击箭头 Conversion Functions-转换为文本(浮点)

image

转换-value-VM Stats-Health image

image

绑定完成

在玩家角色 BP_CharacterViewmodel 中更新视图模型的变量

事件图表: event begin play

获取MVVM游戏实例子系统(MVVM Game Subsystem)

方法1: get MVVM Game Subsystem 从 MVVM游戏子系统 获取视图模型集合 Get View Model Collection Find View Model Instance 或 Find First View Model Instance of Type

方法2: 直接从游戏实例获取 get game instance cast to GI_GameInstance 从 游戏实例 GI_GameInstance 获取 VM Stats get VM Stats

设置视图模型的Health变量并广播 set Health

新的健康值为原值加1 get Health + 1

设置一个定时器,每秒增加1健康值 set timer by event-looping-启用 添加自定义事件 Timer Event BPGraphScreenshot_2024Y-02M-14D-18h-57m-00s-452_00

现在 WBP_MainHUD 的健康值控件每秒都会更新 image 他不会每一帧都更新,只在视图模型变量更新时更新到控件。

VM_Stats 视图模型 添加 随Health 一起更新的变量 Stamina 耐力

添加整形变量 Stamina,启用字段通知 image

选择 Health 变量-细节-字段通知-同时勾选 变量 Stamina 表示 Health 变量 更新时将触发 变量 Stamina 的更新通知

image

VM_Stats 视图模型蓝图中的Viewmodel函数

函数也可以视为FieldNotify。要将函数用作FieldNotify,它必须满足以下条件:

必须是纯函数。

必须标记为Const。

必须仅返回一个值。

不能接受输入变量。

例如,你可以创建一个Getter函数,返回一个角色的当前生命值相较于最大生命值的百分比。如果你不想创建单独的变量来保存角色的生命值百分比值,这是很好的备用选项。

添加浮点变量MaxHealth 默认值100

添加函数 HealthPercentGetter image

HealthPercentGetter 细节面板-图表-纯函数-启用 高级-常量-启用

事件图表: 添加返回值 add return node

返回健康百分比 get Health get MaxHealth divide

multiply 100

BPGraphScreenshot_2024Y-02M-14D-22h-04m-19s-714_00

注意: 此处必须保证逻辑正确。 例如,如果使用整数相除,结果应为浮点类型,但实际上此时有bug,结果仍显示为整数,这将造成绑定到控件时无法正确转换,导致绑定失效。健康百分比将不会更新。 所以大部分情况下,应使用浮点类型。

此时可以启用字段通知 细节面板-字段通知-启用 image

VM_Stats 视图模型 使用 Health FieldNotify变量触发其他 HealthPercentGetter FieldNotify

Health-细节面板-字段通知-同时勾选 HealthPercentGetter image

WBP_MainHUD 控件绑定 视图模型 的 HealthPercentGetter 字段通知函数

设计器: 添加健康百分比部分控件 image

窗口-视图绑定-添加控件 Text_HealthPercentValue image

控件部分 绑定 Text_HealthPercentValue文本

绑定到视图模型 VM_Stats-HealthPercentGetter image

使用转换为文本函数转换类型 启用 Immediate image

现在 健康百分比将随着健康值一同变化 image

C++ 中创建视图模型 VMMana

要想在C++中创建Viewmodel,实现 INotifyFieldValueChanged 接口即可。你还可以扩展类 UMVVMViewModelBase ,后者默认实现该接口。Viewmodel是 UObject ,依赖FieldNotify变量和函数将更改广播给绑定到它们的控件。

基于 UMVVMViewModelBase 创建 VMMana C++ image

image

Source/Sample/ViewModel/VMMana.h


#pragma once

#include "CoreMinimal.h"
#include "MVVMViewModelBase.h"
#include "VMMana.generated.h"

// Blueprintable 必须写,默认的VM不支持蓝图
UCLASS(Blueprintable)
class SAMPLE_API UVMMana : public UMVVMViewModelBase
{
    GENERATED_BODY()

public:
    UFUNCTION(BlueprintPure, FieldNotify)
    float GetManaPercent() const;

    void SetCurrentMana(float NewCurrentMana);

    void SetMaxMana(float NewCurrentMana);

protected:
    UPROPERTY(BlueprintReadWrite, FieldNotify, Setter, Getter)
    float CurrentMana;

private:
    UPROPERTY(BlueprintReadOnly, FieldNotify, Setter, Getter, meta=(AllowPrivateAccess))
    float MaxMana = 100;

    float GetCurrentMana() const;

    float GetMaxMana() const;
};

Source/Sample/ViewModel/VMMana.cpp


#include "VMMana.h"

float UVMMana::GetManaPercent() const
{
    //检查以避免除零错误

    if (MaxMana != 0)
    {
        return  CurrentMana /  MaxMana;
    }
    return 0;
}

void UVMMana::SetCurrentMana(float const NewCurrentMana)
{
    if (UE_MVVM_SET_PROPERTY_VALUE(CurrentMana, NewCurrentMana))
    {
        // 此处编辑器会报错 无法解析符号'GetManaPercent' 忽略即可 不影响编译
        UE_MVVM_BROADCAST_FIELD_VALUE_CHANGED(GetManaPercent);
    }
}

float UVMMana::GetCurrentMana() const
{
    return CurrentMana;
}

void UVMMana::SetMaxMana(float const NewMaxMana)
{
    if (UE_MVVM_SET_PROPERTY_VALUE(MaxMana, NewMaxMana))
    {
        // 此处编辑器会报错 无法解析符号'GetManaPercent' 忽略即可 不影响编译
        UE_MVVM_BROADCAST_FIELD_VALUE_CHANGED(GetManaPercent);
    }
}

float UVMMana::GetMaxMana() const
{
    return MaxMana;
}

GI_GameInstance 游戏实例中 手动创建视图模型VMMana,添加到 MVVM游戏子系统 获取的视图模型集合 中

BPGraphScreenshot_2024Y-02M-15D-00h-24m-53s-421_00

Add View Model instance-Context Context Name-VMMana 与c++模型视图类名一致 不包括U前缀

玩家中 BP_CharacterViewmodel 每秒更新一次 视图模型VMMana 中的 CurrentMana

image BPGraphScreenshot_2024Y-02M-15D-00h-26m-19s-227_00

WBP_MainHUD 控件中添加 魔力部分控件

image

窗口-视图模型-添加 C++ 版本的视图模型 VMMana image image

窗口-视图绑定 为魔力值控件 Text_ManaValue 绑定 CurrentMana 模型变量 为魔力值百分比控件 Text_ManaPercentValue 绑定 GetCurrentMana 模型变量

image

HUD_Main 从游戏实例中去获取 视图模型 VMMana 然后赋值给 WBP_MainHUD 控件

image BPGraphScreenshot_2024Y-02M-15D-00h-19m-46s-156_00

现在 魔力值也将更新。 魔力值百分比也会随着魔力值更新 image

C++ 中 Viewmodel 配合 CommonUI

VM

Source/Ro2ea/Public/Viewmodel/VMAttribute.h

#pragma once

#include "CoreMinimal.h"
#include "MVVMViewModelBase.h"
#include "VMAttribute.generated.h"

UCLASS(Blueprintable)
class RO2EA_API UVMAttribute : public UMVVMViewModelBase
{
    GENERATED_BODY()

public:
    UFUNCTION(BlueprintPure, FieldNotify)
    float GetManaPercent() const;

    void SetMana(float NewMana);

    void SetMaxMana(float NewMaxMana);

    float GetMana() const;

    float GetMaxMana() const;

protected:
    UPROPERTY(BlueprintReadWrite, FieldNotify, Setter, Getter)
    float Mana=50;

private:
    UPROPERTY(BlueprintReadOnly, FieldNotify, Setter, Getter, meta=(AllowPrivateAccess))
    float MaxMana = 100;
};

Source/Ro2ea/Private/Viewmodel/VMAttribute.cpp

#include "Viewmodel/VMAttribute.h"

float UVMAttribute::GetManaPercent() const
{
    //检查以避免除零错误

    if (MaxMana != 0)
    {
        return Mana / MaxMana;
    }
    return 0;
}

void UVMAttribute::SetMana(float NewMana)
{
    if (UE_MVVM_SET_PROPERTY_VALUE(Mana, NewMana))
    {
        // 此处编辑器会报错 无法解析符号'GetManaPercent' 忽略即可 不影响编译
        UE_MVVM_BROADCAST_FIELD_VALUE_CHANGED(GetManaPercent);
    }
}

void UVMAttribute::SetMaxMana(float NewMaxMana)
{
    if (UE_MVVM_SET_PROPERTY_VALUE(MaxMana, NewMaxMana))
    {
        // 此处编辑器会报错 无法解析符号'GetManaPercent' 忽略即可 不影响编译
        UE_MVVM_BROADCAST_FIELD_VALUE_CHANGED(GetManaPercent);
    }
}

float UVMAttribute::GetMana() const
{
    return Mana;
}

float UVMAttribute::GetMaxMana() const
{
    return MaxMana;
}

游戏实例中初始化 VM

Source/Ro2ea/Public/Game/RoGameInstance.h

#pragma once

#include "CoreMinimal.h"
#include "Engine/GameInstance.h"
#include "RoGameInstance.generated.h"

class UVMLogin;
class UVMAttribute;

UCLASS()
class RO2EA_API URoGameInstance : public UGameInstance
{
    GENERATED_BODY()

public:
    virtual void Init() override;

    TObjectPtr<UVMAttribute> GetVMAttribute();

    TObjectPtr<UVMLogin> GetVMLogin();

protected:
    UPROPERTY(EditAnywhere, Category="VM")
    TSubclassOf<UVMLogin> VMLoginClass;

    UPROPERTY(EditAnywhere, Category="VM")
    TSubclassOf<UVMAttribute> VMAttributeClass;

public:
    UPROPERTY(BlueprintReadWrite)
    TObjectPtr<UVMLogin> VMLogin;

    UPROPERTY(BlueprintReadWrite)
    TObjectPtr<UVMAttribute> VMAttribute;
};

Source/Ro2ea/Private/Game/RoGameInstance.cpp

#include "Game/RoGameInstance.h"
#include "MVVMGameSubsystem.h"
#include "MVVMSubsystem.h"
#include "Viewmodel/VMLogin.h"
#include "Viewmodel/VMAttribute.h"

void URoGameInstance::Init()
{
    Super::Init();
    UMVVMGameSubsystem* MVVMGameSubsystem = GetSubsystem<UMVVMGameSubsystem>();
    if (!IsValid(MVVMGameSubsystem)) return;
    UMVVMViewModelCollectionObject* ViewModelCollection = MVVMGameSubsystem->GetViewModelCollection();

    check(VMLoginClass);
    FMVVMViewModelContext VMLoginContext;
    VMLoginContext.ContextClass=VMLoginClass;
    VMLoginContext.ContextName="VMLogin";
    VMLogin = NewObject<UVMLogin>(this, VMLoginClass);
    bool bAddVMLoginSuccess = ViewModelCollection->AddViewModelInstance(VMLoginContext, VMLogin);
    //if (!bSuccess)return;

    check(VMAttributeClass);
    FMVVMViewModelContext VMAttributeContext;
    VMAttributeContext.ContextClass=VMAttributeClass;
    VMAttributeContext.ContextName="VMAttribute";
    VMAttribute = NewObject<UVMAttribute>(this, VMAttributeClass);
    bool bAddVMAttributeSuccess = ViewModelCollection->AddViewModelInstance(VMAttributeContext, VMAttribute);
}

TObjectPtr<UVMAttribute> URoGameInstance::GetVMAttribute()
{
    return VMAttribute;
}

TObjectPtr<UVMLogin> URoGameInstance::GetVMLogin()
{
    return VMLogin;
}

玩家控制器中更新VM属性值

每隔一秒加1

Source/Ro2ea/Public/Player/LoginPlayerController.h

#pragma once

#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "LoginPlayerController.generated.h"

UCLASS()
class RO2EA_API ALoginPlayerController : public APlayerController
{
    GENERATED_BODY()

public:
    ALoginPlayerController();
    virtual void SetupInputComponent() override;

protected:
    virtual void BeginPlay() override;

    void IncreaseMana();
};

Source/Ro2ea/Private/Player/LoginPlayerController.cpp

#include "Player/LoginPlayerController.h"
#include "EnhancedInputSubsystems.h"
#include "EnhancedInputComponent.h"
#include "Engine/LocalPlayer.h"
#include "GameFramework/Controller.h"
#include "InputActionValue.h"
#include "Game/RoGameInstance.h"
#include "Viewmodel/VMAttribute.h"

ALoginPlayerController::ALoginPlayerController()
{
}

void ALoginPlayerController::SetupInputComponent()
{
    Super::SetupInputComponent();
    //强制转换输入组件为增强型输入组件 失败则程序崩溃 确保输入组件未被破坏
    //#include "EnhancedInputComponent.h"
    //UEnhancedInputComponent* EnhancedInputComponent = CastChecked<UEnhancedInputComponent>(InputComponent);
}

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

    //显示光标
    bShowMouseCursor = true;
    bEnableClickEvents = true;
    bEnableMouseOverEvents = true;
    //设置光标类型
    DefaultMouseCursor = EMouseCursor::Default;
    //设置输入模式 可使用鼠标和键盘,并且影响UI部件
    FInputModeGameAndUI InputModeData;
    // 锁定模式:不将鼠标锁定在视口上
    InputModeData.SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock);
    //获取输入模式数据并在捕获期间使用设置隐藏光标为 false。
    //因此,一旦我们的光标被捕获到视口中,我们就不会隐藏光标。
    InputModeData.SetHideCursorDuringCapture(false);
    //为了使用此输入模式数据,我们使用玩家控制器函数设置输入模式传递
    SetInputMode(InputModeData);

    //
    FTimerHandle TimerHandle;
    float TimeDelay = 1.0f; // 定时器触发之前的延迟时间(秒)

    // 设置一个循环定时器
    GetWorld()->GetTimerManager().SetTimer(TimerHandle, this, &ALoginPlayerController::IncreaseMana, TimeDelay, true);
}

void ALoginPlayerController::IncreaseMana()
{
    UGameInstance* _GI = GetGameInstance();
    if (!IsValid(_GI))return;
    URoGameInstance* GI = Cast<URoGameInstance>(_GI);
    if (!IsValid(GI))return;
    UVMAttribute* VMAttribute = GI->GetVMAttribute();
    if(!IsValid(VMAttribute))return;
    VMAttribute->SetMana(VMAttribute->GetMana() + 1);
    VMAttribute->SetMaxMana(VMAttribute->GetMaxMana() + 2);
}

HUD中添加控件 OverlayWidget

Source/Ro2ea/Public/UI/HUD/RoHUD.h

#pragma once

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

class UAttributeSet;
class UAbilitySystemComponent;
class UOverlayWidgetController;
class URoUserWidget;
struct FWidgetControllerParams;

UCLASS()
class RO2EA_API ARoHUD : public AHUD
{
    GENERATED_BODY()

public:

    UPROPERTY()
    TObjectPtr<URoUserWidget>  OverlayWidget;

    // 游戏中只有一个 OverlayWidgetController /单例
    UOverlayWidgetController* GetOverlayWidgetController(const FWidgetControllerParams& WCParams);

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

protected:
    virtual void BeginPlay() override;

private:

    UPROPERTY(EditAnywhere)
    TSubclassOf<URoUserWidget> OverlayWidgetClass;

    UPROPERTY()
    TObjectPtr<UOverlayWidgetController> OverlayWidgetController;

    // 在蓝图子类中指定
    UPROPERTY(EditAnywhere)
    TSubclassOf<UOverlayWidgetController> OverlayWidgetControllerClass;
};

Source/Ro2ea/Private/UI/HUD/RoHUD.cpp

#include "UI/HUD/RoHUD.h"
#include "UI/Widget/RoUserWidget.h"
#include "UI/WidgetController/OverlayWidgetController.h"

UOverlayWidgetController* ARoHUD::GetOverlayWidgetController(const FWidgetControllerParams& WCParams)
{
    // 游戏中只有一个 OverlayWidgetController /单例
    if (OverlayWidgetController == nullptr)
    {
        OverlayWidgetController = NewObject<UOverlayWidgetController>(this, OverlayWidgetControllerClass);
        OverlayWidgetController->SetWidgetControllerParams(WCParams);

        return OverlayWidgetController;
    }
    return OverlayWidgetController;
}

void ARoHUD::InitOverlay(APlayerController* PC, APlayerState* PS, UAbilitySystemComponent* ASC, UAttributeSet* AS)
{
    // checkf 检查并打印数据到日志文件 
    // 蓝图子类中需要覆盖设置 控件类和控件控制器类
    checkf(OverlayWidgetClass, TEXT("Overlay Widget Class uninitialized, please fill out BP_RoHUD"));
    checkf(OverlayWidgetControllerClass,
           TEXT("Overlay Widget Controller Class uninitialized, please fill out BP_RoHUD"));

    UUserWidget* Widget = CreateWidget<UUserWidget>(GetWorld(), OverlayWidgetClass);
    OverlayWidget = Cast<URoUserWidget>(Widget);

    // 游戏开始时,begin paly 时,还不能访问 玩家控制器,玩家状态,技能系统组件,属性集。
    // 所以在自定义方法 InitOverlay 中设置。
    const FWidgetControllerParams WidgetControllerParams(PC, PS, ASC, AS);
    UOverlayWidgetController* WidgetController = GetOverlayWidgetController(WidgetControllerParams);

    // 为覆盖控件设置控件控制器
    OverlayWidget->SetWidgetController(WidgetController);
    // 将覆盖控件添加到视图
    Widget->AddToViewport();
}

void ARoHUD::BeginPlay()
{
    Super::BeginPlay();
    UUserWidget* Widget = CreateWidget<UUserWidget>(GetWorld(), OverlayWidgetClass);
    Widget->AddToViewport();
}

OverlayWidget 控件中绑定VM变量

image

image

OverlayWidget 控件中从游戏实例中获取VM后设置到控件自身的VM变量

image

注意,设置具体的vm之前需要确保vm有效,例如 VMAttribute

BPGraphScreenshot_2024Y-03M-02D-01h-20m-58s-182_00

WangShuXian6 commented 8 months ago

GameplayMessageRouter 通信插件

启用插件 Gameplay Message subsystem

Gameplay Message subsystem 复制 插件目录的下的 GameplayMessageRouter 插件文件夹

启用插件 Gameplay Message subsystem image

自定义消息标签

Message.Prompt.Show 弹窗 Message.Prompt.Hide 关闭弹窗

自定义消息类型

Source/Ro2ea/Public/RoMessageTypes.h

#pragma once
#include "RoMessageTypes.generated.h"

UENUM(BlueprintType)
enum class EPromptType : uint8
{
    QuitGame,
    ClosePrompt
};

USTRUCT(BlueprintType)
struct FRoMessageTypes
{
    GENERATED_BODY()

};

USTRUCT(BlueprintType)
struct FPromptMessageTypes
{
    GENERATED_BODY()

    UPROPERTY(BlueprintReadWrite)
    FString Message;

    UPROPERTY(BlueprintReadWrite)
    EPromptType Confirm;

    UPROPERTY(BlueprintReadWrite)
    bool bShowCancel;
};

Source/Ro2ea/Private/RoMessageTypes.cpp

#include "RoMessageTypes.h"

监听指定游戏标签对应的消息

例如,在全局UI控件WBP_Overlay的构造事件中监听弹窗消息

图表: Listen for Gameplay Messages Listen for Gameplay Messages- Channel-Message.Prompt.Show 消息频道,通过游戏标签区分: 弹窗标签 Listen for Gameplay Messages-PayloadType-PromptMessageTypes 消息携带的信息的结构体 Listen for Gameplay Messages-Match Type-Exact Match 监听与广播的游戏标签必须完全一致

PushPrompt 将弹窗推入弹窗栈,然后获取推入的弹窗,为其设置信息 BPGraphScreenshot_2024Y-03M-01D-01h-34m-10s-732_00

BPGraphScreenshot_2024Y-03M-01D-01h-35m-14s-998_00

发送消息

游戏实例子系统-get Gameplay Message subsystem

Broadcast Message Broadcast Message-Channel-Message.Prompt.Show Broadcast Message-Message

make PromptMessageTypes 创建消息结构体 表示弹窗的信息 image

弹窗控件接收消息,以设置弹窗

弹窗 取消按钮 点击事件 停用当前弹窗控件,然后移除弹窗 image

弹窗 确认按钮 根据设置的消息类型为按钮添加事件 ConfirmClick BPGraphScreenshot_2024Y-03M-01D-01h-37m-09s-411_00

设置弹窗 BPGraphScreenshot_2024Y-03M-01D-01h-38m-15s-356_00

bind event to on button base clicked-event-事件分发器-create event-创建匹配函数