2
-- This helps you re-use assets in your app instead of creating extraneous
3
-- copies of them. It also hides Love-related calls so that your code is
6
-- If you're using a class built into Zoetrope, you do not need to use
7
-- this class directly. They take care of setting things up for you
8
-- appropriately. However, if you're rolling your own, you'll want to use
9
-- this to save memory.
11
-- This class is not meant to be created directly. Instead, call
12
-- methods on Cached directly, e.g. Cached:sound(), Cached:image(), and so on.
19
-- Property: defaultGlyphs
20
-- The default character order of a bitmap font, if none is specified
22
defaultGlyphs = ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`' ..
23
'abcdefghijklmnopqrstuvwxyz{|}~',
25
-- private property: library
26
-- a table to store already-instantiated assets
27
_library = { image = {}, text = {}, sound = {}, font = {}, binds = {}, },
30
-- Returns a cached image asset.
33
-- path - pathname to image file
38
image = function (self, path)
40
assert(type(path) == 'string', 'path must be a string')
43
local realPath = self:_absolutePath(path)
45
if not self._library.image[realPath] then
46
self._library.image[realPath] = love.graphics.newImage(realPath)
49
return self._library.image[realPath]
53
-- Returns a cached text asset.
56
-- path - pathname to text file
61
text = function (self, path)
63
assert(type(path) == 'string', 'path must be a string')
66
local realPath = self:_absolutePath(path)
68
if not self._library.text[realPath] then
69
self._library.text[realPath] = love.filesystem.read(realPath)
72
return self._library.text[realPath]
76
-- Returns a cached sound asset.
79
-- path - pathname to sound file
80
-- length - either 'short' or 'long'. *It's very important to pass
81
-- the correct option here.* A short sound is loaded entirely
82
-- into memory, while a long one is streamed from disk. If you
83
-- mismatch, you'll either hear a delay in the sound (short sounds
84
-- played from disk) or your app will freeze (long sounds played from
88
-- Either a Love SoundData object (for short sounds) or a
89
-- Love Decoder object (for long sounds). Either can be used to
90
-- create a Love Source object.
93
-- <playSound>, <sound>
95
sound = function (self, path, length)
97
assert(type(path) == 'string', 'path must be a string')
100
local realPath = self:_absolutePath(path)
102
if not self._library.sound[realPath] then
103
if length == 'short' then
104
self._library.sound[realPath] = love.sound.newSoundData(realPath)
105
elseif length == 'long' then
106
self._library.sound[realPath] = love.sound.newDecoder(realPath)
108
error('length must be either "short" or "long"')
112
return self._library.sound[path]
116
-- Returns a cached font asset.
120
-- * A single number. This uses Love's default outline font at that point size.
121
-- * A single string. This uses a bitmap font given by this pathname, and assumes that
122
-- the characters come in
123
-- <printable ASCII order at https://en.wikipedia.org/wiki/ASCII#ASCII_printable_characters>.
124
-- * A string, then a number. This uses an outline font whose pathname is the first argument,
125
-- at the point size given in the second argument.
126
-- * Two strings. The first is treated as a pathname to a bitmap font, the second
127
-- as the character order in the font.
132
font = function (self, ...)
134
local libKey = arg[1]
136
if type(libKey) == 'string' then
137
libKey = self:_absolutePath(libKey)
140
if #arg > 1 then libKey = libKey .. arg[2] end
142
if not self._library.font[libKey] then
146
if type(arg[1]) == 'number' then
147
font = love.graphics.newFont(arg[1])
148
elseif type(arg[1]) == 'string' then
149
image = Cached:image(arg[1])
150
font = love.graphics.newImageFont(image, self.defaultGlyphs)
152
error("don't understand single argument: " .. arg[1])
154
elseif #arg == 2 then
155
if type(arg[2]) == 'number' then
156
font = love.graphics.newFont(arg[1], arg[2])
157
elseif type(arg[2]) == 'string' then
158
image = Cached:image(arg[1])
159
font = love.graphics.newImageFont(image, arg[2])
161
error("don't understand arguments: " .. arg[1] .. ", " .. arg[2])
164
error("too many arguments; should be at most two")
167
self._library.font[libKey] = font
170
return self._library.font[libKey]
174
-- Returns a function that's bound to an object so it can be later called with
175
-- the correct context. This can be abbreviated as just bind().
178
-- obj - object to use as function owner
179
-- func - either a string name of a property of obj, or a free-standing
181
-- ... - any number of extra arguments
183
bind = function (self, obj, func, ...)
186
if STRICT and type(func) == 'string' then
187
assert(type(obj[func]) == 'function', 'asked to bind an object to a non-existent method named ' .. func)
190
-- look for previous bind
192
for key, value in pairs(self._library.binds) do
193
if key[1] == func and key[2] == obj then
197
if key[i + 2] ~= arg[i] then
209
-- have to create a new one
210
-- note that we have to create a compound key, hence the loop above
212
local result = function()
213
if type(func) == 'string' then
214
return obj[func](obj, unpack(arg))
216
return func(obj, unpack(arg))
220
self._library.binds[{func, obj, arg}] = result
224
-- internal function: _absolutePath
225
-- Replaces any .. references in a path, where possible.
228
-- rawPath - string path to expand
233
_absolutePath = function (self, rawPath)
235
local result = rawPath
238
result, matches = string.gsub(result, '[^/]+/%.%./', '')