├── .gitignore ├── LICENSE ├── MultiplayerSession ├── Content │ └── WBP_Menu.uasset ├── MultiplayerSession.uplugin ├── Resources │ └── Icon128.png └── Source │ └── MultiplayerSession │ ├── MultiplayerSession.Build.cs │ ├── Private │ ├── Menu.cpp │ ├── MultiplayerSession.cpp │ └── MultiplayerSessionSubsystem.cpp │ └── Public │ ├── Menu.h │ ├── MultiplayerSession.h │ └── MultiplayerSessionSubsystem.h └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Abdul Moez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /MultiplayerSession/Content/WBP_Menu.uasset: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Anonym0usWork1221/MultiPlayerSessionPlugin/1d7c80bd99657ec5f45994da2c49c7c4eeeae3af/MultiplayerSession/Content/WBP_Menu.uasset -------------------------------------------------------------------------------- /MultiplayerSession/MultiplayerSession.uplugin: -------------------------------------------------------------------------------- 1 | { 2 | "FileVersion": 3, 3 | "Version": 1, 4 | "VersionName": "1.0", 5 | "FriendlyName": "MultiplayerSession", 6 | "Description": "A plugin for handling online multipler sessions of steam.", 7 | "Category": "Other", 8 | "CreatedBy": "Abdul Moez", 9 | "CreatedByURL": "", 10 | "DocsURL": "", 11 | "MarketplaceURL": "", 12 | "SupportURL": "", 13 | "CanContainContent": true, 14 | "IsBetaVersion": false, 15 | "IsExperimentalVersion": false, 16 | "Installed": false, 17 | "Modules": [ 18 | { 19 | "Name": "MultiplayerSession", 20 | "Type": "Runtime", 21 | "LoadingPhase": "Default" 22 | } 23 | ], 24 | "Plugins": [ 25 | { 26 | "Name": "OnlineSubsystem", 27 | "Enabled": true 28 | }, 29 | { 30 | "Name": "OnlineSubsystemSteam", 31 | "Enabled": true 32 | } 33 | ] 34 | } -------------------------------------------------------------------------------- /MultiplayerSession/Resources/Icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Anonym0usWork1221/MultiPlayerSessionPlugin/1d7c80bd99657ec5f45994da2c49c7c4eeeae3af/MultiplayerSession/Resources/Icon128.png -------------------------------------------------------------------------------- /MultiplayerSession/Source/MultiplayerSession/MultiplayerSession.Build.cs: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | using UnrealBuildTool; 4 | 5 | public class MultiplayerSession : ModuleRules 6 | { 7 | public MultiplayerSession(ReadOnlyTargetRules Target) : base(Target) 8 | { 9 | PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; 10 | 11 | PublicIncludePaths.AddRange( 12 | new string[] { 13 | // ... add public include paths required here ... 14 | } 15 | ); 16 | 17 | 18 | PrivateIncludePaths.AddRange( 19 | new string[] { 20 | // ... add other private include paths required here ... 21 | } 22 | ); 23 | 24 | 25 | PublicDependencyModuleNames.AddRange( 26 | new string[] 27 | { 28 | "Core", 29 | "OnlineSubsystem", 30 | "OnlineSubsystemSteam", 31 | "UMG", 32 | "Slate", 33 | "SlateCore" 34 | // ... add other public dependencies that you statically link with here ... 35 | } 36 | ); 37 | 38 | 39 | PrivateDependencyModuleNames.AddRange( 40 | new string[] 41 | { 42 | "CoreUObject", 43 | "Engine", 44 | "Slate", 45 | "SlateCore", 46 | // ... add private dependencies that you statically link with here ... 47 | } 48 | ); 49 | 50 | 51 | DynamicallyLoadedModuleNames.AddRange( 52 | new string[] 53 | { 54 | // ... add any modules that your module loads dynamically here ... 55 | } 56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /MultiplayerSession/Source/MultiplayerSession/Private/Menu.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | 4 | #include "Menu.h" 5 | #include "Components/Button.h" 6 | #include "MultiplayerSessionSubsystem.h" 7 | #include "OnlineSessionSettings.h" 8 | #include "OnlineSubsystem.h" 9 | 10 | 11 | 12 | void UMenu::MenuSetup(int32 NumOfPublicConnections, FString TypeOfMatch, FString LobbyPath) 13 | { 14 | // Set the private variables 15 | PathToLobby = FString::Printf(TEXT("%s?listen"), *LobbyPath); 16 | NumPublicConnections = NumOfPublicConnections; 17 | MatchType = TypeOfMatch; 18 | 19 | AddToViewport(); 20 | SetVisibility(ESlateVisibility::Visible); 21 | bIsFocusable = true; 22 | 23 | UWorld* World = GetWorld(); 24 | if (World) { 25 | APlayerController* PlayerController = World->GetFirstPlayerController(); 26 | if (PlayerController) { 27 | FInputModeUIOnly InputModeData; 28 | InputModeData.SetWidgetToFocus(TakeWidget()); 29 | InputModeData.SetLockMouseToViewportBehavior(EMouseLockMode::DoNotLock); 30 | PlayerController->SetInputMode(InputModeData); 31 | PlayerController->SetShowMouseCursor(true); 32 | } 33 | } 34 | 35 | UGameInstance* GameInstance = GetGameInstance(); 36 | if (GameInstance) { 37 | MultiplayerSessionSubsystem = GameInstance->GetSubsystem(); 38 | 39 | } 40 | 41 | // bind callback 42 | if (MultiplayerSessionSubsystem) { 43 | MultiplayerSessionSubsystem->MultiPlayerOnCreateSessionComplete.AddDynamic(this, &ThisClass::OnCreateSession); // UFunction 44 | MultiplayerSessionSubsystem->MultiPlayerOnFindSessionsComplete.AddUObject(this, &ThisClass::OnFindSessions); // Not UFunction 45 | MultiplayerSessionSubsystem->MultiPlayerOnJoinSessionComplete.AddUObject(this, &ThisClass::OnJoinSession); 46 | MultiplayerSessionSubsystem->MultiPlayerOnDestroySessionComplete.AddDynamic(this, &ThisClass::OnDestroySession); 47 | MultiplayerSessionSubsystem->MultiPlayerOnStartSessionComplete.AddDynamic(this, &ThisClass::OnStartSession); 48 | 49 | } 50 | } 51 | 52 | bool UMenu::Initialize() 53 | { 54 | if (!Super::Initialize()) 55 | return false; 56 | 57 | // bind out callabacks to our buttons 58 | if (HostButton) 59 | HostButton->OnClicked.AddDynamic(this, &UMenu::HostButtonClicked); 60 | 61 | if (JoinButton) 62 | JoinButton->OnClicked.AddDynamic(this, &UMenu::JoinButtonClicked); 63 | 64 | return true; 65 | } 66 | 67 | void UMenu::OnLevelRemovedFromWorld(ULevel* InLevel, UWorld* InWorld) 68 | { 69 | UMenu::MenuTearDown(); 70 | Super::OnLevelRemovedFromWorld(InLevel, InWorld); 71 | } 72 | 73 | void UMenu::OnCreateSession(bool bWasSuccessful) 74 | { 75 | if (bWasSuccessful) { 76 | if (GEngine) { 77 | GEngine->AddOnScreenDebugMessage( 78 | -1, 79 | 15.f, 80 | FColor::Yellow, 81 | FString(TEXT("Session Created successful!")) 82 | ); 83 | } 84 | UWorld* World = GetWorld(); 85 | if (World) 86 | World->ServerTravel(PathToLobby); 87 | } 88 | else 89 | { 90 | if (GEngine) { 91 | GEngine->AddOnScreenDebugMessage( 92 | -1, 93 | 15.f, 94 | FColor::Red, 95 | FString(TEXT("Failed to Created Session!")) 96 | ); 97 | } 98 | HostButton->SetIsEnabled(true); 99 | } 100 | } 101 | 102 | void UMenu::OnFindSessions(const TArray& SessionResults, bool bWasSuccessful) 103 | { 104 | if (GEngine) { 105 | GEngine->AddOnScreenDebugMessage( 106 | -1, 107 | 15.f, 108 | FColor::Orange, 109 | FString::Printf(TEXT("Total Results: %d, %d"), SessionResults.Num(), bWasSuccessful) 110 | ); 111 | } 112 | if (MultiplayerSessionSubsystem == nullptr) 113 | { 114 | return; 115 | } 116 | 117 | for (auto Result : SessionResults) 118 | { 119 | FString SettingsValue; 120 | Result.Session.SessionSettings.Get(FName("MatchType"), SettingsValue); 121 | if (SettingsValue == MatchType) 122 | { 123 | MultiplayerSessionSubsystem->JoinSession(Result); 124 | return; 125 | } 126 | } 127 | if (!bWasSuccessful || SessionResults.Num() == 0) { 128 | JoinButton->SetIsEnabled(true); 129 | } 130 | } 131 | 132 | void UMenu::OnJoinSession(EOnJoinSessionCompleteResult::Type Result) 133 | { 134 | IOnlineSubsystem* Subsystem = IOnlineSubsystem::Get(); 135 | if (Subsystem) 136 | { 137 | IOnlineSessionPtr SessionInterface = Subsystem->GetSessionInterface(); 138 | if (SessionInterface.IsValid()) 139 | { 140 | FString Address; 141 | SessionInterface->GetResolvedConnectString(NAME_GameSession, Address); 142 | 143 | APlayerController* PlayerController = GetGameInstance()->GetFirstLocalPlayerController(); 144 | if (PlayerController) 145 | { 146 | PlayerController->ClientTravel(Address, ETravelType::TRAVEL_Absolute); 147 | } 148 | } 149 | } 150 | if (Result != EOnJoinSessionCompleteResult::Success) { 151 | JoinButton->SetIsEnabled(true); 152 | } 153 | } 154 | 155 | void UMenu::OnDestroySession(bool bWasSuccessful) 156 | { 157 | } 158 | 159 | void UMenu::OnStartSession(bool bWasSuccessful) 160 | { 161 | } 162 | 163 | void UMenu::HostButtonClicked() 164 | { 165 | HostButton->SetIsEnabled(false); 166 | if (MultiplayerSessionSubsystem) { 167 | MultiplayerSessionSubsystem->CreateSession(NumPublicConnections, MatchType); 168 | } 169 | } 170 | 171 | void UMenu::JoinButtonClicked() 172 | { 173 | JoinButton->SetIsEnabled(false); 174 | if (MultiplayerSessionSubsystem) { 175 | if (GEngine) { 176 | GEngine->AddOnScreenDebugMessage( 177 | -1, 178 | 15.f, 179 | FColor::Green, 180 | FString(TEXT("Join Button Clicked!")) 181 | ); 182 | } 183 | 184 | MultiplayerSessionSubsystem->FindSessions(10000); 185 | } 186 | } 187 | 188 | void UMenu::MenuTearDown() 189 | { 190 | RemoveFromParent(); // remove menu widget so we can move 191 | UWorld* World = GetWorld(); 192 | if (World) { 193 | APlayerController* PlayerController = World->GetFirstPlayerController(); 194 | if (PlayerController) { 195 | FInputModeGameOnly InputModeData; 196 | PlayerController->SetInputMode(InputModeData); 197 | PlayerController->SetShowMouseCursor(false); 198 | } 199 | 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /MultiplayerSession/Source/MultiplayerSession/Private/MultiplayerSession.cpp: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #include "MultiplayerSession.h" 4 | 5 | #define LOCTEXT_NAMESPACE "FMultiplayerSessionModule" 6 | 7 | void FMultiplayerSessionModule::StartupModule() 8 | { 9 | // This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module 10 | } 11 | 12 | void FMultiplayerSessionModule::ShutdownModule() 13 | { 14 | // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, 15 | // we call this function before unloading the module. 16 | } 17 | 18 | #undef LOCTEXT_NAMESPACE 19 | 20 | IMPLEMENT_MODULE(FMultiplayerSessionModule, MultiplayerSession) -------------------------------------------------------------------------------- /MultiplayerSession/Source/MultiplayerSession/Private/MultiplayerSessionSubsystem.cpp: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | 4 | #include "MultiplayerSessionSubsystem.h" 5 | #include "OnlineSubsystem.h" // IOnlineSubsystem 6 | #include "OnlineSessionSettings.h" // FOnlineSessionSettings 7 | 8 | UMultiplayerSessionSubsystem::UMultiplayerSessionSubsystem(): 9 | // Binding the delegates with the callback functions. 10 | CreateSessionCompleteDelegate(FOnCreateSessionCompleteDelegate::CreateUObject(this, &ThisClass::OnCreateSessionComplete)), 11 | FindSessionsCompleteDelegate(FOnFindSessionsCompleteDelegate::CreateUObject(this, &ThisClass::OnFindSessionsComplete)), 12 | JoinSessionCompleteDelegate(FOnJoinSessionCompleteDelegate::CreateUObject(this, &ThisClass::OnJoinSessionComplete)), 13 | DestroySessionCompleteDelegate(FOnDestroySessionCompleteDelegate::CreateUObject(this, &ThisClass::OnDestroySessionComplete)), 14 | StartSessionCompleteDelegate(FOnStartSessionCompleteDelegate::CreateUObject(this, &ThisClass::OnStartSessionComplete)) 15 | { 16 | IOnlineSubsystem* Subsystem = IOnlineSubsystem::Get(); 17 | if (Subsystem) { 18 | SessionInterface = Subsystem->GetSessionInterface(); 19 | } 20 | } 21 | 22 | void UMultiplayerSessionSubsystem::CreateSession(int32 NumPublicConnections, FString MatchType) 23 | { 24 | if (!SessionInterface.IsValid()) 25 | return; 26 | auto ExistingSession = SessionInterface->GetNamedSession(NAME_GameSession); 27 | if (ExistingSession != nullptr) { 28 | bCreateSessionOnDestroy = true; 29 | LastNumPublicConnection = NumPublicConnections; 30 | LastMatchType = MatchType; 31 | DestroySession(); 32 | } 33 | 34 | // Store the delegate in a FDelegateHandle so we can later remove it from the delegate list. 35 | CreateSessionCompleteDelegateHandle = SessionInterface->AddOnCreateSessionCompleteDelegate_Handle(CreateSessionCompleteDelegate); 36 | 37 | // Setting up session settings 38 | LastSessionSettings = MakeShareable(new FOnlineSessionSettings()); 39 | // setting up session settings properties (conditions ? if true: else) 40 | LastSessionSettings->bIsLANMatch = IOnlineSubsystem::Get()->GetSubsystemName() == "NULL" ? true : false; 41 | LastSessionSettings->NumPublicConnections = NumPublicConnections; 42 | LastSessionSettings->bAllowJoinInProgress = true; 43 | LastSessionSettings->bAllowJoinViaPresence = true; 44 | LastSessionSettings->bShouldAdvertise = true; 45 | LastSessionSettings->bUsesPresence = true; 46 | LastSessionSettings->bUseLobbiesIfAvailable = true; 47 | LastSessionSettings->Set(FName("MatchType"), MatchType, EOnlineDataAdvertisementType::ViaOnlineServiceAndPing); 48 | LastSessionSettings->BuildUniqueId = 1; 49 | 50 | const ULocalPlayer* LocalPlayer = GetWorld()->GetFirstLocalPlayerFromController(); 51 | if (!SessionInterface->CreateSession(*LocalPlayer->GetPreferredUniqueNetId(), NAME_GameSession, *LastSessionSettings)) { 52 | // pass the delegate we used while creating it inorder to clear it if the session is not created 53 | SessionInterface->ClearOnCreateSessionCompleteDelegate_Handle(CreateSessionCompleteDelegateHandle); 54 | 55 | // Broadcast our own custom delegate 56 | MultiPlayerOnCreateSessionComplete.Broadcast(false); 57 | } 58 | 59 | } 60 | 61 | void UMultiplayerSessionSubsystem::FindSessions(int32 MaxSearchResults) 62 | { 63 | if (!SessionInterface.IsValid()) 64 | { 65 | return; 66 | } 67 | 68 | FindSessionsCompleteDelegateHandle = SessionInterface->AddOnFindSessionsCompleteDelegate_Handle(FindSessionsCompleteDelegate); 69 | 70 | LastSessionSearch = MakeShareable(new FOnlineSessionSearch()); 71 | LastSessionSearch->MaxSearchResults = MaxSearchResults; 72 | LastSessionSearch->bIsLanQuery = IOnlineSubsystem::Get()->GetSubsystemName() == "NULL" ? true : false; 73 | LastSessionSearch->QuerySettings.Set(SEARCH_PRESENCE, true, EOnlineComparisonOp::Equals); 74 | 75 | const ULocalPlayer* LocalPlayer = GetWorld()->GetFirstLocalPlayerFromController(); 76 | if (!SessionInterface->FindSessions(*LocalPlayer->GetPreferredUniqueNetId(), LastSessionSearch.ToSharedRef())) 77 | { 78 | SessionInterface->ClearOnFindSessionsCompleteDelegate_Handle(FindSessionsCompleteDelegateHandle); 79 | 80 | MultiPlayerOnFindSessionsComplete.Broadcast(TArray(), false); 81 | } 82 | } 83 | 84 | void UMultiplayerSessionSubsystem::JoinSession(const FOnlineSessionSearchResult& SessionResult) 85 | { 86 | if (!SessionInterface.IsValid()) 87 | { 88 | MultiPlayerOnJoinSessionComplete.Broadcast(EOnJoinSessionCompleteResult::UnknownError); 89 | return; 90 | } 91 | 92 | JoinSessionCompleteDelegateHandle = SessionInterface->AddOnJoinSessionCompleteDelegate_Handle(JoinSessionCompleteDelegate); 93 | 94 | const ULocalPlayer* LocalPlayer = GetWorld()->GetFirstLocalPlayerFromController(); 95 | if (!SessionInterface->JoinSession(*LocalPlayer->GetPreferredUniqueNetId(), NAME_GameSession, SessionResult)) 96 | { 97 | SessionInterface->ClearOnJoinSessionCompleteDelegate_Handle(JoinSessionCompleteDelegateHandle); 98 | 99 | MultiPlayerOnJoinSessionComplete.Broadcast(EOnJoinSessionCompleteResult::UnknownError); 100 | } 101 | } 102 | 103 | void UMultiplayerSessionSubsystem::DestroySession() 104 | { 105 | if (!SessionInterface.IsValid()) { 106 | MultiPlayerOnDestroySessionComplete.Broadcast(false); 107 | return; 108 | } 109 | 110 | DestroySessionCompleteDelegateHandle = SessionInterface->AddOnDestroySessionCompleteDelegate_Handle(DestroySessionCompleteDelegate); 111 | if (!SessionInterface->DestroySession(NAME_GameSession)) { 112 | SessionInterface->ClearOnDestroySessionCompleteDelegate_Handle(DestroySessionCompleteDelegateHandle); 113 | MultiPlayerOnDestroySessionComplete.Broadcast(false); 114 | } 115 | } 116 | 117 | void UMultiplayerSessionSubsystem::StartSession() 118 | { 119 | } 120 | 121 | void UMultiplayerSessionSubsystem::OnCreateSessionComplete(FName SessionName, bool bWasSuccessful) 122 | { 123 | if (SessionInterface) 124 | SessionInterface->ClearOnCreateSessionCompleteDelegate_Handle(CreateSessionCompleteDelegateHandle); 125 | MultiPlayerOnCreateSessionComplete.Broadcast(bWasSuccessful); 126 | } 127 | 128 | void UMultiplayerSessionSubsystem::OnFindSessionsComplete(bool bWasSuccessful) 129 | { 130 | if (SessionInterface) 131 | { 132 | SessionInterface->ClearOnFindSessionsCompleteDelegate_Handle(FindSessionsCompleteDelegateHandle); 133 | } 134 | 135 | if (LastSessionSearch->SearchResults.Num() <= 0) 136 | { 137 | MultiPlayerOnFindSessionsComplete.Broadcast(TArray(), false); 138 | return; 139 | } 140 | 141 | MultiPlayerOnFindSessionsComplete.Broadcast(LastSessionSearch->SearchResults, bWasSuccessful); 142 | } 143 | 144 | void UMultiplayerSessionSubsystem::OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result) 145 | { 146 | if (SessionInterface) 147 | { 148 | SessionInterface->ClearOnJoinSessionCompleteDelegate_Handle(JoinSessionCompleteDelegateHandle); 149 | } 150 | 151 | MultiPlayerOnJoinSessionComplete.Broadcast(Result); 152 | } 153 | 154 | void UMultiplayerSessionSubsystem::OnDestroySessionComplete(FName SessionName, bool bWasSuccessful) 155 | { 156 | if (SessionInterface) { 157 | SessionInterface->ClearOnDestroySessionCompleteDelegate_Handle(DestroySessionCompleteDelegateHandle); 158 | } 159 | if (bWasSuccessful && bCreateSessionOnDestroy) { 160 | // if user want to create a session but it was already created then destroy and create a new one 161 | bCreateSessionOnDestroy = false; 162 | CreateSession(LastNumPublicConnection, LastMatchType); 163 | } 164 | MultiPlayerOnDestroySessionComplete.Broadcast(bWasSuccessful); 165 | } 166 | 167 | void UMultiplayerSessionSubsystem::OnStartSessionComplete(FName SessionName, bool bWasSuccessful) 168 | { 169 | } 170 | -------------------------------------------------------------------------------- /MultiplayerSession/Source/MultiplayerSession/Public/Menu.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Blueprint/UserWidget.h" 7 | #include "interfaces/OnlineSessionInterface.h" 8 | #include "Menu.generated.h" 9 | 10 | /** 11 | * 12 | */ 13 | UCLASS() 14 | class MULTIPLAYERSESSION_API UMenu : public UUserWidget 15 | { 16 | GENERATED_BODY() 17 | public: 18 | UFUNCTION(BlueprintCallable) 19 | void MenuSetup( 20 | int32 NumOfPublicConnections = 4, 21 | FString TypeOfMatch = FString(TEXT("FreeForAll")), 22 | FString LobbyPath = FString(TEXT("/Game/ThirdPerson/Maps/Lobby")) 23 | ); 24 | 25 | protected: 26 | 27 | virtual bool Initialize() override; 28 | virtual void OnLevelRemovedFromWorld(ULevel* InLevel, UWorld* InWorld) override; 29 | 30 | // 31 | // Callbacks for the custom delegates on the MultiPlayerSessionSubsystem 32 | // 33 | UFUNCTION() 34 | void OnCreateSession(bool bWasSuccessful); 35 | void OnFindSessions(const TArray& SessionResults, bool bWasSuccessful); 36 | void OnJoinSession(EOnJoinSessionCompleteResult::Type Result); 37 | 38 | UFUNCTION() 39 | void OnDestroySession(bool bWasSuccessful); 40 | UFUNCTION() 41 | void OnStartSession(bool bWasSuccessful); 42 | 43 | private: 44 | // Buttons 45 | UPROPERTY(meta = (BindWidget)) 46 | class UButton* HostButton; // Need to be same name as widget blueprint. Else we get crashes in Unreal Engine. 47 | 48 | UPROPERTY(meta = (BindWidget)) 49 | class UButton* JoinButton; // Need to be same name as widget blueprint. Else we get crashes in Unreal Engine. 50 | 51 | // Button callbacks 52 | UFUNCTION() 53 | void HostButtonClicked(); 54 | 55 | UFUNCTION() 56 | void JoinButtonClicked(); 57 | 58 | void MenuTearDown(); 59 | 60 | // The subsystem designed to handle all online session functionality 61 | class UMultiplayerSessionSubsystem* MultiplayerSessionSubsystem; 62 | 63 | int32 NumPublicConnections{4}; 64 | FString MatchType{ TEXT("FreeForAll") }; 65 | FString PathToLobby{ TEXT("") }; 66 | 67 | }; 68 | -------------------------------------------------------------------------------- /MultiplayerSession/Source/MultiplayerSession/Public/MultiplayerSession.h: -------------------------------------------------------------------------------- 1 | // Copyright Epic Games, Inc. All Rights Reserved. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Modules/ModuleManager.h" 7 | 8 | class FMultiplayerSessionModule : public IModuleInterface 9 | { 10 | public: 11 | 12 | /** IModuleInterface implementation */ 13 | virtual void StartupModule() override; 14 | virtual void ShutdownModule() override; 15 | }; 16 | -------------------------------------------------------------------------------- /MultiplayerSession/Source/MultiplayerSession/Public/MultiplayerSessionSubsystem.h: -------------------------------------------------------------------------------- 1 | // Fill out your copyright notice in the Description page of Project Settings. 2 | 3 | #pragma once 4 | 5 | #include "CoreMinimal.h" 6 | #include "Subsystems/GameInstanceSubsystem.h" 7 | #include "Interfaces/OnlineSessionInterface.h" 8 | #include "MultiplayerSessionSubsystem.generated.h" 9 | 10 | // 11 | // Declaring our own custom delegates for the Menu Class to bind calbacks to. 12 | // 13 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMultiplayerOnCreateSessionComplete, bool, bWasSuccessful); 14 | DECLARE_MULTICAST_DELEGATE_TwoParams(FMultiplayerOnFindSessionsComplete, const TArray& SessionResults, bool bWasSuccessful); 15 | DECLARE_MULTICAST_DELEGATE_OneParam(FMultiplayerOnJoinSessionComplete, EOnJoinSessionCompleteResult::Type Result); 16 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMultiplayerOnDestroySessionComplete, bool, bWasSuccessful); 17 | DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FMultiplayerOnStartSessionComplete, bool, bWasSuccessful); 18 | 19 | 20 | /** 21 | * 22 | */ 23 | UCLASS() 24 | class MULTIPLAYERSESSION_API UMultiplayerSessionSubsystem : public UGameInstanceSubsystem 25 | { 26 | GENERATED_BODY() 27 | 28 | public: 29 | UMultiplayerSessionSubsystem(); 30 | 31 | // 32 | // To handle session functionality. The Menu class will call these 33 | // 34 | void CreateSession(int32 NumPublicConnections, FString MatchType); 35 | void FindSessions(int32 MaxSearchResults); 36 | void JoinSession(const FOnlineSessionSearchResult& SessionResult); 37 | void DestroySession(); 38 | void StartSession(); 39 | 40 | // 41 | // Our own custom delegates for the Menu class to bind callbacks to. 42 | // 43 | FMultiplayerOnCreateSessionComplete MultiPlayerOnCreateSessionComplete; 44 | FMultiplayerOnFindSessionsComplete MultiPlayerOnFindSessionsComplete; 45 | FMultiplayerOnJoinSessionComplete MultiPlayerOnJoinSessionComplete; 46 | FMultiplayerOnDestroySessionComplete MultiPlayerOnDestroySessionComplete; 47 | FMultiplayerOnStartSessionComplete MultiPlayerOnStartSessionComplete; 48 | 49 | protected: 50 | 51 | // 52 | // Internal Callbacks for the delegates we'll add to the online session interface delegate list. 53 | // These don't need to be called outside this class. 54 | // 55 | void OnCreateSessionComplete(FName SessionName, bool bWasSuccessful); 56 | void OnFindSessionsComplete(bool bWasSuccessful); 57 | void OnJoinSessionComplete(FName SessionName, EOnJoinSessionCompleteResult::Type Result); 58 | void OnDestroySessionComplete(FName SessionName, bool bWasSuccessful); 59 | void OnStartSessionComplete(FName SessionName, bool bWasSuccessful); 60 | 61 | private: 62 | IOnlineSessionPtr SessionInterface; 63 | TSharedPtr LastSessionSettings; 64 | TSharedPtr LastSessionSearch; 65 | 66 | // 67 | // To add to the online session interface delegate list. 68 | // We'll bind our MultiplayerSessionSubsystem internal callbacks to these. 69 | // 70 | FOnCreateSessionCompleteDelegate CreateSessionCompleteDelegate; // Delegate 71 | FDelegateHandle CreateSessionCompleteDelegateHandle; // Delegate handle 72 | 73 | FOnFindSessionsCompleteDelegate FindSessionsCompleteDelegate; 74 | FDelegateHandle FindSessionsCompleteDelegateHandle; 75 | 76 | FOnJoinSessionCompleteDelegate JoinSessionCompleteDelegate; 77 | FDelegateHandle JoinSessionCompleteDelegateHandle; 78 | 79 | FOnDestroySessionCompleteDelegate DestroySessionCompleteDelegate; 80 | FDelegateHandle DestroySessionCompleteDelegateHandle; 81 | 82 | FOnStartSessionCompleteDelegate StartSessionCompleteDelegate; 83 | FDelegateHandle StartSessionCompleteDelegateHandle; 84 | 85 | bool bCreateSessionOnDestroy{ false }; 86 | int32 LastNumPublicConnection; 87 | FString LastMatchType; 88 | 89 | }; 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | MultiPlayerSessionPlugin 2 | ----------------------- 3 | 4 | **MultiPlayerSessionPlugin**: A C++ multiplayer Steam plugin designed to simplify your development workflow. 5 | 6 | - **Date**: 2023/09/30 7 | - **Author**: Abdul Moez 8 | - **Version**: 0.1.0 (Initial Release) 9 | - **Study**: Undergraduate at GCU Lahore, Pakistan 10 | 11 | Supported Engine Version 12 | ------------------------ 13 | 14 | - Unreal Engines: 5.0–5.0.3 15 | - Works exclusively on the Steam platform 16 | 17 | Documentation 18 | ------------- 19 | 20 | **Step 1:** Create a new project. In the plugin section (under Edit > Plugins), search for `Online Subsystem Steam` and enable it. Then, close the Unreal Engine editor. 21 | 22 | **Step 2:** Copy the `MultiplayerSession` folder from this repository to the `Plugins` folder in your project directory. If the plugin folder is not available, create it manually. 23 | 24 | **Step 3:** Open the `DefaultEngine.ini` file inside the `Config` folder in the project's root directory. Paste the following at the end of the file. You can change `SteamDevAppId` to your personal dev ID. The current ID is a test ID provided by Steam. 25 | ```ini 26 | [/Script/Engine.GameEngine] 27 | +NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="OnlineSubsystemSteam.SteamNetDriver",DriverClassNameFallback="OnlineSubsystemUtils.IpNetDriver") 28 | 29 | [OnlineSubsystem] 30 | DefaultPlatformService=Steam 31 | 32 | [OnlineSubsystemSteam] 33 | bEnabled=true 34 | SteamDevAppId=480 35 | bInitServerOnClient=true 36 | 37 | [/Script/OnlineSubsystemSteam.SteamNetDriver] 38 | NetConnectionClassName="OnlineSubsystemSteam.SteamNetConnection" 39 | ``` 40 | Then open `DefaultGame.ini` file and paste the following at the end of the file. Here you will specify max number of players. 41 | ```ini 42 | [/Script/Engine.GameSession] 43 | MaxPlayers=100 44 | ``` 45 | 46 | **Step 4:** Delete the following folders from the root directory: `Binaries`, `Intermediate`, and `Saved`. Right-click on your project file (ending with the extension `.uproject`) and select `Generate Visual Studio project files`. 47 | 48 | **Step 5:** Open the project by clicking on the `.uproject` file. It will prompt you to build the files; click OK. 49 | 50 | **Step 6:** Open the `Open Level Blueprint`. On `EventBeginPlay`, set an action by right-clicking and selecting `Create Widget`. For the class parameter of the created widget, select `WBP_Menu`. On Return Value, search for the `Menu Setup` function and select it. Here, you can set three parameters: 51 | 52 | * `Num Of Public Connections`: self-explanatory. 53 | * `Type Of Match`: ensure it is unique for different matches. 54 | * `Lobby Path`: the path of the level where the game begins. Pass `/Game/` up to the Content folder, and then the next path to the Lobby map (e.g., `/Game/Maps/Lobby`). 55 | 56 | **Step 7:** Compile the game and run it on different systems. Ensure Steam is running in the background. 57 | 58 | ----------- 59 | 60 | # Contributor 61 | 62 | 63 | 64 | 65 | 66 | ----------- 67 | Support and Contact Information 68 | ---------- 69 | > If you require any assistance or have questions, please feel free to reach out to me through the following channels: 70 | * **Email**: `abdulmoez123456789@gmail.com` 71 | 72 | > I have also established a dedicated Discord group for more interactive communication: 73 | * **Discord Server**: `https://discord.gg/RMNcqzmt9f` 74 | 75 | 76 | ----------- 77 | 78 | Buy Me a coffee 79 | -------------- 80 | __If you'd like to show your support and appreciation for my work, you can buy me a coffee using the 81 | following payment option:__ 82 | 83 | **Payoneer**: `abdulmoez123456789@gmail.com` 84 | 85 | > Your support is greatly appreciated and helps me continue providing valuable assistance and resources. 86 | Thank you for your consideration. 87 | 88 | 89 | --------------------------------------------------------------------------------