news 2026/9/30 9:58:16

UE5 C++ UMG UI实战:动态控件、数据绑定与列表刷新

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
UE5 C++ UMG UI实战:动态控件、数据绑定与列表刷新

这次我们来讲一个很多 UE5 开发者会卡住的方向:用 C++ 写“前端 UI”。

不是拖几个蓝图节点去拼界面,而是用 C++ 直接创建控件、绑定数据、响应事件,做一套具备复用性、可维护性、能对接网络数据和批量列表的 UMG UI 系统。这个方向在项目里到底怎么落地,怎么调试,性能和生命周期要注意什么,这篇直接给你拆开讲。

1. UMG C++ UI 编程核心能力速览

能力项说明
技术方向虚幻引擎 5 UMG 与 Slate 之上的 C++ UI 开发
主要功能动态创建 Widget、UMG 数据绑定、按钮事件绑定、列表批量刷新、动态加载资源、HTTP 数据接入
开发语言C++,蓝图可作为辅助配合
推荐环境Windows 10/11 + Visual Studio 2022 + UE5.0 及以上版本
硬件门槛CPU 多核,内存 16 GB 起步;GPU 取决于编辑器分辨率和 UI 特效复杂度
批量任务支持动态生成列表,支持批量刷新 Widget 数据
接口 API支持接入 HTTP/JSON 服务,需在工程模块中启用 HTTP、Json 模块
调试方式编辑器日志、VS 断点、stat UMG、stat Slate
适合场景复杂主界面、数据驱动列表、战斗 HUD、大世界交互 UI、网络数据展示

表格先给你判断的一个框架。下面所有内容,都围绕“用 C++ 把这些能力做出来”这条主线展开。

2. 适用场景与使用边界

2.1 这套方案适合谁

  • 项目里 UI 数量多、界面复杂,蓝图节点多到难以维护。
  • 需要频繁刷新数据,例如任务系统、背包、商店、排行榜。
  • 需要动态生成同一结构的控件,例如列表项、消息滚动条、多页签内容。
  • 需要把 UI 操作和网络请求、业务逻辑分开管理。
  • 团队有 C++ 开发能力,希望 UI 层也走代码评审、版本管理和复用逻辑。

2.2 能解决什么问题

用 C++ 写 UMG 最直接的价值是:把界面逻辑从蓝图里搬出来,用代码控制控件的创建、销毁和刷新。这样可以做到:

  • 控件结构统一。列表项、按钮、文本、输入框都按同一个 C++ 类生成,而不是在蓝图层复制粘贴。
  • 数据驱动。界面不再关心业务数据从哪里来,只负责接收数据并刷新。
  • 逻辑可测试。Widget 控制器可以用纯逻辑函数处理数据,方便断点和单元化验证。
  • 加载更灵活。可以按模块动态加载资产,不需要所有 UI 都常驻内存。

2.3 不适合什么场景

  • 快速原型验证:团队只是想看一下交互效果,蓝图拖节点效率远高于写 C++。
  • 策划频繁调整布局和样式:纯 C++ 创建的布局每次调整都要重新编译,不利于试错。
  • 没有 C++ 经验的团队:维护成本会比蓝图更高。

更合理的做法是:静态布局用 UMG 设计器摆放,动态结构和数据处理用 C++ 控制。两者配合,而不是谁替代谁。

3. 环境准备与前置条件

3.1 安装 Visual Studio 2022

在 Epic Games Launcher 安装引擎前,先确认 VS 环境可用。

安装 VS 2022 时建议勾选:

  • 使用 C++ 的桌面开发
  • MSVC v143 生成工具
  • Windows 10/11 SDK
  • 用于虚幻引擎的 C++ 工具(如果有该组件)

安装完成后重启一次电脑,让环境变量生效。

3.2 安装虚幻引擎 5

通过 Epic Games Launcher 安装最新稳定版 UE5。项目创建时选择“游戏”类,然后选择“Blank”模板。

关键点:模板本身没有特别重要,因为 UI 系统完全由自己创建。但创建项目时必须确保可以使用 C++。

方法一:直接创建 C++ 项目。

方法二:如果已经建好蓝图项目,在项目文件夹右键“服务”,选择“添加 C++ 类”,让引擎生成一个 C++ 类并更新项目文件。这种方式会自动补全模块依赖。

3.3 检查模块依赖

用 VS 或 IDE 打开项目的Build.cs文件,路径在:

Source/<项目名>/<项目名>.Build.cs

要使用 UMG、HTTP、Json,需要在PublicDependencyModuleNames或PrivateDependencyModuleNames中增加模块。

示例:

PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "UMG", "Slate", "SlateCore", "HTTP", "Json", "JsonUtilities", "HeadMountedDisplay" // 按模板自带情况保留 });

修改后,如果项目文件没有自动更新,可以右键.uproject文件选择“Generate Visual Studio project files”,重新生成解决方案。

3.4 磁盘与性能

UE5 编辑器本身占用空间较大,建议预留 100 GB 以上磁盘空间。首次编译 C++ 工程需要等待较长时间,后续增量编译会明显加快。

4. 安装部署与启动方式

4.1 创建 UI 目录结构

进入Source/<项目名>/目录,建议按以下结构组织 UI 代码:

Source/ <项目名>/ UI/ Widgets/ UI_BaseWidget.h/.cpp UI_ItemWidget.h/.cpp UI_ListViewPage.h/.cpp Controllers/ UI_MainController.h/.cpp Data/ UI_ItemData.h/.cpp

这种分层的目的是:控件类只负责展示,控制器类负责数据和事件,数据类承载业务模型。后面所有测试代码都会按这个分层来写。

4.2 创建第一个 C++ Widget 类

在编辑器中点击菜单:

Tools -> New C++ Class -> 父类选择 UserWidget -> 命名为 UI_BaseWidget

生成后,默认类大概是这样:

#pragma once #include "CoreMinimal.h" #include "Blueprint/UserWidget.h" #include "UI_BaseWidget.generated.h" UCLASS() class YOURPROJECT_API UUI_BaseWidget : public UUserWidget { GENERATED_BODY() };

这个类可以有蓝图子类,也可以直接在 C++ 中动态创建。下一节会写具体的创建和展示流程。

4.3 启动编辑器验证编译链路

C++ 工程创建完成后,用 VS 打开解决方案,右键项目“生成”。生成完成后,回到 Epic Launcher 或项目目录,打开编辑器。

如果一切正常,编辑器启动后会加载刚才创建的 C++ 类。这一步只验证一件事:C++ 编译、UHT(Unreal Header Tool)扫描、编辑器加载三个环节都通。

5. 用 C++ 动态创建 Widget 并绑定事件

这一节是核心,所有功能都从这里展开。建议新建一个普通 Actor 来模拟 UI 管理器,避免直接塞进 GameMode。

5.1 创建 UI 管理器类

新建 C++ 类,父类选择 Actor:

#pragma once #include "CoreMinimal.h" #include "GameFramework/Actor.h" #include "UI_MainController.generated.h" class UUI_BaseWidget; UCLASS() class YOURPROJECT_API AUI_MainController : public AActor { GENERATED_BODY() public: AUI_MainController(); virtual void BeginPlay() override; UFUNCTION(BlueprintCallable, Category = "UI") void ShowMainUI(); private: UPROPERTY() TObjectPtr<UUI_BaseWidget> MainWidget; };

5.2 动态创建并添加到屏幕

在BeginPlay中创建 Widget:

#include "UI_MainController.h" #include "UI_Widgets/UI_BaseWidget.h" #include "Blueprint/UserWidget.h" AUI_MainController::AUI_MainController() { PrimaryActorTick.bCanEverTick = false; } void AUI_MainController::BeginPlay() { Super::BeginPlay(); ShowMainUI(); } void AUI_MainController::ShowMainUI() { if (!MainWidget) { TSubclassOf<UUserWidget> WidgetClass = UUI_BaseWidget::StaticClass(); MainWidget = CreateWidget<UUI_BaseWidget>(GetWorld(), WidgetClass); } if (MainWidget) { MainWidget->AddToViewport(10); } }

这是动态创建 Widget 的最小闭环:CreateWidget+AddToViewport。调试时可以观察MainWidget是否为空,如果为空优先检查WidgetClass是否为有效蓝图类。

5.3 在 C++ 类中手动创建按钮和文本

上面的UI_BaseWidget并没有内容。现在给它增加一个SButton。这里有两种做法,一种是创建 UMG 的 C++ 版控件,另一种是直接创建 Slate 按钮。先从 UMG 控件开始,保持与蓝图一致。

修改UI_BaseWidget:

#pragma once #include "CoreMinimal.h" #include "Blueprint/UserWidget.h" #include "Components/Button.h" #include "Components/TextBlock.h" #include "UI_BaseWidget.generated.h" UCLASS() class YOURPROJECT_API UUI_BaseWidget : public UUserWidget { GENERATED_BODY() protected: virtual void NativeConstruct() override; UPROPERTY(meta = (BindWidget)) TObjectPtr<UButton> ConfirmButton; UPROPERTY(meta = (BindWidget)) TObjectPtr<UTextBlock> TitleText; UFUNCTION() void OnConfirmClicked(); };

注意BindWidget的用法:如果这个 C++ 类有蓝图子类,那么蓝图里必须存在同名按钮和文本,否则编辑器会警告。如果完全在 C++ 中创建控件,则不需要BindWidget,而要用WidgetTree动态添加。

NativeConstruct中动态添加控件:

#include "UI_BaseWidget.h" #include "Components/CanvasPanel.h" #include "Components/CanvasPanelSlot.h" void UUI_BaseWidget::NativeConstruct() { Super::NativeConstruct(); if (!ConfirmButton && WidgetTree) { UCanvasPanel* RootCanvas = WidgetTree->ConstructWidget<UCanvasPanel>(UCanvasPanel::StaticClass()); WidgetTree->SetRootWidget(RootCanvas); ConfirmButton = WidgetTree->ConstructWidget<UButton>(UButton::StaticClass()); ConfirmButton->OnClicked.AddDynamic(this, &UUI_BaseWidget::OnConfirmClicked); TitleText = WidgetTree->ConstructWidget<UTextBlock>(UTextBlock::StaticClass()); TitleText->SetText(FText::FromString(TEXT("UI System Ready"))); TitleText->SetColorAndOpacity(FSlateColor(FLinearColor::White)); if (UCanvasPanelSlot* ConfirmSlot = RootCanvas->AddChildToCanvas(ConfirmButton)) { ConfirmSlot->SetPosition(FVector2D(100.f, 100.f)); } if (UCanvasPanelSlot* TextSlot = RootCanvas->AddChildToCanvas(TitleText)) { TextSlot->SetPosition(FVector2D(100.f, 50.f)); } } } void UUI_BaseWidget::OnConfirmClicked() { // 事件绑定测试,后续可以替换成实际业务逻辑 if (TitleText) { TitleText->SetText(FText::FromString(TEXT("Button Clicked!"))); } }

需要包含库:

#include "Blueprint/WidgetTree.h" #include "Components/CanvasPanel.h" #include "Components/CanvasPanelSlot.h" #include "Components/Button.h" #include "Components/TextBlock.h"

这里重点观察WidgetTree是否为空。如果为空,表示 Widget 不是通过正常流程创建的,需要检查创建方式。

5.4 测试步骤

  1. 将AUI_MainController拖入当前 Level。
  2. 启动 PIE。
  3. 观察是否出现按钮和文本。
  4. 点击按钮,观察文本是否变为Button Clicked!。

判断标准:

  • PIE 启动后 1 秒内看到 UI 元素。
  • 点击按钮后文本刷新。
  • 编辑器输出日志无空指针引用。
  • Widget 不显示且无日志,优先看Visibility是否为SelfHitTestInvisible或Collapsed。

常见问题:

  • BindWidget与动态创建冲突:确认当前类是否有蓝图子类。
  • 按钮不响应点击:检查是否使用AddToViewport,以及是不是在无鼠标模式下测试。
  • 编译失败:检查Build.cs是否添加UMG、Slate、SlateCore模块。

6. 数据绑定与列表批量刷新

UI 进入项目后,核心需求通常不是单个按钮,而是列表、数据、动态内容。

6.1 定义列表项数据结构

新建一个普通 UObject 数据结构UI_ItemData:

USTRUCT(BlueprintType) struct FUI_ItemInfo { GENERATED_BODY() UPROPERTY(BlueprintReadOnly) FString ItemName; UPROPERTY(BlueprintReadOnly) int32 Count = 0; UPROPERTY(BlueprintReadOnly) FLinearColor ItemColor = FLinearColor::White; };

列表项 Widget 负责接收这个数据并刷新自身显示。

新建UI_ItemWidget:

UCLASS() class YOURPROJECT_API UUI_ItemWidget : public UUserWidget { GENERATED_BODY() public: UFUNCTION(BlueprintCallable, Category = "UI|Data") void SetupItemData(const FUI_ItemInfo& Info); protected: UPROPERTY(meta = (BindWidget)) TObjectPtr<UTextBlock> NameText; UPROPERTY(meta = (BindWidget)) TObjectPtr<UTextBlock> CountText; UPROPERTY(meta = (BindWidget)) TObjectPtr<UImage> IconImage; };

实现:

void UUI_ItemWidget::SetupItemData(const FUI_ItemInfo& Info) { if (NameText) { NameText->SetText(FText::FromString(Info.ItemName)); } if (CountText) { CountText->SetText(FText::FromString(FString::Printf(TEXT("x%d"), Info.Count))); } if (IconImage) { IconImage->SetColorAndOpacity(Info.ItemColor); } }

这种模式很简单,但它是所有复杂 UI 的基础:数据对象传入 Widget,Widget 只做显示,不做业务判断。

6.2 动态生成列表

使用UVerticalBox动态添加子项。优势是逻辑直观,适合数量较少的列表。如果列表数量很大,需要换成UListView配合 EntryWidget 使用。

#include "Components/VerticalBox.h" #include "UI_Widgets/UI_ItemWidget.h" void UUI_ListViewPage::RebuildList(const TArray<FUI_ItemInfo>& Items) { if (!ItemListBox) { return; } ItemListBox->ClearChildren(); for (const FUI_ItemInfo& Info : Items) { UUI_ItemWidget* ItemWidget = CreateWidget<UUI_ItemWidget>(GetOwningPlayer(), UUI_ItemWidget::StaticClass()); if (ItemWidget) { ItemWidget->SetupItemData(Info); ItemListBox->AddChildToVerticalBox(ItemWidget); } } }

清空再生成是一种稳定做法。如果每秒只刷新一次,数据量在几十条级别,这种方式完全够用。

6.3 使用 UListView 做大批量数据展示

当列表项数量超过几百条,或者需要滚动裁剪时,应该使用UListView。

#include "Components/ListView.h" UPROPERTY(meta = (BindWidget)) TObjectPtr<UListView> ItemListView; void UUI_ListViewPage::SetupListView(const TArray<FUI_ItemInfo>& Items) { if (!ItemListView) { return; } ItemListView->ClearListItems(); TArray<UObject*> ItemObjects; for (const FUI_ItemInfo& Info : Items) { UUI_ItemDataObject* DataObject = NewObject<UUI_ItemDataObject>(); DataObject->Info = Info; ItemObjects.Add(DataObject); } ItemListView->SetListItems(ItemObjects); }

UListView的 EntryWidget 需要在蓝图或 C++ 中指定。如果列表项 Widget 逻辑简单,建议用 C++ 创建对应 Entry 类,避免蓝图层数据回传困难。

6.4 大批量测试要点

测试数据准备:

TArray<FUI_ItemInfo> GenerateTestData(int32 Count) { TArray<FUI_ItemInfo> Result; Result.Reserve(Count); for (int32 i = 0; i < Count; ++i) { FUI_ItemInfo Info; Info.ItemName = FString::Printf(TEXT("Item_%d"), i); Info.Count = i * 3 + 1; Result.Add(Info); } return Result; }

预期结果:1000 条数据在垂直列表中滚动无明显卡顿。如果出现明显掉帧,观察stat UMG和stat Slate数据。

7. 接口 API 调用与实时数据刷新

UI 本身不产生数据,数据通常来自后端接口。下面给出 UE5 原生 HTTP 调用的基础模型。

7.1 发送 GET 请求

在 UI 控制器中添加请求接口:

#include "HttpModule.h" #include "Interfaces/IHttpRequest.h" #include "Interfaces/IHttpResponse.h" #include "JsonObjectConverter.h" #include "Json.h" void UUI_MainController::RequestServerData(const FString& Url) { TSharedRef<IHttpRequest> Request = FHttpModule::Get().CreateRequest(); Request->SetURL(Url); Request->SetVerb(TEXT("GET")); Request->SetHeader(TEXT("Content-Type"), TEXT("application/json")); Request->OnProcessRequestComplete().BindUObject(this, &UUI_MainController::OnServerDataReceived); Request->ProcessRequest(); } void UUI_MainController::OnServerDataReceived(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bSucceeded) { if (!bSucceeded || !Response.IsValid()) { return; } TSharedPtr<FJsonObject> JsonObject; TSharedRef<TJsonReader<>> Reader = TJsonReaderFactory<>::Create(Response->GetContentAsString()); if (!FJsonSerializer::Deserialize(Reader, JsonObject) || !JsonObject.IsValid()) { return; } // 在这里解析 Json 并调用 UI 刷新函数 TArray<FUI_ItemInfo> NewItems; // 根据实际返回字段填充数据 if (IsValid(CurrentPageWidget)) { CurrentPageWidget->RebuildList(NewItems); } }

7.2 批量刷新策略

批量刷新不是一次刷新完成,而是要避免每一条数据到达都触发 UI 刷新。最稳妥的模式是:

  1. 启动请求后,UI 进入 Loading 状态。
  2. 收到完整响应后,一次性构建数据模型。
  3. 数据模型构建完成后,再调用列表刷新函数。

这样可以防止列表在数据填充中途出现多次ClearChildren和重建。

7.3 接口请求注意事项

  • HTTP 请求回调默认在游戏线程回调,可以直接操作 UMG,但不要在ProcessRequest后立刻等待数据,必须使用回调函数处理。
  • 每条请求都要绑定回调函数,否则回调不会执行。
  • 批量请求建议使用时间戳做编号管理,避免旧请求覆盖新数据。
  • 如果使用第三方服务器,考虑加超时和重试机制。

8. 资源占用与性能观察

UI 性能问题是 UE5 项目上线的常见瓶颈。C++ 写 UI 同样无法避免这些问题,只能更精确地控制。

8.1 观察工具

  • stat UMG:查看控件数量、布局更新频率。
  • stat Slate:查看 Slate 渲染和布局耗时。
  • stat FPS:结合整体帧率判断 UI 造成的影响。
  • VS 中的性能分析器:定位哪个函数占用过高。

8.2 控件数量

动态生成列表时,控件数量会直接反映在内存上。即使只有 100 条数据,每条数据包含 5 个控件,就会产生 500 个 Slate 控件。

优化思路:

  • 小数据量使用VerticalBox,简单清晰。
  • 大数据量改用UListView,按可见区域生成节点。
  • 已经生成的 Widget 不要反复CreateWidget。
  • 不需要显示时用RemoveFromParent而不是仅SetVisibility。

8.3 每帧更新的代价

不要在Tick里更新文本。文本刷新涉及 Slate 布局、文字缓存、脏标记。如果确实需要实时刷新,每秒限制最多 10 次,并且只刷新变化内容。

可以从控制器提供一个刷新频率:

float UpdateInterval = 0.2f; float LastUpdateTime = 0.0f; void AUI_MainController::Tick(float DeltaSeconds) { Super::Tick(DeltaSeconds); LastUpdateTime += DeltaSeconds; if (LastUpdateTime >= UpdateInterval) { LastUpdateTime = 0.0f; // 批量刷新 UI 数据 } }

这种方式比每帧刷新节省大量开销。

8.4 图片资源的处理

动态创建UImage时,尽量使用已经常驻内存的贴图引用,避免每帧去LoadObject。图标类资源建议合并图集,减少 DrawCall。

可以用软引用预加载:

UPROPERTY(EditDefaultsOnly) TSoftObjectPtr<UTexture2D> IconTexture;

在SetupItemData中异步加载:

FSoftObjectPath TexturePath = IconTexture.ToSoftObjectPath(); if (!TexturePath.IsValid()) { return; } FStreamableManager& StreamableManager = UAssetManager::GetStreamableManager(); StreamableManager.RequestAsyncLoad( TexturePath, FStreamableDelegate::CreateLambda([this](UTexture2D* LoadedTexture) { if (IconImage && LoadedTexture) { IconImage->SetBrushFromTexture(LoadedTexture); } }) );

具体写法根据 UE 版本会有差异,5.1 之后推荐使用FSoftObjectPtr配合UAssetManager。

9. 常见问题与排查方法

下面是 C++ UI 开发中高频问题的排查表:

问题现象可能原因排查方式解决方案
C++ 编译无法识别 UMG 相关类型模块未添加检查Build.cs模块列表增加 UMG、Slate、SlateCore
CreateWidget 返回空指针控件类不合法,或蓝图子类未编译打印 WidgetClass 日志确保类有UCLASS(),蓝图已保存
Widget 不显示没有调用 AddToViewport,或 PlayerController 失效打断点查 MainWidget 状态检查GetOwningPlayer(),调用 AddToViewport
按钮无法点击Visibility 不对,或父容器遮挡查看控件层级设置合适的 Visibility,调整 ZOrder
点击回调不触发动态创建的 Button 没有绑定查看 NativeConstruct 是否执行在 NativeConstruct 中绑定,避免在构造函数中绑定
列表刷新后旧 UI 仍在没有 ClearChildren查看控件数量刷新前先清空容器
HTTP 请求回调不执行绑定了临时对象的回调检查回调是否被 GC使用 UPROPERTY 持有请求对象,或改用方法绑定
中文字符串乱码源文件编码问题检查文件保存格式源码保存为 UTF-8 with BOM,使用 TEXT() 宏
打包后下载图片不显示软引用路径非法看 Log 中加载报错检查纹理路径和 Cook 设置
频闪或掉帧严重每帧刷新 Widgetstat UMG 观察更新频率按间隔刷新,仅更新变化内容
界面出现不可见碰撞根节点没有设置 Visibility检查 CanvasPanel 可见性设置 SelfHitTestInvisible 或 NotHitTestable

10. 最佳实践与工程化建议

10.1 保持 UI 控制器独立

业务逻辑和 UI 逻辑要分开。

建议模式:

  • 数据层:业务数据由 GameMode、PlayerState 或数据服务提供。
  • 控制器层:接收数据,解析数据,决定何时刷新 UI。
  • 控件层:只负责显示。

在这套结构下,后续改数据源时不需要动 UI 代码,改 UI 样式时不需要影响数据处理。

10.2 控件生命周期管理

UUserWidget是 UObject 对象,受 GC 管理。动态创建的 Widget 如果没有持有引用,可能被回收。

建议:

  • 全局 UI 控件用UPROPERTY()保存引用。
  • 临时弹窗创建后,如果希望延迟销毁,不要立刻RemoveFromParent,可以在控制器中统一管理。
  • 在BeginDestroy或NativeDestruct中清理定时器和绑定。

10.3 统一入口启动 UI

不要在每个地图都直接创建 UI。做一个UIManager,提供统一的打开、关闭、切换接口。接口名称就像这样:

UFUNCTION(BlueprintCallable, Category = "UI") void OpenPanel(FName PanelId); UFUNCTION(BlueprintCallable, Category = "UI") void ClosePanel(FName PanelId);

这样可以避免多个 UI 互相覆盖、层级混乱。

10.4 数据刷新策略

  • 接收数据前先判断是否为空。
  • 数据量超过 100 条时使用 ListView。
  • 阶段刷新频率控制在 0.2 秒以上。
  • 有大量图片动态加载时,使用异步加载和小图集中方案。

10.5 接口与安全边界

UI 对接网络接口时,需要把处理重点放在数据安全上:

  • 不把服务器密钥或内部配置写死在 UI 层。
  • 敏感信息,例如账号、Token,避免写入日志。
  • 涉及用户个人信息、聊天内容、上传文件时,确保来源合法、展示授权明确。
  • 涉及图形界面中的人脸、头像、角色形象等素材,确保具备使用授权。

11. 总结与下一步

这篇文章演示了从环境准备到 C++ 动态创建 Widget、事件绑定、数据列表、网络请求、批量刷新和性能优化的完整链路。最值得自己先跑通的是:创建UI_BaseWidget、动态加按钮、点击改文本。这一步通了,后面对接列表和接口就只是同一个模式的延伸。

最容易踩的坑有三个:Build.cs模块缺失导致编译失败、CreateWidget返回空指针、控件生命周期没有持有引用。

下一步可以按这个顺序继续深入:

  • 去源码里翻一下UListView、UListViewBase的实现,掌握 EntryWidget 复用机制。
  • 用 Slate 直接写一个自定义控件,看 Slate 和 UMG 的互相嵌套方式。
  • 接一套真实后端接口,做数据驱动的排行榜或者背包页面。
  • 给 UI 加上统一状态管理,用类似栈的结构控制页面跳转。

建议先把这一套代码放到一个新测试工程里跑通,再迁移到正式项目。UI 系统只要架构能立住,后面加页面只是数量问题。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 9:57:30

eclipse-jee-2023-09 zip包下载解压与JDK 17配置详解

简介&#xff1a;Eclipse-JEE 2023-09 Windows 64位发行包&#xff0c;专为使用Java EE技术栈进行Web与企业级应用开发的工程师和初学者准备&#xff0c;内置了Java开发、动态Web项目、服务器配置等常用功能&#xff0c;解压后即可搭建完整IDE环境。压缩包共2000个文件&#xf…

作者头像 李华
网站建设 2026/9/30 9:57:21

VSAN设计与Sizing指南:磁盘组与缓存比例如何决定超融合性能

简介&#xff1a;《VSAN设计与Sizing指南》是VMware官方针对Virtual SAN 6.0发布的技术文档&#xff0c;主要面向IT架构师、存储管理员和虚拟化工程师&#xff0c;用于指导VSAN环境的设计、容量预估与部署规划。资源为PDF格式&#xff0c;单文件&#xff0c;共959KB&#xff0c…

作者头像 李华
网站建设 2026/9/30 9:56:50

高难度物理平台跳跃游戏通关攻略:机制拆解与分段优化

之前陪朋友打这个游戏&#xff0c;自己也跟着卡了快三个晚上&#xff0c;从“这游戏怎么这么简单”到“我为什么连一根针都控制不住”&#xff0c;中间经历了无数次视角翻转、重心乱摆和心态爆炸。后来把操作逻辑、分段策略、设备手感全部梳理了一遍&#xff0c;才勉强摸到通关…

作者头像 李华
网站建设 2026/9/30 9:54:46

Unity iOS手游Deep Link接入全流程:配置、回调与冷启动处理

接手 iOS 买量项目时&#xff0c;市场丢过来一个需求&#xff1a;Safari 广告页点一下&#xff0c;装了 App 的直接打开进活动页&#xff0c;没装的就先去下载页。我当时以为这只是配置一个链接的事&#xff0c;结果从 iOS 系统到原生壳再到 Unity 引擎&#xff0c;每一层都有各…

作者头像 李华
网站建设 2026/9/30 9:54:44

Redis接入AI实战:向量检索、缓存与Agent状态管理

1. Redis接入AI&#xff1a;到底接的是什么&#xff0c;解决什么问题先说结论&#xff1a;Redis接AI这件事&#xff0c;不是Redis官方出了个什么“AI插件”让你一键变身通用大模型&#xff0c;而是Redis生态开始围绕大模型应用提供一套完整的基础设施能力。说白了&#xff0c;以…

作者头像 李华