Headless Server

Often times you will want to run a server that uses fewer resources and in most of the cases, you need to run an instance of your game without any graphical interface or human iteration. For this, Unity has special arguments that you can pass to your game and get some sorts of different behaviors.

Bolt was built with this in mind, so you can run several servers of your game on hosting services and let your players just join and have fun. For this purpose, we've made a simple example script, so you can get a headstart on this aspect. You will find it inside the bolt_samples/HeadlessServer folder.

Steps to get your Headless Server up and running:

  1. If you want to change the default configuration, open the HeadlessServer/Scenes/BoltHeadlessServer scene on your editor:
    1. Click on the HeadlessServerHolder game object;
    2. Update the values of Map (scene name that will be loaded by the server), Game Type (just an example of a custom property) and Room ID (name of the created room, if you let it empty, a random name will be created).
  2. Build a version of your game using BoltHeadlessServer scene as the Scene with index 0. Don't forget to run the Bolt Compiler (Assets/Bolt/Compile Assembly);
  3. From the Command Line, locate your executable and run:
    • <path/to/your executable>.exe -batchmode -nographics -logFile [-map <other scene>] [-gameType <other game type>] [-room <other room name>];
    • ex: myGame.exe -batchmode -nographics -map Level1, and it will launch a server for your scene Level1.
  4. Build your game with your main scene, start as a client peer and connect to the room as usual.

Here you can find a transcript of the actual HeadlessServerManager class that is responsible to start the Server and run the game:

using System;
using Bolt.photon;
using UnityEngine;
using UnityEngine.SceneManagement;

public class HeadlessServerManager : Bolt.GlobalEventListener
{
    public string Map = "";
    public string GameType = "";
    public string RoomID = "";

    public override void BoltStartBegin()
    {
        // Register any Protocol Token that are you using
        BoltNetwork.RegisterTokenClass<PhotonRoomProperties>();
    }

    public override void BoltStartDone()
    {
        if (BoltNetwork.isServer)
        {
            // Create some room custom properties
            PhotonRoomProperties roomProperties = new PhotonRoomProperties();

            roomProperties.AddRoomProperty("t", GameType); // ex: game type
            roomProperties.AddRoomProperty("m", Map); // ex: map id

            roomProperties.IsOpen = true;
            roomProperties.IsVisible = true;

            // If RoomID was not set, create a random one
            if (RoomID.Length == 0)
            {
                RoomID = new Guid().ToString();
            }

            // Create the Photon Room
            BoltNetwork.SetServerInfo(RoomID, roomProperties);

            // Load the requested Level
            BoltNetwork.LoadScene(Map);
        }
    }

    // Use this for initialization
    void Start()
    {
        // Get custom arguments from command line
        Map = GetArg("-m", "-map") ?? Map;
        GameType = GetArg("-t", "-gameType") ?? GameType; // ex: get game type from command line
        RoomID = GetArg("-r", "-room") ?? RoomID;

        // Validate the requested Level
        var validMap = false;

        foreach (string value in BoltScenes.AllScenes)
        {
            if (SceneManager.GetActiveScene().name != value)
            {
                if (Map == value)
                {
                    validMap = true;
                    break;
                }
            }
        }

        if (!validMap)
        {
            Debug.LogError("Invalid configuration: please verify level name");
            Application.Quit();
        }

        // Start the Server
        BoltLauncher.StartServer();
        DontDestroyOnLoad(this);
    }

    /// <summary>
    /// Utility function to detect if the game instance was started in headless mode.
    /// </summary>
    /// <returns><c>true</c>, if headless mode was ised, <c>false</c> otherwise.</returns>
    public static bool IsHeadlessMode()
    {
        return Environment.CommandLine.Contains("-batchmode") && Environment.CommandLine.Contains("-nographics");
    }

    static string GetArg(params string[] names)
    {
        var args = Environment.GetCommandLineArgs();
        for (int i = 0; i < args.Length; i++)
        {
            foreach (var name in names)
            {
                if (args[i] == name && args.Length > i + 1)
                {
                    return args[i + 1];
                }
            }
        }

        return null;
    }
}

Of course, you can customize this to use a port you enter, a different game mode, etc. by making more argument checks.

You will also want to disable some behaviors in the game when running in the headless mode such as locking the cursor, spawning a player, and music. For the tutorial there is Screen.lockCursor = true in PlayerCamera.cs, you can modify the script to only lock the cursor if not in headless mode. The same goes for Player.serverPlayer.InstantiateEntity() in ServerCallbacks.cs. For this we've added the utility function HeadlessServerManager.IsHeadlessMode() that test if the game instance was started in headless mode.

 To Document Top