10. Entity Sprites
This is the first lesson in which we aren't building the start of a system, but are instead beginning to couple things together. As such, the format will be slightly different.
In a game with the style we are aiming for, almost all entities are going to be sprite based. To that end, it makes sense to have sprite related data attached directly to the entities.
The Fractal Code Cycle Applied to Entity Sprites
Recall our fundamental pattern:
Input → Processing → Output
For sprite animations:
- Input: Entity sprite data, animation state, transform (position, rotation, scale) data
- Processing: Update sprite frames, apply transforms, prepare rendering data (if any)
- Output: Renderable sprite positioned in world space
What Are Entity Sprites Good For?
- Visual representation of all interactive entities
- Characters, whether animated or static
- Environmental storytelling (chest open/closed could signal something to the player)
- UI feedback (selection indicators, interaction prompts, though we haven't decided whether these will be entities or a separate system yet)
Entity Sprites in the System Stack
Entity Sprites bridge and couple two systems together:
- Base Layer: Uses Entity and Asset systems
- Presentation: Feeds into Animation and Rendering
- Interaction: Responds to Player and AI systems
Design Considerations
Looking at the screenshot below, you may notice 6 obvious entities. The player characters, the chest, and some kind of interactable entity in the background.
![[Pasted image 20250703083441.png]]
Octopath Traveller - our target style
Looking at other screenshots from the game, and thinking about how we want our game to work, we can safely assume that dozens of entities on screen will most likely be the upper limit.
This means that we don't have to worry about the performance of our megastruct approach too much.
All modern CPUs (1990+, some earlier) have memory and instruction caches built onto the CPU chip. The CPU pre-fetches memory from RAM that it thinks you'll be using soon.
The speed difference between accessing data that's in cache versus in RAM can be hundreds of times faster!
That's why, sometimes, even when an algorithm's complexity is faster on paper, you can get faster benchmarks using memory locality (better for pre-fetching) + caching with a "dumb" approach.
Let's quickly walk through why. Let's assume that we add a lot of data to our entities, as is the plan. What I'm listing below is not what we are adding now, it's just to get a feel for the kind of size we may expect.
// omitting types
Entity :: {
transform, // 36 bytes
sprite, // 136 bytes
physics, // 32 bytes
attributes, // 12 bytes
stats, // 8 bytes
status_effects, // 512 bytes
ai, // 40 bytes
equipment, // 128 bytes
audio, // 24 bytes
type, // 8 bytes
flags, // 4 bytes
padding, // 128 bytes
}
This generous size comes out to 1080 bytes. That is pretty huge. However, if we do some quick maths here:
- Battle scenes: 4 party members + 1-4 enemies = ~8 entities
- Wilderness: Player party + handful of NPCs = ~10-20 entities
- Town: Maybe 20-30 NPCs maximum
With ~50 entities:
- Total memory: 50 × 1080 = 54KB
- Fits entirely in L1 cache (32KB) + L2 cache (256KB+)
- All entity data stays hot in cache between frames
We are unlikely to hit performance issues in this type of game. In fact, it may be better than other entity systems just due to the small number of entities in our game.
Sprite Data Attachment
- Store sprite reference directly in Entity struct
- Include animation state
- Transform data controls world positioning, rotation, scale, etc
Animation State Management
- Each entity tracks its own animation state
- State changes driven by other systems (movement, combat, AI)
The Big Idea
Entity Sprites are the visual manifestation of your game world's interactivity. By coupling sprite data directly to entities, we create a simple, but still cache-friendly (for this game) system that should scale well with dozens, but not thousands, of entities per scene.
We are not wasting the CPU resources, as the data size of the amount of entities expected fits within L1 + L2 cache!