UE5多人MOBA+GAS 53、测试专属服务器打包和连接,以及配置EOS
文章目录
- 改一下代码,运行打开编辑器
- 打包
- 创建和配置 EOS产品
- 项目中配置EOS
- 编辑器中打开插件
- Build文件中补充模块
- 项目设置中设置
- 创建会话读取命令行参数
- 创建启动脚本
- 实现服务器端会话创建
- 实现游戏会话类并对玩家加入和离开做出反应
- 启用和测试 EOS
改一下代码,运行打开编辑器
将项目改完源码引擎
选择你源码的位置
预防万一,再操作一手这个
点Crunch.sln
进去
点击运行
命名为CrunchServer.Target
,并拖进vs中
修改一下
顺便再修改一下AStormCore
#if WITH_EDITOR// 编辑器属性变更回调virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override;
#endif
#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中添加
"OnlineSubsystem","OnlineSubsystemEOS","OnlineSubsystemUtils","Networking","HTTP","Json"
项目设置中设置
最后一行的再此生成点我
再创建一个服务器
把默认修改为游戏客户端
创建会话读取命令行参数
UTNetStatics
添加函数
/*** 检查当前上下文是否为会话服务器* @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);
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
到游戏实例中
// 游戏实例初始化virtual void Init() override;/*************************************//* 会话服务器功能 *//*************************************/
private:// 创建会话void CreateSession();// 服务器会话名称FString ServerSessionName;// 会话服务器端口int32 SessionServerPort;
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
打开你下载的引擎源码位置
到这个位置,复制目录,然后把\
改为/
"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
%UNREAL_EDITOR% ^
%~dp0../Crunch.uproject ^
-server ^
-log ^
-epicapp="ServerClient" ^
-SESSION_NAME="TestSession" ^
-SESSION_SEARCH_ID="dsfsdfsdfsdfsdfisdfsjdfsdfisdfjsdfjdsf" ^
-PORT=7779
launchGame.bat
%UNREAL_EDITOR% ^
%~dp0../Crunch.uproject ^
-game ^
-log ^
-epicapp="GameClient" ^
实现服务器端会话创建
UTNetStatics
类中
#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();
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
// 当会话创建完成时触发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();
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 != ""){// 构造带有监听和端口参数的 URLFString TravelStr = FString::Printf(TEXT("%s?listen?port=%d"), *LevelURL.ToString(), SessionServerPort);UE_LOG(LogTemp, Warning, TEXT("服务器地图切换至 %s"), *(TravelStr));// 开启 ServerTravel(切换关卡并开启监听)GetWorld()->ServerTravel(TravelStr);}
}
实现游戏会话类并对玩家加入和离开做出反应
#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,并通知 GameInstancevirtual void RegisterPlayer(APlayerController* NewPlayer, const FUniqueNetIdRepl& UniqueId, bool bWasFromInvite) override;// 当玩家离开时调用// 负责将玩家从 Session 注销,并通知 GameInstancevirtual void UnregisterPlayer(FName FromSessionName, const FUniqueNetIdRepl& UniqueId) override;
};
#include "Network/TGameSession.h"#include "Framework/MGameInstance.h"bool ATGameSession::ProcessAutoLogin()
{// return Super::ProcessAutoLogin();// 服务器不需要登录直接返回truereturn 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);}
}
游戏实例中添加玩家的加入以及离开函数,玩家记录集合
public:// 玩家加入会话(服务器端调用)void PlayerJoined(const FUniqueNetIdRepl& UniqueId);// 玩家离开会话(服务器端调用)void PlayerLeft(const FUniqueNetIdRepl& UniqueId);
private:// 玩家记录集合TSet<FUniqueNetIdRepl> PlayerRecord;
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();}
}
给游戏模式基类添加构造函数,构造函数中添加默认的游戏会话类
ACGameMode::ACGameMode()
{// 创建游戏会话类GameSessionClass = ATGameSession::StaticClass();
}
大厅游戏模式也是如此(父类设置了,子类是不是应该不用管了呢)
ALobbyGameMode::ALobbyGameMode()
{bUseSeamlessTravel = true;GameSessionClass = ATGameSession::StaticClass();
}
确实不用管
启用和测试 EOS
点击进入EOS插件文档
需要在DefaultEngine.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")
随后双击一下脚本
通过属性可以找到会话,但现在还是不能让客户端连接到服务器