Skip to main content
Version: v4 (current)

Engine Plugins

Orchestrator is game-engine agnostic. While Unity is the built-in default, you can plug in any game engine -Godot, Unreal, a custom engine, or anything else -without forking the orchestrator.

An engine plugin tells the orchestrator how to handle engine-specific concerns like cache folders and container lifecycle hooks. Everything else (provisioning, git sync, logging, hooks, secrets) works the same regardless of engine.

How It Works

The orchestrator only needs to know two things about your engine:

  1. Which folders to cache between builds (e.g. Library for Unity, .godot/imported for Godot)
  2. What to run on container shutdown (e.g. Unity needs to return its license)

That's the entire EnginePlugin interface:

interface EnginePlugin {
name: string; // 'unity', 'godot', 'unreal', etc.
cacheFolders: string[]; // folders to cache, relative to project root
preStopCommand?: string; // shell command for container shutdown (optional)
}

Built-in: Unity

Unity is the default engine plugin. When you don't specify --engine or --engine-plugin, the orchestrator behaves exactly as it always has -caching the Library folder and returning the Unity license on container shutdown.

No configuration needed. Existing workflows are unchanged.

Using a Different Engine

Set engine and enginePlugin to load a community or custom engine plugin:

# GitHub Actions
- uses: game-ci/unity-builder@v4
with:
engine: godot
enginePlugin: '@game-ci/godot-engine'
targetPlatform: StandaloneLinux64
# CLI
game-ci build \
--engine godot \
--engine-plugin @game-ci/godot-engine \
--target-platform linux

Plugin Sources

Engine plugins can be loaded from three sources:

SourceFormatExample
NPM modulePackage name or local path@game-ci/godot-engine, ./my-plugin.js
CLI executablecli:<path>cli:/usr/local/bin/my-engine-plugin
Docker imagedocker:<image>docker:gameci/godot-engine-plugin

When no prefix is specified, the plugin is loaded as an NPM module.

NPM Module

The simplest way to distribute an engine plugin. Publish an NPM package that exports an EnginePlugin object:

// index.ts
export default {
name: 'godot',
cacheFolders: ['.godot/imported', '.godot/shader_cache'],
};

Supports default export, named plugin export, or module.exports:

// CommonJS
module.exports = {
name: 'godot',
cacheFolders: ['.godot/imported'],
};

Install the package in your project, then reference it:

enginePlugin: '@your-org/godot-engine'

Or point to a local file during development:

enginePlugin: './my-engine-plugin.js'

CLI Executable

For plugins written in any language (Go, Python, Rust, shell, etc.). The executable receives a get-engine-config argument and must print a JSON config on stdout:

$ my-engine-plugin get-engine-config
{"name": "godot", "cacheFolders": [".godot/imported"], "preStopCommand": ""}

Reference it with the cli: prefix:

enginePlugin: 'cli:/usr/local/bin/my-engine-plugin'

Docker Image

For containerized plugin distribution. The image is run with docker run --rm <image> get-engine-config and must print JSON config on stdout:

FROM alpine
COPY config.json /config.json
ENTRYPOINT ["sh", "-c", "cat /config.json"]

Reference it with the docker: prefix:

enginePlugin: 'docker:your-org/godot-engine-plugin'

Writing an Engine Plugin

To create a plugin for your engine, you need to answer two questions:

  1. What folders should be cached? These are directories that take a long time to regenerate but don't change between builds. For Unity this is Library, for Godot it's .godot/imported.

  2. Does your engine need cleanup on container shutdown? Unity needs to return its license. Most engines don't need anything here -just omit preStopCommand.

Example: Minimal Godot Plugin

export default {
name: 'godot',
cacheFolders: ['.godot/imported', '.godot/shader_cache'],
};

Example: Engine with License Cleanup

export default {
name: 'my-engine',
cacheFolders: ['Cache', 'Intermediate'],
preStopCommand: '/opt/my-engine/return-license.sh',
};

What the Plugin Controls

The engine plugin only controls orchestrator-level behavior that varies by engine:

BehaviorControlled by pluginNotes
Cache foldersYesWhich project folders to persist between builds
Container preStop hookYesShell command run on K8s container shutdown
Docker imageNoPassed by the caller via customImage or baseImage
Build scriptsNoOwned by the builder action (e.g. unity-builder)
Version detectionNoHandled by the caller or builder action
License activationNoHandled by the builder action's entrypoint

This keeps plugins minimal. A complete engine plugin is typically 3-5 lines of config.

Programmatic Usage

If you're building a custom integration, you can use the engine plugin API directly:

import { setEngine, getEngine, loadEngineFromModule } from '@game-ci/orchestrator';

// Load from an NPM package
const plugin = loadEngineFromModule('@game-ci/godot-engine');
setEngine(plugin);

// Or set inline
setEngine({
name: 'godot',
cacheFolders: ['.godot/imported'],
});

// Check current engine
console.log(getEngine().name); // 'godot'