diff --git a/.vsconfig b/.vsconfig index 7f5f4b38..f2f6477a 100644 --- a/.vsconfig +++ b/.vsconfig @@ -7,6 +7,7 @@ "Microsoft.VisualStudio.Component.Windows10SDK.22621", "Microsoft.VisualStudio.Workload.CoreEditor", "Microsoft.VisualStudio.Workload.ManagedDesktop", + "Microsoft.VisualStudio.Workload.NativeCrossPlat", "Microsoft.VisualStudio.Workload.NativeDesktop", "Microsoft.VisualStudio.Workload.NativeGame" ] diff --git a/Config/DefaultEngine.ini b/Config/DefaultEngine.ini index 7a1cb99a..d428a262 100644 --- a/Config/DefaultEngine.ini +++ b/Config/DefaultEngine.ini @@ -4,6 +4,11 @@ GameName=OdinUnrealSample [/Script/Engine.Engine] +ActiveClassRedirects=(OldClassName="/Script/PhotonDemoParticle.PhotonLBClient",NewClassName="/Script/OdinUnrealSample.PhotonLBClient") +[/Script/Engine.Engine] +!NetDriverDefinitions=ClearArray ++NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="/Script/OnlineSubsystemEOS.NetDriverEOS",DriverClassNameFallback="OnlineSubsystemUtils.IpNetDriver") ++NetDriverDefinitions=(DefName="DemoNetDriver",DriverClassName="/Script/Engine.DemoNetDriver",DriverClassNameFallback="/Script/Engine.DemoNetDriver") + [/Script/EngineSettings.GameMapsSettings] EditorStartupMap=/Game/Maps/Lobby.Lobby TransitionMap= @@ -290,35 +295,47 @@ ManualIPAddress= [/Script/OnlineSubsystemEOS.EOSSettings] CacheDir=CacheDir DefaultArtifactName=OdinTechDemo +RTCBackgroundMode= TickBudgetInMilliseconds=0 bEnableOverlay=True bEnableSocialOverlay=False bEnableEditorOverlay=True -bUseLauncherChecks=true +bPreferPersistentAuth=False TitleStorageReadChunkLength=0 -+Artifacts=(ArtifactName="OdinTechDemo",ClientId="xyza7891sGny9XgwwoemBhEtcdqBLJzF",ClientSecret="PuhTi6v+f1CJZ4U6ciXomR5mqszjvMtIaJZtUb3MFvc",ProductId="d32c08ed3f8748ed9acd524f76a0984f",SandboxId="28c4620348f946829cfa7dd482ea5b2b",DeploymentId="d3c8d8c7ed0d4d87ab7a50c2a0094c45",ClientEncryptionKey="ab7adf02eb4473d8ee6296275acc4ef426492705021963df282ef37915abe8b9") ++Artifacts=(ArtifactName="OdinTechDemo",ClientId="xyza7891Cuze2kv36y1wIGenT7FgzWma",ClientSecret="Ey5yR1Fsy49Z/U5uPhUVtpu4HwxRKito24mGLiEWVPA",ProductId="2082b359b5594412b2e9730a2e1ce26d",SandboxId="ce2a6d01a5124712b6b6de49f84631ed",DeploymentId="3f586ca0c3e340f69a53d7d0b08f08cf",ClientEncryptionKey="ab7adf02eb4473d8ee6296275acc4ef426492705021963df282ef37915abe8b9") -AuthScopeFlags=BasicProfile -AuthScopeFlags=FriendsList -AuthScopeFlags=Presence -bUseEAS=False -bUseEOSConnect=False +bUseEAS=True +bUseEOSConnect=True bMirrorStatsToEOS=False bMirrorAchievementsToEOS=False bUseEOSSessions=True bMirrorPresenceToEAS=False +SteamTokenType=Session [OnlineSubsystemEOS] bEnabled=true [OnlineSubsystem] -DefaultPlatformService=Null +DefaultPlatformService=EOS [/Script/Engine.GameEngine] - +NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="OnlineSubsystemEOS.NetDriverEOS",DriverClassNameFallback="OnlineSubsystemUtils.IpNetDriver") +!NetDriverDefinitions=ClearArray + +NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="OnlineSubsystemEOS.NetDriverEOS",DriverClassNameFallback="OnlineSubsystemUtils.IpNetDriver") + +[/Script/OnlineSubsystemEOS.NetDriverEOSBase] +bIsUsingP2PSockets=true [/Script/OnlineSubsystemEOS.NetDriverEOS] bIsUsingP2PSockets=true +[/Script/SocketSubsystemEOS.NetDriverEOS] +bIsUsingP2PSockets=true + +[/Script/SocketSubsystemEOS.NetDriverEOSBase] +bIsUsingP2PSockets=true + [/Script/LinuxTargetPlatform.LinuxTargetSettings] OcclusionPlugin=Built-in Occlusion diff --git a/Content/Blueprints/Game/GameInstance_TD.uasset b/Content/Blueprints/Game/GameInstance_TD.uasset index 122223d2..6aaeb477 100644 Binary files a/Content/Blueprints/Game/GameInstance_TD.uasset and b/Content/Blueprints/Game/GameInstance_TD.uasset differ diff --git a/Content/Blueprints/Game/TopDownController.uasset b/Content/Blueprints/Game/TopDownController.uasset index 6b0bdbbc..e98a15fd 100644 Binary files a/Content/Blueprints/Game/TopDownController.uasset and b/Content/Blueprints/Game/TopDownController.uasset differ diff --git a/Content/Blueprints/Lobby/LobbyGameMode.uasset b/Content/Blueprints/Lobby/LobbyGameMode.uasset index 72a5e7f3..db0ff1c6 100644 Binary files a/Content/Blueprints/Lobby/LobbyGameMode.uasset and b/Content/Blueprints/Lobby/LobbyGameMode.uasset differ diff --git a/Content/Blueprints/Odin/OdinClientComponent.uasset b/Content/Blueprints/Odin/OdinClientComponent.uasset index cbfeec0b..614212ef 100644 Binary files a/Content/Blueprints/Odin/OdinClientComponent.uasset and b/Content/Blueprints/Odin/OdinClientComponent.uasset differ diff --git a/Content/Blueprints/Widgets/LobbyMenu.uasset b/Content/Blueprints/Widgets/LobbyMenu.uasset index 109757d5..2a8b6feb 100644 Binary files a/Content/Blueprints/Widgets/LobbyMenu.uasset and b/Content/Blueprints/Widgets/LobbyMenu.uasset differ diff --git a/Readme.md b/Readme.md index 8bdb5410..6c3df277 100644 --- a/Readme.md +++ b/Readme.md @@ -19,12 +19,25 @@ The Demo showcases the usage of ODIN together with Unreal's Audio Engine. The ex ## Getting Started -In the releases you can find a pre-built game executable and the current project's code base. The pre-built game can likely be executed without any installations required - if you do not have the redistributable packages of C++ installed, that Unreal needs, you can find it in the `\Engine\Extras\Redist\en-us`folder of the pre-built game. +In the releases you can find a pre-built game executable and the current project's code base. The pre-built game can likely be executed without any installations required - if you do not have the redistributable packages of C++ installed, that Unreal needs, you can find it in the `\Engine\Extras\Redist\en-us` folder of the pre-built game. Per platform there are two builds available in a release with different Online Subsystems active, changing how clients connect to each other: +1. EOS Online Subsystem: This uses EOS for matchmaking. This version of the demo game needs the user to login to their Epic Games Account and allow the Demo Game to access basic information (user name and online presence). +1. No Online Subsystem (also called "NULL"): This uses matchmaking over a local network. No account needed for this version but you will only be able to find other clients in the same local network. -To open the project in the Unreal Editor you need to install the Unreal Engine 5.3 or higher. You can open the source code with the corresponding version of the branch easily. If you need to open it with a higher version of the Unreal Engine you can right-click the `OdinUnrealSample.uproject` file and `Switch Unreal Engine version ...` to the Engine version you have installed that you want the project to open with. If the Editor fails to launch, rebuild the project from Visual Studio. You might also need to exchange the Odin Plugin to the corresponding version, downloadable e.g. in its [Github Repository](https://github.com/4Players/odin-sdk-unreal/releases). +To open the project in the Unreal Editor you need to install the Unreal Engine 5.4 or higher. You can open the source code with the corresponding version of the branch easily. If you need to open it with a higher version of the Unreal Engine you can right-click the `OdinUnrealSample.uproject` file and `Switch Unreal Engine version ...` to the Engine version you have installed that you want the project to open with. If the Editor fails to launch, rebuild the project from Visual Studio. You might also need to exchange the Odin Plugin to the corresponding version, downloadable e.g. in its [Github Repository](https://github.com/4Players/odin-sdk-unreal/releases). :warning: In UE5.4 currently there is a bug, which causes UnfocusedVolumeMultiplier to not apply to the Odin Synth - so testing is best done on 2 devices instead of starting two clients in the editor process. +To change the Online Subsystem in the Unreal project open the `Config\DefaultEngine.ini` and change the lines +``` +[OnlineSubsystem] +DefaultPlatformService=EOS +``` +to +``` +[OnlineSubsystem] +DefaultPlatformService=NULL +``` + ### Installing Visual Studio and Compiling the Project As stated above, sometimes it is necessary to compile from Visual Studio: diff --git a/Source/OdinUnrealSample/OdinUnrealSample.Build.cs b/Source/OdinUnrealSample/OdinUnrealSample.Build.cs index 4ef8630b..45fc4a25 100644 --- a/Source/OdinUnrealSample/OdinUnrealSample.Build.cs +++ b/Source/OdinUnrealSample/OdinUnrealSample.Build.cs @@ -11,9 +11,9 @@ public class OdinUnrealSample : ModuleRules public OdinUnrealSample(ReadOnlyTargetRules Target) : base(Target) { MinFilesUsingPrecompiledHeaderOverride = 1; - PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "OnlineSubsystem", "OnlineSubsystemUtils" }); + PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "HeadMountedDisplay", "EnhancedInput", "OnlineSubsystem", "OnlineSubsystemUtils", "OnlineSubsystemEOS" }); - PrivateDependencyModuleNames.AddRange(new string[] { }); + PrivateDependencyModuleNames.AddRange(new string[] { }); DynamicallyLoadedModuleNames.Add("OnlineSubsystemNull"); diff --git a/Source/OdinUnrealSample/Private/EosPlayerController.cpp b/Source/OdinUnrealSample/Private/EosPlayerController.cpp new file mode 100644 index 00000000..f4007789 --- /dev/null +++ b/Source/OdinUnrealSample/Private/EosPlayerController.cpp @@ -0,0 +1,333 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "EosPlayerController.h" +#include "Misc/ConfigCacheIni.h" +#include "OnlineSubsystem.h" +#include "OnlineSubsystemUtils.h" +#include "OnlineSubsystemTypes.h" +#include "OnlineSessionSettings.h" +#include "Online/OnlineSessionNames.h" +#include "Interfaces/OnlineIdentityInterface.h" +#include "Containers/Array.h" +#include "Kismet/GameplayStatics.h" +#include "Serialization/BufferArchive.h" +#include "Serialization/MemoryReader.h" +#include "Serialization/MemoryWriter.h" +#include "Serialization/ArchiveLoadCompressedProxy.h" + +void AEosPlayerController::Login() +{ + /* + Tutorial 2: This function will access the EOS OSS via the OSS identity interface to log first into Epic Account Services, and then into Epic Game Services. + It will bind a delegate to handle the callback event once login call succeeeds or fails. + All functions that access the OSS will have this structure: 1-Get OSS interface, 2-Bind delegate for callback and 3-Call OSS interface function (which will call the correspongin EOS OSS function) + */ + IOnlineSubsystem* Subsystem = Online::GetSubsystem(GetWorld()); + IOnlineIdentityPtr Identity = Subsystem->GetIdentityInterface(); // This is the generic OSS interface that will access the EOS OSS. + + // If you're logged in, don't try to login again. + // This can happen if your player travels to a dedicated server or different maps as BeginPlay() will be called each time. + + FUniqueNetIdPtr NetId = Identity->GetUniquePlayerId(0); + + if (NetId != nullptr && Identity->GetLoginStatus(0) == ELoginStatus::LoggedIn) + { + return; + } + + /* This binds a delegate so we can run our function when the callback completes. 0 represents the player number. + You should parametrize this Login function and pass the parameter here for splitscreen. + */ + LoginDelegateHandle = + Identity->AddOnLoginCompleteDelegate_Handle( + 0, + FOnLoginCompleteDelegate::CreateUObject( + this, + &ThisClass::HandleLoginCompleted)); + + // Grab command line parameters. If empty call hardcoded login function - Hardcoded login function useful for Play In Editor. + FString AuthType; + FParse::Value(FCommandLine::Get(), TEXT("AUTH_TYPE="), AuthType); + + if (!AuthType.IsEmpty()) //If parameter is NOT empty we can autologin. + { + /* + In most situations you will want to automatically log a player in using the parameters passed via CLI. + For example, using the exchange code for the Epic Games Store. + */ + UE_LOG(LogTemp, Log, TEXT("Logging into EOS...")); // Log to the UE logs that we are trying to log in. + + if (!Identity->AutoLogin(0)) + { + UE_LOG(LogTemp, Warning, TEXT("Failed to login... ")); // Log to the UE logs that we are trying to log in. + // Clear our handle and reset the delegate. + Identity->ClearOnLoginCompleteDelegate_Handle(0, LoginDelegateHandle); + LoginDelegateHandle.Reset(); + } + } + else + { + /* + Fallback if the CLI parameters are empty.Useful for PIE. + The type here could be developer if using the DevAuthTool, ExchangeCode if the game is launched via the Epic Games Launcher, etc... + */ + FOnlineAccountCredentials Credentials("persistentauth", "", ""); + + UE_LOG(LogTemp, Log, TEXT("Logging into EOS with persistentauth...")); // Log to the UE logs that we are trying to log in. + + if (!Identity->Login(0, Credentials)) + { + UE_LOG(LogTemp, Warning, TEXT("Failed to login with persistentauth, trying accountportal... ")); // Log to the UE logs that we are trying to log in. + FOnlineAccountCredentials Credentials2("accountportal", "", ""); + + if (!Identity->Login(0, Credentials2)) + { + // Clear our handle and reset the delegate. + Identity->ClearOnLoginCompleteDelegate_Handle(0, LoginDelegateHandle); + LoginDelegateHandle.Reset(); + } + } + } +} + + + +void AEosPlayerController::HandleLoginCompleted(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& UserId, const FString& Error) +{ + /* + Tutorial 2: This function handles the callback from logging in. You should not proceed with any EOS features until this function is called. + This function will remove the delegate that was bound in the Login() function. + */ + IOnlineSubsystem* Subsystem = Online::GetSubsystem(GetWorld()); + IOnlineIdentityPtr Identity = Subsystem->GetIdentityInterface(); + if (bWasSuccessful) + { + UE_LOG(LogTemp, Log, TEXT("Login callback completed!")); + UE_LOG(LogTemp, Log, TEXT("Searching for a session...")); + // Maybe via button or player action? Maybe add parameters here + FindSessions(); + } + else //Login failed + { + // If your game is online only, you may want to return an errror to the user and return to a menu that uses a different GameMode/PlayerController. + + UE_LOG(LogTemp, Warning, TEXT("EOS login failed.")); //Print sign in failure in logs as a warning. + } + + Identity->ClearOnLoginCompleteDelegate_Handle(LocalUserNum, LoginDelegateHandle); + LoginDelegateHandle.Reset(); +} + +//Default class constructor included here for completeness +AEosPlayerController::AEosPlayerController() +{ + // Tutorial 2: Including constructor here for clarity. Nothing added in derived class for this tutorial. +} + +void AEosPlayerController::BeginPlay() +{ + // Tutorial 2: On BeginPlay call our login function. This is only on the GameClient, not on the DedicatedServer. + Super::BeginPlay(); // Call parent class BeginPlay() function + //Login(); //Call login function + + GConfig->GetString(TEXT("OnlineSubsystem"), TEXT("DefaultPlatformService"), SelectedSubsystem, GEngineIni); +} + +void AEosPlayerController::CreateLobby(FName KeyName, FString KeyValue) +{ + // Tutorial 7: Create lobby - this code is similar to creating session, notice that bIsDedicated is false, bUseLobbiesIfAvailable and UseLobbiesVoiceChatIfAvailable is true + + IOnlineSubsystem* Subsystem = Online::GetSubsystem(GetWorld()); + IOnlineSessionPtr Session = Subsystem->GetSessionInterface(); + + CreateLobbyDelegateHandle = + Session->AddOnCreateSessionCompleteDelegate_Handle(FOnCreateSessionCompleteDelegate::CreateUObject( + this, + &ThisClass::HandleCreateLobbyCompleted)); + + TSharedRef SessionSettings = MakeShared(); + SessionSettings->NumPublicConnections = 25; //We will test our sessions with 2 players to keep things simple + SessionSettings->bShouldAdvertise = true; //This creates a public match and will be searchable. + SessionSettings->bUsesPresence = false; //No presence on dedicated server. This requires a local user. + SessionSettings->bAllowJoinViaPresence = false; + SessionSettings->bAllowJoinViaPresenceFriendsOnly = false; + SessionSettings->bAllowInvites = false; //Allow inviting players into session. This requires presence and a local user. + SessionSettings->bAllowJoinInProgress = true; //Once the session is started, no one can join. + SessionSettings->bIsDedicated = false; //Session created on dedicated server. + SessionSettings->bUseLobbiesIfAvailable = true; //For P2P we will use a lobby instead of a session + SessionSettings->bUseLobbiesVoiceChatIfAvailable = false; //We will also enable voice + SessionSettings->bUsesStats = true; //Needed to keep track of player stats. + SessionSettings->Settings.Add(KeyName, FOnlineSessionSetting((KeyValue), EOnlineDataAdvertisementType::ViaOnlineService)); + + UE_LOG(LogTemp, Log, TEXT("Creating Lobby...")); + + if (!Session->CreateSession(0, LobbyName, *SessionSettings)) + { + UE_LOG(LogTemp, Warning, TEXT("Failed to create Lobby!")); + } +} + +void AEosPlayerController::HandleCreateLobbyCompleted(FName EOSLobbyName, bool bWasSuccessful) +{ + // Tutorial 7: Callback function: This is called once our lobby is created + + IOnlineSubsystem* Subsystem = Online::GetSubsystem(GetWorld()); + IOnlineSessionPtr Session = Subsystem->GetSessionInterface(); + if (bWasSuccessful) + { + UE_LOG(LogTemp, Log, TEXT("Lobby: %s Created!"), *EOSLobbyName.ToString()); + FString Map = "/Game/Maps/TopDownExampleMap?listen"; //Hardcoding map name here, should be passed by parameter + FURL TravelURL; + TravelURL.Map = Map; + GetWorld()->ServerTravel(Map, true); + GetWorld()->Listen(TravelURL); + SetupNotifications(); // Setup our listeners for lobby notification events + } + else + { + UE_LOG(LogTemp, Warning, TEXT("Failed to create lobby!")); + } + + // Clear our handle and reset the delegate. + Session->ClearOnCreateSessionCompleteDelegate_Handle(CreateLobbyDelegateHandle); + CreateLobbyDelegateHandle.Reset(); +} + +void AEosPlayerController::SetupNotifications() +{ + // Tutorial 7: EOS Lobbies are great as there are notifications sent for our backend when there are changes to lobbies (ex: Participant Joins/Leaves, lobby or lobby member data is updated, etc...) + IOnlineSubsystem* Subsystem = Online::GetSubsystem(GetWorld()); + IOnlineSessionPtr Session = Subsystem->GetSessionInterface(); + + // In this tutorial we're only giving an example of a notification for when a participant joins/leaves the lobby. The approach is similar for other notifications. + Session->AddOnSessionParticipantsChangeDelegate_Handle(FOnSessionParticipantsChangeDelegate::CreateUObject( + this, + &ThisClass::HandleParticipantChanged)); +} + +void AEosPlayerController::HandleParticipantChanged(FName EOSLobbyName, const FUniqueNetId& NetId, bool bJoined) +{ + // Tutorial 7: Callback function called when participants join/leave. + if (bJoined) + { + UE_LOG(LogTemp, Log, TEXT("A player has joined Lobby: %s"), *LobbyName.ToString()); + } + else + { + UE_LOG(LogTemp, Log, TEXT("A player has left Lobby: %s"), *LobbyName.ToString()); + } +} + +void AEosPlayerController::FindSessions(FName SearchKey, FString SearchValue) //put default value for example +{ + // Tutorial 4: This function will find our EOS Session that was created by our DedicatedServer. + + IOnlineSubsystem* Subsystem = Online::GetSubsystem(GetWorld()); + IOnlineSessionPtr Session = Subsystem->GetSessionInterface(); + TSharedRef Search = MakeShared(); + + // Remove the default search parameters that FOnlineSessionSearch sets up. + Search->QuerySettings.SearchParams.Empty(); + + Search->QuerySettings.Set(SearchKey, SearchValue, EOnlineComparisonOp::Equals); // Seach using our Key/Value pair + Search->QuerySettings.Set(SEARCH_LOBBIES, true, EOnlineComparisonOp::Equals); + FindSessionsDelegateHandle = + Session->AddOnFindSessionsCompleteDelegate_Handle(FOnFindSessionsCompleteDelegate::CreateUObject( + this, + &ThisClass::HandleFindSessionsCompleted, + Search)); + + UE_LOG(LogTemp, Log, TEXT("Finding session.")); + + if (!Session->FindSessions(0, Search)) + { + UE_LOG(LogTemp, Warning, TEXT("Find session failed")); + } +} + +void AEosPlayerController::HandleFindSessionsCompleted(bool bWasSuccessful, TSharedRef Search) +{ + // Tutorial 4: This function is triggered via the callback we set in FindSession once the session is found (or there is a failure) + + IOnlineSubsystem* Subsystem = Online::GetSubsystem(GetWorld()); + IOnlineSessionPtr Session = Subsystem->GetSessionInterface(); + + if (bWasSuccessful) + { + UE_LOG(LogTemp, Log, TEXT("Found session.")); + + if (Search->SearchResults.Num() > 0) + { + for (auto SessionInSearchResult : Search->SearchResults) + { + // Typically you want to check if the session is valid before joining. There is a bug in the EOS OSS where IsValid() returns false when the session is created on a DS. + // Instead of customizing the engine for this tutorial, we're simply not checking if the session is valid. The code below should go in this if statement once the bug is fixed. + /* + if (SessionInSearchResult.IsValid()) + { + + + } + */ + + //Ensure the connection string is resolvable and store the info in ConnectInfo and in SessionToJoin + if (Session->GetResolvedConnectString(SessionInSearchResult, NAME_GamePort, ConnectString)) + { + SessionToJoin = &SessionInSearchResult; + } + + // For the tutorial we will join the first session found automatically. Usually you would loop through all the sessions and determine which one is best to join. + break; + } + JoinSession(); + } + else + { + CreateLobby(); + } + } + else + { + UE_LOG(LogTemp, Warning, TEXT("Find Sessions failed.")); //print warning in logs of failure + } + + Session->ClearOnFindSessionsCompleteDelegate_Handle(FindSessionsDelegateHandle); + FindSessionsDelegateHandle.Reset(); +} + +void AEosPlayerController::JoinSession() +{ + // Tutorial 4: Join the session. + + IOnlineSubsystem* Subsystem = Online::GetSubsystem(GetWorld()); + IOnlineSessionPtr Session = Subsystem->GetSessionInterface(); + + JoinSessionDelegateHandle = + Session->AddOnJoinSessionCompleteDelegate_Handle(FOnJoinSessionCompleteDelegate::CreateUObject( + this, + &ThisClass::HandleJoinSessionCompleted)); + + UE_LOG(LogTemp, Log, TEXT("Joining session.")); + if (!Session->JoinSession(0, "SessionName", *SessionToJoin)) + { + UE_LOG(LogTemp, Warning, TEXT("Join session failed")); + } +} + +void AEosPlayerController::HandleJoinSessionCompleted(FName SessionName, EOnJoinSessionCompleteResult::Type Result) +{ + // Tutorial 4: This function is triggered via the callback we set in JoinSession once the session is joined (or there is a failure) + + IOnlineSubsystem* Subsystem = Online::GetSubsystem(GetWorld()); + IOnlineSessionPtr Session = Subsystem->GetSessionInterface(); + if (Result == EOnJoinSessionCompleteResult::Success) + { + UE_LOG(LogTemp, Log, TEXT("Joined lobby with name %s"), *(SessionName.ToString())); + UE_LOG(LogTemp, Warning, TEXT("Open Level: %s"), *ConnectString); + ClientTravel(ConnectString, TRAVEL_Absolute); + SetupNotifications(); // Setup our listeners for lobby event notifications + } + Session->ClearOnJoinSessionCompleteDelegate_Handle(JoinSessionDelegateHandle); + JoinSessionDelegateHandle.Reset(); +} \ No newline at end of file diff --git a/Source/OdinUnrealSample/Private/EosPlayerController.h b/Source/OdinUnrealSample/Private/EosPlayerController.h new file mode 100644 index 00000000..684c8cfe --- /dev/null +++ b/Source/OdinUnrealSample/Private/EosPlayerController.h @@ -0,0 +1,81 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/PlayerController.h" +#include "Interfaces/OnlineSessionInterface.h" +#include "EosPlayerController.generated.h" + +class FOnlineSessionSearch; +class FOnlineSessionSearchResult; + +/** +* Child class of APlayerController to hold EOS OSS code. +*/ +UCLASS() +class AEosPlayerController : public APlayerController +{ + GENERATED_BODY() +public: + // Class constructor. We won't use this in this tutorial. + AEosPlayerController(); + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + FString SelectedSubsystem; + +protected: + // Function called when play begins + virtual void BeginPlay(); + + UFUNCTION(BlueprintCallable) + //Function to sign into EOS Game Services + void Login(); + + //Callback function. This function is ran when signing into EOS Game Services completes. + void HandleLoginCompleted(int32 LocalUserNum, bool bWasSuccessful, const FUniqueNetId& UserId, const FString& Error); + + //Delegate to bind callback event for login. + FDelegateHandle LoginDelegateHandle; + + // Hardcoded name for the lobby. + FName LobbyName = "LobbyName"; + // Function to create an EOS session. + void CreateLobby(FName KeyName = "KeyName", FString KeyValue = "KeyValue"); + + // Callback function. This function will run when creating the session compeletes. + void HandleCreateLobbyCompleted(FName LobbyName, bool bWasSuccessful); + + // Delegate to bind callback event for session creation. + FDelegateHandle CreateLobbyDelegateHandle; + + // Function used to setup our listeners to lobby notification events - example on participant change only. + void SetupNotifications(); + + // Callback function. This function will run when a lobby participant joins / leaves. + void HandleParticipantChanged(FName EOSLobbyName, const FUniqueNetId& NetId, bool bJoined); + + // Function to find EOS sessions. Hardcoded attribute key/value pair to keep things simple + void FindSessions(FName SearchKey = "KeyName", FString SearchValue = "KeyValue"); + + // Callback function. This function will run when the session is found. + void HandleFindSessionsCompleted(bool bWasSuccessful, TSharedRef Search); + + //Delegate to bind callback event for when sessions are found. + FDelegateHandle FindSessionsDelegateHandle; + + // This is the connection string for the client to connect to the dedicated server. + FString ConnectString; + + // This is used to store the session to join information from the search. You could pass it as a paramter to JoinSession() instead. + FOnlineSessionSearchResult* SessionToJoin; + + // Function to join the EOS session. + void JoinSession(); + + // Callback function. This function will run when the session is joined. + void HandleJoinSessionCompleted(FName SessionName, EOnJoinSessionCompleteResult::Type Result); + + // Delegate to bind callback event for join session. + FDelegateHandle JoinSessionDelegateHandle; +};