Appearance
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:
typescript
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:
typescript
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:
typescript
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:
typescript
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
undefinedornull, the spritesheet is not found and will not be displayed
Resolver Function Signature
typescript
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:
typescript
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:
typescript
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:
typescriptengine.spritesheets.delete('spritesheet-id');Error Handling: Always handle errors gracefully in async resolvers:
typescriptspritesheetResolver: 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
spritesheetsarray.ID Patterns: Use consistent ID patterns to make resolver logic easier:
typescriptspritesheetResolver: (id: string) => { // Pattern: type-variant-color // Example: character-warrior-red const [type, variant, color] = id.split('-'); // ... }
Example: Loading from CDN
typescript
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
typescript
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:
typescript
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
- Display Animations Guide - Learn about animation spritesheets
- Sprite Components Guide - Add visual components to sprites
- Dialog Box Guide - Using facesets in dialogs