├── LICENSE ├── README.md ├── chiro.lua └── main.lua /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Bjorn Swenson 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Chiro 2 | === 3 | 4 | A library that makes it easier to work with [Spine](http://esotericsoftware.com/) animations in LÖVE 5 | games. 6 | 7 | Example 8 | --- 9 | 10 | If your project looks like this: 11 | 12 | ``` 13 | ├── main.lua 14 | ├── chiro.lua 15 | ├── spine-love 16 | ├── spine-lua 17 | └── spineboy 18 | ├── spineboy.json 19 | ├── spineboy.atlas 20 | └── spineboy.png 21 | 22 | ``` 23 | 24 | Then you can get up and running quickly by doing this in `main.lua`: 25 | 26 | ```lua 27 | require 'spine-love/spine' 28 | 29 | chiro = require 'chiro' 30 | 31 | animation = chiro.create({ 32 | dir = 'spineboy', 33 | states = { 34 | walk = { 35 | loop = true 36 | } 37 | }, 38 | default = 'walk' 39 | }) 40 | 41 | function love.update(delta) 42 | animation:update(delta) 43 | end 44 | 45 | function love.draw() 46 | love.graphics.setColor(255, 255, 255) 47 | animation:draw(400, 550) 48 | end 49 | ``` 50 | 51 | Advanced 52 | --- 53 | 54 | To create a chiro animation, call `chiro.create(options)`. Chiro needs to know where the JSON 55 | file and images folder are, which can be specified using the `dir` option. This should point to 56 | a directory containing a JSON file, an atlas, and a png file, all named the same as the directory. 57 | 58 | Here are the other optional options that can be customized: 59 | 60 | ```lua 61 | local animation = chiro.create({ 62 | scale = 1, -- base scale of animation 63 | flip = false, -- flip animation along the x axis 64 | flip = { -- can also flip both x and y 65 | x = false, 66 | y = false 67 | }, 68 | x = 0, -- x value to draw at 69 | y = 0, -- y value to draw at 70 | offset = { 71 | x = 0, -- offset x values by this amount 72 | y = 0 -- offset y values by this amount 73 | }, 74 | speed = 1, -- global speed of animation 75 | states = { 76 | = { 77 | loop = false, -- whether or not to repeat the animation 78 | track = 0, -- the track to play the animation on 79 | speed = 1, -- the speed at which to play the animation 80 | length = 1 -- alternatively, specify how long the animation should take to complete 81 | next = -- specify an animation to transition to on completion 82 | } 83 | }, 84 | on = { 85 | = function(animation, event) end, 86 | start = function(animation, state) end, 87 | complete = function(animation, state) end, 88 | ['end'] = function(animation, state) end 89 | }, 90 | default = -- immediately start playing this animation 91 | }) 92 | ``` 93 | 94 | An animation should be updated to play through the tracks and set the bones in the right position. 95 | To do this, call the `update` function on the chiro animation: 96 | 97 | ```lua 98 | function love.update(dt) 99 | animation:update(dt) 100 | end 101 | ``` 102 | 103 | `dt` is the number of seconds elapsed since the last call to `update`. 104 | 105 | To draw the animation to the screen, call `draw`: 106 | 107 | ```lua 108 | function love.draw() 109 | animation:draw(x, y) 110 | end 111 | ``` 112 | 113 | `x` and `y` are optional. They can also be set on the animation directly: 114 | 115 | ```lua 116 | animation.x = 100 117 | animation.y = 100 118 | ``` 119 | 120 | Use `set` to play a specific animation: 121 | 122 | ```lua 123 | animation:set('walk') -- Play the walk animation 124 | ``` 125 | 126 | The animation will play using the settings defined in the `states` part of the config for that 127 | animation. This can be used to control speed, looping, tracks, and can also be used to transition 128 | to another animation after the current one is finished playing. 129 | 130 | To reset things, call `clear` on the animation to clear all animation tracks, or `resetTo(name)` 131 | to clear all tracks and begin playing an animation. 132 | 133 | To hook into events for the animation, specify functions for keys in the `on` section of the config. 134 | `start`, `complete`, and `end` will be passed the animation object and the state object as 135 | arguments. 136 | 137 | Chiro also exposes most of the underlying Spine objects as properties on the animation object: 138 | 139 | - `skeletonJson` 140 | - `skeletonData` 141 | - `skeletonRenderer` 142 | - `skeleton` 143 | - `animationStateData` 144 | - `animationState` 145 | 146 | License 147 | --- 148 | 149 | MIT, see [`LICENSE`](LICENSE) for details. 150 | -------------------------------------------------------------------------------- /chiro.lua: -------------------------------------------------------------------------------- 1 | local chiro = {} 2 | chiro.__index = chiro 3 | 4 | function chiro.create(config) 5 | local self = setmetatable(config, chiro) 6 | 7 | local name = self.dir:match('[^%/]+$') 8 | 9 | if self.dir then 10 | self.json = self.json or (self.dir .. '/' .. name .. '.json') 11 | end 12 | 13 | local loader = function (path) return love.graphics.newImage(self.dir .. '/' .. path) end 14 | local atlas = spine.TextureAtlas.new(spine.utils.readFile(self.dir .. '/' .. name .. ".atlas"), loader) 15 | 16 | self.skeletonJson = spine.SkeletonJson.new(spine.AtlasAttachmentLoader.new(atlas)) 17 | self.skeletonJson.scale = self.scale or 1 18 | 19 | if type(self.json) == 'table' then 20 | self.skeletonData = self.skeletonJson:readSkeletonData(self.json) 21 | else 22 | self.skeletonData = self.skeletonJson:readSkeletonDataFile(self.json) 23 | end 24 | 25 | self.skeleton = spine.Skeleton.new(self.skeletonData) 26 | 27 | self.animationStateData = spine.AnimationStateData.new(self.skeletonData) 28 | self.animationState = spine.AnimationState.new(self.animationStateData) 29 | 30 | self.skeleton:setToSetupPose() 31 | 32 | for name, state in pairs(self.states) do 33 | state.name = name 34 | end 35 | 36 | self.on = self.on or {} 37 | 38 | self.animationState.onStart = function(entry) 39 | local name = entry.animation.name 40 | local state = self.states[name] 41 | if state and self.on.start then 42 | self.on.start(self, state) 43 | end 44 | end 45 | 46 | self.animationState.onEvent = function(_, event) 47 | local name = event.data.name 48 | if self.on[name] then 49 | self.on[name](self, event) 50 | end 51 | end 52 | 53 | self.animationState.onEnd = function(entry) 54 | local name = entry.animation.name 55 | local state = self.states[name] 56 | if state then 57 | if self.on['end'] then 58 | self.on['end'](self, state) 59 | end 60 | state.active = false 61 | if state.next then 62 | self:set(state.next) 63 | end 64 | end 65 | end 66 | 67 | self.animationState.onComplete = function(entry) 68 | local name = entry.animation.name 69 | local state = self.states[name] 70 | if state and self.on.complete then 71 | self.on.complete(self, state) 72 | end 73 | end 74 | 75 | self:resetTo(self.default) 76 | 77 | self.skeletonRenderer = spine.SkeletonRenderer.new(true) 78 | 79 | return self 80 | end 81 | 82 | function chiro:draw(x, y) 83 | x = (x or self.x or 0) + (self.offset and self.offset.x or 0) 84 | y = (y or self.y or 0) + (self.offset and self.offset.y or 0) 85 | local skeleton = self.skeleton 86 | skeleton.x, skeleton.y = x, y 87 | if type(self.flip) == 'table' then 88 | skeleton.flipX = self.flip.x 89 | skeleton.flipY = not self.flip.y 90 | else 91 | skeleton.flipX = self.flip 92 | skeleton.flipY = true 93 | end 94 | skeleton:updateWorldTransform() 95 | self.skeletonRenderer:draw(skeleton) 96 | end 97 | 98 | function chiro:update(delta) 99 | self.animationState.timeScale = self.speed or 1 100 | for _, track in ipairs(self.animationState.tracks) do 101 | if track then 102 | local animation = track.animation 103 | local state = self.states[animation.name] 104 | if state.length then 105 | local speed = animation.duration / state.length 106 | track.timeScale = speed 107 | else 108 | track.timeScale = state.speed or 1 109 | end 110 | end 111 | end 112 | 113 | self.animationState:update(delta) 114 | self.animationState:apply(self.skeleton) 115 | end 116 | 117 | function chiro:resetTo(name) 118 | self:clear() 119 | self:set(name) 120 | end 121 | 122 | function chiro:set(name) 123 | if not name then return end 124 | local state = self.states[name] 125 | if state and not state.active then 126 | local track = state.track or 0 127 | local loop = state.loop 128 | state.active = true 129 | self.animationState:setAnimationByName(track, name, loop) 130 | end 131 | end 132 | 133 | function chiro:clear() 134 | self.animationState:clearTracks() 135 | end 136 | 137 | return chiro 138 | -------------------------------------------------------------------------------- /main.lua: -------------------------------------------------------------------------------- 1 | require 'spine-love/spine' 2 | 3 | chiro = require 'chiro' 4 | 5 | animation = chiro.create({ 6 | dir = 'spineboy', 7 | states = { 8 | walk = { 9 | loop = true 10 | } 11 | }, 12 | default = 'walk' 13 | }) 14 | 15 | function love.update(delta) 16 | animation:update(delta) 17 | end 18 | 19 | function love.draw() 20 | love.graphics.setColor(255, 255, 255) 21 | animation:draw(400, 600) 22 | end 23 | --------------------------------------------------------------------------------