Skip to content

๐Ÿ• Perro โ€” A game engine written in Rust that utilizes a unique transpilation system that allows developers to write in high level languages for quick iteration and ease of use, while achieving native performance.

License

Notifications You must be signed in to change notification settings

PerroEngine/Perro

๐Ÿ• Perro Game Engine

Perro Logo

Perro is an experimental, open-source game engine written in Rust, designed as a modern alternative to engines like Unreal, Godot, and Unity.

It focuses on performance, flexibility, and ease of use with a unique scripting system that transpiles to Rust for native performance:

  • ๐Ÿถ Pup DSL โ€“ a beginner-friendly, lightweight scripting language that compiles to Rust for native performance. Write your gameplay logic in Pup and get the speed of Rust.
  • ๐ŸŽจ FUR (Flexible UI Rules) โ€“ a declarative UI system with layouts, panels, and boxing for easy UI design.
  • ๐Ÿ“ฆ Type-Safe Transpilation โ€“ full type checking and casting during code generation. All scripts transpile to Rust under the hood.
  • โšก Optimized Release Builds โ€“ scripts and assets statically link into your final binary.
  • ๐Ÿ”— Decoupled Signal System โ€“ global, name-based signals that completely decouple emitters from listeners. Use on SIGNALNAME() {} shorthand for automatic connections.

๐Ÿ“š Full Documentation โ†’


๐Ÿš€ Quick Start

Install Syntax Highlighting

First, install the Perro syntax highlighting extension for VS Code:

# From the repository root
code --install-extension .vscode-extensions/perro-syntax

This provides syntax highlighting for .pup, .fur, and .scn files.

Create Your First Project

# Create a new project (defaults to workspace/projects/ProjectName)
cargo run -p perro_core -- new MyGame

# Or specify a custom path
cargo run -p perro_core -- new MyGame /path/to/project

This creates a new project structure with:

  • project.toml - Project configuration
  • res/ - Resources folder (scenes, scripts, assets)
  • res/main.scn - Default scene with a "World" Node2D and Camera
  • .perro/ - Build artifacts (generated by compiler)
  • .gitignore - Ignores generated files

Run Your Project

# Build scripts and run project in one command
cargo run -p perro_core -- --path /path/to/project --dev

The --dev command automatically:

  1. Compiles perro_dev in release mode (only recompiles on engine changes - longer compile time)
  2. Transpiles scripts (Pup โ†’ Rust) - typically under 5 seconds
  3. Compiles scripts into a DLL - only recompiles on Pup source changes
  4. Runs the project - instant if no changes detected

Smart Recompilation:

  • No changes: Instant run (no compilation needed)
  • Pup source changes: Fast recompile (~1-5 seconds)
  • Engine changes: Full recompile of perro_dev (longer, but only when needed)

๐Ÿถ Pup DSL

Pup is Perro's built-in scripting language โ€” simple, readable, and compiles to Rust.

Script Definition

Scripts are defined with the @script directive followed by a name and the node type they extend:

@script Player extends Sprite2D
    var speed = 7.5

    on init() {
        Console.print("Player is ready!")
        set_speed(2.1)
    }

    fn set_speed(new_speed: float) {
        speed = new_speed
    }

    on update() {
        var delta = Time.get_delta()
        self.transform.position.x += speed * delta
    }

Function Syntax:

  • on - Used for lifecycle methods (init, update, draw, fixed_update) and signal handlers (auto-connection). Lifecycle methods cannot be called directly - they're hooks that the engine calls automatically.
  • fn - Used for regular methods that you can call from your code.

Accessing Nodes

Get child nodes using self.get_node("name"). This returns a node reference that you can use with both . (engine-level fields/functions) and :: (custom script functions):

@script Player extends Sprite2D

on init() {
    // Get a child node - returns a node reference
    self.get_node("Weapon").name = "Sword"
    
    // Store in a variable for reuse
    var weapon = self.get_node("Weapon")
    
    // Use . for engine-level fields and functions
    weapon.name = "Axe"
    weapon.transform.position.x = 10.0
    
    // Use :: for script-level operations (non-concrete syntax)
    weapon::reload()
    weapon::ammo = 100
    
    // Chain node access
    self.get_node("Weapon").get_node("Scope").visible = true
}

Key Points:

  • . (dot) - Used for concrete engine-level fields and functions that nodes expose (like transform, name, texture, etc.). These are direct, in-place mutations or reads on the node itself.
  • :: (double colon) - Used for non-concrete syntax that the engine exposes - script-level operations that cross the script boundary. This allows you to differentiate between concrete operations and script-level operations.

Why the distinction matters:

  • self.name vs self::name - Differentiates between engine field access and script variable access
  • self.transform vs transform - Makes it clear when you're accessing the engine field vs a local variable
  • enemy.name vs enemy::name - Same distinction for other node references

The :: syntax is both a compiler hint (telling the transpiler this is a script-level operation) and a visual indicator (helping you see the difference between concrete engine operations and dynamic script operations).

Signals & Event Handling

Perro uses a global, decoupled signal system. Signals are identified by name strings, and any script can listen for any signal without needing a reference to the emitter. This completely decouples signalers from listeners.

Signal Shorthand: on SIGNALNAME() {}

The easiest way to handle signals is using the on keyword shorthand. This automatically creates a function and connects it to the signal at script initialization. The on keyword is also used for lifecycle methods (init, update, draw, fixed_update), making it clear these are hooks that connect your script to the engine's lifecycle.

@script GameManager extends Node

on start_Pressed() {
    Console.print("Start button was pressed!")
    // Start the game...
}

on pause_Pressed() {
    Console.print("Game paused")
}

The on syntax automatically:

  • Connects the code inside to the signal matching the same name
  • For lifecycle methods (init, update, draw, fixed_update), connects your script to the engine's lifecycle hooks (these cannot be called directly)

Manual Signal Connection

You can also manually connect signals using Signal.connect(). When connecting to script-level functions (non-concrete operations), use :: to indicate it's a script boundary operation:

@script Player extends Sprite2D

var enemy: Node2D
var bob: Sprite2D

on init() {
    // Connect to a signal on self (function name as string)
    Signal.connect("player_Died", on_player_died)
    
    // Connect to script-level functions on other nodes using ::
    Signal.connect("enemy_Defeated", enemy::on_enemy_defeated)
    Signal.connect("bob_Pressed", bob::on_bob_pressed)
}

fn on_player_died() {
    Console.print("Player died!")
}

Why :: for script-level operations?

  • :: indicates non-concrete syntax that the engine exposes - script-level operations that cross the script boundary
  • This allows you to differentiate between concrete engine operations (.) and dynamic script operations (::)
  • Use . for concrete engine fields/functions (like node.name, node.transform) - direct, in-place operations
  • Use :: for script-level operations (like node::name, node::custom_function()) - dynamic calls across script boundaries
  • The distinction helps both the compiler (knowing what type of operation to generate) and you (seeing the difference visually)

Decoupled Signal System Example

Here's a complete example showing how signals work across different scripts:

FUR UI File (res/ui.fur):

    [Button id=start]
        Start Game
    [/Button]

Game Manager Script (res/game_manager.pup):

@script GameManager extends Node

on init() {
    Console.print("Game manager ready, listening for start button...")
}

// Listen for the start button signal (emitted automatically by the button)
on start_Pressed() {
    Console.print("Starting the game!")
    // Initialize game state, load level, etc.
}

Key Points:

  • The button in FUR automatically emits start_Pressed when clicked (based on its id)
  • The game manager doesn't need a reference to the button
  • The game manager doesn't even need to be in the same scene
  • Any script anywhere can listen for start_Pressed by name if connected
  • The signal system is completely global and decoupled

This decoupling means you can:

  • Have UI buttons that emit signals without any scripts attached
  • Have game logic scripts that listen for signals without knowing where they come from
  • Easily add new listeners or emitters without modifying existing code
  • Test signals independently of their sources

๐ŸŽจ FUR (Flexible UI Rules)

FUR is Perro's declarative UI system for building layouts and UI panels.

Basic UI

    [Panel bg=sea-5 padding=4]
        [Text font-weight=bold text-color=white text-size=xl]
            Hello Perro!
        [/Text]
    [/Panel]

UI Composition with Includes

FUR supports powerful composition using [Include] to include other FUR files:

    [Panel bg=dark-3]
        [Include path="res://ui/header.fur"/]
        [Include path="res://ui/menu.fur"/]
        [Include path="res://ui/footer.fur"/]
    [/Panel]

This allows you to:

  • Break UI into reusable components
  • Compose complex UIs from smaller pieces
  • Maintain consistent styling across your application
  • Easily swap out UI sections

Current Features:

  • Layouts and child layouts
  • Panels and boxing
  • Styling and padding
  • UI composition with includes

See perro_editor/res/fur for real examples of FUR in use.


๐Ÿ‘ฉโ€๐Ÿ’ป Development

Development Workflow

Quick Dev Command (Build + Run):

# Build scripts and run project in one command
cargo run -p perro_core -- --path /path/to/project --dev

The --dev command automatically:

  1. Compiles perro_dev in release mode (only recompiles on engine changes - longer compile time)
  2. Transpiles scripts (Pup โ†’ Rust) - typically under 5 seconds
  3. Compiles scripts into a DLL - only recompiles on Pup source changes
  4. Runs the project - instant if no changes detected

Smart Recompilation:

  • No changes: Instant run (no compilation needed)
  • Pup source changes: Fast recompile (~1-5 seconds)
  • Engine changes: Full recompile of perro_dev (longer, but only when needed)

Manual Workflow (if you prefer more control):

Build Scripts Only:

# Compile scripts only (for testing changes)
cargo run -p perro_core -- --path /path/to/project --scripts

Iteration Cycle:

  • Make changes to scripts in res/*
  • Re-run --dev - it will only recompile what changed
  • Fast iteration cycle (instant if no changes, ~1โ€“5s for script changes)

โš ๏ธ Source Mode Performance Considerations

When building games from source, the runtime (perro_dev) runs in debug mode by default, which means it's unoptimized. This is fine for development, but if you need better performance:

For better performance, run in release mode:

# Run perro_dev in release mode for better performance
cargo run --release -p perro_dev -- --path /path/to/project

If you're making engine changes:

  • Rebuild the runtime in release mode when you make engine changes:
    cargo build --release -p perro_dev
  • Then run the release binary:
    ./target/release/perro_dev.exe --path /path/to/project

If you're NOT making engine changes:

  • Precompile the runtime in release mode once:
    cargo build --release -p perro_dev
  • Then use the release binary for all your game development:
    ./target/release/PerroDevRuntime.exe --path /path/to/project
  • If no path is provided, the release binary must be in the same directory as a project folder with a valid project.toml in it.

Why? The source mode workflow (cargo run) is optimized for engine development, not heavy game operations. The engine (perro_core) is primarily designed for compiling scripts and building projects, not for active development. For better performance during game development, use --release flag or a pre-built release binary.

Release Build

Build Final Release:

# Build complete release (compiles everything statically and strips out console logs)
cargo run -p perro_core -- --path /path/to/project --project

Build Verbose Release (with console window):

# Build release with verbose output and visible console
cargo run -p perro_core -- --path /path/to/project --project --verbose

This:

  • Transpiles all scripts โ†’ Rust
  • Compiles scripts + project into a single binary
  • Embeds assets and scripts statically
  • Produces an optimized, distributable executable
  • Verbose mode: Removes Windows subsystem flag so console is visible and makes console logs visible (useful for debugging)

Result: A single executable with no external dependencies or DLLs.

Making Your First Game

  1. Create a new project using the CLI (see above)
  2. Write scripts in Pup in res/ folder
  3. Design scenes by editing res/*.scn JSON files
  4. Design UI with FUR files in res/
  5. Follow the development workflow above to test and iterate
  6. Build release when ready to distribute

๐Ÿ”„ Dev vs Release

Dev Mode (DLL Loading)

  • Scripts are transpiled to Rust, compiled into a DLL
  • Engine loads the DLL at runtime
  • Load files from disk
  • Make changes โ†’ recompile (~1โ€“3s) โ†’ restart to see updates

Release Mode (Static Linking)

  • All scripts transpile โ†’ Rust
  • Statically linked into final binary
  • Result:
    • Single executable (no DLLs, no source included)
    • Optimized machine code from LLVM
    • Scenes, FUR files, images, etc. are all statically embedded
    • Your source scripts are protected

๐Ÿ› ๏ธ For Engine Contributors & Development

This repository contains the Perro engine source code. To build and work on the engine itself:

Prerequisites

  • Rust 1.92.0 or later (GNU toolchain required - this is what ships with the editor binary for compilation)
  • Cargo

โš ๏ธ Linux System Dependencies

On Linux, you may need to install system dependencies before building. If you encounter errors about missing libraries (such as libdbus-1-dev or libudev-dev), run:

# Install all dependencies (recommended)
./install-deps.sh

# Or install essential packages individually
sudo apt install -y libdbus-1-dev libudev-dev pkg-config

The install-deps.sh script will install all required dependencies for your distribution (Ubuntu/Debian, Fedora/RHEL, or Arch Linux). If you encounter GPG errors with apt update, the script will continue anyway and attempt to install the packages.

Common build errors:

  • libdbus-sys build failure โ†’ Install libdbus-1-dev and pkg-config
  • hidapi build failure (Unable to find libudev) โ†’ Install libudev-dev
  • Missing graphics libraries โ†’ The install script includes all necessary graphics dependencies

โš ๏ธ Important: GNU Toolchain Required on Windows

Perro requires the GNU toolchain. Here's how to install and set it up:

# Install the GNU toolchain (1.92.0 or later)
rustup toolchain install stable-x86_64-pc-windows-gnu

# Set GNU toolchain as default
rustup default stable-x86_64-pc-windows-gnu

# Verify you're using GNU toolchain
rustc --version
# Should show: rustc 1.92.0 (or later) ... (x86_64-pc-windows-gnu)

# Or verify with rustup
rustup show
# Should show: default toolchain: stable-x86_64-pc-windows-gnu

If you already have Rust installed with MSVC:

# Install GNU toolchain for 1.92.0
rustup toolchain install 1.92.0-x86_64-pc-windows-gnu

# Set it as default
rustup default 1.92.0-x86_64-pc-windows-gnu

# Verify
rustc --version

Updating an existing GNU toolchain:

# Update to latest stable GNU toolchain
rustup update stable-x86_64-pc-windows-gnu

# Or update your default (if already set to GNU)
rustup update stable

Repository Structure

perro/
โ”œโ”€โ”€ perro_core/          # Core engine (structs, scene, render graph)
โ”œโ”€โ”€ perro_dev/           # Dev wrapper binary (loads DLLs, runs projects with --path)
โ”œโ”€โ”€ perro_editor/        # Editor game project
โ”‚   โ”œโ”€โ”€ .perro/
โ”‚   โ”‚   โ”œโ”€โ”€ project/     # Editor project crate (final exported binary in --project mode)
โ”‚   โ”‚   โ””โ”€โ”€ scripts/     # Editor scripts crate (contains transpiled rust + builds DLL)
โ”‚   โ””โ”€โ”€ res/             # Resources (FUR files, scenes, assets, scripts)
โ””โ”€โ”€ projects/            # Example game projects

Building & Running

Open the Editor in Dev Mode:

# โš ๏ธ You must specify -- --path PATH when running perro_dev
cargo run -p perro_dev -- --path /path/to/project

# For better performance during development:
cargo run --release -p perro_dev -- --path /path/to/project

Build the Core Alone:

cargo build -p perro_core

All projects share a build cache (the main workspace target/ in source mode), so the core only compiles once.

Toolchain & Versioning

The editors are pinned to specific versions of the toolchain, (eg. 1.0 => 1.92.0), toolchains will NOT always be updated each engine update, as to not clog the end user's system with multiple toolchains they don't need. (1.0 and 1.1 could support the same toolchain, even if users update it only is installed once)

Current Requirements:

  • Rust 1.92.0 or later (required for wgpu 28.0.0)
  • Default toolchain version: 1.92.0

Project Compatibility:

  • Old projects use their original editor version by default
  • The Project Manager auto-updates to the latest version
  • You can manually upgrade a project to a newer editor version if desired
  • Older editor versions remain available for projects that haven't upgraded

Stabilized Features

  • โœ… Pup scripting system (Pup -> Rust pipeline)
  • โœ… Type checking and casting during Rust codegen
  • โœ… DLL loading & dynamic script loading
  • โœ… Static linking of scripts and assets during release
  • โœ… FUR layouts, panels, child layouts, and boxing
  • โœ… Global decoupled signal system with 500ns dispatch

In Progress / Planned

  • ๐Ÿ”„ Pup DSL expansion (control flow, standard library)
  • ๐Ÿ”„ FUR runtime editing & editor viewer
  • ๐Ÿ“‹ Scene editor
  • ๐Ÿ“‹ Asset pipeline

Experimental Features

  • ๐Ÿงช C# & TypeScript Support โ€“ Experimental transpiler support for C# and TypeScript via Tree Sitter. These languages would be great additions once the transpiler stabilizes, but are currently experimental and not all AST bindings and behavior are implemented yet.

๐Ÿค Contributing

Contributions are welcome! You can work on:

  • Engine โ€“ perro_core (rendering, scene, runtime)
  • Editor โ€“ Edit the source code and UI of the editor at perro_editor/res
  • Scripting โ€“ Pup DSL expansion, transpiler improvements, other language support as needed
  • Tooling โ€“ build system, asset pipeline

See CONTRIBUTING.md for guidelines.


๐Ÿ’œ Support Perro

Your support helps us continue developing Perro and making it accessible to everyone. With enough funding, donations can enable full-time development on Perro, dramatically accelerating progress and bringing features to production faster.

Donations help fund:

  • ๐Ÿ’ผ Full-Time Development โ€“ Your contributions can enable dedicated, full-time work on Perro, allowing for faster feature development, more consistent updates, and quicker bug fixes
  • ๐Ÿš€ Accelerated Development โ€“ More time for core features like the scene editor, asset pipeline, and expanded Pup DSL capabilities
  • ๐Ÿ› ๏ธ Better Tooling & Infrastructure โ€“ Improved development tools, CI/CD pipelines, documentation, and testing infrastructure
  • ๐Ÿ“š Enhanced Documentation โ€“ Comprehensive tutorials, example projects, and detailed guides
  • ๐Ÿ› Bug Fixes & Stability โ€“ More time fixing bugs, improving performance, and ensuring Perro runs smoothly across platforms
  • ๐ŸŒ Community Growth โ€“ Supporting forums, community events, and developer outreach
  • โšก Performance & Optimization โ€“ Investing in profiling tools and optimization work

Ways to Support:

Every contribution, no matter the size, makes a difference! Thank you for supporting open-source game development! ๐ŸŽฎ


๐Ÿ“œ License

Perro is licensed under the Apache 2.0 License. See LICENSE for details.


๐Ÿพ Why "Perro"?

Every developer needs a loyal partner, just like a dog โ€” and that's what Perro means in Spanish.

About

๐Ÿ• Perro โ€” A game engine written in Rust that utilizes a unique transpilation system that allows developers to write in high level languages for quick iteration and ease of use, while achieving native performance.

Topics

Resources

License

Code of conduct

Contributing

Stars

Watchers

Forks

Sponsor this project

Languages