game-engines

Installation
SKILL.md

Game Engine Development: Unity & Godot

Develop games with Unity (C#) and Godot (GDScript) using command-line tools, editor automation, and MCP server integrations for asset management, scene editing, and build pipelines.

Direct Control (CLI / API / Scripting)

Unity CLI

# Unity paths
export UNITY_PATH="/Applications/Unity/Hub/Editor/2023.2.0f1/Unity.app/Contents/MacOS/Unity"
# Linux: /opt/unity/Editor/Unity
# Windows: "C:\Program Files\Unity\Hub\Editor\2023.2.0f1\Editor\Unity.exe"

# Create new Unity project
"$UNITY_PATH" -createProject /path/to/MyGame -quit

# Open Unity project
"$UNITY_PATH" -projectPath /path/to/MyGame

# Batch mode build (no GUI)
"$UNITY_PATH" -quit -batchmode -projectPath /path/to/MyGame \
  -executeMethod BuildScript.BuildWindows

# Build for multiple platforms
"$UNITY_PATH" -quit -batchmode -projectPath /path/to/MyGame \
  -buildTarget StandaloneWindows64 \
  -executeMethod BuildScript.Build

"$UNITY_PATH" -quit -batchmode -projectPath /path/to/MyGame \
  -buildTarget Android \
  -executeMethod BuildScript.BuildAndroid

# Import package
"$UNITY_PATH" -quit -batchmode -projectPath /path/to/MyGame \
  -importPackage /path/to/package.unitypackage

# Export package
"$UNITY_PATH" -quit -batchmode -projectPath /path/to/MyGame \
  -exportPackage Assets/MyFolder /path/to/output.unitypackage

# Run tests
"$UNITY_PATH" -quit -batchmode -projectPath /path/to/MyGame \
  -runTests -testResults /path/to/results.xml

# License activation (CI/CD)
"$UNITY_PATH" -quit -batchmode \
  -serial SB-XXXX-XXXX-XXXX-XXXX-XXXX \
  -username "your@email.com" \
  -password "yourpassword"

# Return license
"$UNITY_PATH" -quit -batchmode -returnlicense

# Execute arbitrary C# method
"$UNITY_PATH" -quit -batchmode -projectPath /path/to/MyGame \
  -executeMethod Namespace.ClassName.MethodName

Unity Build Script (Editor/BuildScript.cs)

using UnityEditor;
using UnityEngine;
using System.IO;

public class BuildScript
{
    static void BuildWindows()
    {
        string[] scenes = { "Assets/Scenes/MainMenu.unity", "Assets/Scenes/Game.unity" };
        string buildPath = "Builds/Windows/MyGame.exe";

        BuildPipeline.BuildPlayer(scenes, buildPath, BuildTarget.StandaloneWindows64, BuildOptions.None);
        Debug.Log($"Build completed: {buildPath}");
    }

    static void BuildAndroid()
    {
        string[] scenes = FindEnabledEditorScenes();
        string buildPath = "Builds/Android/MyGame.apk";

        PlayerSettings.Android.bundleVersionCode = GetVersionCode();
        PlayerSettings.bundleVersion = "1.0.0";

        BuildPipeline.BuildPlayer(scenes, buildPath, BuildTarget.Android, BuildOptions.None);
        Debug.Log($"Android build completed: {buildPath}");
    }

    static void BuildAllPlatforms()
    {
        BuildWindows();
        BuildAndroid();
        BuildiOS();
    }

    static string[] FindEnabledEditorScenes()
    {
        System.Collections.Generic.List<string> scenes = new System.Collections.Generic.List<string>();
        foreach (EditorBuildSettingsScene scene in EditorBuildSettings.scenes)
        {
            if (scene.enabled)
                scenes.Add(scene.path);
        }
        return scenes.ToArray();
    }

    static int GetVersionCode()
    {
        return System.DateTime.Now.Year * 10000 +
               System.DateTime.Now.Month * 100 +
               System.DateTime.Now.Day;
    }
}

Unity Asset Processing

// Assets/Editor/AssetProcessor.cs
using UnityEditor;
using UnityEngine;

public class AssetProcessor : AssetPostprocessor
{
    void OnPreprocessTexture()
    {
        TextureImporter importer = (TextureImporter)assetImporter;

        // Auto-configure sprites
        if (assetPath.Contains("Sprites"))
        {
            importer.textureType = TextureImporterType.Sprite;
            importer.spritePixelsPerUnit = 32;
            importer.filterMode = FilterMode.Point;
        }

        // Auto-configure UI textures
        if (assetPath.Contains("UI"))
        {
            importer.textureType = TextureImporterType.Sprite;
            importer.spritePixelsPerUnit = 100;
            importer.npotScale = TextureImporterNPOTScale.None;
        }
    }

    void OnPreprocessModel()
    {
        ModelImporter importer = (ModelImporter)assetImporter;

        // Default model settings
        importer.importAnimation = true;
        importer.importBlendShapes = true;
        importer.importCameras = false;
        importer.importLights = false;
    }

    static void OnPostprocessAllAssets(
        string[] importedAssets,
        string[] deletedAssets,
        string[] movedAssets,
        string[] movedFromAssetPaths)
    {
        foreach (string asset in importedAssets)
        {
            Debug.Log($"Imported: {asset}");
        }
    }
}

Godot CLI

# Godot paths
export GODOT_PATH="/Applications/Godot.app/Contents/MacOS/Godot"
# Linux: /usr/bin/godot
# Windows: "C:\Program Files\Godot\godot.exe"

# Create new Godot project
mkdir MyGame
cd MyGame
"$GODOT_PATH" --path . --editor --quit

# Open Godot editor
"$GODOT_PATH" --path /path/to/MyGame --editor

# Run game
"$GODOT_PATH" --path /path/to/MyGame

# Run specific scene
"$GODOT_PATH" --path /path/to/MyGame res://scenes/Level1.tscn

# Export project (requires export preset)
"$GODOT_PATH" --path /path/to/MyGame --export "Windows Desktop" builds/windows/MyGame.exe
"$GODOT_PATH" --path /path/to/MyGame --export "Linux/X11" builds/linux/MyGame.x86_64
"$GODOT_PATH" --path /path/to/MyGame --export "Android" builds/android/MyGame.apk
"$GODOT_PATH" --path /path/to/MyGame --export "HTML5" builds/html5/index.html

# Export debug build
"$GODOT_PATH" --path /path/to/MyGame --export-debug "Windows Desktop" builds/debug/MyGame.exe

# Run tests
"$GODOT_PATH" --path /path/to/MyGame -s res://tests/run_tests.gd --quit

# Script mode (headless)
"$GODOT_PATH" --path /path/to/MyGame -s res://scripts/generate_assets.gd --quit

# Check script syntax
"$GODOT_PATH" --path /path/to/MyGame --check-only res://scripts/Player.gd

# Convert 3D scene to GLTF
"$GODOT_PATH" --path /path/to/MyGame --export-pack "Export Preset" output.pck

# Print available export presets
"$GODOT_PATH" --path /path/to/MyGame --export-list

Godot Project Setup (project.godot)

[application]
config/name="My Awesome Game"
run/main_scene="res://scenes/MainMenu.tscn"
config/icon="res://icon.png"

[display]
window/size/width=1920
window/size/height=1080
window/size/resizable=true
window/stretch/mode="2d"
window/stretch/aspect="expand"

[rendering]
quality/driver/driver_name="GLES3"
vram_compression/import_etc=true
quality/shadows/filter_mode=2

[physics]
common/enable_pause_aware_picking=true
2d/default_gravity=980

[input]
move_left={
"deadzone": 0.5,
"events": [ Object(InputEventKey,"resource_local_to_scene":false,"resource_name":"","device":0,"alt":false,"shift":false,"control":false,"meta":false,"command":false,"pressed":false,"scancode":65,"unicode":0,"echo":false,"script":null)
 ]
}

GDScript Examples

# Player.gd - Player controller
extends KinematicBody2D

export var speed = 200
export var jump_force = 400
export var gravity = 800

var velocity = Vector2()

func _physics_process(delta):
    # Horizontal movement
    var input_x = Input.get_action_strength("move_right") - Input.get_action_strength("move_left")
    velocity.x = input_x * speed

    # Gravity
    velocity.y += gravity * delta

    # Jump
    if is_on_floor() and Input.is_action_just_pressed("jump"):
        velocity.y = -jump_force

    # Move
    velocity = move_and_slide(velocity, Vector2.UP)

# GameManager.gd - Singleton autoload
extends Node

var score = 0
var level = 1
var player_health = 100

signal score_changed(new_score)
signal level_changed(new_level)

func add_score(amount):
    score += amount
    emit_signal("score_changed", score)

func next_level():
    level += 1
    emit_signal("level_changed", level)

func save_game():
    var save_file = File.new()
    save_file.open("user://savegame.save", File.WRITE)
    var save_data = {
        "score": score,
        "level": level,
        "health": player_health
    }
    save_file.store_line(to_json(save_data))
    save_file.close()

func load_game():
    var save_file = File.new()
    if not save_file.file_exists("user://savegame.save"):
        return

    save_file.open("user://savegame.save", File.READ)
    var save_data = parse_json(save_file.get_line())
    save_file.close()

    score = save_data.score
    level = save_data.level
    player_health = save_data.health

# AssetGenerator.gd - Procedural generation script
extends Node

func generate_tilemap(width, height):
    var tilemap = TileMap.new()
    tilemap.cell_size = Vector2(32, 32)

    for x in range(width):
        for y in range(height):
            if randf() > 0.7:
                tilemap.set_cell(x, y, 0)  # Wall tile
            else:
                tilemap.set_cell(x, y, 1)  # Floor tile

    return tilemap

func create_enemy_spawner(position, enemy_scene):
    var spawner = Node2D.new()
    spawner.position = position
    spawner.set_script(preload("res://scripts/EnemySpawner.gd"))
    spawner.enemy_scene = enemy_scene
    return spawner

CI/CD Build Script

#!/bin/bash
# build_game.sh - Automated build for Unity and Godot

set -e

GAME_ENGINE=${1:-unity}  # unity or godot
PROJECT_PATH=${2:-.}

if [ "$GAME_ENGINE" = "unity" ]; then
    echo "Building Unity project..."

    # Activate license (CI environment)
    if [ -n "$UNITY_LICENSE" ]; then
        echo "$UNITY_LICENSE" | base64 -d > Unity_v2023.x.ulf
        "$UNITY_PATH" -quit -batchmode -nographics \
            -manualLicenseFile Unity_v2023.x.ulf
    fi

    # Build all platforms
    "$UNITY_PATH" -quit -batchmode -nographics \
        -projectPath "$PROJECT_PATH" \
        -executeMethod BuildScript.BuildAllPlatforms

    # Run tests
    "$UNITY_PATH" -quit -batchmode -nographics \
        -projectPath "$PROJECT_PATH" \
        -runTests -testResults tests/results.xml

    # Return license
    "$UNITY_PATH" -quit -batchmode -returnlicense

elif [ "$GAME_ENGINE" = "godot" ]; then
    echo "Building Godot project..."

    # Export for all platforms
    for preset in "Windows Desktop" "Linux/X11" "HTML5"; do
        echo "Exporting $preset..."
        "$GODOT_PATH" --path "$PROJECT_PATH" \
            --export "$preset" "builds/${preset// /_}/game"
    done

    # Run tests
    "$GODOT_PATH" --path "$PROJECT_PATH" \
        -s res://tests/run_tests.gd --quit
fi

echo "Build complete!"

MCP Server Integration

Add this to .codebuddy/mcp.json:

{
  "mcpServers": {
    "unity": {
      "command": "npx",
      "args": ["-y", "@codergamesters/mcp-unity"],
      "env": {
        "UNITY_PROJECT_PATH": "/path/to/UnityProject",
        "UNITY_EDITOR_PATH": "/Applications/Unity/Hub/Editor/2023.2.0f1/Unity.app/Contents/MacOS/Unity"
      }
    },
    "godot": {
      "command": "npx",
      "args": ["-y", "@bradypp/godot-mcp"],
      "env": {
        "GODOT_PROJECT_PATH": "/path/to/GodotProject",
        "GODOT_EXECUTABLE_PATH": "/Applications/Godot.app/Contents/MacOS/Godot"
      }
    }
  }
}

Unity MCP Tools

  • list_scenes - List all scenes in the project with build index
  • get_scene - Get scene hierarchy and GameObjects
  • create_gameobject - Create GameObject with components in scene
  • modify_component - Update component properties (Transform, Rigidbody, etc.)
  • list_assets - List assets by type (prefabs, materials, scripts)
  • import_asset - Import external asset into project
  • create_prefab - Create prefab from GameObject
  • list_scripts - List all C# scripts with namespaces
  • run_build - Execute build for target platform
  • run_tests - Execute Unity Test Framework tests
  • get_player_settings - Get PlayerSettings (version, bundle ID, etc.)
  • set_player_settings - Update PlayerSettings
  • install_package - Install Unity package via Package Manager

Godot MCP Tools

  • list_scenes - List all .tscn scene files in project
  • get_scene - Get scene tree structure with node types
  • create_node - Add node to scene tree
  • modify_node - Update node properties and scripts
  • list_resources - List resources by type (.tres, .res files)
  • get_script - Read GDScript file contents
  • edit_script - Modify GDScript file
  • list_autoloads - List singleton autoload scripts
  • export_project - Export for platform using preset
  • run_tests - Execute GDScript unit tests
  • get_project_settings - Read project.godot settings
  • set_project_settings - Update project.godot settings
  • import_asset - Import asset and configure importer

MCP Usage Examples

// Unity - List all scenes
const scenes = await mcp.callTool('unity', 'list_scenes', {});
console.log(scenes.map(s => s.name));

// Unity - Create player GameObject
await mcp.callTool('unity', 'create_gameobject', {
  scene: 'Assets/Scenes/Game.unity',
  name: 'Player',
  components: [
    { type: 'Rigidbody', properties: { mass: 1.5, drag: 0.5 } },
    { type: 'CapsuleCollider', properties: { height: 2, radius: 0.5 } },
    { type: 'PlayerController', properties: { speed: 5, jumpForce: 10 } }
  ],
  position: { x: 0, y: 1, z: 0 }
});

// Unity - Build for Windows
await mcp.callTool('unity', 'run_build', {
  target: 'StandaloneWindows64',
  outputPath: 'Builds/Windows/MyGame.exe',
  development: false
});

// Godot - Create enemy node
await mcp.callTool('godot', 'create_node', {
  scene: 'res://scenes/Level1.tscn',
  parentPath: 'Root/Enemies',
  nodeType: 'KinematicBody2D',
  nodeName: 'Enemy',
  script: 'res://scripts/Enemy.gd',
  properties: {
    position: { x: 100, y: 200 },
    speed: 150
  }
});

// Godot - Edit player script
await mcp.callTool('godot', 'edit_script', {
  scriptPath: 'res://scripts/Player.gd',
  operation: 'insert',
  line: 15,
  content: `
func take_damage(amount):
    health -= amount
    if health <= 0:
        die()
`
});

// Godot - Export for web
await mcp.callTool('godot', 'export_project', {
  preset: 'HTML5',
  outputPath: 'builds/html5/index.html',
  debug: false
});

Common Workflows

1. Unity - Automated Asset Import and Processing

#!/bin/bash
# import_assets.sh - Batch import and configure assets

UNITY_PROJECT="/path/to/UnityProject"
ASSETS_DIR="$UNITY_PROJECT/Assets/ImportQueue"

# Watch for new assets
inotifywait -m -r -e create "$ASSETS_DIR" | while read path action file; do
    echo "New asset detected: $file"

    # Trigger Unity import
    "$UNITY_PATH" -quit -batchmode -projectPath "$UNITY_PROJECT" \
        -executeMethod AssetImporter.ProcessNewAssets
done
// Editor/AssetImporter.cs
using UnityEditor;
using UnityEngine;
using System.IO;

public class AssetImporter
{
    [MenuItem("Assets/Process Import Queue")]
    static void ProcessNewAssets()
    {
        string importPath = "Assets/ImportQueue";

        if (!Directory.Exists(importPath))
            return;

        string[] files = Directory.GetFiles(importPath, "*.*", SearchOption.AllDirectories);

        foreach (string file in files)
        {
            if (file.EndsWith(".meta")) continue;

            string assetPath = file.Replace("\\", "/");
            ProcessAsset(assetPath);
        }

        AssetDatabase.Refresh();
    }

    static void ProcessAsset(string assetPath)
    {
        if (assetPath.Contains("Textures"))
        {
            TextureImporter importer = AssetImporter.GetAtPath(assetPath) as TextureImporter;
            if (importer != null)
            {
                importer.textureType = TextureImporterType.Sprite;
                importer.spritePixelsPerUnit = 32;
                importer.filterMode = FilterMode.Point;
                importer.SaveAndReimport();
            }

            // Move to organized folder
            string targetPath = assetPath.Replace("ImportQueue", "Sprites");
            AssetDatabase.MoveAsset(assetPath, targetPath);
        }
        else if (assetPath.EndsWith(".fbx") || assetPath.EndsWith(".obj"))
        {
            ModelImporter importer = AssetImporter.GetAtPath(assetPath) as ModelImporter;
            if (importer != null)
            {
                importer.importAnimation = true;
                importer.animationType = ModelImporterAnimationType.Generic;
                importer.SaveAndReimport();
            }

            string targetPath = assetPath.Replace("ImportQueue", "Models");
            AssetDatabase.MoveAsset(assetPath, targetPath);
        }

        Debug.Log($"Processed and moved: {assetPath}");
    }
}

2. Godot - Procedural Level Generation

# scripts/LevelGenerator.gd
extends Node

const ROOM_WIDTH = 20
const ROOM_HEIGHT = 15
const TILE_SIZE = 32

var tilemap
var rooms = []

func _ready():
    generate_dungeon(10)  # Generate 10 rooms

func generate_dungeon(num_rooms):
    tilemap = TileMap.new()
    tilemap.cell_size = Vector2(TILE_SIZE, TILE_SIZE)
    add_child(tilemap)

    # Generate rooms
    for i in range(num_rooms):
        var room = generate_room(i)
        rooms.append(room)

    # Connect rooms with corridors
    for i in range(rooms.size() - 1):
        create_corridor(rooms[i], rooms[i + 1])

    # Place enemies and items
    populate_dungeon()

    # Save generated level
    var scene = PackedScene.new()
    scene.pack(self)
    ResourceSaver.save("res://generated/Level_" + str(OS.get_unix_time()) + ".tscn", scene)

func generate_room(index):
    var x = (index % 3) * (ROOM_WIDTH + 5)
    var y = (index / 3) * (ROOM_HEIGHT + 5)

    # Create floor
    for i in range(ROOM_WIDTH):
        for j in range(ROOM_HEIGHT):
            tilemap.set_cell(x + i, y + j, 1)  # Floor tile

    # Create walls
    for i in range(ROOM_WIDTH):
        tilemap.set_cell(x + i, y, 0)  # Top wall
        tilemap.set_cell(x + i, y + ROOM_HEIGHT - 1, 0)  # Bottom wall

    for j in range(ROOM_HEIGHT):
        tilemap.set_cell(x, y + j, 0)  # Left wall
        tilemap.set_cell(x + ROOM_WIDTH - 1, y + j, 0)  # Right wall

    return Rect2(x, y, ROOM_WIDTH, ROOM_HEIGHT)

func create_corridor(room1, room2):
    var start = room1.position + room1.size / 2
    var end = room2.position + room2.size / 2

    # Horizontal corridor
    var x_dir = 1 if end.x > start.x else -1
    for x in range(abs(end.x - start.x)):
        tilemap.set_cell(start.x + x * x_dir, start.y, 1)

    # Vertical corridor
    var y_dir = 1 if end.y > start.y else -1
    for y in range(abs(end.y - start.y)):
        tilemap.set_cell(end.x, start.y + y * y_dir, 1)

func populate_dungeon():
    var enemy_scene = preload("res://entities/Enemy.tscn")
    var item_scene = preload("res://entities/Item.tscn")

    for room in rooms:
        # Spawn 2-4 enemies per room
        for i in range(randi() % 3 + 2):
            var enemy = enemy_scene.instance()
            enemy.position = Vector2(
                (room.position.x + 1 + randi() % int(room.size.x - 2)) * TILE_SIZE,
                (room.position.y + 1 + randi() % int(room.size.y - 2)) * TILE_SIZE
            )
            add_child(enemy)

        # Spawn 1-2 items per room
        for i in range(randi() % 2 + 1):
            var item = item_scene.instance()
            item.position = Vector2(
                (room.position.x + 1 + randi() % int(room.size.x - 2)) * TILE_SIZE,
                (room.position.y + 1 + randi() % int(room.size.y - 2)) * TILE_SIZE
            )
            add_child(item)

3. Unity - Automated Testing Pipeline

// Tests/PlayMode/GameplayTests.cs
using NUnit.Framework;
using UnityEngine;
using UnityEngine.TestTools;
using System.Collections;

public class GameplayTests
{
    GameObject player;

    [SetUp]
    public void SetUp()
    {
        player = GameObject.Instantiate(Resources.Load<GameObject>("Prefabs/Player"));
    }

    [TearDown]
    public void TearDown()
    {
        Object.Destroy(player);
    }

    [UnityTest]
    public IEnumerator PlayerCanJump()
    {
        var controller = player.GetComponent<PlayerController>();
        var initialY = player.transform.position.y;

        controller.Jump();
        yield return new WaitForSeconds(0.5f);

        Assert.Greater(player.transform.position.y, initialY);
    }

    [Test]
    public void PlayerTakesDamage()
    {
        var health = player.GetComponent<Health>();
        var initialHealth = health.currentHealth;

        health.TakeDamage(10);

        Assert.AreEqual(initialHealth - 10, health.currentHealth);
    }

    [UnityTest]
    public IEnumerator EnemySpawnsCorrectly()
    {
        var spawner = new GameObject().AddComponent<EnemySpawner>();
        spawner.enemyPrefab = Resources.Load<GameObject>("Prefabs/Enemy");
        spawner.spawnInterval = 1f;

        spawner.StartSpawning();
        yield return new WaitForSeconds(1.5f);

        var enemies = GameObject.FindGameObjectsWithTag("Enemy");
        Assert.GreaterOrEqual(enemies.Length, 1);

        Object.Destroy(spawner.gameObject);
    }
}
# Run Unity tests in CI
"$UNITY_PATH" -quit -batchmode -projectPath /path/to/MyGame \
    -runTests -testPlatform PlayMode \
    -testResults tests/playmode_results.xml

"$UNITY_PATH" -quit -batchmode -projectPath /path/to/MyGame \
    -runTests -testPlatform EditMode \
    -testResults tests/editmode_results.xml

# Parse results
if grep -q "failures=\"0\"" tests/*.xml; then
    echo "All tests passed!"
else
    echo "Tests failed!"
    exit 1
fi

4. Cross-Platform Build and Deploy

#!/bin/bash
# deploy.sh - Build and deploy to multiple platforms

PROJECT_NAME="MyAwesomeGame"
VERSION="1.0.0"

# Unity builds
build_unity() {
    echo "Building Unity project..."

    # Windows
    "$UNITY_PATH" -quit -batchmode -projectPath . \
        -buildTarget StandaloneWindows64 \
        -executeMethod BuildScript.BuildWindows

    # macOS
    "$UNITY_PATH" -quit -batchmode -projectPath . \
        -buildTarget StandaloneOSX \
        -executeMethod BuildScript.BuildMac

    # Linux
    "$UNITY_PATH" -quit -batchmode -projectPath . \
        -buildTarget StandaloneLinux64 \
        -executeMethod BuildScript.BuildLinux

    # Android
    "$UNITY_PATH" -quit -batchmode -projectPath . \
        -buildTarget Android \
        -executeMethod BuildScript.BuildAndroid

    # WebGL
    "$UNITY_PATH" -quit -batchmode -projectPath . \
        -buildTarget WebGL \
        -executeMethod BuildScript.BuildWebGL
}

# Godot builds
build_godot() {
    echo "Building Godot project..."

    "$GODOT_PATH" --path . --export "Windows Desktop" \
        "builds/windows/${PROJECT_NAME}_${VERSION}.exe"

    "$GODOT_PATH" --path . --export "Linux/X11" \
        "builds/linux/${PROJECT_NAME}_${VERSION}.x86_64"

    "$GODOT_PATH" --path . --export "Mac OSX" \
        "builds/macos/${PROJECT_NAME}_${VERSION}.zip"

    "$GODOT_PATH" --path . --export "HTML5" \
        "builds/html5/index.html"
}

# Package builds
package_builds() {
    cd builds

    # Zip each platform
    for platform in windows linux macos; do
        if [ -d "$platform" ]; then
            zip -r "${PROJECT_NAME}_${VERSION}_${platform}.zip" "$platform"
        fi
    done

    cd ..
}

# Upload to itch.io
upload_itchio() {
    butler push builds/windows/${PROJECT_NAME}_${VERSION}.zip user/game:windows
    butler push builds/linux/${PROJECT_NAME}_${VERSION}.zip user/game:linux
    butler push builds/macos/${PROJECT_NAME}_${VERSION}.zip user/game:osx
    butler push builds/html5 user/game:html5
}

# Main
if [ "$1" = "unity" ]; then
    build_unity
elif [ "$1" = "godot" ]; then
    build_godot
else
    echo "Usage: ./deploy.sh [unity|godot]"
    exit 1
fi

package_builds
upload_itchio

echo "Deployment complete!"

5. Live Hot-Reload Development Setup

# addons/hot_reload/hot_reload.gd
tool
extends EditorPlugin

var file_watcher
var last_modified = {}

func _enter_tree():
    file_watcher = Timer.new()
    file_watcher.wait_time = 0.5
    file_watcher.connect("timeout", self, "_check_files")
    add_child(file_watcher)
    file_watcher.start()

    print("Hot Reload: Enabled")

func _exit_tree():
    if file_watcher:
        file_watcher.queue_free()

func _check_files():
    var scripts_to_reload = []

    # Check all GDScript files
    var dir = Directory.new()
    _scan_directory("res://", dir, scripts_to_reload)

    # Reload changed scripts
    for script_path in scripts_to_reload:
        print("Hot Reload: Reloading " + script_path)
        var script = load(script_path)

        # Find all instances using this script
        var root = get_tree().edited_scene_root
        if root:
            _reload_script_instances(root, script)

func _scan_directory(path, dir, scripts_to_reload):
    if dir.open(path) == OK:
        dir.list_dir_begin(true, true)
        var file_name = dir.get_next()

        while file_name != "":
            var full_path = path + "/" + file_name

            if dir.current_is_dir():
                _scan_directory(full_path, dir, scripts_to_reload)
            elif file_name.ends_with(".gd"):
                var file = File.new()
                var modified = file.get_modified_time(full_path)

                if not last_modified.has(full_path) or last_modified[full_path] < modified:
                    last_modified[full_path] = modified
                    scripts_to_reload.append(full_path)

            file_name = dir.get_next()

        dir.list_dir_end()

func _reload_script_instances(node, script):
    if node.get_script() == script:
        node.set_script(null)
        node.set_script(script)

    for child in node.get_children():
        _reload_script_instances(child, script)
Related skills
Installs
2
GitHub Stars
7
First Seen
Mar 18, 2026
Security Audits