xnailbender

I realize you guys are VERY busy working hard to provide more thorough documentation/video tuts, but...
-------------
Edited:

I can't figure out how to play a non looping animation a second time after it plays once. I set this parameter to false, the non looping animation plays as expected, but I can't get it to play a second time. The animation seems to only load frame 1 and stops.
walkAnimation:apply(skeleton, event.time / 1000, false) --set to true "LOOPS" animation
It seems if looping is set to false, a variable is set somewhere when a non looping animation completes that is not being reset on loading the next non looping animation.

The problem may be in my code to load animations, but my code plays looping animations properly. I only have problems loading successive non looping animations.

Any help on this would be appreciated.

I realize the suggestion below would be a "Wish List" feature in the Corona runtimes.

An nice option would be to change the boolean (true/false) in the paramenter to a number. 0 = loop infinitely (which is "true" now), 1 = 1x loop, 2 = 2x loop, -1 = stop at frame 1, -10 = stop at frame 10, etc.

Also, I made this comment in another post, but didn't get a response from Nate:

This would be a "Wish List" feature to be added to the Corona runtimes.

I'm working in Corona SDK and I am wondering if I can access the keyframes so I can stop the animation at any frame or keyframe location. I could then start the animation from that frame and continue the animation to another frame on the timeline. I would also like to be able to play the animation in reverse, let's say from frame 60 back to 40.

I've got a cannon and I'd like to control it's up and down movements in order for it to aim. The user touches the cannon it loads and becomes active, the further the user drags back away from the cannon, the more powerful the shot. They can adjust their aim, if they drag back and down, the cannon fires up (opposite direction) and when they drag back and up, the cannon aims and fires downward, opposite of the drag.

If I could access the frames and also play the animation in reverse, the cannon would aim up and down relative to the users drag, creating a very interactive animation.

An example somewhere in code would be:

if skeleton.frame == 45 then
stopAnimation() --remove runtime listener
else
end

or if last frame in animation is 60 then

if skeleton.frame == 60 then
startNextAnimation()
else
end

Obviously the ability to utilize keyframes would be the most versatile, but at a minimum, an AnimationComplete = true return flag or the option to add an onComplete() as a parameter would be nice when an animation completes (last frame) so I can initiate the next animation or call a function to do something else. Right now I'm using a timer to start the next animation. This is doable, but requires converting the specific animations frames into miliseconds and creating a specific timer for each transition of animations. If the animation is stopped during it's execution for some reason (hero was destroyed), the specific timer calling the next animation needs to be canceled and nil'd.

Thanks,

Nail
Avatar utente
xnailbender
  • Messaggi: 13

Nate

Your animation only plays once because of the time you pass. Once the time goes past the animation duration, it will just pose the skeleton for the last frame of the animation. You need to track the time the skeleton is in an animation state and reset the time to zero when you want to play the animation again.

Typically in games time is tracked by running code each frame. This code increments a variable by the amount of wall time that has passed since the last frame. Let me know if you need assistance writing the Corona code to do this.

You can detect if an animation is complete by checking if the time is >= animation.duration.

You can create an animation programmatically to control your cannon. Eg, do this in the example main.lua instead of loading walkAnimation from a file:
local timeline = spine.Animation.RotateTimeline.new()
timeline.boneIndex = skeletonData:findBoneIndex("head")
timeline:setKeyframe(0, 0, 0) -- This is: keyframeIndex, time, angle
timeline:setKeyframe(1, 1.5, 90)
local timelines = {}
table.insert(timelines, timeline)
local walkAnimation = spine.Animation.new(timelines, timeline:getDuration())
Avatar utente
Nate

Nate
  • Messaggi: 12040

xnailbender

Wow! That's got to hurt... :clap:

That's what I'm talking about! Your simple code example clearly demonstrates the potential to create dynamically changing animations on the fly relative to user input, powerful stuff that opens up so many possibilities.

I didn't realize I could call spine.Animation like that. This technique should certainly work to aim my cannon, I'll be playing with it today.

I'm still hitting a wall resetting a non-looping animation's "time". When I run the main.lua posted below, which plays non-looping animations sequentially, it demonstrates the need to reset "time".

Yesterday I spent quite a bit of time trying to figure out how the Corona runtimes work and figured either the "time" or "duration" were the variables that needed to be reset to solve my issue.

Just to clarify, your Corona lua runtimes are above my coding level, although I am able to partially follow some of it.

I was able to manually reset the "duration" value to insure it was "0", I am not exactly sure how the code runs, so in case it wasn't seeing the declaration <local duration = 0> in SkeletonJson.lua because "loop = false", but that didn't work.

Frankly, there are so many instances of "time" throughout the code, I have no idea where to reset it manually after a non-looping animation completes it's last frame.

If you find time, could you modify the main.lua posted below so it resets the "time" and works properly. This would be an good example for others to review also as I would think other users will run into this issue.
Nate wrote:
You can detect if an animation is complete by checking if the time is >= animation.duration.
I see what you are suggesting here and I should be able to code the function to keep track of an animations playing time, if I stumble, I'll ask for help.

I'm also wondering if it is possible to add more bones to the <spine.Animation.RotateTimeline.new()> call in your example code. Let's say I wanted to rotate the "right shoulder" at the same time the "head" rotates. I tried several modifications to your example code, but can't get it to work.
--modified main.lua

local spine = require "spine.spine"

local walkAnimation

-- 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")
walkAnimation = json:readAnimationFile(skeletonData, "data/spineboy-walk.json")

local function startSnapHead()
local timeline = spine.Animation.RotateTimeline.new()
timeline.boneIndex = skeletonData:findBoneIndex("head")
timeline:setKeyframe(0, 0, 0) -- This is: keyframeIndex, time, angle
timeline:setKeyframe(1, 1.5, 90)

local timelines = {}
table.insert(timelines, timeline)
walkAnimation = spine.Animation.new(timelines, timeline:getDuration())
end

timer.performWithDelay(3000, startSnapHead)
timer.performWithDelay(6000, function() walkAnimation = json:readAnimationFile(skeletonData, "data/spineboy-walk.json") end)
timer.performWithDelay(9000, startSnapHead)

--local walkAnimation = json:readAnimationFile(skeletonData, "data/spineboy-jump.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()

Runtime:addEventListener("enterFrame", function (event)
walkAnimation:apply(skeleton, event.time / 1000, false) --true == loop infinitely / false == 1x loop
skeleton:updateWorldTransform()
end)
I've integrated my new Spine iBot into my app, if you'd like to see what I've been working on and how the cannon is being used take a look at this video I just made previewing the New Super iBot! It could be the first integration of Spine in an iOS app built with Corona SDK.

You've got to realize, I had zero animation experience 5 days ago before I found Spine and I've never used sprites either!

http://www.youtube.com/watch?v=WzrTfJ8ndf8

Thanks for the help,

Nail
Avatar utente
xnailbender
  • Messaggi: 13

Nate

Cool video!

The time I'm talking about is here:
walkAnimation:apply(skeleton, event.time / 1000, true)
Try this:
local lastTime = 0
local animationTime = 0
Runtime:addEventListener("enterFrame", function (event)
local currentTime = event.time / 1000
local delta = currentTime - lastTime
lastTime = currentTime

animationTime = animationTime + delta
walkAnimation:apply(skeleton, animationTime, true)
skeleton:updateWorldTransform()
end)
Corona has an odd way of writing games... but anyway, here we compute "delta", which is the time in seconds since the last frame. Everything in your game should be based on this. Now, "animationTime" is incremented by the delta. This accumulates time. When animationTime is passed to apply(), it poses the skeleton using the animation at that time. Set animationTime back to zero at any time to start the animation over.

You can add as many timelines to the animation as you like. Eg:
local timelines = {}

local timeline = spine.Animation.RotateTimeline.new()
timeline.boneIndex = skeletonData:findBoneIndex("head")
timeline:setKeyframe(0, 0, 0) -- This is: keyframeIndex, time, angle
timeline:setKeyframe(1, 1.5, 90)
table.insert(timelines, timeline)

local timeline = spine.Animation.RotateTimeline.new()
timeline.boneIndex = skeletonData:findBoneIndex("left upper leg")
timeline:setKeyframe(0, 0, 0)
timeline:setKeyframe(1, 0.5, 0)
timeline:setKeyframe(2, 1.5, -90)
table.insert(timelines, timeline)

local walkAnimation = spine.Animation.new(timelines, timeline:getDuration())
Avatar utente
Nate

Nate
  • Messaggi: 12040

xnailbender

Thanks Nate!

Now I understand how the time is working and being read by the Spine runtimes. I was thinking the runtimes computed their own times relative to the animation. For some reason I thought "event.time / 1000" was only a "ratio" of some sort for the runtimes to use, not the actual time that was being compared... |(

Now I see the Runtime listener is actually delivering the specific time of the animation to be played on each enterframe from the listener. I can now start thinking about different ways to use this insight. :party:

LOL...those stupid emoji's are kinda cool in their own stupid way.

I was close on adding the multiple timeline instances to the timelines{}.
I was declaring a timeline = {} and then indexing each, timeline[1], timeline[2] then dropping those into the timelines {} ...arggh... didn't work, wrong format.

I'm glad you liked the video, I don't know if you noticed I was in "Editor Mode" in my game. Users can create their own levels then Upload & Share them with other users. It saves the levels in a json format similar to Spine.

Although tiny due to the nature of the game, vertical jumper, I think the animations look good. My boy who's quite the app critic said that he thought they almost looked real. Compliments are far and few between, so I'll take'm when I get'm...

Anyways, I'm close to getting the cannon to aim properly relative to the user drag with the targeting line you saw in the video. Now that I have a better understanding of how the runtimes use time I should be able to get the shooting animations playing properly.

I really appreciate the lesson and taking the time to hack out those code blocks for me, hopefully they'll help other users also.

I'll post another video here when I get the shooting animations working properly.

Thanks again.
Avatar utente
xnailbender
  • Messaggi: 13

xnailbender

FWIW, I got my Spine created iBot fully integrated into my Super iBot app built with Corona SDK.

Once Nate straightened me out on handling "time" in the Runtime listener, my iBot was off and running/jumping.

I think adding the animated iBot has made a huge improvement to my game and have to thank Spine for enabling it, hopefully it will show up in increased downloads.

Lot's of tricky lua "if" statements to get the animations to play smoothly together, I suppose it would be the same when using sprites though, which I hadn't done.

Aiming the cannon with the user's touch/drag worked out well. Hopefully I've got all the bugs worked out.

Here's a quick recap of the animations used that you'll see in the video linked below. Only 1 animation direction is needed for each movement since the animations can be "flipped" on X or Y axis's which is nice.

1. Standing
2. Jumping
3. Running
4. Grabbing Cannon
5. Holstering Cannon
6. Shooting (recoil effect)
7. Exploding (iBot dies when hitting Atomic Charge)
8. Backflip

They all seem to play quite smoothly together and am very happy with the results. Take a look...

http://www.youtube.com/watch?v=SYHjR1HgKBE

Thanks again for the help guys!
Nail
Avatar utente
xnailbender
  • Messaggi: 13

Nate

Very nice! :)

You can google "finite state machine" to possibly research how you might refactor you animation management code.
Avatar utente
Nate

Nate
  • Messaggi: 12040


Torna a Editor