《UE5_C++多人TPS完整教程》学习笔记22 ——《P23 记录加入的玩家(Couting Incoming Players)》


本文为B站系列教学视频 《UE5_C++多人TPS完整教程》 —— 《P23 记录加入的玩家(Couting Incoming Players)》 的学习笔记,该系列教学视频为 Udemy 课程 《Unreal Engine 5 C++ Multiplayer Shooter》 的中文字幕翻译版,UP主(也是译者)为 游戏引擎能吃么



P23 记录加入的玩家

本节课将创建一个游戏模式,以便追踪(Track)加入游戏会话的玩家、打印游戏人数,之后我们就可以利用统计的人数来决定是否要从关卡 “Lobby” 过渡(Transition)到实际的匹配中。
在这里插入图片描述


23.1 游戏模式和游戏状态

  1. 在多人游戏中,两个重要的类分别是游戏模式类和游戏状态类:
  • 游戏模式规定了游戏规则,这涉及了很多方面,如何时将玩家移动至关卡中、选择出生位等。游戏模式有几个继承的虚拟函数,可以在玩家加入或离开会话时进行追踪,例如每当玩家加入游戏时都会调用 “PostLogin()” 访问玩家的控制器;当玩家离开时调用 “Logout()” 函数。
  • 客户端可以监控游戏状态,游戏状态将保存游戏的状态信息,而非特定的单个玩家的信息。游戏状态包含玩家状态数组,玩家状态类中保存了特定的单个玩家的信息,比如得分和胜利次数等。
  • 游戏模式类可以访问游戏状态类的玩家状态数组,通过查看该数组的大小可以得出玩家的个数。
    在这里插入图片描述

    两个主要类负责处理进行中游戏的相关信息:Game Mode 和 Game State。
    即使最开放的游戏也拥有基础规则,而这些规则构成了 Game Mode。在最基础的层面上,这些规则包括:

    • 出现的玩家和观众数量,以及允许的玩家和观众最大数量。
    • 玩家进入游戏的方式,可包含选择生成地点和其他生成/重生成行为的规则。
    • 游戏是否可以暂停,以及如何处理游戏暂停。
    • 关卡之间的过渡,包括游戏是否以动画模式开场。

    基于规则的事件在游戏中发生,需要进行追踪并和所有玩家共享时,信息将通过 Game State 进行存储和同步。这些信息包括:

    • 游戏已运行的时间(包括本地玩家加入前的运行时间)。
    • 每个个体玩家加入游戏的时间和玩家的当前状态。
    • 当前 Game Mode 的基类。
    • 游戏是否已开始。

    Game Modes
    特定的基础(如进行游戏所需要的玩家数量,或玩家加入游戏的方法)在多种类型的游戏中具有共通性。可根据开发的特定游戏进行无穷无尽的规则变化。无论规则如何,Game Modes 的任务都是定义和实现规则。Game Modes 当前常用的基类有两个。
    4.14 版本中加入了 AGameModeBase,这是所有 Game Mode 的基类,是经典的 AGameMode 简化版本。AGameMode 是 4.14 版本之前的基类,仍然保留,功能 如旧,但现在是 AGameModeBase 的子类。由于其比赛状态概念的实现,AGameMode 更适用于标准游戏类型(如多人射击游戏)。AGameModeBase 简洁高效,是新代码项目中包含的全新默认游戏模式。


    Game State
    Game State 负责启用客户端监控游戏状态。从概念上而言,Game State 应该管理所有已连接客户端已知的信息(特定于 Game Mode 但不特定于任何个体玩家)。它能够追踪游戏层面的属性,如已连接玩家的列表、夺旗游戏中的团队得分、开放世界游戏中已完成的任务,等等。
    Game State 并非追踪玩家特有内容(如夺旗比赛中特定玩家为团队获得的分数)的最佳之处,因为它们由 Player State 更清晰地处理。整体而言,Game State 应该追踪游戏进程中变化的属性。这些属性与所有人皆相关,且所有人可见。Game Mode 只存在于服务器上,而 Game State 存在于服务器上且会被复制到所有客户端,保持所有已连接机器的游戏进程更新。


    —— 虚幻引擎官方文档《Game Mode 和 Game State》

  1. 打开虚幻引擎,在内容浏览器中展开 “C++类/MenuSystem”,添加游戏模式基础(Game mode base)C++ 类,命名为 “LobbyGameMode”,选择模块为我们上节课新建的插件 “MenuSystem (Runtime)”。
    在这里插入图片描述
    在这里插入图片描述
  2. 点击 “创建类” 按钮,VS 中出现弹窗,选择 “全部重新加载(A)”。
    在这里插入图片描述

23.2 追踪加入或离开游戏的玩家

  1. 在 “LobbyGameMode.h” 中添加头文件 “GameFramework/GameStateBase.h” 和 “GameFramework/PlayerState.h”,避免出现错误 “使用了未定义类型 AGameStateBase” 和 “使用了未定义类型 APlayerState。接着,声明重写 “virtual void PostLogin()” 和 “virtual void Logout()”。

    // Fill out your copyright notice in the Description page of Project Settings.
    
    #pragma once
    
    #include "CoreMinimal.h"
    #include "GameFramework/GameModeBase.h"
    
    /* P23 记录加入的玩家(Couting Incoming Players)*/
    #include "GameFramework/GameStateBase.h"
    #include "GameFramework/PlayerState.h"
    /* P23 记录加入的玩家(Couting Incoming Players)*/
    
    #include "LobbyGameMode.generated.h"
    
    /**
     * 
     */
    UCLASS()
    class MENUSYSTEM_API ALobbyGameMode : public AGameModeBase
    {
    	GENERATED_BODY()
    
    /* P23 记录加入的玩家(Couting Incoming Players)*/
    public:
    	// 成功登录后调用。这是首个在 PlayerController 上安全调用复制函数之处。
    	// OnPostLogin 可在蓝图中实现,以添加额外的逻辑。
    	virtual void PostLogin(APlayerController* NewPlayer) override;	// 重写 virtual void PostLogin()
    	
    	// 玩家离开游戏或被摧毁时调用。可实现 OnLogout 执行蓝图逻辑。
    	virtual void Logout(AController* Exiting) override;				// virtual void Logout()
    /* P23 记录加入的玩家(Couting Incoming Players)*/
    };
    
  2. 在 “LobbyGameMode.cpp” 中实现“virtual void PostLogin()” 和 “virtual void Logout()” 的覆写。如果 “GameState” 和 “PlayerState” 标红且错误提示为 “不允许指针指向不完整的类类型 AGameStateBase” 和 “不允许指针指向不完整的类类型 APlayerState”,则添加头文件 “GameFramework/GameStateBase.h” 和 “GameFramework/PlayerState.h

    // Fill out your copyright notice in the Description page of Project Settings.
    
    
    #include "LobbyGameMode.h"
    
    
    /* P23 记录加入的玩家(Couting Incoming Players)*/
    void ALobbyGameMode::PostLogin(APlayerController* NewPlayer)
    {
    	Super::PostLogin(NewPlayer);
    	
    	if (GameState) {
    		int32 NumberOfPlayers = GameState.Get()->PlayerArray.Num();
    		if (GEngine) {
    			GEngine->AddOnScreenDebugMessage(	// 添加调试信息到屏幕上
    				1,				// Key 不是 -1 时,则更新现有消息
    				60.f,			// 调试信息的显示时间
    				FColor::Red,	// 字体颜色:黄色
    				FString::Printf(TEXT("Players in game: %d!"), NumberOfPlayers)	// 打印玩家人数
    			);
    
    			APlayerState* PlayerState = NewPlayer->GetPlayerState<APlayerState>();
    			if (PlayerState) {
    				FString PlayerName = PlayerState->GetPlayerName();
    				GEngine->AddOnScreenDebugMessage(	// 添加调试信息到屏幕上
    					2,				// Key 不是 -1 时,则更新现有消息
    					60.f,			// 调试信息的显示时间
    					FColor::Cyan,	// 字体颜色:蓝绿色
    					FString::Printf(TEXT("%s has joined the game!"), *PlayerName)	// 打印进入游戏的玩家昵称
    				);
    			}	
    		}
    	}
    }
    
    void ALobbyGameMode::Logout(AController* Exiting)
    {
    	Super::Logout(Exiting);
    
    	APlayerState* PlayerState = Exiting->GetPlayerState<APlayerState>();
    	if (PlayerState) {
    		int32 NumberOfPlayers = GameState.Get()->PlayerArray.Num();
    		GEngine->AddOnScreenDebugMessage(	// 添加调试信息到屏幕上
    			1,				// Key 不是 -1 时,则更新现有消息
    			60.f,			// 调试信息的显示时间
    			FColor::Red,	// 字体颜色:红色
    			FString::Printf(TEXT("Players in game: %d!"), NumberOfPlayers - 1)	// 打印玩家人数,
    			// 此时 PlayerArray.Num() 还未更新,这里进行减 1 操作只是为了方便测试时显示正确的人数,在实际项目中不会如此操作
    		);
    		
    		
    		FString PlayerName = PlayerState->GetPlayerName();
    		GEngine->AddOnScreenDebugMessage(	// 添加调试信息到屏幕上
    			2,				// Key 不是 -1 时,则更新现有消息
    			60.f,			// 调试信息的显示时间
    			FColor::Cyan,	// 字体颜色:蓝绿色
    			FString::Printf(TEXT("%s has exited the game!"), *PlayerName)	// 打印进入游戏的玩家昵称
    		);
    	}
    
    }
    /* P23 记录加入的玩家(Couting Incoming Players)*/
    

    这里可以复习一下函数 “AddOnScreenDebugMessage()” 第一个入参 “int32 Key” 取不同值的含义。这里 打印玩家人数打印进入或退出游戏的玩家昵称 设置了不同的 “Key” 是为了都能在屏幕上显示,如果设置为 -1 则 玩家人数消息 会被 进入或退出游戏的玩家昵称消息 覆盖。

    调用全局变量 GEngine 指针调用函数 AddOnScreenDebugMessage节点,进行屏幕输出。

    void AddOnScreenDebugMessage {
    	int32 Key,
    	float TimeToDisplay,
    	FColor Di splayColor,
    	const FString & DebugMessage,
    	bool bNewerOnTop,
    	const FVector2D & TextScale
    }
    
    • Key = -1 时,则添加新的消息,不会覆盖旧有消息(当 Key = -1 时,bNewerOnTop 有效,直接添加到队列最上层)
    • Key != -1 时,则更新现有消息,效率更高。

    —— 《虚幻引擎基础入门(C++) — 【日志输出篇 03】》

  3. 在 “MultiplayerSessionsSubsystem.cpp” 的 “CreateSession()” 函数中加入一条会话设置的代码行,用来设置唯一构建标识,防止不同的构建在搜索过程中可以互相搜索到。

    void UMultiplayerSessionsSubsystem::CreateSession(int32 NumpublicConnections, FString MatchType)
    {
    	
    	...
    	
    	// FOnlineSessionSettings 在头文件 "OnlineSessionSettings.h" 中
    	LastSessionSettings = MakeShareable(new FOnlineSessionSettings());	// 创建会话设置,利用函数 MakeShareable 初始化
    	
    	// 会话设置成员变量参阅及含义:https://docs.unrealengine.com/5.0/en-US/API/Plugins/OnlineSubsystem/FOnlineSessionSettings/
    	LastSessionSettings->bIsLANMatch = IOnlineSubsystem::Get()->GetSubsystemName() == "NULL" ? true : false;	// 会话设置:如果找到的子系统名称为 “NULL”,则使用 LAN 连接,否则不使用
    	LastSessionSettings->NumPublicConnections = NumpublicConnections;	// 会话设置:设置最大公共连接数为函数输入变量 NumpublicConnections
    	
    	...
    	
    	/* P23 记录加入的玩家(Couting Incoming Players)*/
    	LastSessionSettings->BuildUniqueId = 1;								// 会话设置:设置唯一构建标识,防止不同的构建在搜索过程中可以互相搜索到。
    	/* P23 记录加入的玩家(Couting Incoming Players)*/
    
    	...
    	
    }
    
  4. 在 “MenuSystem\Config” 目录下打开 “DefaultGame.ini”,设置最大玩家数为 100。创建会话中设置最大公共连接数 “NumPublicConnections”区别开,设置最大公共连接数是指定加入会话的最大连接数,而设置最大玩家数是指定连接到游戏项目最大人数。

    [/Script/EngineSettings.GeneralProjectSettings]
    ProjectID=6A5F83AB4DEB75FB9BB586AC8DE40CDA
    ProjectName=Third Person Game Template
    
    [StartupActions]
    bAddPacks=True
    InsertPack=(PackSource="StarterContent.upack",PackName="StarterContent")
    
    [/Script/Engine.GameSession]
    MaxPlayers=100	
    
  5. (选做)注释掉 “MenuSystemCharacter.cpp” 获取在线子系统并打印在线子系统名称到屏幕上的代码,并删除 “BP_ThirdPersonCharacter” 角色蓝图中按下数字键 “1” 和 “2” 对应的事件。

    // AMenuSystemCharacter
    
    AMenuSystemCharacter::AMenuSystemCharacter() :	// 为委托绑定回调函数
    	CreateSessionCompleteDelegate(FOnCreateSessionCompleteDelegate::CreateUObject(this, &ThisClass::OnCreateSessionComplete)),
    	FindSessionsCompleteDelegate(FOnFindSessionsCompleteDelegate::CreateUObject(this, &ThisClass::OnFindSessionsComplete)),
    	JoinSessionCompleteDelegate(FOnJoinSessionCompleteDelegate::CreateUObject(this, &ThisClass::OnJoinSessionComplete))
    {
    	
    	...
    	
    	/* P23 记录加入的玩家(Couting Incoming Players)*/
    	//IOnlineSubsystem* OnlineSubsystem = IOnlineSubsystem::Get();			// 获取当前的在线子系统指针
    	//if (OnlineSubsystem) {													// 如果当前在线子系统有效
    	//	OnlineSessionInterface = OnlineSubsystem->GetSessionInterface();	// 获取会话接口智能指针
    	//	if (GEngine) {
    	//		GEngine->AddOnScreenDebugMessage(	// 添加调试信息到屏幕上
    	//			-1,				// 使用 -1 不会覆盖前面的调试信息
    	//			15.f,			// 调试信息的显示时间
    	//			FColor::Blue,	// 字体颜色
    	//			FString::Printf(TEXT("Found subsystem %s!"),
    	//				*OnlineSubsystem->GetSubsystemName().ToString())	// 打印在线子系统的名称
    	//		);
    	//	}
    	//}
    	/* P23 记录加入的玩家(Couting Incoming Players)*/
    	
    }
    

    在这里插入图片描述


23.3 使用 Lobby 游戏模式

  1. 打开虚幻引擎,在内容浏览器中目录 “/内容/ThirdPerson/Blueprints/” 下新建 “LobbyGameMode” 蓝图类,命名为 “BP_LobbyGameMode”。
    在这里插入图片描述
    在这里插入图片描述
  2. 双击 “BP_LobbyGameMode”,进入蓝图编辑器,在右侧 “细节” 面板 “类” 选项卡下设置 “默认 pawn 类” 为 “BP_ThirdPersonCharacter”,编译、保存。
    在这里插入图片描述
  3. 在目录 “/内容/ThirdPerson/Maps/” 下打开地图 “Lobby”,在右下方 “世界场景设置” 面板设置 “游戏模式” 为“BP_LobbyGameMode”,保存。
    在这里插入图片描述

    如果没有看到右下方的 “世界场景设置” 面板,可以点击工具栏右侧 “项目和编辑器设置” 按钮(虚幻引擎窗口右上方),在下拉菜单栏中选中 “世界场景设置”。
    在这里插入图片描述


23.4 进行测试

  1. 将项目打包之后发送到另一台设备上。在设备 1 上运行游戏(保证 Steam 已经运行),点击 “Host” 按钮,当前关卡跳转至 “Lobby”,且左上角显示会话创建成功消息。
    在这里插入图片描述
    在这里插入图片描述

  2. 在设备 2 上运行游戏(保证 Steam 已经运行且登录的账户与设备1 上登录的账号不同),点击 “Join” 按钮,当前关卡跳转至 “Lobby”,并且可以看到有两个玩家存在,说明设备 2 成功找到并加入到了设备 1 创建的会话中,但是设备 2 屏幕左上角没有显示玩家人数和加入游戏信息,而创建会话的设备 1 上有显示。
    在这里插入图片描述
    在这里插入图片描述

  3. 在设备 2 上退出游戏,可以看到设备 1 屏幕左上角显示了设备 2 上的玩家离开游戏的消息,玩家人数也发生了变化。
    在这里插入图片描述


23.5 Summary

本节课了解了游戏模式和游戏状态的基本概念,并在虚幻引擎创建了游戏模式基础(Game mode base)C++ 类 “LobbyGameMode”。接着我们在 “LobbyGameMode.cpp” 中实现“virtual void PostLogin()” 和 “virtual void Logout()” 的覆写,以便能够打印在线玩家人数以及玩家加入或退出游戏的消息。为了能够使用这个游戏模式,我们以 “LobbyGameMode” 为父类新建了蓝图类 “BP_LobbyGameMode”,设置蓝图类的 “默认 pawn 类” 为 “BP_ThirdPersonCharacter”,并将地图 “Lobby” 的 “游戏模式” 设置为“BP_LobbyGameMode”。最后,我们在两台设备上以玩家加入和退出游戏的方式进行来了测试,玩家人数以及玩家加入和退出游戏的消息都能正常显示。
在这里插入图片描述
23.2 追踪加入或离开游戏的玩家步骤 2 中使用函数 AddOnScreenDebugMessage() 进行屏幕消息输出时,若函数第一个入参 “int32 Key” 为 -1 ,则添加新的消息,不会覆盖旧有消息(当 Key 为 -1 时,bNewerOnTop 有效,直接添加到队列最上层),若 Key 不为 -1 ,则更新现有消息。这里打印 玩家人数 和 打印进入或退出游戏的玩家昵称 设置了不同的 “Key” 是为了都能在屏幕上显示,如果设置为 -1 则 玩家人数消息 会被 进入或退出游戏的玩家昵称消息 覆盖。


  • 33
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值