Multiplayer session creation on dedicated server

I’ve found some information about multiplayer and dedicated server etc. but I think I’m still missing some info. The procedure is not that clear for me.

What I would like to have:

  • dedicated server
  • 2 players (so both as clients connected)

Setup

I’ve downloaded and compiled the unreal engine 4.10 from source on Mac OS X.
The project is mostly based on the multiplayer shootout example. But it is a Paper2D based project.
I’ve created an own TournamentGameInstance (c++ class) as well as a TheTournamentGameInstance_BP (blueprint class). TheTournamentGameInstance_BP’s parent is the c++ class, both are working. I also got a main menu with a play button.
I will put the sources at the bottom.

  • Standalone Game
  • Number of Players: 2
  • Run Dedicated server: checked
  • Use single process: unchecked
  • Editor Multiplayer Mode: Play As Client

Intro

After I click on the play button. A routine is looking if a Session is available, if not a session is created, the CreateSessionEvent BP node is fired. Then some other actions happen, so when I get to the PaperCharacter_BP → to the EventBeginPlay Node I got the “Switch Has Authority” and just for testing purposes I take the “Remote” path and set the sprite color to pink (see MyPaperCharacte_BP part 1 image). The “Authority” path (see MyPaperCharacte_BP part 2 image) is setting the color to red. Both players are using the same sprite. It is the part of the multiplayer shootout where the hat gets colored.
On the first window I click the play button, as we got no session until now, a session will be created. Inside the second window, I click the play button, a session is available, so the session will be joined.

The Problem

I thought that the owning sprite on each client should be pink colored the opponent instead is not. But after the first player creates the session, its sprite is not colored at all. After the second player joins, the second window got two pink colored sprites. The first window still hot his not colored sprite and the joined player is represented with a red colored sprite.
The movement is working and replicated correctly.
So is the setup for a dedicated server wrong? I got the flag “SessionSettings->bIsDedicated = true;”. Is it even possible to test it that way with the unreal editor, so after clicking the play button in the unreal editor, I thought a dedicated server is started. Or do I have to create a standalone dedicated server?
Am I even correct with my thinking that I can start a dedicated server with the editor and the first client is able to tell the server to create a session with an C++ GameInstance like this one, so he can join as well as the second player?
Thanks


MyPaperCharacter_BP

Remote path

69089-screen+shot+2015-12-02+at+16.57.53.png

Authority path

TheTournamentGameInstance_BP

I’ve replaced the standard CreateSession node, and now calling “StartOnlineGameWithDedicated Server”. Afterwards the “OpenLevel” node with Options: listen.

TheTournamentGameInstance

[TheTournamentGameInstance on github][4]

#include "TheTournament.h"
#include "TheTournamentGameInstance.h"









UTheTournamentGameInstance::UTheTournamentGameInstance(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{
    /** Bind function for CREATING a Session */
    OnCreateSessionCompleteDelegate = FOnCreateSessionCompleteDelegate::CreateUObject(this, &UTheTournamentGameInstance::OnCreateSessionComplete);
    OnStartSessionCompleteDelegate = FOnStartSessionCompleteDelegate::CreateUObject(this, &UTheTournamentGameInstance::OnStartOnlineGameComplete);
}





bool UTheTournamentGameInstance::HostSession(TSharedPtr<const FUniqueNetId> UserId, FName SessionName, bool bIsLAN, bool bIsPresence, int32 MaxNumPlayers)
{
    // Get the Online Subsystem to work with
    IOnlineSubsystem* const OnlineSub = IOnlineSubsystem::Get();
    
    if (OnlineSub)
    {
        // Get the Session Interface, so we can call the "CreateSession" function on it
        IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();
        
        if (Sessions.IsValid() && UserId.IsValid())
        {
            /*
             Fill in all the Session Settings that we want to use.
             
             There are more with SessionSettings.Set(...);
             For example the Map or the GameMode/Type.
             */
            SessionSettings = MakeShareable(new FOnlineSessionSettings());
            
            SessionSettings->bIsLANMatch = bIsLAN;
            SessionSettings->bUsesPresence = bIsPresence;
            SessionSettings->NumPublicConnections = MaxNumPlayers;
            SessionSettings->NumPrivateConnections = 0;
            SessionSettings->bAllowInvites = true;
            SessionSettings->bAllowJoinInProgress = true;
            SessionSettings->bShouldAdvertise = true;
            SessionSettings->bAllowJoinViaPresence = true;
            SessionSettings->bAllowJoinViaPresenceFriendsOnly = false;
            SessionSettings->bIsDedicated = true;
            
            SessionSettings->Set(SETTING_MAPNAME, FString("RailroadDraisine"), EOnlineDataAdvertisementType::ViaOnlineService);
            
            // Set the delegate to the Handle of the SessionInterface
            OnCreateSessionCompleteDelegateHandle = Sessions->AddOnCreateSessionCompleteDelegate_Handle(OnCreateSessionCompleteDelegate);
            
            // Our delegate should get called when this is complete (doesn't need to be successful!)
            return Sessions->CreateSession(*UserId, SessionName, *SessionSettings);
        }
    }
    else
    {
        GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, TEXT("No OnlineSubsytem found!"));
    }
    
    return false;
}



void UTheTournamentGameInstance::OnCreateSessionComplete(FName SessionName, bool bWasSuccessful)
{
    GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("OnCreateSessionComplete %s, %d"), *SessionName.ToString(), bWasSuccessful));
    
    // Get the OnlineSubsystem so we can get the Session Interface
    IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();
    if (OnlineSub)
    {
        // Get the Session Interface to call the StartSession function
        IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();
        
        if (Sessions.IsValid())
        {
            // Clear the SessionComplete delegate handle, since we finished this call
            Sessions->ClearOnCreateSessionCompleteDelegate_Handle(OnCreateSessionCompleteDelegateHandle);
            if (bWasSuccessful)
            {
                // Set the StartSession delegate handle
                OnStartSessionCompleteDelegateHandle = Sessions->AddOnStartSessionCompleteDelegate_Handle(OnStartSessionCompleteDelegate);
                
                // Our StartSessionComplete delegate should get called after this
                Sessions->StartSession(SessionName);
            }
        }
        
    }
}



void UTheTournamentGameInstance::OnStartOnlineGameComplete(FName SessionName, bool bWasSuccessful)
{
    GEngine->AddOnScreenDebugMessage(-1, 10.f, FColor::Red, FString::Printf(TEXT("OnStartSessionComplete %s, %d"), *SessionName.ToString(), bWasSuccessful));
    
    // Get the Online Subsystem so we can get the Session Interface
    IOnlineSubsystem* OnlineSub = IOnlineSubsystem::Get();
    if (OnlineSub)
    {
        // Get the Session Interface to clear the Delegate
        IOnlineSessionPtr Sessions = OnlineSub->GetSessionInterface();
        if (Sessions.IsValid())
        {
            // Clear the delegate, since we are done with this call
            Sessions->ClearOnStartSessionCompleteDelegate_Handle(OnStartSessionCompleteDelegateHandle);
        }
    }
    
    // If the start was successful, we can open a NewMap if we want. Make sure to use "listen" as a parameter!
    if (bWasSuccessful)
    {
//        UGameplayStatics::OpenLevel(GetWorld(), "RailroadDraisine", true, "listen");
        // open level with blueprints
        
    }
}




void UTheTournamentGameInstance::StartOnlineGameWithDedicatedServer()
{
    // Creating a local player where we can get the UserID from
    ULocalPlayer* const Player = GetFirstGamePlayer();
    
    // Call our custom HostSession function. GameSessionName is a GameInstance variable
    HostSession(Player->GetPreferredUniqueNetId(), GameSessionName, false, false, 2);
}

I know this is rather late but if you haven’t solved this, I may have a solution for you. This is something that actually tripped me up in my first Unreal Engine networked problem. I believe your problem lies with the switch has Authority. My original thoughts on this function based on tutorials and what I had first learned was that the Authority execution was always the server where as the Remote execution was always the client. This however, is not always the case.

This comes down to owner ship of the object, IE where the object was created. If the object was created on the server and owned by the server, the server will have authority of said object. This is the same as the client. This was very confusing to me at first but once I continued to work with it, it became easier.

Another helpful link here that has two charts for the what happens with an RPC call based on ownership of the actor.

I am also currently still learning the basics of networking here but I hope this helps!