Skip to content
On this page

Sprite Sheets

A major challenge of 2D (and UI) game development is solved by using sprite sheets. It means that we copy together many single images into one big sheet file and write down a config file (e.g. json) which contains the position of the source image on that sprite sheet.

How to create a Sprite Sheet?

TypeSpriteJS does that automatically using naming conventions. As soon as we name a folder and files in the right way the dev-server creates a sprite sheet for us.

A Sprite Sheet is composed of: a folder, a sheet.json and source files:

bash
assets/sprites1.sheet.json
assets/sprites1/
assets/sprites1/company-logo.single.aseprite
assets/sprites1/game-logo.single.aseprite
assets/sprites1/ui-skin.slices.aseprite
assets/sprites1/tiny.fnt
assets/sprites1/tiny-page0.png

NOTE

This only works in a folder (or subfolder) of assets/.

Sprite Sheet Sources: Overview

Naming/ConventionSource/ProgramDescription
*.slices.aspriteAseprite (1)Imports all slices, 9-patch-slices and fonts
*.single.aspriteAsepriteImports whole file as a single image
*.anim.asepriteAsepriteAll frame animations in that Aseprite file
*.font.asepriteAsepriteUses correctly named slices to export single letters
*.fnt + *.pngTrue Type Font + BMFont GeneratorImports Bitmap Fonts with their page files.

Notes

(1) You need Aseprite for this. Also make sure to configure the asepritePath in typesprite.config.mjs.

(2) Support is very early

The configuration json my look like this:

jsonc
{
  "width": 2048,         // [optional] max with of sheet
  "height": 2048,        // [optional] max height of sheet
  "trim": true,          // [optional] trim to rectangle
  "alwaysCompose": false // [optional] forces composition on every request
}

Load and use a Sprite Sheet

ts
import {Component, SpriteSheet} from 'typesprite';

export class MyComponent extends Component {
    
    @res('sheet', 'assets/sprites1.sheet.json')
    private sprites1:SpriteSheet;
    
    onInit() {
        const skin = this.sprites1.ninePatches["main-button"];
    }
}

Like all resources they can be requested via @res() in a component. The SpriteSheet class gives you access to fonts, slices and everything is ready to be used.

Workflow

One time:

  • You create a simple json for configuration
  • You create your real world program assets in the sprite sheet folder (aseprite, etc.)

While working:

  • You run your game
  • You modify your big file (e.g. adjust an animation in Aseprite)
  • You reload your game in browser and it's updated

Benifits:

  • Allows modifications in the real program in one place
  • Export is automatically (no extra step in Aseprite)
  • Exports only happen when your game requests them
  • No automatic background exporting when you just switch your app window
  • To move a sprite into another sheet is just a file copy in explorer
  • Adding Things is just a file copy
  • Configuration changes trackable and human-readable in git
  • The conventions help all involved to understand what happens - no side-workflows.

Sprite Sheet Sources: Aseprite

Aseprite is a first class citizen in TypeSpriteJS sheet export workflow. There are some configurations and conventions to consider and we need to explain them.

Aseprite: Animations (*.anim.ase)

Aseprite supports frame based animations. There are two major ways of doing things:

  • One animation in the entire file
  • Many animations grouped by tags

All in One:

If there are no tags in the animation all frames will be exported as one animation called default:

FileName in Sprite Sheet
run.anim.aserun:default
walk.anim.asewalk:default
turn.anim.aseturn:default

Using Tags:

FileTagName in Sprite Sheet
player.anim.aserunplayer:run
"walkplayer:walk
"turnplayer:turn

Additional Settings (looping, pivot):

To set a pivot for a single animation use the following name convention on the tag:

FileTagAnim LoopsCustom PivotIn Sheet
player.anim.aserunplayer:run
"run:L!player:run
"run:12x14!x:12 y:14player:run
"run:L,12x14!x:12 y:14player:run

Use in code:

ts
import {Component, GraphicsEngine, AnimationElement, SpriteSheet} from 'typesprite';

export class MyComponent extends Component {

    @res('sheet', 'assets/sprites1.sheet.json')
    private sprites1:SpriteSheet;
    @link('GraphicsEngine:typesprite')
    private gfx:GraphicsEngine; // given that it's defined in the EDF somewhere

    onInit() {
        const spr = new AnimationElement();
        spr.setAnimation("player:run");
        this.gfx.gameLayer.addChild(spr);
    }
}

Aseprite: Slices + Nine-Patches + Fonts (*.slices.ase, *.font.ase)

Slices are exported directly:

FileSlice NameName in Sprite Sheet
level.slices.aseblockblock
"grassgrass

A slice is imported as a Nine Patches when you provide the slice values in Aseprite:

FileSlice NameName in Sprite Sheet
ui.slices.asegrow-blockgrow-block
"ice-blockice-block

To define a Font you create a slice for each letter you like to support:

FileSlice NameLetterFont in Sheet
ui.slices.asefont:tinytim:aatinytim
"font:tinytim:bb"
"font:tinytim:cc"
"......(a lot of work)

Use in code:

ts
import {Component, GraphicsEngine, AnimationElement, SpriteSheet} from 'typesprite';

export class MyComponent extends Component {

    @res('sheet', 'assets/sprites1.sheet.json')
    private sprites1:SpriteSheet;
    @link('GraphicsEngine:typesprite')
    private gfx:GraphicsEngine; // given that it's defined in the EDF somewhere

    onInit() {
        // SLICES, SINGLES
        const blockFrame = this.sprites1.slices["block"];
        const fixedBlock = new SpriteElement(blockFrame);
        this.gfx.gameLayer.addChild(fixedBlock);
        
        // NINEPATCH
        const growBlock9P = this.sprites1.setNinePatch["grow-block"];
        const growBlock = new NinePatchElement().setNinePatch(growBlock9P);
        growBlock.setSize(20, 150);
        this.gfx.gameLayer.addChild(growBlock);
        
        // FONT
        const font = yy.fonts["tinytim"];
        const text = new TextElement().setFont(font)
        text.textbox.setText("My Text")
        this.gfx.gameLayer.addChild(text);
    }
}

NOTE

For creating UIs the same SpriteSheet class is used. But instead of this.gfx.gameLayer you use $ui(this.gfx.gui) or new LUILabelButton(...).