UE5多人MOBA+GAS 54、用户登录和会话创建请求

文章目录


创建主菜单需要的

创建主菜单游戏模式

MainMenuGameMode

创建主菜单游戏控制器

MainMenuPlayerController

cpp 复制代码
#pragma once

#include "CoreMinimal.h"
#include "MenuPlayerController.h"
#include "MainMenuPlayerController.generated.h"

/**
 * 
 */
UCLASS()
class CRUNCH_API AMainMenuPlayerController : public AMenuPlayerController
{
	GENERATED_BODY()
public:	
	AMainMenuPlayerController();
};
cpp 复制代码
#include "MainMenuPlayerController.h"

AMainMenuPlayerController::AMainMenuPlayerController()
{
	// 禁用自动管理相机目标
	bAutoManageActiveCameraTarget = false;
}

分别创建两个的蓝图

创建一个摄像机,自动启用玩家0

创建主菜单界面UI

MainMenuWidget

cpp 复制代码
#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "Components/WidgetSwitcher.h"
#include "Framework/MGameInstance.h"
#include "MainMenuWidget.generated.h"

class UButton;
/**
 * 
 */
UCLASS()
class CRUNCH_API UMainMenuWidget : public UUserWidget
{
	GENERATED_BODY()
public:
	virtual void NativeConstruct() override;
	
	/******************************/	
	/*           Main             */
	/******************************/	
private:
	UPROPERTY(meta = (BindWidget))
	TObjectPtr<UWidgetSwitcher> MainSwitcher;

	// 游戏实例
	UPROPERTY()
	TObjectPtr<UMGameInstance> MGameInstance;
	/******************************/	
	/*           Login            */
	/******************************/
private:
	// 登录界面的根组件
	UPROPERTY(meta = (BindWidget))
	TObjectPtr<UWidget> LoginWidgetRoot;

	// 登录按钮
	UPROPERTY(meta = (BindWidget))
	TObjectPtr<UButton> LoginButton;

	// 登录按钮点击事件
	UFUNCTION()
	void OnLoginButtonClicked();
	
	/**
	 * 登录完成时的回调函数
	 * @param bWasSuccessful 是否登录成功
	 * @param PlayerNickname 登录成功时的玩家昵称
	 * @param ErrorMsg 登录失败时的错误信息
	 */
	void LoginCompleted(bool bWasSuccessful, const FString& PlayerNickname, const FString& ErrorMsg);
};
cpp 复制代码
#include "MainMenuWidget.h"

#include "Components/Button.h"

void UMainMenuWidget::NativeConstruct()
{
	Super::NativeConstruct();

	// 获取游戏实例
	MGameInstance = GetGameInstance<UMGameInstance>();
	if (MGameInstance)
	{
		
	}

	// 绑定登录按钮点击事件
	LoginButton->OnClicked.AddDynamic(this, &UMainMenuWidget::OnLoginButtonClicked);
}

void UMainMenuWidget::OnLoginButtonClicked()
{
	// 登录按钮点击时触发
	UE_LOG(LogTemp, Warning, TEXT("Longing In!"))
}

void UMainMenuWidget::LoginCompleted(bool bWasSuccessful, const FString& PlayerNickname, const FString& ErrorMsg)
{
	if (bWasSuccessful)
	{
		UE_LOG(LogTemp, Warning, TEXT("登录成功: %s"), *PlayerNickname)
	}else
	{
		UE_LOG(LogTemp, Warning, TEXT("登录失败: %s"), *ErrorMsg)
	}
}

创建蓝图版本

然后修改对应命名

设置切换器的锚点

设置一下登录

添加一个尺寸框包裹

添加一个背景模糊

把UI添加到主菜单玩家控制器中

实现登录游戏实例

UMGameInstance

cpp 复制代码
/**
 * 登录完成委托
 * 参数:
 *   - bWasSuccessful:登录是否成功
 *   - PlayerNickName:玩家昵称
 *   - ErrorMsg:错误信息
 */
DECLARE_MULTICAST_DELEGATE_ThreeParams(FOnLoginCompleted, bool /*bWasSuccessful*/, const FString& /*PlayerNickName*/, const FString& /*ErrorMsg*/);
cpp 复制代码
	/*************************************/
	/*             登录功能              */
	/*************************************/
public:
	// 检查是否已登录
	bool IsLoggedIn() const;
	
	// 检查是否正在登录中
	bool IsLoggingIn() const;
	
	// 客户端通过账户门户登录
	void ClientAccountPortalLogin();
	
	// 登录完成委托
	FOnLoginCompleted OnLoginCompleted;
	
private:
	// 客户端登录实现
	void ClientLogin(const FString& Type, const FString& Id, const FString& Token);
	
	// 登录完成回调
	void LoginCompleted(int NumOfLocalPlayer, bool bWasSuccessful, const FUniqueNetId& UserId, const FString& Error);

	// 登录委托句柄
	FDelegateHandle LoggingInDelegateHandle;
cpp 复制代码
bool UMGameInstance::IsLoggedIn() const
{
	if (IOnlineIdentityPtr IdentityPtr = UTNetStatics::GetIdentityPtr())
	{
		// 查询本地玩家0的登录状态,是否为已登录
		return IdentityPtr->GetLoginStatus(0) == ELoginStatus::LoggedIn;
	}
	// 如果IdentityPtr无效,则默认未登录
	return false;
}

bool UMGameInstance::IsLoggingIn() const
{
	// 如果登录委托句柄有效,说明还在等待登录回调
	return LoggingInDelegateHandle.IsValid();
}

void UMGameInstance::ClientAccountPortalLogin()
{
	// 调用统一的ClientLogin接口,使用AccountPortal方式
	ClientLogin("AccountPortal", "", "");
}

void UMGameInstance::ClientLogin(const FString& Type, const FString& Id, const FString& Token)
{
	if (IOnlineIdentityPtr IdentityPtr = UTNetStatics::GetIdentityPtr())
	{
		// 如果已经有一个登录委托在监听,先移除它,避免重复绑定
		if (LoggingInDelegateHandle.IsValid())
		{
			IdentityPtr->OnLoginCompleteDelegates->Remove(LoggingInDelegateHandle);
			LoggingInDelegateHandle.Reset();
		}
		// 绑定登录完成回调
		LoggingInDelegateHandle = IdentityPtr->OnLoginCompleteDelegates->AddUObject(this, &UMGameInstance::LoginCompleted);
		
		// 调用OnlineSubsystem的登录函数(异步)
		if (!IdentityPtr->Login(0,FOnlineAccountCredentials(Type, Id, Token)))
		{
			UE_LOG(LogTemp, Warning, TEXT("登录失败!"))

			if (LoggingInDelegateHandle.IsValid())
			{
				IdentityPtr->OnLoginCompleteDelegates->Remove(LoggingInDelegateHandle);
				LoggingInDelegateHandle.Reset();
			}
			// 通知外部,登录失败
			OnLoginCompleted.Broadcast(false, "", "登录失败!");
		}
	}
}

void UMGameInstance::LoginCompleted(int32 NumOfLocalPlayer, bool bWasSuccessful, const FUniqueNetId& UserId,
	const FString& Error)
{
	if (IOnlineIdentityPtr IdentityPtr = UTNetStatics::GetIdentityPtr())
	{
		// 移除登录完成委托
		if (LoggingInDelegateHandle.IsValid())
		{
			IdentityPtr->OnLoginCompleteDelegates->Remove(LoggingInDelegateHandle);
			LoggingInDelegateHandle.Reset();
		}

		FString PlayerNickname = "";
		if (bWasSuccessful)
		{
			// 获取玩家昵称
			PlayerNickname = IdentityPtr->GetPlayerNickname(UserId);
			UE_LOG(LogTemp, Warning, TEXT("登录成功: %s"), *(PlayerNickname))
		}
		else
		{
			UE_LOG(LogTemp, Warning, TEXT("登录失败: %s"), *(Error))
		}
		
		OnLoginCompleted.Broadcast(bWasSuccessful, PlayerNickname, Error);
	}else
	{
		OnLoginCompleted.Broadcast(false, "", "无法找到身份指针");
	}
}

UMainMenuWidget到主菜单UI中绑定委托

cpp 复制代码
void UMainMenuWidget::NativeConstruct()
{
	Super::NativeConstruct();

	// 获取游戏实例
	MGameInstance = GetGameInstance<UMGameInstance>();
	if (MGameInstance)
	{
		MGameInstance->OnLoginCompleted.AddUObject(this, &UMainMenuWidget::LoginCompleted);
	}

	// 绑定登录按钮点击事件
	LoginButton->OnClicked.AddDynamic(this, &UMainMenuWidget::OnLoginButtonClicked);
}

void UMainMenuWidget::OnLoginButtonClicked()
{
	// 登录按钮点击时触发
	UE_LOG(LogTemp, Warning, TEXT("Longing In!"))
	if (MGameInstance && !MGameInstance->IsLoggedIn() && !MGameInstance->IsLoggingIn())
	{
		// 触发登录
		MGameInstance->ClientAccountPortalLogin();
	}
}

编译运行后点击登录会跳转到网页登录

创建等待界面

WaitingWidget

cpp 复制代码
#pragma once

#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "Components/Button.h"
#include "Components/TextBlock.h"
#include "WaitingWidget.generated.h"

/**
 * 等待界面控件
 * 用于在需要玩家等待(如连接、加载、匹配)时显示提示信息,
 * 并可选提供"取消"按钮。
 */
UCLASS()
class CRUNCH_API UWaitingWidget : public UUserWidget
{
	GENERATED_BODY()
public:
	virtual void NativeConstruct() override;
	// 获取 Cancel 按钮的点击事件引用,并清空之前绑定的所有回调。
	FOnButtonClickedEvent& ClearAndGetButtonClickedEvent();

	/**
	 * 设置等待提示信息,并控制"取消"按钮是否可见。
	 * @param WaitInfo   等待提示文本
	 * @param bAllowCancel 是否允许取消(决定按钮显隐)
	 */
	void SetWaitInfo(const FText& WaitInfo, bool bAllowCancel = false);

private:
	// 等待提示文本
	UPROPERTY(meta = (BindWidget))
	TObjectPtr<UTextBlock> WaitInfoText;

	// 取消按钮
	UPROPERTY(meta = (BindWidget))
	TObjectPtr<UButton> CancelButton;
};
cpp 复制代码
#include "WaitingWidget.h"

void UWaitingWidget::NativeConstruct()
{
	Super::NativeConstruct();
}

FOnButtonClickedEvent& UWaitingWidget::ClearAndGetButtonClickedEvent()
{
	// 清空按钮已有的点击事件
	CancelButton->OnClicked.Clear();
	return CancelButton->OnClicked;
}

void UWaitingWidget::SetWaitInfo(const FText& WaitInfo, bool bAllowCancel)
{
	// 设置取消按钮的可见性
	if (CancelButton)
	{
		CancelButton->SetVisibility(bAllowCancel ? ESlateVisibility::Visible : ESlateVisibility::Hidden);
	}

	// 更新提示文字
	if (WaitInfoText)
	{
		WaitInfoText->SetText(WaitInfo);
	}
}

回到主菜单界面UMainMenuWidget

cpp 复制代码
	/******************************/	
	/*           Main             */
	/******************************/
	// 切换到主界面
	void SwitchToMainWidget();
	// 主界面的根组件
	UPROPERTY(meta = (BindWidget))
	TObjectPtr<UWidget> MainWidgetRoot;

	/******************************/	
	/*           等待			  */
	/******************************/
private:
	// 等待界面
	UPROPERTY(meta = (BindWidget))
	TObjectPtr<UWaitingWidget> WaitingWidget;
	// 切换到等待界面
	FOnButtonClickedEvent& SwitchToWaitingWidget(const FText& WaitInfo, bool bAllowCancel = false);
cpp 复制代码
void UMainMenuWidget::NativeConstruct()
{
	Super::NativeConstruct();

	// 获取游戏实例
	MGameInstance = GetGameInstance<UMGameInstance>();
	if (MGameInstance)
	{
		MGameInstance->OnLoginCompleted.AddUObject(this, &UMainMenuWidget::LoginCompleted);
		if (MGameInstance->IsLoggedIn())
		{
			SwitchToMainWidget();
		}
	}

	// 绑定登录按钮点击事件
	LoginButton->OnClicked.AddDynamic(this, &UMainMenuWidget::OnLoginButtonClicked);
}

void UMainMenuWidget::SwitchToMainWidget()
{
	if (MainSwitcher)
	{
		MainSwitcher->SetActiveWidget(MainWidgetRoot);
	}
}

void UMainMenuWidget::OnLoginButtonClicked()
{
	// 登录按钮点击时触发
	UE_LOG(LogTemp, Warning, TEXT("Longing In!"))
	if (MGameInstance && !MGameInstance->IsLoggedIn() && !MGameInstance->IsLoggingIn())
	{
		// 触发登录
		MGameInstance->ClientAccountPortalLogin();
		// SwitchToWaitingWidget(FText::FromString("正在登录..."));
		SwitchToWaitingWidget(FText::FromString(FString(TEXT("正在登录..."))));
	}
}

void UMainMenuWidget::LoginCompleted(bool bWasSuccessful, const FString& PlayerNickname, const FString& ErrorMsg)
{
	if (bWasSuccessful)
	{
		UE_LOG(LogTemp, Warning, TEXT("登录成功: %s"), *PlayerNickname)
	}
	else
	{
		UE_LOG(LogTemp, Warning, TEXT("登录失败: %s"), *ErrorMsg)
	}
	SwitchToMainWidget();
}

FOnButtonClickedEvent& UMainMenuWidget::SwitchToWaitingWidget(const FText& WaitInfo, bool bAllowCancel)
{
	// 切换到等待界面
	MainSwitcher->SetActiveWidget(WaitingWidget);
	// 设置等待信息
	WaitingWidget->SetWaitInfo(WaitInfo, bAllowCancel);
	return WaitingWidget->ClearAndGetButtonClickedEvent();
}

创建等待UI蓝图版本

设置按钮锚点和位置

可以添加等待动画

主界面菜单中添加该UI

主菜单界面UMainMenuWidget中创建用于创建会话的按钮已经输入

cpp 复制代码
	/******************************/	
	/*           会话			  */
	/******************************/
	// 创建会话按钮
	UPROPERTY(meta=(BindWidget))
	TObjectPtr<UButton> CreateSessionButton;

	// 输入房间(会话)的名称的文本框
	UPROPERTY(meta=(BindWidget))
	TObjectPtr<UEditableText> NewSessionNameText;

	// 点击"创建会话"按钮时调用
	UFUNCTION()
	void CreateSessionBtnClicked();

	// 取消房间创建(如等待界面点击取消时触发)
	UFUNCTION()
	void CancelSessionCreation();

	// 房间(会话)名称输入框文字变更时调用(用于校验是否可创建)
	UFUNCTION()
	void NewSessionNameTextChanged(const FText& NewText);
cpp 复制代码
void UMainMenuWidget::NativeConstruct()
{
	Super::NativeConstruct();

	// 获取游戏实例
	MGameInstance = GetGameInstance<UMGameInstance>();
	if (MGameInstance)
	{
		MGameInstance->OnLoginCompleted.AddUObject(this, &UMainMenuWidget::LoginCompleted);
		if (MGameInstance->IsLoggedIn())
		{
			SwitchToMainWidget();
		}
	}

	// 绑定登录按钮点击事件
	LoginButton->OnClicked.AddDynamic(this, &UMainMenuWidget::OnLoginButtonClicked);
	// 绑定创建会话按钮点击事件
	CreateSessionButton->OnClicked.AddDynamic(this, &UMainMenuWidget::CreateSessionBtnClicked);
	// 绑定新会话名称输入框内容改变事件
	NewSessionNameText->OnTextChanged.AddDynamic(this, &UMainMenuWidget::NewSessionNameTextChanged);
}

void UMainMenuWidget::CreateSessionBtnClicked()
{
	// 确保玩家已登录
	if (MGameInstance && MGameInstance->IsLoggedIn())
	{
		// 请求创建并加入一个新的房间(会话)
		MGameInstance->RequestCreateAndJoinSession(FName(NewSessionNameText->GetText().ToString()));

		// 切换到等待界面,提示"Creating Lobby",并绑定取消操作
		SwitchToWaitingWidget(FText::FromString(FString(TEXT("创建大厅"))), true)
			.AddDynamic(this, &UMainMenuWidget::CancelSessionCreation);
	}
}

void UMainMenuWidget::CancelSessionCreation()
{
	if (MGameInstance)
	{
		MGameInstance->CancelSessionCreation();
	}
	SwitchToMainWidget();
}

void UMainMenuWidget::NewSessionNameTextChanged(const FText& NewText)
{
	// 创建按钮是否可用
	CreateSessionButton->SetIsEnabled(!NewText.IsEmpty());
}

游戏实例UMGameInstance中补充函数

cpp 复制代码
	/*************************************/
	/*      客户端会话创建和搜索			 */
	/*************************************/
public:
	// 请求创建并加入新会话
	void RequestCreateAndJoinSession(const FName& NewSessionName);
	
	// 取消会话创建
	void CancelSessionCreation();
cpp 复制代码
void UMGameInstance::RequestCreateAndJoinSession(const FName& NewSessionName)
{
	UE_LOG(LogTemp, Warning, TEXT("请求创建并加入会话: %s"), *(NewSessionName.ToString()))
}

void UMGameInstance::CancelSessionCreation()
{
	UE_LOG(LogTemp, Warning, TEXT("取消会话创建"))
}

添加控件

再修改一下

配置和获取协调器 URL

cpp 复制代码
	/**
	 * 获取协调器URL键值
	 * @return 协调器服务URL的FName键
	 */
	static FName GetCoordinatorURLKey();

	/**
	 * 获取协调器URL地址
	 * @return 协调器服务地址字符串
	 */
	static FString GetCoordinatorURL();

	/**
	 * 获取默认协调器URL
	 * @return 默认的协调器服务地址
	 */
	static FString GetDefaultCoordinatorURL();
cpp 复制代码
FName UTNetStatics::GetCoordinatorURLKey()
{
	// 返回用于解析命令行参数的键值(这里是固定字符串 "COORDINATOR_URL")
	return FName("COORDINATOR_URL");
}

FString UTNetStatics::GetCoordinatorURL()
{
	// 优先从命令行中获取 Coordinator URL
	FString CoordinatorURL = GetCommandlineArgAsString(GetCoordinatorURLKey());
	if (CoordinatorURL != "")
	{
		// 如果命令行中有值,则直接返回
		return CoordinatorURL;
	}

	// 如果命令行参数为空,则使用配置文件中的默认值
	return GetDefaultCoordinatorURL();
}

FString UTNetStatics::GetDefaultCoordinatorURL()
{
	FString CoordinatorURL = "";

	// 从配置文件 [Crunch.Net] 节点中读取键 "CoordinatorURL"
	// 目标配置文件为 DefaultGame.ini
	GConfig->GetString(TEXT("Crunch.Net"), TEXT("CoordinatorURL"), CoordinatorURL, GGameIni);

	// 打印日志,方便调试,输出获取到的默认 URL
	UE_LOG(LogTemp, Warning, TEXT("Getting Default Coordinator URL as: %s"), *CoordinatorURL)

	// 返回默认配置中的 URL
	return CoordinatorURL;
}

填写本地主机号,这里的 127.0.0.1表示 本机

ini 复制代码
[Crunch.Net]
CoordinatorURL="127.0.0.1"

撰写和发送会话创建请求

UMGameInstance

cpp 复制代码
#include "Interfaces/IHttpResponse.h"
#include "Interfaces/IHttpRequest.h"

	/*************************************/
	/*      客户端会话创建和搜索			 */
	/*************************************/

private:
	// 会话创建请求完成回调
	void SessionCreationRequestCompleted(FHttpRequestPtr Request, FHttpResponsePtr Response, bool bConnectedSuccessfully, FGuid SessionSearchId);
cpp 复制代码
void UMGameInstance::RequestCreateAndJoinSession(const FName& NewSessionName)
{
	UE_LOG(LogTemp, Warning, TEXT("请求创建并加入会话: %s"), *(NewSessionName.ToString()))
	
	// 创建HTTP请求对象,准备向协调器发起会话创建请求
	FHttpRequestRef Request = FHttpModule::Get().CreateRequest();
	// 生成唯一会话搜索ID用于后续查询
	FGuid SessionSearchId = FGuid::NewGuid();
	// 获取协调器服务的基础URL地址
	FString CoordinatorURL  = UTNetStatics::GetCoordinatorURL();

	// 拼接目标 API 地址:<CoordinatorURL>/Sessions
	FString URL = FString::Printf(TEXT("%s/Sessions"), *CoordinatorURL);
	UE_LOG(LogTemp, Warning, TEXT("发送会话创建请求到 URL:%s"), *URL)
	
	// 配置 HTTP 请求:目标地址 + 请求方式 POST
	Request->SetURL(URL);
	Request->SetVerb("POST");

	// 设置 HTTP 请求头,指定请求体为 JSON 格式
	Request->SetHeader(TEXT("Content-Type"), TEXT("application/json"));

	// 构造 JSON 请求体,包含 SessionName 和 SessionSearchId
	TSharedPtr<FJsonObject> JsonObject = MakeShareable(new FJsonObject);
	JsonObject->SetStringField(UTNetStatics::GetSessionNameKey().ToString(), NewSessionName.ToString());
	JsonObject->SetStringField(UTNetStatics::GetSessionSearchIdKey().ToString(), SessionSearchId.ToString());

	// 将 JSON 对象转换为字符串
	FString RequestBody;
	TSharedRef<TJsonWriter<>> Writer = TJsonWriterFactory<>::Create(&RequestBody);
	FJsonSerializer::Serialize(JsonObject.ToSharedRef(), Writer);

	// 设置 HTTP 请求体
	Request->SetContentAsString(RequestBody);
	// 绑定会话创建完成回调
	Request->OnProcessRequestComplete().BindUObject(this, &UMGameInstance::SessionCreationRequestCompleted, SessionSearchId);

	if (!Request->ProcessRequest())
	{
		UE_LOG(LogTemp, Warning, TEXT("会话创建请求失败"))
	}
}

void UMGameInstance::SessionCreationRequestCompleted(FHttpRequestPtr Request, FHttpResponsePtr Response,
	bool bConnectedSuccessfully, FGuid SessionSearchId)
{
	if (!bConnectedSuccessfully)
	{
		UE_LOG(LogTemp, Warning, TEXT("连接协调服务器失败,网络连接未成功!"))
		return;
	}

	UE_LOG(LogTemp, Warning, TEXT("连接协调服务器成功!"))
}
相关推荐
阿巴斯甜13 小时前
Android 报错:Zip file '/Users/lyy/develop/repoAndroidLapp/l-app-android-ble/app/bu
android
Kapaseker14 小时前
实战 Compose 中的 IntrinsicSize
android·kotlin
xq952715 小时前
Andorid Google 登录接入文档
android
黄林晴16 小时前
告别 Modifier 地狱,Compose 样式系统要变天了
android·android jetpack
冬奇Lab1 天前
Android触摸事件分发、手势识别与输入优化实战
android·源码阅读
城东米粉儿1 天前
Android MediaPlayer 笔记
android
Jony_1 天前
Android 启动优化方案
android
阿巴斯甜1 天前
Android studio 报错:Cause: error=86, Bad CPU type in executable
android
张小潇1 天前
AOSP15 Input专题InputReader源码分析
android
_小马快跑_2 天前
Kotlin | 协程调度器选择:何时用CoroutineScope配置,何时用launch指定?
android