initial commit

This commit is contained in:
harper
2026-05-14 20:14:48 -07:00
commit 9d65ca32c4
11 changed files with 846 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
export/
captures/
+14
View File
@@ -0,0 +1,14 @@
{
"$schema": "https://raw.githubusercontent.com/LuaLS/vscode-lua/master/setting/schema.json",
"runtime.version": "Lua 5.4",
"runtime.nonstandardSymbol": ["+=", "-=", "*=", "/=", "%="],
"workspace.library": ["./meta"],
"format.defaultConfig": {
"indent_style": "space",
"indent_size": "2"
},
"diagnostics": {
"globals": ["vim", "love"],
"disable": ["lowercase-global"]
}
}
+7
View File
@@ -0,0 +1,7 @@
Class = {}
Class.__index = Class
function Class:new(tbl)
tbl = tbl or {}
setmetatable(tbl, { __index = self })
end
+61
View File
@@ -0,0 +1,61 @@
collision = {}
collision.colliders = {}
collision.debug = false
function collision.draw()
for _, v in pairs(collision.colliders) do
gfx.rect(v.x, v.y, v.w, v.h, gfx.COLOR_BLUE)
end
end
function collision.add(tbl)
table.insert(collision.colliders, tbl)
end
function collision.remove(index)
table.remove(collision.colliders, index)
end
function collision.move(aabb, dx, dy)
local normal = { x = 0, y = 0 }
if aabb.normal ~= nil then
aabb.normal = normal
end
-- move x
aabb.x = aabb.x + dx
for _, v in pairs(collision.colliders) do
if aabb ~= v and collision.aabb_check(aabb, v) then
if dx > 0 then
aabb.x = v.x - aabb.w
normal.x = -1
elseif dx < 0 then
aabb.x = v.x + v.w
normal.x = 1
end
end
end
-- move y
aabb.y = aabb.y + dy
for _, v in pairs(collision.colliders) do
if aabb ~= v and collision.aabb_check(aabb, v) then
if dy > 0 then
aabb.y = v.y - aabb.h
normal.y = -1
elseif dy < 0 then
aabb.y = v.y + v.h
normal.y = 1
end
end
end
return normal
end
function collision.aabb_check(a, b)
return a.x < b.x + b.w and b.x < a.x + a.w and a.y < b.y + b.h and b.y < a.y + a.h
end
+24
View File
@@ -0,0 +1,24 @@
vector = {}
vector.utils = {}
function vec(x, y)
return {
x = x,
y = y,
}
end
function vector.utils.input_vector()
local vec = { x = 0, y = 0 }
vec.x = vector.utils.bool_to_int(input.held(input.RIGHT)) - vector.utils.bool_to_int(input.held(input.LEFT))
vec.y = vector.utils.bool_to_int(input.held(input.DOWN)) - vector.utils.bool_to_int(input.held(input.UP))
return vec
end
function vector.utils.bool_to_int(b)
if b == true then
return 1
else
return 0
end
end
+48
View File
@@ -0,0 +1,48 @@
require("player")
require("engine.vector")
require("engine.class")
require("engine.collision")
require("world")
function _config()
return {
name = "Game",
pixel_perfect = true,
game_id = "com.harper.usagitest",
}
end
collision.add(Player.aabb)
for i = 1, 10 do
local new_block = {
color = gfx.COLOR_DARK_GRAY,
aabb = {
x = (i - 1) * 10,
y = 50,
w = world.cell_size,
h = world.cell_size,
},
}
world.utils.set_tile(new_block.aabb.x, new_block.aabb.y, new_block)
end
world.utils.update_colliders()
function _init()
State = {}
end
function _update(dt)
Player:update(dt)
end
function _draw()
gfx.clear(gfx.COLOR_BLACK)
Player:draw()
for _, v in pairs(world.tiles) do
gfx.rect_fill(v.aabb.x, v.aabb.y, 10, 10, v.color)
end
collision.draw()
end
+608
View File
@@ -0,0 +1,608 @@
---@meta
-- Generated by usagi 0.6.1. Run `usagi refresh` to update.
-- Usagi API stubs for lua-language-server.
-- Declarations only; this file is never executed by the runtime.
---Pico-8 palette, indices 0-15.
---@class Usagi.Gfx
---@field COLOR_BLACK integer 0
---@field COLOR_DARK_BLUE integer 1
---@field COLOR_DARK_PURPLE integer 2
---@field COLOR_DARK_GREEN integer 3
---@field COLOR_BROWN integer 4
---@field COLOR_DARK_GRAY integer 5
---@field COLOR_LIGHT_GRAY integer 6
---@field COLOR_WHITE integer 7
---@field COLOR_RED integer 8
---@field COLOR_ORANGE integer 9
---@field COLOR_YELLOW integer 10
---@field COLOR_GREEN integer 11
---@field COLOR_BLUE integer 12
---@field COLOR_INDIGO integer 13
---@field COLOR_PINK integer 14
---@field COLOR_PEACH integer 15
gfx = {}
---Clears the screen to the given color.
---@param color integer a gfx.COLOR_* constant
function gfx.clear(color) end
---Draws text at (x, y) in the given color. Uses the bundled monogram
---font at its 16px design size (a 5×7 pixel font with 16px line height).
---@param text string string to render
---@param x number left edge in game-space pixels
---@param y number top edge in game-space pixels
---@param color integer a gfx.COLOR_* constant
function gfx.text(text, x, y, color) end
---Draws a rectangle outline.
---@param x number left edge in game-space pixels
---@param y number top edge in game-space pixels
---@param w number width in pixels
---@param h number height in pixels
---@param color integer a gfx.COLOR_* constant
function gfx.rect(x, y, w, h, color) end
---Draws a filled rectangle.
---@param x number left edge in game-space pixels
---@param y number top edge in game-space pixels
---@param w number width in pixels
---@param h number height in pixels
---@param color integer a gfx.COLOR_* constant
function gfx.rect_fill(x, y, w, h, color) end
---Draws a circle outline centered at (x, y).
---@param x number center x in game-space pixels
---@param y number center y in game-space pixels
---@param r number radius in pixels
---@param color integer a gfx.COLOR_* constant
function gfx.circ(x, y, r, color) end
---Draws a filled circle centered at (x, y).
---@param x number center x in game-space pixels
---@param y number center y in game-space pixels
---@param r number radius in pixels
---@param color integer a gfx.COLOR_* constant
function gfx.circ_fill(x, y, r, color) end
---Draws a line from (x1, y1) to (x2, y2).
---@param x1 number start x in game-space pixels
---@param y1 number start y in game-space pixels
---@param x2 number end x in game-space pixels
---@param y2 number end y in game-space pixels
---@param color integer a gfx.COLOR_* constant
function gfx.line(x1, y1, x2, y2, color) end
---Sets a single pixel.
---@param x number x in game-space pixels
---@param y number y in game-space pixels
---@param color integer a gfx.COLOR_* constant
function gfx.pixel(x, y, color) end
---Draws a 16×16 sprite from the loaded sheet at (x, y). The sheet is
---`sprites.png` next to the game's main .lua; indices run left-to-right,
---top-to-bottom. Alpha-channel pixels render as transparent.
---@param index integer one-based sprite index (1 = top-left cell)
---@param x number destination left edge in game-space pixels
---@param y number destination top edge in game-space pixels
function gfx.spr(index, x, y) end
---Extended `spr`: draws a 16×16 sprite with required flip flags. Same
---indexing as `gfx.spr`.
---@param index integer one-based sprite index (1 = top-left cell)
---@param x number destination left edge in game-space pixels
---@param y number destination top edge in game-space pixels
---@param flip_x boolean flip horizontally (mirror left/right) when true
---@param flip_y boolean flip vertically (mirror top/bottom) when true
function gfx.spr_ex(index, x, y, flip_x, flip_y) end
---Draws an arbitrary (sx, sy, sw, sh) rectangle from `sprites.png` at
---(dx, dy) at its original size. `s*` args index into the source sheet
---in pixels; `d*` args are the destination on screen.
---@param sx number source rect left edge on `sprites.png` (pixels)
---@param sy number source rect top edge on `sprites.png` (pixels)
---@param sw number source rect width in pixels
---@param sh number source rect height in pixels
---@param dx number destination left edge in game-space pixels
---@param dy number destination top edge in game-space pixels
function gfx.sspr(sx, sy, sw, sh, dx, dy) end
---Extended `sspr`: source rect stretched to (dw, dh) at the destination
---with required flip flags. All ten args required; write a thin
---wrapper if a particular flag combination shows up often in your
---code.
---@param sx number source rect left edge on `sprites.png` (pixels)
---@param sy number source rect top edge on `sprites.png` (pixels)
---@param sw number source rect width in pixels
---@param sh number source rect height in pixels
---@param dx number destination left edge in game-space pixels
---@param dy number destination top edge in game-space pixels
---@param dw number destination width in pixels (stretches the source)
---@param dh number destination height in pixels (stretches the source)
---@param flip_x boolean flip horizontally (mirror left/right) when true
---@param flip_y boolean flip vertically (mirror top/bottom) when true
function gfx.sspr_ex(sx, sy, sw, sh, dx, dy, dw, dh, flip_x, flip_y) end
---Activates a post-process fragment shader. Loads `shaders/<name>.fs`
---(and optional `<name>.vs`) and runs it as the final pass when the
---game render target is blitted to the window. Pass nil to clear.
---On web the loader prefers `<name>_es.fs` (GLSL ES 100); on desktop
---it prefers `<name>.fs` (GLSL 330). Shader source live-reloads on
---save in `usagi dev`.
---@param name string|nil shader name (file stem under `shaders/`), or nil to clear
function gfx.shader_set(name) end
---Sets a uniform on the active shader. The value type drives the
---uniform type: a number maps to float, a 2/3/4-length numeric table
---maps to vec2 / vec3 / vec4. Queues the write; the engine flushes
---queued uniforms once per frame before the post-process pass.
---@param name string uniform name as declared in the shader source
---@param value number|number[] float, or {x, y} / {x, y, z} / {x, y, z, w}
function gfx.shader_uniform(name, value) end
---@class Usagi.Sfx
sfx = {}
---Plays a sound effect by name. Names are file stems from the `sfx/`
---directory next to the game's main .lua (e.g. `sfx/jump.wav` → "jump").
---Unknown names silently no-op. Calling while already playing restarts.
---@param name string file stem of a `.wav` under `sfx/`
function sfx.play(name) end
---@class Usagi.Music
music = {}
---Plays a music track once and stops at the end. Names are file stems
---from the `music/` directory next to the game's main .lua (e.g.
---`music/intro.ogg` → "intro"). Recognized extensions: ogg, mp3, wav,
---flac. Stops the currently-playing track first if there is one.
---Unknown names silently no-op. Callable from `_init` so a title
---track can start the moment the window opens.
---@param name string file stem under `music/`
function music.play(name) end
---Plays a music track and loops it forever. Stops the currently-
---playing track first. Callable from `_init`.
---@param name string file stem under `music/`
function music.loop(name) end
---Stops whatever music is currently playing. No-op when nothing is.
function music.stop() end
---Abstract input actions. Each is a union over keyboard keys, gamepad
---buttons, and analog-stick directions:
---
---- LEFT: arrow left, A, dpad left, left stick left
---- RIGHT: arrow right, D, dpad right, left stick right
---- UP: arrow up, W, dpad up, left stick up
---- DOWN: arrow down, S, dpad down, left stick down
---- BTN1: Z, J; gamepad south face (Xbox A, PS Cross)
---- BTN2: X, K; gamepad east face (Xbox B, PS Circle)
---- BTN3: C, L; gamepad north + west face (Xbox Y/X, PS Triangle/Square)
---
---Mouse buttons (separate from the action constants above):
---
---- MOUSE_LEFT: left mouse button
---- MOUSE_RIGHT: right mouse button
---
---Source identifiers for `input.last_source()` and the source-aware
---`input.mapping_for`:
---
---- SOURCE_KEYBOARD: "keyboard"
---- SOURCE_GAMEPAD: "gamepad"
---@class Usagi.Input
---@field LEFT integer
---@field RIGHT integer
---@field UP integer
---@field DOWN integer
---@field BTN1 integer
---@field BTN2 integer
---@field BTN3 integer
---@field MOUSE_LEFT integer
---@field MOUSE_RIGHT integer
---@field SOURCE_KEYBOARD string
---@field SOURCE_GAMEPAD string
---@field KEY_A integer
---@field KEY_B integer
---@field KEY_C integer
---@field KEY_D integer
---@field KEY_E integer
---@field KEY_F integer
---@field KEY_G integer
---@field KEY_H integer
---@field KEY_I integer
---@field KEY_J integer
---@field KEY_K integer
---@field KEY_L integer
---@field KEY_M integer
---@field KEY_N integer
---@field KEY_O integer
---@field KEY_P integer
---@field KEY_Q integer
---@field KEY_R integer
---@field KEY_S integer
---@field KEY_T integer
---@field KEY_U integer
---@field KEY_V integer
---@field KEY_W integer
---@field KEY_X integer
---@field KEY_Y integer
---@field KEY_Z integer
---@field KEY_0 integer
---@field KEY_1 integer
---@field KEY_2 integer
---@field KEY_3 integer
---@field KEY_4 integer
---@field KEY_5 integer
---@field KEY_6 integer
---@field KEY_7 integer
---@field KEY_8 integer
---@field KEY_9 integer
---@field KEY_F1 integer
---@field KEY_F2 integer
---@field KEY_F3 integer
---@field KEY_F4 integer
---@field KEY_F5 integer
---@field KEY_F6 integer
---@field KEY_F7 integer
---@field KEY_F8 integer
---@field KEY_F9 integer
---@field KEY_F10 integer
---@field KEY_F11 integer
---@field KEY_F12 integer
---@field KEY_SPACE integer
---@field KEY_ENTER integer
---@field KEY_ESCAPE integer
---@field KEY_TAB integer
---@field KEY_BACKSPACE integer
---@field KEY_DELETE integer
---@field KEY_LEFT integer
---@field KEY_RIGHT integer
---@field KEY_UP integer
---@field KEY_DOWN integer
---@field KEY_LSHIFT integer
---@field KEY_RSHIFT integer
---@field KEY_LCTRL integer
---@field KEY_RCTRL integer
---@field KEY_LALT integer
---@field KEY_RALT integer
---@field KEY_BACKTICK integer
---@field KEY_MINUS integer
---@field KEY_EQUAL integer
---@field KEY_LBRACKET integer
---@field KEY_RBRACKET integer
---@field KEY_BACKSLASH integer
---@field KEY_SEMICOLON integer
---@field KEY_APOSTROPHE integer
---@field KEY_COMMA integer
---@field KEY_PERIOD integer
---@field KEY_SLASH integer
input = {}
---Returns true the frame any source bound to `action` first went down.
---@param action integer one of input.LEFT / RIGHT / UP / DOWN / BTN1 / BTN2 / BTN3
---@return boolean
function input.pressed(action) end
---Returns true while any source bound to `action` is held.
---@param action integer one of input.LEFT / RIGHT / UP / DOWN / BTN1 / BTN2 / BTN3
---@return boolean
function input.held(action) end
---Returns true the frame any source bound to `action` first went up
---(transitioned from held to released). Mirrors `input.pressed` for the
---release edge.
---@param action integer one of input.LEFT / RIGHT / UP / DOWN / BTN1 / BTN2 / BTN3
---@return boolean
function input.released(action) end
---Label of the active input source's primary binding for `action` (e.g.
---"Z" on keyboard, "Pad-A" on gamepad). Honors any keymap remap the
---player set via the pause menu's Configure Keys flow. Useful for
---rendering contextual control prompts. Returns `nil` for unknown
---actions or when the active source has no binding for `action`.
---@param action integer one of input.LEFT / RIGHT / UP / DOWN / BTN1 / BTN2 / BTN3
---@return string?
function input.mapping_for(action) end
---The input source that most recently fired any bound action. Returns
---`input.SOURCE_KEYBOARD` ("keyboard") or `input.SOURCE_GAMEPAD`
---("gamepad"). Switches only when a *bound* input fires, so menu keys
---and idle activity don't flip it.
---@return string matches one of input.SOURCE_KEYBOARD / input.SOURCE_GAMEPAD
function input.last_source() end
---Cursor position in game-space pixels (so it lines up with `gfx.*`
---coords regardless of window size or pixel-perfect scaling). Returns
---two values: `x, y`. When the cursor sits over the letterbox bars,
---the values fall outside `0..usagi.GAME_W` / `0..usagi.GAME_H` —
---bounds-check before treating them as in-game coords.
---@return integer x game-space x in pixels
---@return integer y game-space y in pixels
function input.mouse() end
---Returns true while the given mouse button is held.
---@param button integer one of input.MOUSE_LEFT / input.MOUSE_RIGHT
---@return boolean
function input.mouse_held(button) end
---Returns true the frame the given mouse button first went down.
---@param button integer one of input.MOUSE_LEFT / input.MOUSE_RIGHT
---@return boolean
function input.mouse_pressed(button) end
---Returns true the frame the given mouse button first went up
---(transitioned from held to released).
---@param button integer one of input.MOUSE_LEFT / input.MOUSE_RIGHT
---@return boolean
function input.mouse_released(button) end
---Returns true while the given keyboard key is held.
---
---Direct keyboard reads bypass the keymap override and gamepad
---bindings — prefer `input.held(action)` for game actions players
---should be able to remap or play with a controller. Use this for dev
---hotkeys (toggling debug overlays, F-key shortcuts) and for
---keyboard-and-mouse-only games.
---@param key integer one of the input.KEY_* constants
---@return boolean
function input.key_held(key) end
---Returns true the frame the given keyboard key first went down. See
---`input.key_held` for the bypass-the-keymap caveat.
---@param key integer one of the input.KEY_* constants
---@return boolean
function input.key_pressed(key) end
---Returns true the frame the given keyboard key first went up
---(transitioned from held to released). See `input.key_held` for the
---bypass-the-keymap caveat.
---@param key integer one of the input.KEY_* constants
---@return boolean
function input.key_released(key) end
---Show or hide the OS cursor over the game window. Persists until
---changed. Callable from `_init` so games can hide the cursor before
---the first frame draws (e.g. when rendering a custom in-game cursor).
---@param visible boolean true to show, false to hide
function input.set_mouse_visible(visible) end
---Returns true when the OS cursor is currently shown over the window.
---Reflects the latest `input.set_mouse_visible` call synchronously, so
---it's safe to use as part of a toggle:
---`input.set_mouse_visible(not input.mouse_visible())`.
---@return boolean
function input.mouse_visible() end
---Engine-level info. The per-domain APIs (`gfx`, `input`) are top-level
---globals, not fields on this table.
---@class Usagi
---@field GAME_W number game render width in pixels
---@field GAME_H number game render height in pixels
---@field SPRITE_SIZE integer side length, in pixels, of one cell in `sprites.png` (drives `gfx.spr` indexing)
---@field IS_DEV boolean true under `usagi dev`; false for `usagi run` and compiled binaries
---@field elapsed number wall-clock seconds since session start; updated once per frame before _update
usagi = {}
---Measures `text` in the bundled font and returns its rendered size
---in pixels. Returns two values: `width, height`. Available from any
---callback (`_init`, `_update`, `_draw`) — useful for pre-computing
---layout once in `_init` and reusing the result every frame.
---@param text string string to measure
---@return integer width pixel width
---@return integer height pixel height (equals the font's line height)
function usagi.measure_text(text) end
---Persist a Lua table as JSON. Saves are per-game, namespaced by
---`game_id` from `_config()`. One file per game; nest your own
---structure inside (settings, run state, unlocks).
---@param t table table to serialize. functions, userdata, NaN, and cycles error
function usagi.save(t) end
---Read the persisted save table back. Returns `nil` on first run
---(no save file). Idiomatic call: `state = usagi.load() or { ... defaults ... }`.
---@return table?
function usagi.load() end
---Config table returned by `_config()`. All fields optional except
---`game_id`, which is only required if you call `usagi.save` /
---`usagi.load`. Missing fields fall back to engine defaults.
---@class Usagi.Config
---@field name? string display name. Window title, macOS .app bundle directory, and (slugged) archive/binary names on `usagi export` (default: project directory name)
---@field pixel_perfect? boolean false (default) = any scale that fits the window while preserving aspect ratio; true = integer scale only with letterbox bars
---@field game_id? string reverse-DNS identifier (e.g. "com.you.mygame"), required for save/load
---@field icon? integer 1-based tile index into sprites.png to use as the window icon (same indexing as gfx.spr); omit for the default Usagi bunny
---@field game_width? number game render width in pixels (default 320). Tested range 160..640
---@field game_height? number game render height in pixels (default 180). Tested range 90..360
---@field sprite_size? integer side length, in pixels, of one cell in sprites.png (default 16). Drives gfx.spr indexing, the tilepicker tool's grid, and the window-icon slicer. sprites.png must be a multiple of this value on both axes.
---Optional. Returns engine config read once before the window opens.
---Omit if the defaults are fine.
---@return Usagi.Config?
function _config() end
---Called once when the game starts. Use for loading assets and initializing state.
function _init() end
---Called every frame to update game state. Runs before _draw.
---@param dt number delta-time: seconds since last frame
function _update(dt) end
---Called every frame to render. Runs after _update.
---@param dt number delta-time: seconds since last frame
function _draw(dt) end
---@class Usagi.Vec2
---@field x number
---@field y number
---@class Usagi.Rect
---@field x number
---@field y number
---@field w number
---@field h number
---@class Usagi.Circ
---@field x number
---@field y number
---@field r number
---Drop-in math/geometry helpers. Pure Lua, no engine state. Source
---lives in `runtime/util.lua` — read it for full implementations or
---fork it if you want different semantics.
---@class Usagi.Util
util = {}
---Clamps `v` into `[lo, hi]`.
---@param v number
---@param lo number
---@param hi number
---@return number
function util.clamp(v, lo, hi) end
---Returns -1, 0, or 1 according to the sign of `v`.
---@param v number
---@return integer
function util.sign(v) end
---Half-up rounding to the nearest integer. Pixel snapping is the
---driving use case in 2D pixel-art games.
---@param v number
---@return integer
function util.round(v) end
---Moves `current` toward `target` by at most `max_delta`, never
---overshooting. Per-frame smoothing primitive — pass a delta
---scaled by `dt` for frame-rate independence.
---@param current number
---@param target number
---@param max_delta number
---@return number
function util.approach(current, target, max_delta) end
---Linear interpolation. `t = 0` returns `a`, `t = 1` returns `b`.
---Values of `t` outside `[0, 1]` extrapolate (no clamping).
---@param a number
---@param b number
---@param t number
---@return number
function util.lerp(a, b, t) end
---Wraps `v` into `[lo, hi)`. Useful for cyclic values like angles or
---looped indexing. Works for negative `v`: `util.wrap(-1, 0, 4) == 3`.
---@param v number
---@param lo number
---@param hi number
---@return number
function util.wrap(v, lo, hi) end
---Boolean from time. Toggles `hz` times per second — the on/off
---interval is `1/hz` seconds. For invincibility flicker, UI blinks,
---low-health warnings.
---@param t number seconds
---@param hz number toggles per second
---@return boolean
function util.flash(t, hz) end
---Normalizes a `{x, y}` vector to unit length. Returns a new table;
---the input is unchanged. A zero vector returns `{x = 0, y = 0}`.
---@param v Usagi.Vec2
---@return Usagi.Vec2
function util.vec_normalize(v) end
---Distance between two `{x, y}` points.
---@param a Usagi.Vec2
---@param b Usagi.Vec2
---@return number
function util.vec_dist(a, b) end
---Squared distance between two `{x, y}` points. Cheaper than
---`vec_dist` (skips the sqrt); use for "is X closer than Y?" by
---comparing against `r * r`.
---@param a Usagi.Vec2
---@param b Usagi.Vec2
---@return number
function util.vec_dist_sq(a, b) end
---Builds a vector at `angle` (radians) with magnitude `len`. `len`
---defaults to 1 for a unit vector. Pair with `math.atan(dy, dx)` to
---convert any direction into a velocity.
---@param angle number radians
---@param len? number magnitude (default 1)
---@return Usagi.Vec2
function util.vec_from_angle(angle, len) end
---True when the `{x, y}` point is inside the rect `{x, y, w, h}`.
---Half-open: left/top edges are inside, right/bottom edges are
---outside. Matches typical sprite-rect hit testing.
---@param p Usagi.Vec2
---@param r Usagi.Rect
---@return boolean
function util.point_in_rect(p, r) end
---True when the `{x, y}` point is strictly inside the circle
---`{x, y, r}`. Points on the boundary are considered outside.
---@param p Usagi.Vec2
---@param c Usagi.Circ
---@return boolean
function util.point_in_circ(p, c) end
---True when the two AABBs share interior area. Edge-adjacent rects
---are considered non-overlapping.
---@param a Usagi.Rect
---@param b Usagi.Rect
---@return boolean
function util.rect_overlap(a, b) end
---True when the two circles overlap. Tangent circles are
---considered non-overlapping.
---@param a Usagi.Circ
---@param b Usagi.Circ
---@return boolean
function util.circ_overlap(a, b) end
---True when a circle and a rect overlap. Uses the closest-point
---method: clamp the circle center to the rect, test distance.
---@param c Usagi.Circ
---@param r Usagi.Rect
---@return boolean
function util.circ_rect_overlap(c, r) end
---Engine-level juice primitives: hitstop, screen shake, flash, and
---slow-motion. Each call sets per-session state that decays once per
---frame. Stacking rule across all four: longer duration wins; for
---the magnitude param, the latest call wins. Spam-calling is safe.
effect = {}
---Freezes the game's `_update` loop for `time` seconds. `_draw` keeps
---running so the world stays on-screen. The classic juice trick for
---weighty hits: pair with `effect.screen_shake` and `effect.flash` on
---impact. If a longer hitstop is already in flight, this call is a
---no-op (longer wins).
---@param time number seconds to freeze update
function effect.hitstop(time) end
---Shakes the rendered view for `time` seconds with up to `intensity`
---game-pixel offset. Magnitude decays linearly to zero across the
---duration. The shake is applied to the RT-to-screen blit, so
---overlays drawn outside the world (error, REC indicator) stay
---stable.
---@param time number seconds to shake
---@param intensity number maximum offset in game pixels (try 2-6)
function effect.screen_shake(time, intensity) end
---Flashes a full-screen overlay of palette color `color` over the
---rendered view for `time` seconds. Alpha decays linearly from
---opaque to transparent. White on hits, red on damage, etc.
---@param time number seconds the flash is visible
---@param color integer a gfx.COLOR_* constant
function effect.flash(time, color) end
---Scales the `dt` passed to `_update` for `time` seconds. `scale=0.5`
---is half-speed; `scale=0` freezes update (use `effect.hitstop` for
---that explicitly); `scale>1` plays faster. Wall-clock decay is
---unaffected; the slow_mo timer itself counts down at real time.
---@param time number seconds the scale is applied
---@param scale number dt multiplier; 0..1 for slow, >1 for fast
function effect.slow_mo(time, scale) end
+58
View File
@@ -0,0 +1,58 @@
require("engine.vector")
Player = {
speed = 1,
velocity = vec(0, 0),
aabb = {
x = 0,
y = 0,
w = 10,
h = 10,
normal = {},
},
pointer = vec(0, 0),
looking_at = nil,
}
function Player:update(dt)
self:input()
if self.aabb.normal.y ~= -1 then
self.velocity.y += 10 * dt
end
local targeting = world.utils.get_tile(self.pointer.x, self.pointer.y)
if targeting ~= nil then
self.looking_at = targeting
else
self.looking_at = nil
end
collision.move(self.aabb, self.velocity.x, self.velocity.y)
end
function Player:draw()
gfx.rect_fill(Player.aabb.x, Player.aabb.y, 10, 10, gfx.COLOR_WHITE)
gfx.circ(self.pointer.x + (world.cell_size / 2), self.pointer.y + (world.cell_size / 2), 3, gfx.COLOR_WHITE)
end
function Player:input()
local input_dir = vector.utils.input_vector()
input_dir = util.vec_normalize(vector.utils.input_vector())
self.velocity.x = input_dir.x * self.speed
self.pointer = world.utils.to_tile(
self.aabb.x + (input_dir.x * world.cell_size),
self.aabb.y + (input_dir.y * world.cell_size)
)
if input.pressed(input.BTN1) and self.aabb.normal.y == -1 then
self.velocity.y = -3
end
if input.pressed(input.BTN2) and self.looking_at ~= nil then
self.looking_at.aabb = nil
for i, v in pairs(collision.colliders) do
print(v.aabb.x, v.aabb.y, v.aabb.w, v.aabb.h)
end
world.utils.set_tile(self.pointer.x, self.pointer.y, nil)
end
end
Executable
BIN
View File
Binary file not shown.
Executable
BIN
View File
Binary file not shown.
+24
View File
@@ -0,0 +1,24 @@
world = {}
world.tiles = {}
world.utils = {}
world.cell_size = 10
function world.utils.update_colliders()
for i, v in pairs(world.tiles) do
collision.add(v.aabb)
end
end
function world.utils.to_tile(x, y)
return vec(util.round(x / world.cell_size) * world.cell_size, util.round(y / world.cell_size) * world.cell_size)
end
function world.utils.set_tile(x, y, tbl)
local key = x .. "," .. y
world.tiles[key] = tbl
end
function world.utils.get_tile(x, y)
local key = x .. "," .. y
return world.tiles[key]
end