prime31 / Nez

Nez is a free 2D focused framework that works with MonoGame and FNA
MIT License
1.76k stars 357 forks source link

Adds support for loading Aseprite files #781

Closed AristurtleDev closed 7 months ago

AristurtleDev commented 7 months ago

This pull request adds support for loading the contents of an Aseprite file in Nez through the use of the new AsepriteFileLoader. Using this will create a new AsepriteFile class instance that contains the data read from the Aseprite file.

Example: Load Aseprite File

AsepriteFile aseFile = scene.Content.LoadAsepriteFile("fileName");

This PR also adds support for translating the data loaded from the Aseprite file into the already built in Nez SpriteAtlas class, which can then be used to load into a SpriteAnimator component to turn the Aseprite file animation tags into an animated sprite in Nez.

Example: Create SpriteAnimator Component

Scene scene = Scene.CreateWithDefaultRenderer(Color.CornflowerBlue);
AsepriteFile aseFile = scene.Content.LoadAsepriteFile("fileName");
SpriteAtlas atlas = aseFile.ToSpriteAtlas();

Entity entity = scene.CreateEntity("animated-sprite");
SpriteAnimator animator = entity.AddComponent<SpriteAnimator>();
animator.AddAnimationsFromAtlas(atlas);
animator.Play("run");
AristurtleDev commented 7 months ago

Benchmarks for those that are curious for this pull request. Two benchmarks were performed. Each benchmark used the following aseprite file

adventurer.zip

Load Asperite File Benchmark

The first benchmark measure loading the Aseprite file from disk, parsing, and creating the AsepriteFile class instance containing all the data parsed from the file

The Benchmark Code

using BenchmarkDotNet.Attributes;
using Nez.Aseprite;

namespace Benchmark;

[SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.Net70)]
[MemoryDiagnoser(true)]
public class AsepriteLoadBenchmark
{
    [Benchmark]
    public AsepriteFile LoadAsepriteFile()
    {
        AsepriteFile aseFile = AsepriteFileLoader.Load("adventurer.aseprite");
        return aseFile;
    }
}

The Result image

Create Sprite Atlas Benchmark

The second benchmark does a presetup that preloads the Aseprite file and creates a fake MonoGame Game class using GraphicsDevice.ReferenceAdapter so that a GraphicsDevice can be created. Then the actual benchmark measure taking that Aseprite file and converting it to a SpritAtlas in Nez which performs the following steps

  1. Flatten all frames (179 frames with 11 layers in the test file totaling ~1,969 cels processed, including blending)
  2. Pack all flattened frame data into a Color[] representing a packed texture atlas
  3. Create a Texture2D from the Color[] data
  4. Generate Rectangle source rects for each frame packed into the texture atlas
  5. Generate sprite animation data based on the tags in the Aseprite file
  6. Create the SpriteAtlas instance with the generated data

The Benchmark Code

using BenchmarkDotNet.Attributes;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Nez.Aseprite;
using Nez.Sprites;

namespace Benchmark;

[SimpleJob(BenchmarkDotNet.Jobs.RuntimeMoniker.Net70)]
[MemoryDiagnoser(true)]
public class AsepriteBenchmarks
{
    private AsepriteFile _aseFile;
    private Game _game;
    private GraphicsDeviceManager _gdm;
    private GraphicsDevice _gd;

    [GlobalSetup]
    public void Setup()
    {
        _aseFile = AsepriteFileLoader.Load("adventurer.aseprite");
        _game = new FakeGame();
        _gdm = new GraphicsDeviceManager(_game);
        _gdm.GraphicsProfile = GraphicsProfile.HiDef;
        ((IGraphicsDeviceManager)_game.Services.GetService(typeof(IGraphicsDeviceManager))).CreateDevice();
        _gd = _game.GraphicsDevice;

    }

    [Benchmark]
    public void ConvertToSpriteAtlas()
    {
        SpriteAtlas atlas = _aseFile.ToSpriteAtlas(_gd);
    }
}

The result image