├── LICENSE ├── README.md └── flux.lua /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 rxi 2 | 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # flux 2 | A fast, lightweight tweening library for Lua. 3 | 4 | ## Installation 5 | The [flux.lua](flux.lua?raw=1) file should be dropped into an existing project 6 | and required by it. 7 | ```lua 8 | flux = require "flux" 9 | ``` 10 | The `flux.update()` function should be called at the start of each frame. As 11 | its only argument It should be given the time in seconds that has passed since 12 | the last call. 13 | ```lua 14 | flux.update(deltatime) 15 | ``` 16 | 17 | ## Usage 18 | Any number of numerical values in a table can be tweened simultaneously. Tweens 19 | are started by using the `flux.to()` function. This function requires 3 20 | arguments: 21 | * `obj` The object which contains the variables to tween 22 | * `time` The amount of time the tween should take to complete 23 | * `vars` A table where the keys correspond to the keys in `obj` which should be 24 | tweened, and their values correspond to the destination 25 | ```lua 26 | -- Moves the ball object to the position 200, 300 over 4 seconds 27 | flux.to(ball, 4, { x = 200, y = 300 }) 28 | ``` 29 | If you try to tween a variable which is already being tweened, the original 30 | tween stops tweening the variable and the new tween begins from the current 31 | value. 32 | 33 | ### Additional options 34 | Additional options when creating a tween can be set through the use of chained 35 | functions provided by the tween object which `flux.to()` returns. 36 | ```lua 37 | flux.to(t, 4, { x = 10 }):ease("linear"):delay(1) 38 | ``` 39 | 40 | #### :ease(type) 41 | The easing type which should be used by the tween; `type` should be a string 42 | containing the name of the easing to be used. The library provides the 43 | following easing types: 44 | 45 | `linear` 46 | `quadin` `quadout` `quadinout` 47 | `cubicin` `cubicout` `cubicinout` 48 | `quartin` `quartout` `quartinout` 49 | `quintin` `quintout` `quintinout` 50 | `expoin` `expoout` `expoinout` 51 | `sinein` `sineout` `sineinout` 52 | `circin` `circout` `circinout` 53 | `backin` `backout` `backinout` 54 | `elasticin` `elasticout` `elasticinout` 55 | 56 | The default easing type is `quadout`. Examples of the different easing types 57 | can be [found here](http://easings.net/). 58 | 59 | 60 | #### :delay(time) 61 | The amount of time flux should wait before starting the tween; `time` should be 62 | a number of seconds. The default delay time is `0`. 63 | 64 | #### :onstart(fn) 65 | Sets the function `fn` to be called when the tween starts (once the delay has 66 | finished). `:onstart()` can be called multiple times to add more than one 67 | function. 68 | 69 | #### :onupdate(fn) 70 | Sets the function `fn` to be called each frame the tween updates a value. 71 | `onupdate()` can be called multiple times to add more than one function. 72 | 73 | #### :oncomplete(fn) 74 | Sets the function `fn` to be called once the tween has finished and reached its 75 | destination values. `oncomplete()` can be called multiple times to add more 76 | than one function. 77 | 78 | #### :after([obj,] time, vars) 79 | Creates a new tween and chains it to the end of the existing tween; the chained 80 | tween will be called after the original one has finished. Any additional 81 | chained function used after `:after()` will effect the chained tween. There is 82 | no limit to how many times `:after()` can be used in a chain, allowing the 83 | creation of long tween sequences. If `obj` is not specified the `obj` argument 84 | from the original tween is used. 85 | ```lua 86 | -- Tweens t.x to 10 over 2 seconds, then to 20 over 1 second 87 | flux.to(t, 2, { x = 10 }):after(t, 1, { x = 20 }) 88 | ``` 89 | 90 | ### Stopping a tween 91 | If you want the ability to stop a tween before it has finished, the tween 92 | should be assigned to a variable when it is created. 93 | ```lua 94 | local tween = flux.to(x, 2, { y = 20 }):delay(1) 95 | ``` 96 | The tween can then be stopped at any point by calling its `:stop()` method. 97 | ```lua 98 | tween:stop() 99 | ``` 100 | This will cause the tween to immediatly be removed from its parent group and 101 | will leave its tweened variables at their current values. The tween's 102 | `oncomplete()` callback is not called. 103 | 104 | ### Groups 105 | flux provides the ability to create tween groups; these are objects 106 | which can have tweens added to them, and who are in charge of updating and 107 | handling their contained tweens. A group is created by calling the 108 | `flux.group()` function. 109 | ```lua 110 | group = flux.group() 111 | ``` 112 | Once a group is created it acts independently of the `flux` object, and must 113 | be updated each frame using its own update method. 114 | ```lua 115 | group:update(deltatime) 116 | ``` 117 | To add a tween to a group, the group's `to()` method should be used. 118 | ```lua 119 | group:to(t, 3, { x = 10, y = 20 }) 120 | ``` 121 | A good example of where groups are useful is for games where you may have a set 122 | of tweens which effect objects in the game world and which you want to pause 123 | when the game is paused. A group's tweens can be paused by simply neglecting 124 | to call its `update()` method; when a group is destroyed its tweens are also 125 | destroyed. 126 | 127 | 128 | ## License 129 | This library is free software; you can redistribute it and/or modify it under 130 | the terms of the MIT license. See [LICENSE](LICENSE) for details. 131 | 132 | -------------------------------------------------------------------------------- /flux.lua: -------------------------------------------------------------------------------- 1 | -- 2 | -- flux 3 | -- 4 | -- Copyright (c) 2016 rxi 5 | -- 6 | -- This library is free software; you can redistribute it and/or modify it 7 | -- under the terms of the MIT license. See LICENSE for details. 8 | -- 9 | 10 | local flux = { _version = "0.1.5" } 11 | flux.__index = flux 12 | 13 | flux.tweens = {} 14 | flux.easing = { linear = function(p) return p end } 15 | 16 | local easing = { 17 | quad = "p * p", 18 | cubic = "p * p * p", 19 | quart = "p * p * p * p", 20 | quint = "p * p * p * p * p", 21 | expo = "2 ^ (10 * (p - 1))", 22 | sine = "-math.cos(p * (math.pi * .5)) + 1", 23 | circ = "-(math.sqrt(1 - (p * p)) - 1)", 24 | back = "p * p * (2.7 * p - 1.7)", 25 | elastic = "-(2^(10 * (p - 1)) * math.sin((p - 1.075) * (math.pi * 2) / .3))" 26 | } 27 | 28 | local makefunc = function(str, expr) 29 | local load = loadstring or load 30 | return load("return function(p) " .. str:gsub("%$e", expr) .. " end")() 31 | end 32 | 33 | for k, v in pairs(easing) do 34 | flux.easing[k .. "in"] = makefunc("return $e", v) 35 | flux.easing[k .. "out"] = makefunc([[ 36 | p = 1 - p 37 | return 1 - ($e) 38 | ]], v) 39 | flux.easing[k .. "inout"] = makefunc([[ 40 | p = p * 2 41 | if p < 1 then 42 | return .5 * ($e) 43 | else 44 | p = 2 - p 45 | return .5 * (1 - ($e)) + .5 46 | end 47 | ]], v) 48 | end 49 | 50 | 51 | 52 | local tween = {} 53 | tween.__index = tween 54 | 55 | local function makefsetter(field) 56 | return function(self, x) 57 | local mt = getmetatable(x) 58 | if type(x) ~= "function" and not (mt and mt.__call) then 59 | error("expected function or callable", 2) 60 | end 61 | local old = self[field] 62 | self[field] = old and function() old() x() end or x 63 | return self 64 | end 65 | end 66 | 67 | local function makesetter(field, checkfn, errmsg) 68 | return function(self, x) 69 | if checkfn and not checkfn(x) then 70 | error(errmsg:gsub("%$x", tostring(x)), 2) 71 | end 72 | self[field] = x 73 | return self 74 | end 75 | end 76 | 77 | tween.ease = makesetter("_ease", 78 | function(x) return flux.easing[x] end, 79 | "bad easing type '$x'") 80 | tween.delay = makesetter("_delay", 81 | function(x) return type(x) == "number" end, 82 | "bad delay time; expected number") 83 | tween.onstart = makefsetter("_onstart") 84 | tween.onupdate = makefsetter("_onupdate") 85 | tween.oncomplete = makefsetter("_oncomplete") 86 | 87 | 88 | function tween.new(obj, time, vars) 89 | local self = setmetatable({}, tween) 90 | self.obj = obj 91 | self.rate = time > 0 and 1 / time or 0 92 | self.progress = time > 0 and 0 or 1 93 | self._delay = 0 94 | self._ease = "quadout" 95 | self.vars = {} 96 | for k, v in pairs(vars) do 97 | if type(v) ~= "number" then 98 | error("bad value for key '" .. k .. "'; expected number") 99 | end 100 | self.vars[k] = v 101 | end 102 | return self 103 | end 104 | 105 | 106 | function tween:init() 107 | for k, v in pairs(self.vars) do 108 | local x = self.obj[k] 109 | if type(x) ~= "number" then 110 | error("bad value on object key '" .. k .. "'; expected number") 111 | end 112 | self.vars[k] = { start = x, diff = v - x } 113 | end 114 | self.inited = true 115 | end 116 | 117 | 118 | function tween:after(...) 119 | local t 120 | if select("#", ...) == 2 then 121 | t = tween.new(self.obj, ...) 122 | else 123 | t = tween.new(...) 124 | end 125 | t.parent = self.parent 126 | self:oncomplete(function() flux.add(self.parent, t) end) 127 | return t 128 | end 129 | 130 | 131 | function tween:stop() 132 | flux.remove(self.parent, self) 133 | end 134 | 135 | 136 | 137 | function flux.group() 138 | return setmetatable({}, flux) 139 | end 140 | 141 | 142 | function flux:to(obj, time, vars) 143 | return flux.add(self, tween.new(obj, time, vars)) 144 | end 145 | 146 | 147 | function flux:update(deltatime) 148 | for i = #self, 1, -1 do 149 | local t = self[i] 150 | if t._delay > 0 then 151 | t._delay = t._delay - deltatime 152 | else 153 | if not t.inited then 154 | flux.clear(self, t.obj, t.vars) 155 | t:init() 156 | end 157 | if t._onstart then 158 | t._onstart() 159 | t._onstart = nil 160 | end 161 | t.progress = t.progress + t.rate * deltatime 162 | local p = t.progress 163 | local x = p >= 1 and 1 or flux.easing[t._ease](p) 164 | for k, v in pairs(t.vars) do 165 | t.obj[k] = v.start + x * v.diff 166 | end 167 | if t._onupdate then t._onupdate() end 168 | if p >= 1 then 169 | flux.remove(self, i) 170 | if t._oncomplete then t._oncomplete() end 171 | end 172 | end 173 | end 174 | end 175 | 176 | 177 | function flux:clear(obj, vars) 178 | for t in pairs(self[obj]) do 179 | if t.inited then 180 | for k in pairs(vars) do t.vars[k] = nil end 181 | end 182 | end 183 | end 184 | 185 | 186 | function flux:add(tween) 187 | -- Add to object table, create table if it does not exist 188 | local obj = tween.obj 189 | self[obj] = self[obj] or {} 190 | self[obj][tween] = true 191 | -- Add to array 192 | table.insert(self, tween) 193 | tween.parent = self 194 | return tween 195 | end 196 | 197 | 198 | function flux:remove(x) 199 | if type(x) == "number" then 200 | -- Remove from object table, destroy table if it is empty 201 | local obj = self[x].obj 202 | self[obj][self[x]] = nil 203 | if not next(self[obj]) then self[obj] = nil end 204 | -- Remove from array 205 | self[x] = self[#self] 206 | return table.remove(self) 207 | end 208 | for i, v in ipairs(self) do 209 | if v == x then 210 | return flux.remove(self, i) 211 | end 212 | end 213 | end 214 | 215 | 216 | 217 | local bound = { 218 | to = function(...) return flux.to(flux.tweens, ...) end, 219 | update = function(...) return flux.update(flux.tweens, ...) end, 220 | remove = function(...) return flux.remove(flux.tweens, ...) end, 221 | } 222 | setmetatable(bound, flux) 223 | 224 | return bound 225 | --------------------------------------------------------------------------------