PUN Classic (v1), PUN 2 and Bolt are in maintenance mode. PUN 2 will support Unity 2019 to 2022, but no new features will be added. Of course all your PUN & Bolt projects will continue to work and run with the known performance in the future. For any upcoming or new projects: please switch to Photon Fusion or Quantum.

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 interaction. 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:

C#

using System;
using Bolt.Matchmaking;
using Bolt.Photon;
using UnityEngine;
using UnityEngine.SceneManagement;

namespace Bolt.Samples.HeadlessServer
{
    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 = Guid.NewGuid().ToString();
                }

                // Create the Photon Room
                BoltMatchmaking.CreateSession(
                    sessionID: RoomID,
                    token: roomProperties,
                    sceneToLoad: 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)
            {
                BoltLog.Error("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.

Back to top