diff --git a/spritesheet.lua b/spritesheet.lua index eea094b..3fc7ad6 100644 --- a/spritesheet.lua +++ b/spritesheet.lua @@ -10,6 +10,16 @@ local error, error_internal do end end +-------------------------------------------------------------------------------- +-- Checks that LÖVE is defined; if not, run in information loading +-- mode only. + +local define_love = true +if type(love) ~= 'table' then + io.stderr:write '[Spritesheet]: Loaded in non-LÖVE environment. Can still load spritesheet data,\n but image drawing methods will not be defined.\n' + define_love = false +end + -------------------------------------------------------------------------------- -- Util @@ -17,8 +27,19 @@ local function calculate_animation_duration (self, frame_i) local frame_i = frame_i or math.huge assert(type(self) == 'table') assert(type(frame_i) == 'number') + + -- If time_total is provided, return that. + if self.time_total then + assert(self.time == nil) + self.time = self.time_total/#self + return self.time_total + end + -- Easy if number - if type(self.time) == 'number' then return math.min(#self, frame_i) * self.time end + if type(self.time) == 'number' then + return math.min(#self, frame_i) * self.time + end + -- Sum if table local sum = 0 for i = 1, math.min(#self, frame_i) do sum = sum + self.time[i] end @@ -51,6 +72,8 @@ function Sprite.new (quad, imagesheet) return setmetatable({ quad = quad, imagesheet = imagesheet, is_sprite = true }, Sprite) end +if define_love then + function Sprite:generateImage () local imagesheet, quad = self.imagesheet, self.quad self.func = function (x, y) @@ -75,6 +98,8 @@ function Sprite:getSheetDimensions() return self.imagesheet.image:getDimensions() end +end + setmetatable(Sprite, {__call = function(_, ...) return Sprite.new(...) end}) -------------------------------------------------------------------------------- @@ -85,25 +110,26 @@ local Animation = {} function Animation.new (self) assert(type(self) == 'table') - assert(type(self.time) == 'table' or type(self.time) == 'number' and self.time > 0) + assert(type(self.time) == 'table' or type(self.time) == 'number' and self.time > 0 or type(self.time_total) == 'number') assert(#self > 0) - assert(type(self.time) == 'number' or #self == #self.time) - + assert(type(self.time) == 'number' or type(self.time_total) == 'number' or #self == #self.time) + if self.time_total then assert(self.time == nil) end assert(self.wrap == nil or self.wrap == true or self.wrap == false) local self = setmetatable(self, Animation) self.duration = calculate_animation_duration(self) self.is_animation = true + -- Contact frame? if self.contact_frame then - -- why was this minus one? It screwed up the audio. - --self.contact_time = calculate_animation_duration(self, self.contact_frame - 1) self.contact_time = calculate_animation_duration(self, self.contact_frame) end -- return self end +if define_love then + function Animation:generateImage () self.func = function (x, y, t) t = t or 0 @@ -138,6 +164,8 @@ function Animation:getSheetDimensions() return self.imagesheet.image:getDimensions() end +end + setmetatable(Animation, {__call = function(_, ...) return Animation.new(...) end}) -------------------------------------------------------------------------------- @@ -152,7 +180,7 @@ local function calculate_frame_times (time_list, nr_frames) assert(type(time_list) == 'table' or type(time_list) == 'number') assert(type(nr_frames) == 'number') - local frame_times = { [0] = -math.huge } + local frame_times = { [0] = -math.huge, orig = not define_love and time_list or nil } if type(time_list) == 'number' then for i = 1, nr_frames do @@ -168,27 +196,33 @@ local function calculate_frame_times (time_list, nr_frames) return frame_times end -local function load_quads (image, quad_data, imagesheet) - assert(image.typeOf and image:typeOf('Image')) - assert(type(quad_data) == 'table') +local function load_quads (_, _, quad_data, imagesheet) + assert(type(imagesheet) == 'table') + assert(type(imagesheet.tiles_per_row) == 'number') + assert(type(imagesheet.tiles_per_column) == 'number') - local image_width, image_height = image:getDimensions() - local tile_width, tile_height = quad_data.tile_width, quad_data.tile_height - local tiles_per_row = image_width/tile_width - local max_quad_id = tiles_per_row * (image_height/tile_height) - 1 + local tile_width, tile_height = imagesheet.tile_width, imagesheet.tile_height + local tiles_per_row = imagesheet.tiles_per_row + local max_quad_id = tiles_per_row * imagesheet.tiles_per_column - 1 local quad_cache = {} local function quad_from_id (id) -- Error checking if type(id) ~= 'number' then error('All quad ids must be natural numbers, but one was %s (%s)', id, type(id)) end - if id ~= math.floor(id) then error('All quad ids must be natural numbers, but one was %03.03f (floating point number)', id) end + if id % 1 ~= 0 then error('All quad ids must be natural numbers, but one was %03.03f (floating point number)', id) end if not (0 <= id and id <= max_quad_id) then error('All quad ids must - for this spritesheet ("%s") - be natural numbers equal/below %i, but one was %s = 0x%X', imagesheet.filename, max_quad_id, id, id) end -- Calculate - if not quad_cache[id] then - quad_cache[id] = love.graphics.newQuad((id%tiles_per_row)*tile_width, math.floor(id/tiles_per_row)*tile_height, tile_width, tile_height, image_width, image_height) + local quad = quad_cache[id] + if quad == nil then + if define_love then + quad = love.graphics.newQuad((id%tiles_per_row)*tile_width, math.floor(id/tiles_per_row)*tile_height, tile_width, tile_height, imagesheet.width, imagesheet.height) + else + quad = { id = id, (id%tiles_per_row)*tile_width, math.floor(id/tiles_per_row)*tile_height, tile_width, tile_height } + end + quad_cache[id] = quad end - return quad_cache[id] + return quad end local function visit_animation (t) @@ -203,7 +237,7 @@ local function load_quads (image, quad_data, imagesheet) return Sprite(quad_from_id(n), imagesheet) end - local function visit_node (t) + local function visit_node (t, already_seen) assert(type(t) == 'table') for key, val in pairs(t) do @@ -211,12 +245,14 @@ local function load_quads (image, quad_data, imagesheet) if val_type == 'number' then t[key] = visit_quad(val) elseif val_type == 'table' then - local visit_func = val.is_animation and visit_animation or visit_node - visit_func(val) + local visit_func = rawget(val,'is_animation') and visit_animation or visit_node + visit_func(val, already_seen) end end - error.strict_table(t) + if type(error) == 'table' and define_love then + error.strict_table(t) + end end assert(type(quad_data) == 'table') @@ -232,7 +268,14 @@ local function load_quad_data (filename) -- Attempt to load file for filetype in pairs(SPRITESHEET_DATA_FILETYPES) do - local chunk, error_msg = love.filesystem.load(filename..'.lua') + + local chunk, error_msg + if define_love then + chunk, error_msg = love.filesystem.load(filename..'.lua') + else + chunk, error_msg = loadfile(filename..'.lua') + end + if chunk then local data = setfenv(chunk, SPRITESHEET_ENV)() @@ -292,25 +335,36 @@ function SpriteSheet.new (filename) -- spriteimage itself if you can, as it will silently ignore -- several errors. + local img, width, height + if define_love then + img = love.graphics.newImage(filename..'.png') + width, height = img:getDimensions() + else + img = require 'imlib2'.image.load(filename..'.png') + width = img:get_width() + height = img:get_height() + end + -- Set info local self = setmetatable({}, SpriteSheet) self.filename = filename - self.image = love.graphics.newImage(filename..'.png') + self.image = img + self.width = width + self.height = height self.origin_x = 0 self.origin_y = 0 -- TODO: Give warning/error due to rounding down. - quad_data.tile_width = quad_data.tile_width or math.floor(self.image:getWidth() / quad_data.tiles_per_row) - quad_data.tile_height = quad_data.tile_height or math.floor(self.image:getHeight() / quad_data.tiles_per_column) - print(self.tile_width, self.tile_height) + self.tiles_per_row = quad_data.tiles_per_row or math.floor(width / quad_data.tile_width) + self.tiles_per_column = quad_data.tiles_per_column or math.floor(height / quad_data.tile_height) - self.tile_width = quad_data.tile_width - self.tile_height = quad_data.tile_height + self.tile_width = quad_data.tile_width or math.floor(width / self.tiles_per_row) + self.tile_height = quad_data.tile_height or math.floor(height / self.tiles_per_column) -- Error checking do - local rem_width = self.image:getWidth() % self.tile_width - local rem_height = self.image:getHeight() % self.tile_height + local rem_width = width % self.tile_width + local rem_height = height % self.tile_height if not quad_data.force_uneven_tile_size and (rem_width ~= 0 or rem_height ~= 0) then local s = ('Bad spritesheet "%s". Image size (%i, %i) must be dividable by tile size (%i, %i)') :format(filename, self.image:getWidth(), self.image:getHeight(), self.tile_width, self.tile_height) @@ -326,7 +380,7 @@ function SpriteSheet.new (filename) end -- Import quads into SpriteSheet - self.quads = load_quads(self.image, quad_data, self) + self.quads = load_quads(width, height, quad_data, self) if rawget(self.quads, 'is_sprite') or rawget(self.quads, 'is_animation') then self.only_quads = self.quads else @@ -344,15 +398,19 @@ function SpriteSheet:setOrigin (ox, oy, mode) assert(type(ox) == 'number') assert(type(oy) == 'number') + self.origin_orig = { ox, oy, mode } + if mode == 'absolute' then self.origin_x = ox self.origin_y = oy elseif mode == 'relative' or mode == nil then - self.origin_x = self.tile_width * ox - self.origin_y = self.tile_height * oy + self.origin_x = math.floor(self.tile_width * ox) + self.origin_y = math.floor(self.tile_height * oy) else error('Unknown origin mode %s (%s)', mode, type(mode)) end + assert(self.origin_x % 1 == 0) + assert(self.origin_y % 1 == 0) end function SpriteSheet:getQuadKeys ()