虚幻引擎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 rotation
		SetControlRotation( 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();

	//重写BeginPlay
	virtual 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 character
	static 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 them
	const 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 options
	ErrorMessage = 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 well

	GenericPlayerInitialization(NewPlayer);

	// Perform initialization that only happens on initially joining a server

	UWorld* 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 match
	HandleStartingNewPlayer(NewPlayer);
}
  • 首先就是GenericPlayerInitialization(NewPlayer);初始化
  • GenericPlayerInitialization源码:
void AGameModeBase::GenericPlayerInitialization(AController* C)
{
	APlayerController* PC = Cast<APlayerController>(C);
	if (PC != nullptr)
	{
		//生成HUD
		InitializeHUDForPlayer(PC);

		// Notify the game that we can now be muted and mute others
		UpdateGameplayMuteList(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 mode
		if (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();

	//重写BeginPlay
	virtual 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()
{
	//创建UI
	MainUI = 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是NativeConstruct
	virtual 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是NativeConstruct
	virtual 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)
	{
		//设置游戏名字到UI
		PlayerName->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.mfbz.cn/a/597383.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Prometheus 2: 一个专门评估其他语言模型的开源语言模型(续集)

普罗米修斯的续集来了。 专有的语言模型如 GPT-4 经常被用来评估来自各种语言模型的回应品质。然而,透明度、可控制性和可负担性等考虑强烈促使开发专门用于评估的开源语言模型。另一方面,现有的开源评估语言模型表现出关键的缺点:1) 它们给出的分数与人类给出的分数存在显著差…

[Android]四大组件简介

在 Android 开发中&#xff0c;“四大组件”&#xff08;Four Major Components&#xff09;是指构成 Android 应用程序的四种核心组件&#xff0c;它们通过各自的方式与系统交互&#xff0c;实现应用的多样功能。这些组件是&#xff1a;Activity、Service、Broadcast Receiver…

用 node 写一个命令行工具,全局安装可用

现在&#xff0c;不管是前端项目还是 node 项目&#xff0c;一般都会用 npm 做包管理工具&#xff0c;而 package.json 是其相关的配置信息。 对 node 项目而言&#xff0c;模块导出入口文件由 package.json 的 main 字段指定&#xff0c;而如果是要安装到命令行的工具&#x…

28 - 算术运算指令

---- 整理自B站UP主 踌躇月光 的视频 文章目录 1. ALU改进2. CPU 整体电路3. 程序4. 实验结果 1. ALU改进 此前的 ALU&#xff1a; 改进后的 ALU&#xff1a; 2. CPU 整体电路 3. 程序 # pin.pyMSR 1 MAR 2 MDR 3 RAM 4 IR 5 DST 6 SRC 7 A 8 B 9 C 10 D 11 DI 1…

在.NET架构的Winform项目中引入“异步编程”思想和技术

在.NET架构的Winform项目中引入“异步编程”思想和技术 一、异步编程引入&#xff08;1&#xff09;异步编程引入背景&#xff08;2&#xff09;异步编程程序控制流图&#xff08;3&#xff09;异步编程前置知识&#xff1a; 二、异步编程demo步骤1&#xff1a;步骤2&#xff1…

政安晨:【Keras机器学习示例演绎】(三十八)—— 从零开始的文本分类

目录 简介 设置 加载数据IMDB 电影评论情感分类 准备数据 数据矢量化的两种选择 建立模型 训练模型 在测试集上评估模型 制作端到端模型 政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: TensorFlow与Keras机器学习实战 希望政安晨…

在Linux上使用Selenium驱动Chrome浏览器无头模式

大家好&#xff0c;我们平时在做UI自动化测试的时候&#xff0c;经常会用到Chrome浏览器的无头模式&#xff08;无界面模式&#xff09;&#xff0c;并且将测试代码部署到Linux系统中执行&#xff0c;或者平时我们写个爬虫爬取网站的数据也会使用到&#xff0c;接下来和大家分享…

软考中级-软件设计师(九)数据库技术基础 考点最精简

一、基本概念 1.1数据库与数据库系统 数据&#xff1a;是数据库中存储的基本对象&#xff0c;是描述事物的符号记录 数据库&#xff08;DataBase&#xff0c;DB&#xff09;&#xff1a;是长期存储在计算机内、有组织、可共享的大量数据集合 数据库系统&#xff08;DataBas…

python基础---面向对象相关知识

面向对象 可以把数据以及功能打包为一个整体 类: 名称属性(数据)方法 class Person:def __init__(self, name, age):self.age ageself.name namedef print_info:print(self.name, self.age)定义 #经典类 class Dog1:pass# 新式类 class Dog2(object):pass在python3里面这…

LeetCode-741. 摘樱桃【数组 动态规划 矩阵】

LeetCode-741. 摘樱桃【数组 动态规划 矩阵】 题目描述&#xff1a;解题思路一&#xff1a;动态规划&#xff0c;定推初遍举。解题思路二&#xff1a;倒序循环解题思路三&#xff1a;0 题目描述&#xff1a; 给你一个 n x n 的网格 grid &#xff0c;代表一块樱桃地&#xff0…

VMware虚拟机中Linux系统奔溃,怎么办?

一大早启动虚拟机准备开始工作&#xff0c;却遭遇到Linux系统崩溃&#xff0c;屏幕上显示以下错误提示&#xff1a; 这段文本看起来是来自系统引导时的日志信息&#xff0c;提到了一些关于文件系统的问题和建议。根据这段信息&#xff0c;似乎 /dev/sda1 分区中的文件系统存在一…

红日靶场ATTCK 1通关攻略

环境 拓扑图 VM1 web服务器 win7&#xff08;192.168.22.129&#xff0c;10.10.10.140&#xff09; VM2 win2003&#xff08;10.10.10.135&#xff09; VM3 DC win2008&#xff08;10.10.10.138&#xff09; 环境搭建 win7&#xff1a; 设置内网两张网卡&#xff0c;开启…

SeetaFace6人脸检测C++代码实现Demo

SeetaFace6包含人脸识别的基本能力&#xff1a;人脸检测、关键点定位、人脸识别&#xff0c;同时增加了活体检测、质量评估、年龄性别估计&#xff0c;并且顺应实际应用需求&#xff0c;开放口罩检测以及口罩佩戴场景下的人脸识别模型。 官网地址&#xff1a;https://github.co…

dockerk8s常用知识点

1、什么是docker 容器化和虚拟化对比 ▪开源的应用容器引擎&#xff0c;基于 Go 语言开发 ▪容器是完全使用沙箱机制,容器开销极低 ▪Docker就是容器化技术的代名词 ▪Docker也具备一定虚拟化职能 docker三大核心&#xff1a; Docker Engine: 提供了一个可以用来运行和管…

代码+视频,R语言绘制生存分析模型的时间依赖(相关)性roc曲线和时间依赖(相关)性cindex曲线

ROC曲线分析是用于评估一个因素预测能力的手段&#xff0c;是可以用于连续型变量分组的方法。在生存分析中&#xff0c;疾病状态和因素取值均会随时间发生变化。而标准的ROC曲线分析将个体的疾病状态和因素取值视作固定值&#xff0c;未将时间因素考虑在分析之中。在这种情况下…

edge使用心得

1. **性能提升**&#xff1a;基于Chromium的Edge浏览器在速度和响应方面有显著提升&#xff0c;特别是在处理复杂的网页结构和执行JavaScript代码时。这意味着无论是日常浏览还是运行Web应用程序&#xff0c;都能享受流畅的用户体验。 2. **更好的兼容性**&#xff1a;由于与G…

大模型最新消息

最新消息如下&#xff1a; 大语言模型服务的多样化&#xff1a;互联网上出现了许多免费的大语言模型服务&#xff0c;如OpenAI的ChatGPT、Google的Gemini、Anthropic的Claude、Meta的Llama等。这些服务的推出使得大语言模型的应用更加广泛和便捷。软银和苹果的AI新动向&#x…

​​【收录 Hello 算法】3.3 数字编码

目录 3.3 数字编码 3.3.1 原码、反码和补码 3.3.2 浮点数编码 3.3 数字编码 Tip 在本书中&#xff0c;标题带有 * 符号的是选读章节。如果你时间有限或感到理解困难&#xff0c;可以先跳过&#xff0c;等学完必读章节后再单独攻克。 3.3.1 原码、反码和补码 在…

迅睿CMS中实现关键词搜索高亮

在迅睿CMS系统中实现关键词搜索高亮是提升用户体验和搜索效果的重要手段。当用户搜索某个关键词时&#xff0c;将搜索结果中的关键词高亮显示&#xff0c;可以帮助用户更快速地定位到所需信息。 关键词高亮的实现 在迅睿CMS中&#xff0c;你可以使用内置的dr_keyword_highlig…

Study--Oracle-01-单实例部署Oracle11G-R2

Oracle版本发布介绍 Oracle 19c和12c和11g功能区别_数据库_oracle_支持 一、CentOS 7 环境准备 1、软件准备 操作系统&#xff1a;CentOS 7 数据库版本: Oracle11g R2 2、操作系统环境配置 关闭selinux &#xff0c;编辑 /etc/selinux/config文件&#xff0c;设置SELINU…
最新文章