Developer Manual
Step-by-step guide to creating your first PLUTO-8 game.
★ Table of Contents
1. Getting Started
PLUTO-8 is a fantasy console that runs entirely inside Roblox. You do not need to install anything — just join the Roblox game and you are ready to create.
What is a Fantasy Console?
A fantasy console is a simulated retro gaming machine with deliberately limited specifications. Those constraints make it easy to learn game development and to finish small games quickly. PLUTO-8 is inspired by PICO-8 but lives natively inside Roblox, so your games are instantly playable by millions of people.
Specifications at a Glance
- Display: 256×256 pixels, 256-color palette
- Frame rate: 30 FPS
- Sprites: 256 sprites, each 8×8 pixels
- Tilemap: 128×64 tiles
- Scripting: Lua (sandbox, PICO-8-style API)
- RAM: 32 KB addressable memory
- SFX: 8 sound effect slots
Opening the Code Editor
- Join the PLUTO-8 Roblox game.
- Press Tab (or tap the pencil icon on mobile) to enter Developer Mode.
- The code editor opens on the right side of the screen.
- Type your Lua code and press Ctrl+Enter (or the Run button) to load it.
2. Hello World
The smallest possible PLUTO-8 game just needs a _draw function:
function _draw() cls(0) print("HELLO, WORLD!", 60, 120, 7) end
Copy this into the code editor and press Run. You should see white text on a black background.
Let's break it down:
cls(0)— clear the screen to color 0 (black). Always call this first in _draw or you will see the previous frame.print("HELLO, WORLD!", 60, 120, 7)— draw text at position (60, 120) in color 7 (white). The screen is 256×256, so (60, 120) is near the left-center.
3. The Game Loop
PLUTO-8 runs three functions in a fixed order, 30 times per second:
function _init() -- Runs ONCE when the cartridge starts. -- Initialise your variables here. score = 0 player = { x = 128, y = 200 } end function _update() -- Runs 30 times per second. -- Move objects, check collisions, update state. if btn(0) then player.x -= 2 end if btn(1) then player.x += 2 end end function _draw() -- Runs after _update. -- Issue all draw commands here. cls(1) spr(1, player.x, player.y) print("SCORE: "..score, 2, 2, 7) end
Execution Order
- On first run:
_init()is called once. - Every frame (30 fps):
_update()is called. - Immediately after:
_draw()is called. - The resulting frame is sent to all connected clients.
_init is missing, the game still works. If _draw is missing, nothing is displayed.
Delta Time
Since _update is called exactly 30 times per second, you can use a fixed timestep: moving an object by 2 pixels per frame is moving it at 60 pixels per second. No delta-time multiplication is needed.
4. Drawing
Colors
PLUTO-8 uses a 256-color palette. Colors 0–15 follow the classic PICO-8 palette. Colors 16–255 are extended. The most common colors:
- 0 — Black
- 1 — Dark Blue
- 2 — Dark Purple
- 3 — Dark Green
- 5 — Dark Grey
- 6 — Light Grey
- 7 — White
- 8 — Red
- 9 — Orange
- 10 — Yellow
- 11 — Green
- 12 — Blue
- 14 — Pink
- 15 — Peach
Shapes
-- Pixel pset(50, 50, 8) -- Line line(0, 0, 100, 100, 7) -- Rectangle (outline / filled) rect(10, 10, 60, 40, 6) rectfill(10, 10, 60, 40, 1) -- Circle (outline / filled) circ(128, 128, 30, 11) circfill(128, 128, 30, 3)
Text
Use print(text, x, y, color) for simple text. The built-in font is 4×6 pixels. Each character is 4 pixels wide with 1 pixel spacing, so a line of text is about 5 pixels per character.
print("PLAYER 1", 80, 10, 7) print("SCORE: "..tostr(score), 2, 2, 10)
Camera
Use camera(x, y) to scroll the view. All draw calls after this are offset so that world position (x, y) appears at screen position (0, 0).
function _draw() cls(1) camera(cam_x, cam_y) -- follow the player map(0, 0, 0, 0, 32, 32) spr(1, player.x, player.y) camera(0, 0) -- reset for HUD print("HP:"..hp, 2, 2, 8) end
5. Input
PLUTO-8 supports up to 7 buttons. On desktop they map to keyboard keys; on mobile a D-pad and A/B buttons are shown on screen.
| ID | Action | Keyboard | Mobile |
|---|---|---|---|
| 0 | Left | ← or A | D-pad left |
| 1 | Right | → or D | D-pad right |
| 2 | Up | ↑ or W | D-pad up |
| 3 | Down | ↓ or S | D-pad down |
| 4 | A (confirm) | Z | A button |
| 5 | B (cancel) | X | B button |
| 6 | Start | Enter | Start button |
btn vs btnp
btn(id) returns true every frame the button is held. Use it for continuous movement.
btnp(id) returns true only on the first frame the button is pressed. Use it for actions that should fire once per press — like jumping, firing, or selecting a menu item.
function _update() -- continuous movement if btn(0) then x -= 2 end if btn(1) then x += 2 end -- single press: jump only once if btnp(4) and on_ground then vy = -6 end end
6. Sprites
The sprite sheet holds 256 sprites, each 8×8 pixels. Sprite 0 is the top-left; sprite 1 is to its right, and so on across 16 columns.
Creating Sprites
- Open the sprite editor tab (palette icon) in Developer Mode.
- Click a sprite slot to select it.
- Draw with the pencil tool — left-click to place pixels, right-click to erase.
- Use the palette to pick colors.
- Zoom in with the zoom slider for precision.
Drawing Sprites in Code
-- Draw sprite 1 at position (x, y) spr(1, x, y) -- Draw a 2x2 block of sprites starting at sprite 4 -- This draws sprites 4, 5, 20, 21 as a 16x16 image spr(4, x, y, 2, 2) -- Flip horizontally (for left-facing walk) spr(1, x, y, 1, 1, facing_left)
Sprite Animation
function _init() frame = 0 anim_timer = 0 end function _update() anim_timer += 1 if anim_timer >= 8 then -- change frame every 8 ticks (~4 fps) anim_timer = 0 frame = (frame + 1) % 4 -- cycle frames 0-3 end end function _draw() cls(0) spr(1 + frame, player.x, player.y) end
palt() to change which colors are transparent.
7. Tilemap
The tilemap is a 128×64 grid of tile indices. Each tile refers to a sprite. Draw a section of the map with map().
Designing Maps
- Open the Map Editor tab (grid icon) in Developer Mode.
- Select a tile from the sprite sheet at the bottom.
- Click and drag on the map canvas to paint tiles.
- Use the fill tool (bucket) for large areas.
Drawing the Map
function _draw() cls(1) -- draw tiles 0,0 to 31,31 of the map at screen (0,0) map(0, 0, -- map top-left tile 0, 0, -- screen destination 32, 32) -- tile count (256x256 pixels) end
Tile-based Collision
function is_solid(wx, wy) local tx = flr(wx / 8) local ty = flr(wy / 8) local tile = mget(tx, ty) -- sprite flag 0 = solid tile return (tile ~= 0) and (tile & 1) ~= 0 end
8. Sound
PLUTO-8 supports 8 SFX slots (0–7). You define sounds in the SFX editor inside Developer Mode, then play them in code.
Playing SFX
sfx(0) -- play sfx 0 at full volume sfx(1, 0.5) -- play sfx 1 at 50% volume function _update() if btnp(4) then sfx(0) -- play "jump" sound vy = -5 end end
9. Tips & Tricks
Performance
- Keep
_drawlean. Minimize draw calls per frame — avoid drawing thousands of individual pixels withpset. - Use sprite sheets and
spr()instead of per-pixel drawing. - Avoid allocating new tables every frame — reuse objects instead.
- Clearing only dirty rectangles with
rectfillinstead ofclscan help in static scenes.
Common Patterns
Entity list:
bullets = {} function spawn_bullet(x, y, vx, vy) add(bullets, {x=x, y=y, vx=vx, vy=vy, alive=true}) end function _update() for b in all(bullets) do b.x += b.vx; b.y += b.vy if b.x < 0 or b.x > 256 then del(bullets, b) end end end
State machine:
state = "title" function _update() if state == "title" then if btnp(4) then state = "game" end elseif state == "game" then update_game() elseif state == "gameover" then if btnp(4) then state = "title" end end end
AABB collision check:
function overlaps(a, b) return a.x < b.x+b.w and a.x+a.w > b.x and a.y < b.y+b.h and a.y+a.h > b.y end
10. Publishing
Once your game is complete, you can publish it to the PLUTO-8 community browser.
How to Publish
- Open the code editor and make sure your game runs without errors.
- Click the Publish button (upload icon) in the Developer Mode toolbar.
- Enter a name and optional description for your game.
- Click Confirm.
- Your game is now listed in the Community Games browser and playable by everyone.
After Publishing
- Players can like your game and leave it in their favourites.
- You can update your game at any time — the cartridge is replaced for all future plays.
- Play count and like count are shown on your game card.