Scene Graph & Layering System
The layering system controls the render order of entities using the LayerComponent and RenderSystem.
Overview
Entities are rendered from lowest to highest layer value, allowing you to create depth through: - Background layers (parallax, skybox) - Game entities (players, enemies, projectiles) - Effects (particles, explosions) - UI/HUD overlays
LayerComponent
#include "components/LayerComponent.hpp"
struct LayerComponent
{
int layer = RenderLayer::Entities; // Default layer
static LayerComponent create(int layer);
};
Standard Render Layers
Predefined constants for common layer types:
namespace RenderLayer
{
constexpr int Background = -100; // Far background (parallax, skybox)
constexpr int Midground = -50; // Middle background elements
constexpr int Entities = 0; // Default layer for game entities
constexpr int Effects = 50; // Particle effects, explosions
constexpr int UI = 100; // UI elements
constexpr int HUD = 150; // HUD overlay (health bars, scores)
constexpr int Debug = 200; // Debug overlays, collision boxes
}
Render Order: Background → Midground → Entities → Effects → UI → HUD → Debug
Usage
Basic Layering
#include "components/LayerComponent.hpp"
#include "components/SpriteComponent.hpp"
#include "components/TransformComponent.hpp"
// Background entity
EntityId background = registry.createEntity();
registry.emplace<SpriteComponent>(background);
registry.emplace<TransformComponent>(background);
registry.emplace<LayerComponent>(background, LayerComponent::create(RenderLayer::Background));
// Player entity (default layer)
EntityId player = registry.createEntity();
registry.emplace<SpriteComponent>(player);
registry.emplace<TransformComponent>(player);
registry.emplace<LayerComponent>(player, LayerComponent::create(RenderLayer::Entities));
// HUD entity
EntityId healthBar = registry.createEntity();
registry.emplace<SpriteComponent>(healthBar);
registry.emplace<TransformComponent>(healthBar);
registry.emplace<LayerComponent>(healthBar, LayerComponent::create(RenderLayer::HUD));
Custom Layers
You can use any integer value for custom layering:
// Custom layers between standard ones
constexpr int LAYER_PLAYER = RenderLayer::Entities + 1;
constexpr int LAYER_ENEMY = RenderLayer::Entities + 2;
EntityId player = registry.createEntity();
// ... add components ...
registry.emplace<LayerComponent>(player, LayerComponent::create(LAYER_PLAYER));
EntityId enemy = registry.createEntity();
// ... add components ...
registry.emplace<LayerComponent>(enemy, LayerComponent::create(LAYER_ENEMY));
// Player renders first, then enemy
Default Layer
If an entity has no LayerComponent, it defaults to RenderLayer::Entities (0):
EntityId entity = registry.createEntity();
registry.emplace<SpriteComponent>(entity);
registry.emplace<TransformComponent>(entity);
// No LayerComponent - will render at layer 0 (Entities)
Complete Example
#include "components/LayerComponent.hpp"
#include "components/SpriteComponent.hpp"
#include "components/TransformComponent.hpp"
#include "ecs/Registry.hpp"
#include "systems/RenderSystem.hpp"
void setupGameScene(Registry& registry, TextureManager& textures)
{
// 1. Background layer - scrolling space
EntityId background = registry.createEntity();
auto& bgSprite = registry.emplace<SpriteComponent>(background);
bgSprite.setTexture(*textures.get("space_background"));
registry.emplace<TransformComponent>(background);
registry.emplace<LayerComponent>(background,
LayerComponent::create(RenderLayer::Background));
// 2. Entities layer - player
EntityId player = registry.createEntity();
auto& playerSprite = registry.emplace<SpriteComponent>(player);
playerSprite.setTexture(*textures.get("player_ship"));
registry.emplace<TransformComponent>(player);
registry.emplace<LayerComponent>(player,
LayerComponent::create(RenderLayer::Entities));
// 3. Entities layer - enemies
for (int i = 0; i < 5; ++i) {
EntityId enemy = registry.createEntity();
auto& enemySprite = registry.emplace<SpriteComponent>(enemy);
enemySprite.setTexture(*textures.get("enemy_ship"));
registry.emplace<TransformComponent>(enemy);
registry.emplace<LayerComponent>(enemy,
LayerComponent::create(RenderLayer::Entities));
}
// 4. Effects layer - particle systems
EntityId explosion = registry.createEntity();
auto& fxSprite = registry.emplace<SpriteComponent>(explosion);
fxSprite.setTexture(*textures.get("explosion"));
registry.emplace<TransformComponent>(explosion);
registry.emplace<LayerComponent>(explosion,
LayerComponent::create(RenderLayer::Effects));
// 5. HUD layer - health bar
EntityId healthBar = registry.createEntity();
auto& hudSprite = registry.emplace<SpriteComponent>(healthBar);
hudSprite.setTexture(*textures.get("health_bar"));
auto& hudTransform = registry.emplace<TransformComponent>(healthBar);
hudTransform.x = 10.0F; // Fixed position
hudTransform.y = 10.0F;
registry.emplace<LayerComponent>(healthBar,
LayerComponent::create(RenderLayer::HUD));
// 6. Debug layer - collision boxes (optional)
#ifdef DEBUG_DRAW
EntityId debugBox = registry.createEntity();
auto& debugSprite = registry.emplace<SpriteComponent>(debugBox);
debugSprite.setTexture(*textures.get("debug_box"));
registry.emplace<TransformComponent>(debugBox);
registry.emplace<LayerComponent>(debugBox,
LayerComponent::create(RenderLayer::Debug));
#endif
}
RenderSystem Integration
The RenderSystem automatically sorts entities by layer before rendering:
// In RenderSystem::update()
// 1. Collect all entities with SpriteComponent + TransformComponent
// 2. Get their LayerComponent (or default to 0)
// 3. Sort by layer (stable_sort)
// 4. Render in order
This means you don't need to worry about creation order - entities are always rendered in the correct layer order.
Best Practices
Layer Organization
Group related entities in the same layer range:
namespace GameLayers
{
// Background
constexpr int FAR_BG = -150;
constexpr int MID_BG = -100;
constexpr int NEAR_BG = -50;
// Entities
constexpr int GROUND = -10;
constexpr int PLAYER = 0;
constexpr int ENEMY = 5;
constexpr int BULLET = 10;
// Effects
constexpr int PARTICLE = 50;
constexpr int EXPLOSION = 55;
// UI
constexpr int UI_BG = 100;
constexpr int UI_ICON = 110;
constexpr int UI_TEXT = 120;
}
HUD Elements
Always use high layer values for HUD to ensure they appear on top:
// Good
registry.emplace<LayerComponent>(hud, LayerComponent::create(RenderLayer::HUD));
// Bad - might be covered by effects
registry.emplace<LayerComponent>(hud, LayerComponent::create(RenderLayer::Entities));
Parallax Backgrounds
Use multiple background layers with different scroll speeds:
EntityId farBg = registry.createEntity();
// ...
registry.emplace<LayerComponent>(farBg, LayerComponent::create(RenderLayer::Background));
registry.emplace<VelocityComponent>(farBg, 0.2F, 0.0F); // Slow
EntityId nearBg = registry.createEntity();
// ...
registry.emplace<LayerComponent>(nearBg, LayerComponent::create(RenderLayer::Midground));
registry.emplace<VelocityComponent>(nearBg, 0.5F, 0.0F); // Faster
Dynamic Layer Changes
You can change an entity's layer at runtime:
auto& layer = registry.get<LayerComponent>(entity);
layer.layer = RenderLayer::Effects; // Move to effects layer
Performance
- Sorting: O(n log n) per frame where n = visible entities
- Stable Sort: Preserves order of entities within the same layer
- Optimization: Consider batching entities by texture within each layer
Common Patterns
Scene Layers
// Level background
layerBackground = LayerComponent::create(RenderLayer::Background);
// Game world
layerEntities = LayerComponent::create(RenderLayer::Entities);
// Effects over entities
layerEffects = LayerComponent::create(RenderLayer::Effects);
// UI always on top
layerUI = LayerComponent::create(RenderLayer::UI);
Debug Visualization
#ifdef DEBUG_MODE
EntityId debugEntity = registry.createEntity();
// ... setup ...
registry.emplace<LayerComponent>(debugEntity,
LayerComponent::create(RenderLayer::Debug));
#endif
Troubleshooting
Entity Not Rendering
Check layer values:
if (registry.has<LayerComponent>(entity)) {
auto& layer = registry.get<LayerComponent>(entity);
std::cout << "Entity layer: " << layer.layer << std::endl;
}
Wrong Render Order
Verify layer constants are in correct order:
assert(RenderLayer::Background < RenderLayer::Entities);
assert(RenderLayer::Entities < RenderLayer::HUD);
HUD Behind Entities
Make sure HUD uses a high layer value: