Skip to content

TheLazyLemur/engine

Repository files navigation

Engine + Game Architecture

This repository contains two separate Go modules in a workspace:

Structure

.
├── go.work              # Workspace file linking both modules
├── engine/              # Engine module (reusable game engine)
│   ├── go.mod
│   ├── component/       # Core ECS components (Transform, Camera, RenderMesh, Name)
│   ├── core/            # Engine core, systems, registries
│   ├── ecs/             # ECS type wrappers
│   ├── editor/          # Editor UI and interaction
│   ├── math/            # 3D math library
│   ├── scene/           # Scene API and serialization
│   ├── system/          # System interface
│   ├── viewport/        # Viewport rendering
│   └── config/          # Configuration
└── game/                # Game module (your game built on the engine)
    ├── go.mod
    ├── component/       # Game-specific components (Rotator, Bouncer, etc.)
    ├── system/          # Game-specific systems
    ├── systems.go       # RegisterSystems() entry point
    ├── cmd/game/        # Main executable
    │   └── main.go
    └── assets/          # Game assets (models, textures, scenes, fonts)

Building

Build the game:

cd game/cmd/game
go build -o game .

Run the game:

# From the game directory (default, assets are in ./assets)
cd game
./cmd/game/game -mode=editor  # Editor mode
./cmd/game/game -mode=game    # Game mode

# Or specify custom asset path
./cmd/game/game -mode=editor -assets=./assets

# From the cmd/game directory (specify parent assets path)
cd game/cmd/game
./game -mode=editor -assets=../../assets

Command-Line Flags

  • -mode - Run mode: editor (default) or game
  • -assets - Path to assets directory (default: ./assets)

Asset Directory Structure:

assets/
├── models/      # 3D models (.obj, .gltf, .glb)
├── textures/    # Textures (.png, .jpg)
├── scenes/      # Scene files (.json)
└── fonts/       # Fonts (.ttf)

How It Works

Engine Module

The engine is a reusable game engine package at github.com/danielr/engine. It provides:

  • ECS System: Entity-Component-System architecture via Donburi
  • Scene Management: High-level API for entities and components
  • Transform System: Hierarchical transforms with parent-child relationships
  • Rendering: 3D mesh rendering with Raylib
  • Editor: Built-in editor with hierarchy, inspector, and viewport
  • Math Library: Vector3, Quaternion, Matrix4, Ray, BoundingBox
  • Serialization: JSON scene save/load

Game Module

The game module imports the engine and builds game-specific logic on top:

import (
    "flag"
    engine "github.com/TheLazyLemur/engine/core"
    "game"
    gameComponent "game/component"
)

var (
    mode      = flag.String("mode", "editor", "run mode: editor or game")
    assetRoot = flag.String("assets", "./assets", "path to assets directory")
)

func main() {
    flag.Parse()

    // Create engine with asset root
    eng := engine.NewEngine(*mode == "editor", *assetRoot)
    defer eng.Close()

    // Register game components for serialization (BEFORE loading scene)
    engine.RegisterComponent(eng, "Rotator", gameComponent.Rotator)
    engine.RegisterComponent(eng, "Bouncer", gameComponent.Bouncer)

    // Optional: Override default scene creation
    // eng.SetDefaultSceneCreator(game.CreateDefaultScene)

    // Load or create scene (AFTER registering components)
    // Path is relative to asset root (e.g., if assetRoot="./assets", this loads "./assets/scenes/default.json")
    eng.LoadOrCreateScene("scenes/default.json")

    // Register game systems
    game.RegisterSystems(eng)

    // Run the engine
    eng.Run()
}

Scene Management

Engine Default Scene

The engine provides a minimal default scene if no scene file exists:

  • Ground plane
  • Several colored cubes (red, yellow, blue, green, cyan)
  • Red sphere
  • Green cylinder
  • Main camera

This default scene only uses engine components (Transform, RenderMesh, Camera, Name) - no game-specific components.

Overriding the Default Scene

Games can provide their own default scene by calling SetDefaultSceneCreator():

// In your game package
func CreateDefaultScene(scn *scene.Scene) {
    // Create your custom default scene with game components
    cube := scn.CreateEntity("Bouncing Cube")
    // ... add components, including game-specific ones
}

// In main.go
eng.SetDefaultSceneCreator(game.CreateDefaultScene)
eng.LoadOrCreateScene("scenes/default.json")

Scene Loading Order

Critical: The order matters!

// 1. Create engine with asset root (no scene loaded yet)
eng := engine.NewEngine(editorMode, assetRoot)

// 2. Register components FIRST
engine.RegisterComponent(eng, "MyComponent", myComponent.MyComponent)

// 3. (Optional) Set custom scene creator
eng.SetDefaultSceneCreator(game.CreateDefaultScene)

// 4. Load scene (now knows about your components)
// Path is relative to asset root configured in step 1
eng.LoadOrCreateScene("scenes/default.json")

// 5. Register systems and run
game.RegisterSystems(eng)
eng.Run()

Path Handling: LoadOrCreateScene() paths are relative to the asset root. If you created the engine with assetRoot="./assets", then LoadOrCreateScene("scenes/default.json") will load ./assets/scenes/default.json. Absolute paths are used as-is.

If you register components after loading the scene, they will be skipped during deserialization!

Creating a New Game

To create a new game on top of this engine:

  1. Create a new game module:

    mkdir my-game
    cd my-game
    go mod init my-game
  2. Add engine dependency:

    # If using local engine (workspace)
    echo 'replace github.com/TheLazyLemur/engine => ../engine' >> go.mod
    
    # Or if engine is published
    go get github.com/TheLazyLemur/engine
  3. Create asset directories:

    mkdir -p assets/{models,textures,scenes,fonts}
  4. Create main.go:

    package main
    
    import (
        "flag"
        engine "github.com/TheLazyLemur/engine/core"
    )
    
    func main() {
        assetRoot := flag.String("assets", "./assets", "path to assets directory")
        mode := flag.String("mode", "editor", "run mode: editor or game")
        flag.Parse()
    
        eng := engine.NewEngine(*mode == "editor", *assetRoot)
        defer eng.Close()
    
        // Register your components
        // engine.RegisterComponent(eng, "MyComponent", myComponent.MyComponent)
    
        // Load scene
        eng.LoadOrCreateScene("scenes/default.json")
    
        // Register your systems
        // eng.RegisterSystem(&mySystem.MySystem{})
    
        eng.Run()
    }
  5. Add your game components (see game/component/ for examples)

  6. Add your game systems (see game/system/ for examples)

Development

When working on both engine and game simultaneously, use the workspace:

# The go.work file automatically links both modules
go work sync

# Build from anywhere in the workspace
cd game/cmd/game && go build

Architecture Principles

  • Engine is game-agnostic: No game-specific logic in the engine
  • Game imports engine: One-way dependency (game → engine)
  • System registration: Game systems register via SystemRegistry interface
  • Component registration: Game components register for serialization
  • Current pattern: Simple and explicit registration in main.go

Version

Engine: v0.x.x (experimental, breaking changes allowed)

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages