This repository contains two separate Go modules in a workspace:
.
├── 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)
cd game/cmd/game
go build -o 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-mode- Run mode:editor(default) orgame-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)
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
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()
}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.
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")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!
To create a new game on top of this engine:
-
Create a new game module:
mkdir my-game cd my-game go mod init my-game -
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
-
Create asset directories:
mkdir -p assets/{models,textures,scenes,fonts} -
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() }
-
Add your game components (see
game/component/for examples) -
Add your game systems (see
game/system/for examples)
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- 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
SystemRegistryinterface - Component registration: Game components register for serialization
- Current pattern: Simple and explicit registration in main.go
Engine: v0.x.x (experimental, breaking changes allowed)