UE5多人MOBA+GAS 53、测试专属服务器打包和连接,以及配置EOS

文章目录


改一下代码,运行打开编辑器

将项目改完源码引擎

选择你源码的位置

预防万一,再操作一手这个

Crunch.sln进去

点击运行

命名为CrunchServer.Target,并拖进vs中

修改一下

顺便再修改一下AStormCore

cpp 复制代码
#if WITH_EDITOR
	// 编辑器属性变更回调
	virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override;
#endif
cpp 复制代码
#if WITH_EDITOR
void AStormCore::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent)
{
	Super::PostEditChangeProperty(PropertyChangedEvent);
	// 获取属性名称
	FName PropertyName = PropertyChangedEvent.GetPropertyName();
	
	// 当影响力半径改变时,更新组件
	if (PropertyName == GET_MEMBER_NAME_CHECKED(AStormCore, InfluenceRadius))
	{
		InfluenceRange->SetSphereRadius(InfluenceRadius); // 更新球体半径
		FVector DecalSize = GroundDecalComponent->DecalSize;
		GroundDecalComponent->DecalSize = FVector{DecalSize.X, InfluenceRadius, InfluenceRadius}; // 更新贴花大小
	}
}
#endif

随后再次打开,选择编辑器服务器(这步在干嘛我看不懂思密达,运行完什么也没有)

生成后切换为编辑器,然后运行

打包

打包中点击高级

然后设置这个

然后打包项目

再打包客户端

服务器打包中输入cmd回车

CrunchServer.exe /Game/Maps/Lobby/L_Lobby?listen?port=7780 -log

客户端中

Crunch.exe -log -window

如果打开后全屏了,点一下~输入r.setres 1080x720w,修改一下大小

127.0.0.1是服务器地址,也就是本地机器 7780是刚刚指定的端口号

创建和配置 EOS产品

点我进链接

点击开发者门户

随便输名字创建

创建产品

点击游戏服务,接受并浏览

直滑底部然后点击接受

点击产品设置

把几个许可证都点了

创建客户端

添加新的客户端策略

再添加新的客户端,以及新的策略


添加应用程序


然后就不管了

项目中配置EOS

编辑器中打开插件

Build文件中补充模块

到Build.cs中添加

cpp 复制代码
"OnlineSubsystem","OnlineSubsystemEOS","OnlineSubsystemUtils","Networking","HTTP","Json"

项目设置中设置


最后一行的再此生成点我

再创建一个服务器

把默认修改为游戏客户端

创建会话读取命令行参数

UTNetStatics添加函数

cpp 复制代码
	/**
	 * 检查当前上下文是否为会话服务器
	 * @param WorldContextObject 世界上下文对象
	 * @return 是否为服务器实例
	 */
	static bool IsSessionServer(const UObject* WorldContextObject);
	
	/**
	 * 获取会话名称字符串常量
	 * @return 会话名称字符串
	 */
	static FString GetSessionNameStr();
	/**
	 * 获取会话名称键值
	 * @return 会话名称的FName键
	 */
	static FName GetSessionNameKey();
	
	/**
	 * 获取会话搜索ID字符串
	 * @return 会话搜索ID字符串
	 */
	static FString GetSessionSearchIdStr();
	/**
	 * 获取会话搜索ID键值
	 * @return 会话搜索ID的FName键
	 */
	static FName GetSessionSearchIdKey();
	
	/**
	 * 获取会话端口配置
	 * @return 网络监听端口号
	 */
	static int GetSessionPort();
	/**
	 * 获取端口配置键值
	 * @return 端口号配置的FName键
	 */
	static FName GetPortKey();

	
	
	/**
	 * 获取命令行参数(字符串)
	 * @param ParamName 参数名称
	 * @return 命令行参数值
	 */
	static FString GetCommandlineArgAsString(const FName& ParamName);
	/**
	 * 获取命令行参数(整数)
	 * @param ParamName 参数名称
	 * @return 参数转换为整数的值
	 */
	static int GetCommandlineArgAsInt(const FName& ParamName);
cpp 复制代码
bool UTNetStatics::IsSessionServer(const UObject* WorldContextObject)
{
	// 是否为服务器模式
	return WorldContextObject->GetWorld()->GetNetMode() == ENetMode::NM_DedicatedServer;
}

FString UTNetStatics::GetSessionNameStr()
{
	return GetCommandlineArgAsString(GetSessionNameKey());
}

FName UTNetStatics::GetSessionNameKey()
{
	return FName("SESSION_NAME");
}

FString UTNetStatics::GetSessionSearchIdStr()
{
	return GetCommandlineArgAsString(GetSessionSearchIdKey());
}

FName UTNetStatics::GetSessionSearchIdKey()
{
	return FName("SESSION_SEARCH_ID");
}

int UTNetStatics::GetSessionPort()
{
	return GetCommandlineArgAsInt(GetPortKey());
}

FName UTNetStatics::GetPortKey()
{
	return FName("PORT");
}

FString UTNetStatics::GetCommandlineArgAsString(const FName& ParamName)
{
	FString OutVal = "";
	FString CommandLineArg = FString::Printf(TEXT("%s="), *(ParamName.ToString()));
	FParse::Value(FCommandLine::Get(), *CommandLineArg, OutVal);
	return OutVal;
}

int32 UTNetStatics::GetCommandlineArgAsInt(const FName& ParamName)
{
	int32 OutVal = 0;
	FString CommandLineArg = FString::Printf(TEXT("%s="), *(ParamName.ToString()));
	FParse::Value(FCommandLine::Get(), *CommandLineArg, OutVal);
	return OutVal;
}

UMGameInstance到游戏实例中

cpp 复制代码
	// 游戏实例初始化
	virtual void Init() override;
	/*************************************/
	/*           会话服务器功能			 */
	/*************************************/
private:
	// 创建会话
	void CreateSession();
	// 服务器会话名称
	FString ServerSessionName;
	// 会话服务器端口
	int32 SessionServerPort;
cpp 复制代码
void UMGameInstance::Init()
{
	Super::Init();
	// 编辑器环境下不执行
	if (GetWorld()->IsEditorWorld()) return;

	// 如果是会话服务器,则创建会话
	if (UTNetStatics::IsSessionServer(this))
	{
		CreateSession();
	}
}

void UMGameInstance::CreateSession()
{
	// 获取会话名称、搜索ID、会话端口
	ServerSessionName = UTNetStatics::GetSessionNameStr();
	FString SessionSearchId = UTNetStatics::GetSessionSearchIdStr();
	SessionServerPort = UTNetStatics::GetSessionPort();

	UE_LOG(LogTemp, Warning, TEXT("#### 创建会话 With Name: %s, ID: %s, Port: %d"), *(ServerSessionName), *(SessionSearchId), SessionServerPort)
}

创建启动脚本

创建两个bat

打开你下载的引擎源码位置

到这个位置,复制目录,然后把\改为/

bat 复制代码
"D:/UnrealSource/UnrealEngine/Engine/Binaries/Win64/UnrealEditor.exe" ^
%~dp0../Crunch.uproject ^
-server ^
-log ^
-epicapp="ServerClient" ^
-SESSION_NAME="TestSession" ^
-SESSION_SEARCH_ID="dsfsdfsdfsdfsdfisdfsjdfsdfisdfjsdfjdsf" ^
-PORT=7779

也可以添加一个系统环境变量


然后就可以变成这样launchServer.bat

bat 复制代码
%UNREAL_EDITOR% ^
%~dp0../Crunch.uproject ^
-server ^
-log ^
-epicapp="ServerClient" ^
-SESSION_NAME="TestSession" ^
-SESSION_SEARCH_ID="dsfsdfsdfsdfsdfisdfsjdfsdfisdfjsdfjdsf" ^
-PORT=7779

launchGame.bat

bat 复制代码
%UNREAL_EDITOR% ^
%~dp0../Crunch.uproject ^
-game ^
-log ^
-epicapp="GameClient" ^

实现服务器端会话创建

UTNetStatics类中

cpp 复制代码
#include "OnlineSubsystem.h"
#include "OnlineSessionSettings.h"


	/**
	 * 生成在线会话配置
	 * @param SessionName 会话名称标识符
	 * @param SessionSearchId 会话搜索ID
	 * @param Port 监听端口号
	 * @return 配置好会话设置对象
	 */
	static FOnlineSessionSettings GenerateOnlineSessionSettings(const FName& SessionName, const FString& SessionSearchId, int Port);

	/**
	 * 获取在线会话接口指针
	 * @return 会话接口智能指针
	 */
	static IOnlineSessionPtr GetSessionPtr();

	/**
	 * 获取在线身份验证接口指针
	 * @return 身份接口智能指针
	 */
	static IOnlineIdentityPtr GetIdentityPtr();
cpp 复制代码
FOnlineSessionSettings UTNetStatics::GenerateOnlineSessionSettings(const FName& SessionName,
	const FString& SessionSearchId, int32 Port)
{
	// 创建一个会话设置对象
	FOnlineSessionSettings OnlineSessionSettings{};
	// 是否是局域网对战(false 表示在线匹配)
	OnlineSessionSettings.bIsLANMatch = false;
	// 可加入的玩家数,这里取「每队人数 * 2」
	OnlineSessionSettings.NumPublicConnections = GetPlayerCountPerTeam() * 2;
	// 是否向在线服务公开这个会话(可被搜索到)
	OnlineSessionSettings.bShouldAdvertise = true;
	// 是否使用在线状态(Presence,如好友在线显示)功能,(这是服务器没有登录用户)
	OnlineSessionSettings.bUsesPresence = false;
	// 是否允许通过在线状态直接加入
	OnlineSessionSettings.bAllowJoinViaPresence = false;
	// 是否仅允许好友通过在线状态加入
	OnlineSessionSettings.bAllowJoinViaPresenceFriendsOnly = false;
	// 是否允许玩家通过邀请加入
	OnlineSessionSettings.bAllowInvites = true;
	// 是否允许游戏进行中途加入(false = 只能在开始前加入)
	OnlineSessionSettings.bAllowJoinInProgress = false;
	// 是否在可用时使用 Lobbies 功能
	OnlineSessionSettings.bUseLobbiesIfAvailable = false;
	// 是否在 Lobbies 中启用语音聊天
	OnlineSessionSettings.bUseLobbiesVoiceChatIfAvailable = false;
	// 是否启用统计系统(用于战绩、匹配等统计)
	OnlineSessionSettings.bUsesStats = true;

	// 设置自定义键值对(附加会话元数据,用于搜索和识别)
	OnlineSessionSettings.Set(GetSessionNameKey(), SessionName.ToString(), EOnlineDataAdvertisementType::ViaOnlineServiceAndPing);
	OnlineSessionSettings.Set(GetSessionSearchIdKey(), SessionSearchId, EOnlineDataAdvertisementType::ViaOnlineServiceAndPing);
	OnlineSessionSettings.Set(GetPortKey(), Port, EOnlineDataAdvertisementType::ViaOnlineServiceAndPing);

	return OnlineSessionSettings;
}

IOnlineSessionPtr UTNetStatics::GetSessionPtr()
{
	IOnlineSubsystem* OnlineSubSystem = IOnlineSubsystem::Get();
	if (OnlineSubSystem)
	{
		// 返回会话接口(IOnlineSession)
		return OnlineSubSystem->GetSessionInterface();
	}
	return nullptr;
}

IOnlineIdentityPtr UTNetStatics::GetIdentityPtr()
{
	IOnlineSubsystem* OnlineSubSystem = IOnlineSubsystem::Get();
	if (OnlineSubSystem)
	{
		// 返回身份接口(IOnlineIdentity)
		return OnlineSubSystem->GetIdentityInterface();
	}
	return nullptr;
}

会话创建成功加载入关卡,失败则中止
UMGameInstance

cpp 复制代码
	// 当会话创建完成时触发
	void OnSessionCreated(FName SessionName, bool bWasSuccessful);
	// 当会话结束完成时回调
	void EndSessionCompleted(FName SessionName, bool bWasSuccessful);

	// 终止会话服务器
	void TerminateSessionServer();
	
	// 等待玩家加入的超时计时器句柄
	FTimerHandle WaitPlayerJoinTimeoutHandle;

	// 等待玩家加入的超时时间(默认60秒)
	UPROPERTY(EditDefaultsOnly, Category = "Session")
	float WaitPlayerJoinTimeOutDuration = 60.f;
	// 玩家超时未加入时触发
	void WaitPlayerJoinTimeoutReached();
cpp 复制代码
void UMGameInstance::CreateSession()
{
	IOnlineSessionPtr SessionPtr = UTNetStatics::GetSessionPtr();
	if (SessionPtr)
	{
		// 获取会话名称、搜索ID、会话端口
		ServerSessionName = UTNetStatics::GetSessionNameStr();
		FString SessionSearchId = UTNetStatics::GetSessionSearchIdStr();
		SessionServerPort = UTNetStatics::GetSessionPort();

		UE_LOG(LogTemp, Warning, TEXT("#### 创建会话 With Name: %s, ID: %s, Port: %d"), *(ServerSessionName), *(SessionSearchId), SessionServerPort)

		// 生成会话设置
		FOnlineSessionSettings OnlineSessionSettings = UTNetStatics::GenerateOnlineSessionSettings(FName(ServerSessionName), SessionSearchId, SessionServerPort);
		// 先移除旧的委托,避免重复绑定
		SessionPtr->OnCreateSessionCompleteDelegates.RemoveAll(this);

		// 绑定本实例的回调函数(异步完成时调用)
		SessionPtr->OnCreateSessionCompleteDelegates.AddUObject(this, &UMGameInstance::OnSessionCreated);
		// 发起会话创建(参数:本地用户索引=0,会话名,会话设置)
		if (!SessionPtr->CreateSession(0, FName(ServerSessionName), OnlineSessionSettings))
		{
			UE_LOG(LogTemp, Warning, TEXT("错误:会话创建立即失败!!!!"))
			SessionPtr->OnCreateSessionCompleteDelegates.RemoveAll(this);
			TerminateSessionServer();
		}
	}
	else
	{
		// 无法获取 Session 接口(通常是 OnlineSubsystem 没初始化)
		UE_LOG(LogTemp, Warning, TEXT("错误:无法获取会话接口,正在终止服务"));
		TerminateSessionServer();
	}
}

void UMGameInstance::OnSessionCreated(FName SessionName, bool bWasSuccessful)
{
	if (bWasSuccessful)
	{
		UE_LOG(LogTemp, Warning, TEXT("------------- 会话创建成功! -------------"));

		// 设置一个定时器:如果在指定时间内没有玩家加入,则自动关闭服务器
		GetWorld()->GetTimerManager().SetTimer(
			WaitPlayerJoinTimeoutHandle, 
			this, 
			&UMGameInstance::WaitPlayerJoinTimeoutReached, 
			WaitPlayerJoinTimeOutDuration
		);

		// 会话创建成功后,加载大厅关卡并开启监听(等待客户端连接)
		LoadLevelAndListen(LobbyLevel);
	}
	else
	{
		UE_LOG(LogTemp, Warning, TEXT("------------ 会话创建失败 ------------"));
		// 如果会话创建失败,立刻关闭服务器
		TerminateSessionServer();
	}

	// 无论结果如何,解绑 OnCreateSessionComplete 委托,避免重复绑定
	if (IOnlineSessionPtr SessionPtr = UTNetStatics::GetSessionPtr())
	{
		SessionPtr->OnCreateSessionCompleteDelegates.RemoveAll(this);
	}
}

void UMGameInstance::EndSessionCompleted(FName SessionName, bool bWasSuccessful)
{
	// 直接请求退出游戏进程
	FGenericPlatformMisc::RequestExit(false);
}

void UMGameInstance::TerminateSessionServer()
{
	if (IOnlineSessionPtr SessionPtr = UTNetStatics::GetSessionPtr())
	{
		// 确保委托不重复绑定
		SessionPtr->OnEndSessionCompleteDelegates.RemoveAll(this);
		SessionPtr->OnEndSessionCompleteDelegates.AddUObject(this, &UMGameInstance::EndSessionCompleted);

		// 尝试结束会话,如果失败则直接退出
		if (!SessionPtr->EndSession(FName{ ServerSessionName }))
		{
			FGenericPlatformMisc::RequestExit(false);
		}
	}
	else
	{
		// 如果拿不到 SessionPtr,说明 OnlineSubsystem 不可用,直接退出
		FGenericPlatformMisc::RequestExit(false);
	}
}

void UMGameInstance::WaitPlayerJoinTimeoutReached()
{
	UE_LOG(LogTemp, Warning, TEXT("会话服务器在等待%f秒后后无玩家加入,现已关闭"), WaitPlayerJoinTimeOutDuration);
	// 超时无人加入,关闭服务器
	TerminateSessionServer();
}

void UMGameInstance::LoadLevelAndListen(TSoftObjectPtr<UWorld> Level)
{
	// 从软引用的 UWorld 获取包路径(比如 /Game/Maps/MyMap.MyMap)
	const FName LevelURL = FName(*FPackageName::ObjectPathToPackageName(Level.ToString()));
	if (LevelURL != "")
	{
		// 构造带有监听和端口参数的 URL
		FString TravelStr = FString::Printf(TEXT("%s?listen?port=%d"), *LevelURL.ToString(), SessionServerPort);
		UE_LOG(LogTemp, Warning, TEXT("服务器地图切换至 %s"), *(TravelStr));
		// 开启 ServerTravel(切换关卡并开启监听)
		GetWorld()->ServerTravel(TravelStr);
	}
}

实现游戏会话类并对玩家加入和离开做出反应

cpp 复制代码
#pragma once

#include "CoreMinimal.h"
#include "GameFramework/GameSession.h"
#include "TGameSession.generated.h"

/**
 * 自定义的游戏会话类,继承自 AGameSession
 * 主要用于管理玩家的注册、注销,以及自动登录等逻辑。
 */
UCLASS()
class ATGameSession : public AGameSession
{
	GENERATED_BODY()
	
public:	
	// 处理自动登录逻辑,返回 true 表示成功
	virtual bool ProcessAutoLogin() override;
	
	// 当新玩家加入时调用
	// 负责将玩家注册进 Session,并通知 GameInstance
	virtual void RegisterPlayer(APlayerController* NewPlayer, const FUniqueNetIdRepl& UniqueId, bool bWasFromInvite) override;

	// 当玩家离开时调用
	// 负责将玩家从 Session 注销,并通知 GameInstance
	virtual void UnregisterPlayer(FName FromSessionName, const FUniqueNetIdRepl& UniqueId) override;
};
cpp 复制代码
#include "Network/TGameSession.h"

#include "Framework/MGameInstance.h"


bool ATGameSession::ProcessAutoLogin()
{
	// return Super::ProcessAutoLogin();
	// 服务器不需要登录直接返回true
	return true;
}

void ATGameSession::RegisterPlayer(APlayerController* NewPlayer, const FUniqueNetIdRepl& UniqueId, bool bWasFromInvite)
{
	Super::RegisterPlayer(NewPlayer, UniqueId, bWasFromInvite);
	// 获取游戏实例
	if (UMGameInstance* GameInstance = GetGameInstance<UMGameInstance>())
	{
		// 玩家加入(传入唯一ID)
		GameInstance->PlayerJoined(UniqueId);
	}
}

void ATGameSession::UnregisterPlayer(FName FromSessionName, const FUniqueNetIdRepl& UniqueId)
{
	Super::UnregisterPlayer(FromSessionName, UniqueId);

	if (UMGameInstance* GameInstance = GetGameInstance<UMGameInstance>())
	{
		// 玩家退出(传入唯一ID)
		GameInstance->PlayerLeft(UniqueId);
	}
}

游戏实例中添加玩家的加入以及离开函数,玩家记录集合

cpp 复制代码
public:
	// 玩家加入会话(服务器端调用)
	void PlayerJoined(const FUniqueNetIdRepl& UniqueId);
	
	// 玩家离开会话(服务器端调用)
	void PlayerLeft(const FUniqueNetIdRepl& UniqueId);
private:
	// 玩家记录集合
	TSet<FUniqueNetIdRepl> PlayerRecord;
cpp 复制代码
void UMGameInstance::PlayerJoined(const FUniqueNetIdRepl& UniqueId)
{
	// 清除等待超时定时器
	if (WaitPlayerJoinTimeoutHandle.IsValid())
	{
		GetWorld()->GetTimerManager().ClearTimer(WaitPlayerJoinTimeoutHandle);
	}
	// 记录玩家
	PlayerRecord.Add(UniqueId);
}

void UMGameInstance::PlayerLeft(const FUniqueNetIdRepl& UniqueId)
{
	// 移除玩家记录
	PlayerRecord.Remove(UniqueId);
	
	// 如果所有玩家都离开,终止会话服务器
	if (PlayerRecord.Num() == 0)
	{
		UE_LOG(LogTemp, Warning, TEXT("所有玩家都离开了会话, 结束服务器"))
		TerminateSessionServer();
	}
}

给游戏模式基类添加构造函数,构造函数中添加默认的游戏会话类

cpp 复制代码
ACGameMode::ACGameMode()
{
	// 创建游戏会话类
	GameSessionClass = ATGameSession::StaticClass();
}

大厅游戏模式也是如此(父类设置了,子类是不是应该不用管了呢)

cpp 复制代码
ALobbyGameMode::ALobbyGameMode()
{
	bUseSeamlessTravel = true;
	GameSessionClass = ATGameSession::StaticClass();
}

确实不用管


启用和测试 EOS

点击进入EOS插件文档

需要在DefaultEngine.ini中配置一下文件

ini 复制代码
[OnlineSubsystemEOS]
bEnabled=true

[OnlineSubsystem]
DefaultPlatformService=EOS

[/Script/Engine.Engine]
!NetDriverDefinitions=ClearArray
+NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="SocketSubsystemEOS.NetDriverEOSBase",DriverClassNameFallback="OnlineSubsystemUtils.IpNetDriver")
+NetDriverDefinitions=(DefName="DemoNetDriver",DriverClassName="/Script/Engine.DemoNetDriver",DriverClassNameFallback="/Script/Engine.DemoNetDriver")

[/Script/Engine.GameEngine]
!NetDriverDefinitions=ClearArray
+NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="SocketSubsystemEOS.NetDriverEOSBase",DriverClassNameFallback="OnlineSubsystemUtils.IpNetDriver")
+NetDriverDefinitions=(DefName="DemoNetDriver",DriverClassName="/Script/Engine.DemoNetDriver",DriverClassNameFallback="/Script/Engine.DemoNetDriver")

随后双击一下脚本

通过属性可以找到会话,但现在还是不能让客户端连接到服务器