diff options
| author | iamcheeseman <[email protected]> | 2026-03-30 17:52:23 -0400 |
|---|---|---|
| committer | iamcheeseman <[email protected]> | 2026-03-30 17:52:23 -0400 |
| commit | f52aaaad971d11fa145a391e521e37ea3c3b230e (patch) | |
| tree | baf5ae7bd8b284209a2551386fd324b5f57e7d44 /src/specks.lua | |
| parent | 7859709ac3b1500e3037c5dceacb77bcff778e89 (diff) | |
| parent | feafbd1e1a6a1846ab5fe416ee31e2e14f603edd (diff) | |
Merge branch 'main' of ssh://codeberg.org/the-gutter/gutshot
Diffstat (limited to 'src/specks.lua')
| -rw-r--r-- | src/specks.lua | 283 |
1 files changed, 283 insertions, 0 deletions
diff --git a/src/specks.lua b/src/specks.lua new file mode 100644 index 0000000..a703d04 --- /dev/null +++ b/src/specks.lua @@ -0,0 +1,283 @@ +Speck_Sys = {} +Speck_Sys.__index = Speck_Sys + +local SPAWN_FUNCTIONS = { + Rectangle = function (speck_sys) + local w = (speck_sys.spawn_width or 0) / 2 + local h = (speck_sys.spawn_height or 0) / 2 + + return randf_range(-w, w), randf_range(-h, h) + end, + + Circle = function (speck_sys) + local angle = randf_range(0, math.pi * 2) + local r = speck_sys.spawn_radius or 0 + r = r * lmath.random() + return r*math.cos(angle), r*math.sin(angle) + end, + + Point = function (speck_sys) + return 0, 0 + end +} + +function Speck_Sys.new() + local self = setmetatable({}, Speck_Sys) + + self.data = { + size = 0, + pos = {}, + vel = {}, + + alive = {}, + lifetime = {}, + lifetime_max = {}, + + scale_start = {}, + scale_end = {} + } + + self.free_ids = {} + self.free_ids_size = 0 + + self.bounce = false + self.gradient = "res/img/test_gradient.png" + + self.spawn_shape = "Point" + + self.spawn_amount_min = 1 + self.spawn_amount_max = 1 + + self.scale_curve = "Lerp" + self.scale_start_min = 0.8 + self.scale_start_max = 1.25 + + self.scale_end_min = 0 + self.scale_end_max = 0 + + self.forcex = 0 + self.forcey = 0 + + self.initial_velx = 0 + self.initial_vely = 0 + self.spread = 0 + + self.lifetime_min = 1 + self.lifetime_max = 5 + + self.interval = 0.1 + + self.texture_path = "res/img/speck.png" + + self.x = 0 + self.y = 0 + self.spawn_timer = self.interval + + self.oneshot = false + self.emitting = true + return self +end + +function Speck_Sys:check_bounce(i) + if not self.bounce then + return false + end + + local scn = get_current_scene() + assert(scn, "No scene set.") + + return has_tile( + scn.tilemap, + to_tile_coords(self.data.pos[i].x, self.data.pos[i].y)) +end + +function Speck_Sys:spawn_particles() + local data = self.data + local amt = lmath.random(self.spawn_amount_min, self.spawn_amount_max) + + local shape_func = SPAWN_FUNCTIONS[self.spawn_shape] + for i = 1, amt do + local id + if self.free_ids_size > 0 then + id = self.free_ids[self.free_ids_size] + self.free_ids_size = self.free_ids_size - 1 + else + self.data.size = self.data.size + 1 + id = self.data.size + end + + data.alive[id] = true + + local offx, offy = shape_func(self) + data.pos[id] = { x = self.x + offx, y = self.y + offy } + data.vel[id] = { x = self.initial_velx, y = self.initial_vely } + + local beta = self.spread * math.pi / 180 + beta = beta / 2 + beta = randf_range(-beta, beta) + + local rvx = (data.vel[id].x * math.cos(beta)) + - (data.vel[id].y * math.sin(beta)) + local rvy = (data.vel[id].x * math.sin(beta)) + + (data.vel[id].y * math.cos(beta)) + data.vel[id].x = rvx + data.vel[id].y = rvy + + data.lifetime[id] = randf_range( + self.lifetime_min, self.lifetime_max) + data.lifetime_max[id] = data.lifetime[id] + + data.scale_start[id] = randf_range( + self.scale_start_min, self.scale_start_max) + data.scale_end[id] = randf_range( + self.scale_end_min, self.scale_end_max) + end +end + +function Speck_Sys:update(dt) + local data = self.data + + -- particle spawning + self.spawn_timer = self.spawn_timer - dt + if self.spawn_timer <= 0 and self.emitting then + self.spawn_timer = self.interval + + self:spawn_particles() + if self.oneshot then + self.emitting = false + end + end + + -- particles processing + for i = 1, data.size do + if not data.alive[i] then + goto next_speck_update + end + + if data.lifetime[i] <= 0 then + data.alive[i] = false + + self.free_ids_size = self.free_ids_size + 1 + self.free_ids[self.free_ids_size] = i + goto next_speck_update + end + + data.vel[i].x = data.vel[i].x + self.forcex * dt + data.vel[i].y = data.vel[i].y + self.forcey * dt + + -- move and bounce + data.pos[i].x = data.pos[i].x + data.vel[i].x * dt + if self:check_bounce(i) then + data.vel[i].x = -data.vel[i].x + data.pos[i].x = data.pos[i].x + data.vel[i].x * dt + end + + data.pos[i].y = data.pos[i].y + data.vel[i].y * dt + if self:check_bounce(i) then + data.vel[i].y = -data.vel[i].y + data.pos[i].y = data.pos[i].y + data.vel[i].y * dt + end + + data.lifetime[i] = data.lifetime[i] - dt + + ::next_speck_update:: + end +end + +function Speck_Sys:is_empty() + if self.data.size == 0 then + return true + end + return self.free_ids_size == self.data.size +end + +function Speck_Sys:draw() + local scale_curve = EASING_FUNCTIONS[self.scale_curve] + local data = self.data + local tex = get_tex(self.texture_path) + + for i = 1, data.size do + if not data.alive[i] then + goto next_speck_draw + end + + local anim = 1 - data.lifetime[i] / data.lifetime_max[i] + local scale = scale_curve(data.scale_start[i], data.scale_end[i], anim) + + local w, h = tex:getDimensions() + + local gradient = get_tex_data(self.gradient) + local sample_pos = math.floor((gradient:getWidth() - 1) * anim) + lg.setColor(gradient:getPixel(sample_pos, 0.5)) + + lg.draw( + tex, data.pos[i].x, data.pos[i].y, 0, scale, scale, w / 2, h / 2 + ) + lg.setColor(1, 1, 1, 1) + ::next_speck_draw:: + end +end + + +function export_speck_sys(sys, filename) + local EXPORTED_ARGS = { + "spawn_shape", + "spawn_amount_min", + "spawn_amount_max", + "spawn_width", + "spawn_height", + "spawn_radius", + "scale_curve", + "scale_start_min", + "scale_start_max", + "scale_end_min", + "scale_end_max", + "forcex", + "forcey", + "initial_velx", + "initial_vely", + "spread", + "lifetime_min", + "lifetime_max", + "interval", + "texture_path", + "gradient", + "oneshot", + "bounce", + } + local exp = {} + + for i, arg in ipairs(EXPORTED_ARGS) do + exp[arg] = sys[arg] + end + export_to_source(exp, filename) +end + +local speck_bank = {} +function load_specks_from(path) + path = path or "res/speck" + local files = lf.getDirectoryItems(path) + + for _, file in ipairs(files) do + local filepath = path.."/"..file + + if lf.getInfo(filepath).type == "directory" then + load_specks_from(filepath) + else + if is_filetype(filepath, {"speck.lua"}) then + local data = lf.load(filepath)() + speck_bank[filepath] = data + end + end + end +end + +function load_speck_sys(filename) + local sys = Speck_Sys.new() + local loaded = speck_bank[filename] + for key, data in pairs(loaded) do + sys[key] = data + end + return sys +end + |
