Can now also be used outside of LÖVE, in a low capacity.
This commit is contained in:
parent
bc3752da1b
commit
89884e59c4
126
spritesheet.lua
126
spritesheet.lua
|
@ -10,6 +10,16 @@ local error, error_internal do
|
||||||
end
|
end
|
||||||
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
|
-- Util
|
||||||
|
|
||||||
|
@ -17,8 +27,19 @@ local function calculate_animation_duration (self, frame_i)
|
||||||
local frame_i = frame_i or math.huge
|
local frame_i = frame_i or math.huge
|
||||||
assert(type(self) == 'table')
|
assert(type(self) == 'table')
|
||||||
assert(type(frame_i) == 'number')
|
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
|
-- 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
|
-- Sum if table
|
||||||
local sum = 0
|
local sum = 0
|
||||||
for i = 1, math.min(#self, frame_i) do sum = sum + self.time[i] end
|
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)
|
return setmetatable({ quad = quad, imagesheet = imagesheet, is_sprite = true }, Sprite)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if define_love then
|
||||||
|
|
||||||
function Sprite:generateImage ()
|
function Sprite:generateImage ()
|
||||||
local imagesheet, quad = self.imagesheet, self.quad
|
local imagesheet, quad = self.imagesheet, self.quad
|
||||||
self.func = function (x, y)
|
self.func = function (x, y)
|
||||||
|
@ -75,6 +98,8 @@ function Sprite:getSheetDimensions()
|
||||||
return self.imagesheet.image:getDimensions()
|
return self.imagesheet.image:getDimensions()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
setmetatable(Sprite, {__call = function(_, ...) return Sprite.new(...) end})
|
setmetatable(Sprite, {__call = function(_, ...) return Sprite.new(...) end})
|
||||||
|
|
||||||
--------------------------------------------------------------------------------
|
--------------------------------------------------------------------------------
|
||||||
|
@ -85,25 +110,26 @@ local Animation = {}
|
||||||
|
|
||||||
function Animation.new (self)
|
function Animation.new (self)
|
||||||
assert(type(self) == 'table')
|
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(#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)
|
assert(self.wrap == nil or self.wrap == true or self.wrap == false)
|
||||||
|
|
||||||
local self = setmetatable(self, Animation)
|
local self = setmetatable(self, Animation)
|
||||||
self.duration = calculate_animation_duration(self)
|
self.duration = calculate_animation_duration(self)
|
||||||
self.is_animation = true
|
self.is_animation = true
|
||||||
|
|
||||||
-- Contact frame?
|
-- Contact frame?
|
||||||
if self.contact_frame then
|
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)
|
self.contact_time = calculate_animation_duration(self, self.contact_frame)
|
||||||
end
|
end
|
||||||
--
|
--
|
||||||
return self
|
return self
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if define_love then
|
||||||
|
|
||||||
function Animation:generateImage ()
|
function Animation:generateImage ()
|
||||||
self.func = function (x, y, t)
|
self.func = function (x, y, t)
|
||||||
t = t or 0
|
t = t or 0
|
||||||
|
@ -138,6 +164,8 @@ function Animation:getSheetDimensions()
|
||||||
return self.imagesheet.image:getDimensions()
|
return self.imagesheet.image:getDimensions()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
setmetatable(Animation, {__call = function(_, ...) return Animation.new(...) 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(time_list) == 'table' or type(time_list) == 'number')
|
||||||
assert(type(nr_frames) == '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
|
if type(time_list) == 'number' then
|
||||||
for i = 1, nr_frames do
|
for i = 1, nr_frames do
|
||||||
|
@ -168,27 +196,33 @@ local function calculate_frame_times (time_list, nr_frames)
|
||||||
return frame_times
|
return frame_times
|
||||||
end
|
end
|
||||||
|
|
||||||
local function load_quads (image, quad_data, imagesheet)
|
local function load_quads (_, _, quad_data, imagesheet)
|
||||||
assert(image.typeOf and image:typeOf('Image'))
|
assert(type(imagesheet) == 'table')
|
||||||
assert(type(quad_data) == '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 = imagesheet.tile_width, imagesheet.tile_height
|
||||||
local tile_width, tile_height = quad_data.tile_width, quad_data.tile_height
|
local tiles_per_row = imagesheet.tiles_per_row
|
||||||
local tiles_per_row = image_width/tile_width
|
local max_quad_id = tiles_per_row * imagesheet.tiles_per_column - 1
|
||||||
local max_quad_id = tiles_per_row * (image_height/tile_height) - 1
|
|
||||||
local quad_cache = {}
|
local quad_cache = {}
|
||||||
|
|
||||||
local function quad_from_id (id)
|
local function quad_from_id (id)
|
||||||
-- Error checking
|
-- 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 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
|
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
|
-- Calculate
|
||||||
if not quad_cache[id] then
|
local quad = quad_cache[id]
|
||||||
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)
|
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
|
end
|
||||||
return quad_cache[id]
|
return quad
|
||||||
end
|
end
|
||||||
|
|
||||||
local function visit_animation (t)
|
local function visit_animation (t)
|
||||||
|
@ -203,7 +237,7 @@ local function load_quads (image, quad_data, imagesheet)
|
||||||
return Sprite(quad_from_id(n), imagesheet)
|
return Sprite(quad_from_id(n), imagesheet)
|
||||||
end
|
end
|
||||||
|
|
||||||
local function visit_node (t)
|
local function visit_node (t, already_seen)
|
||||||
assert(type(t) == 'table')
|
assert(type(t) == 'table')
|
||||||
|
|
||||||
for key, val in pairs(t) do
|
for key, val in pairs(t) do
|
||||||
|
@ -211,12 +245,14 @@ local function load_quads (image, quad_data, imagesheet)
|
||||||
if val_type == 'number' then
|
if val_type == 'number' then
|
||||||
t[key] = visit_quad(val)
|
t[key] = visit_quad(val)
|
||||||
elseif val_type == 'table' then
|
elseif val_type == 'table' then
|
||||||
local visit_func = val.is_animation and visit_animation or visit_node
|
local visit_func = rawget(val,'is_animation') and visit_animation or visit_node
|
||||||
visit_func(val)
|
visit_func(val, already_seen)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
error.strict_table(t)
|
if type(error) == 'table' and define_love then
|
||||||
|
error.strict_table(t)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
assert(type(quad_data) == 'table')
|
assert(type(quad_data) == 'table')
|
||||||
|
@ -232,7 +268,14 @@ local function load_quad_data (filename)
|
||||||
|
|
||||||
-- Attempt to load file
|
-- Attempt to load file
|
||||||
for filetype in pairs(SPRITESHEET_DATA_FILETYPES) do
|
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
|
if chunk then
|
||||||
local data = setfenv(chunk, SPRITESHEET_ENV)()
|
local data = setfenv(chunk, SPRITESHEET_ENV)()
|
||||||
|
|
||||||
|
@ -292,25 +335,36 @@ function SpriteSheet.new (filename)
|
||||||
-- spriteimage itself if you can, as it will silently ignore
|
-- spriteimage itself if you can, as it will silently ignore
|
||||||
-- several errors.
|
-- 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
|
-- Set info
|
||||||
local self = setmetatable({}, SpriteSheet)
|
local self = setmetatable({}, SpriteSheet)
|
||||||
self.filename = filename
|
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_x = 0
|
||||||
self.origin_y = 0
|
self.origin_y = 0
|
||||||
|
|
||||||
-- TODO: Give warning/error due to rounding down.
|
-- 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)
|
self.tiles_per_row = quad_data.tiles_per_row or math.floor(width / quad_data.tile_width)
|
||||||
quad_data.tile_height = quad_data.tile_height or math.floor(self.image:getHeight() / quad_data.tiles_per_column)
|
self.tiles_per_column = quad_data.tiles_per_column or math.floor(height / quad_data.tile_height)
|
||||||
print(self.tile_width, self.tile_height)
|
|
||||||
|
|
||||||
self.tile_width = quad_data.tile_width
|
self.tile_width = quad_data.tile_width or math.floor(width / self.tiles_per_row)
|
||||||
self.tile_height = quad_data.tile_height
|
self.tile_height = quad_data.tile_height or math.floor(height / self.tiles_per_column)
|
||||||
|
|
||||||
-- Error checking
|
-- Error checking
|
||||||
do
|
do
|
||||||
local rem_width = self.image:getWidth() % self.tile_width
|
local rem_width = width % self.tile_width
|
||||||
local rem_height = self.image:getHeight() % self.tile_height
|
local rem_height = height % self.tile_height
|
||||||
if not quad_data.force_uneven_tile_size and (rem_width ~= 0 or rem_height ~= 0) then
|
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)')
|
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)
|
:format(filename, self.image:getWidth(), self.image:getHeight(), self.tile_width, self.tile_height)
|
||||||
|
@ -326,7 +380,7 @@ function SpriteSheet.new (filename)
|
||||||
end
|
end
|
||||||
|
|
||||||
-- Import quads into SpriteSheet
|
-- 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
|
if rawget(self.quads, 'is_sprite') or rawget(self.quads, 'is_animation') then
|
||||||
self.only_quads = self.quads
|
self.only_quads = self.quads
|
||||||
else
|
else
|
||||||
|
@ -344,15 +398,19 @@ function SpriteSheet:setOrigin (ox, oy, mode)
|
||||||
assert(type(ox) == 'number')
|
assert(type(ox) == 'number')
|
||||||
assert(type(oy) == 'number')
|
assert(type(oy) == 'number')
|
||||||
|
|
||||||
|
self.origin_orig = { ox, oy, mode }
|
||||||
|
|
||||||
if mode == 'absolute' then
|
if mode == 'absolute' then
|
||||||
self.origin_x = ox
|
self.origin_x = ox
|
||||||
self.origin_y = oy
|
self.origin_y = oy
|
||||||
elseif mode == 'relative' or mode == nil then
|
elseif mode == 'relative' or mode == nil then
|
||||||
self.origin_x = self.tile_width * ox
|
self.origin_x = math.floor(self.tile_width * ox)
|
||||||
self.origin_y = self.tile_height * oy
|
self.origin_y = math.floor(self.tile_height * oy)
|
||||||
else
|
else
|
||||||
error('Unknown origin mode %s (%s)', mode, type(mode))
|
error('Unknown origin mode %s (%s)', mode, type(mode))
|
||||||
end
|
end
|
||||||
|
assert(self.origin_x % 1 == 0)
|
||||||
|
assert(self.origin_y % 1 == 0)
|
||||||
end
|
end
|
||||||
|
|
||||||
function SpriteSheet:getQuadKeys ()
|
function SpriteSheet:getQuadKeys ()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user