viniciusgerevini / godot-aseprite-wizard

Godot Editor plugin to help import Aseprite animations to AnimationPlayers, AnimatedSprites and SpriteFrames.
MIT License
821 stars 42 forks source link

Workaround for loss of Trim feature? #119

Open Kylemcarthur opened 7 months ago

Kylemcarthur commented 7 months ago

First off, thanks for making this plugin! People can be so awesome.

I saw that the Trim feature was removed in the past due to issues with it. I'm looking for a workaround because that feature is currently necessary for how I animate.

Player Character Animations

My workflow has all directions for a character side-by-side. This ensures that the action stays consistent across the different directions for a given animation. When it comes time to export, I would typically just hide the layers not in use and have the exported image trimmed. Prior to trimming, this would be the result:

image

While I can get this imported just fine using the plugin, it's still a ton of alpha that needs to be trimmed. Normally that step would be handled in the native Aseprite export. Is there a viable workaround for this using the plugin?

viniciusgerevini commented 7 months ago

Hello @Kylemcarthur . Thank you :). And thanks for sharing your use-case. Most of the times when people bring trimming, they refer to file size gains, which are negligible. I think yours is the first scenario that justify bringing trimming back.

Unfortunately, I can't think on any workaround at the moment. A lot has changed in both Godot and Aseprite since I removed the feature, so maybe I can make it work properly this time. I'll give it a try and let you know how it goes.

Good luck with your project. Your sprite looks pretty good.

viniciusgerevini commented 7 months ago

By the way, what are you using for your animations? AnimationPlayers or AnimatedSprites?

Kylemcarthur commented 7 months ago

Unfortunate to hear that there isn't a workaround, but I'd be very eager to see if one ends up becoming implemented. I've been a game designer / pixel artist for the past 10 or so years. Only recently getting into the programming side of things. So I have a pretty well-defined art and animation workflow pipeline at this point. For animations with multiple facing directions where things need to be consistent, this is the best workflow I've found - if there's a better alternative, I've never seen it. Layers and layer groups are your friends :) Happy to lend my experience if that can be at all useful for the plugin.

Thanks! The sprite is actually just a placeholder from a previous project I drew art for.

I'm currently learning about the use cases for Sprite2D, AnimatedSprite2D, and AnimationPlayer with AnimationTree. I found a plugin that seems to do a good job of converting an AnimatedSprite2D to AnimationPlayer (much easier to do a quick setup than keying every single frame...), so most likely that will be what I end up using, but that's subject to change as I learn more.

viniciusgerevini commented 7 months ago

Hello @Kylemcarthur . I put some thought on the trimming issue and I believe the feature you are looking for is Slices and not trimming. (https://www.aseprite.org/docs/slices/)

All explain the current issue and why I think slices is a better fit.

You might already know this, but Aseprite has 3 trimming options (trim, trim sprite and trim by grid).

Trim sprite does the trimming across frames. This would be the one to go if it respected layer visibility, but it doesn't. So this means it also includes hidden layers and the resulting sprite sheet still has the empty space.

Trim and Trim by grid are very similar, and both result on the same issue, that was the reason I removed it in the first place. Frames are trimmed individually, which result on the sprite moving around:

crop_issue

One way I can solve the issue above is by going through all the frames checking for the position and dimensions to identify the bounding box and calculate the crop manually, preventing the offset from the example above.

But even if I do this. there is still the issue of growing frames. If you set a frame bigger than the existing ones this would also expand your sprite node, potentially changing it's position a bit, so you would have to re-adjust it in Godot to be at the right place again.

I think slices is a better fit for this because you can define multiple regions in your file and the size won't change (unless you change it in Aseprite). Currently, the Aseprite CLI does not support slicing spritesheets, but it does include the metadata in the export so I'm able to set the right region. Technically, the spriteesheet png will still contain the empty space, however in the engine you would be seeing just the region from the slice you selected. I might be able to pair this with trimming to get rid of the extra space in the sheet, but I haven't tested if it works.

What this means for the plugin is that besides being able to select which layer to import you will also be able to select a Slice from the list, and just the slice area you selected will be visible.

Do you think this approach would fit your workflow?

Kylemcarthur commented 7 months ago

I may be misunderstanding something, but I don't have the issue with trimming that you describe. I'm on Aseprite v1.3.2, if that helps. The trim feature respects what layers I have visible. I'll show you with the same file as before. Here is absolutely every layer in the file visible.

image

Trimming this would lead to just trimming to the border, as seen here. (Despite the pixels on the background layer, I guess Aseprite can detect it's a single solid color background and trims it regardless, though I couldn't explain why. Nice feature though.)

image

Now, if I hide all the layers other than a direction I want to use (let's go with right-facing for this example, with helmet and some SFX turned off), it'll look like this.

image

And then trimming it results in this.

image

The trim is respecting only the visible layers with consideration to every frame that I have. So in this case, this frame here is the reason for this trim's size.

image

This also works the same when exporting a sprite sheet. Here is the same file, setting up the export, before the trim.

image

And here is what the trim looks like.

image

No issue, so this has been my workflow. Now, could slices do this better? That's an interesting question. I'm admittedly not well versed in the slice tool since I've only really needed it for the rare times I'm doing a 9-slice for something like a button. Beyond that, I haven't needed it. If after seeing that I don't have the trim issue you're describing (I'm not sure why you are having that issue, hmmm), you believe that slices are still the way to go, I can try to familiarize myself with it a bit more and report back.

What do you think?

viniciusgerevini commented 7 months ago

Thanks for the screenshots. They are very helpful. I found where the issue is. The problem is that exporting a specific layer still considers the other layers for trimming.

When exporting via the CLI, instead of hiding all other layers like you are doing via the interface, the command is equivalent to this screen:

Screenshot from 2024-02-09 08-49-16

I can see the issue even in the UI. If I enable "trim sprite" and select one layer only the result is this:

Screenshot from 2024-02-09 08-49-39

This might be by design, but I found an open issue related to that. https://github.com/aseprite/aseprite/issues/1634 . I can add a comment to that, but not sure when this is going to be tackled.

For the slice solution, I'll be implementing it anyway as I can see myself using it in another project. I also didn't pay much attention to that feature before, but I think it's a nice fit for this case. I think in earlier versions the tool was a little clanky, so maybe that's why I didn't use it before. I know they made some improvements to it and there are few others in their issue tracking.

The slice tool also does not work as intended for spritesheets (https://github.com/aseprite/aseprite/issues/2469), but this one I'm able to workaround in the plugin. I can't think on any workaround for the trimming issue though.

Kylemcarthur commented 6 months ago

Thanks for the update! I'll give this a look when it's ready (or if it needs testing). Maybe slices are the future? My current project will definitely be able to make use of the feature, so it'll be a great testing ground for feedback if needed. Feel free to ping me when that's possible (I'm new to this whole Github thing), and direct me where you'd like feedback to go if relevant.

viniciusgerevini commented 6 months ago

Thanks. The current version in the asset lib already has the slice feature. For feedback, you can always post issues in this repo for bug reports, feature requests or even usage questions. All the best for your project. Cheers.

Kylemcarthur commented 3 months ago

Hello! I've finally been able to dive into the slice solution and report back. I've put together a quick example animation to show a case where the trim method has a major flaw and the slice method actually seems to be superior, but I'm definitely hitting some snags.

Here's the example animation: a simple 3 frame attack for a character facing in 3 directions (3 separate animations in total, onion skinning is on). image

As you might expect, when exporting the animations using the trimming method I described previously, you'll end up with spritesheets where the character is off-center and inconsistent from sheet to sheet, requiring setting offsets for each animation., such as these:

haunted_armor_attack_up haunted_armor_attack_right haunted_armor_attack_down

Personally, I find that pretty tedious and error prone. So, making use of the slices method as you suggested, I produced 3 identical slices like this, though centering and sizing them requires manual adjustment which isn't ideal, but ultimately doable if no other option exists. image

Using your plugin to import these slices to Godot works pretty well! I'm able to set the layer groups "Up", "Right", and "Down" in the Layer import, as well as the corresponding slice, and this works as you'd expect, which makes the overlapping slices not have an issue. This is fairly close to ideal, but has a few major snags and some more minor ones.

Let's start with the major ones. This may very well be user error, so please let me know if there's a better way to do this. So, when I go to import a slice, it produces the correct result. image

However, when I go to import the next slice, this will overwrite the previous import entirely rather than allowing me to add it to a new animation or something similar. Notice how the "test_animation" from the previous image is gone and "default" has been overwritten. image In posting this, I see that "default" has turned into the "Attack" tag I added to the .ase file, and testing with additional tags shows they get added as separate animations - neat! That actually solves the other snag I had of not seeing a field to tell the importer which animation to do.

The problem here is that this overwriting is preventing me from including all of the slices in the animation list for the node. This image shows the desired end goal with the animation list including all of the character directions. image

If there's a way to do that with the importer, I haven't been able to figure it out yet. If not, that does pose a pretty major roadblock to being able to make use of the plugin unfortunately. The solution I think I'd want to see would either be the ability to tell the importer to include all slices, or multiple designated slices for more control, with each slice following the current import rules - each slice within each tag as a separate animation. Or alternatively, import each slice separately as it is currently setup, but be able to include those into the current SpriteFrames, adding the new imported animations to the same list, as opposed to overwriting the previous import.

EDIT: I should also mention that trying to create the animations manually by using the imported textures directly does not solve this because, while I can then produce multiple animations on the same node's list as needed, they retain all of the alpha from the original .ase canvas as described next, rather than using the slice area. image

On the more minor side of things, there's a few issues. First is the amount of alpha present in the imports. I noticed that the imported file has all of the original alpha from the .ase canvas, though the slice shown in the animation does not and only has the slice area. Since I don't fully understand what's going on in that distinction, I don't know if this causes any impact on the game's performance or not. image

Along those same lines, a disadvantage of the slice method is that there is inherently a lot of alpha space within the slice by necessity - trimming that would result in the same problem as before where the character is no longer centered. In an ideal world, I think the solution would be some way to trim the alpha down in a way that doesn't change where the art is aligned relative to its canvas edges, resulting in it remaining centered (that is, the character centered correctly, and not centering based on the entirety of the sprite - important distinction) without unnecessary alpha. If this is even possible, I have no idea, but it would solve the issue. I noticed that in Aseprite, the slice properties has the ability to set a pivot point, which perhaps could be used in some way to achieve this goal? image

Perhaps in the end, the best solution ultimately is to just trim spritesheets and set offsets on every animation, but that's pretty... gag.

The other (very) minor issue is the naming of file outputs. I noticed that the output includes either the layer or slice name at the end of the output name. So for example, I end up with "haunted_armorattack" becoming "haunted_armor_attack_Down" whereas my convention would prefer it to be "haunted_armor_attack_down". This is simple enough to work around, such as making my layer / slice names lower case within Aseprite, but since I'm writing up this post anyway, I figured might as well mention it too in case an option to overwrite that behavior is simple to do. Naming the output manually such as "haunted_armor_attack_down" lead to "haunted_armor_attack_downDown".

To wrap this up, I fully recognize that this is a free plugin that I'm benefiting from, so I don't in anyway feel entitled to any of these changes, but it would certainly be awesome, because the art workflow natively into Godot is... kind of stupifyingly awful. Either way, I really appreciate the work you've done on this to help out your fellow game devs. You best believe I'll be finding a way to contribute when I'm not as broke as a record. Stay awesome.

Kylemcarthur commented 3 months ago

I should also note that I looked through the other potential workflows for the plugin: Importers, Sprites and TextureRect -> AnimationPlayer (I'm currently using an AnimationPlayer + AnimationTree, so this workflow would ultimately make the most sense), Imports Manager, and Wizard Dock, but none of those seemed to provide the necessary solutions (or were missing the previous functionality of dealing with slices altogether).

viniciusgerevini commented 2 months ago

Hello @Kylemcarthur . Thanks a lot for putting the effort to write this detailed feedback.

when I go to import the next slice, this will overwrite the previous import entirely rather than allowing me to add it to a new animation or something similar. Notice how the "test_animation" from the previous image is gone and "default" has been overwritten. The problem here is that this overwriting is preventing me from including all of the slices in the animation list for the node. This image shows the desired end goal with the animation list including all of the character directions.

Yes, this is a limitation with working with AnimatedSprites and SpriteFrames. On every import the whole SpriteFrames is replaced. Maybe one interesting feature that could be implemented in the plugin would be providing a way to split animations into slices. For example, if you have an Attack animation, then the spriteframes would be generated with the following animations: Up-Attack, Down-Attack, Left-Attack, and so on (). The major downside of doing this way is that now you need to consider the position when building the animation name to be played.

The tricky part of implementing something like that is the naming decisions, because people have different preferences. (i.e. should the slice name come before or after? What's the separator to use? Should it be normalised?) I guess this aligns a little bit with one of your following points about file naming. There might be something that can be done for that, but that sounds like a lot of complexity here.

The way I've seen people implementing for multiple character positions like you do is using one Sprite for each position and one animation player to hold the animations for all of them.

Node: body (no strictly necessary, just using for grouping)
  - Sprite: Up
  - Sprite: Down
  - Sprite: Left
  - Sprite: Right
  - AnimationPlayer

All animations should be imported to the same AnimationPlayer (optional actually, you can have multiple). Then programmatically only the current position sprite is visible. The benefit of doing this way is that whatever plays your animation just needs to call the AnimationPlayer with the same animation name from aseprite and everything should work just fine. The downside though is that if you have a change that is applied across positions it has to be re-imported for each node.

This whole node/animation management is not ideal, but hopefully I can think into something to help with that when looking into https://github.com/viniciusgerevini/godot-aseprite-wizard/issues/135


On the more minor side of things, there's a few issues. First is the amount of alpha present in the imports. I noticed that the imported file has all of the original alpha from the .ase canvas, though the slice shown in the animation does not and only has the slice area. Since I don't fully understand what's going on in that distinction, I don't know if this causes any impact on the game's performance or not.

:( this is caused by this limitation in Aseprite (https://github.com/aseprite/aseprite/issues/2469). Unfortunately, I don't think this is high priority for them or that they even read my comment. I tried looking into their code myself, but I'm not an expert in c++ and I don't feel like spending this amount of effort on a tool that's not even open source. I wish there was an open source pixel art editor that worked as well as Aseprite does.

The good thing though is that I don't think that amount of alpha will impact your game's performance, unless working with huge canvases. But even then you need lots of images for that to become an issue.


Along those same lines, a disadvantage of the slice method is that there is inherently a lot of alpha space within the slice by necessity - trimming that would result in the same problem as before where the character is no longer centered. In an ideal world, I think the solution would be some way to trim the alpha down in a way that doesn't change where the art is aligned relative to its canvas edges, resulting in it remaining centered (that is, the character centered correctly, and not centering based on the entirety of the sprite - important distinction) without unnecessary alpha

Yeah, this sounds similar to the workaround I implemented for trimming once, where I applied trimming and used the metadata to reconstruct the sprite boundary in Godot. For the trimming case it didn't bring any benefit as the visible canvas in Godot had the original boundaries and the resulting spriteframes size offset the bytes saved in alpha. Of course it moved some bytes from VRAM, but the cost/benefit was not good. That could work for slices though, but only after they fix the issue I mentioned above.


The other (very) minor issue is the naming of file outputs. I noticed that the output includes either the layer or slice name at the end of the output name. So for example, I end up with "haunted_armorattack" becoming "haunted_armor_attack_Down" whereas my convention would prefer it to be "haunted_armor_attack_down". This is simple enough to work around, such as making my layer / slice names lower case within Aseprite, but since I'm writing up this post anyway, I figured might as well mention it too in case an option to overwrite that behavior is simple to do. Naming the output manually such as "haunted_armor_attack_down" lead to "haunted_armor_attack_downDown".

Yeah, I deliberately keep the users name to avoid other issues (i.e. even though mac is case insensitive, linux does differentiate down and Down. By normalizing one would overwrite the other. No one should rely on case, but each person has their way haha). But all things considered, I do think file naming can be better. I just need to find a good way to provide options without being too complex or crowding the interface. I tried using Aseprite's naming patterns feature, but that doesn't work on every command, so I'd have to roll my own.

Hopefully we can make the experience better little by little :D