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.damping = 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 SPECK_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", "damping", "spread", "lifetime_min", "lifetime_max", "interval", "texture_path", "gradient", "oneshot", "bounce", } function Speck_Sys:check_bounce(i) if not self.bounce then return false end local scn = get_current_scene() assert(scn, "No scene set.") if not scn.tilemap then return false end 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 data.lifetime[i] = data.lifetime[i] - dt 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 local dampx = -data.vel[i].x * self.damping local dampy = -data.vel[i].y * self.damping data.vel[i].x = data.vel[i].x + (dampx + self.forcex) * dt data.vel[i].y = data.vel[i].y + (dampy + 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 ::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 exp = {} for i, arg in ipairs(SPECK_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 function load_speck_sys_non_cached(filename) speck_bank[filename] = lf.load(filename)() return load_speck_sys(filename) end