How TetraForce runs Godot on AWS
Tags: godot, aws, gamedev, cloud
Published: 2020-11-06 07:27:40

TetraForce is an open-source multiplayer action-adventure RPG inspired by the popular Zelda game, Link's Awakening. It uses Godot’s built-in UDP networking library and scripting language for most of the game’s logic.

The main developers managing the project are fornclake and TheRetroDragon. Back in July, I got in contact with them to discuss putting TetraForce into the cloud! Within a week, TetraForce was running on AWS. Since then, there have been many gradual improvements to get the project where it is today.

For a summary of this project, the Amazon Elastic Container Service cluster manages and runs TetraForce’s containers using a serverless API. Initially, our cluster was hosted in an auto-scaling group of EC2 instances (Virtual Machines). Later, it was moved to AWS Fargate (Serverless Container Platform) to simplify scaling and reduce costs during periods of low utilization.

There is a Serverless REST API using Lambda for creating and joining rooms, which sends and sets server information, such as name and ECS task ID, in a DynamoDB table. When a server closes, an additional lambda removes it from the DynamoDB table.

Now let’s jump into some of the details.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/pujg9a9dp1hj0njyf34n.png)

# Dockerization

Godot being lightweight, is easy to Dockerize. For the TetraForce Docker image, it just installs dependencies, the Godot server runtime, and the TetraForce’s `pck` file. It can easily be extended to add additional `pck` files in the future for mods or expansions.

As of when this post was written, this is our working Dockerfile:
```
FROM centos:centos8

RUN yum install -y wget unzip libXcursor openssl openssl-libs libXinerama libXrandr-devel libXi alsa-lib pulseaudio-libs mesa-libGL

ENV GODOT_VERSION "3.2.2"

# Install Godot Server
RUN wget -q https://downloads.tuxfamily.org/godotengine/${GODOT_VERSION}/Godot_v${GODOT_VERSION}-stable_linux_headless.64.zip \
    && unzip Godot_v${GODOT_VERSION}-stable_linux_headless.64.zip \
    && mv Godot_v${GODOT_VERSION}-stable_linux_headless.64 /usr/local/bin/godot \
    && chmod +x /usr/local/bin/godot

# Create Runtime User
RUN useradd -d /tetra tetra


# Add pck file
ADD build/TetraForce.pck /tetra/TetraForce.pck

CMD /usr/local/bin/godot --main-pack /tetra/TetraForce.pck --empty-server-timeout=300
```
The CMD for the image is to run the Godot server runtime using the TetraForce `pck` file. You can notice in the run command we also pass a `--empty-server-timeout=300`; this is for our containers to close servers that have no active players automatically.

# Server Management

Each game room runs as a task in ECS. Amazon assigns those tasks a public IP. The client receives those IPs by interacting with TetraForce’s API.

##The REST API

The client can get room information and create new rooms by making HTTPS requests to the REST API. The REST API uses API Gateway to route requests to a Lambda functions.

The `/create_server` endpoint takes in an optional parameter of a name. If it does not have a name, it will randomly generate one. From there, it will spin up a new ECS task tagged with the room’s name and add a new entry into the DynamoDB table. The Lambda function will return whether it was a success and the name of the room created.

The `/get_servers` endpoint takes in an optional parameter of a room name and page. If it’s passed a name, it will only do a lookup for the given room. Otherwise, it will return a list of room information. The room information includes the room name, public IP, and port. It’s also modular to all for additional values in the future, such as version.

## CloudWatch Events

Server cleanup is managed by an additional Lambda function that is triggered by a CloudWatch Event Rule:

```
{
  "source": ["aws.ecs"],
  "detail-type": [ "ECS Task State Change" ]
}
```

This event will trigger the Lambda anytime an ECS Task in the cluster enters a new state. That leaves the responsibility of verifying the task’s state and then removing the room from the DynamoDB table if it’s no longer running to the Lambda function.

# Client Integration

Using Godot’s `HTTPRequest` Node, the client can hit the REST API hosted on AWS to request server information or create a new lobby.
```
export(String) var api_endpoint = "api.tetraforce.io"

var _http_client : HTTPRequest# Asynchronous coroutine.
# Requests API for data from a specific server
# Returns: {"message": [MESSAGE], "data" : [DATA] }
func get_server(lobby : String) -> Dictionary:
	_http_client.request("https://" + api_endpoint + "/get_servers?server=" + str(lobby), [], true, HTTPClient.METHOD_GET)
	var result = yield(_http_client, "request_completed")
	if len(result) > 3 and result[1] == 200:
		var json : JSONParseResult = JSON.parse(result[3].get_string_from_utf8())
		if json.error:
			return _build_error_message(json.error_string)
		return json.result
		
	return _build_error_message("Request failed!")
```

After that, the client will parse the returned JSON object into a dictionary. That dictionary will include the public server IP and Port, which is passed into the connection method when the player attempts to connect to the game server.

# What’s next?

The team is currently moving forward towards building a full demo of TetraForce within the next few months. The demo will include a handful of zones, a dungeon, and an eventual boss battle.

The team is looking to include skins in their game as Patreon rewards on the infrastructure side, so a user identity system is here on the horizon.

If you have any questions about anything, feel free to reach out to me on [Twitter](https://twitter.com/josephbmanley).

## Related Links

TetraForce Discord: [https://discord.gg/cxTBVCZ](https://discord.gg/cxTBVCZ)

TetraForce Repository: [https://github.com/fornclake/TetraForce](https://github.com/fornclake/TetraForce)

Infrastructure Repository: [https://github.com/josephbmanley/tetraforce-infrastructure](https://github.com/josephbmanley/tetraforce-infrastructure)