手机怎么自创网站,做响应式网站的意义,永嘉规划建设局网站,企业文化有哪些UE4 C联网RPC教程笔记#xff08;三#xff09;#xff08;第8~9集#xff09;完结 8. exe 后缀实现监听服务器9. C 实现监听服务器 8. exe 后缀实现监听服务器
前面我们通过蓝图节点实现了局域网连接的功能#xff0c;实际上我们还可以给项目打包后生成的 .exe 文件创建… UE4 C联网RPC教程笔记三第8~9集完结 8. exe 后缀实现监听服务器9. C 实现监听服务器 8. exe 后缀实现监听服务器
前面我们通过蓝图节点实现了局域网连接的功能实际上我们还可以给项目打包后生成的 .exe 文件创建一个快捷方式然后修改这个快捷方式的属性中的目标就可以实现简易的联网功能。
下面内容截取自梁迪老师准备的 RPC 联网文档
使用 .exe 后缀输入和 open IP 地址联网
注意这里只讨论 NM_Standalone、NM_ListenServer 以及 NM_Client 的情况
NM_Standalone打包出 exe 后不创建快捷方式直接运行 exe执行 GetNetMode() 返回 NM_Standalone 类型在这个状态下打印 GetWorld()-IsServer() 以及 HasAuthority() 都是 true如果在存在监听服务器的情况下运行命令行 open 127.0.0.1该单独端就会链接上监听服务器执行 GetNetMode() 返回 NM_Client 类型打印 GetWorld()-IsServer() 以及 HasAuthority() 都是 false如果再次运行命令行 open 127.0.0.1该端会先断开服务器然后再链接一次NM_ListenServer打包出来的 exe 生成快捷方式并且在快捷方式的属性下在 exe 结尾添加 空格?listen如RPCProject.exe ?listen运行该快捷方式就会运行监听服务器端执行 GetNetMode() 返回 NM_ListenServer 类型打印 GetWorld()-IsServer() 以及 HasAuthority() 都是true如果运行命令行 open 127.0.0.1该监听服务器端就会变成单独端 NM_StandaloneNM_Client打包出来的 exe 生成快捷方式并且在快捷方式的属性下在 exe 结尾添加 空格127.0.0.1 -game如RPCProject.exe 127.0.0.1 -game运行该快捷方式如果存在监听端就会链接上监听服务器成为客户端执行 GetNetMode() 返回 NM_Client 类型打印 GetWorld()-IsServer() 以及 HasAuthority() 都是 false
接下来我们在 GameMap 里用到的玩家控制器中添加一些打印当前端的逻辑。
RPCController.h
protected:.void EchoNetMode();RPCController.cpp
// 引入头文件
#include RPCHelper.hvoid ARPCController::BeginPlay()
{Super::BeginPlay();// 限定打开的窗口尺寸。此处老师将变量名拼写错成 “Src”FString ScreenCommand FString(r.setres 1280x720w);ConsoleCommand(ScreenCommand);bShowMouseCursor false;FInputModeGameOnly InputMode;SetInputMode(InputMode);// 打印当前的端EchoNetMode();
}void ARPCController::EchoNetMode()
{ENetMode NetMode GetNetMode();switch (NetMode){case NM_Standalone:DDH::Debug() NM_Standalone DDH::Endl();break;case NM_DedicatedServer:DDH::Debug() NM_DedicatedServer DDH::Endl();break;case NM_ListenServer:DDH::Debug() NM_ListenServer DDH::Endl();break;case NM_Client:DDH::Debug() NM_Client DDH::Endl();break;case NM_MAX:DDH::Debug() NM_MAX DDH::Endl();break;}
}编译后将默认关卡设置为 GameMap。随后开始打包。 打包成功后运行 .exe 文件可以看到左上角打印了当前的端名以及控制器名。 创建 .exe 文件的一个快捷方式命名为 RPCCourseServer 保留 .exe 后缀随后修改该文件的属性。 运行 RPCCourseServer.exe可以看到左上角已经变成了聆听服务器。 再创建一个快捷方式命名为 RPCCourseClient这次给属性里的目标添加后缀 (空格)127.0.0.1 -game。先运行 RPCCourseServer然后再运行 RPCCourseClient.exe可以看到后者的窗口里左上角显示是客户端。 此时在服务端按下 J 键只有在客户端能看到角色处生成了红色的数字 1。说明联网方法和变量都是可以用的。
关掉客户端服务端左上角会显示客户端的控制器登出了。 保持服务端开启运行 RPCCourse.exe按 ~ 键波浪符呼出控制台输入 open 127.0.0.1。可以看到独立端加入了服务端并且原独立端左上角输出了当前为客户端。 此时在服务端按下 J 键也是只有原独立端可以看到自己角色位置生成了一个红色的数字 1。
在服务端呼出控制台然后输入 open 127.0.0.1服务器会关闭。 9. C 实现监听服务器
前面我们用蓝图和快捷方式实现聆听服务器联机接下来我们尝试下用 C 来实现同样的效果。下面内容截取自梁迪老师准备的 RPC 联网文档
创建寻找加入会话 C 模式这里只实现局域网
C 联网步骤和蓝图基本相同但是中间多了一个 StartSession() 方法需要调用主要可以参考
UCreateSessionCallbackProxyUStartSessionCallbackProxyUFindSessionsCallbackProxyUJoinSessionCallbackProxyUDestroySessionCallbackProxy
这些类的实现具体实现参考项目里的 URPCInstance 类。
UE4 官方推荐将联网的逻辑放在 GameInstance 下处理GameInstance 在整个游戏所有关卡中都存在用来传递关卡数据在保存数据方面起作用联网数据放在 GameInstance 下方便在任何关卡去操作联网
接下来开始实操。在默认路径下创建一个 C 的 Instance 类命名为 RPCInstance。
将默认地图设置成 MenuMap。
来到主界面 UI 类声明一个 URPCInstance 的指针和相应的注册方法用来保存对 GameInstance 的引用并且声明两个蓝图可调用的方法用于接入主界面的按钮点击事件。
MenuWidget.h
// 提前声明
class URPCInstance;UCLASS()
class RPCCOURSE_API UMenuWidget : public UUserWidget
{GENERATED_BODY()public:void AssignRPCInstance(URPCInstance* InInstance);UFUNCTION(BlueprintCallable)void LANServerEvent();UFUNCTION(BlueprintCallable)void LANClientEvent();public:URPCInstance* RPCInstance;
};MenuWidget.cpp
// 引入头文件
#include RPCInstance.hvoid UMenuWidget::AssignRPCInstance(URPCInstance* InInstance)
{RPCInstance InInstance;
}void UMenuWidget::LANServerEvent()
{RPCInstance-HostSession();
}void UMenuWidget::LANClientEvent()
{RPCInstance-ClientSession();
}RPCInstance 里承载着联网相关的逻辑主要都是调用网络模块的 API 和利用委托绑定回调函数。
RPCInstance.h
// 引入头文件
#include Interfaces/OnlineSessionInterface.h // 如果这个不行就用下面这句
//#include ../Plugins/Online/OnlineSubsystem/Source/Public/Interfaces/OnlineSessionInterface.h
#include Delegates/IDelegateInstance.h#include RPCInstance.generated.h// 提前声明
class IOnlineSubsystem;
class APlayerController;UCLASS()
class RPCCOURSE_API URPCInstance : public UGameInstance
{GENERATED_BODY()public:URPCInstance();// 注册玩家控制器并且获取联网系统和端 IDvoid AssignPlayerController(APlayerController* InController);// 创建会话void HostSession();// 寻找会话void ClientSession();// 销毁会话void DestroySession();protected:// 开启服务器回调函数void OnCreateSessionComplete(FName SessionName, bool bWasSuccessful);void OnStartOnlineGameComplete(FName SessionName, bool bWasSuccessful);// 加入服务器回调函数void OnFindSessionsComplete(bool bWasSuccessful);void OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result);// 销毁会话回调函数void OnDestroySessionComplete(FName SessionName, bool bWAsSuccessful);protected:APlayerController* PlayerController;// 开启服务器委托与句柄FOnCreateSessionCompleteDelegate OnCreateSessionCompleteDelegate;FOnStartSessionCompleteDelegate OnStartSessionCompleteDelegate;FDelegateHandle OnCreateSessionCompleteDelegateHandle;FDelegateHandle OnStartSessionCompleteDelegateHandle;// 加入服务器委托与句柄FOnFindSessionsCompleteDelegate OnFindSessionsCompleteDelegate;FOnJoinSessionCompleteDelegate OnJoinSessionCompleteDelegate;FDelegateHandle OnFindSessionsCompleteDelegateHandle;FDelegateHandle OnJoinSessionCompleteDelegateHandle;// 销毁会话委托与句柄FOnDestroySessionCompleteDelegate OnDestroySessionCompleteDelegate;FDelegateHandle OnDestroySessionCompleteDelegateHandle;// 联网系统IOnlineSubsystem* OnlineSub;// 端的 IDTSharedPtrconst FUniqueNetId UserID;// 保存寻找到的 SessionsTSharedPtrFOnlineSessionSearch SearchObject;
};RPCInstance.cpp
// 引入头文件
#include ../Plugins/Online/OnlineSubsystem/Source/Public/OnlineSubsystem.h
#include ../Plugins/Online/OnlineSubsystem/Source/Public/OnlineSessionSettings.h
#include ../Plugins/Online/OnlineSubsystem/Source/Public/Interfaces/OnlineSessionInterface.h
#include ../Plugins/Online/OnlineSubsystemUtils/Source/OnlineSubsystemUtils/Public/OnlineSubsystemUtils.h
#include GameFramework/PlayerController.h
#include RPCHelper.h
#include Kismet/GameplayStatics.hURPCInstance::URPCInstance()
{// 绑定回调函数OnCreateSessionCompleteDelegate FOnCreateSessionCompleteDelegate::CreateUObject(this, URPCInstance::OnCreateSessionComplete);OnStartSessionCompleteDelegate FOnStartSessionCompleteDelegate::CreateUObject(this, URPCInstance::OnStartOnlineGameComplete);OnFindSessionsCompleteDelegate FOnFindSessionsCompleteDelegate::CreateUObject(this, URPCInstance::OnFindSessionsComplete);OnJoinSessionCompleteDelegate FOnJoinSessionCompleteDelegate::CreateUObject(this, URPCInstance::OnJoinSessionComplete);OnDestroySessionCompleteDelegate FOnDestroySessionCompleteDelegate::CreateUObject(this, URPCInstance::OnDestroySessionComplete);
}void URPCInstance::AssignPlayerController(APlayerController* InController)
{PlayerController InController;// 获取 OnlineSub// 获取方式一Online::GetSubsystem(GetWorld(), NAME_None)推荐方式// 获取方式二使用 IOnlineSubsystem::Get()直接获取可以 CreateSession 但是 JoinSession 后客户端没有跳转场景OnlineSub Online::GetSubsystem(PlayerController-GetWorld(), NAME_None);// 获取 UserID// 获取方式一UGameplayStatics::GetGameInstance(GetWorld())-GetLocalPlayers()[0]-GetPreferredUniqueNetId()if (GetLocalPlayers().Num() 0) {DDH::Debug() No LocalPlayer Exists, Cant Get UserID DDH::Endl();}else {UserID (*GetLocalPlayers()[0]-GetPreferredUniqueNetId()).AsShared();}#if 0// 获取方式二使用 PlayerState 获取该方式在打包成 exe 运行无问题但是在编辑器模式下运行多个窗口就会找不到 PlayerStateif (PlayerController-PlayerState)UserID PlayerController-PlayerState-UniqueId.GetUniqueNetId();elseDDH::Debug() No PlayerState Exists, Cant Get UserID DDH::Endl();
#endif// 如果在这里直接获取 Session 运行时会报错生命周期的问题
}void URPCInstance::HostSession()
{if (OnlineSub) {IOnlineSessionPtr Session OnlineSub-GetSessionInterface();if (Session.IsValid()) {// 会话设置FOnlineSessionSettings Settings;// 连接数Settings.NumPublicConnections 10;Settings.bShouldAdvertise true;Settings.bAllowJoinInProgress true;// 使用局域网Settings.bIsLANMatch true;Settings.bUsesPresence true;Settings.bAllowJoinViaPresence true;// 绑定委托OnCreateSessionCompleteDelegateHandle Session-AddOnCreateSessionCompleteDelegate_Handle(OnCreateSessionCompleteDelegate);// 创建会话Session-CreateSession(*UserID, NAME_GameSession, Settings);}}
}void URPCInstance::ClientSession()
{if (OnlineSub) {IOnlineSessionPtr Session OnlineSub-GetSessionInterface();if (Session.IsValid()) {// 实例化搜索结果指针并且设定参数SearchObject MakeShareable(new FOnlineSessionSearch);// 返回结果数SearchObject-MaxSearchResults 10;// 是否是局域网就是 IsLANSearchObject-bIsLanQuery true;SearchObject-QuerySettings.Set(SEARCH_PRESENCE, true, EOnlineComparisonOp::Equals);// 绑定寻找会话委托OnFindSessionsCompleteDelegateHandle Session-AddOnFindSessionsCompleteDelegate_Handle(OnFindSessionsCompleteDelegate);// 进行会话寻找Session-FindSessions(*UserID, SearchObject.ToSharedRef());}}
}void URPCInstance::DestroySession()
{if (OnlineSub) {IOnlineSessionPtr Session OnlineSub-GetSessionInterface();if (Session.IsValid()) {// 绑定销毁会话委托OnDestroySessionCompleteDelegateHandle Session-AddOnDestroySessionCompleteDelegate_Handle(OnDestroySessionCompleteDelegate);// 执行销毁会话Session-DestroySession(NAME_GameSession);}}
}void URPCInstance::OnCreateSessionComplete(FName SessionName, bool bWasSuccessful)
{if (OnlineSub) {IOnlineSessionPtr Session OnlineSub-GetSessionInterface();if (Session.IsValid()) {// 解绑创建会话完成回调函数Session-ClearOnCreateSessionCompleteDelegate_Handle(OnCreateSessionCompleteDelegateHandle);// 判断创建会话是否成功if (bWasSuccessful) {DDH::Debug() CreateSession Succeed DDH::Endl();// 绑定开启会话委托OnStartSessionCompleteDelegateHandle Session-AddOnStartSessionCompleteDelegate_Handle(OnStartSessionCompleteDelegate);// 执行开启会话Session-StartSession(NAME_GameSession);}elseDDH::Debug() CreateSession Failed DDH::Endl();}}
}void URPCInstance::OnStartOnlineGameComplete(FName SessionName, bool bWasSuccessful)
{if (OnlineSub) {IOnlineSessionPtr Session OnlineSub-GetSessionInterface();if (Session.IsValid()) {// 注销开启会话委托绑定Session-ClearOnStartSessionCompleteDelegate_Handle(OnStartSessionCompleteDelegateHandle);if (bWasSuccessful) {DDH::Debug() StartSession Succeed DDH::Endl();// 服务端跳转场景UGameplayStatics::OpenLevel(PlayerController-GetWorld(), FName(GameMap), true, FString(listen));}elseDDH::Debug() StartSession Failed DDH::Endl();}}
}void URPCInstance::OnFindSessionsComplete(bool bWasSuccessful)
{if (OnlineSub) {IOnlineSessionPtr Session OnlineSub-GetSessionInterface();if (Session.IsValid()) {// 取消寻找会话委托绑定Session-ClearOnStartSessionCompleteDelegate_Handle(OnStartSessionCompleteDelegateHandle);if (bWasSuccessful) {// 如果收集的结果存在并且大于 1if (SearchObject.IsValid() SearchObject-SearchResults.Num() 0) {DDH::Debug() Find Sessions Succeed DDH::Endl();// 绑定加入 Session 委托OnJoinSessionCompleteDelegateHandle Session-AddOnJoinSessionCompleteDelegate_Handle(OnJoinSessionCompleteDelegate);// 执行加入 SessionSession-JoinSession(*UserID, NAME_GameSession, SearchObject-SearchResults[0]);}elseDDH::Debug() Find Sessions Succeed But Num 0 DDH::Endl();}elseDDH::Debug() Find Sessions Failed DDH::Endl();}}
}void URPCInstance::OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result)
{if (OnlineSub) {IOnlineSessionPtr Session OnlineSub-GetSessionInterface();if (Session.IsValid()) {// 取消加入会话委托绑定Session-ClearOnJoinSessionCompleteDelegate_Handle(OnJoinSessionCompleteDelegateHandle);// 如果加入成功if (Result EOnJoinSessionCompleteResult::Success) {// 传送玩家到新地图FString ConnectString;if (Session-GetResolvedConnectString(NAME_GameSession, ConnectString)) {DDH::Debug() Join Sessions Succeed DDH::Endl();// 客户端切换到服务器的关卡PlayerController-ClientTravel(ConnectString, TRAVEL_Absolute);}}else DDH::Debug() Join Sessions Failed DDH::Endl();}}
}void URPCInstance::OnDestroySessionComplete(FName SessionName, bool bWAsSuccessful)
{if (OnlineSub) {IOnlineSessionPtr Session OnlineSub-GetSessionInterface();if (Session.IsValid()) {// 注销销毁会话委托Session-ClearOnDestroySessionCompleteDelegate_Handle(OnDestroySessionCompleteDelegateHandle);// 其他逻辑...}}
}读者可能会发现上面的代码中绑定委托后直接就通过 Session 执行相应逻辑了然后在接下来的逻辑里解绑委托。这里笔者倾向于将这一过程理解为 装弹 — 发射 — 退弹壳。UE4 已经将网络模块的细枝末节都为我们封装好了我们只需要知道如何使用就够了当然喜欢探索的读者也可以查阅源码去深入理解。
在主界面控制器里注册自己到 RPCInstance并且将 RPCInstance 注册到主界面 UI。
MenuController.cpp
// 引入头文件
#include Kismet/GameplayStatics.h
#include RPCInstance.hvoid AMenuController::BeginPlay()
{// 获取 GameInstanceURPCInstance* RPCInstance CastURPCInstance(UGameplayStatics::GetGameInstance(GetWorld()));RPCInstance-AssignPlayerController(this);UClass* MenuWidgetClass LoadClassUMenuWidget(NULL, TEXT(WidgetBlueprint/Game/Blueprint/MenuWidget_BP.MenuWidget_BP_C));UMenuWidget* MenuWidget CreateWidgetUMenuWidget(GetWorld(), MenuWidgetClass);MenuWidget-AddToViewport();MenuWidget-AssignRPCInstance(RPCInstance); // 注册 RPCInstance 到 MenuWidget
}最后添加一些网络模块相关的依赖。
RPCCourse.Build.cs
public RPCCourse(ReadOnlyTargetRules Target) : base(Target){PCHUsage PCHUsageMode.UseExplicitOrSharedPCHs;PublicDependencyModuleNames.AddRange(new string[] { Core, CoreUObject, Engine, InputCore, HeadMountedDisplay, Slate, UMG, HeadMountedDisplay, OnlineSubsystem, OnlineSubsystemUtils }); // 添加这三个模块依赖// 添加动态加载模组DynamicallyLoadedModuleNames.AddRange(new string[] {OnlineSubsystemNull,});}编译后在项目设置里将默认的 GameInstance 设置为 RPCInstance。
来到 MenuWidget_BP 的图表重新调整两个按钮点击事件的连接节点如下 此时运行玩家数应该是 3。运行后在服务端创建服务器创建成功让另外两个客户端加入服务器也能进入成功。并且在服务端按 J 键另外两个客户端各自能看到自己角色处生成红色数字。
不过如果在客户端创建服务器另外一个客户端可以加入但是服务端加入会显示找到会话和加入成功但不会跳转到 GameMap。所以必须要让服务端创建服务器才能正常运作。
至此梁迪老师的 RPC 课程到这里就结束了衷心感谢梁迪老师提供的优质课程 : )