当前位置: 首页 > news >正文

UE5多人MOBA+GAS 49、创建大厅

文章目录


创建各种类

创建大厅游戏模式

LobbyGameMode
在这里插入图片描述

创建新的玩家控制器

MenuPlayerController菜单玩家控制器
在这里插入图片描述
LobbyPlayerController大厅玩家控制器
在这里插入图片描述

创建玩家状态

在这里插入图片描述

创建大厅UI

LobbyWidget
在这里插入图片描述

#pragma once#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "LobbyWidget.generated.h"class UUniformGridPanel;
class UButton;
class UWidgetSwitcher;
/*** */
UCLASS()
class CRUNCH_API ULobbyWidget : public UUserWidget
{GENERATED_BODY()
private:// 主界面切换器UPROPERTY(meta=(BindWidget))TObjectPtr<UWidgetSwitcher> MainSwitcher;// 队伍选择根节点UPROPERTY(meta=(BindWidget))	TObjectPtr<UWidget> TeamSelectionRoot;// 开始英雄选择按钮UPROPERTY(meta=(BindWidget))TObjectPtr<UButton> StartHeroSelectionButton;// 队伍选择格子面板UPROPERTY(meta=(BindWidget))TObjectPtr<UUniformGridPanel> TeamSelectionSlotGridPanel;
};

创建大厅UI蓝图
在这里插入图片描述

在这里插入图片描述
添加垂直框里面再放一个水平框,放入两个文本
在这里插入图片描述
添加一个统一网格面板
在这里插入图片描述

菜单玩家控制器中显示菜单

#pragma once#include "CoreMinimal.h"
#include "GameFramework/PlayerController.h"
#include "MenuPlayerController.generated.h"/*** 菜单界面专用玩家控制器* 负责菜单UI的生成与管理*/
UCLASS()
class CRUNCH_API AMenuPlayerController : public APlayerController
{GENERATED_BODY()
public:	// 游戏开始时调用virtual void BeginPlay() override;// 玩家状态同步时调用virtual void OnRep_PlayerState() override;private:// 菜单界面类UPROPERTY(EditDefaultsOnly, Category = "Menu")TSubclassOf<UUserWidget> MenuWidgetClass;// 菜单界面实例UPROPERTY()TObjectPtr<UUserWidget> MenuWidget;// 生成菜单界面void SpawnWidget();
};
#include "MenuPlayerController.h"#include "Blueprint/UserWidget.h"void AMenuPlayerController::BeginPlay()
{Super::BeginPlay();// 设置为仅UI输入模式并显示鼠标SetInputMode(FInputModeUIOnly());SetShowMouseCursor(true);// 具有服务器权限而且是本地玩家控制器时生成菜单UIif (HasAuthority() && IsLocalPlayerController()){SpawnWidget();}
}void AMenuPlayerController::OnRep_PlayerState()
{Super::OnRep_PlayerState();if (IsLocalPlayerController()){SpawnWidget();}
}void AMenuPlayerController::SpawnWidget()
{if (MenuWidgetClass){MenuWidget = CreateWidget<UUserWidget>(this, MenuWidgetClass);if (MenuWidget){MenuWidget->AddToViewport();}}
}

大厅游戏模式中添加构造函数

#pragma once#include "CoreMinimal.h"
#include "CGameMode.h"
#include "LobbyGameMode.generated.h"/*** */
UCLASS()
class CRUNCH_API ALobbyGameMode : public ACGameMode
{GENERATED_BODY()
public:	ALobbyGameMode();
};
#include "LobbyGameMode.h"ALobbyGameMode::ALobbyGameMode()
{bUseSeamlessTravel = true;
}

创建大厅的游戏模式和玩家控制器的蓝图版本
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

把大厅游戏模式放入大厅地图中
在这里插入图片描述

TeamSelectionWidget
在这里插入图片描述

#pragma once#include "CoreMinimal.h"
#include "Blueprint/UserWidget.h"
#include "TeamSelectionWidget.generated.h"class UTextBlock;
class UButton;
// 声明一个委托:当这个插槽被点击时广播出去,并携带这个插槽的ID。
DECLARE_MULTICAST_DELEGATE_OneParam(FOnSlotClicked, uint8 /*SlotID*/);/*** 用于表示队伍选择界面中一个可选槽位(Slot)的UI控件。*/
UCLASS()
class CRUNCH_API UTeamSelectionWidget : public UUserWidget
{GENERATED_BODY()
public:// 设置此UI控件代表的槽位ID。void SetSlotID(uint8 NewSlotID);// 更新此槽位显示的信息(通常是玩家昵称)。void UpdateSlotInfo(const FString& PlayerNickName);// 重写原生构建函数,用于初始化绑定。virtual void NativeConstruct() override;// 当用户点击这个槽位的选择按钮时,会广播此委托。FOnSlotClicked OnSlotClicked;private:// 用于选择此槽位的按钮。UPROPERTY(meta=(BindWidget))TObjectPtr<UButton> SelectButton;  // 用于显示槽位信息(如玩家名)的文本控件。UPROPERTY(meta=(BindWidget))TObjectPtr<UTextBlock> InfoText;  // UFUNCTION宏标记,用于绑定按钮点击事件。UFUNCTION()void SelectButtonClicked(); // 内部处理按钮点击的函数。// 此控件所代表的槽位的唯一标识符(ID)。uint8 SlotID;  
};

#include "TeamSelectionWidget.h"#include "Components/Button.h"
#include "Components/TextBlock.h"void UTeamSelectionWidget::SetSlotID(uint8 NewSlotID)
{SlotID = NewSlotID;
}void UTeamSelectionWidget::UpdateSlotInfo(const FString& PlayerNickName)
{// 显示玩家昵称InfoText->SetText(FText::FromString(PlayerNickName));
}void UTeamSelectionWidget::NativeConstruct()
{Super::NativeConstruct();// 将按钮控件的"OnClicked"事件动态绑定到本类的SelectButtonClicked函数。// 当用户点击按钮时,就会调用SelectButtonClicked。SelectButton->OnClicked.AddDynamic(this, &UTeamSelectionWidget::SelectButtonClicked);
}void UTeamSelectionWidget::SelectButtonClicked()
{// 广播委托OnSlotClicked.Broadcast(SlotID);
}

创建蓝图版本
在这里插入图片描述
在这里插入图片描述

添加边界
在这里插入图片描述

添加网络函数库类

这里选错了,选第二个
在这里插入图片描述

#pragma once#include "CoreMinimal.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "TNetStatics.generated.h"/*** */
UCLASS()
class CRUNCH_API UTNetStatics : public UBlueprintFunctionLibrary
{GENERATED_BODY()
public:	/*** 获取每支队伍的基础玩家数量* @return 玩家数量(默认值为5)*/static uint8 GetPlayerCountPerTeam();
};
#include "TNetStatics.h"uint8 UTNetStatics::GetPlayerCountPerTeam()
{return 5;
}

大厅UI中添加队伍插槽

public:virtual void NativeConstruct() override;
private:// 队伍选择格子UPROPERTY(EditDefaultsOnly, Category = "TeamSelection")TSubclassOf<UTeamSelectionWidget> TeamSelectionWidgetClass;// 所有队伍选择格子UPROPERTY()TArray<UTeamSelectionWidget*> TeamSelectionSlots;/*** 清空并重新生成队伍选择槽位* 根据玩家数量生成双队列网格布局*/void ClearAndPopulateTeamSelectionSlots();/*** 处理槽位点击事件* @param NewSlotID 新选择的槽位ID*/void SlotSelected(uint8 NewSlotID);
void ULobbyWidget::NativeConstruct()
{Super::NativeConstruct();ClearAndPopulateTeamSelectionSlots();
}void ULobbyWidget::ClearAndPopulateTeamSelectionSlots()
{TeamSelectionSlotGridPanel->ClearChildren();// 生成两队玩家槽for (int i = 0; i < UTNetStatics::GetPlayerCountPerTeam() * 2; ++i){// 创建槽位if (UTeamSelectionWidget* NewSelectionSlot = CreateWidget<UTeamSelectionWidget>(this, TeamSelectionWidgetClass)){// 设置槽IDNewSelectionSlot->SetSlotID(i);// 添加到网格布局if (UUniformGridSlot* NewGridSlot = TeamSelectionSlotGridPanel->AddChildToUniformGrid(NewSelectionSlot)){// 计算行列位置int Row = i % UTNetStatics::GetPlayerCountPerTeam();int Column = i < UTNetStatics::GetPlayerCountPerTeam() ? 0 : 1;NewGridSlot->SetRow(Row);NewGridSlot->SetColumn(Column);}// 绑定槽点击事件产生的广播委托NewSelectionSlot->OnSlotClicked.AddUObject(this, &ULobbyWidget::SlotSelected);TeamSelectionSlots.Add(NewSelectionSlot);}}
}void ULobbyWidget::SlotSelected(uint8 NewSlotID)
{UE_LOG(LogTemp, Warning, TEXT("切换的槽位: %d"), NewSlotID)
}

在这里插入图片描述

新建一个空白类PlayerInfoTypes用于存储玩家的网络ID、名字以及对应的队伍插槽
在这里插入图片描述
在这里插入图片描述

#pragma once#include "CoreMinimal.h"
#include "PlayerInfoTypes.generated.h"class APlayerState;/*** 玩家选择信息结构体* 存储玩家在游戏大厅中的选择状态,包括槽位、角色定义等信息*/
USTRUCT()
struct FPlayerSelection
{GENERATED_BODY()
public:/*** 默认构造函数* 初始化空的玩家选择*/FPlayerSelection();/*** 带参数的构造函数* @param InSlot 玩家槽位ID* @param InPlayerState 玩家状态对象*/FPlayerSelection(uint8 InSlot, const APlayerState* InPlayerState);/*** 设置玩家槽位* @param NewSlot 新槽位ID*/FORCEINLINE void SetSlot(uint8 NewSlot) { Slot = NewSlot; }/*** 获取玩家槽位ID* @return 当前玩家槽位*/FORCEINLINE uint8 GetPlayerSlot() const { return Slot; }/*** 获取玩家唯一ID* @return 网络唯一ID副本*/FORCEINLINE FUniqueNetIdRepl GetPLayerUniqueId() const { return PlayerUniqueId; }/*** 获取玩家昵称* @return 玩家显示名称*/FORCEINLINE FString GetPlayerNickName() const { return PlayerNickName; }/*** 检查是否属于指定玩家* @param PlayerState 待比较的玩家状态对象* @return 是否匹配*/bool IsForPlayer(const APlayerState* PlayerState) const;/*** 验证选择数据有效性* @return 是否为有效选择*/bool IsValid() const;/*** 获取无效槽位标识符* @return 无效槽位的数值表示*/static uint8 GetInvalidSlot();private:/** 玩家在队伍中的槽位ID */UPROPERTY()uint8 Slot;/** 玩家的网络唯一ID */UPROPERTY()FUniqueNetIdRepl PlayerUniqueId;/** 玩家显示名称 */UPROPERTY() FString PlayerNickName;
};
#include "Player/PlayerInfoTypes.h"#include "GameFramework/PlayerState.h"
#include "Network/TNetStatics.h"FPlayerSelection::FPlayerSelection():Slot{ GetInvalidSlot() },PlayerUniqueId{ FUniqueNetIdRepl::Invalid() },PlayerNickName{""}
{
}FPlayerSelection::FPlayerSelection(uint8 InSlot, const APlayerState* InPlayerState):Slot{ InSlot }
{if (InPlayerState){// 获取网络唯一ID和名称PlayerUniqueId = InPlayerState->GetUniqueId();PlayerNickName = InPlayerState->GetPlayerName();}
}bool FPlayerSelection::IsForPlayer(const APlayerState* PlayerState) const
{if (!PlayerState)return false;// 编辑器启动的时候用玩家名字判断
#if WITH_EDITORreturn PlayerState->GetPlayerName() == PlayerNickName;
#else// 运行时用网络唯一ID判断return PlayerState->GetUniqueId() == GetPLayerUniqueId();
#endif
}bool FPlayerSelection::IsValid() const
{
#if WITH_EDITORreturn true; // 编辑器模式始终返回有效(用于测试)
#else// 检查网络唯一ID是否有效if (!PlayerUniqueId.IsValid())return false;// 槽位是否有效if (Slot == GetInvalidSlot())return false;// 检查槽位是否超出双队列限制if (Slot >= UCNetStatics::GetPlayerCountPerTeam() * 2)return false;return true;
#endif
}uint8 FPlayerSelection::GetInvalidSlot()
{// 无效槽位return 255;
}

创建游戏状态

CGameState
在这里插入图片描述

// 幻雨喜欢小猫咪#pragma once#include "CoreMinimal.h"
#include "GameFramework/GameStateBase.h"
#include "Player/PlayerInfoTypes.h"
#include "CGameState.generated.h"/*** 玩家选择更新委托* 当玩家选择状态发生变化时广播* @param NewPlayerSelection 新的玩家选择数组*/
DECLARE_MULTICAST_DELEGATE_OneParam(FOnPlayerSelectionUpdated, const TArray<FPlayerSelection>& /*NewPlayerSelection*/);/*** 游戏状态类(CGameState)* 管理大厅阶段的玩家选择状态,处理角色选择和队伍槽位分配* 负责维护玩家选择数据并提供相关操作接口*/
UCLASS()
class CRUNCH_API ACGameState : public AGameStateBase
{GENERATED_BODY()
public:/*** 请求更改玩家选择槽位* @param RequestingPlayer 请求更改的玩家状态* @param DesiredSlot 目标槽位ID*/void RequestPlayerSelectionChange(const APlayerState* RequestingPlayer, uint8 DesiredSlot);/*** 检查槽位是否被占用* @param SlotId 要检查的槽位ID* @return 是否被占用*/bool IsSlotOccupied(uint8 SlotId) const;/** 玩家选择更新事件(多播委托) */FOnPlayerSelectionUpdated OnPlayerSelectionUpdated;/*** 获取玩家选择数组* @return 当前所有玩家的选择信息数组*/const TArray<FPlayerSelection>& GetPlayerSelection() const;/*** 检查是否可以开始英雄选择* @return 是否满足开始条件*/bool CanStartHeroSelection() const;/*** 复制属性接口实现* 声明需要网络复制的属性*/virtual void GetLifetimeReplicatedProps(TArray< FLifetimeProperty > &OutLifetimeProps) const override;private:/*** 玩家选择数组(网络复制)* 存储所有玩家当前的槽位和角色选择信息*/UPROPERTY(ReplicatedUsing = OnRep_PlayerSelectionArray)TArray<FPlayerSelection> PlayerSelectionArray;/*** 玩家选择数组复制回调* 当网络同步玩家选择数据时触发*/UFUNCTION()void OnRep_PlayerSelectionArray();
};
// 幻雨喜欢小猫咪#include "CGameState.h"#include "Net/UnrealNetwork.h"void ACGameState::RequestPlayerSelectionChange(const APlayerState* RequestingPlayer, uint8 DesiredSlot)
{// 仅服务器处理且目标槽位未被占用if (!HasAuthority() || IsSlotOccupied(DesiredSlot))return;// 查找当前玩家的已有选择FPlayerSelection* PlayerSelectionPtr = PlayerSelectionArray.FindByPredicate([&](const FPlayerSelection& PlayerSelection){return PlayerSelection.IsForPlayer(RequestingPlayer);});if (PlayerSelectionPtr){// 更新现有槽位PlayerSelectionPtr->SetSlot(DesiredSlot);}else{// 添加新的玩家选择PlayerSelectionArray.Add(FPlayerSelection(DesiredSlot, RequestingPlayer));}// 广播玩家选择更新OnPlayerSelectionUpdated.Broadcast(PlayerSelectionArray);
}bool ACGameState::IsSlotOccupied(uint8 SlotId) const
{// 寻找已经选择的玩家数组,查看是否有该插槽,如果找到说明给占了for (const FPlayerSelection& PlayerSelection : PlayerSelectionArray){if (PlayerSelection.GetPlayerSlot() == SlotId){return true;}}return false;
}const TArray<FPlayerSelection>& ACGameState::GetPlayerSelection() const
{return PlayerSelectionArray;
}bool ACGameState::CanStartHeroSelection() const
{// 玩家数量和已选择的玩家数量相等return PlayerSelectionArray.Num() == PlayerArray.Num();
}void ACGameState::GetLifetimeReplicatedProps(TArray<FLifetimeProperty>& OutLifetimeProps) const
{Super::GetLifetimeReplicatedProps(OutLifetimeProps);// 将玩家选择数组声明为网络复制属性DOREPLIFETIME_CONDITION_NOTIFY(ACGameState, PlayerSelectionArray, COND_None, REPNOTIFY_Always);
}void ACGameState::OnRep_PlayerSelectionArray()
{// 广播玩家选择更新事件OnPlayerSelectionUpdated.Broadcast(PlayerSelectionArray);
}

玩家选择和大厅连接

大厅玩家控制器中

#pragma once#include "CoreMinimal.h"
#include "MenuPlayerController.h"
#include "LobbyPlayerController.generated.h"/*** */
UCLASS()
class CRUNCH_API ALobbyPlayerController : public AMenuPlayerController
{GENERATED_BODY()
public:/*** 服务器端处理槽位选择变更请求* @param NewSlotID 新的槽位ID* * 网络调用:客户端请求变更槽位选择时调用* 服务器验证后更新玩家槽位*/UFUNCTION(Server, Reliable, WithValidation)void Server_RequestSlotSelectionChange(uint8 NewSlotID);
};
#include "LobbyPlayerController.h"
#include "GameFramework/PlayerState.h"
#include "Framework/CGameState.h"void ALobbyPlayerController::Server_RequestSlotSelectionChange_Implementation(uint8 NewSlotID)
{if (!GetWorld())return;// 获取当前游戏状态ACGameState* CGameState = GetWorld()->GetGameState<ACGameState>();if (!CGameState)return;// 委托给游戏状态管理器处理槽位变更CGameState->RequestPlayerSelectionChange(GetPlayerState<APlayerState>(), NewSlotID);
} bool ALobbyPlayerController::Server_RequestSlotSelectionChange_Validate(uint8 NewSlotID)
{return true;
}

大厅UI

	// 玩家控制器UPROPERTY()TObjectPtr<ALobbyPlayerController> LobbyPlayerController;/*** 配置游戏状态监听* 尝试获取游戏状态对象并绑定更新事件*/void ConfigureGameState();// 配置游戏状态定时器句柄FTimerHandle ConfigureGameStateTimerHandle;// 游戏状态UPROPERTY()TObjectPtr<ACGameState> CGameState;/*** 更新玩家选择显示* @param PlayerSelections 玩家选择信息数组*/void UpdatePlayerSelectionDisplay(const TArray<FPlayerSelection>& PlayerSelections);
void ULobbyWidget::NativeConstruct()
{Super::NativeConstruct();ClearAndPopulateTeamSelectionSlots();// 获取玩家控制器LobbyPlayerController = GetOwningPlayer<ALobbyPlayerController>();// 配置游戏状态ConfigureGameState();
}void ULobbyWidget::SlotSelected(uint8 NewSlotID)
{if (LobbyPlayerController){LobbyPlayerController->Server_RequestSlotSelectionChange(NewSlotID);}
}void ULobbyWidget::ConfigureGameState()
{UWorld* World = GetWorld();if (!World) return;// 获取游戏状态CGameState = World->GetGameState<ACGameState>();if (!CGameState){// 如果没有找到,设置定时器定期重试World->GetTimerManager().SetTimer(ConfigureGameStateTimerHandle, this, &ULobbyWidget::ConfigureGameState, 1.f);}else{// 绑定玩家选择更新事件CGameState->OnPlayerSelectionUpdated.AddUObject(this, &ULobbyWidget::UpdatePlayerSelectionDisplay);// 初始化显示当前玩家选择UpdatePlayerSelectionDisplay(CGameState->GetPlayerSelection());}
}void ULobbyWidget::UpdatePlayerSelectionDisplay(const TArray<FPlayerSelection>& PlayerSelections)
{// 清空所有槽位显示for (UTeamSelectionWidget* SelectionSlot : TeamSelectionSlots){SelectionSlot->UpdateSlotInfo("Empty");}// 更新每个玩家的槽位显示for (const FPlayerSelection& PlayerSelection : PlayerSelections){if (!PlayerSelection.IsValid())continue;// 更新槽位名称显示TeamSelectionSlots[PlayerSelection.GetPlayerSlot()]->UpdateSlotInfo(PlayerSelection.GetPlayerNickName());}
}

创建游戏状态蓝图版本
在这里插入图片描述
游戏模式中设置游戏状态
在这里插入图片描述
在这里插入图片描述
创建玩家状态的蓝图版本
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

http://www.lryc.cn/news/623952.html

相关文章:

  • java设计模式之迪米特法则使用场景分析
  • ​​Vue 3 开发速成手册
  • PHP现代化全栈开发:测试驱动开发与持续交付实践
  • MCP原理与开发及与大模型交互流程
  • 最小路径和
  • 【JAVASE】-9- 接口语法基础
  • Android中切换语言的方法
  • DNS总结
  • 【Linux内核】Linux信号机制
  • linux 常用代码
  • nodejs 错误处理
  • Collections.synchronizedList是如何将List变为线程安全的
  • vs studio 2017项目不支持studio vs2022
  • 【k8s】Kubernetes核心概念与架构详解
  • 从0实现系统设计
  • Leetcode 15 java
  • GitHub Copilot:AI编程助手的架构演进与真实世界影响
  • 浜掕仈缃戝ぇ鍘侸ava姹傝亴鑰呴潰璇曠幇鍦猴細褰撲弗鑲冮潰璇曞畼閬囦笂鎼炵瑧绋嬪簭鍛樿阿椋炴満
  • Conda 环境 在AI 私有化部署 有怎么用?
  • 电力设备状态监测与健康管理:基于多源异构数据融合的技术实现
  • 五、redis入门 之 客户端连接redis
  • 计算机网络 HTTP1.1、HTTP2、HTTP3 的核心对比及性能分析
  • ReactNode 类型
  • Java项目中短信的发送
  • 密码学系列 - 零知识证明(ZKP) - 多种承诺方案
  • Java ConcurrentHashMap 深度解析
  • 【LeetCode 热题 100】(八)二叉树
  • EC11编码器
  • 集成电路学习:什么是SIFT尺度不变特征变换
  • 43 C++ STL模板库12-容器4-容器适配器-堆栈(stack)