seraphki

  • 31 мая 2023
  • Регистрация: 8 июл 2016

    Whalep. Maybe I can't delete this post. I thought I saw a delete button but now I can't find it. Guess it will just have to live here to show off my shame

    Hello!

    We just upgraded our Unity runtime to 4.1 and it seems like one of the changes is that SkeletonAnimation is no longer an accepted data field for the [SpineAnimation] attribute, it now wants a SkeletonDataAsset. We have hundreds of components with has SpineAnimation serialized out these references which are now just falling back to text fields - is there a way to continue using SkeletonAnimation, or do I need to swap all of those out to SkeletonDataAsset fields and folks are just going to have to reassign when they go to use them?

    Would very much appreciate help - unfortunately we can't go back down to the old version without someone losing a lot of work


    FALSE ALARM

    My SkeletonAnimation's SkeletonDataAsset had apparently come unassigned somewhere! Will delete this post momentarily

    • Изменено

    Okie dokie. When I was trying to figure some of this out I was forum surfing and there were a couple forum posts where people seemed to be complaining about the opposite (AddAnimation interrupting the SetAnimation and not letting the loop finish) and I thought I'd seen someone say that it was either a bug or that they had an issue in their code, so I got the impression that it wasn't supposed to interrupt.

    So, working off the assumption that non-interrupt was the desired behaviour, I just switched to a coroutine and just moving it to a coroutine and switching to set fixed my problem, further supporting that line of thought. But maybe did miss a weird value or a mis-timed clear. I'll dig some more and if I see that AddAnimation isn't interrupting I'll put together a simple script/project/whatever showcasing the issue.

    Thank you!


    Alright, did some tests and looks like it is something else!

    Sorry for the red herring, I just really thought I'd seen in a couple other posts that non-interruption was the intended behaviour, and since it so matched up with what I was seeing I was running with that. Also, sorry if I was sounding abrupt. I thought I was being misunderstood, not that you didn't know what you were talking about 🙂

    I found a piece of our animation controller that manipulates timescale, which in turn must have been manipulating the delay to not be quite right, triggering the outro late and making it look like it was waiting for that last loop.

    Thanks for getting me back on track!

    I have tried with the mix duration set to zero, and it just doesn't mix, as I expect. But I still don't interrupt the loop's current iteration. I might not be being clear enough about what I'm trying to accomplish. With this example setup:

    Animation A: 1s duration
    Animation B: 0.5s duration

    I want the entire animation sequence, loops and outro, to be, for example, 5 seconds. So, I use this setup:

    state.setAnimation(0, "A", true);
    entry = state.addAnimation(0, "B", false, 4.5);
    entry.mixDuration = 0;

    From start to finish, this entire sequence will take 5.5 seconds to complete, and animation "B" will start playing at 5 seconds, not 4.5. After 4.5 seconds "B" is added to the end of the queue, and it waits for the currently executing loop of animation A (which after 4.5 seconds is 0.5s into its 1s loop) to finish before it plays animation "B". Where what I want is for animation A to end immediately and B to immediately start playing.

    Yes, I have a looping animation with an outro. The problem is, I need the looping animation + the outro to be a very specific length, and that length is dynamic.

    AddAnimation will always add the animation to the end of the queue, not fully interrupt at the end of the delay. So if my looping animation is 1s and my outro is 0.5s and I need the animation to be 5 seconds so I set the delay to 4.5s (total length minus outro length) at 4.5 seconds it doesn't interrupt, it just adds the outro to the end of the queue and stops looping, so the current looping animation will finish its iteration so the outro will start at 5s and I'll have a total length of 5.5s.

    Using AddAnimation with a delay of <= 0 just uses the currently playing's remaining animation time as a delay, effectively still just adding it to the end of the queue. Which makes sense, since its called AddAnimation.

    What I really want here is a delay argument in the SetAnimation method.

    I have been able to accomplish this by having a coroutine wait the 4.5s (or whatever length, again, its very dynamic) and then use SetAnimation to cancel the loop, wherever it is, and start the outro. I was just hoping to avoid the coroutine.

    Hello!

    As far as I can tell, if you use SetAnimation() to play a looping animation, and then use AddAnimation() with a delay, it will wait the delay and then essentially cancel the loop, but the looping animation will complete its current iteration before playing the added animation.

    From other forum posts I can see this is generally the desired behaviour, but I have a situation where I want the looping animation plus its outro to be an exact length (which is variable) so I need to interrupt that loop at an exact point, not waiting for the looping animation to complete its iteration.

    Is there a setting that I can do that with? Currently I'm resorting to kicking off a coroutine and using SetAnimation() again after the desired delay, but I was hoping there was a way to do this within the spine structure.

    Thanks!

    • Изменено

    Hey! Thanks for getting back to me!

    So, I think my issue may have been in the fact that I completely lost my mind and was keeping a local reference to the TrackEntry in the StateMachineBehaviour, forgetting that the controller is an asset instead of an instance. It was working out most of the time, but I'm betting that's what was responsible for me sometimes missing a Complete event - I wasn't listening to the right event.

    Unfortunately, I think that also means I can't set up the state how I had it previously, since the callback doesn't include a reference to the animator, so I can't set the bool that releases me out of the state.

    But, I can still use the animator reference in the StateUpdate to get the SkeletonAnimation and check the current track's animation's duration against the state's current time and exit that way. I'll see how that goes.

    Excited to hear that the Mecanim layers work like the tracks do in SkeletonAnimation - I was really hoping that was the case. I'm not sure I'll be able to refactor my current project, but I'll probably convert over to SkeletonAnimators the next time I have to set up a controller - it's way nicer having those clips than having the state machine be fairly ignorant of the SkeletonAnimation.

    • Изменено

    Hello!

    I've been using a setup for a while where I use Mecanim as a basic state machine, but the state machine behaviours still talk to a controller script that accesses a skeleton animation.

    One of the more frustrating things I've encountered is when I want to pop into a state that plays a non-looping animation, and I want it to exit the state once the animation is complete. Currently, I'm playing the track in OnStateEnter, getting the TrackEntry back, and then subscribing to the Complete event. Once it triggers, I set a property in the animator that allows me to be released back into another state. This has been working for the most part, but I've unfortunately had it get stuck before (it might have missed the event or the event didn't fire for some reason?)

    Previously I was just checking if the TrackEntry was 'complete' during the OnStateUpdate, but occasionally I would get false positives or the track would get set to <none> and then never be 'complete'.

    I was exploring using the Skeleton Animator because I really like that the animations become clips so the states can naturally exit once the clip is complete, but I was having some trouble with tracks. I have animations that utilize two separate tracks (moving legs while firing, etc) and didn't quite see how, using mecanim, I'd specify what track each clip would play on. Is there still a concept of Tracks with the Skeleton Animator? How do you use them?

    Also, if using the Skeleton Animator ends up being too much of a refactor, do you have advice on the cleanest way for the Skeleton Animation to pop an animator out of its current state when a particular TrackEntry is complete?

    Thank you so much! =D

    • Изменено

    Hello!

    Sorry if this has been asked and answered, I didn't find anything.

    I was hoping to be able to take aa spine character in unity and set its alpha to about 0.5 via a script (only during certain events, which is why I'm not having the transparency baked into the animation) but I noticed when I tried to set color for the sharedmaterial that the shader that _Color isn't a thing for the skeleton shader.

    I'm also, unfortunately, woefully uninformed when it comes to shaders.
    My spine runtimes are a little old (we version locked a while ago to avoid issues) so if this was implemented already, sorry!

    Soooo anyone who can point me in the right direction for adding color to the skeleton shader (if that's something that can be done) I'd greatly appreciate it =]

    Awesome! Thanks for that =D
    Looks similar to what I did which is a huge relief. Going to create a new class and throw that in there so its cleaner =3

    Though I do have one question about your CreateAtlasAsset method that takes Textures - you search the atlas file for the texture names, but if you're recieving textures in an array, is there any reason you can't just do:

    for (int i = 0; i < textures.Length; i++)
    {
       mat = new Material(shader);
       mat.mainTexture = textures[i];
       break;
    }
    

    As someone writing a tool I understand why you wouldn't - just to make sure that the textures they hand in are the textures that are in the atlas, but is there another reason? Our team is small enough that I'd be willing to take that shortcut, as long as there isn't another reason not to do it.

    Thanks for the info!
    Most people in this situation seem to be using prefabs, but the way our situation is set up (our levels are all one big prefab and nested prefabs aren't really a thing) makes that more difficult. Also, since I'm using assets that are stored in asset bundles, I have to do something in the script anyway to point the scripts at the bundled assets, so I might as well do what I originally wanted to do, without the use of prefabs.
    I was eventually able to pull it off.

    Something that would have been (and would still be, in case you guys make updates) very helpful is a script that does the same thing your guys' import script does (taking textures, atlas, and json and creating materials, skeleton data, and atlas asset) but instead of creating asset files, you just return the assembled skeleton data. No asset files created, everything in memory. Essentially, tahts what I ended up doing.

    Some problems I encountered doing this, just in case someone else had some of the same issues (or in case you have a better way to solve them):

    #1. Scriptable Objects and Asset Bundles apparently don't place nice.
    It would have been a lot easier if I could have just added an HD and SD skeleton data into asset bundles with different variants, but it would seem that they don't collect and re-link dependencies properly. I looked around for a solution, but it seems this is a known problem, I couldn't find a solution. If I manually included all the linked asset, it didn't seem to be able to find them. I even forced 'Collect Dependencies' in my asset build script (even though thats apparently default) and it didn't work. My solution was to just build the two scriptable objects (Atlas Asset and Skeleton Data Asset) from scratch.

    #2. Things with the same name, but different extension
    Spine exports things to have the same name, but different extensions. When you're loading things from an asset bundle, you only use its name, not its extension. I don't know why. Anyway, this meant I couldn't differenciate between the different assets. My solution was to add them to smaller asset bundles grouped by file type, and access them from there.

    #3. Issues getting the Asset Text Asset from asset bundles
    I suspect it's because of the .asset in its name (since you don't use extensions, there might be an issue with the '.') but for whatever reason, it kept not being able to load the Asset text files. Since I was grouping my bundles by level and asset type, my "atlas text file" bundle was pretty small, so the solution was just to load all of them and then find one with a matching name. I need to investivate this more later, but for now the fix works.

    #4. Material Naming
    If you have one texture for your spine character (called Character), the import process will create a predictable material called Character_Material. If there are two textures (Character and Character2) it will less predictably name the materials Character_Character and Character_Character2 instead of Character_Material and Character_Material2, as I suspected they would be. I just ended up creating new materials since I could more easily predict the name of the textures, but just FYI.

    #5. Issues with 'fromAnimation'
    After everything was assembled, I was getting an error in the GetSkeletonData method about fromAnimation being null. I tracked down that in the IngestSpineProject method, called from that import process I assume, fromAnimation and toAnimation were intialized as empty string arrays. As soon as I added that into my code after setting the atlas asset and json, things started working properly.

    Anyway, as complicated as the process seemed, I think it best suits my need.
    If you guys ever decide to implement a method for creating those assets via script, please let me know. I'd love it.

    PS your forum kicks people out way too fast. I lost this post twice cause by the time I submitted it was like 'oops gotta log in'. :giggle: Guess its a good way to kick verbose buttface like myself.

    Cheers! :beer:

    So I'm not switching it on the fly, just once at runtime. Doing so because you use scripts to reference atlases in Asset Bundles. =)

    I'll look into that pre-multiply alpha and see whats up. Though I don't actually handle spine so if I can mess with some solutions without having to get someone to re-export some things from spine. I do care about optimization though, since we're pushing texture resolutions as far as we can take them.

    So what you're suggesting (building the skeletondataasset at runtime) is similar to the #3 method I've been using, and what I'll probably end up going with. I didn't even think to build it from scratch, I was including them in the Asset Databases and referencing them directly. Maybe it'll work better if I build it.

    I first tried just adding the skeleton data asset (unity should add all dependencies automatically) but when that didn't work, I added all the pieces. I think they're all getting in there, it's just a linking issue. But I might be able to construct the whole thing from scratch, I guess I just wanted to make sure I wasn't going through that when I didn't have to. =)

    • Изменено

    Hello everyone!

    I've got a couple issues I'm facing, and I'm hoping someone here can point a simple solution. Cause that's the dream, right?

    Problem 1: a simple way to switch between HD and SD assets.
    A note on my implementation: the methods I'm implementing do the actual "swapping" at runtime instead of a script that swaps things out during editing because I'm using asset bundles, so I need to point to a new location at runtime anyway, I just point to the proper variant.

    Method 1. Resizing the texture spine generates in photoshop, and switching out the material's texture on runtime based on set quality level. The problem with this is, every time I export a resize, I get weird white artifacts in the texture, even after the same import settings are applied. If I get a spine export that is half scale, the sprites always seem to repackage in a different order so they need a new atlas.

    Method 2. Using different Atlas Assets and switching them out in the Skeleton Data
    The other method was getting two exports from spine - one at half resolution, and switching them out in the Atlas Asset field in the skeleton data at runtime (based on resolution choice). Unfortunately, since these themselves are assets, any changes you make at runtime serialize and stay that way. Since I'm using Asset Bundles, that means that I have to deal with broken characters after every test run.

    Method 3. Using different Skeleton Datas and switching them out on the characters at runtime
    The last method I've been toying with is to switch out the entire skeleton data at runtime. I do have to delete the skeleton and regenerate everything, but I'm hoping its not too heavy a process. This seems to mostly work, and is my current fallback.

    I can probably make something from one of the above methods work, but I'd love any advice if someone has a simpler way.

    Problem 2: Asset Bundles.

    Using method #3, I put all of the skeleton datas into asset bundles (same name, different variant). Since unity 5 it automatically collects dependencies, but I threw them all in there manually anyway (after it failed the first time). Unfortunately, when I run the project, all the textures are pink. The characters load, and they seem to animate (their little pink boxy arms flail about) but there's no image. I've read in a couple other posts that Unity might have an issue tracking down dependencies and re-linking them in Scriptable Objects (which I guess the Atlas files are), but I don't know if this is the issue. I've read that a couple people have gotten it to successfully work if they throw an entire prefab into an asset bundle, but our pipeline as it stands would require quite a bit of re-wiring to work with prefabs like that, so I'd rather avoid it if possible.

    Hope someone has some advice they could lend me!
    Thanks for reading =)

    Cheers~