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 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 multi-language scripting system:

  • 🐢 Pup DSL – a beginner-friendly, lightweight scripting language that compiles to Rust for native performance.
  • 🎨 FUR (Flexible UI Rules) – a declarative UI system with layouts, panels, and boxing for easy UI design.
  • 🌐 Multi-Language Scripts – write gameplay in Pup, C#, TypeScript, or pure Rust β€” everything transpiles to Rust under the hood.
  • πŸ“¦ Type-Safe Transpilation – full type checking and casting during code generation.
  • ⚑ 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.

πŸ‘©β€πŸ’» For Game Developers

Quick Start

Clone the repository and build from source:

git clone https://github.com/PerroEngine/Perro.git
cd perro
cargo run -p perro_dev

This launches the Perro Editor in dev mode.

Creating a New Project

Using the CLI:

# 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

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

This automatically:

  1. Transpiles scripts (Pup/C#/TypeScript β†’ Rust)
  2. Compiles scripts into a DLL
  3. Runs the project with hot-reload enabled

Manual Workflow (if you prefer more control):

1. Build Scripts Only:

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

2. Run Project:

# Run project in dev mode (injects compiled scripts dynamically)
cargo run -p perro_dev -- --path /path/to/project
OR
cargo run -p perro_core -- --path /path/to/project --run

Iteration Cycle:

  • Make changes to scripts in res/*
  • Re-run --dev or just --scripts + perro_dev
  • Changes are automatically picked up by the running game
  • Fast iteration cycle (~1–3s recompile time)

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, C#, TypeScript, or Rust 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

🐢 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

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

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

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

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 in init():

@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:

  • Creates a function with the signal name
  • Adds Signal.connect("signal_name", function_name) to your init() function
  • No need to manually write connection code!

Manual Signal Connection

You can also manually connect signals using Signal.connect(). The new simplified format takes just 2 arguments:

@script Player extends Sprite2D

var enemy: Node2D
var bob: Sprite2D

fn init() {
    // Connect to a signal on self (function name as string)
    Signal.connect("player_Died", on_player_died)
    
    // Connect to a signal on another node (using any node reference)
    Signal.connect("enemy_Defeated", enemy.on_enemy_defeated)
    Signal.connect("bob_Pressed", bob.on_bob_pressed)
    
    // You can use any variable that holds a node reference
    var my_button = self.get_node("start_button")
    Signal.connect("start_Pressed", my_button.on_start_pressed)
}

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

Signal.connect() format:

  • Signal.connect(signal_name, function_reference)
    • If function_reference is a string or identifier β†’ connects to self
    • If function_reference is node_var.function_name β†’ connects to that node variable (e.g., bob.functionname, enemy.on_defeated, my_button.on_clicked)

Decoupled Signal System Example

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

FUR UI File (res/ui.fur):

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

Game Manager Script (res/game_manager.pup):

@script GameManager extends Node

fn 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
  • 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

🌐 Multi-Language Scripting

You can write scripts in multiple languages. Languages using Tree Sitter for parsing have their full syntax supported:

  • Pup (native DSL, hand-written parser)
  • C# (syntax via Tree Sitter CST β†’ Perro AST; not all AST bindings implemented yet)
  • TypeScript (yntax via Tree Sitter CST β†’ Perro AST; not all AST bindings implemented yet)
  • Rust (direct, no transpilation)

The transpilation pipeline:

  1. Parse – Tree Sitter CST β†’ Perro AST (or manual parser for Pup)
  2. Codegen – AST β†’ type-checked Rust
  3. Compile – Rust β†’ DLL (Dev) or static binary (Release)
  4. Load – DLL hot-load (Dev) or direct calls (Release)

🎨 FUR (Flexible UI Rules)

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

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

Current Features:

  • Layouts and child layouts
  • Panels and boxing
  • Styling and padding

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


πŸ”„ Dev vs Release

Dev Mode (Hot-Reload via DLL)

  • Scripts are transpiled to Rust, compiled into a DLL
  • Engine loads the DLL at runtime
  • Load files from disk
  • Make changes β†’ recompile (~1–3s) β†’ see updates instantly without restarting

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

⚠️ Important: GNU Toolchain Required on Windows

On Windows, Rust defaults to the MSVC toolchain, but 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
β”‚   β”‚   └── scripts/     # Editor scripts crate (contains transpiled rust + builds DLL)
β”‚   └── res/             # Resources (FUR files, scenes, assets, scripts)
└── examples/            # Example game projects

Building & Running

Open the Editor in Dev Mode:

cargo run -p perro_dev

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

  • βœ… Basic scripting system (Pup, C#, TS -> 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)
  • πŸ”„ C# & TypeScript AST bindings completion
  • πŸ”„ FUR runtime editing & editor viewer
  • πŸ“‹ Scene editor
  • πŸ“‹ Asset pipeline

🀝 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.


πŸ“œ 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