tiramisu

Tiramisu game engine main module - immutable game loop with effect system.

This module provides the core game loop following the Model-View-Update (MVU) architecture, inspired by Lustre. Your game state is immutable, and updates return new state along with effects.

Quick Example

import gleam/option
import tiramisu
import tiramisu/background
import tiramisu/camera
import tiramisu/effect
import tiramisu/geometry
import tiramisu/material
import tiramisu/scene
import tiramisu/transform
import vec/vec3

type Model {
  Model(rotation: Float)
}

type Msg {
  Tick
}

type Ids {
  Cube
  MainCamera
}

pub fn main() {
  tiramisu.run(
    dimensions: option.None,
    background: background.Color(0x111111),
    init: init,
    update: update,
    view: view,
  )
}

fn init(_ctx: tiramisu.Context(Ids)) {
  #(Model(rotation: 0.0), effect.tick(Tick), option.None)
}

fn update(model: Model, msg: Msg, ctx: tiramisu.Context(Ids)) {
  case msg {
    Tick -> {
      let new_rotation = model.rotation +. ctx.delta_time
      #(Model(rotation: new_rotation), effect.tick(Tick), option.None)
    }
  }
}

fn view(model: Model, _ctx: tiramisu.Context(Ids)) {
  let assert Ok(cam) = camera.perspective(field_of_view: 75.0, near: 0.1, far: 1000.0)
  let assert Ok(cube_geo) = geometry.box(width: 1.0, height: 1.0, depth: 1.0)
  let assert Ok(cube_mat) =
    material.new()
    |> material.with_color(0xff0000)
    |> material.build()

  [
    scene.Camera(
      id: MainCamera,
      camera: cam,
      transform: transform.at(position: vec3.Vec3(0.0, 0.0, 5.0)),
      look_at: option.None,
      active: True,
      viewport: option.None,
    ),
    scene.Mesh(
      id: Cube,
      geometry: cube_geo,
      material: cube_mat,
      transform: transform.identity
        |> transform.rotate_y(model.rotation),
      physics: option.None,
    ),
  ]
}

Types

Game context passed to init and update functions.

Contains timing information, input state, canvas dimensions, and physics world for the current frame.

Example

fn update(model: Model, msg: Msg, ctx: Context) {
  // Move player based on delta time for smooth motion
  let speed = 5.0
  let new_x = model.x +. speed *. ctx.delta_time

  // Convert screen coordinates to world space
  let world_x = { screen_x -. ctx.canvas_width /. 2.0 } /. 100.0
  let world_y = { ctx.canvas_height /. 2.0 -. screen_y } /. 100.0

  // Check if space key is pressed
  case input.is_key_down(ctx.input, "Space") {
    True -> jump(model)
    False -> Model(..model, x: new_x)
  }
}
pub type Context(id) {
  Context(
    delta_time: Float,
    input: input.InputState,
    canvas_width: Float,
    canvas_height: Float,
    physics_world: option.Option(physics.PhysicsWorld(id)),
    input_manager: @internal InputManager,
  )
}

Constructors

Canvas dimensions for the game window.

Used with tiramisu.run() to specify the size of the game canvas. If not provided (None), the game will run in fullscreen mode.

Example

import gleam/option.{None, Some}
import tiramisu
import tiramisu/background

// Fullscreen mode
tiramisu.run(
  dimensions: None,
  background: background.Color(0x111111),
  // ...
)

// Fixed size
tiramisu.run(
  dimensions: Some(tiramisu.Dimensions(width: 800.0, height: 600.0)),
  background: background.Color(0x111111),
  // ...
)
pub type Dimensions {
  Dimensions(width: Float, height: Float)
}

Constructors

  • Dimensions(width: Float, height: Float)

Values

pub fn run(
  dimensions dimensions: option.Option(Dimensions),
  background background: background.Background,
  init init: fn(Context(id)) -> #(
    state,
    effect.Effect(msg),
    option.Option(physics.PhysicsWorld(id)),
  ),
  update update: fn(state, msg, Context(id)) -> #(
    state,
    effect.Effect(msg),
    option.Option(physics.PhysicsWorld(id)),
  ),
  view view: fn(state, Context(id)) -> List(scene.Node(id)),
) -> Nil

Initialize and run the game loop.

This is the main entry point for your game. It sets up the renderer, initializes your game state, and starts the game loop. The loop will call your update and view functions each frame.

Parameters

  • dimensions: Canvas dimensions (width and height). Use None for fullscreen mode.
  • background: Background as Color, Texture, or CubeTexture (see Background type)
  • init: Function to create initial game state and effect
  • update: Function to update state based on messages
  • view: Function to render your game state as scene nodes

Camera Setup

You must include a Camera scene node with active: True in your initial scene.

Example

import gleam/option.{None, Some}
import tiramisu
import tiramisu/background
import tiramisu/camera
import tiramisu/effect
import tiramisu/scene
import tiramisu/transform
import vec/vec3

type Model {
  Model(rotation: Float)
}

type Msg {
  Tick
}

pub fn main() {
  // Fullscreen mode with color background
  tiramisu.run(
    dimensions: None,
    background: background.Color(0x111111),
    init: fn(_ctx) {
      #(Model(rotation: 0.0), effect.tick(Tick), option.None)
    },
    update: fn(model, msg, ctx) {
      case msg {
        Tick -> #(
          Model(rotation: model.rotation +. ctx.delta_time),
          effect.tick(Tick),
          option.None,
        )
      }
    },
    view: fn(model, _ctx) {
      let assert Ok(cam) = camera.perspective(field_of_view: 75.0, near: 0.1, far: 1000.0)
      let assert Ok(geo) = geometry.box(width: 1.0, height: 1.0, depth: 1.0)
      let assert Ok(mat) = material.new() |> material.with_color(0xff0000) |> material.build()

      [
        scene.Camera(
          id: "main-camera",
          camera: cam,
          transform: transform.at(position: vec3.Vec3(0.0, 0.0, 5.0)),
          look_at: option.None,
          active: True,
          viewport: option.None,
        ),
        scene.Mesh(
          id: "cube",
          geometry: geo,
          material: mat,
          transform: transform.identity
            |> transform.rotate_y(model.rotation),
          physics: option.None,
        ),
      ]
    },
  )
}
Search Document