├── .gitignore ├── README.md ├── animator.lua ├── examples ├── animator.lua ├── conf.lua ├── images │ ├── 1.png │ ├── 2 and 4.png │ ├── 3.png │ └── Quad.png └── main.lua └── images └── Grid.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | 38 | # Directories potentially created on remote AFP share 39 | .AppleDB 40 | .AppleDesktop 41 | Network Trash Folder 42 | Temporary Items 43 | .apdisk 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Walt 2 | ==== 3 | 4 | An animation library for LÖVE. 5 | 6 | # Table of Contents 7 | 8 | - [Usage](#usage) 9 | - [Name](#name) 10 | - [Functions](#functions) 11 | - [animator.merge](#animatormerge) 12 | - [animator.newAnimation](#animatornewanimation) 13 | - [anim:draw](#animdraw) 14 | - [anim:getActive](#animgetactive) 15 | - [anim:getCurrentFrame](#animgetcurrentframe) 16 | - [anim:getDimensions](#animgetdimensions) 17 | - [anim:getHeight](#animgetheight) 18 | - [anim:getLooping](#animgetlooping) 19 | - [anim:getOnAnimationChange](#animgetonanimationchange) 20 | - [anim:getOnAnimationEnd](#animgetonanimationend) 21 | - [anim:getOnLoop](#animgetonloop) 22 | - [anim:getPauseAtEnd](#animgetpauseatend) 23 | - [anim:getPaused](#animgetpaused) 24 | - [anim:getWidth](#animgetwidth) 25 | - [anim:pause](#animpause) 26 | - [anim:pauseAtEnd](#animpauseatend) 27 | - [anim:restart](#animrestart) 28 | - [anim:resume](#animresume) 29 | - [anim:setActive](#animsetactive) 30 | - [anim:setCurrentFrame](#animsetcurrentframe) 31 | - [anim:setLooping](#animsetlooping) 32 | - [anim:setOnAnimationChange](#animsetonanimationchange) 33 | - [anim:setOnAnimationEnd](#animsetonanimationend) 34 | - [anim:setOnLoop](#animsetonloop) 35 | - [anim:setPauseAtEnd](#animsetpauseatend) 36 | - [anim:setPaused](#animsetpaused) 37 | - [anim:togglePause](#animtogglepause) 38 | - [anim:toggleActive](#animtoggleactive) 39 | - [anim:toggleLooping](#animtogglelooping) 40 | - [anim:togglePauseAtEnd](#animtogglepauseatend) 41 | - [anim:update](#animupdate) 42 | - [Aliases](#aliases) 43 | - [animator.newGrid](#animatornewgrid) 44 | - [grid:getFrames](#gridgetframes) 45 | - [Examples](#examples) 46 | 47 | 48 | ## Usage 49 | 50 | ```lua 51 | local animator = require 'path.to.walt' 52 | 53 | function love.load() 54 | local image = love.graphics.newImage( 'Path/to/image.png' ) 55 | anim = animator.newAnimation( { image }, 1 ) 56 | end 57 | 58 | function love.update( dt ) 59 | anim:update( dt ) 60 | end 61 | 62 | function love.draw() 63 | anim:draw() 64 | end 65 | ``` 66 | And that's it! 67 | 68 | 69 | ## Name 70 | 71 | - Walt is named for famous animator Walt Disney. 72 | 73 | 74 | ## Functions 75 | 76 | ### animator.merge 77 | 78 | - Combine several tables into one compact table. 79 | - Synopsis: 80 | - `frames = animator.merge( ... )` 81 | - Arguments: 82 | - `...`: Tables. A list of images and quads in the order of the animation. 83 | - Returns: 84 | - `frames`: Table. A flattened list of the quads and images. 85 | - Notes: 86 | - Works well in combination with [`animator.newAnimation`](#animatornewanimation). 87 | 88 | ### animator.newAnimation 89 | 90 | - Creates a new animation object. 91 | - Synopsis: 92 | - `anim = animator.newAnimation( frames, duration, [quadImage] )` 93 | - `anim = animator.newAnimation( frames, durations, [quadImage] )` 94 | - Arguments: 95 | - `frames`: Table. A flat table of images and quads in the order that they will be played. 96 | - `duration`: Number. The amount of time each animation will be played. 97 | - `durations`: Table. There are several formats to this table. Each must have the same number of entries as `frames`. You can combine these in any way. See below table for more. 98 | - `quadImage`: Image. The image that the quads for the animation will be using. Not needed if you aren't using any quads. 99 | 100 | | Format | Example | Description | 101 | | ----------|-----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| 102 | | __Flat__ | `{ .1, .2, .1, .5 }` | A numbered list, representing the corresponding frame. In this case, makes frame 1 have a duration of .1, 2 a duration of .2, etc. | 103 | | __List__ | `{ ['1 - 5'] = 1 }` | A table key, listing the frame numbers, in the style of `lower - larger`. Can have 0-unlimited spaces between the `-`. In this case, frames 1 through 5 each have a duration of 1 second | 104 | | __Key__ | `{ ['1, 3, 5'] = 1 }` | Frame numbers are seperated by `,`. Can have 0-unlimited spaces after the `,`. In this case, frames 1, 3, and 5 have a duration of 1 second. | 105 | 106 | - Returns: 107 | - `anim`: Table. Animation object that can be used. For more, see the `anim:` sub-functions. 108 | - Notes: 109 | - The quads should all be from the same image. 110 | - After all of the `Key`s and `List`s are inserted, the unassigned keys are done in the order of the `flat` entries. 111 | - `{ ['1 - 3'] = .5, ['6 - 10'] = 1, .2, .3, .4 }` = `{ .5, .5, .5, .2, .3, 1, 1, 1, 1, 1, .4 }` 112 | 113 | #### anim:draw 114 | 115 | - Draw the animation. 116 | - Synopsis: 117 | - `anim:draw()` 118 | - Arguments: 119 | - Returns: 120 | - Nothing. 121 | - Notes: 122 | - You have to call `[anim:update](#animupdate)` as well. 123 | 124 | #### anim:getActive 125 | 126 | - Returns if the animation is currently playing. 127 | - Synopsis: 128 | - `active = anim:getActive()` 129 | - Arguments: 130 | - `anim`: Table. An animation object returned from [`animator.newAnimation`](#animatornewanimation). 131 | - Returns: 132 | - `active`: Boolean. Whether the animation is playing or not. 133 | 134 | #### anim:getCurrentFrame 135 | 136 | - Gets the current frame number. 137 | - Synopsis: 138 | - `frame = anim:getCurrentFrame()` 139 | - Arguments: 140 | - `anim`: Table. An animation object returned from [`animator.newAnimation`](#animatornewanimation). 141 | - Returns: 142 | - `frame`: Number. The number frame that the animation is on. 143 | 144 | #### anim:getDimensions 145 | 146 | - Gets the width and height of the current frame of the animation. 147 | - Synopsis: 148 | - `width, height = anim:getDimensions()` 149 | - Arguments: 150 | - `anim`: Table. An animation object returned from [`animator.newAnimation`](#animatornewanimation). 151 | - Returns: 152 | - `width`: Number. The width of the current frame. 153 | - `height`: Number. THe height of the current frame. 154 | 155 | #### anim:getHeight 156 | 157 | - Gets the height of the current frame of the animation. 158 | - Synopsis: 159 | - `height = anim:getHeight()` 160 | - Arguments: 161 | - `anim`: Table. An animation object returned from [`animator.newAnimation`](#animatornewanimation). 162 | - Returns: 163 | - `height`: Number. The height of the current frame. 164 | 165 | #### anim:getLooping 166 | 167 | - Gets whether the animation is a looping animation or not. 168 | - Synopsis: 169 | - `isLooping = anim:getLooping()` 170 | - Arguments: 171 | - `anim`: Table. An animation object returned from [`animator.newAnimation`](#animatornewanimation). 172 | - Returns: 173 | - `isLooping`: Boolean. Whether the animation is looping or not. 174 | 175 | #### anim:getOnAnimationChange 176 | 177 | - Gets the function called on frame-change. 178 | - Synopsis: 179 | - `func = anim:getOnAnimationChange()` 180 | - Arguments: 181 | - `anim`: Table. An animation object returned from [`animator.newAnimation`](#animatornewanimation). 182 | - Returns: 183 | - `func`: Function. The function called on frame-change. 184 | 185 | #### anim:getOnAnimationEnd 186 | 187 | - Gets the function called on animation end. 188 | - Synopsis: 189 | - `func = anim:getOnAnimationEnd()` 190 | - Arguments: 191 | - `anim`: Table. An animation object returned from [`animator.newAnimation`](#animatornewanimation). 192 | - Returns: 193 | - `func`: Function. The function called on animation end. 194 | 195 | 196 | #### anim:getOnLoop 197 | 198 | - Gets the function called on-loop. 199 | - Synopsis: 200 | - `func = anim:getOnLoop()` 201 | - Arguments: 202 | - `anim`: Table. An animation object returned from [`animator.newAnimation`](#animatornewanimation). 203 | - Returns: 204 | - `func`: Function. The function called on-loop. 205 | 206 | #### anim:getPauseAtEnd 207 | 208 | - Gets whether the animation should pause on the last frame. 209 | - Synopsis: 210 | - `shouldPause = anim:getPauseAtEnd()` 211 | - Arguments: 212 | - `anim`: Table. An animation object returned from [`animator.newAnimation`](#animatornewanimation). 213 | - Returns: 214 | - `shouldPause`: Boolean. Whether the animation will pause at the end or not. 215 | 216 | #### anim:getPaused 217 | 218 | - Gets whether the animation is paused or not. 219 | - Synopsis: 220 | - `isPaused = anim:getPaused()` 221 | - Arguments: 222 | - `anim`: Table. An animation object returned from [`animator.newAnimation`](#animatornewanimation). 223 | - Returns: 224 | - `isPaused`: Boolean. Whether the animation is paused or not. 225 | 226 | #### anim:getWidth 227 | 228 | - Gets the width of the current frame of the animation. 229 | - Synopsis: 230 | - `width = anim:getWidth()` 231 | - Arguments: 232 | - `anim`: Table. An animation object returned from [`animator.newAnimation`](#animatornewanimation). 233 | - Returns: 234 | - `width`: Number. The width of the frame. 235 | 236 | #### anim:pause 237 | 238 | - Pauses the animation. 239 | - Synopsis: 240 | - `anim:pause()` 241 | - Arguments: 242 | - `anim`: Table. An animation object returned from [`animator.newAnimation`](#animatornewanimation). 243 | - Returns: 244 | - Nothing. 245 | 246 | #### anim:pauseAtEnd 247 | 248 | - Sets the function to pause after the last frame has played. 249 | - Synopsis: 250 | - `anim:pauseAtEnd()` 251 | - Arguments: 252 | - `anim`: Table. An animation object returned from [`animator.newAnimation`](#animatornewanimation). 253 | - Returns: 254 | - Nothing. 255 | 256 | #### anim:restart 257 | 258 | - Resets the animation to the first frame. 259 | - Synopsis: 260 | - `anim:restart()` 261 | - Arguments: 262 | - `anim`: Table. An animation object returned from [`animator.newAnimation`](#animatornewanimation). 263 | - Returns: 264 | - Nothing. 265 | 266 | #### anim:resume 267 | 268 | - Resumes the animation from a pause. 269 | - Synopsis: 270 | - `anim:resume()` 271 | - Arguments: 272 | - `anim`: Table. An animation object returned from [`animator.newAnimation`](#animatornewanimation). 273 | - Returns: 274 | - Nothing. 275 | 276 | #### anim:setActive 277 | 278 | - Stops or resumes the animation. 279 | - Synopsis: 280 | - `anim:setActive( active )` 281 | - Arguments: 282 | - `anim`: Table. An animation object returned from [`animator.newAnimation`](#animatornewanimation). 283 | - `active`: Boolean. Whether the animation should be playing (`true`) or not (`false`). 284 | - Returns: 285 | - Nothing. 286 | 287 | #### anim:setCurrentFrame 288 | 289 | - Sets the current frame of the animation. 290 | - Synopsis: 291 | - `anim:setCurrentFrame( frame )` 292 | - Arguments: 293 | - `anim`: Table. An animation object returned from [`animator.newAnimation`](#animatornewanimation). 294 | - `frame`: Number. The frame to be set. 295 | - Returns: 296 | - Nothing. 297 | 298 | #### anim:setLooping 299 | 300 | - Sets whether the animation should loop at the end or not. 301 | - Synopsis: 302 | - `anim:setLooping( loop )` 303 | - Arguments: 304 | - `anim`: Table. An animation object returned from [`animator.newAnimation`](#animatornewanimation). 305 | - `loop`: Boolean. Whether the animation should loop at the end or not. 306 | - Returns: 307 | - Nothing. 308 | 309 | #### anim:setOnAnimationChange 310 | 311 | - Sets the function executed on animation change. 312 | - Synopsis: 313 | - `anim:setOnAnimationChange( func )` 314 | - Arguments: 315 | - `anim`: Table. An animation object returned from [`animator.newAnimation`](#animatornewanimation). 316 | - `func`: Function. The function to be called on each animation increment. 317 | - Returns: 318 | - Nothing 319 | 320 | #### anim:setOnAnimationEnd 321 | 322 | - Sets the function executed on animation end. 323 | - Synopsis: 324 | - `anim:setOnAnimationEnd( func )` 325 | - Arguments: 326 | - `anim`: Table. An animation object returned from [`animator.newAnimation`](#animatornewanimation). 327 | - `func`: Function. The function to be called on each animation end. 328 | - Returns: 329 | - Nothing 330 | 331 | #### anim:setOnLoop 332 | 333 | - Sets the function executed on loop. 334 | - Synopsis: 335 | - `anim:setOnLoop( func )` 336 | - Arguments: 337 | - `anim`: Table. An animation object returned from [`animator.newAnimation`](#animatornewanimation). 338 | - `func`: Function. The function to be called on each loop. 339 | - Returns: 340 | - Nothing. 341 | 342 | #### anim:setPauseAtEnd 343 | 344 | - Sets the animation to pause after displaying the final frame. 345 | - Synopsis: 346 | - `anim:setPauseAtEnd( shouldPause )` 347 | - Arguments: 348 | - `anim`: Table. An animation object returned from [`animator.newAnimation`](#animatornewanimation). 349 | - `shouldPause`: Boolean. Whether or not the function should pause after displaying the final frame. 350 | - Returns: 351 | - Nothing. 352 | 353 | #### anim:setPaused 354 | 355 | - Sets if the animation is paused or not. 356 | - Synopsis: 357 | - `anim:setPaused( isPaused )` 358 | - Arguments: 359 | - `anim`: Table. An animation object returned from [`animator.newAnimation`](#animatornewanimation). 360 | - `isPaused`: Boolean. Whether the animation should be paused or not. 361 | - Returns: 362 | - Nothing. 363 | 364 | #### anim:togglePause 365 | 366 | - Toggles the pause of the animation. 367 | - Synopsis: 368 | - `anim:togglePause()` 369 | - Arguments: 370 | - `anim`: Table. An animation object returned from [`animator.newAnimation`](#animatornewanimation). 371 | - Returns: 372 | - Nothing. 373 | 374 | #### anim:toggleActive 375 | 376 | - Toggles whether the animation is active or not. 377 | - Synopsis: 378 | - `anim:toggleActive()` 379 | - Arguments: 380 | - `anim`: Table. An animation object returned from [`animator.newAnimation`](#animatornewanimation). 381 | - Returns: 382 | - Nothing. 383 | 384 | #### anim:toggleLooping 385 | 386 | - Toggles whether the animation is looping or not. 387 | - Synopsis: 388 | - `anim:toggleLooping()` 389 | - Arguments: 390 | - `anim`: Table. An animation object returned from [`animator.newAnimation`](#animatornewanimation). 391 | - Returns: 392 | - Nothing. 393 | 394 | #### anim:togglePauseAtEnd 395 | 396 | - Toggles whether the animation pauses after playing the last frame. 397 | - Synopsis: 398 | - `anim:togglePauseAtEnd()` 399 | - Arguments: 400 | - `anim`: Table. An animation object returned from [`animator.newAnimation`](#animatornewanimation). 401 | - Returns: 402 | - Nothing. 403 | 404 | #### anim:update 405 | 406 | - Update the animation. 407 | - Synopsis: 408 | - `anim:update( dt )` 409 | - Arguments: 410 | - `anim`: Table. An animation object returned from [`animator.newAnimation`](#animatornewanimation). 411 | - `dt`: Number. The time between each update-frame. 412 | - Returns: 413 | - Nothing. 414 | 415 | ### animator.newGrid 416 | 417 | - Creates a new grid to make animation easier. 418 | - Synopsis: 419 | - `grid = animator.newGrid( frameWidth, frameHeight, image, [startX, startY, stopX, stopY] )` 420 | - Arguments: 421 | - `frameWidth`: Number. The width each quad will be. 422 | - `frameHeight`: Number. The height each frame will be. 423 | - `image`: Image. The image that will be used. 424 | - `startX`: Number. The x position to start creating the grid. Defaults as 0. 425 | - `startY`: Number. The y position to start creating the grid. Defaults as 0. 426 | - `stopX`: Number. The x position to stop creating the grid. Defauls as the image's width. 427 | - `stopY`: Number. The y position to stop creating the grid. Defaults as the image's height. 428 | - Returns: 429 | - `grid`: Table. A table of quads. 430 | 431 | #### grid:getFrames 432 | 433 | - Returns the quads in the specified order you create it. 434 | - Synopsis: 435 | - `frames = grid:getFrames()` 436 | - Arguments: 437 | - `grid`: Table. A grid object retured by [`animator:newGrid`](#animatornewgrid). See the picture below to get a better idea of how they work. 438 | - Returns: 439 | - `frames`: Table. A table containing the quads created from the grid. 440 | 441 | - Each pair of numbers is a pair of x and y coordinates, referring to the grid. 442 | - Use plain numbers to refer to single simple x and y coordinates. 443 | - Use a string with numbers seperated by a hyphen (`-`) to represent _"through"_. 444 | - In a pairs, both arguments can't be strings. 445 | - So you would make a grid like this (assuming the image is called `'grid.png'`): 446 | 447 | ![Grid](images/Grid.png) 448 | 449 | ```lua 450 | local image = love.graphics.newImage( 'grid.png' ) 451 | local grid = animator.newGrid( 32, 32, image, 32, 32, 288, 224 ) 452 | local anim = animator.newAnimation( grid.getFrames( 1,1 2,1 3,1 '4 - 7',1, 8, '1 - 6' ), 1, image ) 453 | -- You have to remember to pass the image that the quads are from. 454 | ``` 455 | 456 | ### Alisases 457 | 458 | - There functions are also available, and work just like their conterpart. 459 | 460 | | Alias | Corresponding Function | 461 | | --------------------------|:---------------------------------------------------------:| 462 | | anim:isActive | [anim:getActive](#animgetactive) | 463 | | anim:isLooping | [anim:getLooping](#animgetlooping) | 464 | | anim:isPaused | [anim:getPaused](#animgetpaused) | 465 | | anim:getFrame | [anim:getGetCurrentFrame](#animgetcurrentframe) | 466 | | anim:gotoFrame | [anim:setCurrentFrame](#animsetcurrentframe) | 467 | | anim:setAnimationChange | [anim:setOnAnimationChange](#animsetonanimationchange) | 468 | | anim:setAnimationEnd | [anim:setOnAnimationEnd](#animsetonanimationend) | 469 | | anim:setFrame | [anim:setCurrentFrame](#animsetcurrentframe) | 470 | | grid:__call | [grid:getFrames](#gridgetframes) | 471 | 472 | 473 | ## Examples 474 | 475 | See [Examples](Examples/). 476 | -------------------------------------------------------------------------------- /animator.lua: -------------------------------------------------------------------------------- 1 | -- https://github.com/davisdude/Walt 2 | --[[ 3 | Copyright (c) 2018 Davis Claiborne 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | ]] 24 | --[[ 25 | TODO: 26 | 27 | - Padding for grids 28 | 29 | - Different sized images 30 | --]] 31 | 32 | local unpack = table.unpack or unpack 33 | 34 | local function err( errCode, passed, ... ) 35 | local types = { ... } 36 | local typeOfPassed = type( passed ) 37 | if type( types[1] ) == 'function' then 38 | assert( types[1]( passed ), errCode:gsub( '%%type%%', typeOfPassed ) ) 39 | return true 40 | end 41 | local passed = false 42 | for i = 1, #types do 43 | if types[i] == typeOfPassed then 44 | passed = true 45 | break 46 | end 47 | end 48 | errCode = errCode:gsub( '%%type%%', typeOfPassed ) 49 | assert( passed, 'Animation error: ' .. errCode ) 50 | end 51 | 52 | local function getFrameType( self, frame ) 53 | frame = frame or self.currentFrame 54 | return self.frames[frame]:type() 55 | end 56 | 57 | local function isPositive( x ) return x > 0 end 58 | local function isInteger( x ) return x % 1 == 0 end 59 | local function sign( x, y ) 60 | return math.abs( y - x ) / ( y - x ) 61 | end 62 | 63 | local function validateCoordinates( self, name, index, arg, Y, X, which ) 64 | local numbers = {} 65 | tostring( arg ):gsub( '%d+', function( x ) table.insert( numbers, tonumber( x ) ) end ) 66 | err( 'getFrames: ' .. name .. ' argument ' .. index .. ': too many numbers passed.', nil, 67 | function() 68 | return #numbers <= 2 69 | end 70 | ) 71 | 72 | local start, stop = unpack( numbers ) 73 | if stop then -- 2 numbers passed 74 | which = name 75 | for i = start, stop, sign( start, stop ) do 76 | if name == 'y' then 77 | if not self.reference[i] then return false end 78 | elseif name == 'x' then 79 | if not self.reference[Y][i] then return false end 80 | end 81 | end 82 | else 83 | local i = tonumber( start ) 84 | if name == 'y' then 85 | if not self.reference[i] then return false end 86 | elseif name == 'x' then 87 | if which then -- which is set to y 88 | local y = tonumber( Y:match( '%d+' ) ) 89 | return self.reference[y][i], which, start, stop 90 | else 91 | local y = tonumber( Y ) 92 | return self.reference[y][i], which, start, stop 93 | end 94 | end 95 | end 96 | 97 | return true, which, start, stop 98 | end 99 | 100 | local function decodeDelays( delays, frames ) 101 | if type( delays ) == 'number' then 102 | local delay = delays 103 | local delays = {} 104 | for i = 1, frames do 105 | delays[i] = delay 106 | end 107 | return delays 108 | else 109 | local ret = {} 110 | local unassigned = {} 111 | for _, v in ipairs( delays ) do 112 | table.insert( unassigned, v ) 113 | end 114 | for i, v in pairs( delays ) do 115 | if type( i ) == 'string' then 116 | local groups = {} 117 | i:gsub( '(%d+)%s*%-?%s*(%d*)', function( x, y ) table.insert( groups, { x, y } ) end ) 118 | for _, group in ipairs( groups ) do 119 | if #group[2] > 0 then -- Second capture isn't blank 120 | local start, stop = unpack( group ) 121 | err( 'newAnimation: delays with ranges should be arranged from smallest to largest, i.e. [1-3], not [3-1].', nil, function() return start < stop end ) 122 | for frame = start, stop do 123 | err( 'newAnimation: there should only be one value for each delay!', nil, function() return not ret[frame] end ) 124 | frame = tonumber( frame ) 125 | ret[frame] = v 126 | end 127 | else 128 | local frame = group[1] 129 | err( 'newAnimation: there should only be one value for each delay!', nil, function() return not ret[frame] end ) 130 | frame = tonumber( frame ) 131 | ret[frame] = v 132 | end 133 | end 134 | end 135 | end 136 | for i = 1, #unassigned do 137 | local index = 0 138 | for ii = 1, #ret do 139 | if ret[ii] == nil then 140 | index = ii 141 | break 142 | end 143 | end 144 | if ( index == 0 ) or ( index == #ret and #ret > 0 ) then index = #ret + 1 end 145 | ret[index] = unassigned[i] 146 | end 147 | return ret 148 | end 149 | end 150 | 151 | return { 152 | merge = function( ... ) 153 | local tables = { ... } 154 | local final = {} 155 | for i = 1, #tables do 156 | local tab = tables[i] 157 | if type( tab ) == 'table' then 158 | for _, frame in ipairs( tab ) do 159 | table.insert( final, frame ) 160 | end 161 | else 162 | table.insert( final, tab ) 163 | end 164 | end 165 | return final 166 | end, 167 | newGrid = function( frameWidth, frameHeight, image, startX, startY, stopX, stopY ) 168 | local imageWidth, imageHeight = image:getDimensions() 169 | startX, startY = startX or 0, startY or 0 170 | stopX, stopY = stopX or imageWidth, stopY or imageHeight 171 | 172 | local frames = { 173 | reference = {}, 174 | getFrames = function( self, ... ) 175 | local args = { ... } 176 | local returns = {} 177 | 178 | err( 'getFrames: expected an even amount of arguments greater than one after the grid.', args, 179 | function( args ) 180 | return #args % 2 == 0 and #args ~= 0 181 | end 182 | ) 183 | for i = 1, #args, 2 do 184 | local arg1, arg2 = args[i], args[i + 1] 185 | err( 'getFrames: expected x argument ' .. i .. ' after grid to be a number or a string, got a %type%.', arg1, 'number', 'string' ) 186 | err( 'getFrames: expected y argument ' .. i .. ' after grid to be a number or a string, got a %type%.', arg2, 'number', 'string' ) 187 | err( 'getFrames: argument ' .. i .. ': both x and y can\'t be strings.', nil, 188 | function() 189 | local arg1, arg2 = type( arg1 ), type( arg2 ) 190 | return ( arg1 == arg2 and arg1 == 'number' ) or ( arg1 ~= arg2 ) 191 | end 192 | ) 193 | 194 | local which, start, stop 195 | err( 'getFrames: expected y argument ' .. i + 1 .. ' to be valid coordinate(s) of grid.', arg2, 196 | function( y ) 197 | local passed 198 | passed, which, start, stop = validateCoordinates( self, 'y', i + 1, y, y, arg1 ) 199 | return passed 200 | end 201 | ) 202 | err( 'getFrames: expected x argument ' .. i .. ' to be valid coordinate(s) of grid.', arg1, 203 | function( x ) 204 | local results = { validateCoordinates( self, 'x', i, x, arg2, x, which ) } 205 | if not which then 206 | local _ 207 | _, which, start, stop = unpack( results ) 208 | end 209 | return results[1] 210 | end 211 | ) 212 | 213 | if which then 214 | if which == 'x' then 215 | local y = tonumber( arg2 ) 216 | for x = start, stop, sign( start, stop ) do 217 | table.insert( returns, self.reference[y][x] ) 218 | end 219 | else -- which == y 220 | local x = tonumber( arg1 ) 221 | for y = start, stop, sign( start, stop ) do 222 | table.insert( returns, self.reference[y][x] ) 223 | end 224 | end 225 | else 226 | table.insert( returns, self.reference[arg2][arg1] ) 227 | end 228 | end 229 | 230 | return returns 231 | end, 232 | } 233 | 234 | for y = startY, stopY - 1, frameHeight do 235 | local Y = ( y - startY ) / frameHeight + 1 236 | frames.reference[Y] = {} 237 | for x = startX, stopX - 1, frameWidth do 238 | local X = ( x - startX ) / frameWidth + 1 239 | local frame = love.graphics.newQuad( x, y, frameWidth, frameHeight, imageWidth, imageHeight ) 240 | frames.reference[Y][X] = frame 241 | end 242 | end 243 | 244 | return setmetatable( frames, { __call = frames.getFrames } ) 245 | end, 246 | 247 | newAnimation = function( frames, delays, quadImage ) 248 | err( 'newAnimation: expected argument 1 to be a table, got %type%.', frames, 'table' ) 249 | err( 'newAnimation: expected argument 2 to be a table or a number, got %type%.', delays, 'table', 'number' ) 250 | if quadImage then 251 | err( 'newAnimation: expected argument 3 to be an image, got %type%.', quadImage, 252 | function( quadImage ) 253 | if type( quadImage ) ~= 'userdata' then 254 | return false 255 | else 256 | if quadImage:type() == 'Image' then 257 | return true 258 | end 259 | return false 260 | end 261 | end 262 | ) 263 | end 264 | local needsQuads = false 265 | err( 'newAnimation: expected argument 1 to be a table of images and/or quads.', frames, 266 | function( frames ) 267 | for _, frame in ipairs( frames ) do 268 | local frameType = type( frame ) 269 | if frameType == 'userdata' then 270 | frameType = frame:type() 271 | if frameType ~= 'Image' and frameType ~= 'Quad' then 272 | return false 273 | elseif frameType == 'Quad' then 274 | needsQuads = true 275 | end 276 | else 277 | return false 278 | end 279 | end 280 | return true 281 | end 282 | ) 283 | if needsQuads then 284 | err( 'newAnimation: Need an image as the third argument for the reference quads!', needsQuads, 285 | function( needsQuads ) 286 | return not not quadImage 287 | end 288 | ) 289 | end 290 | 291 | delays = decodeDelays( delays, #frames ) 292 | err( 'newAnimation: argument 2 should have the same number of entries as argument 1.', delays, 293 | function( delays ) 294 | return #delays == #frames 295 | end 296 | ) 297 | err( 'newAnimation: expected argument 2 to be a table of numbers or a single number.', delays, 298 | function( delays ) 299 | for index, delay in ipairs( delays ) do 300 | if type( delay ) ~= 'number' then return false end 301 | end 302 | return true 303 | end 304 | ) 305 | 306 | local animation = { 307 | frames = frames, 308 | delays = delays, 309 | quadImage = quadImage, 310 | 311 | onAnimationChange = function() end, 312 | onAnimationEnd = function() end, 313 | onLoop = function() end, 314 | 315 | currentFrame = 1, 316 | delayTimer = 0, 317 | looping = false, 318 | active = true, 319 | paused = false, 320 | shouldPauseAtEnd = false, 321 | 322 | update = function( self, dt ) 323 | err( 'update: expected argument two to be a number, got %type%.', dt, 'number' ) 324 | err( 'update: expected argument two to be positive.', dt, isPositive ) 325 | 326 | if self.active and not self.paused then 327 | self.delayTimer = self.delayTimer + dt 328 | if self.delayTimer > self.delays[self.currentFrame] then 329 | self.delayTimer = 0 330 | self.currentFrame = self.currentFrame + 1 331 | self:onAnimationChange( self.currentFrame ) 332 | if self.currentFrame > #self.frames then 333 | if self.looping then 334 | self.currentFrame = 1 335 | self:onLoop() 336 | else 337 | if self.shouldPauseAtEnd then 338 | self.paused = true 339 | else 340 | self.active = false 341 | self:onAnimationEnd() 342 | end 343 | self.currentFrame = #self.frames 344 | end 345 | end 346 | end 347 | end 348 | end, 349 | draw = function( self, x, y, rotation, scaleX, scaleY, offsetX, offsetY, shearingX, shearingY ) 350 | if self.active then 351 | err( 'draw: expected argument two to be a number or nil, got %type%.', x, 'number', 'nil' ) 352 | err( 'draw: expected argument three to be a number or nil, got %type%.', y, 'number', 'nil' ) 353 | err( 'draw: expected argument four to be a number or nil, got %type%.', rotation, 'number', 'nil' ) 354 | err( 'draw: expected argument five to be a number or nil, got %type%.', scaleX, 'number', 'nil' ) 355 | err( 'draw: expected argument six to be a number or nil, got %type%.', scaleY, 'number', 'nil' ) 356 | err( 'draw: expected argument seven to be a number or nil, got %type%.', offsetX, 'number', 'nil' ) 357 | err( 'draw: expected argument eight to be a number or nil, got %type%.', offsetX, 'number', 'nil' ) 358 | err( 'draw: expected argument nine to be a number or nil, got %type%.', shearingX, 'number', 'nil' ) 359 | err( 'draw: expected argument ten to be a number or nil, got %type%.', shearingY, 'number', 'nil' ) 360 | 361 | local frame = self.frames[self.currentFrame] 362 | local frameType = frame:type() 363 | if frameType == 'Image' then 364 | love.graphics.draw( frame, x, y, rotation, scaleX, scaleY, offsetX, offsetY, shearingX, shearingY ) 365 | elseif frameType == 'Quad' then 366 | love.graphics.draw( self.quadImage, frame, x, y, rotation, scaleX, scaleY, offsetX, offsetY, shearingX, shearingY ) 367 | end 368 | end 369 | end, 370 | 371 | setOnLoop = function( self, func ) 372 | err( 'setOnLoop: expected argument two be a function, got %type%', func, 'function' ) 373 | self.onLoop = func 374 | end, 375 | getOnLoop = function( self ) return self.onLoop end, 376 | setOnAnimationChange = function( self, func ) 377 | err( 'setOnAnimationChange: expected argument two to be a function, got %type%.', func, 'function' ) 378 | self.onAnimationChange = func 379 | end, 380 | getOnAnimationChange = function( self ) return self.onAnimationChange end, 381 | setOnAnimationEnd = function( self, func ) 382 | err( 'setOnAnimationEnd: expected argument two to be a function, got %type%.', func, 'function' ) 383 | self.onAnimationEnd = func 384 | end, 385 | getOnAnimationEnd = function( self ) return self.onAnimationEnd end, 386 | setPaused = function( self, paused ) 387 | err( 'setPaused: expected argument two be a boolean, got %type%.', paused, 'boolean' ) 388 | self.paused = paused 389 | end, 390 | getPaused = function( self ) return self.pause end, 391 | pause = function( self ) self.paused = true end, 392 | resume = function( self ) self.paused = false end, 393 | togglePause = function( self ) self.paused = not self.paused end, 394 | setCurrentFrame = function( self, frame ) 395 | err( 'setCurrentFrame: expected argument two to be a be number, got %type%.', frame, 'number' ) 396 | err( 'setCurrentFrame: argument two must be a positive integer 1-#animation.frames.', frame, function( frame ) return self.frames[frame] end ) 397 | self.currentFrame = frame 398 | self.delayTimer = 0 399 | end, 400 | getCurrentFrame = function( self ) return self.currentFrame end, 401 | setActive = function( self, active ) 402 | err( 'setActive: expected argument two to be a boolean, got %type%.', active, 'boolean' ) 403 | self.active = active 404 | end, 405 | getActive = function( self ) return self.active end, 406 | toggleActive = function( self ) self.active = not self.active end, 407 | setLooping = function( self, looping ) 408 | looping = ( looping == nil ) and true or looping 409 | err( 'setLooping: expected argument two to be a boolean, got %type%.', looping, 'boolean' ) 410 | self.looping = looping 411 | end, 412 | getLooping = function( self ) return self.looping end, 413 | toggleLooping = function( self ) self.looping = not self.looping end, 414 | setPauseAtEnd = function( self, pauseAtEnd ) 415 | err( 'setPauseAtEnd: expected argument two to be a boolean, got %type%.', pauseAtEnd, 'boolean' ) 416 | self.shouldPauseAtEnd = pauseAtEnd 417 | end, 418 | getPauseAtEnd = function( self ) return self.shouldPauseAtEnd end, 419 | togglePauseAtEnd = function( self ) self.shouldPauseAtEnd = not self.shouldPauseAtEnd end, 420 | pauseAtEnd = function( self ) self.shouldPauseAtEnd = true end, 421 | restart = function( self ) 422 | self:setCurrentFrame( 1 ) 423 | self.active = true 424 | self.paused = false 425 | end, 426 | getWidth = function( self ) 427 | local currentFrame = self.frames[self.currentFrame] 428 | local currentFrameType = getFrameType( self ) 429 | return self.active and ( 430 | ( currentFrameType == 'Image' and currentFrame:getWidth() ) 431 | or ( currentFrameType == 'Quad' and select( 3, currentFrame:getViewport() ) ) 432 | ) 433 | end, 434 | getHeight = function( self ) 435 | local currentFrame = self.frames[self.currentFrame] 436 | local currentFrameType = getFrameType( self ) 437 | return self.active and ( 438 | ( currentFrameType == 'Image' and currentFrame:getHeight() ) 439 | or ( currentFrameType == 'Quad' and select( 4, currentFrame:getViewport() ) ) 440 | ) 441 | end, 442 | getDimensions = function( self ) 443 | local currentFrame = self.frames[self.currentFrame] 444 | local currentFrameType = getFrameType( self ) 445 | if self.active then -- Ternary returns don't do multiples 446 | if currentFrameType == 'Image' then 447 | return currentFrame:getDimensions() 448 | elseif currentFrameType == 'Quad' then 449 | return self:getWidth(), self:getHeight() 450 | end 451 | end 452 | end, 453 | } 454 | -- Aliases 455 | animation.setAnimationChange = animation.setOnAnimationChange 456 | animation.isPaused = animation.getPaused 457 | animation.setFrame = animation.setCurrentFrame 458 | animation.gotoFrame = animation.setCurrentFrame 459 | animation.getFrame = animation.getCurrentFrame 460 | animation.isActive = animation.getActive 461 | animation.isLooping = animation.getLooping 462 | 463 | return animation 464 | end 465 | } 466 | -------------------------------------------------------------------------------- /examples/animator.lua: -------------------------------------------------------------------------------- 1 | ../animator.lua -------------------------------------------------------------------------------- /examples/conf.lua: -------------------------------------------------------------------------------- 1 | function love.conf( t ) 2 | t.console = true 3 | end 4 | -------------------------------------------------------------------------------- /examples/images/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davisdude/Walt/8c3b46de0885fe4350bde027f5f45cfc0ffae783/examples/images/1.png -------------------------------------------------------------------------------- /examples/images/2 and 4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davisdude/Walt/8c3b46de0885fe4350bde027f5f45cfc0ffae783/examples/images/2 and 4.png -------------------------------------------------------------------------------- /examples/images/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davisdude/Walt/8c3b46de0885fe4350bde027f5f45cfc0ffae783/examples/images/3.png -------------------------------------------------------------------------------- /examples/images/Quad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davisdude/Walt/8c3b46de0885fe4350bde027f5f45cfc0ffae783/examples/images/Quad.png -------------------------------------------------------------------------------- /examples/main.lua: -------------------------------------------------------------------------------- 1 | local animator = require 'animator' 2 | local anim, anim2, anim3, anim4 3 | local rotation 4 | 5 | function love.load() 6 | love.graphics.setDefaultFilter( 'nearest', 'nearest' ) 7 | local one = love.graphics.newImage( 'images/1.png' ) 8 | local quadImage = love.graphics.newImage( 'images/2 and 4.png' ) 9 | local three = love.graphics.newImage( 'images/3.png' ) 10 | local two = love.graphics.newQuad( 0, 0, 32, 32, 64, 32 ) 11 | local four = love.graphics.newQuad( 32, 0, 32, 32, 64, 32 ) 12 | 13 | -- newAnimation( frames, times, [quadImage] ) 14 | anim = animator.newAnimation( { one, two, three, four }, { 1, 1, 1, 1 }, quadImage ) 15 | anim:setLooping() 16 | rotation = 0 17 | 18 | local bigQuad = love.graphics.newImage( 'images/Quad.png' ) 19 | local grid = animator.newGrid( 32, 32, bigQuad, 0, 0, bigQuad:getDimensions() ) 20 | anim2 = animator.newAnimation( grid( 1,1, 2,'2 - 4', '2 - 4',1, 1,2, 4,'4 - 2', 1,4, 3,'4-2', 1,3 ), 1, bigQuad ) 21 | anim2:setLooping( true ) 22 | 23 | grid = animator.newGrid( 32, 32, bigQuad, 32, 32, bigQuad:getDimensions() ) 24 | anim3 = animator.newAnimation( grid( 1,'1-3', 3,'3-1', 2,'3-1' ), { ['1, 6, 9'] = 2, 5, ['2, 4-5, 7-8'] = 1 }, bigQuad ) 25 | 26 | local grid1 = animator.newGrid( 32, 32, bigQuad, 64, 64, bigQuad:getDimensions() ) 27 | local grid2 = animator.newGrid( 32, 32, bigQuad, 0, 0, 64, 64 ) 28 | anim4 = animator.newAnimation( animator.merge( grid2( 1,1, 2,2, 2,1, 1,2 ), grid1( 2,2, 2,1, 1,2, 1,1 ) ), { ['1, 4, 5, 7'] = 1, ['2, 3, 6, 8'] = 3 }, bigQuad ) 29 | end 30 | 31 | function love.update( dt ) 32 | rotation = rotation + dt 33 | 34 | anim:update( dt ) 35 | anim2:update( dt ) 36 | anim3:update( dt ) 37 | anim4:update( dt ) 38 | end 39 | 40 | function love.draw() 41 | anim:draw( 400, 300, rotation, 1, 1, anim:getWidth() / 2, anim:getHeight() / 2 ) 42 | anim2:draw() 43 | anim3:draw( 32, 32 ) 44 | anim4:draw( 64, 64 ) 45 | end 46 | 47 | function love.keyreleased( key ) 48 | if key == 'escape' then 49 | love.event.quit() 50 | else 51 | anim:restart() 52 | anim2:restart() 53 | anim3:restart() 54 | anim4:restart() 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /images/Grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/davisdude/Walt/8c3b46de0885fe4350bde027f5f45cfc0ffae783/images/Grid.png --------------------------------------------------------------------------------