Announcement

Collapse
No announcement yet.

OnlineBeacons Tutorial with Blueprint Access

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

    [TUTORIAL] OnlineBeacons Tutorial with Blueprint Access

    Hey everyone,

    I noticed that this is covered very little in the official docs and around the web. You can find tidbits here and there, but not a full example that works with Blueprints. Today, I am going to reveal the great mystery behind how they work and how to get them to work in C++ and have blueprint access to them.

    First lets cover what Beacons are good for in an online game:
    1. They can be used to get a quick ping from the server.
    2. You can use it to reserve spots on the server.
    3. They can be used for sending RPC to the server without taking up a spot.
    4. You can use it for party invites and party formation.
    5. Your imagination is the limit as to how you can use them.

    Beacons are broken down into three categories: Client Beacon, Host Beacon, and a Host Beacon Object. They all branch off of the AOnlineBeacon class, except for the Host Beacon Object. The Host Beacon Object simply inherits directly from AActor.

    But before we get started with the code. You need to set some stuff up in the DefaultEngine.ini to get this to work properly.

    In the DefaultEngine.ini in the Config folder add the following to support both Steam and basic IP:

    We set up the port to use for the beacon host and some timeout stuff.

    [/Script/OnlineSubsystemUtils.OnlineBeaconHost]
    ListenPort=7787
    BeaconConnectionInitialTimeout=48.0
    BeaconConnectionTimeout=49.0

    [/Script/Engine.Engine]
    !NetDriverDefinitions=ClearArray
    +NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="/Script/OnlineSubsystemSteam.SteamNetDriver",DriverClassNameFallback="/Script/OnlineSubsystemUtils.IpNetDriver")
    +NetDriverDefinitions=(DefName="DemoNetDriver",DriverClassName="/Script/Engine.DemoNetDriver",DriverClassNameFallback="/Script/Engine.DemoNetDriver")
    +NetDriverDefinitions=(DefName="BeaconNetDriver",DriverClassName="/Script/OnlineSubsystemSteam.SteamNetDriver",DriverClassNameFallback="/Script/OnlineSubsystemUtils.IpNetDriver")

    You will notice the third one is the most important the BeaconNetDriver. Without this your server and client beacons will not work. Now, you will need to restart the editor for these new settings to take place.

    Let's start by creating our client beacon for a simple one shot ping request to get the milliseconds for a round trip.

    First create your class via C++ in the Editor and pick the AOnlineBeaconClient as the parent class. Once that is done let's look at a header file.

    The header file for the client:
    Code:
    #include "CoreMinimal.h"
    #include "Engine.h"
    #include "OnlineBeaconClient.h"
    #include "PingBeacon.generated.h"
    
    DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOnBeaconPingComplete, int32, TimeMS);
    
    DECLARE_LOG_CATEGORY_EXTERN(FBeaconLog, Log, All);
    
    /**
     * Simple ping client beacon class
     */
     /** the config = engine is for if you want to use config based properties. Engine = Engine.ini */
    UCLASS(Blueprintable, BlueprintType, transient, notplaceable, config = Engine)
    class APingBeacon : public AOnlineBeaconClient
    {    
        GENERATED_UCLASS_BODY()
    
    
        //~ Begin AOnlineBeaconClient Interface
        virtual void OnFailure() override;
        //~ End AOnlineBeaconClient Interface
    
        /** Send a ping RPC to the client */
        UFUNCTION(client, reliable)
        virtual void ClientPing();
    
        /** Let's us know the beacon is ready so we can prep the initial start time for ping round trip */
        UFUNCTION(client, reliable)
        virtual void Ready();
    
        /** Send a pong RPC to the host */
        UFUNCTION(server, reliable, WithValidation)
        virtual void ServerPong();
    
        /** Provide Blueprint Access to Start the Beacon
        UFUNCTION(BlueprintCallable, Category = "PingBeacon")
        bool Start(FString address, int32 port, const bool portOverride);
    
        /** Provide Blueprint access to disconnect and destroy the client beacon */
        UFUNCTION(BlueprintCallable, Category = "PingBeacon")
        void Disconnect();
    
    public:
        /** Provide a Blueprint binding for the OnPingComplete event */
        UPROPERTY(BlueprintAssignable, Category = "PingBeacon")
        FOnBeaconPingComplete OnPingComplete;
    
        /** Need to add one also for a failure. That is up to you though! */
    
    protected:
        /** Holds our initial start time in Ticks from FDateTime.Now().GetTicks() */
        int64 startTime;
    };
    There you have it, the header for a PingBeacon client.

    Now lets look at the .cpp file for it.

    Code:
    #include "PingBeacon.h"
    
    DEFINE_LOG_CATEGORY(FBeaconLog);
    
    APingBeacon::APingBeacon(const FObjectInitializer& ObjectInitializer) :
        Super(ObjectInitializer)
    {
    }
    
    void APingBeacon::OnFailure()
    {
        Super::OnFailure();
    
        /** This is where you would call the delegate for failure if you had one */
        UE_LOG(FBeaconLog, Log, TEXT("Beacon Connection failure"));
    }
    
    /** The rpc client ping implementation */
    void APingBeacon::ClientPing_Implementation()
    {
        UE_LOG(FBeaconLog, Log, TEXT("Ping RPC Called"));
    
        //Get our end time in Ticks
        int64 endTime = FDateTime::Now().GetTicks();
        //Find the difference in ticks.
        int64 diff = endTime - startTime;
    
        //Divide diff by 10,000 to convert to Milliseconds
        //And cast to int32 while we are at
        int32 ms = (int32)diff / 10000;
    
        //Broadcast the ping complete
        OnPingComplete.Broadcast(ms);
    
       //For looping simply call Ready() again from right here
    }
    
    /** The rpc client ready implementation */
    void APingBeacon::Ready_Implementation()
    {
        UE_LOG(FBeaconLog, Log, TEXT("Ready RPC Called"));
        //Set our initial start time in ticks
        startTime = FDateTime::Now().GetTicks();
        //Call server pong rpc
        ServerPong();
    }
    
    bool APingBeacon::ServerPong_Validate()
    {
        return true;
    }
    
    /** ServerPong rpc implementation **/
    void APingBeacon::ServerPong_Implementation()
    {
        UE_LOG(FBeaconLog, Log, TEXT("Pong RPC Called"));
        //Send ping rpc back to client
        ClientPing();
    }
    
    /** Our blueprint helper for stuff **/
    bool APingBeacon::Start(FString address, int32 port, const bool portOverride)
    {
        //Address must be an IP or valid domain name such as epicgames.com or 127.0.0.1
        //Do not include a port in the address! Beacons use a different port then the standard 7777 for connection
        FURL url(nullptr, *address, ETravelType::TRAVEL_Absolute);
    
        //overriding it with a user specified port?
        if (portOverride)
        {
            url.Port = port;
        }
        //if not overriding just pull the config for it based on the beacon host ListenPort
        else
        {
            int32 portConfig;
            GConfig->GetInt(TEXT("/Script/OnlineSubsystemUtils.OnlineBeaconHost"), TEXT("ListenPort"), portConfig, GEngineIni);
            url.Port = portConfig;
        }
    
        //Tell our beacon client to begin connection request to server address with our beacon port
        return InitClient(url);
    }
    
    /** Our blueprint helper for disconnecting and destroying the beacon */
    void APingBeacon::Disconnect()
    {
        DestroyBeacon();
    }
    And that is all there is to the Client PingBeacon.

    Now this is where things get tricky and the provided official documentation is incorrect.

    The official documentation says you should not have to override AOnlineBeaconHost. This is completely false. If you do not override it with your own then it will always have a BeaconState of DenyRequests. At least that is what it looks like after examining the code for it thoroughly. There is no place in the AOnlineBeacon or AOnlineBeaconHost that dynamically changes it to AllowRequests.

    The BeaconState is a protected variable in the AOnlineBeacon class, and thus the only way to modify is to inherit the AOnlineBeaconHost and set it to AllowRequests.

    A quick an dirty implementation of it to always allow requests. The header file:

    Code:
    #pragma once
    
    #include "CoreMinimal.h"
    #include "OnlineBeaconHost.h"
    #include "PingBeaconHost.generated.h"
    
    class AOnlineBeaconHostObject;
    
    UCLASS(Blueprintable, BlueprintType, transient, notplaceable, config=Engine)
    class APingBeaconHost : public AOnlineBeaconHost
    {
        GENERATED_UCLASS_BODY()
    
    public:
        /** Blueprint accessor to init the beacon host */
        UFUNCTION(BlueprintCallable, Category = "PingBeaconHost")
        bool Start();
    
        /** A blueprint helper to add our PingBeaconHostObject */
        UFUNCTION(BlueprintCallable, Category = "PingBeaconHost")
        void AddHost(AOnlineBeaconHostObject* HostObject)
    
        /** You can also remove a host if you so wish to as well */
        /** You can remove it with: UnregisterHost(const FString& BeaconType) */
    
    protected:
        /** If we successfully started are not */
        bool IsReady;
    };
    The .cpp file for it:

    Code:
    #include "PingBeaconHost.h"
    #include "OnlineBeaconHostObject.h"
    
    APingBeaconHost::APingBeaconHost(const FObjectInitializer& ObjectInitializer) :
        Super(ObjectInitializer)
    {
        //Set the beacon host state to allow requests
        BeaconState = EBeaconState::AllowRequests;
    }
    
    bool APingBeaconHost::Start()
    {
        //Call our init to start up the network interface
        IsReady = InitHost();
        return IsReady;
    }
    
    void APingBeaconHost::AddHost(AOnlineBeaconHostObject* HostObject)
    {
        /** Make sure we inited properly */
        if(IsReady)
        {
            RegisterHost(HostObject);
        }
    }
    Great that solves that problem. Now one more class to go!

    We still need to create the PingBeaconHostObject to actually allow the PingBeacon to have RPC capabilities.

    The header file for the PingBeaconHostObject:

    Code:
    #pragma once
    
    #include "CoreMinimal.h"
    #include "OnlineBeaconHostObject.h"
    #include "PingBeaconHostObject.generated.h"
    
    UCLASS(Blueprintable, BlueprintType, transient, notplaceable, config = Engine)
    class APingBeaconHostObject : public AOnlineBeaconHostObject
    {
        GENERATED_UCLASS_BODY()
    
        //~ Begin AOnlineBeaconHost Interface
        /** You can do stuff in this one if you want, but we just use the super for this example */
        virtual AOnlineBeaconClient* SpawnBeaconActor(class UNetConnection* ClientConnection) override;
        virtual void OnClientConnected(class AOnlineBeaconClient* NewClientActor, class UNetConnection* ClientConnection) override;
        //~ End AOnlineBeaconHost Interface
    
        /** In case you ever want to do other things */
        virtual bool Init();
    
        /**
            Other override functions of the AOnlineBeaconHostObject:
    
            /**
             * Disconnect a given client from the host
             *
             * param ClientActor the beacon client to disconnect
             */
            virtual void DisconnectClient(AOnlineBeaconClient* ClientActor);
    
            /**
             * Notification that a client has been disconnected from the host in some way (timeout, client initiated, etc)
             *
             * param LeavingClientActor actor that has disconnected
             */
            virtual void NotifyClientDisconnected(AOnlineBeaconClient* LeavingClientActor);
    
            /**
             * Called when this class is unregistered by the beacon host
             * Do any necessary cleanup.
             */
            virtual void Unregister();
        */
    };
    Now the .cpp file for it:

    Code:
    #include "PingBeaconHostObject.h"
    #include "PingBeacon.h"
    
    APingBeaconHostObject::APingBeaconHostObject(const FObjectInitializer& ObjectInitializer) :
        Super(ObjectInitializer)
    {
        /** Set our actual actor client class this host object will handle */
        ClientBeaconActorClass = APingBeacon::StaticClass();
        /** Set the beacon type name **/
        BeaconTypeName = ClientBeaconActorClass->GetName();
    
        /** Make sure we can tick **/
        PrimaryActorTick.bCanEverTick = true;
        PrimaryActorTick.bAllowTickOnDedicatedServer = true;
        PrimaryActorTick.bStartWithTickEnabled = true;
    }
    
    bool APingBeaconHostObject::Init()
    {
        //just returning true for now
        return true;
    }
    
    void APingBeaconHostObject::OnClientConnected(AOnlineBeaconClient* NewClientActor, UNetConnection* ClientConnection)
    {
        //Call super
        Super::OnClientConnected(NewClientActor, ClientConnection);
    
        //Cast to our actual APingBeacon
        APingBeacon* BeaconClient = Cast<APingBeacon>(NewClientActor);
        if (BeaconClient != NULL)
        {
            //It's good, so lets rpc back to the client and tell it we are ready
            BeaconClient->Ready();
        }
    }
    
    AOnlineBeaconClient* APingBeaconHostObject::SpawnBeaconActor(UNetConnection* ClientConnection)
    {
        //Just super for now, technically you can return NULL here as well to prevent spawning
        return Super::SpawnBeaconActor(ClientConnection);
    }
    Yay, all of our classes are ready to go! But, how do we use them? Obviously, the Host and HostObject classes should be server only and thus created in our custom GameMode class or Blueprint.

    So, let's go through the steps of setting up the server side in your game mode blueprint:
    In BeginPlay, first create the PingBeaconHost via SpawnActor of Class node. Then next, call the Start, and if successful spawn a PingBeaconHostObject the same way as before and use AddHost on PingBeaconHost to add it.

    I do not have a screenshot of that, since I implemented that part directly into my base C++ GameMode class in via BeginPlay() override;

    Hit run and test your game mode real quick. In the log you should see a second Network Adapter come up like so: LogNet: IpNetDriver_1 IpNetDriver_1 IpNetDriver listening on port 7787. If you saw it, then your server beacon is working properly and is ready to receive connections. You can also look in the editor to see if there is a Host and HostObject actors in the scene view.

    Now the client part!

    The image example you see is from a UI widget that is being setup in the server listing. But, this is how you should handle it roughly. Ignore the port override, I was just testing to make sure the function worked as expected.

    The most important parts of the screenshot are how to handle the Start of the client beacon and how to Handle the ping complete. This is a one shot ping example.


    Click image for larger version  Name:	BeaconPing.png Views:	2 Size:	387.3 KB ID:	1355436

    A little messy, but it works.

    And that's it for a one shot PingBeacon.

    You could also make it ping indefinitely if you wanted until you call Disconnect. You would have to call Ready() again from the ClientPing() in PingBeacon to create a looping effect.

    I hope this helps someone else. Took me a bit to put all the pieces together from all over the net and my own research. Let's just say, not much is out there on the subject at all currently. However, it is a really important aspect to have in a multiplayer game.
    Attached Files
    Last edited by Metricton; 09-18-2017, 12:00 AM.

    #2
    Originally posted by Metricton View Post
    I hope this helps someone else. Took me a bit to put all the pieces together from all over the net and my own research. Let's just say, not much is out there on the subject at all currently. However, it is a really important aspect to have in a multiplayer game.
    Hey thanks for the tutorial. I have to implement beacons in my current project so any enlightenment on the subject is very helpful.

    Comment


      #3
      Hi Metricton,
      i am implementing your code C++ for my project in blueprint, but i have some problem. I add in engine.in the following to support both Steam and basic IP and restart the egnine, then i have create the
      OnlineBeaconClient header and cpp adding your code, so also for OnlineBeaconHost. Then i have created a class PingBeaconHostObject. When in blueprint i want to create the PingBeaconHost via SpawnActor of Class node, there isn't. Can you help me? My knowledge in C++ is very ridicoulous Thanks.

      Comment


        #4
        Hey Metricton, I wish I saw your post before last week as I painstakingly went through the same process to get it functional the last few days.
        I have got it to work when the Host and Client Beacons are created on two game instances without any map options.
        However, when the Host is made a listen server and the beacons are then created, client shows a connection failure message when trying to connect to a Host Beacon.
        Is this an issue with the FURL being incorrect on the Client beacon due to additional options added to the server URL i.e. listen? Any suggestions as to how to actually set the client URL correctly as right now mine is just set to blank?

        Comment


          #5
          Also, you don't have to override the OnlineBeaconHost. After the Host beacon is spawned, you can just call
          BeaconHost->PauseBeaconRequests(false);
          which sets the enum to Allow Requests.

          This function is public and in the OnlineBeacon.h file.
          Last edited by Pramod Ramesh; 09-28-2017, 02:50 PM.

          Comment


            #6
            There are few tutorial about OnlineBeacon. It's very helpful.Thanks!

            Comment


              #7
              Unfortunately you can't use BP version of OnlineBeaconClient Either OnBeaconSpawnedMapping is not populated or FOnBeaconSpawned->Execute(Connection) is failing
              Available for contract hiring! Complex mechanics, quick game prototyping, VR, AI, Animation, Tools for designers.

              Check out my latest game! Last Joy - 2D RPG with unique combat system.

              Comment


                #8
                Is that Server structure in 'VersesCard' custom? How do you get the IP address the server?

                Comment

                Working...
                X