虚幻引擎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广播出去,确保所有玩家看到相同的游戏状态。

验证流程

  • 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_MainUI界面

// 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_MainUI界面
  • 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++进行绑定的组件名字一样
  • 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)));}
}
  • 使用批处理脚本运行结果
    在这里插入图片描述
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/7407.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

零基础怎么学鸿蒙开发?

对于零基础的学习者来说&#xff0c;掌握鸿蒙开发不仅是迈向新技术的第一步&#xff0c;更是开拓职业道路的重要机遇。随着鸿蒙系统在各行各业的应用逐渐扩展&#xff0c;对于掌握这一项技术的开发人员需求也随之增长。下文将为大家提供针对零基础学习鸿蒙开发的逻辑&#xff0…

提高曝光率与点击率:海外KOL推广与社交媒体算法的完美结合

在当今数字化时代&#xff0c;社交媒体已经成为了企业和品牌推广的重要渠道之一&#xff0c;海外KOL也在各个领域拥有着庞大的粉丝群体和影响力。然而&#xff0c;想要在众多内容中脱颖而出&#xff0c;提高推广内容的曝光率和点击率&#xff0c;仅仅依靠KOL的影响力是不够的&a…

论文阅读-THE GENERALIZATION GAP IN OFFLINE REINFORCEMENT LEARNING(ICLR 2024)

1.Motivation 本文希望比较online RL、offline RL、序列决策和BC等方法的泛化能力(对于不同的初始状态、transition functions、reward functions&#xff0c;现阶段offline RL训练的方式都是在同一个环境下的数据集进行训练)。实验发现offline的算法相较于online算法对新环境…

特别提醒:2024年软考连考时间再次调整!

近期&#xff0c;辽宁省发布了今年5月软考考试安排&#xff0c;有以下变化&#xff1a; 高级论文单独考试&#xff08;2小时&#xff09;&#xff0c;之前是案例和论文一起考&#xff0c;案例剩下的时间可以给论文用&#xff0c;现在是综合和案例一起考&#xff0c;综合剩下的…

菜鸡学习netty源码(一)——ServerBootStrap启动

1.概述 对于初学者而然,写一个netty本地进行测试的Server端和Client端,我们最先接触到的类就是ServerBootstrap和Bootstrap。这两个类都有一个公共的父类就是AbstractBootstrap. 那既然 ServerBootstrap和Bootstrap都有一个公共的分类,那就证明它们两个肯定有很多公共的职…

Redis集群.md

Redis集群 本章是基于 CentOS7 下的 Redis 集群教程&#xff0c;包括&#xff1a; 单机安装RedisRedis主从Redis分片集群 1.单机安装Redis 首先需要安装Redis所需要的依赖&#xff1a; yum install -y gcc tcl然后将课前资料提供的Redis安装包上传到虚拟机的任意目录&#xf…

如何在CentOS部署青龙面板并实现无公网IP远程访问本地面板

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

Java双亲委派机制

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 概述 Java程序在运…

一起长锈:4 默认不可变的变量绑定与引用(从Java与C++转Rust之旅)

讲动人的故事,写懂人的代码 故事梗概:在她所维护的老旧Java系统即将被淘汰的危机边缘,这位在编程中总想快速完事的女程序员,希望能转岗到公司内部使用Rust语言的新项目组,因此开始自学Rust;然而,在掌握了Rust编程知识之后,为了通过Rust项目组的技术面试,使得转岗成功而…

sql优化思路

sql的优化经验 这里解释一下SQL语句的优化的原理 1.指明字段名称&#xff0c;可以尽量使用覆盖索引&#xff0c;避免回表查询&#xff0c;因此可以提高效率 2.字面意思&#xff0c;无需过多赘述。索引就是为了提高查询效率的。 3.图中两条sql直接可以使用union all 或者 uni…

羊大师分解,羊奶不为人知的健康奇迹

羊大师分解&#xff0c;羊奶不为人知的健康奇迹 羊奶是一种营养丰富的食品&#xff0c;拥有许多不为人知的健康益处。 益智健脑&#xff1a;羊奶中含有较高的钙、蛋白质、氨基酸、维生素、矿物质、无机盐、核酸等营养成分&#xff0c;这些都对大脑发育及增强智力有很大帮助。…

MySQL mydumper工具

目录 1. mydumper介绍 2. mydumper参数解释 3. 备份例子 3.1 备份全库(未包含其他) 3.2 备份全库(包含其他) 3.3 备份指定数据库(-B或--database) 3.4 导出指定表(-T或--tables-list) 3.5 只导出表结构&#xff0c;不导出表数据(-d或--no-data) 3.6 只导出表数据&#…

包管理工具npm的安装和使用

包管理工具 管理 包 的应用软件&#xff0c;可以对 包 进行下载 安装&#xff0c;更新&#xff0c;删除&#xff0c;上传 等操作。 借助包管理工具&#xff0c;可以快速开发项目&#xff0c;提升开发效率。 包管理工具是一个通用的概念&#xff0c;很多编程语言都有包管理工…

likeshop多商户单商户商城_likeshop跑腿源码_likeshop物品租赁系统开源版怎么配置小程序对接?

本人是商业用户所以能持续得到最新商业版&#xff0c;今天我说下likeshop里面怎么打包小程序&#xff0c;大家得到程序时候会发现它有admin目录 app目录 server目录 这三个目录分别是做什么呢&#xff1f; 1.admin目录 下面都是架构文件使用得是Node.js打包得&#xff0c;至于…

管道通信与Linux命令的执行-(读书笔记-十三)

|前一个命令的输出作为后一个命令的输入。 在Linux中&#xff0c;|符号是一个管道符号&#xff0c;用于将前一个命令的输出作为后一个命令的输入。这种机制允许你将多个命令组合在一起&#xff0c;以执行复杂的操作。下面是一些基本的例子&#xff1a; 查看当前目录下的文件列…

数据互通新纪元,企业如何实现系统无缝对接?

一、客户介绍 某文化发展有限公司&#xff0c;是一家在二次元文化领域深耕的领先企业&#xff0c;自成立以来&#xff0c;始终致力于为广大二次元爱好者提供丰富多样的正版衍生品和独特的文化体验。作为一家专注于二次元周边产品的公司&#xff0c;该公司拥有全面的产品线&…

网站访问免费升级成 HTTPS——值得收藏

实现HTTPS协议可以分为四个主要步骤&#xff0c;以确保网站数据传输的安全性。以下是简明清晰的流程概述&#xff1a; 1. 申请SSL证书 - SSL证书是实现HTTPS的基础&#xff0c;它包含了网站的身份信息以及公钥等。首先&#xff0c;你需要从受信任的证书颁发机构(CA)申请一个SSL…

新建的springBoot WEB项目无法自动返回html模版(gradle+kotlin版本)

最近研究了springBoot创建web项目&#xff0c; 第一步服务端返回字符串没有问题&#xff0c;第二步返回html时&#xff0c;还是返回的字符串。 文章目录 一、参考方案二、新建springBoot web项目三、启动项目的三种方式 一、参考方案 将控制器类的 RestController 改为 Contro…

Listview控件的5种视图

在C#中&#xff0c;ListView控件是用于显示和编辑列表数据的常用控件。它可以显示数据项的列表&#xff0c;并允许用户对列表中的数据进行操作。 ListView控件有五种视图模式&#xff0c;分别是&#xff1a; LargeIcon视图&#xff1a;在此视图中&#xff0c;每个数据项都显示…

如何剪辑视频中不想要的片段?教你轻松剪辑

如何剪辑视频中不想要的片段&#xff1f;在视频编辑的精湛艺术中&#xff0c;精确剪辑掉不想要的片段无疑是提升视频品质和专业水准的关键一环。技术的进步使我们得以借助多种高效、易用的视频剪辑软件&#xff0c;轻松去除冗余&#xff0c;保留精华&#xff0c;从而创作出更加…