commit e2120ca07bfe220d47c5ae121d24e208f3438d04 Author: NathanSweet Date: Wed Feb 20 01:13:59 2013 +0100 Initial Corona runtime. diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..4478e9e1f --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +spine-cpp/Debug/* +spine-libgdx/bin/* diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..1750d7973 --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +Copyright (c) 2013, Esoteric Software +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/spine-corona/build.settings b/spine-corona/build.settings new file mode 100644 index 000000000..a55b73421 --- /dev/null +++ b/spine-corona/build.settings @@ -0,0 +1,12 @@ +settings = { + orientation = { + default = "portrait", + supported = { "portrait", } + }, + iphone = { + plist = { + UIStatusBarHidden = false, + UIPrerenderedIcon = true, -- set to false for "shine" overlay + } + }, +} diff --git a/spine-corona/config.lua b/spine-corona/config.lua new file mode 100644 index 000000000..643443087 --- /dev/null +++ b/spine-corona/config.lua @@ -0,0 +1,8 @@ +application = { + content = { + width = 320, + height = 480, + scale = "letterBox", + fps = 60, + }, +} diff --git a/spine-corona/data/eyes-closed.png b/spine-corona/data/eyes-closed.png new file mode 100644 index 000000000..60718e101 Binary files /dev/null and b/spine-corona/data/eyes-closed.png differ diff --git a/spine-corona/data/eyes.png b/spine-corona/data/eyes.png new file mode 100644 index 000000000..707c91b72 Binary files /dev/null and b/spine-corona/data/eyes.png differ diff --git a/spine-corona/data/head.png b/spine-corona/data/head.png new file mode 100644 index 000000000..5a98aa37a Binary files /dev/null and b/spine-corona/data/head.png differ diff --git a/spine-corona/data/left-ankle.png b/spine-corona/data/left-ankle.png new file mode 100644 index 000000000..fcf9a2813 Binary files /dev/null and b/spine-corona/data/left-ankle.png differ diff --git a/spine-corona/data/left-arm.png b/spine-corona/data/left-arm.png new file mode 100644 index 000000000..4447dec8a Binary files /dev/null and b/spine-corona/data/left-arm.png differ diff --git a/spine-corona/data/left-foot.png b/spine-corona/data/left-foot.png new file mode 100644 index 000000000..9b8768277 Binary files /dev/null and b/spine-corona/data/left-foot.png differ diff --git a/spine-corona/data/left-hand.png b/spine-corona/data/left-hand.png new file mode 100644 index 000000000..b95a3523c Binary files /dev/null and b/spine-corona/data/left-hand.png differ diff --git a/spine-corona/data/left-lower-leg.png b/spine-corona/data/left-lower-leg.png new file mode 100644 index 000000000..f316b6500 Binary files /dev/null and b/spine-corona/data/left-lower-leg.png differ diff --git a/spine-corona/data/left-pant-bottom.png b/spine-corona/data/left-pant-bottom.png new file mode 100644 index 000000000..29a05bc4f Binary files /dev/null and b/spine-corona/data/left-pant-bottom.png differ diff --git a/spine-corona/data/left-shoulder.png b/spine-corona/data/left-shoulder.png new file mode 100644 index 000000000..7fd429dc3 Binary files /dev/null and b/spine-corona/data/left-shoulder.png differ diff --git a/spine-corona/data/left-upper-leg.png b/spine-corona/data/left-upper-leg.png new file mode 100644 index 000000000..f076d5c91 Binary files /dev/null and b/spine-corona/data/left-upper-leg.png differ diff --git a/spine-corona/data/neck.png b/spine-corona/data/neck.png new file mode 100644 index 000000000..c7b938863 Binary files /dev/null and b/spine-corona/data/neck.png differ diff --git a/spine-corona/data/pelvis.png b/spine-corona/data/pelvis.png new file mode 100644 index 000000000..f52c33cdd Binary files /dev/null and b/spine-corona/data/pelvis.png differ diff --git a/spine-corona/data/right-ankle.png b/spine-corona/data/right-ankle.png new file mode 100644 index 000000000..92fc568cf Binary files /dev/null and b/spine-corona/data/right-ankle.png differ diff --git a/spine-corona/data/right-arm.png b/spine-corona/data/right-arm.png new file mode 100644 index 000000000..cac970f4b Binary files /dev/null and b/spine-corona/data/right-arm.png differ diff --git a/spine-corona/data/right-foot-idle.png b/spine-corona/data/right-foot-idle.png new file mode 100644 index 000000000..aaf609f4e Binary files /dev/null and b/spine-corona/data/right-foot-idle.png differ diff --git a/spine-corona/data/right-foot.png b/spine-corona/data/right-foot.png new file mode 100644 index 000000000..7a06bf2de Binary files /dev/null and b/spine-corona/data/right-foot.png differ diff --git a/spine-corona/data/right-hand.png b/spine-corona/data/right-hand.png new file mode 100644 index 000000000..17c62bf4a Binary files /dev/null and b/spine-corona/data/right-hand.png differ diff --git a/spine-corona/data/right-lower-leg.png b/spine-corona/data/right-lower-leg.png new file mode 100644 index 000000000..1f00e8ec9 Binary files /dev/null and b/spine-corona/data/right-lower-leg.png differ diff --git a/spine-corona/data/right-pant-bottom.png b/spine-corona/data/right-pant-bottom.png new file mode 100644 index 000000000..73309c05d Binary files /dev/null and b/spine-corona/data/right-pant-bottom.png differ diff --git a/spine-corona/data/right-shoulder.png b/spine-corona/data/right-shoulder.png new file mode 100644 index 000000000..23e9a2fd1 Binary files /dev/null and b/spine-corona/data/right-shoulder.png differ diff --git a/spine-corona/data/right-upper-leg.png b/spine-corona/data/right-upper-leg.png new file mode 100644 index 000000000..df0b11661 Binary files /dev/null and b/spine-corona/data/right-upper-leg.png differ diff --git a/spine-corona/data/spineboy-skeleton.json b/spine-corona/data/spineboy-skeleton.json new file mode 100644 index 000000000..a51a9feeb --- /dev/null +++ b/spine-corona/data/spineboy-skeleton.json @@ -0,0 +1,101 @@ +{ +"bones": [ + { "name": "root", "length": 0 }, + { "name": "hip", "parent": "root", "length": 0, "x": 0.64, "y": 114.41 }, + { "name": "left upper leg", "parent": "hip", "length": 50.39, "x": 14.45, "y": 2.81, "rotation": -89.09 }, + { "name": "left lower leg", "parent": "left upper leg", "length": 56.45, "x": 51.78, "y": 3.46, "rotation": -16.65 }, + { "name": "left foot", "parent": "left lower leg", "length": 46.5, "x": 64.02, "y": -8.67, "rotation": 102.43 }, + { "name": "right upper leg", "parent": "hip", "length": 45.76, "x": -18.27, "rotation": -101.13 }, + { "name": "right lower leg", "parent": "right upper leg", "length": 58.52, "x": 50.21, "y": 0.6, "rotation": -10.7 }, + { "name": "right foot", "parent": "right lower leg", "length": 45.45, "x": 64.88, "y": 0.04, "rotation": 110.3 }, + { "name": "torso", "parent": "hip", "length": 85.82, "x": -6.42, "y": 1.97, "rotation": 94.95 }, + { "name": "neck", "parent": "torso", "length": 18.38, "x": 83.64, "y": -1.78, "rotation": 0.9 }, + { "name": "head", "parent": "neck", "length": 68.28, "x": 19.09, "y": 6.97, "rotation": -8.94 }, + { "name": "right shoulder", "parent": "torso", "length": 49.95, "x": 81.9, "y": 6.79, "rotation": 130.6 }, + { "name": "right arm", "parent": "right shoulder", "length": 36.74, "x": 49.95, "y": -0.12, "rotation": 40.12 }, + { "name": "right hand", "parent": "right arm", "length": 15.32, "x": 36.9, "y": 0.34, "rotation": 2.35 }, + { "name": "left shoulder", "parent": "torso", "length": 44.19, "x": 78.96, "y": -15.75, "rotation": -156.96 }, + { "name": "left arm", "parent": "left shoulder", "length": 35.62, "x": 44.19, "y": -0.01, "rotation": 28.16 }, + { "name": "left hand", "parent": "left arm", "length": 11.52, "x": 35.62, "y": 0.07, "rotation": 2.7 }, + { "name": "pelvis", "parent": "hip", "length": 0, "x": 1.41, "y": -6.57 } +], +"slots": [ + { "name": "template", "bone": "root", "color": "ff898c86" }, + { "name": "left shoulder", "bone": "left shoulder", "attachment": "left-shoulder" }, + { "name": "left arm", "bone": "left arm", "attachment": "left-arm" }, + { "name": "left hand", "bone": "left hand", "attachment": "left-hand" }, + { "name": "left foot", "bone": "left foot", "attachment": "left-foot" }, + { "name": "left lower leg", "bone": "left lower leg", "attachment": "left-lower-leg" }, + { "name": "left upper leg", "bone": "left upper leg", "attachment": "left-upper-leg" }, + { "name": "pelvis", "bone": "pelvis", "attachment": "pelvis" }, + { "name": "right foot", "bone": "right foot", "attachment": "right-foot" }, + { "name": "right lower leg", "bone": "right lower leg", "attachment": "right-lower-leg" }, + { "name": "right upper leg", "bone": "right upper leg", "attachment": "right-upper-leg" }, + { "name": "torso", "bone": "torso", "attachment": "torso" }, + { "name": "neck", "bone": "neck", "attachment": "neck" }, + { "name": "head", "bone": "head", "attachment": "head" }, + { "name": "eyes", "bone": "head", "attachment": "eyes" }, + { "name": "right shoulder", "bone": "right shoulder", "attachment": "right-shoulder" }, + { "name": "right arm", "bone": "right arm", "attachment": "right-arm" }, + { "name": "right hand", "bone": "right hand", "attachment": "right-hand" } +], +"skins": { + "default": { + "template": { + "spineboy": { "y": 167.82, "width": 145, "height": 341 } + }, + "left shoulder": { + "left-shoulder": { "x": 23.74, "y": 0.11, "rotation": 62.01, "width": 34, "height": 53 } + }, + "left arm": { + "left-arm": { "x": 15.11, "y": -0.44, "rotation": 33.84, "width": 35, "height": 29 } + }, + "left hand": { + "left-hand": { "x": 0.75, "y": 1.86, "rotation": 31.14, "width": 35, "height": 38 } + }, + "left foot": { + "left-foot": { "x": 24.35, "y": 8.88, "rotation": 3.32, "width": 65, "height": 30 } + }, + "left lower leg": { + "left-lower-leg": { "x": 24.55, "y": -1.92, "rotation": 105.75, "width": 49, "height": 64 } + }, + "left upper leg": { + "left-upper-leg": { "x": 26.12, "y": -1.85, "rotation": 89.09, "width": 33, "height": 67 } + }, + "pelvis": { + "pelvis": { "x": -4.83, "y": 10.62, "width": 63, "height": 47 } + }, + "right foot": { + "right-foot": { "x": 19.02, "y": 8.47, "rotation": 1.52, "width": 67, "height": 30 } + }, + "right lower leg": { + "right-lower-leg": { "x": 23.28, "y": -2.59, "rotation": 111.83, "width": 51, "height": 64 } + }, + "right upper leg": { + "right-upper-leg": { "x": 23.03, "y": 0.25, "rotation": 101.13, "width": 44, "height": 70 } + }, + "torso": { + "torso": { "x": 44.57, "y": -7.08, "rotation": -94.95, "width": 68, "height": 92 } + }, + "neck": { + "neck": { "x": 9.42, "y": -3.66, "rotation": -100.15, "width": 34, "height": 28 } + }, + "head": { + "head": { "x": 53.94, "y": -5.75, "rotation": -86.9, "width": 121, "height": 132 } + }, + "eyes": { + "eyes": { "x": 28.94, "y": -32.92, "rotation": -86.9, "width": 34, "height": 27 }, + "eyes-closed": { "x": 28.77, "y": -32.86, "rotation": -86.9, "width": 34, "height": 27 } + }, + "right shoulder": { + "right-shoulder": { "x": 25.86, "y": 0.03, "rotation": 134.44, "width": 52, "height": 51 } + }, + "right arm": { + "right-arm": { "x": 18.34, "y": -2.64, "rotation": 94.32, "width": 21, "height": 45 } + }, + "right hand": { + "right-hand": { "x": 6.82, "y": 1.25, "rotation": 91.96, "width": 32, "height": 32 } + } + } +} +} \ No newline at end of file diff --git a/spine-corona/data/spineboy-walk.json b/spine-corona/data/spineboy-walk.json new file mode 100644 index 000000000..b40e53a95 --- /dev/null +++ b/spine-corona/data/spineboy-walk.json @@ -0,0 +1,278 @@ +{ +"bones": { + "left upper leg": { + "rotate": [ + { "time": 0, "angle": -26.55 }, + { "time": 0.1333, "angle": -8.78 }, + { "time": 0.2666, "angle": 9.51 }, + { "time": 0.4, "angle": 30.74 }, + { "time": 0.5333, "angle": 25.33 }, + { "time": 0.6666, "angle": 26.11 }, + { "time": 0.8, "angle": -7.7 }, + { "time": 0.9333, "angle": -21.19 }, + { "time": 1.0666, "angle": -26.55 } + ], + "translate": [ + { "time": 0, "x": -3, "y": -2.25 }, + { "time": 0.4, "x": -2.18, "y": -2.25 }, + { "time": 1.0666, "x": -3, "y": -2.25 } + ] + }, + "right upper leg": { + "rotate": [ + { "time": 0, "angle": 42.45 }, + { "time": 0.1333, "angle": 52.1 }, + { "time": 0.2666, "angle": 5.96 }, + { "time": 0.5333, "angle": -16.93 }, + { "time": 0.6666, "angle": 1.89 }, + { + "time": 0.8, + "angle": 28.06, + "curve": [ 0.462, 0.11, 1, 1 ] + }, + { + "time": 0.9333, + "angle": 58.68, + "curve": [ 0.5, 0.02, 1, 1 ] + }, + { "time": 1.0666, "angle": 42.45 } + ], + "translate": [ + { "time": 0, "x": 8.11, "y": -2.36 }, + { "time": 0.1333, "x": 10.03, "y": -2.56 }, + { "time": 0.4, "x": 2.76, "y": -2.97 }, + { "time": 0.5333, "x": 2.76, "y": -2.81 }, + { "time": 0.9333, "x": 8.67, "y": -2.54 }, + { "time": 1.0666, "x": 8.11, "y": -2.36 } + ] + }, + "left lower leg": { + "rotate": [ + { "time": 0, "angle": -10.21 }, + { "time": 0.1333, "angle": -55.64 }, + { "time": 0.2666, "angle": -68.12 }, + { "time": 0.5333, "angle": 5.11 }, + { "time": 0.6666, "angle": -28.29 }, + { "time": 0.8, "angle": 4.08 }, + { "time": 0.9333, "angle": 3.53 }, + { "time": 1.0666, "angle": -10.21 } + ] + }, + "left foot": { + "rotate": [ + { "time": 0, "angle": -3.69 }, + { "time": 0.1333, "angle": -10.42 }, + { "time": 0.2666, "angle": -17.14 }, + { "time": 0.4, "angle": -2.83 }, + { "time": 0.5333, "angle": -3.87 }, + { "time": 0.6666, "angle": 2.78 }, + { "time": 0.8, "angle": 1.68 }, + { "time": 0.9333, "angle": -8.54 }, + { "time": 1.0666, "angle": -3.69 } + ] + }, + "right shoulder": { + "rotate": [ + { + "time": 0, + "angle": 20.89, + "curve": [ 0.264, 0, 0.75, 1 ] + }, + { + "time": 0.1333, + "angle": 3.72, + "curve": [ 0.272, 0, 0.841, 1 ] + }, + { "time": 0.6666, "angle": -278.28 }, + { "time": 1.0666, "angle": 20.89 } + ], + "translate": [ + { "time": 0, "x": -7.84, "y": 7.19 }, + { "time": 0.1333, "x": -6.36, "y": 6.42 }, + { "time": 0.6666, "x": -11.07, "y": 5.25 }, + { "time": 1.0666, "x": -7.84, "y": 7.19 } + ] + }, + "right arm": { + "rotate": [ + { + "time": 0, + "angle": -4.02, + "curve": [ 0.267, 0, 0.804, 0.99 ] + }, + { + "time": 0.1333, + "angle": -13.99, + "curve": [ 0.341, 0, 1, 1 ] + }, + { + "time": 0.6666, + "angle": 36.54, + "curve": [ 0.307, 0, 0.787, 0.99 ] + }, + { "time": 1.0666, "angle": -4.02 } + ] + }, + "right hand": { + "rotate": [ + { "time": 0, "angle": 22.92 }, + { "time": 0.4, "angle": -8.97 }, + { "time": 0.6666, "angle": 0.51 }, + { "time": 1.0666, "angle": 22.92 } + ] + }, + "left shoulder": { + "rotate": [ + { "time": 0, "angle": -1.47 }, + { "time": 0.1333, "angle": 13.6 }, + { "time": 0.6666, "angle": 280.74 }, + { "time": 1.0666, "angle": -1.47 } + ], + "translate": [ + { "time": 0, "x": -1.76, "y": 0.56 }, + { "time": 0.6666, "x": -2.47, "y": 8.14 }, + { "time": 1.0666, "x": -1.76, "y": 0.56 } + ] + }, + "left hand": { + "rotate": [ + { + "time": 0, + "angle": 11.58, + "curve": [ 0.169, 0.37, 0.632, 1.55 ] + }, + { + "time": 0.1333, + "angle": 28.13, + "curve": [ 0.692, 0, 0.692, 0.99 ] + }, + { + "time": 0.6666, + "angle": -27.42, + "curve": [ 0.117, 0.41, 0.738, 1.76 ] + }, + { "time": 0.8, "angle": -36.32 }, + { "time": 1.0666, "angle": 11.58 } + ] + }, + "left arm": { + "rotate": [ + { "time": 0, "angle": -8.27 }, + { "time": 0.1333, "angle": 18.43 }, + { "time": 0.6666, "angle": 0.88 }, + { "time": 1.0666, "angle": -8.27 } + ] + }, + "torso": { + "rotate": [ + { "time": 0, "angle": -10.28 }, + { + "time": 0.1333, + "angle": -15.38, + "curve": [ 0.545, 0, 1, 1 ] + }, + { + "time": 0.4, + "angle": -9.78, + "curve": [ 0.58, 0.17, 1, 1 ] + }, + { "time": 0.6666, "angle": -15.75 }, + { "time": 0.9333, "angle": -7.06 }, + { "time": 1.0666, "angle": -10.28 } + ], + "translate": [ + { "time": 0, "x": -3.67, "y": 1.68 }, + { "time": 0.1333, "x": -3.67, "y": 0.68 }, + { "time": 0.4, "x": -3.67, "y": 1.97 }, + { "time": 0.6666, "x": -3.67, "y": -0.14 }, + { "time": 1.0666, "x": -3.67, "y": 1.68 } + ] + }, + "right foot": { + "rotate": [ + { "time": 0, "angle": -5.25 }, + { "time": 0.2666, "angle": -4.08 }, + { "time": 0.4, "angle": -6.45 }, + { "time": 0.5333, "angle": -5.39 }, + { "time": 0.8, "angle": -11.68 }, + { "time": 0.9333, "angle": 0.46 }, + { "time": 1.0666, "angle": -5.25 } + ] + }, + "right lower leg": { + "rotate": [ + { "time": 0, "angle": -3.39 }, + { "time": 0.1333, "angle": -45.53 }, + { "time": 0.2666, "angle": -2.59 }, + { "time": 0.5333, "angle": -19.53 }, + { "time": 0.6666, "angle": -64.8 }, + { + "time": 0.8, + "angle": -82.56, + "curve": [ 0.557, 0.18, 1, 1 ] + }, + { "time": 1.0666, "angle": -3.39 } + ] + }, + "hip": { + "rotate": [ + { "time": 0, "angle": 0, "curve": "stepped" }, + { "time": 1.0666, "angle": 0 } + ], + "translate": [ + { "time": 0, "x": 0, "y": 0 }, + { + "time": 0.1333, + "x": 0, + "y": -7.61, + "curve": [ 0.272, 0.86, 1, 1 ] + }, + { "time": 0.4, "x": 0, "y": 8.7 }, + { "time": 0.5333, "x": 0, "y": -0.41 }, + { + "time": 0.6666, + "x": 0, + "y": -7.05, + "curve": [ 0.235, 0.89, 1, 1 ] + }, + { "time": 0.8, "x": 0, "y": 2.92 }, + { "time": 0.9333, "x": 0, "y": 6.78 }, + { "time": 1.0666, "x": 0, "y": 0 } + ] + }, + "neck": { + "rotate": [ + { "time": 0, "angle": 3.6 }, + { "time": 0.1333, "angle": 17.49 }, + { "time": 0.2666, "angle": 6.1 }, + { "time": 0.4, "angle": 3.45 }, + { "time": 0.5333, "angle": 5.17 }, + { "time": 0.6666, "angle": 18.36 }, + { "time": 0.8, "angle": 6.09 }, + { "time": 0.9333, "angle": 2.28 }, + { "time": 1.0666, "angle": 3.6 } + ] + }, + "head": { + "rotate": [ + { + "time": 0, + "angle": 3.6, + "curve": [ 0, 0, 0.704, 1.61 ] + }, + { "time": 0.1666, "angle": -0.2 }, + { "time": 0.2666, "angle": 6.1 }, + { "time": 0.4, "angle": 3.45 }, + { + "time": 0.5333, + "angle": 5.17, + "curve": [ 0, 0, 0.704, 1.61 ] + }, + { "time": 0.7, "angle": 1.1 }, + { "time": 0.8, "angle": 6.09 }, + { "time": 0.9333, "angle": 2.28 }, + { "time": 1.0666, "angle": 3.6 } + ] + } +} +} \ No newline at end of file diff --git a/spine-corona/data/torso.png b/spine-corona/data/torso.png new file mode 100644 index 000000000..d5a318e72 Binary files /dev/null and b/spine-corona/data/torso.png differ diff --git a/spine-corona/main.lua b/spine-corona/main.lua new file mode 100644 index 000000000..763e72da4 --- /dev/null +++ b/spine-corona/main.lua @@ -0,0 +1,36 @@ + +local spine = require "spine.spine" + +-- Optional attachment resolver customizes where images are loaded. Eg, could use an image sheet. +local attachmentResolver = spine.AttachmentResolver.new() +function attachmentResolver:createImage (attachment) + return display.newImage("data/" .. attachment.name .. ".png") +end + +local json = spine.SkeletonJson.new(attachmentResolver) +json.scale = 1 +local skeletonData = json:readSkeletonDataFile("data/spineboy-skeleton.json") +local walkAnimation = json:readAnimationFile(skeletonData, "data/spineboy-walk.json") + +-- Optional second parameter can be the group for the Skeleton to use. Eg, could be an image group. +local skeleton = spine.Skeleton.new(skeletonData) +skeleton.x = 150 +skeleton.y = 325 +skeleton.flipX = false +skeleton.flipY = false +skeleton.debug = true -- Omit or set to false to not draw debug lines on top of the images. +skeleton:setToBindPose() + +local lastTime = 0 +local animationTime = 0 +Runtime:addEventListener("enterFrame", function (event) + -- Compute time in seconds since last frame. + local currentTime = event.time / 1000 + local delta = currentTime - lastTime + lastTime = currentTime + + -- Accumulate time and pose skeleton using animation. + animationTime = animationTime + delta + walkAnimation:apply(skeleton, animationTime, true) + skeleton:updateWorldTransform() +end) diff --git a/spine-corona/spine/Animation.lua b/spine-corona/spine/Animation.lua new file mode 100644 index 000000000..7897931da --- /dev/null +++ b/spine-corona/spine/Animation.lua @@ -0,0 +1,397 @@ + +local utils = require "spine.utils" + +local Animation = {} +function Animation.new (timelines, duration) + if not timelines then error("timelines cannot be nil", 2) end + + local self = { + timelines = timelines, + duration = duration + } + + function self:apply (skeleton, time, loop) + if not skeleton then error("skeleton cannot be nil.", 2) end + + if loop and duration then time = time % duration end + + for i,timeline in ipairs(self.timelines) do + timeline:apply(skeleton, time, 1) + end + end + + function self:mix (skeleton, time, loop, alpha) + if not skeleton then error("skeleton cannot be nil.", 2) end + + if loop and duration then time = time % duration end + + for i,timeline in ipairs(self.timelines) do + timeline:apply(skeleton, time, alpha) + end + end + + return self +end + +local function binarySearch (values, target, step) + local low = 0 + local high = math.floor((#values + 1) / step - 2) + if high == 0 then return step end + local current = math.floor(high / 2) + while true do + if values[(current + 1) * step] <= target then + low = current + 1 + else + high = current + end + if low == high then return (low + 1) * step end + current = math.floor((low + high) / 2) + end +end + +local function linearSearch (values, target, step) + for i = 0, #values, step do + if (values[i] > target) then return i end + end + return -1 +end + +Animation.CurveTimeline = {} +function Animation.CurveTimeline.new () + local LINEAR = 0 + local STEPPED = -1 + local BEZIER_SEGMENTS = 10 + + local self = { + curves = {} + } + + function self:setLinear (keyframeIndex) + self.curves[keyframeIndex * 6] = LINEAR + end + + function self:setStepped (keyframeIndex) + self.curves[keyframeIndex * 6] = STEPPED + end + + function self:setCurve (keyframeIndex, cx1, cy1, cx2, cy2) + local subdiv_step = 1 / BEZIER_SEGMENTS + local subdiv_step2 = subdiv_step * subdiv_step + local subdiv_step3 = subdiv_step2 * subdiv_step + local pre1 = 3 * subdiv_step + local pre2 = 3 * subdiv_step2 + local pre4 = 6 * subdiv_step2 + local pre5 = 6 * subdiv_step3 + local tmp1x = -cx1 * 2 + cx2 + local tmp1y = -cy1 * 2 + cy2 + local tmp2x = (cx1 - cx2) * 3 + 1 + local tmp2y = (cy1 - cy2) * 3 + 1 + local i = keyframeIndex * 6 + local curves = self.curves + curves[i] = cx1 * pre1 + tmp1x * pre2 + tmp2x * subdiv_step3 + curves[i + 1] = cy1 * pre1 + tmp1y * pre2 + tmp2y * subdiv_step3 + curves[i + 2] = tmp1x * pre4 + tmp2x * pre5 + curves[i + 3] = tmp1y * pre4 + tmp2y * pre5 + curves[i + 4] = tmp2x * pre5 + curves[i + 5] = tmp2y * pre5 + end + + function self:getCurvePercent (keyframeIndex, percent) + local curveIndex = keyframeIndex * 6 + local curves = self.curves + local dfx = curves[curveIndex] + if not dfx then return percent end -- linear + if dfx == STEPPED then return 0 end + local dfy = curves[curveIndex + 1] + local ddfx = curves[curveIndex + 2] + local ddfy = curves[curveIndex + 3] + local dddfx = curves[curveIndex + 4] + local dddfy = curves[curveIndex + 5] + local x = dfx + local y = dfy + local i = BEZIER_SEGMENTS - 2 + while true do + if x >= percent then + local lastX = x - dfx + local lastY = y - dfy + return lastY + (y - lastY) * (percent - lastX) / (x - lastX) + end + if i == 0 then break end + i = i - 1 + dfx = dfx + ddfx + dfy = dfy + ddfy + ddfx = ddfx + dddfx + ddfy = ddfy + dddfy + x = x + dfx + y = y + dfy + end + return y + (1 - y) * (percent - x) / (1 - x) -- Last point is 1,1. + end + + return self +end + +Animation.RotateTimeline = {} +function Animation.RotateTimeline.new () + local LAST_FRAME_TIME = -2 + local FRAME_VALUE = 1 + + local self = Animation.CurveTimeline.new() + self.frames = {} + + function self:getDuration () + return self.frames[#self.frames - 1] + end + + function self:getKeyframeCount () + return (#self.frames + 1) / 2 + end + + function self:setKeyframe (keyframeIndex, time, value) + keyframeIndex = keyframeIndex * 2 + self.frames[keyframeIndex] = time + self.frames[keyframeIndex + 1] = value + end + + function self:apply (skeleton, time, alpha) + local frames = self.frames + if time < frames[0] then return end -- Time is before first frame. + + local bone = skeleton.bones[self.boneIndex] + + if time >= frames[#frames - 1] then -- Time is after last frame. + local amount = bone.data.rotation + frames[#frames] - bone.rotation + while amount > 180 do + amount = amount - 360 + end + while amount < -180 do + amount = amount + 360 + end + bone.rotation = bone.rotation + amount * alpha + return + end + + -- Interpolate between the last frame and the current frame. + local frameIndex = binarySearch(frames, time, 2) + local lastFrameValue = frames[frameIndex - 1] + local frameTime = frames[frameIndex] + local percent = 1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime) + if percent < 0 then percent = 0 elseif percent > 1 then percent = 1 end + percent = self:getCurvePercent(frameIndex / 2 - 1, percent) + + local amount = frames[frameIndex + FRAME_VALUE] - lastFrameValue + while amount > 180 do + amount = amount - 360 + end + while amount < -180 do + amount = amount + 360 + end + amount = bone.data.rotation + (lastFrameValue + amount * percent) - bone.rotation + while amount > 180 do + amount = amount - 360 + end + while amount < -180 do + amount = amount + 360 + end + bone.rotation = bone.rotation + amount * alpha + end + + return self +end + +Animation.TranslateTimeline = {} +function Animation.TranslateTimeline.new () + local LAST_FRAME_TIME = -3 + local FRAME_X = 1 + local FRAME_Y = 2 + + local self = Animation.CurveTimeline.new() + self.frames = {} + + function self:getDuration () + return self.frames[#self.frames - 2] + end + + function self:getKeyframeCount () + return (#self.frames + 1) / 3 + end + + function self:setKeyframe (keyframeIndex, time, x, y) + keyframeIndex = keyframeIndex * 3 + self.frames[keyframeIndex] = time + self.frames[keyframeIndex + 1] = x + self.frames[keyframeIndex + 2] = y + end + + function self:apply (skeleton, time, alpha) + local frames = self.frames + if time < frames[0] then return end -- Time is before first frame. + + local bone = skeleton.bones[self.boneIndex] + + if time >= frames[#frames - 2] then -- Time is after last frame. + bone.x = bone.x + (bone.data.x + frames[#frames - 1] - bone.x) * alpha + bone.y = bone.y + (bone.data.y + frames[#frames] - bone.y) * alpha + return + end + + -- Interpolate between the last frame and the current frame. + local frameIndex = binarySearch(frames, time, 3) + local lastFrameX = frames[frameIndex - 2] + local lastFrameY = frames[frameIndex - 1] + local frameTime = frames[frameIndex] + local percent = 1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime) + if percent < 0 then percent = 0 elseif percent > 1 then percent = 1 end + percent = self:getCurvePercent(frameIndex / 3 - 1, percent) + + bone.x = bone.x + (bone.data.x + lastFrameX + (frames[frameIndex + FRAME_X] - lastFrameX) * percent - bone.x) * alpha + bone.y = bone.y + (bone.data.y + lastFrameY + (frames[frameIndex + FRAME_Y] - lastFrameY) * percent - bone.y) * alpha + end + + return self +end + +Animation.ScaleTimeline = {} +function Animation.ScaleTimeline.new () + local LAST_FRAME_TIME = -3 + local FRAME_X = 1 + local FRAME_Y = 2 + + local self = Animation.TranslateTimeline.new() + + function self:apply (skeleton, time, alpha) + local frames = self.frames + if time < frames[0] then return end -- Time is before first frame. + + local bone = skeleton.bones[self.boneIndex] + + if time >= frames[#frames - 2] then -- Time is after last frame. + bone.scaleX = bone.scaleX + (bone.data.scaleX - 1 + frames[#frames - 1] - bone.scaleX) * alpha + bone.scaleY = bone.scaleY + (bone.data.scaleY - 1 + frames[#frames] - bone.scaleY) * alpha + return + end + + -- Interpolate between the last frame and the current frame. + local frameIndex = binarySearch(frames, time, 3) + local lastFrameX = frames[frameIndex - 2] + local lastFrameY = frames[frameIndex - 1] + local frameTime = frames[frameIndex] + local percent = 1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime) + if percent < 0 then percent = 0 elseif percent > 1 then percent = 1 end + percent = self:getCurvePercent(frameIndex / 3 - 1, percent) + + bone.scaleX = bone.scaleX + (bone.data.scaleX - 1 + lastFrameX + (frames[frameIndex + FRAME_X] - lastFrameX) * percent - bone.scaleX) * alpha + bone.scaleY = bone.scaleY + (bone.data.scaleY - 1 + lastFrameY + (frames[frameIndex + FRAME_Y] - lastFrameY) * percent - bone.scaleY) * alpha + end + + return self +end + +Animation.ColorTimeline = {} +function Animation.ColorTimeline.new () + local LAST_FRAME_TIME = -5 + local FRAME_R = 1 + local FRAME_G = 2 + local FRAME_B = 3 + local FRAME_A = 4 + + local self = Animation.CurveTimeline.new() + self.frames = {} + + function self:getDuration () + return self.frames[#self.frames - 4] + end + + function self:getKeyframeCount () + return (#self.frames + 1) / 5 + end + + function self:setKeyframe (keyframeIndex, time, r, g, b, a) + keyframeIndex = keyframeIndex * 5 + self.frames[keyframeIndex] = time + self.frames[keyframeIndex + 1] = r + self.frames[keyframeIndex + 2] = g + self.frames[keyframeIndex + 3] = b + self.frames[keyframeIndex + 4] = a + end + + function self:apply (skeleton, time, alpha) + local frames = self.frames + if time < frames[0] then return end -- Time is before first frame. + + local slot = skeleton.slots[self.slotIndex] + + if time >= frames[#frames - 4] then -- Time is after last frame. + local r = frames[#frames - 3] + local g = frames[#frames - 2] + local b = frames[#frames - 1] + local a = frames[#frames] + slot:setColor(r, g, b, a) + return + end + + -- Interpolate between the last frame and the current frame. + local frameIndex = binarySearch(frames, time, 5) + local lastFrameR = frames[frameIndex - 4] + local lastFrameG = frames[frameIndex - 3] + local lastFrameB = frames[frameIndex - 2] + local lastFrameA = frames[frameIndex - 1] + local frameTime = frames[frameIndex] + local percent = 1 - (time - frameTime) / (frames[frameIndex + LAST_FRAME_TIME] - frameTime) + if percent < 0 then percent = 0 elseif percent > 255 then percent = 255 end + percent = self:getCurvePercent(frameIndex / 5 - 1, percent) + + local r = lastFrameR + (frames[frameIndex + FRAME_R] - lastFrameR) * percent + local g = lastFrameG + (frames[frameIndex + FRAME_G] - lastFrameG) * percent + local b = lastFrameB + (frames[frameIndex + FRAME_B] - lastFrameB) * percent + local a = lastFrameA + (frames[frameIndex + FRAME_A] - lastFrameA) * percent + if alpha < 1 then + slot:setColor(slot.r + (r - color.r) * alpha, slot.g + (g - color.g) * alpha, slot.b + (b - color.b) * alpha, slot.a + (a - color.a) * alpha) + else + slot:setColor(r, g, b, a) + end + end + + return self +end + +Animation.AttachmentTimeline = {} +function Animation.AttachmentTimeline.new () + local self = Animation.CurveTimeline.new() + self.frames = {} + self.attachmentNames = {} + + function self:getDuration () + return self.frames[#self.frames] + end + + function self:getKeyframeCount () + return #self.frames + 1 + end + + function self:setKeyframe (keyframeIndex, time, attachmentName) + self.frames[keyframeIndex] = time + self.attachmentNames[keyframeIndex] = attachmentName + end + + function self:apply (skeleton, time, alpha) + local frames = self.frames + if time < frames[0] then return end -- Time is before first frame. + + local frameIndex + if time >= frames[#frames] then -- Time is after last frame. + frameIndex = #frames + else + frameIndex = binarySearch(frames, time, 1) - 1 + end + + local attachmentName = self.attachmentNames[frameIndex] + local attachment + if attachmentName then attachment = skeleton:getAttachment(self.slotName, attachmentName) end + skeleton:findSlot(self.slotName):setAttachment(attachment) + end + + return self +end + +return Animation diff --git a/spine-corona/spine/AttachmentResolver.lua b/spine-corona/spine/AttachmentResolver.lua new file mode 100644 index 000000000..73c953544 --- /dev/null +++ b/spine-corona/spine/AttachmentResolver.lua @@ -0,0 +1,30 @@ + +local AttachmentResolver = { + failed = {} +} +function AttachmentResolver.new () + local self = { + images = {} + } + + function self:resolve (skeleton, attachment) + local image = self:createImage(attachment) + if image then + image:setReferencePoint(display.CenterReferencePoint); + image.width = attachment.width + image.height = attachment.height + else + print("Error creating image: " .. attachment.name) + image = AttachmentResolver.failed + end + skeleton.images[attachment] = image + return image + end + + function self:createImage (attachment) + return display.newImage(attachment.name .. ".png") + end + + return self +end +return AttachmentResolver diff --git a/spine-corona/spine/Bone.lua b/spine-corona/spine/Bone.lua new file mode 100644 index 000000000..f22591324 --- /dev/null +++ b/spine-corona/spine/Bone.lua @@ -0,0 +1,54 @@ + +local Bone = {} +function Bone.new (data, parent) + if not data then error("data cannot be nil", 2) end + + local self = { + data = data, + parent = parent + } + + function self:updateWorldTransform (flipX, flipY) + local parent = self.parent + if parent then + self.worldX = self.x * parent.m00 + self.y * parent.m01 + parent.worldX + self.worldY = self.x * parent.m10 + self.y * parent.m11 + parent.worldY + self.worldScaleX = parent.worldScaleX * self.scaleX + self.worldScaleY = parent.worldScaleY * self.scaleY + self.worldRotation = parent.worldRotation + self.rotation + else + self.worldX = self.x + self.worldY = self.y + self.worldScaleX = self.scaleX + self.worldScaleY = self.scaleY + self.worldRotation = self.rotation + end + local radians = math.rad(self.worldRotation) + local cos = math.cos(radians) + local sin = math.sin(radians) + self.m00 = cos * self.worldScaleX + self.m10 = sin * self.worldScaleX + self.m01 = -sin * self.worldScaleY + self.m11 = cos * self.worldScaleY + if flipX then + self.m00 = -self.m00 + self.m01 = -self.m01 + end + if flipY then + self.m10 = -self.m10 + self.m11 = -self.m11 + end + end + + function self:setToBindPose () + local data = self.data + self.x = data.x + self.y = data.y + self.rotation = data.rotation + self.scaleX = data.scaleX + self.scaleY = data.scaleY + end + + return self +end +return Bone diff --git a/spine-corona/spine/BoneData.lua b/spine-corona/spine/BoneData.lua new file mode 100644 index 000000000..9b3edc330 --- /dev/null +++ b/spine-corona/spine/BoneData.lua @@ -0,0 +1,13 @@ + +local BoneData = {} +function BoneData.new (name, parent) + if not name then error("name cannot be nil", 2) end + + local self = { + name = name, + parent = parent + } + + return self +end +return BoneData diff --git a/spine-corona/spine/RegionAttachment.lua b/spine-corona/spine/RegionAttachment.lua new file mode 100644 index 000000000..20c8d6599 --- /dev/null +++ b/spine-corona/spine/RegionAttachment.lua @@ -0,0 +1,12 @@ + +local RegionAttachment = {} +function RegionAttachment.new (name) + if not name then error("name cannot be nil", 2) end + + local self = { + name = name + } + + return self +end +return RegionAttachment diff --git a/spine-corona/spine/Skeleton.lua b/spine-corona/spine/Skeleton.lua new file mode 100644 index 000000000..0dbe95ec4 --- /dev/null +++ b/spine-corona/spine/Skeleton.lua @@ -0,0 +1,172 @@ + +local utils = require "spine.utils" +local Bone = require "spine.Bone" +local Slot = require "spine.Slot" +local AttachmentResolver = require "spine.AttachmentResolver" + +local Skeleton = {} +function Skeleton.new (skeletonData, group) + if not skeletonData then error("skeletonData cannot be nil", 2) end + + local self = group or display.newGroup() + self.data = skeletonData + self.bones = {} + self.slots = {} + self.drawOrder = {} + self.images = {} + + for i,boneData in ipairs(skeletonData.bones) do + local parent + if boneData.parent then parent = self.bones[utils.indexOf(skeletonData.bones, boneData.parent)] end + table.insert(self.bones, Bone.new(boneData, parent)) + end + + for i,slotData in ipairs(skeletonData.slots) do + local bone = self.bones[utils.indexOf(skeletonData.bones, slotData.boneData)] + local slot = Slot.new(slotData, self, bone) + table.insert(self.slots, slot) + table.insert(self.drawOrder, slot) + end + + function self:updateWorldTransform () + for i,bone in ipairs(self.bones) do + bone:updateWorldTransform(self.flipX, self.flipY) + end + + for i,slot in ipairs(self.drawOrder) do + if slot.attachment then + local image = self.images[slot.attachment] + if not image then image = self.data.attachmentResolver:resolve(self, slot.attachment) end + if image ~= AttachmentResolver.failed then + image.x = slot.bone.worldX + slot.attachment.x * slot.bone.m00 + slot.attachment.y * slot.bone.m01 + image.y = -(slot.bone.worldY + slot.attachment.x * slot.bone.m10 + slot.attachment.y * slot.bone.m11) + image.rotation = -(slot.bone.worldRotation + slot.attachment.rotation) + image.xScale = slot.bone.worldScaleX + slot.attachment.scaleX - 1 + image.yScale = slot.bone.worldScaleY + slot.attachment.scaleY - 1 + if self.flipX then + image.xScale = -image.xScale + image.rotation = -image.rotation + end + if self.flipY then + image.yScale = -image.yScale + image.rotation = -image.rotation + end + image:setFillColor(slot.r, slot.g, slot.b, slot.a) + self:insert(image) + end + end + end + + if self.debug then + for i,bone in ipairs(self.bones) do + if not bone.line then bone.line = display.newLine(0, 0, bone.data.length, 0) end + bone.line.x = bone.worldX + bone.line.y = -bone.worldY + bone.line.rotation = -bone.worldRotation + if self.flipX then + bone.line.xScale = -1 + bone.line.rotation = -bone.line.rotation + else + bone.line.xScale = 1 + end + if self.flipY then + bone.line.yScale = -1 + bone.line.rotation = -bone.line.rotation + else + bone.line.yScale = 1 + end + bone.line:setColor(255, 0, 0) + self:insert(bone.line) + + if not bone.circle then bone.circle = display.newCircle(0, 0, 3) end + bone.circle.x = bone.worldX + bone.circle.y = -bone.worldY + bone.circle:setFillColor(0, 255, 0) + self:insert(bone.circle) + end + end + end + + function self:setToBindPose () + self:setBonesToBindPose() + self:setSlotsToBindPose() + end + + function self:setBonesToBindPose () + for i,bone in ipairs(self.bones) do + bone:setToBindPose() + end + end + + function self:setSlotsToBindPose () + for i,slot in ipairs(self.slots) do + slot:setToBindPose() + end + end + + function self:getRootBone () + return self.bones[1] + end + + function self:findSlot (slotName) + if not slotName then error("slotName cannot be nil.", 2) end + for i,slot in ipairs(self.slots) do + if slot.data.name == slotName then return slot end + end + return nil + end + + function self:setSkin (skinName) + local newSkin + if skinName then + newSkin = self.data:findSkin(skinName) + if not newSkin then error("Skin not found: " .. skinName, 2) end + if self.skin then + -- Attach all attachments from the new skin if the corresponding attachment from the old skin is currently attached. + for k,v in self.skin.attachments do + local attachment = v[3] + local slotIndex = v[1] + local slot = self.slots[slotIndex] + if slot.attachment == attachment then + local name = v[2] + local newAttachment = newSkin:getAttachment(slotIndex, name) + if newAttachment then slot:setAttachment(newAttachment) end + end + end + end + end + self.skin = newSkin + end + + function self:getAttachment (slotName, attachmentName) + if not slotName then error("slotName cannot be nil.", 2) end + if not attachmentName then error("attachmentName cannot be nil.", 2) end + local slotIndex = self.data:findSlotIndex(slotName) + if slotIndex == -1 then error("Slot not found: " .. slotName, 2) end + if self.data.defaultSkin then + local attachment = self.data.defaultSkin:getAttachment(slotIndex, attachmentName) + if attachment then return attachment end + end + if self.skin then return self.skin:getAttachment(slotIndex, attachmentName) end + return nil + end + + function self:setAttachment (slotName, attachmentName) + if not slotName then error("slotName cannot be nil.", 2) end + if not attachmentName then error("attachmentName cannot be nil.", 2) end + for i,slot in ipairs(self.slots) do + if slot.data.name == slotName then + slot:setAttachment(self:getAttachment(slotName, attachmentName)) + return + end + end + error("Slot not found: " + slotName, 2) + end + + function self:update (delta) + self.time = self.time + delta + end + + return self +end +return Skeleton diff --git a/spine-corona/spine/SkeletonData.lua b/spine-corona/spine/SkeletonData.lua new file mode 100644 index 000000000..1eb3001a5 --- /dev/null +++ b/spine-corona/spine/SkeletonData.lua @@ -0,0 +1,55 @@ + +local SkeletonData = {} +function SkeletonData.new (attachmentResolver) + if not attachmentResolver then error("attachmentResolver cannot be nil", 2) end + + local self = { + attachmentResolver = attachmentResolver, + bones = {}, + slots = {}, + skins = {} + } + + function self:findBone (boneName) + if not boneName then error("boneName cannot be nil.", 2) end + for i,bone in ipairs(self.bones) do + if bone.name == boneName then return bone end + end + return nil + end + + function self:findBoneIndex (boneName) + if not boneName then error("boneName cannot be nil.", 2) end + for i,bone in ipairs(self.bones) do + if bone.name == boneName then return i end + end + return -1 + end + + function self:findSlot (slotName) + if not slotName then error("slotName cannot be nil.", 2) end + for i,slot in ipairs(self.slots) do + if slot.name == slotName then return slot end + end + return nil + end + + function self:findSlotIndex (slotName) + if not slotName then error("slotName cannot be nil.", 2) end + for i,slot in ipairs(self.slots) do + if slot.name == slotName then return i end + end + return -1 + end + + function self:findSkin (skinName) + if not skinName then error("skinName cannot be nil.", 2) end + for i,skin in ipairs(self.skins) do + if skin.name == skinName then return skin end + end + return nil + end + + return self +end +return SkeletonData diff --git a/spine-corona/spine/SkeletonJson.lua b/spine-corona/spine/SkeletonJson.lua new file mode 100644 index 000000000..a6a5cdb5f --- /dev/null +++ b/spine-corona/spine/SkeletonJson.lua @@ -0,0 +1,255 @@ + +local utils = require "spine.utils" +local SkeletonData = require "spine.SkeletonData" +local BoneData = require "spine.BoneData" +local SlotData = require "spine.SlotData" +local Skin = require "spine.Skin" +local RegionAttachment = require "spine.RegionAttachment" +local AttachmentResolver = require "spine.AttachmentResolver" +local Animation = require "spine.Animation" +local json = require "json" + +local TIMELINE_SCALE = "scale" +local TIMELINE_ROTATE = "rotate" +local TIMELINE_TRANSLATE = "translate" +local TIMELINE_ATTACHMENT = "attachment" +local TIMELINE_COLOR = "color" + +local ATTACHMENT_REGION = "region" +local ATTACHMENT_ANIMATED_REGION = "animatedRegion" + +local SkeletonJson = {} +function SkeletonJson.new (attachmentResolver) + if not attachmentResolver then attachmentResolver = AttachmentResolver.new() end + + local self = { + attachmentResolver = attachmentResolver, + scale = 1 + } + + function self:readSkeletonDataFile (fileName, base) + return self:readSkeletonData(utils.readFile(fileName, base)) + end + + local readAttachment + + function self:readSkeletonData (jsonText) + local skeletonData = SkeletonData.new(self.attachmentResolver) + + local root = json.decode(jsonText) + if not root then error("Invalid JSON: " .. jsonText, 2) end + + -- Bones. + for i,boneMap in ipairs(root["bones"]) do + local boneName = boneMap["name"] + local parent = nil + local parentName = boneMap["parent"] + if parentName then + parent = skeletonData:findBone(parentName) + if not parent then error("Parent bone not found: " .. parentName) end + end + local boneData = BoneData.new(boneName, parent) + boneData.length = (boneMap["length"] or 0) * self.scale + boneData.x = (boneMap["x"] or 0) * self.scale + boneData.y = (boneMap["y"] or 0) * self.scale + boneData.rotation = (boneMap["rotation"] or 0) + boneData.scaleX = (boneMap["scaleX"] or 1) + boneData.scaleY = (boneMap["scaleY"] or 1) + table.insert(skeletonData.bones, boneData) + end + + -- Slots. + if root["slots"] then + for i,slotMap in ipairs(root["slots"]) do + local slotName = slotMap["name"] + local boneName = slotMap["bone"] + local boneData = skeletonData:findBone(boneName) + if not boneData then error("Slot bone not found: " .. boneName) end + local slotData = SlotData.new(slotName, boneData) + + local color = slotMap["color"] + if color then + slotData:setColor( + tonumber(color:sub(1, 2), 16), + tonumber(color:sub(3, 4), 16), + tonumber(color:sub(5, 6), 16), + tonumber(color:sub(7, 8), 16) + ) + end + + slotData.attachmentName = slotMap["attachment"] + + table.insert(skeletonData.slots, slotData) + end + end + + -- Skins. + map = root["skins"] + if map then + for skinName,skinMap in pairs(map) do + local skin = Skin.new(skinName) + for slotName,slotMap in pairs(skinMap) do + local slotIndex = skeletonData:findSlotIndex(slotName) + for attachmentName,attachmentMap in pairs(slotMap) do + local attachment = readAttachment(attachmentName, attachmentMap, self.scale) + skin:addAttachment(slotIndex, attachmentName, attachment) + end + end + if skin.name == "default" then + skeletonData.defaultSkin = skin + else + table.insert(skeletonData.skins, skin) + end + end + end + + return skeletonData + end + + readAttachment = function (name, map, scale) + name = map["name"] or name + local attachment + local type = map["type"] or ATTACHMENT_REGION + if type == ATTACHMENT_REGION then + attachment = RegionAttachment.new(name) + else + error("Unknown attachment type: " .. type .. " (" + name + ")") + end + + attachment.x = (map["x"] or 0) * scale + attachment.y = (map["y"] or 0) * scale + attachment.scaleX = (map["scaleX"] or 1) + attachment.scaleY = (map["scaleY"] or 1) + attachment.rotation = (map["rotation"] or 0) + attachment.width = map["width"] * scale + attachment.height = map["height"] * scale + return attachment + end + + function self:readAnimationFile (skeletonData, fileName, base) + return self:readAnimation(skeletonData, utils.readFile(fileName, base)) + end + + local readCurve + + function self:readAnimation (skeletonData, jsonText) + local timelines = {} + local duration = 0 + + local root = json.decode(jsonText) + if not root then error("Invalid JSON: " .. jsonText, 2) end + + local bonesMap = root["bones"] + for boneName,propertyMap in pairs(bonesMap) do + local boneIndex = skeletonData:findBoneIndex(boneName) + if boneIndex == -1 then error("Bone not found: " .. boneName) end + + for timelineType,values in pairs(propertyMap) do + if timelineType == TIMELINE_ROTATE then + local timeline = Animation.RotateTimeline.new() + timeline.boneIndex = boneIndex + + local keyframeIndex = 0 + for i,valueMap in ipairs(values) do + local time = valueMap["time"] + timeline:setKeyframe(keyframeIndex, time, valueMap["angle"]) + readCurve(timeline, keyframeIndex, valueMap) + keyframeIndex = keyframeIndex + 1 + end + table.insert(timelines, timeline) + duration = math.max(duration, timeline:getDuration()) + + elseif timelineType == TIMELINE_TRANSLATE or timelineType == TIMELINE_SCALE then + local timeline + local timelineScale = 1 + if timelineType == TIMELINE_SCALE then + timeline = Animation.ScaleTimeline.new() + else + timeline = Animation.TranslateTimeline.new() + timelineScale = self.scale + end + timeline.boneIndex = boneIndex + + local keyframeIndex = 0 + for i,valueMap in ipairs(values) do + local time = valueMap["time"] + local x = (valueMap["x"] or 0) * timelineScale + local y = (valueMap["y"] or 0) * timelineScale + timeline:setKeyframe(keyframeIndex, time, x, y) + readCurve(timeline, keyframeIndex, valueMap) + keyframeIndex = keyframeIndex + 1 + end + table.insert(timelines, timeline) + duration = math.max(duration, timeline:getDuration()) + + else + error("Invalid timeline type for a bone: " .. timelineType .. " (" .. boneName .. ")") + end + end + end + + local slotsMap = root["slots"] + if slotsMap then + for slotName,propertyMap in pairs(slotsMap) do + local slotIndex = skeletonData:findSlotIndex(slotName) + + for timelineType,values in pairs(propertyMap) do + if timelineType == TIMELINE_COLOR then + local timeline = Animation.ColorTimeline.new() + timeline.slotIndex = slotIndex + + local keyframeIndex = 0 + for i,valueMap in ipairs(values) do + local time = valueMap["time"] + local color = valueMap["color"] + timeline:setKeyframe( + keyframeIndex, time, + tonumber(color:sub(1, 2), 16), + tonumber(color:sub(3, 4), 16), + tonumber(color:sub(5, 6), 16), + tonumber(color:sub(7, 8), 16) + ) + readCurve(timeline, keyframeIndex, valueMap) + keyframeIndex = keyframeIndex + 1 + end + table.insert(timelines, timeline) + duration = math.max(duration, timeline:getDuration()) + + elseif timelineType == TIMELINE_ATTACHMENT then + local timeline = Animation.AttachmentTimeline.new() + timeline.slotName = slotName + + local keyframeIndex = 0 + for i,valueMap in ipairs(values) do + local time = valueMap["time"] + local attachmentName = valueMap["name"] + if attachmentName == json.null then attachmentName = nil end + timeline:setKeyframe(keyframeIndex, time, attachmentName) + keyframeIndex = keyframeIndex + 1 + end + table.insert(timelines, timeline) + duration = math.max(duration, timeline:getDuration()) + + else + error("Invalid frame type for a slot: " .. timelineType .. " (" .. slotName .. ")") + end + end + end + end + + return Animation.new(timelines, duration) + end + + readCurve = function (timeline, keyframeIndex, valueMap) + local curve = valueMap["curve"] + if not curve then return end + if curve == "stepped" then + timeline:setStepped(keyframeIndex) + else + timeline:setCurve(keyframeIndex, curve[1], curve[2], curve[3], curve[4]) + end + end + + return self +end +return SkeletonJson diff --git a/spine-corona/spine/Skin.lua b/spine-corona/spine/Skin.lua new file mode 100644 index 000000000..098b601d9 --- /dev/null +++ b/spine-corona/spine/Skin.lua @@ -0,0 +1,39 @@ + +local Skin = {} +function Skin.new (name) + if not name then error("name cannot be nil", 2) end + + local self = { + name = name, + attachments = {} + } + + function self:addAttachment (slotIndex, name, attachment) + if not name then error("name cannot be nil.", 2) end + self.attachments[slotIndex .. ":" .. name] = { slotIndex, name, attachment } + end + + function self:getAttachment (slotIndex, name) + if not name then error("name cannot be nil.", 2) end + local values = self.attachments[slotIndex .. ":" .. name] + if not values then return nil end + return values[3] + end + + function self:findNamesForSlot (slotIndex) + local names = {} + for k,v in self.attachments do + if v[1] == slotIndex then table.insert(names, v[2]) end + end + end + + function self:findAttachmentsForSlot (slotIndex) + local attachments = {} + for k,v in self.attachments do + if v[1] == slotIndex then table.insert(attachments, v[3]) end + end + end + + return self +end +return Skin diff --git a/spine-corona/spine/Slot.lua b/spine-corona/spine/Slot.lua new file mode 100644 index 000000000..64c712cb5 --- /dev/null +++ b/spine-corona/spine/Slot.lua @@ -0,0 +1,54 @@ + +local utils = require "spine.utils" + +local Slot = {} +function Slot.new (slotData, skeleton, bone) + if not slotData then error("slotData cannot be nil", 2) end + if not skeleton then error("skeleton cannot be nil", 2) end + if not bone then error("bone cannot be nil", 2) end + + local self = { + data = slotData, + skeleton = skeleton, + bone = bone + } + + function self:setColor (r, g, b, a) + self.r = r + self.g = g + self.b = b + self.a = a + end + + function self:setAttachment (attachment) + if self.attachment and self.attachment ~= attachment and self.skeleton.images[self.attachment] then + self.skeleton.images[self.attachment]:removeSelf() + self.skeleton.images[self.attachment] = nil + end + self.attachment = attachment + self.attachmentTime = self.skeleton.time + end + + function self:setAttachmentTime (time) + self.attachmentTime = self.skeleton.time - time + end + + function self:getAttachmentTime () + return self.skeleton.time - self.attachmentTime + end + + function self:setToBindPose () + local data = self.data + + self:setColor(data.r, data.g, data.b, data.a) + + local attachment + if data.attachmentName then attachment = self.skeleton:getAttachment(data.name, data.attachmentName) end + self:setAttachment(attachment) + end + + self:setColor(255, 255, 255, 255) + + return self +end +return Slot diff --git a/spine-corona/spine/SlotData.lua b/spine-corona/spine/SlotData.lua new file mode 100644 index 000000000..27c1f7ee9 --- /dev/null +++ b/spine-corona/spine/SlotData.lua @@ -0,0 +1,23 @@ + +local SlotData = {} +function SlotData.new (name, boneData) + if not name then error("name cannot be nil", 2) end + if not boneData then error("boneData cannot be nil", 2) end + + local self = { + name = name, + boneData = boneData + } + + function self:setColor (r, g, b, a) + self.r = r + self.g = g + self.b = b + self.a = a + end + + self:setColor(255, 255, 255, 255) + + return self; +end +return SlotData diff --git a/spine-corona/spine/spine.lua b/spine-corona/spine/spine.lua new file mode 100644 index 000000000..d2285beba --- /dev/null +++ b/spine-corona/spine/spine.lua @@ -0,0 +1,17 @@ + +local spine = {} + +spine.utils = require "spine.utils" +spine.SkeletonJson = require "spine.SkeletonJson" +spine.SkeletonData = require "spine.SkeletonData" +spine.BoneData = require "spine.BoneData" +spine.SlotData = require "spine.SlotData" +spine.Skin = require "spine.Skin" +spine.RegionAttachment = require "spine.RegionAttachment" +spine.Skeleton = require "spine.Skeleton" +spine.Bone = require "spine.Bone" +spine.Slot = require "spine.Slot" +spine.AttachmentResolver = require "spine.AttachmentResolver" +spine.Animation = require "spine.Animation" + +return spine diff --git a/spine-corona/spine/utils.lua b/spine-corona/spine/utils.lua new file mode 100644 index 000000000..868591584 --- /dev/null +++ b/spine-corona/spine/utils.lua @@ -0,0 +1,59 @@ + +local utils = {} + +utils.readFile = function (fileName, base) + if not base then base = system.ResourceDirectory; end + local path = system.pathForFile(fileName, base) + local file = io.open(path, "r") + if not file then return nil; end + local contents = file:read("*a") + io.close(file) + return contents +end + +function tablePrint (tt, indent, done) + done = done or {} + indent = indent or 0 + if type(tt) == "table" then + local sb = {} + for key, value in pairs (tt) do + table.insert(sb, string.rep (" ", indent)) -- indent it + if type (value) == "table" and not done [value] then + done [value] = true + table.insert(sb, "{\n"); + table.insert(sb, tablePrint (value, indent + 2, done)) + table.insert(sb, string.rep (" ", indent)) -- indent it + table.insert(sb, "}\n"); + elseif "number" == type(key) then + table.insert(sb, string.format("\"%s\"\n", tostring(value))) + else + table.insert(sb, string.format( + "%s = \"%s\"\n", tostring (key), tostring(value))) + end + end + return table.concat(sb) + else + return tt .. "\n" + end +end + +function utils.print (value) + if "nil" == type(value) then + print(tostring(nil)) + elseif "table" == type(value) then + print(tablePrint(value)) + elseif "string" == type(value) then + print(value) + else + print(tostring(value)) + end +end + +function utils.indexOf (haystack, needle) + for i,value in ipairs(haystack) do + if value == needle then return i end + end + return nil +end + +return utils