Documentation Index
Fetch the complete documentation index at: https://v5.rpgjs.dev/llms.txt
Use this file to discover all available pages before exploring further.
Spritesheets Guide
This guide explains how to work with spritesheets in RPG-JS, including static spritesheets and dynamic spritesheet resolution.
Overview
Spritesheets are image files containing multiple frames or tiles that are used to display characters, objects, and animations in your game. RPG-JS supports both pre-loaded static spritesheets and dynamic spritesheet creation through a resolver system.
Static Spritesheets
Basic Configuration
Add spritesheets to your client module configuration:
import { defineModule, RpgClient, Presets } from '@rpgjs/client';
export default defineModule<RpgClient>({
spritesheets: [
{
id: 'hero',
image: 'hero.png',
width: 192,
height: 256,
framesWidth: 3,
framesHeight: 4,
// ... animation configurations
},
Presets.LPCSpritesheetPreset({
id: 'monster',
imageSource: 'monster.png',
width: 1728,
height: 5568,
ratio: 1.5,
}),
],
});
Using Presets
RPG-JS provides several preset helpers to simplify spritesheet configuration:
- LPCSpritesheetPreset: For Liberated Pixel Cup (LPC) style character spritesheets
- FacesetPreset: For character face expressions in dialog boxes
- AnimationSpritesheetPreset: For animation sequences
See the Display Animations Guide for more details on animation spritesheets.
Dynamic Spritesheet Resolver
The spritesheet resolver allows you to create spritesheets on-the-fly when they are requested but not found in the cache. This is useful for:
- Loading spritesheets from external APIs
- Generating spritesheets programmatically
- Creating spritesheets based on runtime data
- Lazy loading spritesheets to reduce initial load time
Configuration
Add a spritesheetResolver function to your client module:
import { defineModule, RpgClient } from '@rpgjs/client';
export default defineModule<RpgClient>({
spritesheetResolver: (id: string) => {
// Synchronous resolver
if (id === 'dynamic-sprite') {
return {
id: 'dynamic-sprite',
image: 'path/to/image.png',
width: 192,
height: 256,
framesWidth: 3,
framesHeight: 4,
// ... other spritesheet properties
};
}
return undefined; // Return undefined if spritesheet cannot be created
},
});
Asynchronous Resolver
The resolver can also be asynchronous, useful for loading spritesheets from APIs or files:
export default defineModule<RpgClient>({
spritesheetResolver: async (id: string) => {
// Load from API
try {
const response = await fetch(`/api/spritesheets/${id}`);
if (!response.ok) {
return undefined;
}
const spritesheetData = await response.json();
return spritesheetData;
} catch (error) {
console.error(`Failed to load spritesheet ${id}:`, error);
return undefined;
}
},
});
Programmatic Generation
You can also generate spritesheets programmatically:
export default defineModule<RpgClient>({
spritesheetResolver: (id: string) => {
// Generate spritesheet based on ID pattern
if (id.startsWith('generated-')) {
const parts = id.split('-');
const type = parts[1];
const variant = parts[2];
return {
id: id,
image: `generated/${type}/${variant}.png`,
width: 64,
height: 64,
framesWidth: 1,
framesHeight: 1,
// Generate animations based on type
textures: {
default: {
animations: () => generateAnimationsForType(type),
},
},
};
}
return undefined;
},
});
How It Works
- When a spritesheet is requested (e.g., when displaying a sprite), the engine first checks the cache
- If the spritesheet is not found and a resolver is configured, the resolver is called with the spritesheet ID
- The resolved spritesheet is automatically cached for future use
- If the resolver returns
undefined or null, the spritesheet is not found and will not be displayed
Resolver Function Signature
type SpritesheetResolver = (id: string) => SpritesheetDefinition | Promise<SpritesheetDefinition> | undefined | null;
Parameters:
id: string - The spritesheet ID that was requested
Returns:
SpritesheetDefinition - A spritesheet configuration object (synchronous)
Promise<SpritesheetDefinition> - A Promise that resolves to a spritesheet (asynchronous)
undefined | null - Indicates the spritesheet cannot be created
Spritesheet Definition
A spritesheet definition is an object with the following properties:
interface SpritesheetDefinition {
id: string; // Unique identifier
image: string; // Path to the image file
width: number; // Width of the spritesheet image
height: number; // Height of the spritesheet image
framesWidth?: number; // Number of frames horizontally
framesHeight?: number; // Number of frames vertically
textures?: { // Animation configurations
[key: string]: {
animations: () => AnimationFrame[][];
};
};
// ... other spritesheet-specific properties
}
Programmatic API
You can also set a resolver programmatically using the engine:
import { RpgClientEngine, inject } from '@rpgjs/client';
// In a hook or initialization code
const engine = inject(RpgClientEngine);
engine.setSpritesheetResolver((id: string) => {
// Your resolver logic
return spritesheetDefinition;
});
Best Practices
-
Cache Management: Resolved spritesheets are automatically cached. If you need to invalidate the cache, you can clear it manually:
engine.spritesheets.delete('spritesheet-id');
-
Error Handling: Always handle errors gracefully in async resolvers:
spritesheetResolver: async (id: string) => {
try {
// Load spritesheet
return spritesheet;
} catch (error) {
console.error(`Error loading spritesheet ${id}:`, error);
return undefined;
}
}
-
Performance: Use resolvers for spritesheets that are not needed immediately. Pre-load critical spritesheets in the static
spritesheets array.
-
ID Patterns: Use consistent ID patterns to make resolver logic easier:
spritesheetResolver: (id: string) => {
// Pattern: type-variant-color
// Example: character-warrior-red
const [type, variant, color] = id.split('-');
// ...
}
Example: Loading from CDN
export default defineModule<RpgClient>({
spritesheetResolver: async (id: string) => {
const cdnUrl = `https://cdn.example.com/spritesheets/${id}.json`;
try {
const response = await fetch(cdnUrl);
if (!response.ok) {
return undefined;
}
const config = await response.json();
// Ensure the image URL is also from CDN
return {
...config,
image: `https://cdn.example.com/images/${config.image}`,
};
} catch (error) {
console.warn(`Spritesheet ${id} not found on CDN`);
return undefined;
}
},
});
Example: Fallback Chain
export default defineModule<RpgClient>({
spritesheetResolver: async (id: string) => {
// Try local assets first
try {
const localResponse = await fetch(`/assets/spritesheets/${id}.json`);
if (localResponse.ok) {
return await localResponse.json();
}
} catch (error) {
// Continue to fallback
}
// Fallback to CDN
try {
const cdnResponse = await fetch(`https://cdn.example.com/spritesheets/${id}.json`);
if (cdnResponse.ok) {
return await cdnResponse.json();
}
} catch (error) {
// Continue to fallback
}
// Fallback to default
if (id.startsWith('character-')) {
return {
id: id,
image: '/assets/default-character.png',
width: 192,
height: 256,
framesWidth: 3,
framesHeight: 4,
};
}
return undefined;
},
});
Combining Static and Dynamic
You can use both static spritesheets and a resolver together. Static spritesheets are loaded first, and the resolver is only called for spritesheets not found in the static list:
export default defineModule<RpgClient>({
// Pre-loaded spritesheets
spritesheets: [
{ id: 'hero', image: 'hero.png', /* ... */ },
{ id: 'npc-1', image: 'npc1.png', /* ... */ },
],
// Resolver for dynamic spritesheets
spritesheetResolver: async (id: string) => {
// Only called for spritesheets not in the static list above
if (id.startsWith('dynamic-')) {
return await loadDynamicSpritesheet(id);
}
return undefined;
},
});
See Also