Gameplay重要类及重要功能使用方法(四)
DeveloperSetting
-
DeveloperSetting是在虚幻引擎中是一个基类,主要用于创建和管理开发者设置相关的类。这类设置允许开发者自定义或调整项目中的各种配置选项,而无需直接修改代码或构建设置。这些设置可以是关于渲染、物理、输入、网络等多方面的,为游戏或应用的开发过程提供了高度的灵活性和可配置性。继承自
UObject
类 -
创建一个
DeveloperSetting
类,这种一般在企业级开发用的比较多,用于拓展 -
写一个功能,用来理解,使用这个拓展类,让游戏设置选择是否保存游戏血量数据并写入到配置文件中
-
控制是否保存游戏数据的逻辑,默认为真
-
是否需要保存游戏数据逻辑
-
是否读取存档数据逻辑
-
运行结果,此时项目设置里面会出现一个GPPSetting的设置,默认保存游戏为真,此时就会默认保存游戏,读取游戏存档,如果去掉勾就不会默认保存与读取了
-
通过引擎对配置文件做出实时改变只需要添加一个宏标记类说明符
defaultconfig
,能实时在配置文件ini
中做出改变
-
运行结果
GameSession简介与重要方法的执行流程
GameSession简单介绍
- 开发一个多人游戏中,客户端需要一个
Session
机制链接到服务器,在这个过程中以便服务器验证客户端的合法条件 GameSession
可以看做是GamePlay
框架与OnlineSession
的接口- 使用
GameSession
过程当中主要依赖的方法就是虚幻引擎提供的接口,虚幻引擎中提供不同网络平台的接口
重要方法的执行流程
- 创建一个GameSession类,查看其中重要方法的执行顺序
- 重写这几个方法查看执行顺序
- 注册
GameSession
到GameMode
里面
- 使用服务器批处理脚本查看结果可以发现:
RegisterServer
早于BeginPlay
也早于GameMode
中的BenginPlay
,然后这里面没有执行RegisterPlayer
只注册了自己的服务器到游戏会话当中
- 然后在使用客户端批处理脚本查看结果:先执行
RegisterPlayer
了,服务器那边只执行注册服务器的,客户端这边执行注册角色的
- 实验结果说明:网络链接游戏会话创建就得创建两个,一个给客户端创建一个游戏会话,一个给服务器创建一个游戏会话,把它们共同注册到会话当中,这样客户端才能找到服务器,服务器也可以接受客户端链接。游戏会话就相当于是中间枢纽
为游戏大厅创建的框架类
- 新建一个空白关卡做为游戏大厅
- 然后创建会话大厅的
GameMode、Character、GameSession、HUD、PlayerController
类,注册到GameMode
里面
创建会话逻辑
- 首先使用
OnlineSubsystem
是作为插件使用的,所以得在编译模块加入两个模块
- 在服务器
GameSession
类中添加两个函数与一个委托
- 编写逻辑
bool AGPPGameSession::CreateSession()
{
IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld());
if (OnlineSub)
{
//拿接口
IOnlineSessionPtr SessionPtr = OnlineSub->GetSessionInterface();
if (SessionPtr.IsValid())
{
//进行一些必要设置
FOnlineSessionSettings Settings;
Settings.NumPublicConnections = 0;
Settings.bShouldAdvertise = true;
Settings.bAllowJoinInProgress = true;
Settings.bIsDedicated = true;
Settings.bIsLANMatch = true;
Settings.bUsesPresence = false;
Settings.bAllowInvites = true;
Settings.bAllowJoinViaPresence = true;
//设置游戏模式
Settings.Set(SETTING_GAMEMODE, FString("StandMatch"), EOnlineDataAdvertisementType::ViaOnlineService);
//设置游戏地图
Settings.Set(SETTING_MAPNAME, GetWorld()->GetMapName(), EOnlineDataAdvertisementType::ViaOnlineService);
//绑定委托句柄,回调函数
OnCreateSessionCompleteDelegateHandle = SessionPtr->AddOnCreateSessionCompleteDelegate_Handle(
FOnCreateSessionCompleteDelegate::CreateUObject(this, &AGPPGameSession::OnCreateSessionComplete));
//创建GameSession
bool Result = SessionPtr->CreateSession(0, FName("NAME_GameSession2"), Settings);
return Result;
}
}
return false;
}
void AGPPGameSession::OnCreateSessionComplete(FName IsSessionName, bool bWasSuccessful)
{
IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld());
if (OnlineSub)
{
//拿接口
IOnlineSessionPtr SessionPtr = OnlineSub->GetSessionInterface();
if (SessionPtr)
{
//拿到GameSession状态
EOnlineSessionState::Type GameSessionState = SessionPtr->GetSessionState(IsSessionName);
//清空句柄
SessionPtr->ClearOnCreateSessionCompleteDelegate_Handle(OnCreateSessionCompleteDelegateHandle);
}
}
}
-
这段代码解释
-
FOnlineSessionSettings源码
-
添加头文件
#include "Online.h"
#include "OnlineSubsystem.h"
#include "OnlineSubsystemUtils.h"
#include "OnlineSessionSettings.h"
#include "Online/OnlineSessionNames.h"
#include "OnlineSubsystemTypes.h"
#include "Interfaces/OnlineSessionDelegates.h"
- 写完逻辑后,如果报错编译器反应不过来,可以到项目中重新编译建立项目
- 最后在
RegisterServer
函数中注册服务器
void AGPPGameSession::RegisterServer()
{
Super::RegisterServer();
UE_LOG(MyLog_GPPGameSession, Log, TEXT("----RegisterServer---- NetMode:%s"), *UBFL_LogManager::GetNetModeStr(this));
//创建服务器
CreateSession();
}
客户端_FindSession和JoinSession逻辑
- 实现查找服务器与加入服务器,将之前直接在控制器中直接加入的服务器的代码逻辑注释掉
HallPlayerController类中
-
我们在
HallPlayerController
函数中创建三个函数,一个用来加入房间,查找房间与测试函数
-
HallPlayerController::JoinSession
的函数逻辑,GameSession->JoinSession(ID, SessionIndexInSerarchResults)
这个函数逻辑还没有实现,放到后面去实现。先不管
-
AHallPlayerController::FindSession
的函数逻辑,GameSeesion->FindSession()
这个函数逻辑还没有实现,放到后面去实现。先不管
-
测试函数,运行结果会自动进入第一个房间
HallPlayerController.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "HallPlayerController.generated.h"
/**
*
*/
UCLASS()
class GAMEPLAYCODEPARSING_API AHallPlayerController : public APlayerController
{
GENERATED_BODY()
public:
//加入房间
UFUNCTION(BlueprintCallable, Category = "PlayerNetSeesion")
bool JoinSession(int32 SessionIndexInSerarchResults);
//查找房间
UFUNCTION(BlueprintCallable,Category = "PlayerNetSession")
void FindSession();
//测试函数
UFUNCTION(BlueprintCallable,Category="PlayerNetSession")
void FindSessionComplete();
};
HallPlayerController.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "HallPlayerController.h"
#include "HallGameSession.h"
#include "GameFramework/GameModeBase.h"
bool AHallPlayerController::JoinSession(int32 SessionIndexInSerarchResults)
{
if (GetNetMode() == NM_Standalone)//是否是单机状态
{
if (UWorld* const World = GetWorld())//拿取当前世界
{
if (AGameModeBase* const GM = World->GetAuthGameMode())//拿取当前世界的GameMode
{
if (AHallGameSession* GameSession = Cast<AHallGameSession>(GM->GameSession))//有了GameMode就可以拿到Session了
{
if (ULocalPlayer* LocalPlayer = GetLocalPlayer())//有了Session就可以拿到本地的玩家了
{
//通过本地玩家拿取玩家在网络中的ID
const TSharedPtr<const FUniqueNetId> ID = LocalPlayer->GetPreferredUniqueNetId().GetUniqueNetId();
//ID与要加入的房间号传入到我们在GameSession中自定义函数中
bool Result = GameSession->JoinSession(ID, SessionIndexInSerarchResults);
return Result;
}
}
}
}
}
return false;
}
void AHallPlayerController::FindSession()
{
if (GetNetMode() == NM_Standalone)//是否是单机状态
{
if (UWorld* const World = GetWorld())//拿取当前世界
{
if (AGameModeBase* const GM = World->GetAuthGameMode())//拿到Session了
{
if (AHallGameSession* GameSeesion = Cast<AHallGameSession>(GM->GameSession))//拿到本地的玩家了
{
GameSeesion->FindSession();//调用我们在GameSession中的查找自定义房间函数
}
}
}
}
}
void AHallPlayerController::FindSessionComplete()
{
JoinSession(0);
}
HallGameSession类中
- 然后在
HallGameSession
类中去实现查找房间发现房间的逻辑 - 新建几个函数与委托
AHallGameSession::FindSession
函数逻辑
AHallGameSession::OnFindSessionComplete
函数逻辑
AHallGameSession::JoinSession
函数逻辑
AHallGameSession::OnJoinSessionComplete
函数逻辑
BeginPlay
中的逻辑,在BeginPlay中去查找房间,这样就会自动化运行上面的所有逻辑了
HallGameSession.h
// Fill out your copyright notice in the Description page of Project Settings.
#pragma once
#include "CoreMinimal.h"
#include "GameFramework/GameSession.h"
#include "OnlineSessionSettings.h"
#include "Interfaces/OnlineSessionInterface.h"
#include "HallGameSession.generated.h"
/**
*
*/
UCLASS()
class GAMEPLAYCODEPARSING_API AHallGameSession : public AGameSession
{
GENERATED_BODY()
public:
virtual void BeginPlay() override;
//查找房间
void FindSession();
//加入房间
bool JoinSession(TSharedPtr<const FUniqueNetId> UserId, int32 SessionIndexInSerarchResults);
//回调函数
void OnJoinSessionComplete(FName InSessionName, EOnJoinSessionCompleteResult::Type Result);
void OnFindSessionComplete(bool bWasSuccessful);
private:
//委托
FDelegateHandle OnFindSessionCompleteDelegateHandle;
FDelegateHandle OnJoinSessionCompleteDelegateHandle;
//缓存查找结果
TSharedPtr<FOnlineSessionSearch> SearchSetting;
};
HallGameSession.cpp
// Fill out your copyright notice in the Description page of Project Settings.
#include "HallGameSession.h"
#include "OnlineSubsystem.h"
#include "Online.h"
#include "OnlineSubsystemUtils.h"
#include "OnlineSessionSettings.h"
#include "Online/OnlineSessionNames.h"
#include "OnlineSubsystemTypes.h"
#include "Interfaces/OnlineSessionDelegates.h"
#include "../GPProjectGameInstance.h"
#include "HallPlayerController.h"
#include "../Utils/BFL_LogManager.h"
DEFINE_LOG_CATEGORY_STATIC(MyLog_HallGameSession, Log, All)
void AHallGameSession::BeginPlay()
{
Super::BeginPlay();
if (GetNetMode() == NM_Standalone)
{
FindSession();
}
}
void AHallGameSession::FindSession()
{
IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld());
if (OnlineSub)
{
//拿接口
IOnlineSessionPtr SessionPtr = OnlineSub->GetSessionInterface();
if (SessionPtr.IsValid())
{
SearchSetting = SearchSetting = MakeShareable(new FOnlineSessionSearch());
SearchSetting->TimeoutInSeconds = 30.;//等待搜索结果的时间
SearchSetting->bIsLanQuery = true;//查询用于局域网匹配
//回调 在完成对在线会话的搜索时触发
OnFindSessionCompleteDelegateHandle = SessionPtr->AddOnFindSessionsCompleteDelegate_Handle(
FOnFindSessionsCompleteDelegate::CreateUObject(this, &AHallGameSession::OnFindSessionComplete));
//拿取GameInstance
if (UGPProjectGameInstance* GI = Cast<UGPProjectGameInstance>(GetWorld()->GetGameInstance()))
{
if (ULocalPlayer* LocalPlayer = GI->GetFirstGamePlayer())//拿取本地玩家
{
//通过本地玩家拿取在服务器中的ID
const TSharedPtr<const FUniqueNetId> UserID = LocalPlayer->GetPreferredUniqueNetId().GetUniqueNetId();
TSharedRef<FOnlineSessionSearch> SearchSettingRef = SearchSetting.ToSharedRef();
//查找房间
SessionPtr->FindSessions(*UserID, SearchSettingRef);
}
}
}
}
}
void AHallGameSession::OnFindSessionComplete(bool bWasSuccessful)
{
IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld());
if (OnlineSub)
{
//拿接口
IOnlineSessionPtr SessionPtr = OnlineSub->GetSessionInterface();
if (SessionPtr.IsValid())
{
//清空句柄
SessionPtr->ClearOnFindSessionsCompleteDelegate_Handle(OnFindSessionCompleteDelegateHandle);
}
}
if (UGPProjectGameInstance* GI = Cast<UGPProjectGameInstance>(GetWorld()->GetGameInstance()))
{
if (AHallPlayerController* PC = Cast<AHallPlayerController>(GI->GetFirstLocalPlayerController()))
{
UE_LOG(MyLog_HallGameSession, Log, TEXT("OnFindSessionComplete-------bWasSuccessful:%d"), bWasSuccessful);
//调用测试函数
PC->FindSessionComplete();
}
}
}
bool AHallGameSession::JoinSession(TSharedPtr<const FUniqueNetId> UserId, int32 SessionIndexInSerarchResults)
{
IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld());
if (OnlineSub)
{ //获取接口
IOnlineSessionPtr SessionPtr = OnlineSub->GetSessionInterface();
if (SessionPtr.IsValid())
{
//回调 在联机会话的加入过程完成时触发的委托
OnJoinSessionCompleteDelegateHandle = SessionPtr->AddOnJoinSessionCompleteDelegate_Handle(
FOnJoinSessionCompleteDelegate::CreateUObject(this, &AHallGameSession::OnJoinSessionComplete));
//发现的房间
int32 FindNum = SearchSetting->SearchResults.Num();
if (SessionIndexInSerarchResults > -1 && SessionIndexInSerarchResults < FindNum)
{
UE_LOG(MyLog_HallGameSession, Log, TEXT("----JoinSession---- SearchResults:%d"), SearchSetting->SearchResults.Num());
//将转房间ID数据换成FOnlineSessionSearchResult&数据
const FOnlineSessionSearchResult& SearchResult = SearchSetting->SearchResults[SessionIndexInSerarchResults];
bool Result = SessionPtr->JoinSession(*UserId, FName("NAME_GameSession2"), SearchResult);//加入房间
return Result;
}
}
}
return false;
}
void AHallGameSession::OnJoinSessionComplete(FName InSessionName, EOnJoinSessionCompleteResult::Type Result)
{
IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld());
if (OnlineSub)
{
//获取接口
IOnlineSessionPtr SessionPtr = OnlineSub->GetSessionInterface();
if (SessionPtr.IsValid())
{
//清空句柄
SessionPtr->ClearOnJoinSessionCompleteDelegate_Handle(OnJoinSessionCompleteDelegateHandle);
if (Result == EOnJoinSessionCompleteResult::Success)
{
if (AHallPlayerController* PC = Cast<AHallPlayerController>(GetWorld()->GetFirstPlayerController()))
{
//充当之前在GamePlayPlayerController直连服务器中的FURL map 的作用
FString ConnectString;
//GetResolvedConnectString:返回用于加入匹配的特定于平台的连接信息。从连接完成的委托调用此函数
if (SessionPtr->GetResolvedConnectString(FName("NAME_GameSession2"), ConnectString))
{
FString PlayerName = "Null";
//获取命令行中的角色名字
if (FParse::Value(FCommandLine::Get(), TEXT("PlayerName="), PlayerName)) {}
//旅行到不同的地图或者地址
PC->ClientTravel(ConnectString + "?PN=" + PlayerName, TRAVEL_Absolute);
UE_LOG(MyLog_HallGameSession, Log, TEXT("----OnJoinSessionComplete---- NetMode:%s"), *UBFL_LogManager::GetNetModeStr(this));
UE_LOG(MyLog_HallGameSession, Log, TEXT("----OnJoinSessionComplete---- ConnectString:%s"), *ConnectString);
}
}
}
}
}
}
运行结果
- 将客户端中的批处理脚本进行修改,进入地图改成我们会话大厅的地图
- 最后在去
GPPGameSession
类中,将我们之前写的这个公共可连接数量改为1,之前写的0。
- 打开服务器与客户端脚本,运行结果
客户端本地获取房间信息逻辑
- 首先在
HallGameSession
类中创建一个结构体用来保存查找到的房间结果里面的数据
- 删除
HallPlayerController
类中的测试函数,改为在蓝图中实现的函数,传入我们需要的房间结果的数据信息
- 在
HallGameSession
的OnFindSessionComplete
函数中提取房间信息
AHallGameSession::OnFindSessionComplete(bool bWasSuccessful)
函数
void AHallGameSession::OnFindSessionComplete(bool bWasSuccessful)
{
IOnlineSubsystem* OnlineSub = Online::GetSubsystem(GetWorld());
if (OnlineSub)
{
//拿接口
IOnlineSessionPtr SessionPtr = OnlineSub->GetSessionInterface();
if (SessionPtr.IsValid())
{
//清空句柄
SessionPtr->ClearOnFindSessionsCompleteDelegate_Handle(OnFindSessionCompleteDelegateHandle);
}
}
if (UGPProjectGameInstance* GI = Cast<UGPProjectGameInstance>(GetWorld()->GetGameInstance()))
{
if (AHallPlayerController* PC = Cast<AHallPlayerController>(GI->GetFirstLocalPlayerController()))
{
TArray<FUISessionInfo> UISessionList;//用来保存结果信息的自定义结构体数组
TArray<FOnlineSessionSearchResult> ResultList = SearchSetting->SearchResults;//拿取信息
for (int32 SearchIdx = 0; SearchIdx < ResultList.Num(); SearchIdx++)
{
//查找的房间结果数据
const FOnlineSessionSearchResult& SearchResult = ResultList[SearchIdx];
FUISessionInfo UISession;
UISession.SearchResultIndex = SearchIdx;//房间序号
UISession.SessionName = SearchResult.Session.OwningUserName; //会话的名字
//玩家数量 = 公共的链接 + 私有的链接 - 已经公开的链接 - 已经公开的私有链接
UISession.PlayerNum = SearchResult.Session.SessionSettings.NumPublicConnections +
SearchResult.Session.SessionSettings.NumPrivateConnections -
SearchResult.Session.NumOpenPublicConnections - SearchResult.Session.NumOpenPrivateConnections;
//房间最大数量 = 公共的链接 + 私有的链接
UISession.PlayerMaxNum = SearchResult.Session.SessionSettings.NumPublicConnections +
SearchResult.Session.SessionSettings.NumPrivateConnections;
//Ping
UISession.Ping = SearchResult.PingInMs;
//游戏模式
SearchResult.Session.SessionSettings.Get(SETTING_GAMEMODE, UISession.GameType);
//游戏地图
SearchResult.Session.SessionSettings.Get(SETTING_MAPNAME, UISession.MapName);
//赋值到我们的自定义结构体数组中
UISessionList.Emplace(UISession);
}
UE_LOG(MyLog_HallGameSession, Log, TEXT("OnFindSessionComplete-------bWasSuccessful:%d"), bWasSuccessful);
//调用测试函数
PC->FindSessionComplete(UISessionList);
}
}
}
- 新建
HallGameMode与HUD、PlayerController
的蓝图类,填入世界设置
- 在
HallPlayerController
蓝图中去获取房间信息
通过按钮点击进入房间
- 创建会话大厅的UI界面
- 在
HallHUD
蓝图中创建这个UI
- 在
HallPlayerController
蓝图中勾选显示鼠标
- 点击进入房间按钮的时候就加入到房间逻辑
- 使用服务器与客户端的批处理脚本,运行结果
通过按钮点击查找房间
-
制作游戏大厅的显示列表的UI
-
创建房间列表的UI蓝图进行制作
-
然后在创建一个函数进行读取这些变量的逻辑进行赋值到UI上
-
添加这个UI蓝图到列表中
-
刷新按钮逻辑
-
使用服务器与客户端的批处理脚本,运行结果
客户端点击加入选中条目房间
- 将列表UI重新调整一下
- 给背景创建一个事件
- 创建一个
BPUI_HallSession
的实例变量,打开可编辑实例与生成时公开
- 回
BPUI_HallSession
蓝图中,将创建列表UI控件的节点刷新一下,将自己的引用传入进去
- 创建一个传入房间的变量和一个更新这个房间变量的函数
- 点击背景事件逻辑
- 在更新房间变量处也添加一个测试打印,打印房间号
- 将加入房间的默认0号换成我们的传入的房间号
- 将列表蓝图中的垂直框也就是信息栏改为非可命中测试(自身和所有子项),这样就点击文字信息栏也选中这个房间
- 使用服务器与客户端的批处理脚本,运行结果
选中房间条目UI颜色变化
-
在
BP_HallSingleList
蓝图的InitData
函数中添加两个线性颜色变量,InitData
函数每次刷新房间后,就会给房间默认的颜色
-
然后创建两个函数,一个是恢复颜色,一个是选择后改变颜色
-
当我们点击服务器的时候就改变颜色
-
在更新更新房间序号函数里面存一下
BP_HallSingleList
蓝图变量,这样就知道点击了那个房间了
-
到
BP_HallSingleList
蓝图里面的点击背景的事件中,将更新的更新房间号函数的自己传入参数,告诉主UI,现在点击的是自己,保存自己,这样就拿到上一个房间了
-
更新房间序号函数添加一个判断逻辑,如果上一个点击的房间对象还有效就改成默认颜色,否则还是原本逻辑执行,第一次运行,这个地方因为没有点击房间,这里必定为空,所以执行原本的逻辑
-
启动两个服务器测试运行结果