Lichendust

I'm Harley, an artist, animator and programmer.
I make all kinds of useless stuff.

Lena

A tiny, handmade game framework in Odin
RELEASED
2025
LICENSE
FOSS — BSD-3
ROLE
Design & Implementation
DISCIPLINES

Game/Engine Architecture

API Design

SOURCES

Itch.io

GitHub

Lena is a compact, handmade framework for making tiny games with palette graphics. It's software-rendered, cross-platform and provides artistic constraints that challenge your creativity without limiting your game's size and scope.

It comes with batteries-included palette graphics, palette-blending and other drawing effects, loaders and decoders for image and audio assets, a simple audio interface, and built-in text rendering. It also compiles and runs on:

The core functionality of Lena is implemented from scratch and the whole code base is designed to be extremely legible and hackable. Lena only relies on the libraries that ship with the Odin compiler.

Example

Here's the WASM version of the little introductory demo.odin that comes with the library:

While it's very basic, it shows off the palette blending and rendering features you can get from it, with both the visible and secret text being revealed by the cursor, and then the image shining through when the cursor is used to paint it in.

This is all achieved with the following code:

Click to expand!
package demo

import "lena"

WHITE  :: 1
CYAN   :: 9
BLUE   :: 10
NAVY   :: 11
YELLOW :: 15
ORANGE :: 16

CLOVER :: #load("demo.png")

main :: proc() {
    lena.init("Hello, Lena!", 128, 128, flags = {.HIDE_WINDOW, .HIDE_CURSOR})
    defer lena.destroy()

    lena.set_window_background(BLUE)

    lena.set_text_color(WHITE)
    lena.set_bold_color(YELLOW)

    // if yellow is drawn over white, substitute orange
    // and don't let yellow on yellow disappear
    lena.set_blend_palette_pair(WHITE,  YELLOW, ORANGE)
    lena.set_blend_palette_pair(YELLOW, YELLOW, ORANGE)

    // if the cyan text is drawn over the navy background
    // or the yellow cursor, flip it to white and orange
    lena.set_blend_palette_pair(CYAN, NAVY,   WHITE)
    lena.set_blend_palette_pair(CYAN, YELLOW, ORANGE)

    // make a blank image to capture drawing
    canvas := lena.create_image(128, 128)
    defer lena.destroy_image(canvas)

    // convert our clover PNG into a Lena image
    clover := lena.create_image_from_png(CLOVER)
    defer lena.destroy_image(clover)

    lena.show_window()

    for _ in lena.step() {
        if lena.key_pressed(.F11) {
            lena.toggle_fullscreen()
        }

        if lena.key_pressed(.ESCAPE) {
            lena.quit()
        }

        mx, my := lena.get_cursor()

        lena.clear_screen(CYAN)

        if lena.mouse_held(.LEFT) {
            // draw paint to canvas
            lena.draw_circle_to_image(canvas, mx, my, 14, YELLOW, true)

            // copy clovers to canvas but clipped to canvas alpha
            lena.set_draw_state({.LOCK_ALPHA})
            lena.draw_image_to_image(clover, canvas, 0, 0)
        }

        // draw composited canvas to screen
        lena.set_draw_state({.BLEND})
        lena.draw_image(canvas, 0, 0)

        // draw yellow cursor
        lena.draw_circle(mx, my, 14, YELLOW, true)

        // draw both sets of text
        lena.set_text_color(WHITE)
        lena.draw_text("Welcome to |*Lena*!", 10, 54 - 47)

        lena.set_text_color(CYAN)
        lena.draw_text("Hold down |left click...", 10, 66 - 47)
    }
}

Design & Philosophy

Lena is designed with simplicity in mind. The entire boilerplate required for a 'Hello, world!' program looks like the following —

package main

import "lena"

main :: proc() {
    lena.init("Title", 128, 128)
    defer lena.destroy()

    for _ in lena.step() {
        lena.clear_screen()
        lena.draw_text("Hello, |world|!", 10, 10)
    }
}

I wrote Lena because I wanted a dead-simple library to make fun little games with, especially for game jams:

It's as much of a fun challenge for me to maintain such a small, legible footprint in a framework as it will hopefully be to make little games in it. I certainly think so.

Education

Lena serves an ulterior motive as well. Many small engines of this kind are still either exceedingly complex in terms of feature set or written in a code-golf style to be as compressed and impressively small as possible, and therefore illegible to beginners.

There are thousands of basic tutorials and toy codebases and playgrounds and sandboxes for the beginner programmer to get started in, some with more value than others. There are also many, many massively complex codebases out there that you're free to peruse, but that even ten-thousand-hour veterans would struggle to understand without putting in months of work doing what amounts to archaeology to wrap their heads around the implementation and architecture.

The problem is the lack of in-between codebases — intermediate-level programs that are doing complex and complete tasks, the kinds of things an enterprising beginner really wants to be doing, but that are implemented in such a way that a single mind can still understand easily.

Lena is just this: a feature-complete toolchain for making fun little games, implemented in a way that can be read and understood in a day. A library doing what I was told, for years, were complicated things — like being natively cross-platform or rendering graphics — but that demonstrates the actual, simple reality of doing so if you don't bury code under layers and layers of abstraction and misdirection and teach beginners that this is 'just how you do it in current year'.

Lena is ~2000 lines of Odin. The hot path for each platform is ~1000 lines. Each platform layer is just ~200 lines of code and a few lookup tables for things like keyboard codes.

Lena is the codebase I would have gifted myself ten years ago if I could have.

Lena Documentation
API Reference
External Resources