Developer Manual
Руководство разработчика
Step-by-step guide to creating your first PLUTO-8 game.
Пошаговое руководство по созданию вашей первой игры на PLUTO-8.
★ Table of Contents
★ Содержание
- Getting StartedНачало работы
- Hello World
- The Game LoopИгровой цикл
- DrawingРисование
- InputУправление
- SpritesСпрайты
- TilemapТайлмап
- SoundЗвук
- Tips & TricksСоветы
- PublishingПубликация
- Code ModulesМодули кода
- Saving ProgressСохранение прогресса
- PlutoFL Game LibrariesИгровые библиотеки PlutoFL
- Physics EngineФизический движок
1. Getting Started
1. Начало работы
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.
PLUTO-8 — это фэнтезийная консоль, которая работает полностью внутри Roblox. Ничего устанавливать не нужно — просто войдите в игру Roblox и начинайте создавать.
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.
Фэнтезийная консоль — это симулятор ретро-игровой машины с намеренно ограниченными характеристиками. Эти ограничения упрощают изучение разработки игр и позволяют быстро заканчивать небольшие проекты. PLUTO-8 вдохновлён PICO-8, но живёт внутри Roblox, поэтому ваши игры мгновенно становятся доступны миллионам игроков.
Specifications at a Glance
Краткие характеристики
- Display: 256×256 pixels, 256-color palette
- Frame rate: 30 FPS
- Sprites: 256 sprites, each 8×8 pixels
- Tilemap: 128×128 tiles
- Scripting: Lua (sandbox, PICO-8-style API)
- RAM: 128 KB addressable memory
- ROM: 512 KB cartridge code (separate from RAM)
- VRAM: 64 KB video buffer (256×256 × 1 byte)
- SFX: 8 sound effect slots
- Дисплей: 256×256 пикселей, 256-цветная палитра
- Частота кадров: 30 FPS
- Спрайты: 256 спрайтов, каждый 8×8 пикселей
- Тайлмап: 128×128 тайлов
- Скриптинг: Lua (песочница, API в стиле PICO-8)
- ОЗУ: 128 КБ адресуемой памяти
- ROM: 512 КБ код картриджа (отдельно от RAM)
- VRAM: 64 КБ видеобуфер (256×256 × 1 байт)
- SFX: 8 слотов звуковых эффектов
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.
- Войдите в игру PLUTO-8 на Roblox.
- Нажмите Tab (или иконку карандаша на мобильном) для входа в режим разработчика.
- Редактор кода откроется справа на экране.
- Введите ваш код на Lua и нажмите Ctrl+Enter (или кнопку Run) для запуска.
2. Hello World
The smallest possible PLUTO-8 game just needs a _draw function:
Самая простая игра на PLUTO-8 требует только функции _draw:
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.
Скопируйте это в редактор кода и нажмите Run. Вы увидите белый текст на чёрном фоне.
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.
Разберём по частям:
cls(0)— очистить экран цветом 0 (чёрный). Всегда вызывайте это первым в _draw, иначе будет виден предыдущий кадр.print("HELLO, WORLD!", 60, 120, 7)— нарисовать текст в позиции (60, 120) цветом 7 (белый). Экран 256×256, поэтому (60, 120) находится слева по центру.
3. The Game Loop
3. Игровой цикл
PLUTO-8 runs three functions in a fixed order, 30 times per second:
PLUTO-8 запускает три функции в фиксированном порядке, 30 раз в секунду:
function _init() -- Runs ONCE when the cartridge starts. -- Initialise your variables here. score = 0 player = { x = 128, y = 200 } end function _update(dt) -- Runs 30 times per second. dt = real delta time (~0.033s). -- 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(dt)is called with the real delta time. - Immediately after:
_draw()is called. - The resulting frame is sent to all connected clients.
- При первом запуске:
_init()вызывается один раз. - Каждый кадр (30 fps): вызывается
_update(dt)с реальным дельта-временем. - Сразу после: вызывается
_draw(). - Полученный кадр отправляется всем подключённым клиентам.
_init is missing, the game still works. If _draw is missing, nothing is displayed._init отсутствует, игра всё равно работает. Если _draw отсутствует, ничего не отображается.Delta Time (dt)
Дельта-время (dt)
The global variable dt contains the real time between frames in seconds (~0.033 at stable 30 FPS). It is also passed as an argument to _update(dt). Use it for frame-rate independent movement and timers:
Глобальная переменная dt содержит реальное время между кадрами в секундах (~0.033 при стабильных 30 FPS). Также передаётся как аргумент в _update(dt). Используйте для движения и таймеров, независимых от частоты кадров:
local speed = 120 -- pixels per second local timer = 3 -- 3 second countdown function _update(dt) x += speed * dt timer -= dt if timer <= 0 then -- time's up! end end
Note: Old cartridges without dt continue to work. You can also use a fixed timestep (2 pixels per frame = 60 pixels per second) if you prefer.
Примечание: Старые картриджи без dt продолжают работать. Вы также можете использовать фиксированный шаг (2 пикселя за кадр = 60 пикселей в секунду), если предпочитаете.
4. Drawing
4. Рисование
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:
PLUTO-8 использует 256-цветную палитру. Цвета 0–15 соответствуют классической палитре PICO-8. Цвета 16–255 — расширенные. Наиболее часто используемые цвета:
- 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
- 0 — Чёрный
- 1 — Тёмно-синий
- 2 — Тёмно-фиолетовый
- 3 — Тёмно-зелёный
- 5 — Тёмно-серый
- 6 — Светло-серый
- 7 — Белый
- 8 — Красный
- 9 — Оранжевый
- 10 — Жёлтый
- 11 — Зелёный
- 12 — Синий
- 14 — Розовый
- 15 — Персиковый
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(text, x, y, color) для простого текста. Встроенный шрифт 4×6 пикселей. Каждый символ 4 пикселя шириной с интервалом 1 пиксель, то есть одна строка текста занимает около 5 пикселей на символ.
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).
Используйте camera(x, y) для прокрутки вида. Все последующие вызовы рисования смещаются так, что мировая позиция (x, y) отображается в экранной позиции (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
5. Управление
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.
PLUTO-8 поддерживает до 7 кнопок. На компьютере они соответствуют клавишам клавиатуры; на мобильных устройствах на экране отображается D-pad и кнопки A/B.
| ID | ActionДействие | KeyboardКлавиатура | MobileМобильный |
|---|---|---|---|
| 0 | LeftВлево | ← or A | D-pad leftD-pad влево |
| 1 | RightВправо | → or D | D-pad rightD-pad вправо |
| 2 | UpВверх | ↑ or W | D-pad upD-pad вверх |
| 3 | DownВниз | ↓ or S | D-pad downD-pad вниз |
| 4 | A (confirm)A (подтвердить) | Z | A buttonКнопка A |
| 5 | B (cancel)B (отмена) | X | B buttonКнопка B |
| 6 | Start | Enter | Start buttonКнопка Start |
btn vs btnp
btn и 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.
btn(id) возвращает true каждый кадр, пока кнопка зажата. Используйте для непрерывного движения.
btnp(id) возвращает true только в первый кадр нажатия. Используйте для действий, которые должны сработать один раз — например, прыжок, выстрел или выбор пункта меню.
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
Mouse and Touch
Мышь и касания
PLUTO-8 also tracks mouse position and clicks. On mobile, touching the game screen directly works as mouse input — separate from the D-pad and buttons.
mx()/my()— cursor X/Y in screen coordinates (0–255). Returns-1if the cursor is outside the screen.mbtn(0)—truewhile left mouse button or finger is held on screen.mbtnp(0)—trueonly on the first frame of a click/tap.
Coordinates are in screen space (same as drawing commands), independent of the camera offset.
PLUTO-8 также отслеживает положение мыши и клики. На мобильных устройствах прямое касание экрана игры работает как ввод мыши — отдельно от D-pad и кнопок.
mx()/my()— X/Y курсора в экранных координатах (0–255). Возвращает-1, если курсор за пределами экрана.mbtn(0)—true, пока левая кнопка мыши или палец удерживается на экране.mbtnp(0)—trueтолько в первый кадр клика/касания.
Координаты в экранном пространстве (как команды рисования), независимо от смещения камеры.
function _draw() cls(0) if mx() >= 0 then -- cursor is on screen local col = mbtn(0) and 7 or 6 circfill(mx(), my(), 4, col) end end
6. Sprites
6. Спрайты
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.
Лист спрайтов содержит 256 спрайтов, каждый 8×8 пикселей. Спрайт 0 — верхний левый; спрайт 1 — правее, и так далее по 16 колонкам.
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.
- Откройте вкладку редактора спрайтов (иконка палитры) в режиме разработчика.
- Кликните на слот спрайта, чтобы выбрать его.
- Рисуйте карандашом — левая кнопка мыши ставит пиксели, правая — стирает.
- Используйте палитру для выбора цветов.
- Увеличьте масштаб ползунком для точного рисования.
Game Icon (Sprite 255)
Иконка игры (спрайт 255)
Sprite 255 is reserved as the game icon. It is displayed in the in-console game catalog (32–36 px) and on the website pluto-8.com/games.html (64×64 px).
Спрайт 255 зарезервирован как иконка игры. Он отображается в каталоге игр в приставке (32–36 px) и на сайте pluto-8.com/games.html (64×64 px).
- Open the SPRITE tab in the IDE.
- Select or draw the sprite you want as your icon (8×8 pixels).
- Click the SET ICON button — the sprite is copied to slot 255.
- Click SAVE — the icon is saved with the cartridge.
- When you publish, the icon automatically appears in the game catalog.
- Откройте вкладку SPRITE в IDE.
- Выберите или нарисуйте нужный спрайт (8×8 пикселей).
- Нажмите кнопку SET ICON — спрайт скопируется в слот 255.
- Нажмите SAVE — иконка сохранится вместе с картриджем.
- При публикации иконка автоматически отобразится в каталоге игр.
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.palt(), чтобы изменить, какие цвета прозрачны.7. Tilemap
7. Тайлмап
The tilemap is a 128×64 grid of tile indices. Each tile refers to a sprite. Draw a section of the map with map().
Тайлмап — это сетка 128×64 тайловых индексов. Каждый тайл ссылается на спрайт. Рисуйте секцию карты с помощью 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
8. Звук
PLUTO-8 supports 8 SFX slots (0–7). You define sounds in the SFX editor inside Developer Mode, then play them in code.
PLUTO-8 поддерживает 8 слотов SFX (0–7). Звуки задаются в редакторе SFX в режиме разработчика, а затем воспроизводятся в коде.
Playing SFX
Воспроизведение 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
Built-in SFX Presets
Встроенные пресеты SFX
| Slot | Character | When to use |
|---|---|---|
| 0 | Low hit | Death, damage, lose |
| 1 | Soft bump | Pick up item, touch |
| 2 | Medium step | Interaction, footstep |
| 3 | Neutral beep | Menu navigation, select |
| 4 | Rising tone | Coin, score point |
| 5 | High pitch | Jump, launch, start |
| 6 | Explosion | Destruction, crash |
| 7 | Victory | Bonus, level complete |
| Слот | Характер | Когда использовать |
|---|---|---|
| 0 | Низкий удар | Смерть, урон, проигрыш |
| 1 | Мягкий бамп | Подбор предмета, касание |
| 2 | Средний шаг | Взаимодействие, шаг |
| 3 | Нейтральный бип | Навигация, выбор |
| 4 | Подъём | Монета, очко |
| 5 | Высокий | Прыжок, запуск |
| 6 | Взрыв | Разрушение, столкновение |
| 7 | Победа | Бонус, финал уровня |
Typical Usage
Типичное использование
function _update() if btnp(4) and on_ground then vy = -6 sfx(5) -- jump sound end if got_coin then sfx(4) -- coin sound end if hp <= 0 then sfx(0) -- death sound end end
9. Tips & Tricks
9. Советы
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.
- Держите
_drawлёгким. Минимизируйте количество вызовов рисования за кадр — не рисуйте тысячи отдельных пикселей черезpset. - Используйте листы спрайтов и
spr()вместо попиксельного рисования. - Не создавайте новые таблицы каждый кадр — переиспользуйте объекты.
- В статичных сценах помогает очистка только изменившихся прямоугольников через
rectfillвместоcls.
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:
Проверка AABB-коллизии:
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
10. Публикация
Once your game is complete, you can publish it to the PLUTO-8 community browser.
Когда игра готова, вы можете опубликовать её в браузере сообщества PLUTO-8.
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.
- Откройте редактор кода и убедитесь, что игра запускается без ошибок.
- Нажмите кнопку Publish (иконка загрузки) на панели инструментов режима разработчика.
- Введите название и необязательное описание игры.
- Нажмите Confirm.
- Ваша игра теперь отображается в браузере Community Games и доступна всем.
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.
- Игроки могут лайкнуть игру и добавить её в избранное.
- Вы можете обновить игру в любое время — картридж заменяется для всех будущих запусков.
- Количество запусков и лайков отображается на карточке игры.
11. Modules & Libraries
11. Модули и библиотеки
PLUTO-8 uses a single require() function to load both cartridge modules and platform libraries.
PLUTO-8 использует единую функцию require() для подключения как модулей картриджа, так и платформенных библиотек.
Loading Priority
Приоритет загрузки
require("name") searches in this order:
- Cartridge modules — files inside your project
- Platform libraries — built-in PLUTO-8 libraries
require("name") ищет в следующем порядке:
- Модули картриджа — файлы внутри вашего проекта
- Платформенные библиотеки — встроенные библиотеки PLUTO-8
Cartridge Modules
Модули картриджа
The code editor supports up to 256 modules per cartridge.
- Main — the entry point module. Always present, cannot be deleted.
- Additional modules — helper code loaded via
require("name"). - Switch between modules via the dropdown menu at the top of the editor.
- Modules can be renamed and deleted (except Main).
- Each module must
returna value (typically a table).
Редактор кода поддерживает до 256 модулей в одном картридже.
- Main — главный модуль, точка входа. Всегда присутствует, удалить нельзя.
- Дополнительные модули — вспомогательный код, подключаемый через
require("name"). - Переключение модулей — через dropdown-меню в верхней части редактора.
- Модули можно переименовывать и удалять (кроме Main).
- Каждый модуль должен возвращать значение через
return(обычно таблицу).
Platform Libraries
Платформенные библиотеки
Built-in libraries available in any cartridge:
| Library | Description |
|---|---|
collision | AABB, circle-circle, point-in-rect, circle-rect checks |
tween | Easing functions (linear, ease in/out, cubic, bounce) and lerp |
particles | Simple particle system (emit, update, draw) |
timer | Timer system: delayed and repeating actions |
geom | 2D geometry utilities |
scene | Scene/state manager |
gameobject | Entity management with tagging |
sprite | Animated sprites with rotation |
Встроенные библиотеки, доступные в любом картридже:
| Библиотека | Описание |
|---|---|
collision | AABB, circle-circle, point-in-rect, circle-rect коллизии |
tween | Easing-функции (linear, ease in/out, cubic, bounce) и lerp |
particles | Простая система частиц (emit, update, draw) |
timer | Таймеры: отложенные и повторяющиеся действия |
geom | 2D-геометрия |
scene | Менеджер сцен |
gameobject | Система сущностей с тегами |
sprite | Анимированные спрайты с rotation |
Example
Пример
-- Module "utils": local utils = {} function utils.lerp(a, b, t) return a + (b - a) * t end function utils.clamp(v, lo, hi) return max(lo, min(hi, v)) end return utils -- Module "Main": local utils = require("utils") -- cartridge module local col = require("collision") -- platform library function _init() x = 128 end function _update() x = utils.lerp(x, mx(), 0.2) end function _draw() cls(0) circfill(utils.clamp(x, 10, 246), 128, 10, 10) end
collision, tween, particles, timer, geom, scene, gameobject, sprite) instead of writing your own. See section 13 for PlutoFL library details. Good cartridge module candidates: Utils, UI (menus, HUD), Enemies (AI, spawning), Levels (data tables).collision, tween, particles, timer, geom, scene, gameobject, sprite) вместо написания своих. Подробнее о библиотеках PlutoFL — в разделе 13. Хорошие кандидаты для модулей картриджа: Utils, UI (меню, HUD), Enemies (ИИ, спавн), Levels (таблицы данных).12. Saving Progress
12. Сохранение прогресса
PLUTO-8 supports saving player scores between sessions. Scores are tied to the player and game name.
PLUTO-8 поддерживает сохранение результатов игроков между сессиями. Результаты привязаны к игроку и названию игры.
Available Functions
Доступные функции
| Function | Description |
|---|---|
save_score(score) | Save score (only if higher than previous best) |
load_score() | Load best score (0 if none) |
get_leaderboard(n) | Top-N players (max 10), returns {name, score} |
load_game_score(name) | Best score in another game by name |
| Функция | Описание |
|---|---|
save_score(score) | Сохранить результат (только если лучше предыдущего) |
load_score() | Загрузить лучший результат (0 если нет) |
get_leaderboard(n) | Топ-N игроков (макс. 10), возвращает {name, score} |
load_game_score(name) | Лучший результат в другой игре по имени |
Full Example: Game with Leaderboard
Полный пример: игра с лидербордом
function _init() score = 0 best = load_score() state = "title" top = {} end function _update() if state == "title" then if mbtnp(0) then sfx(3) state = "play" end elseif state == "play" then score += 1 if score > 100 then save_score(score) top = get_leaderboard(5) state = "over" end elseif state == "over" then if mbtnp(0) then score = 0 state = "title" end end end function _draw() cls(1) if state == "title" then print("TAP TO PLAY", 78, 120, 7) print("BEST: "..tostr(best), 90, 134, 10) elseif state == "play" then print("SCORE: "..tostr(score), 4, 4, 7) elseif state == "over" then print("GAME OVER", 90, 40, 8) print("YOUR SCORE: "..tostr(score), 70, 60, 7) print("TOP PLAYERS:", 70, 85, 10) for i, e in pairs(top) do print(i..". "..e.name.." "..tostr(e.score), 55, 95+i*12, 6) end print("TAP TO RETRY", 78, 170, 11) end end
13. PlutoFL Game Libraries
13. Игровые библиотеки PlutoFL
PlutoFL is a set of high-level libraries for rapid game development. They save hundreds of characters of code (cartridge limit is 9000 characters).
PlutoFL — набор высокоуровневых библиотек для быстрой разработки игр. Экономят сотни символов кода (лимит картриджа — 9000 символов).
timer
timer
Schedule delayed and repeating actions without manual frame counting.
Планируйте отложенные и повторяющиеся действия без ручного подсчёта кадров.
| Function | Description |
|---|---|
timer.after(sec, fn) | Call fn once after sec seconds |
timer.every(sec, fn) | Call fn every sec seconds |
timer.cancel(id) | Cancel a scheduled timer by its ID |
timer.update() | Advance all timers (call in _update) |
timer.clear() | Remove all timers |
| Функция | Описание |
|---|---|
timer.after(sec, fn) | Вызвать fn один раз через sec секунд |
timer.every(sec, fn) | Вызывать fn каждые sec секунд |
timer.cancel(id) | Отменить таймер по его ID |
timer.update() | Обновить все таймеры (вызывать в _update) |
timer.clear() | Удалить все таймеры |
local T = require("timer") function _init() -- spawn an enemy every 2 seconds T.every(2, function() add(enemies, {x=rnd(240), y=0}) end) -- show "GO!" after 1 second T.after(1, function() msg = "GO!" end) end function _update() T.update() end
geom
geom
2D geometry helpers: distance, angles, vectors, and intersections.
Помощники 2D-геометрии: расстояние, углы, векторы, пересечения.
| Function | Description |
|---|---|
geom.dist(x1,y1,x2,y2) | Distance between two points |
geom.angle(x1,y1,x2,y2) | Angle from point 1 to point 2 (0–1, PICO-8 style) |
geom.normalize(x,y) | Returns unit vector (nx, ny) |
geom.lerp2d(x1,y1,x2,y2,t) | Linear interpolation between two points |
geom.rot(x,y,a) | Rotate point around origin by angle a (0–1) |
| Функция | Описание |
|---|---|
geom.dist(x1,y1,x2,y2) | Расстояние между двумя точками |
geom.angle(x1,y1,x2,y2) | Угол от точки 1 к точке 2 (0–1, стиль PICO-8) |
geom.normalize(x,y) | Возвращает единичный вектор (nx, ny) |
geom.lerp2d(x1,y1,x2,y2,t) | Линейная интерполяция между двумя точками |
geom.rot(x,y,a) | Повернуть точку вокруг начала координат на угол a (0–1) |
local G = require("geom") function _update() -- enemy chases player local d = G.dist(enemy.x, enemy.y, px, py) if d < 80 then local nx, ny = G.normalize(px - enemy.x, py - enemy.y) enemy.x += nx * 1.5 enemy.y += ny * 1.5 end end
scene
scene
Manage game states (title, play, gameover) cleanly. Each scene has its own init, update, and draw callbacks.
Чистое управление состояниями игры (title, play, gameover). Каждая сцена имеет свои колбэки init, update и draw.
| Function | Description |
|---|---|
scene.add(name, tbl) | Register a scene with {init, update, draw} |
scene.go(name) | Switch to scene (calls init) |
scene.current() | Returns current scene name |
scene.update() | Call current scene's update |
scene.draw() | Call current scene's draw |
| Функция | Описание |
|---|---|
scene.add(name, tbl) | Зарегистрировать сцену с {init, update, draw} |
scene.go(name) | Переключиться на сцену (вызывает init) |
scene.current() | Возвращает имя текущей сцены |
scene.update() | Вызвать update текущей сцены |
scene.draw() | Вызвать draw текущей сцены |
local S = require("scene") S.add("title", { init = function() t = 0 end, update = function() t += 1 if btnp(4) then S.go("play") end end, draw = function() cls(0) print("PRESS Z", 100, 120, 7) end }) S.add("play", { init = function() score = 0 end, update = function() score += 1 end, draw = function() cls(1) print("SCORE: "..score, 4, 4, 7) end }) function _init() S.go("title") end function _update() S.update() end function _draw() S.draw() end
gameobject
gameobject
Lightweight entity system with tagging. Create, query, update, and draw game objects without manual list management.
Лёгкая система сущностей с тегами. Создавайте, запрашивайте, обновляйте и рисуйте игровые объекты без ручного управления списками.
| Function | Description |
|---|---|
go.new(props) | Create an object with {x, y, tag, update, draw, ...} |
go.all(tag) | Return all objects with a given tag (or all if nil) |
go.remove(obj) | Mark object for removal |
go.update() | Call update on every object, then sweep removed |
go.draw() | Call draw on every object |
go.clear() | Remove all objects |
| Функция | Описание |
|---|---|
go.new(props) | Создать объект с {x, y, tag, update, draw, ...} |
go.all(tag) | Вернуть все объекты с тегом (или все, если nil) |
go.remove(obj) | Пометить объект на удаление |
go.update() | Вызвать update у каждого объекта, затем удалить помеченные |
go.draw() | Вызвать draw у каждого объекта |
go.clear() | Удалить все объекты |
local go = require("gameobject") function spawn_coin(x, y) go.new({ x = x, y = y, tag = "coin", draw = function(self) circfill(self.x, self.y, 3, 10) end }) end function _update() go.update() -- check player vs all coins for c in all(go.all("coin")) do if abs(px - c.x) < 6 and abs(py - c.y) < 6 then score += 1 sfx(4) go.remove(c) end end end
sprite
sprite
Animated sprite helper with frame sequencing and rotation support.
Помощник анимированных спрайтов с последовательностями кадров и поддержкой rotation.
| Function | Description |
|---|---|
sprite.anim(name, {frames}, fps) | Define a named animation |
sprite.new(n, x, y) | Create sprite instance at position |
sprite.play(s, name) | Play named animation |
sprite.update(s) | Advance animation frame |
sprite.draw(s) | Draw sprite (with flip/rotation support) |
sprite.bbox(s) | Get bounding box → x, y, w, h |
sprite.tapped(s) | True if sprite was tapped/clicked this frame |
sprite.held(s) | True if touch/mouse is held on sprite |
sprite.hover(s) | True if cursor is over sprite (no press needed) |
| Функция | Описание |
|---|---|
sprite.anim(name, {frames}, fps) | Определить именованную анимацию |
sprite.new(n, x, y) | Создать экземпляр спрайта в позиции |
sprite.play(s, name) | Запустить анимацию |
sprite.update(s) | Обновить кадр анимации |
sprite.draw(s) | Нарисовать спрайт (с flip/rotation) |
sprite.bbox(s) | Получить bounding box → x, y, w, h |
sprite.tapped(s) | True если спрайт нажат/кликнут в этом кадре |
sprite.held(s) | True если касание/мышь удерживается на спрайте |
sprite.hover(s) | True если курсор над спрайтом (без нажатия) |
local sp = require("sprite") sp.anim("walk", {1,2,3,4}, 8) local hero function _init() hero = sp.new(1, 128, 200) sp.play(hero, "walk") end function _update() sp.update(hero) if btn(0) then hero.x -= 2; hero.flipX = true end if btn(1) then hero.x += 2; hero.flipX = false end -- touch input helpers if sp.tapped(hero) then sfx(0) end end function _draw() cls(0) sp.draw(hero) end
Complete Example: All PlutoFL Libraries Together
Полный пример: все библиотеки PlutoFL вместе
A coin-collecting mini-game using timer, geom, scene, gameobject, and sprite:
Мини-игра по сбору монет с использованием timer, geom, scene, gameobject и sprite:
local T = require("timer") local G = require("geom") local S = require("scene") local go = require("gameobject") local SP = require("sprite") -- ── title scene ── S.add("title", { init = function() blink = 0 end, update = function() blink = (blink + 1) % 40 if btnp(4) then S.go("play") end end, draw = function() cls(1) print("COIN GRAB", 88, 100, 10) if blink < 25 then print("PRESS Z", 100, 130, 7) end end }) -- ── play scene ── S.add("play", { init = function() px, py, score = 128, 128, 0 anim = SP.new({1,2,3,4}, 6) go.clear() T.clear() -- spawn a coin every 1.5 seconds T.every(1.5, function() go.new({ x = 8 + rnd(240), y = 8 + rnd(240), tag = "coin", age = 0, update = function(self) self.age += 1 if self.age > 150 then go.remove(self) end end, draw = function(self) circfill(self.x, self.y, 3, 10) end }) end) end, update = function() T.update() if btn(0) then px -= 2; anim.flip_x = true end if btn(1) then px += 2; anim.flip_x = false end if btn(2) then py -= 2 end if btn(3) then py += 2 end anim:update() go.update() -- collect coins using geom.dist for c in all(go.all("coin")) do if G.dist(px, py, c.x, c.y) < 7 then score += 1 sfx(4) go.remove(c) end end if score >= 10 then S.go("win") end end, draw = function() cls(1) go.draw() anim:draw(px, py) print("COINS: "..score.."/10", 4, 4, 7) end }) -- ── win scene ── S.add("win", { init = function() sfx(7) end, update = function() if btnp(4) then S.go("title") end end, draw = function() cls(3) print("YOU WIN!", 96, 120, 10) end }) function _init() S.go("title") end function _update() S.update() end function _draw() S.draw() end
scene for state management, gameobject for entities, timer for scheduling, geom for spatial math, and sprite for animations. Each library is small and does not count against your 9000-character cartridge limit.scene для управления состояниями, gameobject для сущностей, timer для планирования, geom для пространственной математики, sprite для анимаций. Каждая библиотека мала и не считается в лимит 9000 символов картриджа.14. Physics Engine
14. Физический движок
The physics library provides a lightweight 2D physics engine with gravity, collision detection and response, explosions, and spatial queries. It supports rectangular and circular bodies in three modes: dynamic (moves and collides), static (immovable), and sensor (detects overlap without physical response).
Библиотека physics предоставляет легковесный 2D-физический движок с гравитацией, детекцией и откликом столкновений, взрывами и пространственными запросами. Поддерживает прямоугольные и круглые тела в трёх режимах: dynamic (двигается и сталкивается), static (неподвижное), sensor (детектирует пересечение без физического отклика).
| Function | Description |
|---|---|
physics.world(gx, gy) | Create a physics world with gravity |
physics.step(w) | Advance simulation one frame (call in _update). Auto sub-stepping at high velocities to prevent tunneling |
physics.gravity(w, gx, gy) | Change gravity vector |
physics.body(w, kind, x, y, w, h) | Create rectangular body |
physics.circle(w, kind, x, y, r) | Create circular body |
physics.remove(w, b) | Remove body from world |
physics.push(b, fx, fy) | Apply force (F/mass) |
physics.impulse(b, ix, iy) | Instant velocity change |
physics.blast(w, x, y, r, f) | Radial explosion |
physics.at(w, px, py) | Bodies at point |
physics.overlaps(w, rx, ry, rw, rh) | Bodies in rectangle |
| Функция | Описание |
|---|---|
physics.world(gx, gy) | Создать физический мир с гравитацией |
physics.step(w) | Шаг симуляции (вызывать в _update). Авто sub-stepping при высоких скоростях для предотвращения туннелирования |
physics.gravity(w, gx, gy) | Изменить вектор гравитации |
physics.body(w, kind, x, y, w, h) | Создать прямоугольное тело |
physics.circle(w, kind, x, y, r) | Создать круглое тело |
physics.remove(w, b) | Удалить тело из мира |
physics.push(b, fx, fy) | Приложить силу (F/масса) |
physics.impulse(b, ix, iy) | Мгновенное изменение скорости |
physics.blast(w, x, y, r, f) | Радиальный взрыв |
physics.at(w, px, py) | Тела в точке |
physics.overlaps(w, rx, ry, rw, rh) | Тела в прямоугольнике |
Body Properties
Свойства тела
| Property | Description | Default |
|---|---|---|
b.x, b.y | Position (top-left for rects, center for circles) | — |
b.vx, b.vy | Velocity | 0 |
b.bounce | Restitution (0–1) | 0.2 |
b.friction | Friction (0–1) | 0.1 |
b.mass | Mass | 1 |
b.tag | String tag for identification | nil |
| Свойство | Описание | По умолчанию |
|---|---|---|
b.x, b.y | Позиция (верхний-левый для rect, центр для circle) | — |
b.vx, b.vy | Скорость | 0 |
b.bounce | Упругость (0–1) | 0.2 |
b.friction | Трение (0–1) | 0.1 |
b.mass | Масса | 1 |
b.tag | Строковый тег для идентификации | nil |
Collision Callback
Коллбэк столкновений
Set w.on_collide = function(a, b) to be notified of collisions. Return false to ignore a collision (useful for one-way platforms, triggers, etc.).
Установите w.on_collide = function(a, b) для оповещения о столкновениях. Верните false, чтобы игнорировать столкновение (полезно для односторонних платформ, триггеров и т.д.).
Example: Bouncing Balls
Пример: прыгающие мячи
local phys = require("physics") local w = phys.world(0, 0.3) function _init() phys.body(w, "static", 0, 240, 256, 16) phys.body(w, "static", -8, 0, 8, 256) phys.body(w, "static", 256, 0, 8, 256) for i = 1, 10 do local b = phys.circle(w, "dynamic", 64+rnd(128), rnd(100), 4+rnd(4)) b.bounce = 0.6 end end function _update() phys.step(w) if mbtnp(0) then phys.blast(w, mx(), my(), 40, 5) end end function _draw() cls(0) for _,b in ipairs(w.bodies) do if b.shape=="c" then circfill(b.x, b.y, b.r, 10) else rectfill(b.x, b.y, b.w, b.h, 5) end end print("TAP TO EXPLODE", 60, 2, 7) end
physics library handles up to ~50 bodies comfortably at 30 FPS. Use sensor bodies for triggers and pickups. Combine with gameobject and scene for complete game architecture.physics справляется с ~50 телами при 30 FPS. Используйте sensor-тела для триггеров и подбираемых предметов. Комбинируйте с gameobject и scene для полной архитектуры игры.