我们有一个需求: 构建一个用户小控件, 用来在屏幕上显示一个准星

创建十字准星蓝图的步骤:
- 创建 C++ 类:
新建继承自UserWidget类型的 C++ 类,命名为HUDWidget。- 生成蓝图子类:
基于上述HUDWidget类,创建一个对应的蓝图子类, 命名为WBP_HUD
- 制作十字准星:
在蓝图中添加画板, 然后再添加文本组件,用 “+” 符号作为十字准星的显示内容。- 锚定位置:
将这个文本组件的锚点设置为屏幕中心,确保准星显示在画面正中间。
在C++中调用十字准星蓝图:
调用十字准星蓝图:
可以在玩家控制器中调用, 也可以在游戏模式类中调用;这里我们以玩家控制器举例:
我们先在玩家控制器C++类中写好接口, 再把十字准星蓝图挂载到接口上去;
写接口:
//XXXPlayerController.h #include "HUDWidget.h" public: //HUDWidgetClass是连接 “C++ 基类” 和 “蓝图子类” 的 “类选择器”, //作用可以通俗理解为:“告诉代码‘你要创建的 HUD Widget,具体是哪个蓝图做的版本’” UPROPERTY(EditAnywhere) TSubclassOf<UHUDWidget> HUDWidgetClass; // 声明一个UHUDWidget类型的指针,用于存储即将创建的Widget实例 UPROPERTY(VisibleAnywhere) UHUDWidget* HUDWidget;//XXXPlayerController.cpp void AShooterSamPlayerController::BeginPlay() { Super::BeginPlay(); if (SVirtualJoystick::ShouldDisplayTouchInterface() && IsLocalPlayerController()) { MobileControlsWidget = CreateWidget<UUserWidget>(this, MobileControlsWidgetClass); if (MobileControlsWidget) { MobileControlsWidget->AddToPlayerScreen(0); } else { UE_LOG(LogShooterSam, Error, TEXT("Could not spawn mobile controls widget.")); } } // 调用虚幻的CreateWidget函数,创建HUDWidget的实例 // 模板参数<UHUDWidget>:指定要创建的Widget类是我们定义的UHUDWidget // 第一个参数this:表示“拥有这个Widget的对象”(这里通常是PlayerController类的实例) // 第二个参数HUDWidgetClass:是TSubclassOf<UHUDWidget>类型的变量(需在编辑器中指定对应的蓝图子类) HUDWidget = CreateWidget<UHUDWidget>(this, HUDWidgetClass); // 检查Widget是否成功创建(避免空指针访问,防止崩溃) if (HUDWidget) { // 调用AddToViewport():将创建好的Widget添加到游戏视口层 // 执行后,十字准星(HUDWidget里的内容)就会显示在游戏屏幕上 HUDWidget->AddToViewport(); } }
挂载蓝图
打开BP_XXXPlayerController玩家控制器蓝图, 选择我们的十字准星蓝图WBP_HUD;
最终效果:
TSubclassOf<T> 是什么?
TSubclassOf<T> 是虚幻引擎 C++ 中一个非常重要且特有的模板类型。
用一句话最直白地解释:它是一个“带有过滤器”的类选择器, 用于接受一个T的子类型, 然而T的子类型大概率就是T的蓝图, 因为T蓝图就是基础自T的一个特殊C++类
它的全称意思是:Type Subclass Of T(T 类型的子类)。
为了让你彻底理解,我们把 UClass*(普通类指针)和 TSubclassOf<T> 做个对比。
1. 如果你只用普通的 UClass* (没有过滤器)
假设你想在代码里生成一把武器,你定义了一个变量让策划在编辑器里选:
// 这里的 * 表示任意类型的类(可以是角色、UI、特效、甚至声音类) UPROPERTY(EditAnywhere) UClass* WeaponClass;后果:
在编辑器的下拉菜单里,你会看到成千上万个选项。
策划可能会手滑,选了一个 UTexture(图片类)或者 AMyTank(坦克类)放进去。当你代码执行 SpawnActor<AWeapon>(WeaponClass) 时,如果策划选的是个“图片类”,游戏就会崩溃或者报错,因为你没法把一张图片生成为一把枪。
2. 如果你用 TSubclassOf<AWeapon> (有过滤器)
// 这里的 <> 里的 AWeapon 就是过滤器 // 意思是:这个变量只能存 AWeapon 或者 AWeapon 的儿子、孙子 UPROPERTY(EditAnywhere) TSubclassOf<AWeapon> WeaponClass;后果:
编辑器表现:在下拉菜单里,UE 会自动过滤掉所有无关的类。你只能看到 BP_Rifle(步枪)、BP_Pistol(手枪)等继承自 AWeapon 的蓝图。你根本选不到 BP_Tank 或 Texture。
代码安全:编译器知道 WeaponClass 里面装的一定是武器。
3. 回到你的代码:TSubclassOf<UHUDWidget>
你在代码里写:
UPROPERTY(EditAnywhere) TSubclassOf<UHUDWidget> HUDWidgetClass;这行代码的意思是:
“嘿,虚幻编辑器,请在属性面板里给我生成一个下拉框。但是,这个下拉框里只允许显示 UHUDWidget 的子类(比如你做的 WBP_GameHUD, WBP_HealthBar)。不要把那些乱七八糟的坦克蓝图、子弹蓝图显示出来让我选。”
为什么要这么做?
因为 CreateWidget<UHUDWidget>(...) 函数在创建 UI 时,它要求你传入的那个“配方”(Class),必须得是 UHUDWidget 类型的。如果你传个 ATank 类进去,Widget 创建工厂就罢工了。
总结
TSubclassOf<T> = 类型安全的类引用。
作用 1 (给策划看):在编辑器里缩小选择范围,防止选错,只显示特定类型的蓝图。
作用 2 (给代码看):保证类型安全,避免把“坦克类”传给“生成子弹”的函数。
那我为什么不直接把我需要的控件蓝图传进去, 用HUDWidget直接接受, 都不需要额外创建然后赋值给HUDWidget
UPROPERTY(EditAnywhere)
UHUDWidget* HUDWidget;
这是一个非常直觉的想法,很多刚转虚幻的人都会这么想。
但答案是:绝对不行。这样做在编辑器里根本选不中任何东西。
为了让你彻底死心(并彻底理解),我们需要区分两个概念:“硬盘里的图纸” 和 “内存里的实物”。
1. 核心矛盾:图纸 vs 实物
-
你在内容浏览器(Content Browser)里看到的 WBP_HUD:
-
它是一个 资产文件(Asset)。
-
它是一个 类(Class)。
-
这就好比是一张 “建筑蓝图(图纸)”。你不能住在图纸里,图纸也不能显示血量。
-
-
代码里的 UHUDWidget* HUDWidget:
-
它需要的是一个 内存中的实例(Instance)。
-
它需要的是一栋 “盖好的房子”。
-
你的提议:
“我把 WBP_HUD(图纸)直接拖给 HUDWidget(房子指针)。”
结果:
类型不匹配!
就像你想要“吃披萨”,但你把“披萨的菜谱”塞进了嘴里。






483



被折叠的 条评论
为什么被折叠?



