Files
prosperon/docs/tutorial.md
John Alanbrook 83b798e365 Add Hugo website and rewrite docs to match current engine
New Hugo site in website/ with prosperon.dev theme (blue/gold/castle
aesthetic), docs sidebar navigation, and content pages. Rewrote all
doc files to align with the actual codebase: compositor+film2d
rendering, use() modules (no global prosperon object), Pit language,
script+JSON entity model. Added entities.md, front matter to all
70+ API docs, and updated API index for current module architecture.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 18:09:55 -06:00

5.1 KiB

title, type
title type
Tutorial docs

A Tutorial Introduction

This tutorial walks through building a simple game from scratch. By the end, you'll understand modules, programs, rendering, and input.

How Prosperon Starts

Prosperon runs a .ce program file as the root actor. This is your game's entry point. There is no config.js or main.js — everything is modules you import with use().

Step 1: A Window

Create game.ce:

var core = use('core')

core.start({
  title: "My Game",
  width: 1280,
  height: 720
})

Run it with prosperon game.ce. You get a window. core.start() initializes the GPU backend, opens the window, and starts the main loop.

Step 2: Drawing a Sprite

Sprites are the fundamental drawable. Create one and it appears on screen — you don't issue draw calls.

var core = use('core')
var sprite = use('sprite')
var compositor = use('compositor')
var camera = use('camera')

var cam = camera.make({
  pos: {x: 0, y: 0},
  width: 320,
  height: 240
})

var player = sprite({
  image: "player.png",
  pos: {x: 0, y: 0},
  width: 32,
  layer: 5
})

var plan = compositor.compile({
  planes: [{
    name: 'game',
    plane: 'default',
    resolution: {width: 320, height: 240},
    camera: cam,
    presentation: 'integer_scale'
  }]
})

core.start({
  title: "Sprite Test",
  width: 1280,
  height: 720,
  render: function() {
    return compositor.execute(plan)
  }
})

Key ideas:

  • sprite() creates a sprite and registers it with the engine automatically
  • The compositor describes how to render the scene: which planes, at what resolution, with what camera
  • Your render callback returns the compositor result — the engine sends it to the GPU

Step 3: Moving the Sprite

Add an update callback to move things each frame:

core.start({
  title: "Moving Sprite",
  width: 1280,
  height: 720,
  update: function(dt) {
    player.pos.x += 60 * dt
  },
  render: function() {
    return compositor.execute(plan)
  }
})

dt is the time elapsed since the last frame, in seconds. Multiply speeds by dt for frame-rate-independent movement.

Step 4: Handling Input

Use the input module to map physical keys to named actions, then route those actions to your game objects:

var input = use('input')

input.configure({
  action_map: {
    move_left: ['a', 'left'],
    move_right: ['d', 'right'],
    jump: ['space']
  }
})

var player_entity = {
  on_input: function(action, data) {
    if (action == 'move_left' && data.pressed) {
      player.pos.x -= 16
    }
    if (action == 'move_right' && data.pressed) {
      player.pos.x += 16
    }
  }
}

var p1 = input.player1()
p1.possess(player_entity)

The input system handles keyboard, gamepad, and touch. You define actions once and bind them to any physical input.

Step 5: Adding Sound

var sound = use('sound')

sound.play("jump.wav")

Sounds are loaded by path. The engine caches audio data so loading the same sound twice reuses the existing data.

Step 6: Multiple Planes

Real games often need separate rendering layers — pixel art at low res, HUD at native res:

var plan = compositor.compile({
  clear: {r: 0.1, g: 0.1, b: 0.2, a: 1},
  planes: [
    {
      name: 'game',
      plane: 'default',
      resolution: {width: 320, height: 240},
      camera: cam,
      layer_sort: {'5': 'y'},
      presentation: 'integer_scale'
    },
    {
      name: 'hud',
      plane: 'hud',
      resolution: {width: 1280, height: 720},
      camera: hud_cam,
      presentation: 'stretch'
    }
  ]
})

Sprites belong to a plane via their plane property. Each plane renders at its own resolution with its own camera, then composites onto the screen in order.

Step 7: Entities

For anything more than a few sprites, use the entity system. Define entity types as modules, create instances with overrides:

// entities/coin.cm
var sprite = use('sprite')

return {
  value: 1,
  image: "coin.png",

  init: function() {
    this.sprite = sprite({
      image: this.image,
      pos: this.pos,
      width: 16,
      layer: 3
    })
  },

  on_destroy: function() {
    this.sprite.destroy()
  }
}
// In your game program
var world = use('world')
var coin_proto = use('entities/coin')

var c = world.add_entity(coin_proto, {
  pos: {x: 50, y: 80},
  value: 5
})

The entity's init() runs after overrides are applied. The prototype defines every valid field with a default — it is the schema.

Putting It Together

A complete minimal game has:

  1. A .ce program that imports modules and calls core.start()
  2. Sprite and entity modules (.cm) that define game objects
  3. A compositor config that describes the rendering setup
  4. Input configuration that maps keys to actions

The engine does the rest: batching sprites, sorting layers, managing GPU resources, polling events, scheduling frames.

Next Steps

  • Graphics — Sprites, text, shapes, tilemaps
  • Compositor — Planes, effects, presentation modes
  • Input — Actions, players, control stacks
  • Entities — The entity model and world system