Sprites and Animation in Phaser
Every object you see in a 2D game, whether it is the player character, an enemy, a coin spinning in place, or an explosion, is a sprite. In Phaser, sprites are game objects that display a texture at a specific position, with support for scaling, rotation, tinting, transparency, and animation. Understanding how to work with sprites and the animation system is fundamental to building any Phaser game.
Prepare Your Spritesheet Assets
A spritesheet is a single image file that contains multiple frames of animation arranged in a grid. Each frame shows the character or object in a slightly different pose, and when the frames are displayed in sequence, they create the illusion of movement. For a simple walk cycle, you might have 6 to 8 frames showing the character's legs and arms in different positions.
When creating spritesheets, every frame must have the same width and height in pixels. If your character is 32 pixels wide and 48 pixels tall, every frame in the spritesheet must be exactly 32 by 48 pixels, arranged in a regular grid. Most pixel art tools like Aseprite, Piskel, and LibreSprite export spritesheets in this format automatically.
For more complex projects, texture atlases offer greater flexibility. A texture atlas combines multiple sprites of different sizes into a single image, along with a JSON file that describes the position, size, and name of each frame. Tools like TexturePacker and the free ShoeBox utility generate atlas files in the format Phaser expects. Atlases are more efficient than individual spritesheets because they reduce the number of texture swaps the GPU needs to perform during rendering.
Name your frames descriptively in the atlas JSON so they are easy to reference in code. A common convention is to use the pattern "character-action-frame" such as "player-walk-0" through "player-walk-7" for a walk cycle, and "player-idle-0" through "player-idle-3" for an idle animation. Consistent naming makes it straightforward to generate frame arrays for animations.
Load Spritesheets in Preload
Loading a uniform spritesheet uses the this.load.spritesheet method, which takes three arguments: a string key to identify the asset, the file path to the image, and a configuration object specifying the frame width and frame height. Phaser reads these dimensions and automatically divides the image into individual frames, numbered starting from zero.
For texture atlases, use this.load.atlas instead, providing a key, the path to the atlas image, and the path to the JSON descriptor file. Phaser reads the JSON to understand where each named frame is located within the image. You can then reference individual frames by their name rather than by number, which makes your code more readable and maintainable.
If your spritesheet has spacing between frames or a margin around the edge of the image, include the margin and spacing properties in the frame configuration. Some spritesheet generators add a 1-pixel border between frames to prevent texture bleeding, where adjacent frames bleed color into each other during rendering. Telling Phaser about this spacing ensures it clips each frame correctly.
Multi-atlas loading lets you split a large atlas across multiple image files while using a single JSON descriptor. This is useful when your total sprite content exceeds the maximum texture size supported by the GPU, typically 4096 by 4096 pixels on mobile devices. Phaser handles multi-atlas textures transparently, so your game code does not need to know whether frames come from one image or several.
Define Named Animations
Once your spritesheet or atlas is loaded, you define animations using the Animation Manager with this.anims.create. Each animation needs a unique key name, a list of frames, a frame rate, and a repeat value. The key is how you will reference the animation when playing it on a sprite.
For spritesheets, generate the frame list with this.anims.generateFrameNumbers, passing the spritesheet key and a start and end frame number. For atlases, use this.anims.generateFrameNames with a prefix, suffix, and number range that match your frame naming convention.
The frameRate property controls how many frames display per second. A frame rate of 10 is common for pixel art characters, giving each frame about 100 milliseconds of screen time. Higher frame rates like 24 or 30 create smoother animation but require more frames of art. The ideal frame rate depends on your art style and the type of movement being animated.
The repeat property controls looping. A value of -1 means the animation loops indefinitely, which is appropriate for walk cycles, idle animations, and spinning collectibles. A value of 0 means the animation plays once and stops on the last frame, suitable for death animations, explosions, and one-time effects. Positive values repeat that many times before stopping.
The yoyo property, when set to true, plays the animation forward and then backward before repeating. This creates a ping-pong effect that works well for breathing idle animations, pulsing UI elements, and certain attack animations where the character swings and returns to a ready position.
Play Animations on Sprites
To play an animation on a sprite, call sprite.play with the animation key as the argument. If the sprite is already playing a different animation, it will switch to the new one immediately. If it is already playing the same animation, the call is ignored, preventing the animation from restarting every frame when called inside the update loop.
For conditional animation switching, use sprite.anims.play with the ignoreIfPlaying parameter. This is critical in the update method where you check input and set the appropriate animation. Without this flag, calling play every frame would restart the animation from frame 0 each time, resulting in the character appearing frozen on the first frame.
You can also set a sprite's frame directly without playing an animation. This is useful for showing a specific pose, like a character looking up, crouching, or being hurt. Set sprite.setFrame(frameIndex) for spritesheets or sprite.setFrame(frameName) for atlases to display a specific frame as a static image.
Chaining animations uses the chain method, where you queue an animation to play after the current one finishes. This lets you play an attack animation followed by an idle animation without writing event listener code. Chain calls stack, so you can queue multiple animations in sequence for complex choreographed movements.
Control Playback and Respond to Events
The animation system provides several playback controls beyond play and stop. Call sprite.anims.pause() to freeze the animation on its current frame, and sprite.anims.resume() to continue from where it left off. Use sprite.anims.setTimeScale(value) to speed up or slow down playback, where 2 is double speed and 0.5 is half speed.
Animation events let you trigger game logic at specific moments during playback. The "animationcomplete" event fires when a non-looping animation finishes, which is useful for removing explosion sprites or transitioning to a new game state after a death animation. The "animationrepeat" event fires each time a looping animation completes one cycle. Individual frame events can trigger sounds, spawn particles, or deal damage at the exact frame where a sword swing connects.
Playing animations in reverse uses sprite.anims.playReverse(key), which plays the frames from last to first. This is useful for door-closing animations, rewinding effects, and any situation where you need the inverse of an existing animation without creating separate art assets. You can also reverse a currently playing animation mid-playback for responsive gameplay feedback.
The animation system tracks progress through properties like sprite.anims.currentFrame, sprite.anims.getProgress() which returns a 0-to-1 value, and sprite.anims.isPlaying which tells you whether an animation is currently active. These properties are useful for syncing game logic with animation state, such as only allowing the player to interrupt an attack animation after a certain progress threshold.
Optimize with Texture Atlases
Texture atlases improve rendering performance by reducing the number of texture bind operations the GPU performs each frame. When sprites use separate images, the renderer must switch textures between each sprite, which is an expensive GPU operation. When sprites share an atlas, the renderer can batch them into a single draw call, which is significantly faster.
Pack all sprites for a single scene or game phase into one atlas when possible. A typical indie game might use one atlas for the player character and common enemies, another for environment tiles and decorations, and a third for UI elements and effects. Keep each atlas under the GPU's maximum texture size, which is 4096 by 4096 on most devices and 2048 by 2048 on older mobile hardware.
TexturePacker is the industry standard tool for creating texture atlases. It arranges sprites efficiently using bin-packing algorithms, trims transparent borders to save space, and exports in Phaser's expected JSON format. The free version handles most needs, while the paid version adds features like polygon packing and multi-atlas export.
For dynamic games that load different content over time, you can load and unload atlases to manage GPU memory. Call this.textures.remove(key) to free the memory used by a texture that is no longer needed. This is important for mobile games where GPU memory is limited and loading too many textures simultaneously can cause performance degradation or crashes.
Define animations once in the Animation Manager and play them on any sprite by key name. Use texture atlases to batch sprites into fewer draw calls, and leverage animation events to sync game logic with visual feedback for polished, responsive gameplay.