Skip to content
AI Sprite Maker

Unity

Sprite Sheets in Unity: Slicing, Pixel-Perfect Settings and Animation

Published November 6, 202611 min read
On this page
  1. The sheet we’re importing
  2. Step 1: import settings
  3. Which Pixels Per Unit?
  4. Step 2: slice in the Sprite Editor
  5. Step 3: make the animation clips
  6. Frame rate
  7. Looping
  8. Step 4: the Animator
  9. Step 5: drive it from C#
  10. Side-scrollers: flip instead of a second row
  11. One-shot animations
  12. Step 6: pixel-perfect rendering
  13. Slicing from a JSON atlas
  14. Troubleshooting
  15. Getting sheets to import

Unity handles sprite sheets well once it’s set up, but its defaults are tuned for high-resolution art. Import a 64 × 64 pixel-art sheet with the default settings and you get a blurry, compressed, wrongly scaled sprite with thin lines bleeding in from the next frame. This guide goes through the settings that matter, in the order you’ll meet them, then builds a working 4-direction character with an Animator and a small C# controller.

Everything here works in Unity 6 and Unity 2022 LTS with the 2D template (or the 2D Sprite package installed). Where the two differ, both are shown.

The sheet we’re importing

The examples use a typical character sheet:

  • frames are 64 × 64 px, no padding;
  • 8 frames per row;
  • 4 rows, one per direction: down, left, right, up;
  • the character’s feet sit on the same line near the bottom of every frame.

That’s the layout AI Sprite Maker exports, and it’s common for hand-made sheets too. If your sheet is packed differently (trimmed frames, a JSON atlas), skip to Slicing from a JSON atlas.

Step 1: import settings

Drop the PNG into Assets/Sprites/, select it, and set these in the Inspector:

SettingValueWhy
Texture TypeSprite (2D and UI)Makes it a sprite asset
Sprite ModeMultipleOne texture, many sprites
Pixels Per Unit64 (your frame or tile size)1 world unit = 1 tile; keeps physics and the camera simple
Mesh TypeFull RectEvery frame gets the same quad, so the pivot doesn’t drift
Wrap ModeClampStops edge pixels wrapping around
Filter ModePoint (no filter)Square, sharp pixels
CompressionNoneCompression adds colour noise you will see at pixel scale
Generate Mip MapsOff (under Advanced)Mip maps blur sprites when the camera zooms out
Max SizeAt least the texture’s larger sideA 4096 px sheet with Max Size 2048 is silently downscaled

Click Apply.

Make these the defaults

Setting this on every import gets old. Select a correctly configured texture, click the preset icon (the slider icon at the top right of the Inspector) and Save current to… a preset. Then in Project Settings → Preset Manager add it as the default for TextureImporter, optionally with a filter like glob:"Assets/Sprites/**".

Which Pixels Per Unit?

PPU decides how big a sprite is in world units. The rule that saves the most pain: use the same PPU for every sprite in the game, and set it to your tile size. If tiles are 16 px and characters are drawn on 32 × 32 frames, both get PPU 16, and the character is 2 × 2 tiles. Mixing PPUs means mixing pixel sizes on screen, which looks wrong immediately.

Step 2: slice in the Sprite Editor

Click Open Sprite Editor in the Inspector (if the button is missing, install 2D Sprite from the Package Manager).

  1. Click Slice at the top left.
  2. Type: Grid By Cell Size.
  3. Pixel Size: X 64, Y 64.
  4. Offset and Padding: 0 unless your sheet has gaps between frames.
  5. Pivot: Bottom Center for characters. Use Center for items and effects.
  6. Click Slice, then Apply at the top right.

Grid By Cell Count (columns × rows) works too if you’d rather type 8 and 4.

Unity names the sprites hero_0, hero_1, … left to right, top to bottom. With 8 columns, row r column c is hero_{r*8 + c}: the down row is 0–7, left 8–15, right 16–23, up 24–31.

Why Bottom Center matters

The pivot is where the sprite’s transform position sits. With a centre pivot, an attack frame that’s taller than the idle frame moves the character’s feet when it plays. With Bottom Center on frames whose feet share a baseline, the character stays planted, Y-sorting works, and a collider at the feet lines up with every animation.

After slicing, the Project window shows one sprite per cell. For a single-row strip like this one, hero_0 to hero_7 are simply left to right:

Grid By Cell Size on an 8-frame strip. With Bottom Center pivots, every sprite's origin sits under the feet.

Step 3: make the animation clips

The fast way: in the Project window, expand the sheet, select frames hero_0 to hero_7 (click the first, Shift-click the last), and drag them into the Hierarchy (or Scene). Unity asks where to save the clip; call it Walk_Down.anim. It also creates a GameObject with a SpriteRenderer, an Animator and an Animator Controller named after the first sprite.

For the other rows, don’t drag them into the scene again; that creates more GameObjects and controllers. Instead:

  1. Select the GameObject, open Window → Animation → Animation.
  2. Use the clip dropdown → Create New Clip…, name it Walk_Left.
  3. Drag frames 8–15 onto the timeline.

Repeat for right and up, and for any other sheets (idle, attack).

Frame rate

New clips default to 60 samples per second, so 8 dragged frames play in about 0.13 seconds. Set the clip’s sample rate to your sheet’s fps (8–12 for walks):

  • Unity 2022 and 6: in the Animation window, open the options menu (the ⋮ at the top right) and enable Show Sample Rate, then type 10 in the Samples field.
  • Or select keys and spread them so each frame lasts 1/fps seconds.

If your sheet came with JSON metadata, the fps field tells you the intended rate.

Looping

Select the .anim asset and tick Loop Time for walks, runs and idles. Untick it for one-shot clips like attack, hurt and death.

Step 4: the Animator

Open Window → Animation → Animator with the character selected. You’ll see your clips as states. For a top-down character, a Blend Tree keeps the graph tidy:

  1. Right-click → Create State → From New Blend Tree, name it Walk.
  2. Double-click it. Set Blend Type to 2D Simple Directional.
  3. Add parameters (left panel, Parameters tab): MoveX (Float), MoveY (Float), Speed (Float).
  4. Set the blend tree’s parameters to MoveX and MoveY.
  5. Add four motions with these positions:
MotionPos XPos Y
Walk_Down0-1
Walk_Left-10
Walk_Right10
Walk_Up01

Do the same for an Idle blend tree. Then connect Idle → Walk with condition Speed > 0.1, and Walk → Idle with Speed < 0.1.

For pixel art, select each transition and set:

  • Has Exit Time: off, so the switch happens immediately;
  • Transition Duration: 0. Sprite animation can’t cross-fade; a non-zero duration just delays the change.

Blend trees and sprites

A 2D blend tree between sprite clips doesn’t blend images; it picks the clip whose position is closest to the input. That’s exactly what you want: diagonal input snaps to the nearest of the four directions. For 8 directions, add four more motions at (±0.7, ±0.7).

Step 5: drive it from C#

Attach this to the character along with a Rigidbody2D (Body Type Dynamic, Gravity Scale 0 for top-down, Interpolate Interpolate) and a collider.

using UnityEngine;

[RequireComponent(typeof(Rigidbody2D), typeof(Animator))]
public class TopDownMover : MonoBehaviour
{
    [SerializeField] float speed = 3f; // world units (tiles) per second

    Rigidbody2D body;
    Animator anim;
    Vector2 input;
    Vector2 facing = Vector2.down;

    static readonly int MoveX = Animator.StringToHash("MoveX");
    static readonly int MoveY = Animator.StringToHash("MoveY");
    static readonly int Speed = Animator.StringToHash("Speed");

    void Awake()
    {
        body = GetComponent<Rigidbody2D>();
        anim = GetComponent<Animator>();
    }

    void Update()
    {
        input = new Vector2(Input.GetAxisRaw("Horizontal"), Input.GetAxisRaw("Vertical"));
        if (input.sqrMagnitude > 1f) input.Normalize();
        if (input.sqrMagnitude > 0.01f) facing = input;

        // Keep the last direction when idle so the character doesn't snap to "down".
        anim.SetFloat(MoveX, facing.x);
        anim.SetFloat(MoveY, facing.y);
        anim.SetFloat(Speed, input.magnitude);
    }

    void FixedUpdate()
    {
        // Unity 6: linearVelocity. Unity 2022: use body.velocity instead.
        body.linearVelocity = input * speed;
    }
}

Input.GetAxisRaw uses the old Input Manager. If your project uses only the new Input System, read a Vector2 from an InputAction instead; the animator code stays the same. In Unity 6, Rigidbody2D.velocity still compiles but is marked obsolete; linearVelocity is the new name.

Side-scrollers: flip instead of a second row

For a platformer you usually have one direction of art. Flip it with the SpriteRenderer:

var sr = GetComponent<SpriteRenderer>();
if (input.x != 0) sr.flipX = input.x < 0;

flipX mirrors around the pivot, which is another reason for a bottom-centre pivot. Avoid flipping with a negative transform.localScale.x if the object has child colliders or effects you don’t want mirrored.

Elf Archer - Idle
Elf Archer - Walk
Elf Archer - Shoot Bow
Idle and walk loop; the bow shot is a one-shot clip that returns to idle when it ends.

One-shot animations

For an attack, add a Trigger parameter Attack, a transition from Any State to Attack with that trigger (Has Exit Time off), and a transition from Attack back to Idle with Has Exit Time on and Exit Time 1, so it returns when the clip ends:

static readonly int Attack = Animator.StringToHash("Attack");

void Update()
{
    // ...movement code...
    if (Input.GetKeyDown(KeyCode.Space)) anim.SetTrigger(Attack);
}

To trigger damage on a specific frame, select the attack clip in the Animation window, move to that frame, and click Add Event. Point it at a public method on the character.

Step 6: pixel-perfect rendering

Point filtering keeps pixels sharp, but at non-integer zoom some pixels are drawn wider than others, and moving sprites shimmer. The Pixel Perfect Camera fixes that.

  • URP (2D Renderer): add Pixel Perfect Camera (from UnityEngine.Rendering.Universal) to the Main Camera.
  • Built-in pipeline: install the 2D Pixel Perfect package and add its Pixel Perfect Camera component.

Settings:

SettingValue
Assets Pixels Per UnitSame PPU as your sprites (64 in this example, or your tile size)
Reference ResolutionYour game’s native resolution, e.g. 480 × 270 or 320 × 180
Crop FrameNone, or Windowbox/Letterbox if you want exact integer scaling with borders
Grid SnappingPixel Snapping (or Upscale Render Texture for a stricter retro look)

With a 320 × 180 reference resolution, a 1920 × 1080 screen scales by exactly 6. A 1366 × 768 laptop can’t scale 320 × 180 by a whole number; Crop Frame decides whether you get borders or a slightly cropped view.

Slicing from a JSON atlas

Packed atlases (trimmed frames, rotated frames, different sizes) can’t be sliced with a grid. Your options:

  1. TexturePacker’s importer for Unity reads TexturePacker data files and creates sprites with the right rects and pivots. Simple if you already own TexturePacker.
  2. Re-pack as a grid. Our free Sprite Sheet Slicer cuts any sheet into frames, and the Sprite Sheet Maker packs them back into an even grid with no trimming. That’s often the least effort for a handful of sheets.
  3. An editor script using the 2D Sprite package’s data provider. Minimal version for a grid with known frame size, which you can adapt to read rects from JSON:
// Assets/Editor/GridSlicer.cs
using System.Collections.Generic;
using UnityEditor;
using UnityEditor.U2D.Sprites;
using UnityEngine;

public static class GridSlicer
{
    [MenuItem("Assets/Slice As 64x64 Grid (bottom-center pivot)")]
    static void Slice()
    {
        var tex = Selection.activeObject as Texture2D;
        if (tex == null) return;
        var path = AssetDatabase.GetAssetPath(tex);
        var importer = (TextureImporter)AssetImporter.GetAtPath(path);
        importer.spriteImportMode = SpriteImportMode.Multiple;
        importer.filterMode = FilterMode.Point;
        importer.textureCompression = TextureImporterCompression.Uncompressed;
        importer.SaveAndReimport();

        var factory = new SpriteDataProviderFactories();
        factory.Init();
        var provider = factory.GetSpriteEditorDataProviderFromObject(importer);
        provider.InitSpriteEditorDataProvider();

        const int size = 64;
        var rects = new List<SpriteRect>();
        int cols = tex.width / size, rows = tex.height / size, i = 0;
        for (int r = 0; r < rows; r++)
            for (int c = 0; c < cols; c++)
                rects.Add(new SpriteRect
                {
                    name = $"{tex.name}_{i++}",
                    // Unity's rect origin is bottom-left, so row 0 (top of the image) has the highest y.
                    rect = new Rect(c * size, tex.height - (r + 1) * size, size, size),
                    alignment = SpriteAlignment.BottomCenter,
                    pivot = new Vector2(0.5f, 0f),
                    spriteID = GUID.Generate(),
                });

        provider.SetSpriteRects(rects.ToArray());
        provider.Apply();
        importer.SaveAndReimport();
    }
}

Right-click a texture → Slice As 64x64 Grid and it’s done, with the same naming order as the Sprite Editor. Note the y flip: Unity measures sprite rects from the bottom-left of the texture, while most JSON atlases measure from the top-left. Forgetting that is the usual reason script-sliced sprites come out as the wrong frames.

Troubleshooting

ProblemFix
Sprite is blurryFilter Mode Point, Compression None, mip maps off
Thin lines from the neighbouring frame at the edgesPoint filtering plus Pixel Perfect Camera usually fixes it. If you pack sheets yourself, add 1–2 px padding (or extrude) between frames. With a Sprite Atlas, set its padding to 2 or more
Colours look slightly off or noisyCompression is still on, or the platform override (Android/iOS tab) has its own compression
Animation plays far too fastThe clip is at 60 samples per second; set it to the sheet’s fps
Character hops when switching animationsPivots differ between clips. Slice everything with Bottom Center and keep frames the same size
Wrong frames in a script-sliced sheetYou forgot that sprite rects start at the bottom-left
Character slides on the groundMove speed doesn’t match the stride. Adjust speed until the planted foot stays still
Sprite is huge or tinyInconsistent Pixels Per Unit. Use one PPU everywhere

Getting sheets to import

If you need characters to try this with, AI Sprite Maker’s sprite animation tool outputs exactly the layout used here: equal frames, no padding, one row per direction, feet on a shared baseline. A library animation costs 10 credits per direction, and the free daily credits cover a test run. Export the Frames ZIP if you prefer to drag individual PNGs into Unity, or the PNG sheet and slice it as above. For other engines, see Exporting sprites to your engine.

If you’d rather skip the manual steps, our free Unity package applies the import settings from step 1, slices from the sheet’s frame data, and builds the AnimationClips and Animator Controller for you. It also slices any PNG that has an Aseprite or TexturePacker JSON next to it.