Drawing sprites
You've drawn a solid block and read the player's position. Now let's draw real art — and move it around based on what the game is doing.
Indexed images, again
Recall that an image is a grid of palette indices, and a palette (u32[]) maps each index to a 0xRRGGBB color. The split is the whole trick behind retro graphics: the same pixels can be recolored instantly by swapping the palette, with no change to the image.
There are three ways to get an image into your script. All of them inline the data right in the source — a Munos mod ships as a single self-contained file.
From base64 pixel data
var block: image = image_literal(32, 32, "AQEBAQEB…")image_literal(w, h, data) is the one you met first: raw palette indices, base64-encoded, decoded at compile time. Good for simple shapes and hand-built art.
From a packed bitmap (best for sprites)
Real sprite art is usually stored as a compact indexed bitmap and decoded with image_from_bitmap_bytes:
var bytes: u8[] = base64_decode("FRgD…") // the asset, inlined
var mario: image = image_from_bitmap_bytes(bytes, 0, 0)base64_decode turns a base64 string into raw bytes; image_from_bitmap_bytes decodes those bytes (the format carries its own width, height, and bit depth) into an image. The two trailing numbers are a per-image alignment nudge (dx, dy) added to the draw position every time the image is drawn — handy for keeping an animation's head steady while the limbs swing. Pass 0, 0 when you don't need it.
This is the preferred path for sprites: a few times smaller than PNG for low-color art, with no image decoder running at runtime.
From a PNG
var bytes: u8[] = base64_decode("iVBORw0K…") // a PNG, inlined
var sprite: image = image_from_png_bytes(bytes, 0, 0, 16, 16)
var pal: u32[] = palette_from_png_bytes(bytes)image_from_png_bytes(data, x, y, w, h) decodes a PNG and takes an (x, y, w, h) sub-rectangle — perfect for slicing one frame out of a sprite sheet. It builds a palette automatically (transparent pixels become index 0), which you fetch with palette_from_png_bytes. Every slice of the same PNG shares one palette.
Drawing it
All drawing goes through one builtin:
draw_image(img, pal, x, y)x, y is the top-left corner in screen pixels. Pixels whose palette index is 0 are skipped, so your sprite's transparent areas let the game show through.
Put it together with a memory read and you have a sprite that tracks the player:
const PLAYER_X: u32 = 0x0086
const PLAYER_Y: u32 = 0x00CE
var bytes: u8[] = base64_decode("FRgD…")
var hat: image = image_from_bitmap_bytes(bytes, 0, 0)
var pal: u32[] = [0x00000000, 0x00FFFFFF, 0x00FF0000]
event frame(frame_num: i32) {
var x = i32(read_u8(PLAYER_X))
var y = i32(read_u8(PLAYER_Y))
draw_image(hat, pal, x, y - 16) // float a hat above the player
}Flipping
A character facing left or right usually reuses one sprite, mirrored. draw_image takes optional flip_x / flip_y flags:
draw_image(hat, pal, x, y, flip_x = true) // mirror horizontallyThose are named arguments — flip_x = true. They let you set one optional flag without listing all the ones before it. (More on named arguments in the functions reference.)
Palette swap
Because the palette is a separate argument, you can draw the same image in different colors just by passing a different palette — the basis of every "player 2 is green" effect:
var marioPal: u32[] = [0x00000000, 0x00FFFFFF, 0x00FF0000] // red
var luigiPal: u32[] = [0x00000000, 0x00FFFFFF, 0x0000AA00] // green
draw_image(playerSprite, marioPal, 100, 100) // red
draw_image(playerSprite, luigiPal, 140, 100) // green, same pixelsClipping into a viewport
Sometimes you want a sprite to stay inside a region — to hide a remote player's ghost behind the HUD instead of painting over it, for example. draw_image's screen-space scissor does that. clip_rect_y1 = 32 means "don't draw any pixel above y=32":
// Keep the ghost below a 32px-tall status bar at the top of the screen.
draw_image(ghost, pal, gx, gy, clip_rect_y1 = 32)Each side (clip_rect_x1, clip_rect_y1, clip_rect_x2, clip_rect_y2) defaults to "unbounded," so you only specify the edges you care about. There's also a source-space clip (clip_x/clip_y/clip_w/clip_h) for drawing just part of an image — both are covered in full in the images reference.
What you learned
- Three ways to build an image:
image_literal,image_from_bitmap_bytes(best for sprites), andimage_from_png_bytes+palette_from_png_bytes. base64_decodeinlines binary assets into your single source file.draw_image(img, pal, x, y)draws; index-0 pixels are transparent.- Optional named args:
flip_x,flip_y, and theclip_rect_*scissor. - Swap palettes to recolor the same sprite for free.
Next
Everything so far has been local — one player, one screen. Time to bring in a second player. Next we'll send the player's position to a server.