> For the complete documentation index, see [llms.txt](https://docs.edgegap.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.edgegap.com/docs/sample-projects/colyseus-realtime-tanks.md).

# Colyseus Realtime Tanks

Host a Colyseus 0.17 multiplayer tank game on Edgegap with one dedicated server per match, using direct connection or the Edgegap matchmaker.

Explore how to host [Colyseus](https://colyseus.io/) servers on Edgegap using techniques from the [Realtime Tanks](https://github.com/colyseus/realtime-tanks-demo) sample by the Colyseus team.

The sample has one authoritative Colyseus server and 9 clients built with different engines - Three.js, PlayCanvas, Construct 3, Godot, Unity, Defold, GameMaker, Haxe and MonoGame. Each match gets its own server, started near its players, and the server stops itself when the match is over.

This sample shows:

* **Colyseus 0.17** server in a `linux/amd64` Docker image, listening on port `2567`,
* **one server per match** - the server stops its own deployment when nobody joins, when it stays empty, or after a set number of rounds,
* **direct mode** - start a deployment and pass its URL to any of the 9 clients with `?server=` or `--server=`,
* **Edgegap Matchmaker** (optional) - built into Three.js, Unity and Godot clients, for web and desktop builds,
* **one WebSocket port with TLS Upgrade** - carrying both the Colyseus matchmaking HTTP request and the room WebSocket.

{% hint style="success" %}
See [edgegap/README.md](https://github.com/edgegap/realtime-tanks-demo/blob/master/edgegap/README.md) in the sample repository for the full list of changed files and per-client settings.
{% endhint %}

## ✔️ Preparation

Before we start, you will need:

* Realtime Tanks Sample project (modified for Edgegap) - [download from GitHub](https://github.com/edgegap/realtime-tanks-demo),
* Node.js - [download from official source](https://nodejs.org/), to run the server locally and the deploy script.

<details>

<summary><a href="https://www.docker.com/products/docker-desktop/">Install Docker Desktop (or Docker CLI)</a></summary>

* [Install Docker Desktop from the official source](https://www.docker.com/products/docker-desktop/) (no account required).
* Restart your computer after completing the installation.

</details>

Locally, the server behaves like upstream. From the `server` folder, run `npm install` then `npm run dev`.

## ⚡ Deploy and Connect

### 1. Build the Server Image

☑️ To get started, you'll need to [create a free account with Edgegap](https://app.edgegap.com/auth/register). No credit card required.

☑️ Find your [Container Registry](/learn/advanced-features/edgegap-container-registry.md#getting-your-credentials) project name and credentials.

☑️ Build and push your server image:

{% code collapsedlinecount="10" %}

```bash
cd server
docker build --platform linux/amd64 -t registry.edgegap.com/<project>/realtime-tanks:v1 .
docker login registry.edgegap.com
docker push registry.edgegap.com/<project>/realtime-tanks:v1
```

{% endcode %}

{% hint style="danger" %}
:x: **DO NOT - overwrite existing tags or use `latest` tag** - this will deploy an outdated cache.\
:white\_check\_mark: **DO - always increase your version tag** and deploy the new build.
{% endhint %}

☑️ Optionally, test your image locally first. `ARBITRIUM_REQUEST_ID` makes it act as on Edgegap - with no delete URL set, self-stop logs that and exits:

{% code collapsedlinecount="10" %}

```bash
docker run --rm -p 2567:2567 -e ARBITRIUM_REQUEST_ID=local -e EMPTY_TIMEOUT_S=10 realtime-tanks:v1
```

{% endcode %}

✅ You may now proceed to the next step.

### 2. Create an App Version

☑️ [Create an application](https://app.edgegap.com/application-management/applications/list) named `realtime-tanks`, and a version named `v1` with:

* image `registry.edgegap.com/<project>/realtime-tanks`, tag `v1`,
* your Container Registry username and token, so Edgegap can pull your image,
* resources 0.25 vCPU and 512 MB memory,
* [port](/learn/orchestration/application-and-versions.md#port-mapping) `2567`, named `gameport`, protocol **WS**, with **TLS Upgrade** enabled,
* [max duration](/learn/orchestration/application-and-versions.md#safety-guardrails) 60 minutes for testing.

{% hint style="warning" %}
**TLS Upgrade** lets every client, including browser builds, connect to `wss://<id>.pr.edgegap.net:<port>`. Edgegap terminates TLS and forwards plain HTTP and WebSocket to port `2567`, so the Colyseus matchmaking HTTP request and the room WebSocket share one port. See [Websockets (WS) and Secure Websockets (WSS)](/learn/orchestration/deployments.md#websockets-ws-and-secure-websockets-wss).
{% endhint %}

Optionally, set environment variables on your app version:

* `MATCH_ROUNDS` - end the match after this many won rounds (default `0`, an endless arena),
* `FIRST_JOIN_TIMEOUT_S` - seconds before a server nobody joined stops itself (default `120`),
* `EMPTY_TIMEOUT_S` - seconds before a server everyone left stops itself (default `30`).

✅ You may now proceed to the next step.

### 3. Connect Directly

☑️ Start a server near you with the sample's deploy script. Your Edgegap API token stays on your machine:

{% code collapsedlinecount="10" %}

```bash
cd server
EDGEGAP_API_TOKEN=<token> npm run edgegap:deploy -- realtime-tanks v1
```

{% endcode %}

The script prints `wss://<request_id>.pr.edgegap.net:<port>` and how to pass it to each client.

☑️ Pass the URL to your clients:

* **Three.js, PlayCanvas, Construct 3** - `?server=wss://...` in the page URL,
* **Godot** - `--server=wss://...` (after `--` in the editor's main run args), web export `?server=wss://...`,
* **Unity** - `--server=wss://...` on the command line, WebGL `?server=wss://...`, or the `serverUrl` field,
* **Defold** - HTML5 `?server=wss://...`, native `--config=tanks.server_url=wss://...` or `[tanks] server_url` in `game.project`,
* **GameMaker** - `--server=wss://...` on the command line, HTML5 `?server=wss://...`,
* **Haxe** - JS `?server=wss://...`, HashLink `--server=wss://...`,
* **MonoGame** - `--server=wss://...` or `TANKS_SERVER_URL` environment variable.

{% hint style="danger" %}
In a live game, your backend calls the same [Deploy API](/docs/api/dedicated-servers.md) (`POST /v2/deployments`) when a lobby is ready and sends the URL to its players. **Never put your Edgegap API token in a game client or server image.**
{% endhint %}

🙌 Congratulations on your first Deployment on Edgegap!

### 4. Add the Matchmaker (Optional)

☑️ Open [Matchmaker](https://app.edgegap.com/matchmaker-management-v2/matchmakers/list) page in dashboard, click **Create**, and upload `edgegap/matchmaker-config.json` from the sample.

The config has one `casual` profile for 2 to 8 players per match. After 15 seconds, it starts a match with whoever is waiting, with at least 2 players.

☑️ Add every origin serving your game to `allowed_cors_origins`, as browser builds call the matchmaker from the page (see [CORS](/learn/matchmaking/matchmaker-in-depth.md#cross-origin-resource-sharing-cors)). The config allows `http://localhost:5173` (Vite), `http://localhost:8060` (Godot web) and `http://localhost:8080`.

☑️ Click **Create and Start**. Once your matchmaker shows **Online**, copy its **API URL** and **Auth Token**.

{% hint style="danger" %}
**Do not integrate Edgegap API endpoints in game client, as your API token provides unlimited access to your account. See** [Integration](/docs/api/integration.md) **for secure client-facing API endpoints and functions.**
{% endhint %}

{% hint style="info" %}
Matchmaking Auth Token is scoped to your matchmaker only, so it's safe to ship in game clients.
{% endhint %}

☑️ Start your clients with matchmaker settings:

* **Three.js** - `http://localhost:5173/?mm=<API URL>&mmToken=<Auth Token>`, or `VITE_MM_URL` / `VITE_MM_TOKEN` in `.env.local`,
* **Unity** - `--mm=<API URL> --mm-token=<Auth Token>`, WebGL `?mm=...&mmToken=...`, or *Edgegap Matchmaker* fields on `GameManager`,
* **Godot** - `-- --mm=<API URL> --mm-token=<Auth Token>`, web export `?mm=...&mmToken=...`.

☑️ Open two clients - matchmaking needs at least 2 players, and they can use different engines.

Each client shows "Searching for players..." and connects once a server is assigned. A `server` setting always takes priority over the matchmaker.

🙌 Congratulations on your first matchmade Deployment on Edgegap!

## 🔄 Match Lifecycle

1. Client creates a ticket with `POST /tickets` for `casual` profile, then checks it every 2 seconds with `GET /tickets/{id}`.
2. Matchmaker deploys `realtime-tanks` near the players. When the ticket reaches `HOST_ASSIGNED`, the client reads the assignment's FQDN and external port of `gameport`.
3. Client joins `wss://<fqdn>:<port>`. A freshly started server can answer 502 for a few seconds, so clients retry joining for up to 20 seconds (see [Connect to Server](/learn/matchmaking/matchmaker-in-depth.md#connect-to-server)).
4. Server calls `ARBITRIUM_DELETE_URL` to stop its own deployment when nobody joined within `FIRST_JOIN_TIMEOUT_S`, when everyone left for `EMPTY_TIMEOUT_S`, or when `MATCH_ROUNDS` rounds have been won. Your app version's max duration is the backstop.

The matchmaker client is small and has no dependencies, so it ports easily to other clients - see `web-threejs/src/Matchmaker.ts`, `unity/Assets/Scripts/EdgegapMatchmaker.cs` and `godot/scripts/edgegap_matchmaker.gd`.

See [Matchmaking](/learn/matchmaking.md) and [Deployments](/learn/orchestration/deployments.md) to learn more.

## 🚀 Going Further

* **Validate joiners** - matchmaker deployments get [`MM_TICKET_IDS`](/learn/matchmaking/matchmaker-in-depth.md#injected-variables). Have clients pass their ticket ID in join options and check it in `onAuth`, so only matched players can join.
* **Before launch** - set `inspect` to `false` in your matchmaker config, list your real origins, and raise max duration to fit your matches (see [Before Going Live](/learn/matchmaking/matchmaker-in-depth.md#before-going-live)).

## ⚠️ Known Issues

* Tested end to end on Edgegap with Colyseus 0.17, matchmaker 3.3.2, Godot 4.5.2 and Unity 6000.3:
  * direct - Node Colyseus clients and Godot desktop client joined a deployment over `wss://`, and the server stopped itself after everyone left,
  * matchmaker - Three.js in browser and Godot desktop client were matched onto one server, then Three.js and a Unity Windows build.
* Construct 3, Defold, GameMaker, Haxe and PlayCanvas `server` overrides, Godot web export and Unity WebGL are not tested yet.
