Skip to content

Components

Components are attached to Node objects. A component may also implement:

  • Drawable to render
  • Updatable to receive update(dt)
  • LateUpdatable to receive lateUpdate(dt) after all regular component updates

All components inherit from Component, which gives access to AppContext, Scene, AssetManager, Input, EventBus, and SaveSystem via helper functions.

Built-in Components

  • Transform (always present on every node)
  • Camera (orthographic 2D by default; perspective and orthographic 3D when enabled)
  • SpriteRenderer (draws a textured quad)
  • QuadRenderer (draws a solid-color quad)
  • TriangleRenderer (draws a solid-color triangle)
  • CircleRenderer (draws a solid-color circle)
  • LineRenderer (draws a thick polyline with optional smoothing and rounded caps)
  • FontRenderer (MSDF text rendering)
  • SpineRenderer (Spine skeletal animation)
  • EffekseerRenderer (Effekseer runtime effects)
  • TiledRenderer (Tiled JSON map rendering + query/mutation API)
  • ModelRenderer (static/skinned mesh rendering; SNOWPULSE_HAS_RENDERING3D)
  • Animator3D (clip playback, crossfading, GPU skinning, and sockets; SNOWPULSE_HAS_RENDERING3D)
  • DirectionalLight3D, PointLight3D, and SpotLight3D (forward lights; SNOWPULSE_HAS_RENDERING3D)
  • FbxRenderer (deprecated compatibility wrapper over ModelRenderer)
  • ParticleEffects (2D particle emitter)
  • Trail (vertex trail that follows node movement)
  • RigidBody (2D physics body)
  • PhysicsCollider (2D collision shape/material/filter)
  • RigidBody3D, Collider3D, and CharacterController3D (Jolt physics; SNOWPULSE_HAS_PHYSICS3D)
  • NavigationAgent3D (Detour path planning and desired velocity; SNOWPULSE_HAS_NAVIGATION3D)
  • Script (base class with onStart/onUpdate)
  • ActionRunner (runs actions/tweens on a node)

FontRenderer supports MSDF text effects through effects.outline and effects.shadow. Effect widths, spreads, softness, and offsets use the same local units as fontSize and reuse the loaded font atlas. Very thick effects require a font atlas generated with enough MSDF distance range and padding.

Text uses the solid color by default. Enable gradient for a two-color vertical fill spanning the complete laid-out text block; outlines and shadows remain solid-colored:

auto* labelNode = scene()->createNode("label");
auto* label = labelNode->addComponent<snowpulse::FontRenderer>();
label->font(assetManager()->loadFont("fonts/interface.json"));
label->text = "Ready";
label->fontSize = 36.0f;
label->effects.outline.width = 2.0f;
label->effects.shadow.enabled = true;
label->effects.shadow.setDirection({ 1.0f, -1.0f }, 3.0f);
label->gradient.enabled = true;
label->gradient.topColor = { 1.0f, 0.96f, 0.82f, 1.0f };
label->gradient.bottomColor = { 0.96f, 0.68f, 0.22f, 1.0f };
scene()->rootNode()->addChild(labelNode);

FontRenderer uses RenderSpace::World by default. Set screen render space when its owning node's transform should use logical screen coordinates and remain unaffected by camera position, rotation, zoom, or shake:

label->renderSpace = snowpulse::FontRenderer::RenderSpace::Screen;
labelNode->transform().position = { 320.0f, 180.0f, 0.0f };

For a complete self-cleaning floating-text presentation with screen anchors, movement, fading, and either FontRenderer or UIText, use snowpulse::fx::ToastEffect from Game Feedback.

Primitive Renderers

QuadRenderer, TriangleRenderer, and CircleRenderer draw simple solid-color shapes without requiring textures. They are useful for prototypes, debug overlays, hit-area visualization, and simple VFX.

The examples in this section assume node is already attached to the scene tree. A quad needs only a size and color:

auto* panel = node->addComponent<snowpulse::QuadRenderer>();
panel->size = { 240.0f, 72.0f };
panel->color = { 0.08f, 0.12f, 0.18f, 0.9f };

TriangleRenderer renders exactly three local-space points transformed by the owner node. Degenerate triangles are skipped. Set points, color, sortOrder, and blendMode directly:

auto* triangle = node->addComponent<snowpulse::TriangleRenderer>();
triangle->points = {
    glm::vec2 { 0.0f, 0.0f },
    glm::vec2 { 120.0f, 0.0f },
    glm::vec2 { 60.0f, 90.0f }
};
triangle->color = { 1.0f, 0.6f, 0.1f, 1.0f };
triangle->sortOrder = 5;
triangle->blendMode = snowpulse::BlendMode::Normal;

CircleRenderer draws a full circle by default. Set fillAmount between 0.0f and 1.0f to draw a radial sector, with angles measured in degrees from the positive x-axis:

auto* timer = node->addComponent<snowpulse::CircleRenderer>();
timer->radius = 16.0f;
timer->color = { 1.0f, 0.0f, 0.0f, 1.0f };
timer->fillAmount = 0.5f;
timer->radialStartAngle = 90.0f;
timer->radialDirection = snowpulse::CircleRadialDirection::Clockwise;

Fill amounts are clamped to the valid range. A value of 0.0f draws nothing, while 1.0f uses the normal full-circle rendering path.

Line Renderer

LineRenderer turns local-space anchor points into a thick triangle strip. It can smooth corners with cubic Bézier sampling and optionally add rounded end caps.

auto* route = node->addComponent<snowpulse::LineRenderer>();
route->thickness = 10.0f;
route->color = { 0.2f, 0.8f, 1.0f, 0.9f };
route->useBezierCorners = true;
route->useRoundedEndCaps = true;
route->smoothing = 0.75f;
route->sampleStep = 12.0f;
route->setAnchorPoints({
    { 0.0f, 0.0f, 0.0f },
    { 80.0f, 40.0f, 0.0f },
    { 160.0f, 0.0f, 0.0f },
});

Use clear() to remove all points. Fewer than two points, or non-positive thickness, produces no geometry.

3D Models

ModelRenderer is available when the target selects RENDERING3D ON through snowpulse_add_game() or snowpulse_target_link_engine(). Loading FBX, glTF, or GLB files additionally requires ASSIMP ON. It submits static or skinned mesh instances with material overrides, render-layer filtering, and cast/receive-shadow state.

auto* model = node->addComponent<snowpulse::ModelRenderer>();
model->color = { 1.0f, 1.0f, 1.0f, 1.0f };
model->castsShadows = true;
model->receivesShadows = true;

model->load("models/character.glb");

Attach Animator3D for named clip playback, crossfading, skinning, and socket poses. The deprecated FbxRenderer wrapper remains available for existing code that combines loading and legacy animation playback.

DirectionalLight3D, PointLight3D, and SpotLight3D light these models. Jolt's RigidBody3D, Collider3D, and CharacterController3D are controlled by the independent PHYSICS3D switch. NavigationAgent3D is controlled by NAVIGATION3D; it plans a path and produces desired velocity but does not move the node or replace collision handling. See Forward-rendered 3D for the complete build, camera, material, physics, and navigation contracts.

On that deprecated wrapper, animationNames() lists clips in the base model and addAnimationClips() can load additional clips from another FBX file when its skeleton bone layout matches the base model; an optional prefix prevents name collisions.

Camera

The scene's active Camera exposes zoom and transient shake directly:

auto* activeCamera = scene()->camera();
activeCamera->zoom = 1.25f;
activeCamera->shakeFrequency = 30.0f;
activeCamera->shake(8.0f, 0.25f);

if (activeCamera->isShaking()) {
    // Avoid starting another shake if the current one should finish first.
}

Particles and Trails

ParticleEffects can load an effect document, be controlled at runtime, and report its active state. Trail records movement of its owner node.

auto* emitterNode = scene()->createNode("emitter");
auto* emitter = emitterNode->addComponent<snowpulse::ParticleEffects>();
scene()->rootNode()->addChild(emitterNode);

if (emitter->effects("effects/burst.json")) {
    emitter->play(true);
}
emitter->emit(4);  // Emit an additional burst without rebuilding the component.

auto* trailNode = scene()->createNode("trail");
auto* trail = trailNode->addComponent<snowpulse::Trail>();
trail->width = 10.0f;
trail->lifetime = 0.45f;
trail->color = { 0.3f, 0.8f, 1.0f, 0.8f };
scene()->rootNode()->addChild(trailNode);

Script Helpers

Script includes helpers for quick node creation:

  • createNodeAndComponent<T>(name, ...) creates a detached scene-owned node and adds a component; parent the returned component's owner explicitly.
  • addChildNodeAndComponent<T>(name, ...) does the same, then parents the node under the script owner.
class Spawner final : public snowpulse::Script {
public:
    void onStart() override {
        auto* fx = createNodeAndComponent<snowpulse::ParticleEffects>("spawn_fx");
        fx->effects("effects/spawn.json");
        addChild(fx->owner());

        auto* badge = addChildNodeAndComponent<snowpulse::SpriteRenderer>("badge");
        badge->sprite(assetManager()->loadSprite("ui_badge.png"));
    }
};

Late Update Components

Use LateUpdatable for behavior that should run after movement/physics and other normal Updatable logic in the same frame (for example, camera follow that must read final target positions).

class CameraFollow final : public snowpulse::Script, public snowpulse::LateUpdatable {
public:
    void onUpdate(const float dt) override {
        (void)dt;
        // Normal application update.
    }

    void lateUpdate(const float dt) override {
        (void)dt;
        // runs after all Updatable components
    }
};

Sample Usage

auto* node = scene()->createNode("content");
node->transform().position = { 0.0f, 0.0f, 0.0f };

auto* sprite = node->addComponent<snowpulse::SpriteRenderer>();
sprite->sprite(assetManager()->loadSprite("images/icon.png"));

auto* spine = node->addComponent<snowpulse::SpineRenderer>();
spine->load("spine/character/character.atlas",
            "spine/character/character.json", 1.0f);
spine->setAnimation(0, "loop", true);

auto* tiled = node->addComponent<snowpulse::TiledRenderer>();
tiled->load("maps/layout.tmj");

#if SNOWPULSE_HAS_EFFEKSEER
// Also include <runtime/components/effekseer_renderer.h> when this feature is enabled.
auto* efk = node->addComponent<snowpulse::EffekseerRenderer>();
efk->sortOrder = 8;
efk->load("effects/sample.efkefc", 1.0f);
efk->playPrimary();
#endif

scene()->rootNode()->addChild(node);