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:
- If you want to change the default configuration, open the
HeadlessServer/Scenes/BoltHeadlessServer
scene on your editor:- Click on the
HeadlessServerHolder
game object; - Update the values of
Map
(scene name that will be loaded by the server),Game Type
(just an example of a custom property) andRoom ID
(name of the created room, if you let it empty, a random name will be created).
- Click on the
- Build a version of your game using
BoltHeadlessServer
scene as the Scene with index0
. Don't forget to run the Bolt Compiler (Assets/Bolt/Compile Assembly
); - 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 sceneLevel1
.
- 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.