第一步:在UE4中创建一个自定义的游戏模式类并指定使用自定义的HUD类,可以按照以下步骤进行:
1、创建自定义游戏模式类: 在UE4编辑器中,右键点击内容浏览器中的空白区域,选择“新建C++类”(New C++ Class),然后选择“GameModeBase”作为父类。命名你的新类,例如MainMenuGameMode。
2、编辑自定义游戏模式类: 打开你刚刚创建的MainMenuGameMode类的头文件和源文件,添加代码以指定使用自定义的HUD类。,代码部分在VisualStudio 中编写。
MainMenuGameMode.h:
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "MainMenuGameMode.generated.h"
UCLASS()
class TEAM_WORK_API AMainMenuGameMode : public AGameModeBase
{
GENERATED_BODY()
public:
AMainMenuGameMode();
void BeginPlay();
};
MainMenuGameMode.cpp:
#include "MainMenuGameMode.h"
#include "MainMenuHUD.h"
AMainMenuGameMode::AMainMenuGameMode()
{
// 指定使用自定义的HUD类
HUDClass = AMainMenuHUD::StaticClass();
}
void AMainMenuGameMode::BeginPlay()
{
Super::BeginPlay();
//获取当前游戏世界中的第一个玩家控制器(PlayerController),管理玩家的输入与交互。
APlayerController* PlayerController = GetWorld()->GetFirstPlayerController();
if (PlayerController)
{
//使鼠标光标在屏幕上可见,用鼠标与菜单进行交互。
PlayerController->bShowMouseCursor = true;
//玩家控制器的输入模式设置为Game和UI模式,玩家可与用户界面(UI)以及游戏场景进行交互
PlayerController->SetInputMode(FInputModeGameAndUI());
}
AHUD* HUD = PlayerController->GetHUD();//获取玩家控制器当前绑定的 HUD 实例
//检查HUD实例是否被正确实例化
if (HUD)
{
UE_LOG(LogTemp, Warning, TEXT("HUD is correctly instantiated"));
}
else
{
UE_LOG(LogTemp, Error, TEXT("HUD is not instantiated"));
}
}
该部分总结:
构造函数中将 AMainMenuHUD 设为游戏的 HUD 类。
BeginPlay() 函数中,设置玩家控制器的输入模式允许与 Game和UI 进行交互,并显示鼠标光标。
检查并确认 HUD 是否成功实例化。
设置游戏模式: 在UE4编辑器中,打开你的项目设置(Project Settings),导航到“Maps & Modes”部分。在“Default Modes”下,将“GameMode”设置为你刚刚创建的MainMenuGameMode类。
启用鼠标输入: 确保在游戏开始时启用鼠标输入,以便用户可以点击菜单项。你可以在MainMenuGameMode类的BeginPlay方法中添加以下代码:
void AMainMenuGameMode::BeginPlay()
{
Super::BeginPlay();
APlayerController* PlayerController = GetWorld()->GetFirstPlayerController();
if (PlayerController)
{
PlayerController->bShowMouseCursor = true;
PlayerController->SetInputMode(FInputModeGameAndUI());
}
}
如果按照上述的操作,完成代码编译运行,但是并没有在游戏运行时展示出菜单栏请按照如下几个方法进行排除问题:
1、确认游戏模式是否正确设置: 确保你的MainMenuGameMode类在项目设置中被正确指定为默认游戏模式。 在项目设置中,导航到“Maps & Modes”,并在“Default GameMode”下拉菜单中选择你的MainMenuGameMode类。
3、启用鼠标输入: 确保在游戏开始时启用了鼠标输入,以便用户可以点击菜单项。
void AMainMenuGameMode::BeginPlay()
{
Super::BeginPlay();
APlayerController* PlayerController = GetWorld()->GetFirstPlayerController();
if (PlayerController)
{
PlayerController->bShowMouseCursor = true;
PlayerController->SetInputMode(FInputModeUIOnly());
}
}
3、检查HUD是否被正确实例化: 确保你的HUD类在游戏运行时被正确实例化。你可以在游戏模式的BeginPlay方法中添加一些调试信息来确认这一点。
void AMainMenuGameMode::BeginPlay()
{
Super::BeginPlay();
APlayerController* PlayerController = GetWorld()->GetFirstPlayerController();
if (PlayerController)
{
PlayerController->bShowMouseCursor = true;
PlayerController->SetInputMode(FInputModeUIOnly());
// 获取并检查HUD实例
AHUD* HUD = PlayerController->GetHUD();
if (HUD)
{
UE_LOG(LogTemp, Warning, TEXT("HUD is correctly instantiated"));
}
else
{
UE_LOG(LogTemp, Error, TEXT("HUD is not instantiated"));
}
}
}
第二步:在UE4中创建一个自定义的HUD类并重写BeginPlay()函数,使游戏开始时创建并自定义SlateUI菜单,设置玩家控制器的输入模式。可以按照以下步骤进行:
创建自定义HUD类: 在UE4编辑器中,右键点击内容浏览器中的空白区域,选择“新建C++类”(New C++ Class),然后选择“HUD”作为父类。命名你的新类,例如MainMenuHUD。
在HUD中使用自定义的Slate Widget: 在你的HUD类中,创建并添加自定义的Slate Widget。
MainMenuHUD.h:
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/HUD.h"
#include "MainMenuHUD.generated.h"
UCLASS()
//定义一个AMainMenuHUD类继承自AHUD类
class TEAM_WORK_API AMainMenuHUD : public AHUD
{
GENERATED_BODY()
public:
//声明一个 BeginPlay() 的虚函数,用于在游戏对象创建并初始化后立即调用
virtual void BeginPlay() override;
private:
//声明一个TSharedPtr指针,指向SMainMenuWidget类的实例MainMenuWidget
TSharedPtr<class SMainMenuWidget> MainMenuWidget;
//定义了一个智能指针的SWidget类实例
TSharedPtr<class SWidget> MainMenuWidgetContainer;
};
MainMenuHUD.cpp:
#include "MainMenuHUD.h"
#include "SMainMenuWidget.h"
#include "Engine/Engine.h"
#include "Engine/GameViewportClient.h"
#include "SlateBasics.h"
#include "SlateExtras.h"
//重写AHUD的BeginPlay()函数,用于初始化游戏对象
void AMainMenuHUD::BeginPlay()
{
//调用父类 AHUD 的 BeginPlay() 函数,确保基类的逻辑得以执行
Super::BeginPlay();
//检查GEngine和GEngine->GameViewport是否都有效
if (GEngine && GEngine->GameViewport)
{
//创建自定义主菜单小部件
SAssignNew(MainMenuWidget, SMainMenuWidget);
//添加小部件到游戏视口
GEngine->GameViewport->AddViewportWidgetContent(
SAssignNew(MainMenuWidgetContainer, SWeakWidget)
.PossiblyNullContent(MainMenuWidget.ToSharedRef())
);
UE_LOG(LogTemp, Warning, TEXT("Slate Widget added to viewport"));
//获取并配置玩家控制器
APlayerController* PlayerController = GetWorld()->GetFirstPlayerController();
if (PlayerController)
{
//显示鼠标光标和设置输入模式
PlayerController->bShowMouseCursor = true;
PlayerController->SetInputMode(FInputModeGameAndUI());
//获取并检查HUD实例
AHUD* HUD = PlayerController->GetHUD();
if (HUD)
{
UE_LOG(LogTemp, Warning, TEXT("HUD is correctly instantiated!"))
}
else
{
UE_LOG(LogTemp, Warning, TEXT("HUD is not instantiated#"));
}
}
}
}
第三步:要实现鼠标点击展开菜单栏的二级菜单,可以使用Slate或UMG来创建更复杂的UI。在UE4中创建一个自定义的Slate控件类并在该类中自定义SlateUI,完成一二级菜单栏的整体布局以及他们的点击响应事件和回调函数。可以按照以下步骤进行:
创建自定义HUD类: 在UE4编辑器中,右键点击内容浏览器中的空白区域,选择“新建C++类”(New C++ Class),然后选择“Slate控件”作为父类。命名你的新类,例如MainMenuWidget。
MainMenuWidget.h:
#pragma once
#include "CoreMinimal.h"
#include "Widgets/SCompoundWidget.h"
class TEAM_WORK_API SMainMenuWidget : public SCompoundWidget
{
public:
SLATE_BEGIN_ARGS(SMainMenuWidget)
{}
SLATE_END_ARGS()
/** Constructs this widget with InArgs */
void Construct(const FArguments& InArgs);
private:
FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent);
TSharedPtr<SVerticalBox> MenuBox;
TSharedRef<SWidget> GenerateFileMenu();
TSharedRef<SWidget> GenerateEditMenu();
TSharedRef<SWidget> GenerateWindowMenu();
TSharedRef<SWidget> GenerateHelpMenu();
//一级菜单点击事件回调函数定义
FReply OnFileButtonClicked();
FReply OnEditButtonClicked();
FReply OnWindowButtonClicked();
FReply OnHelpButtonClicked();
TSharedPtr<SMenuAnchor> FileMenuAnchor;
TSharedPtr<SMenuAnchor> EditMenuAnchor;
TSharedPtr<SMenuAnchor> WindowMenuAnchor;
TSharedPtr<SMenuAnchor> HelpMenuAnchor;
//二级菜单点击事件回调函数定义
FReply OnNewFileClicked();
FReply OnOpenFileClicked();
FReply OnSaveFileClicked();
FReply OnCutClicked();
FReply OnCopyClicked();
FReply OnPasteClicked();
FReply OnNewWindowClicked();
FReply OnOpenWindowClicked();
FReply OnOpenNewWindowClicked();
FReply OnHelpClicked();
FReply OnAboutClicked();
};
MainMenuWidget.cpp:
#include "SMainMenuWidget.h"
#include "SlateOptMacros.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Input/SButton.h"
#include "Brushes/SlateColorBrush.h"
//优化 Slate 小部件的构建过程,主要用于减少 Slate 小部件在编译时的冗余代码
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SMainMenuWidget::Construct(const FArguments& InArgs)
{
//SMainMenuWidget 小部件的根容器,所有子小部件都被添加到该插槽中
ChildSlot
[
SNew(SVerticalBox) //创建了一个垂直布局容器 SVerticalBox,将所有子元素依次从上到下排列
+ SVerticalBox::Slot()//在 SVerticalBox 中添加一个插槽(Slot),每个插槽代表一个子小部件
.AutoHeight()//设置插槽高度为自动高度
[
SNew(SHorizontalBox)//垂直布局容器中创建了一个水平布局容器 SHorizontalBox,该容器会水平排列其子小部件
+ SHorizontalBox::Slot()//在 SHorizontalBox 中添加一个插槽,用于放置 "File" 按钮所在的内容
/*.Padding(10)*/ //一级菜单之间的间隔
.AutoWidth()//设置插槽宽度为自动宽度
[
SNew(SBox)//创建了一个 SBox 容器,它可以用来定义子小部件的大小,确保 "File" 按钮有固定的宽度和高度
.WidthOverride(100)//设置 SBox 容器的宽度为 100 像素
.HeightOverride(30)//设置 SBox 容器的高度为 30 像素
[
//使用 SAssignNew 创建一个支持下拉菜单的SMenuAnchor小部件,并将其赋值给FileMenuAnchor变量
SAssignNew(FileMenuAnchor, SMenuAnchor)
/*设置 SMenuAnchor 的回调函数 OnGetMenuContent,当点击 "File" 按钮时,
会调用 GenerateFileMenu() 函数生成下拉菜单的内容*/
.OnGetMenuContent(this, &SMainMenuWidget::GenerateFileMenu)
[
//在SMenuAnchor中创建了一个SButton按钮,表示"File"菜单按钮
SNew(SButton)
/*设置按钮的点击事件,当点击该按钮时,会调用 OnFileButtonClicked()函数执行相应操作,*/
.OnClicked(this, &SMainMenuWidget::OnFileButtonClicked)
[
//在按钮内部创建一个文本块,用于显示文本内容
SNew(STextBlock)
.Text(FText::FromString(TEXT("File")))
]
]
]
]
+ SHorizontalBox::Slot()
/*.Padding(10)*/
.AutoWidth()
[
SNew(SBox)
.WidthOverride(100)
.HeightOverride(30)
[
SAssignNew(EditMenuAnchor, SMenuAnchor)
.OnGetMenuContent(this, &SMainMenuWidget::GenerateEditMenu)
[
SNew(SButton)
.OnClicked(this, &SMainMenuWidget::OnEditButtonClicked)
[
SNew(STextBlock)
.Text(FText::FromString(TEXT("Edit")))
]
]
]
]
+ SHorizontalBox::Slot()
/*.Padding(10)*/
.AutoWidth()
[
SNew(SBox)
.WidthOverride(100)
.HeightOverride(30)
[
SAssignNew(WindowMenuAnchor, SMenuAnchor)
.OnGetMenuContent(this, &SMainMenuWidget::GenerateWindowMenu)
[
SNew(SButton)
.OnClicked(this, &SMainMenuWidget::OnWindowButtonClicked)
[
SNew(STextBlock)
.Text(FText::FromString(TEXT("Window")))
]
]
]
]
+ SHorizontalBox::Slot()
/*.Padding(10)*/
.AutoWidth()
[
SNew(SBox)
.WidthOverride(100)
.HeightOverride(30)
[
SAssignNew(HelpMenuAnchor, SMenuAnchor)
.OnGetMenuContent(this, &SMainMenuWidget::GenerateHelpMenu)
[
SNew(SButton)
.OnClicked(this, &SMainMenuWidget::OnHelpButtonClicked)
[
SNew(STextBlock)
.Text(FText::FromString(TEXT("Help")))
]
]
]
]
]
];
// 单独设置 MenuPlacement使二级菜单在一级菜单下
FileMenuAnchor->SetMenuPlacement(MenuPlacement_BelowAnchor);
EditMenuAnchor->SetMenuPlacement(MenuPlacement_BelowAnchor);
WindowMenuAnchor->SetMenuPlacement(MenuPlacement_BelowAnchor);
HelpMenuAnchor->SetMenuPlacement(MenuPlacement_BelowAnchor);
UE_LOG(LogTemp, Warning, TEXT("SMainMenuWidget Constructed successfully"));
}
//菜单栏函数的定义
TSharedRef<SWidget> SMainMenuWidget::GenerateFileMenu()
{
return SNew(SBorder)
.BorderBackgroundColor(FLinearColor::Black) // 设置背景颜色不透明
.Padding(FMargin(5)) // 设置适当的填充
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SButton)
.OnClicked(this, &SMainMenuWidget::OnNewFileClicked) // 确保可以点击
[
SNew(STextBlock)
.Text(FText::FromString(TEXT("New File")))
/*.ColorAndOpacity(FLinearColor::White)*/
]
]
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SButton)
.OnClicked(this, &SMainMenuWidget::OnOpenFileClicked) // 确保可以点击
[
SNew(STextBlock)
.Text(FText::FromString(TEXT("Open File")))
]
]
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SButton)
.OnClicked(this, &SMainMenuWidget::OnSaveFileClicked) // 确保可以点击
[
SNew(STextBlock)
.Text(FText::FromString(TEXT("Save File")))
]
]
];
}
TSharedRef<SWidget> SMainMenuWidget::GenerateEditMenu()
{
return SNew(SBorder)
.BorderBackgroundColor(FLinearColor::Gray) // 设置背景颜色
.Padding(FMargin(5))
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SButton)
.OnClicked(this, &SMainMenuWidget::OnCutClicked) // 示例点击事件
[
SNew(STextBlock)
.Text(FText::FromString(TEXT("Cut")))
]
]
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SButton)
.OnClicked(this, &SMainMenuWidget::OnCopyClicked)
[
SNew(STextBlock)
.Text(FText::FromString(TEXT("Copy")))
]
]
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SButton)
.OnClicked(this, &SMainMenuWidget::OnPasteClicked)
[
SNew(STextBlock)
.Text(FText::FromString(TEXT("Paste")))
]
]
];
}
TSharedRef<SWidget> SMainMenuWidget::GenerateWindowMenu()
{
return SNew(SBorder)
.BorderBackgroundColor(FLinearColor::Gray) // 设置背景颜色不透明
.Padding(FMargin(5)) // 设置适当的填充
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SButton)
.OnClicked(this, &SMainMenuWidget::OnNewWindowClicked) // 确保可以点击
[
SNew(STextBlock)
.Text(FText::FromString(TEXT("New Window")))
]
]
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SButton)
.OnClicked(this, &SMainMenuWidget::OnOpenWindowClicked) // 确保可以点击
[
SNew(STextBlock)
.Text(FText::FromString(TEXT("Open Window")))
]
]
];
}
TSharedRef<SWidget> SMainMenuWidget::GenerateHelpMenu()
{
return SNew(SBorder)
.BorderBackgroundColor(FLinearColor::Gray) // 设置背景颜色
.Padding(FMargin(5))
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SButton)
.OnClicked(this, &SMainMenuWidget::OnHelpClicked) // 示例点击事件
[
SNew(STextBlock)
.Text(FText::FromString(TEXT("Help")))
]
]
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SButton)
.OnClicked(this, &SMainMenuWidget::OnAboutClicked)
[
SNew(STextBlock)
.Text(FText::FromString(TEXT("About")))
]
]
];
}
//一级菜单栏点击事件回调函数
FReply SMainMenuWidget::OnFileButtonClicked()
{
if (FileMenuAnchor.IsValid())
{
// 如果菜单已经打开,则关闭,否则打开
FileMenuAnchor->SetIsOpen(!FileMenuAnchor->IsOpen());
UE_LOG(LogTemp, Warning, TEXT("FileMenuAnchor was opened successfully !"))
}
return FReply::Handled();
}
FReply SMainMenuWidget::OnEditButtonClicked()
FReply SMainMenuWidget::OnWindowButtonClicked()
FReply SMainMenuWidget::OnHelpButtonClicked()
//二级菜单点击事件回调函数
FReply SMainMenuWidget::OnNewFileClicked()
{
UE_LOG(LogTemp, Warning, TEXT("NewFile was toggled successfully!"));
return FReply::Handled();
}
FReply SMainMenuWidget::OnOpenFileClicked()
FReply SMainMenuWidget::OnSaveFileClicked()
FReply SMainMenuWidget::OnCutClicked()
FReply SMainMenuWidget::OnCopyClicked()
FReply SMainMenuWidget::OnPasteClicked()
FReply SMainMenuWidget::OnOpenWindowClicked()
FReply SMainMenuWidget::OnNewWindowClicked()
FReply SMainMenuWidget::OnOpenNewWindowClicked()
FReply SMainMenuWidget::OnHelpClicked()
FReply SMainMenuWidget::OnAboutClicked()
//由于字数要求只展示一个二级菜单响应函数的结构,其余函数写法与该函数相同
//想添加的功能也在其内部添加
//点击键盘ESC退出程序的响应函数
FReply SMainMenuWidget::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
{
if (InKeyEvent.GetKey() == EKeys::Escape)
{
// 关闭应用程序
FGenericPlatformMisc::RequestExit(false);
return FReply::Handled();
}
return SCompoundWidget::OnKeyDown(MyGeometry, InKeyEvent);
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
由于字数要求,展示部分代码,其余的按照自己的需求去写,与上面函数的结构一样,注意返回值应该是Freply类型,需要添加点击后的其他功能也在该函数内补充完整。后续会将源码传输到Github上。
通过这些步骤,可以创建一个带有横向菜单栏,并通过鼠标点击展开二级菜单。
注意:OnClicked回调函数的签名。OnClicked需要一个返回FReply类型的方法,不能提供返回void的方法。
最终完成的效果图:
最终运行效果视频如下:
UE4 菜单栏演示视频
虚幻引擎输出日志中将一级二级菜单鼠标点击事件回调函数中的内容都打印出来了,如图:
后续有什么问题可以探讨一下!