虚幻引擎5 Gameplay框架(二)
Gameplay重要类及重要功能使用方法(一)
配置LOG类及PlayerController的网络机制
探索验证GamePlay重要函数、类的执行顺序与含义
- 我们定义自己的日志,专门建立一个存放自己日志的类,这个类继承自
BlueprintFunctionLibrary
- 然后在
BFL_LogManager
类中声明两个日志函数,一个用来判断当前是那种网络模式,一个用来打印当前网络模式,注意BlueprintFunctionLibrary
派生类的方法必须都是静态的
BFL_LogManager.h
- 逻辑
// Fill out your copyright notice in the Description page of Project Settings.#pragma once#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "BFL_LogManager.generated.h"/*** */
UCLASS()
class GAMEPLAYCODEPARSING_API UBFL_LogManager : public UBlueprintFunctionLibrary
{GENERATED_BODY()public:static FString GetEnumStr(ENetMode NM);//获取网络模式static FString GetNetModeStr(const AActor* Actor);
};
BFL_LogManager.cpp
- 逻辑
// Fill out your copyright notice in the Description page of Project Settings.#include "BFL_LogManager.h"FString UBFL_LogManager::GetEnumStr(ENetMode NM)
{if (NM == NM_Standalone){return FString("Standalone");}else if (NM == NM_DedicatedServer){return FString("DedicatedServer");}else if ( NM == NM_ListenServer){return FString("ListenServer");}else if ( NM == NM_Client){return FString("Client");}else{return FString("None");}}FString UBFL_LogManager::GetNetModeStr(const AActor* Actor)
{if (Actor){//获取当前网络模式ENetMode NM = Actor->GetNetMode();//打印当前网络模式return GetEnumStr(NM);}else{return FString("NULL");}
}
定义自定义标签进行验证函数的执行顺序
- 在自己的控制器
GamePlayCodeParsingGameMode
类中,定义自己的标签然后进行调用函数 DEFINE_LOG_CATEGORY_STATIC
宏用于在.cpp文件中定义一个静态的日志类别。静态日志类别仅在定义它的编译单元中可见,这意味着它不能在多个文件间共享。这适用于那些日志类别只需在特定模块内部使用的场景。- 也可以使用
DECLARE_LOG_CATEGORY_EXTERN
调试笔记有讲解 - 在
.h
里面声明
DECLARE_LOG_CATEGORY_EXTERN(MyLog, Log, All);
- 在
.cpp
里面定义
DEFINE_LOG_CATEGORY(MyLog);
GamePlayCodeParsingGameMode.cpp
// Fill out your copyright notice in the Description page of Project Settings.#include "GamePlayPlayerController.h"
#include "Utils/BFL_LogManager.h"//DEFINE_LOG_CATEGORY(MyLog);DEFINE_LOG_CATEGORY_STATIC(MyLog,Log,All)void AGamePlayPlayerController::BeginPlay()
{Super::BeginPlay();//获取当前网络模式ENetMode NM = GetNetMode();//获取到网络模式后进行打印一下UE_LOG(MyLog, Log, TEXT("1----BeginPlay---- NetMode:%s"), *UBFL_LogManager::GetNetModeStr(this));if (NM == NM_Standalone){FURL map;map.Host = "127.0.0.1";//本地地址map.Port = 7777;//虚幻默认端口号map.Map = "ThirdPersonMap";//没有连接服务器之前打印一下UE_LOG(MyLog, Log, TEXT("2----BeginPlay---- NetMode:%s"), *UBFL_LogManager::GetNetModeStr(this));//旅行到不同的地图或者地址ClientTravel(map.ToString(), ETravelType::TRAVEL_Absolute);//连接服务器之后打印一下UE_LOG(MyLog, Log, TEXT("3----BeginPlay---- NetMode:%s"), *UBFL_LogManager::GetNetModeStr(this));}
}
- 编译之后用批处理程序打开服务器与客户端
- 我们可以看见在BeginPlay的时候就是客户端了,这是不符合常理的,因为刚开始的时候应该是单机的,这就说明它是由单机转换成客户端的 ,这一个不是第一次执行BeginPlay
- 向前面寻找BeginPlay,找到这三行日志就是单机的时候打印的,然后执行ClientTravel,就旅行到服务器上了。刚开始的时候单机进行的时候有一个控制器,但是进入到服务器的时候就会被销毁掉,服务器会创建一个新的控制器,并且在服务器与玩家本地都有实例,然后这个服务器创建的控制器会将玩家的本地的控制器接管。
- 总结:
玩家本地控制器到服务器后会被接管,并且这个控制器在服务器与玩家本地都有实例
- 总结:
- 这是虚幻的机制,因为所有玩家的控制器在服务器上有一份,有一个玩家的有变化,那么它本地的控制器就会将数据上传到服务器,服务器再去广播到其他玩家,其他玩家的本地控制器接收到,就实现同步了
- 总结:
这样就实现玩家之间的同步了
- 总结:
探索APlayerController中的OnPossess函数
-
重写
OnProssess
函数
-
添加一个打印
-
OnPossess
函数源码:函数意义就是玩家控制器如何安全地交接控制权给新的Pawn,并处理相关逻辑,包括同步状态、网络交互、以及视图和状态管理
void APlayerController::OnPossess(APawn* PawnToPossess)
{if ( PawnToPossess != NULL && (PlayerState == NULL || !PlayerState->IsOnlyASpectator()) ){const bool bNewPawn = (GetPawn() != PawnToPossess);if (GetPawn() && bNewPawn){UnPossess();}if (PawnToPossess->Controller != NULL){PawnToPossess->Controller->UnPossess();}PawnToPossess->PossessedBy(this);// update rotation to match possessed pawn's rotationSetControlRotation( PawnToPossess->GetActorRotation() );SetPawn(PawnToPossess);check(GetPawn() != NULL);if (GetPawn() && GetPawn()->PrimaryActorTick.bStartWithTickEnabled){GetPawn()->SetActorTickEnabled(true);}INetworkPredictionInterface* NetworkPredictionInterface = GetPawn() ? Cast<INetworkPredictionInterface>(GetPawn()->GetMovementComponent()) : NULL;if (NetworkPredictionInterface){NetworkPredictionInterface->ResetPredictionData_Server();}AcknowledgedPawn = NULL;// Local PCs will have the Restart() triggered right away in ClientRestart (via PawnClientRestart()), but the server should call Restart() locally for remote PCs.// We're really just trying to avoid calling Restart() multiple times.if (!IsLocalPlayerController()){GetPawn()->DispatchRestart(false);}ClientRestart(GetPawn());ChangeState( NAME_Playing );if (bAutoManageActiveCameraTarget){AutoManageActiveCameraTarget(GetPawn());ResetCameraMode();}}
}
- 编译之后运行服务器与客户端脚本
- 在客户端中,在单机的时候先接管
Pawn
,然后在BeginPlay
- 在服务器中,服务器应该也会有
OnPossess
,因为服务器会生成控制器去接管玩家本地控制器,就是服务器的控制器来控制本地的Pawn
客户端连接服务器流程(GameMode登录函数)
网络连接流程概论
- 先说结论,总结
GameMode登录网络连接
流程:- 1.客户端向服务器发送连接请求
- 2.如果服务器接受连接,则向客户端发送当前的地图
- 3.服务器等待客户端加载地图
- 4.加载完成之后,服务器调用
PreLogin
,这一过程有可能拒绝客户端登录PreLogin
的主要作用:服务器要不要接纳这个请求登录的客户端
- 5.如果接收连接,服务器调用
Login
(此时RPC函数存在风险)Login
的主要作用:登录客户端创建PC、为玩家确定起始点,若其中失败则销毁创建的PC,并返回空指针,若流程跑通,则相当于玩家在世界中实例化,故而执行BeginPlay
- 6.服务器调用
PostLogin
(此时RPC函数可放心使用)PostLogin
的主要作用:玩家初始化,包括玩家HUD的初始化、网络语音、画面、网络宽带、将玩家加入到游戏实例中的游戏回放列表中
下面是RPC函数的解释
- RPC函数:在虚幻引擎中,
RPC
(Remote Procedure Call)函数是一种特殊类型的游戏玩法相关函数,它用于在网络环境中同步游戏状态或触发远程客户端或服务器上的操作。虚幻引擎中的RPC函数对于开发多人在线游戏至关重要,因为它们帮助维持游戏世界的状态一致性,确保所有玩家看到的游戏行为是同步的。 - 在使用RPC函数时,开发者需要关注如何定义服务接口、如何在客户端构造请求、如何在服务端处理请求及响应,以及如何处理网络和协议层面的细节。不同的RPC框架提供了不同程度的抽象,使得开发者能够更专注于业务逻辑而非底层通信细节。
- RPC的类型
- 客户端到服务器(Client-to-Server, Cts): 由客户端发起调用,但仅在服务器上执行。服务器验证该调用的有效性(比如权限检查、防作弊等),然后根据需要更新游戏状态,并可能广播给所有相关的客户端。
- 服务器到客户端(Server-to-Client, Stc): 由服务器发起调用,并在相应的客户端上执行。常用于同步服务器上的状态变化到每个客户端,比如角色位置更新、状态改变等。
- 可靠RPC与不可靠RPC:
- 可靠RPC保证消息至少送达一次,适用于重要状态更新,如生命值变动。
- 不可靠RPC不保证消息送达,适用于可以容忍少量丢失的非关键信息,如瞬态动画触发。
- RPC函数的使用:
- 标记: 在虚幻引擎蓝图或C++代码中,通过特定的宏例如
UFUNCTION(BlueprintCallable, Reliable, Server)
来标记一个函数为RPC函数,并指定其调用方向和可靠性。 - 调用: 客户端或服务器通过特定的接口调用这些函数,引擎负责在网络间传输调用请求及其参数
- 验证与执行: 服务器通常会验证接收到的RPC调用是否合法,防止作弊。验证通过后,执行函数逻辑,并可能触发进一步的RPC调用或游戏状态更新。
- 同步: 对于需要同步到所有客户端的更改,服务器会通过Stc RPC广播出去,确保所有玩家看到相同的游戏状态。
- 标记: 在虚幻引擎蓝图或C++代码中,通过特定的宏例如
验证流程
- 在
GamePlayCodeParsingGameMode
类中重写BeginPlay、PreLogin、Login、PostLogin
函数进行打印验证流程 - GamePlayCodeParsingGameMode.h
// Copyright Epic Games, Inc. All Rights Reserved.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameModeBase.h"
#include "GamePlayCodeParsingGameMode.generated.h"UCLASS(minimalapi)
class AGamePlayCodeParsingGameMode : public AGameModeBase
{GENERATED_BODY()public:AGamePlayCodeParsingGameMode();//重写BeginPlayvirtual void BeginPlay() override;//重写PreLogin进行打印验证virtual void PreLogin(const FString& Options, const FString& Address, const FUniqueNetIdRepl& UniqueId, FString& ErrorMessage) override;//重写Login进行打印验证virtual APlayerController* Login(UPlayer* NewPlayer, ENetRole InRemoteRole, const FString& Portal, const FString& Options, const FUniqueNetIdRepl& UniqueId, FString& ErrorMessage) override;//PostLogin进行打印验证virtual void PostLogin(APlayerController* NewPlayer) override;};
- GamePlayCodeParsingGameMode.cpp
// Copyright Epic Games, Inc. All Rights Reserved.#include "GamePlayCodeParsingGameMode.h"
#include "GamePlayCodeParsingCharacter.h"
#include "GamePlayPlayerController.h"
#include "UObject/ConstructorHelpers.h"
#include "Utils/BFL_LogManager.h"//自定义标签
DEFINE_LOG_CATEGORY_STATIC(MyLog_GameMode, Log, All);AGamePlayCodeParsingGameMode::AGamePlayCodeParsingGameMode()
{// set default pawn class to our Blueprinted characterstatic ConstructorHelpers::FClassFinder<APawn> PlayerPawnBPClass(TEXT("/Game/ThirdPerson/Blueprints/BP_ThirdPersonCharacter"));if (PlayerPawnBPClass.Class != NULL){DefaultPawnClass = PlayerPawnBPClass.Class;}//注册控制器PlayerControllerClass = AGamePlayPlayerController::StaticClass();
}void AGamePlayCodeParsingGameMode::BeginPlay()
{Super::BeginPlay();UE_LOG(MyLog_GameMode, Log, TEXT("----BenginPlay----NetMode:%s"),*UBFL_LogManager::GetNetModeStr(this));
}void AGamePlayCodeParsingGameMode::PreLogin(const FString& Options, const FString& Address, const FUniqueNetIdRepl& UniqueId, FString& ErrorMessage)
{Super::PreLogin(Options, Address, UniqueId, ErrorMessage);UE_LOG(MyLog_GameMode, Log, TEXT("----PreLogin----NetMode:%s"), *UBFL_LogManager::GetNetModeStr(this));
}APlayerController* AGamePlayCodeParsingGameMode::Login(UPlayer* NewPlayer, ENetRole InRemoteRole, const FString& Portal, const FString& Options, const FUniqueNetIdRepl& UniqueId, FString& ErrorMessage)
{UE_LOG(MyLog_GameMode, Log, TEXT("----Login----NetMode:%s"), *UBFL_LogManager::GetNetModeStr(this));return Super::Login(NewPlayer,InRemoteRole,Portal,Options,UniqueId,ErrorMessage);
}void AGamePlayCodeParsingGameMode::PostLogin(APlayerController* NewPlayer)
{Super::PostLogin(NewPlayer);UE_LOG(MyLog_GameMode, Log, TEXT("----PostLogin----NetMode:%s"), *UBFL_LogManager::GetNetModeStr(this));
}
- 运行脚本验证日志
- 客户端,在单机状态时首先执行的
Login
,然后是OnProssess
接管,再是BeginPlay
,最后才是PlayerController
- 服务器,首先执行了一下
BeginPlay
- 首先进行
PreLogin
预登陆,然后是客户端的网速是100000,客户端进行加入,然后进行Login
登录,PlayerController
就生成了,然后OnProssess
进行接管,最种PostLogin
登录完毕
- 到这里就验证完成了
PreLogin源码解析
- 源码:
void AGameModeBase::PreLogin(const FString& Options, const FString& Address, const FUniqueNetIdRepl& UniqueId, FString& ErrorMessage)
{// Login unique id must match server expected unique id type OR No unique id could mean game doesn't use themconst bool bUniqueIdCheckOk = (!UniqueId.IsValid() || UOnlineEngineInterface::Get()->IsCompatibleUniqueNetId(UniqueId));if (bUniqueIdCheckOk){//批准登录ErrorMessage = GameSession->ApproveLogin(Options);}else{ErrorMessage = TEXT("incompatible_unique_net_id");}FGameModeEvents::GameModePreLoginEvent.Broadcast(this, UniqueId, ErrorMessage);
}
ProLogin主要是验证
:登录唯一id必须与服务器期望的唯一id类型匹配,否则没有唯一id可能意味着游戏不使用它们
Login源码解析
- 源码:
APlayerController* AGameModeBase::Login(UPlayer* NewPlayer, ENetRole InRemoteRole, const FString& Portal, const FString& Options, const FUniqueNetIdRepl& UniqueId, FString& ErrorMessage)
{if (GameSession == nullptr){//GameSession为空,生成PalyerController就失败了ErrorMessage = TEXT("Failed to spawn player controller, GameSession is null");return nullptr;}ErrorMessage = GameSession->ApproveLogin(Options);if (!ErrorMessage.IsEmpty()){return nullptr;}//生成PlayerController,APlayerController* const NewPlayerController = SpawnPlayerController(InRemoteRole, Options);if (NewPlayerController == nullptr){// Handle spawn failure.UE_LOG(LogGameMode, Log, TEXT("Login: Couldn't spawn player controller of class %s"), PlayerControllerClass ? *PlayerControllerClass->GetName() : TEXT("NULL"));ErrorMessage = FString::Printf(TEXT("Failed to spawn player controller"));return nullptr;}// Customize incoming player based on URL optionsErrorMessage = InitNewPlayer(NewPlayerController, UniqueId, Options, Portal);if (!ErrorMessage.IsEmpty()){NewPlayerController->Destroy();return nullptr;}return NewPlayerController;
}
Login
主要作用:生成PlayerController
。服务器中为其他客户端生成PlayerController的逻辑就是在Login函数中
PostLogin源码解析
- 源码:
void AGameModeBase::PostLogin(APlayerController* NewPlayer)
{// Runs shared initialization that can happen during seamless travel as wellGenericPlayerInitialization(NewPlayer);// Perform initialization that only happens on initially joining a serverUWorld* World = GetWorld();NewPlayer->ClientCapBandwidth(NewPlayer->Player->CurrentNetSpeed);if (MustSpectate(NewPlayer)){NewPlayer->ClientGotoState(NAME_Spectating);}else{// If NewPlayer is not only a spectator and has a valid ID, add it as a user to the replay.const FUniqueNetIdRepl& NewPlayerStateUniqueId = NewPlayer->PlayerState->GetUniqueId();if (NewPlayerStateUniqueId.IsValid() && NewPlayerStateUniqueId.IsV1()){//游戏回放列表GetGameInstance()->AddUserToReplay(NewPlayerStateUniqueId.ToString());}}if (GameSession){GameSession->PostLogin(NewPlayer);}DispatchPostLogin(NewPlayer);// Now that initialization is done, try to spawn the player's pawn and start matchHandleStartingNewPlayer(NewPlayer);
}
- 首先就是
GenericPlayerInitialization(NewPlayer);
初始化 - GenericPlayerInitialization源码:
void AGameModeBase::GenericPlayerInitialization(AController* C)
{APlayerController* PC = Cast<APlayerController>(C);if (PC != nullptr){//生成HUDInitializeHUDForPlayer(PC);// Notify the game that we can now be muted and mute othersUpdateGameplayMuteList(PC);if (GameSession != nullptr){// Tell the player to enable voice by default or use the push to talk method//开启语音功能PC->ClientEnableNetworkVoice(!GameSession->RequiresPushToTalk());}ReplicateStreamingStatus(PC);bool HidePlayer = false, HideHUD = false, DisableMovement = false, DisableTurning = false;// Check to see if we should start in cinematic modeif (ShouldStartInCinematicMode(PC, HidePlayer, HideHUD, DisableMovement, DisableTurning)){//SetCinematicMode用于游戏场景创建电影一般的呈现效果PC->SetCinematicMode(true, HidePlayer, HideHUD, DisableMovement, DisableTurning);}}
}
PostLogin主要作用
:玩家初始化,包括玩家HUD的初始化、网络语音、画面、网络宽带、将玩家加入到游戏实例中的游戏回放列表中
HUD类详解
- HUD类:全称(Heads-up Display),用于在游戏中显示2D界面元素和用户界面的类,这个类提供了一些游戏图形场景元素创建管理的方法,主要作用于
UI管理的类
- UFUNCTION(
exec
):控制台命令,下面这些函数一般是用于调试信息的一些函数
//=============================================================================// Utils/** hides or shows HUD */UFUNCTION(exec)virtual void ShowHUD();/*** Toggles displaying properties of player's current ViewTarget* DebugType input values supported by base engine include "AI", "physics", "net", "camera", and "collision"*/UFUNCTION(exec)virtual void ShowDebug(FName DebugType = NAME_None);/*** Toggles sub categories of show debug to customize display*/UFUNCTION(exec)void ShowDebugToggleSubCategory(FName Category);/** Toggles 'ShowDebug' from showing debug info between reticle target actor (of subclass <DesiredClass>) and camera view target */UFUNCTION(exec)void ShowDebugForReticleTargetToggle(TSubclassOf<AActor> DesiredClass);
- 下面一些是重写函数
BlueprintImplementableEvent
标签,这些函数是在cpp中定义蓝图中实现的BlueprintCosmetic
加了这个标签,蓝图节点就会有一个小电脑的标志,被这个标签标志的函数只能在客户端运行,不能在服务器上运行
meta=(DisplayName = "HitBoxClicked")
:在蓝图中调用函数的名字就是这个HitBoxClicked
名字
/** * Hook to allow blueprints to do custom HUD drawing. @see bSuppressNativeHUD to control HUD drawing in base class. * Note: the canvas resource used for drawing is only valid during this event, it will not be valid if drawing functions are called later (e.g. after a Delay node).*/
UFUNCTION(BlueprintImplementableEvent, BlueprintCosmetic)
void ReceiveDrawHUD(int32 SizeX, int32 SizeY);/** Called when a hit box is clicked on. Provides the name associated with that box. */
UFUNCTION(BlueprintImplementableEvent, BlueprintCosmetic, meta=(DisplayName = "HitBoxClicked"))
void ReceiveHitBoxClick(const FName BoxName);
/** Native handler, called when a hit box is clicked on. Provides the name associated with that box. */
virtual void NotifyHitBoxClick(FName BoxName);/** Called when a hit box is unclicked. Provides the name associated with that box. */
UFUNCTION(BlueprintImplementableEvent, BlueprintCosmetic, meta=(DisplayName = "HitBoxReleased"))
void ReceiveHitBoxRelease(const FName BoxName);
/** Native handler, called when a hit box is unclicked. Provides the name associated with that box. */
virtual void NotifyHitBoxRelease(FName BoxName);/** Called when a hit box is moused over. */
UFUNCTION(BlueprintImplementableEvent, BlueprintCosmetic, meta=(DisplayName = "HitBoxBeginCursorOver"))
void ReceiveHitBoxBeginCursorOver(const FName BoxName);
/** Native handler, called when a hit box is moused over. */
virtual void NotifyHitBoxBeginCursorOver(FName BoxName);/** Called when a hit box no longer has the mouse over it. */
UFUNCTION(BlueprintImplementableEvent, BlueprintCosmetic, meta=(DisplayName = "HitBoxEndCursorOver"))
void ReceiveHitBoxEndCursorOver(const FName BoxName);
/** Native handler, called when a hit box no longer has the mouse over it. */
virtual void NotifyHitBoxEndCursorOver(FName BoxName);
- 这两函数将2D坐标与3D坐标进行变换
Project函数
:负责将三维游戏世界中的一个位置投射到二维屏幕上,可以选择是否将结果限制在零高度平面上,常用于计算界面元素应相对于某个世界位置显示的位置。Deproject函数
:则执行相反的操作,它接受屏幕坐标(ScreenX, ScreenY)并计算出相对应的三维世界空间中的位置(WorldPosition)以及观察方向(WorldDirection),这对于实现如射击瞄准、UI 元素交互等功能非常有用。
- 获取控制器与玩家的函数
- HUD的重写函数与简单的绘画函数,用得比较少了解即可。
创建HUD和UserWidget
-
新建一个
HUD
类,然后新建几个UserWidget
类,一般做项目的时候会有很多UserWidget
。一般会隔一层
,就是创建一个UserWidget
类作为规则类
,然后其他的所有UserWidget
类继承自这个规则类
-
新建HUD类
-
新建主UI的规则类
-
后面的UserWidget类都继承自这个规则类
-
派生MainUserUI、PlayerInfoUI、UserSystemUI的蓝图类
-
然后在HUD类中创建
BPUI_Main
UI界面
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once#include "CoreMinimal.h"
#include "GameFramework/HUD.h"
#include "MyHUD.generated.h"/*** */
UCLASS()
class GAMEPLAYCODEPARSING_API AMyHUD : public AHUD
{GENERATED_BODY()public:AMyHUD();//重写BeginPlayvirtual void BeginPlay() override;//承接ConstructorHelpers::FClassFinder找到局部变量TSubclassOf<class UUI_MainUserUI> UI_MainUI;//UI_MainUserUI的指针UUI_MainUserUI* MainUI;//创建主UI界面函数void CreateMainUI();
};
- 创建
BPUI_Main
UI界面 static ConstructorHelpers::FClassFinder<UUI_MainUserUI> MainUIClass(TEXT("/Game/UMG/BPUI_MainUserUI"));
/Game/UMG/BPUI_MainUserUI
:是资源在内容浏览器中的路径。这个路径指向一个具体的蓝图类
// Fill out your copyright notice in the Description page of Project Settings.#include "MyHUD.h"
#include "../UI/UI_MainUserUI.h"AMyHUD::AMyHUD()
{static ConstructorHelpers::FClassFinder<UUI_MainUserUI> MainUIClass(TEXT("/Game/UMG/BPUI_MainUserUI"));if (MainUIClass.Class){//存储MainUIClass值UI_MainUI = MainUIClass.Class;}
}void AMyHUD::BeginPlay()
{//使用创建UI函数Super::BeginPlay();CreateMainUI();
}void AMyHUD::CreateMainUI()
{//创建UIMainUI = CreateWidget<UUI_MainUserUI>(GetWorld(), UI_MainUI);MainUI->AddToViewport(0);
}
- 最后在GameMode里面注册HUD类即可
- 然后在
BPUI_MainUserUI
蓝图中随便绘制一下看看效果
虚幻C++中绑定UI控件
制作UI界面
- 制作PlayerInfoUI界面
- 制作UseSystemUI界面
- 首先绑定时间Text,直接在蓝图中进行绑定方便,创建绑定函数,填写逻辑
- 时间获取逻辑
- 将这两个UI添加到主UI蓝图上
- 运行结果
C++绑定UI控件
- 在
PlayerInfoUI
类中进行声明绑定UI控件 - UPROPERTY(BlueprintReadWrite,meta = (BindWidget))
- meta=(BindWidget):
BindWidget
是用于将C++类
与UMG
(Unreal Motion Graphics)界面蓝图中的UI小部件进行绑定的一种机制 - 进行了绑定,所以要创建和你C++编写名称一样的组件,也就是
蓝图里面的组件要和C++进行绑定的组件名字一样
- meta=(BindWidget):
- UI_PlayerInfoUI.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GamePlayCodeParsing/UI/UI_Main.h"
#include "UI_PlayerInfoUI.generated.h"
/*** *///前向声明
class UTextBlock;
class UProgressBar;
UCLASS()
class GAMEPLAYCODEPARSING_API UUI_PlayerInfoUI : public UUI_Main
{GENERATED_BODY()public://UWidget类里面的BeginPlay是NativeConstructvirtual void NativeConstruct() override;//进行设置控件的函数void SetPlayerName(FName NewName);void SetPlayerHPBar(float Percent);void SetCurHP(float HP);void SetMaxHP(float HP);protected://进行绑定UPROPERTY(BlueprintReadWrite,meta = (BindWidget))UTextBlock* PlayerName;UPROPERTY(BlueprintReadWrite,meta = (BindWidget))UProgressBar* PlayerHPBar;UPROPERTY(BlueprintReadWrite,meta=(BindWidget))UTextBlock* CurHP;UPROPERTY(BlueprintReadWrite, meta = (BindWidget))UTextBlock* MaxHP;
};
- 在
UserSystemUI
类中进行声明绑定UI控件 - UUI_UserSystemUI.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GamePlayCodeParsing/UI/UI_Main.h"
#include "UI_UserSystemUI.generated.h"/*** *///前向声明
class UImage;
class UTextBlock;UCLASS()
class GAMEPLAYCODEPARSING_API UUI_UserSystemUI : public UUI_Main
{GENERATED_BODY()
public://UWidget类里面的BeginPlay是NativeConstructvirtual void NativeConstruct() override;//进行设置控件的函数void SetCurLevelName();protected://进行绑定UPROPERTY(BlueprintReadWrite, meta = (BindWidget))UImage* ImageIcon;UPROPERTY(BlueprintReadWrite, meta = (BindWidget))UTextBlock* LevelName;UPROPERTY(BlueprintReadWrite, meta = (BindWidget))UTextBlock* Time;
};
- UI_PlayerInfoUI.cpp
// Fill out your copyright notice in the Description page of Project Settings.#include "UI_UserSystemUI.h"
#include "Kismet/GameplayStatics.h"
#include "UMG/Public/Components/TextBlock.h"
void UUI_UserSystemUI::NativeConstruct()
{Super::NativeConstruct();}
void UUI_UserSystemUI::SetCurLevelName()
{if (LevelName){//获取当前地图名字FString CurLevelName = UGameplayStatics::GetCurrentLevelName(GetWorld());//设置当前地图名字到LevelName文本组件LevelName->SetText(FText::FromString(CurLevelName));}
}
- 最后在主UI界面绑定一下PlayerInfoUI与UserSystemUI这两个界面UI
- 最后运行一下,写了获取逻辑的就显示到游戏里面了
虚幻C++中读取命令行文本及UI逻辑定义
-
玩家名字是通过批处理脚本填的所以我们需要从命令行中获取玩家名字
-
使用批处理脚本本质就是打开引擎,打开引擎就会生成游戏实例,游戏实例生命周期很长,虚幻引擎有个游戏实例类,当打开游戏的时候实例就会生成,游戏结束才会销毁,游戏实例类只存在一个
-
新建一个
GameInstance
游戏实例类
-
生成完后进行绑定
-
在游戏实例类中获取命令行参,打印主角名字到日志查看
-
然后去
PlayerInfoUI
类里面将没有获取的信息填入
// Fill out your copyright notice in the Description page of Project Settings.#include "UI_PlayerInfoUI.h"
#include "../GPProjectGameInstance.h"
#include "UMG/Public/Components/TextBlock.h"
#include "UMG/Public/Components/ProgressBar.h"
void UUI_PlayerInfoUI::NativeConstruct()
{Super::NativeConstruct();//获取默认的参数FString CurHP_String = CurHP->GetText().ToString();float CurHP_Float = FCString::Atof(*CurHP_String);FString MaxHP_String = MaxHP->GetText().ToString();float MaxHP_Float = FCString::Atof(*MaxHP_String);//调用设置血条的函数SetPlayerHPBar(CurHP_Float / MaxHP_Float);//获取游戏实例UGPProjectGameInstance* GI = Cast<UGPProjectGameInstance>(GetGameInstance());if (GI){//调用设置游戏角色名字函数SetPlayerName(*GI->PlayerName);}
}void UUI_PlayerInfoUI::SetPlayerName(FName NewName)
{if (PlayerName){//设置游戏名字到UIPlayerName->SetText(FText::FromName(NewName));}
}void UUI_PlayerInfoUI::SetPlayerHPBar(float Percent)
{if (PlayerHPBar){//设置百分百PlayerHPBar->SetPercent(Percent);}
}void UUI_PlayerInfoUI::SetCurHP(float HP)
{if (CurHP){//float->FString->Text CurHP->SetText(FText::FromString(FString::SanitizeFloat(HP)));}
}void UUI_PlayerInfoUI::SetMaxHP(float HP)
{if (MaxHP){//float->FString->Text MaxHP->SetText(FText::FromString(FString::SanitizeFloat(HP)));}
}
- 使用批处理脚本运行结果
相关文章:

虚幻引擎5 Gameplay框架(二)
Gameplay重要类及重要功能使用方法(一) 配置LOG类及PlayerController的网络机制 探索验证GamePlay重要函数、类的执行顺序与含义 我们定义自己的日志,专门建立一个存放自己日志的类,这个类继承自BlueprintFunctionLibrary 然后…...

云原生Kubernetes: K8S 1.29版本 部署Sonarqube
一、实验 1.环境 (1)主机 表1 主机 主机架构版本IP备注masterK8S master节点1.29.0192.168.204.8 node1K8S node节点1.29.0192.168.204.9node2K8S node节点1.29.0192.168.204.10已部署Kuboard (2)master节点查看集群 1&…...

读天才与算法:人脑与AI的数学思维笔记19_深度数学
1. 深度数学 1.1. 组合与选择,是发明新事物的两个不可或缺的条件 1.1.1. 保尔瓦雷里(Paul Valry) 1.2. 利用以往的数学定理证明过程训练算法,以发现新的定理 1.3. 谷歌设在伦敦的总部整体有一种现代牛津大学的感觉,…...

Springboot+Vue项目-基于Java+MySQL的旅游网站系统(附源码+演示视频+LW)
大家好!我是程序猿老A,感谢您阅读本文,欢迎一键三连哦。 💞当前专栏:Java毕业设计 精彩专栏推荐👇🏻👇🏻👇🏻 🎀 Python毕业设计 &…...
Element UI 简介
Element UI是一个基于Vue.js的组件库,提供了一套丰富的可复用的组件,包括按钮、表单、弹框、表格、菜单等等。它的设计风格简洁大方,易于使用,能够帮助开发者快速构建现代化的Web应用。 在Element UI中,有许多常用的组…...
mysql 删除重复的数据保留id最大的一条
在 MySQL 中,可以使用以下查询删除重复数据,只保留 ID 最大的那条记录: SQL DELETE t FROM table_name t LEFT JOIN ( SELECT column_name, MAX(id) AS max_id FROM table_name GROUP BY column_name ) t2 ON t.column_name t2…...

UE4 Widget制作搜索框
效果: 一、控件层级结构 1.父控件层级结构 2.子控件层级结构 二、蓝图 1.先清除掉创建子项(注意:这里使用的是reverse循环!) 2.判断是否含有关键字,创建子控件...

JavaScript js写九九乘法表(两种方法)
方法一: 观察规律: 第一个数每行都是自增1。 我们发下第二个数都是从1开始,依次递增1,永远不大于前面的数。 前面数字每自增一次,后面数字自增一轮。 我们可以用双重for循环,外层初始值设为i࿰…...
算法--贪心算法
贪心算法是一种在每一步选择中都采取在当前状态下最好或最优(即最有利)的选择,从而希望导致结果是全局最好或最优的算法。贪心算法在有最优子结构的问题中尤其有效,这意味着局部最优解能决定全局最优解。简单来说,贪心…...

Redis基本數據結構 ― String
Redis基本數據結構 ― String 介紹常用命令範例1. 為字串鍵設值/取得字串鍵的值2. 查看字串鍵的過期時間3. 如何為key設置時間?4. 如何刪除指定key?5. 如何增加value的值?6. 獲取value值的長度 介紹 字串鍵是Redis中最基本的鍵值對類型,這種類型的鍵值對會在數據…...

php7.4在foreach中对使用数据使用无法??[]判读,无法使用引用传递
代码如下图:这样子在foreach中是无法修改class_history的。正确的应该是去掉??[]判断。 public function actionY(){$array [name>aaa,class_history>[[class_name>一班,class_num>1],[class_name>二班,class_num>2]]];foreach ($array[class_…...

传输层协议 TCP UDP协议 解析(二)
文章目录 UDP:用户数据报协议UDP报文格式TCP与UDP的区别 UDP:用户数据报协议 UDP是一种面向无连接的传输层协议(数据一直发送,没有ack,所以不需要考虑ack),传输可靠性没有保证。 UDP不提供重传…...
java+jsp+Oracle+Tomcat 记账管理系统论文(一)
⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️⬇️ ➡️点击免费下载全套资料:源码、数据库、部署教程、论文、答辩ppt一条龙服务 ➡️有部署问题可私信联系 ⬆️⬆️⬆️⬆️…...

echarts双Y轴,并实现图例等
一个Y轴时yAxis为对象 yAxis: {type: value,name: 占比(%) },两个Y轴时yAxis为数组 yAxis: [{ // 左侧的type: value,name: 占比(%),nameTextStyle: {padding: [0, 0, 10, -50]},min: 0,max: 100,splitNumber: this.splitNumber, // 设置坐标轴的分割段数interval: 20, // 标轴…...

STM32 工程移植 LVGL:一步一步完成
STM32 工程移植 LVGL:一步一步完成 LVGL,作为一款强大且灵活的开源图形库,专为嵌入式系统GUI设计而生,极大地简化了开发者在创建美观用户界面时的工作。作为一名初学者,小编正逐步深入探索LVGL的奥秘,并决…...
Linux中分析日志及问题排查
可以参考:Linux命令 Linux系统日志是系统管理和故障排查的关键工具。通过分析系统日志,我们能够深入了解系统的运行状况,迅速发现并解决潜在的问题。 1. 日志文件位置 系统日志通常存储在/var/log/目录下,不同的日志有不同的文件,如下: /var/log/syslog:系统日志,包含…...

复杂环境下实时鲁棒3D激光雷达定位
复杂环境下实时鲁棒3D激光雷达定位 一、摘要 定位是机器人领域的重要研究方向。本篇文章里,我们提出了一种基于3D激光雷达的复杂环境下的定位方案。我们首先使用GPS和雷达建立一张点云地图,然后在匹配定位的时候从大地图中分割出一个小地图,…...

9.3.k8s的控制器资源(deployment部署控制器)
目录 一、deployment部署控制器概念 二、deployment资源的清单编写 三、小结 功能 使用场景 原理 四、deployment实现升级和回滚 1.编辑deployment资源清单(v1版本) 2.创建service资源用于访问 编辑 3.修改deploy清单中pod镜像版本为V2 4…...

通过符号程序搜索提升prompt工程
原文地址:supercharging-prompt-engineering-via-symbolic-program-search 通过自动探索大量提示变体来找到更好的提示 2024 年 4 月 22 日 众所周知,LLMs的成功在很大程度上仍然取决于我们用正确的指导和例子来提示他们的能力。随着新一代LLMs变得越…...
js开启子线程及其使用
众所周知,js是单线程,但是可以开启子线程来帮忙处理一些数据,但是这个子线程是有限制的 1.必须是同源 2.完全受主线程控制 3.不能在子线程中操作dom节点 4.子线程没有window,可以使用self 5.等等 具体的查看官网 进程切换是要耗时…...
变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析
一、变量声明设计:let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性,这种设计体现了语言的核心哲学。以下是深度解析: 1.1 设计理念剖析 安全优先原则:默认不可变强制开发者明确声明意图 let x 5; …...
零门槛NAS搭建:WinNAS如何让普通电脑秒变私有云?
一、核心优势:专为Windows用户设计的极简NAS WinNAS由深圳耘想存储科技开发,是一款收费低廉但功能全面的Windows NAS工具,主打“无学习成本部署” 。与其他NAS软件相比,其优势在于: 无需硬件改造:将任意W…...

docker详细操作--未完待续
docker介绍 docker官网: Docker:加速容器应用程序开发 harbor官网:Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台,用于将应用程序及其依赖项(如库、运行时环…...
【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密
在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...

什么是库存周转?如何用进销存系统提高库存周转率?
你可能听说过这样一句话: “利润不是赚出来的,是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业,很多企业看着销售不错,账上却没钱、利润也不见了,一翻库存才发现: 一堆卖不动的旧货…...

剑指offer20_链表中环的入口节点
链表中环的入口节点 给定一个链表,若其中包含环,则输出环的入口节点。 若其中不包含环,则输出null。 数据范围 节点 val 值取值范围 [ 1 , 1000 ] [1,1000] [1,1000]。 节点 val 值各不相同。 链表长度 [ 0 , 500 ] [0,500] [0,500]。 …...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...
土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等
🔍 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术,可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势,还能有效评价重大生态工程…...

BCS 2025|百度副总裁陈洋:智能体在安全领域的应用实践
6月5日,2025全球数字经济大会数字安全主论坛暨北京网络安全大会在国家会议中心隆重开幕。百度副总裁陈洋受邀出席,并作《智能体在安全领域的应用实践》主题演讲,分享了在智能体在安全领域的突破性实践。他指出,百度通过将安全能力…...

CMake 从 GitHub 下载第三方库并使用
有时我们希望直接使用 GitHub 上的开源库,而不想手动下载、编译和安装。 可以利用 CMake 提供的 FetchContent 模块来实现自动下载、构建和链接第三方库。 FetchContent 命令官方文档✅ 示例代码 我们将以 fmt 这个流行的格式化库为例,演示如何: 使用 FetchContent 从 GitH…...