Components¶
Components are attached to Node objects. A component may also implement:
Drawableto renderUpdatableto receiveupdate(dt)LateUpdatableto receivelateUpdate(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(2D view + shake)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)FbxRenderer(optional static/skinned FBX model rendering and animation)ParticleEffects(2D particle emitter)Trail(vertex trail that follows node movement)RigidBody(2D physics body)PhysicsCollider(2D collision shape/material/filter)Script(base class withonStart/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.
FBX Models¶
FbxRenderer is available when the target selects ASSIMP ON through
snowpulse_add_game() or snowpulse_target_link_engine(). It renders static and
skinned FBX submeshes through the model render queue and uses the active
scene's ambient and directional lighting.
auto* model = node->addComponent<snowpulse::FbxRenderer>();
model->sortOrder = 20;
model->color = { 1.0f, 1.0f, 1.0f, 1.0f };
model->animationSpeed = 1.0f;
if (model->load("models/character.fbx")) {
model->playAnimation("Idle", true);
}
animationNames() lists clips in the base model. 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);