文章目录
- 改一下代码,运行打开编辑器
- 打包
- [创建和配置 EOS产品](#创建和配置 EOS产品)
- 项目中配置EOS
- 实现游戏会话类并对玩家加入和离开做出反应
- [启用和测试 EOS](#启用和测试 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
需要在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")
随后双击一下脚本
通过属性可以找到会话,但现在还是不能让客户端连接到服务器