Tutorial: Animated Spritesheets with GIMP and Unity

posted in: Programming, Tutorials | 2

This is a quick tutorial describing how to use a plugin I’ve written to create simple spritesheets with GIMP. These can be imported into Unity and used to create animated sprites for UI elements, 2D games, etc. Here’s an example of the end result (based on a free icon from the excellent game-icons.net):

Prerequisites

  • Unity: Tested with Unity 2017, but there’s nothing fancy going on so most recent versions should be fine.
  • GIMP 2.8 or later: This may work with earlier versions, but I haven’t tested any.
    • Python: Comes bundled with recent versions of GIMP, but if you’re stuck on an old copy you may need to reinstall it after installing Python for the first time.
  • The plugin: Right-click and save to your GIMP plugin folder. On Windows that’s: “C:\Users\YourUserName\.gimp-2.8\plug-ins”. Close GIMP first or restart afterwards.

 

Phase 1: Working in GIMP

  • Open GIMP and create a new image (File -> New) with a size of one animation frame (square dimensions work best, but aren’t required)
  • Create a layer for each frame of your animation and *insert actual art here* 
  • Line up the contents of each frame for smooth animation. This is easiest with a transparent background and liberal use of eye icons to toggle layers on/off in the Layers window as you go.
  • Order layers so that the first frame of the animation is at the bottom of the layer list, and the last frame is at the top. See below for an example setup.

  • You can now preview your animation in GIMP using the playback filter (Filters -> Animation -> Playback)
  • If you’re using a transparent background, make sure to add “(replace)” to each layer’s name so frames don’t overlay each other when animating.
  • Optional Export as GIF: You can also now export to an animated GIF by selecting (File -> Export As… GIF Image (*gif)) and ticking the ‘As Animation’ option on the dialog that appears.

  • If you installed the plugin as described in the Prerequisites section you should now be able to select (Filters -> Animation -> Create Spritesheet)
  • This will display a simple dialog with an option to output your animation on a single row or in a grid layout (the default). Hit OK to generate a spritesheet as a new image.
  • Voila! You have a spritesheet. Save this to your Unity project’s asset folder as a .jpg or .png (use the latter for transparent images)

Phase 2: Working in Unity

  • Select your spritesheet in Unity and under its Import Settings in the inspector make sure Texture Type is set to ‘Sprite (2D and UI)’
  • Open the spritesheet in the Sprite Editor, select Slice and change the Type to Grid by Cell Count
  • Adjust the number of columns and rows to match your spritesheet and then select Slice
  • You should now have a collection of sprites parented under your spritesheet, one per animation frame
  • These can be referenced individually by an Image (Source Image) component for UI or a Sprite Renderer (Sprite) component for 2D games

  • For full 2D games or other complex scenarios you’ll probably want to drive your animations (updating the sprite over time) from code, but for simple UI animations..
  • You can quickly test your animation by adding an Animator component to an Image object (GameObject -> UI -> Image) and animating the Image.Sprite value over time

That’s it! I hope you found this tutorial useful. I’ve released the GIMP plugin script under a Creative Commons – CC0 license which means its dedicated to the public domain and you’re free to do absolutely anything you like with it. No need to link back to me if you do either, but it’s always appreciated nonetheless.

2 Responses

  1. […] Description: Create a spritesheet from the layers of a GIMP image. Useful for simple sprite animation. Source: GitHub License: Public Domain (CC0 1.0) Documentation:  Tutorial: Animated Spritesheets with GIMP and Unity […]

  2. ll3v3ll
    | Reply

    Thank you for the excellent work.
    Please take a look at this version with some modifications:
    1) The new image mode is the same as the original (RGB, INDEXED, GREY).
    2) If the original has a filename, the new image will have a filename also (with _Sheet added).
    3) The sheet size calculation now handles an even and odd number of sprites.

    #!/usr/bin/env python
    # License: Public Domain – https://creativecommons.org/share-your-work/public-domain/cc0/
    # v1: Original by Karn Bianco
    # v2: Updated by ll3v3ll (Pixeljam)

    from gimpfu import *
    import math

    def create_spritesheet(image, singleRow):

    # Grab all the layers from the original image, each one of which will become an animation frame
    layers = image.layers
    numLayers = len(layers)

    # Work out how many rows and columns we need for each of our layers/animation frames
    numCols = numLayers if singleRow else int(math.floor(math.sqrt(numLayers)))
    numRows = 1 if singleRow else int(math.ceil(float(numLayers) / float(numCols)))

    # And then determine the size of our new image based on the number of rows and columns
    newImgWidth = image.width * numCols
    newImgHeight = image.height * numRows

    # Determine image type (RGB, INDEXED, etc)
    imageType = image.base_type
    imageIsIndexed = (imageType == INDEXED)
    imageIsGray = (imageType == GRAY)
    newImageType = INDEXED if imageIsIndexed else GRAY if imageIsGray else RGB
    newLayerType = INDEXEDA_IMAGE if imageIsIndexed else GRAYA_IMAGE if imageIsGray else RGBA_IMAGE

    # NOTE: [see gimp.h]
    # typedef enum {
    # RGB_IMAGE = 0,
    # RGBA_IMAGE,
    # GRAY_IMAGE,
    # GRAYA_IMAGE,
    # INDEXED_IMAGE,
    # INDEXEDA_IMAGE,
    # UNKNOWN_IMAGE,
    # } ImageType;

    # Get colormap if image is INDEXED
    if imageIsIndexed:
    (numBytes, colorMap) = pdb.gimp_image_get_colormap(image)

    # NOTE: [see pdb browser]
    # The number of entries is specified by the ‘num-bytes’ parameter and
    # corresponds to the number of INT8 triples that must be contained in the
    # ‘colormap’ array. The actual number of colors in the transmitted colormap
    # is ‘num-bytes’ / 3.

    # Create a new image
    newImage = gimp.Image(newImgWidth, newImgHeight, newImageType)

    # Set colormap to new image if image is INDEXED
    if imageIsIndexed:
    pdb.gimp_image_set_colormap(newImage, numBytes, colorMap)

    # Add a single layer that fills the entire canvas
    newLayer = gimp.Layer(newImage, “Spritesheet”, newImgWidth, newImgHeight, newLayerType, 100, NORMAL_MODE)
    newImage.add_layer(newLayer, 1)

    # Give the new image a filename that represents the original image
    if image.filename:
    newImage.filename = image.filename.rstrip(“.”) + “_Sheet”

    # Clear any selections on the original image to esure we copy each layer in its entirety
    pdb.gimp_selection_none(image)

    # Layers are in the reverse order we want them so start at the end of the list and work backwards
    layerIndex = (numLayers – 1)

    # Loop over our spritesheet grid filling each one row at a time
    for y in xrange(0, numRows):
    for x in xrange(0, numCols):

    # Copy the layer’s contents and paste it into a “floating” layer in the new image
    pdb.gimp_edit_copy(layers[layerIndex])
    floatingLayer = pdb.gimp_edit_paste(newLayer, TRUE)

    # This floating layer will default to the center of the new image so we first shift to the top left
    # corner (0, 0) and and then shift to correct grid position based on current row and column index
    xOffset = (-newImgWidth/2) + (image.width/2) + (x * image.width)
    yOffset = (-newImgHeight/2) + (image.height/2) + (y * image.height)

    # GIMP will only copy non-transparent pixels, so if our image contains transparency
    # the new floating layer may be smaller than we want which will cause animation issues.
    # To resolve this we adjust our position by the difference in layer size to ensure everything aligns
    xOffset += (image.width – floatingLayer.width) / 2
    yOffset += (image.height – floatingLayer.height) / 2

    # Move the floating layer into the correct position
    pdb.gimp_layer_translate(floatingLayer, xOffset, yOffset)

    # Move to the next layer, unless we’re all done in which case exit!
    layerIndex = (layerIndex – 1)
    if layerIndex < 0:
    break;

    # Merge the last floating layer into our final 'Spritesheet' layer
    pdb.gimp_image_merge_visible_layers(newImage, 0)

    # Create and show a new image window for our spritesheet
    gimp.Display(newImage)
    gimp.displays_flush()

    # Register the plugin with Gimp so it appears in the filters menu
    register(
    "python_fu_create_spritesheet",
    "Creates a spritesheet (in a new image) from the layers of the current image. (v2)",
    "Creates a spritesheet (in a new image) from the layers of the current image. (v2)",
    "Karn Bianco; ll3v3ll",
    "Karn Bianco; ll3v3ll",
    "2018",
    "Create Spritesheet",
    "*",
    [
    (PF_IMAGE, 'image', 'Input image:', None),
    (PF_BOOL, "singleRow", "Output to a single row?", FALSE)
    ],
    [],
    create_spritesheet, menu="/Filters/Animation/”)

    main()

Leave a Reply