Skip to content

Styling

Styling components modify how entities are rendered without changing their shape or content. These components work with sprites, shapes, and text.

Color

The Color component applies RGBA tinting to sprites, shapes, and text.

teal
gfx.Color(r?, g?, b?, a?)

Parameters:

ParameterTypeDefaultDescription
rnumber1.0Red component (0.0-1.0)
gnumber1.0Green component (0.0-1.0)
bnumber1.0Blue component (0.0-1.0)
anumber1.0Alpha component (0.0-1.0)

Examples:

teal
local gfx = require("tecs2d.gfx")

-- Red tint
world:spawn(
    tecs.builtins.Transform(100, 100),
    gfx.Circle(20),
    gfx.Color(1, 0, 0, 1)
)

-- Semi-transparent white
world:spawn(
    tecs.builtins.Transform(200, 100),
    gfx.Rectangle(50, 50),
    gfx.Color(1, 1, 1, 0.5)
)

-- No color component = full white (1, 1, 1, 1)
world:spawn(
    tecs.builtins.Transform(300, 100),
    gfx.Circle(20)
)

Modifying Color

Tecs syncs color changes only when the Color component is detected as dirty. See Dirty Tracking for details. There are two approaches:

Option 1: Replace the component in the archetype

This is preferred if you have the archetype and row.

teal
archetype:set(row, Color(r, g, b, a))

Option 2: Replace the component in the world

This is useful if you just have the entity ID.

teal
world:set(entityId, Color(r, g, b, a))

Option 3: Modify in place via getMut

If you have an archetype and row:

teal
local colors = archetype:getMut(Color)
local color = colors[row]
color.r = 0.5
color.g = 1.0
color.b = 0.5
color.a = 1.0

getMut flags Color dirty on the archetype, so the renderer re-uploads it next frame. If you have just the entity ID:

teal
world:markComponentDirty(entityId, Color)

Blend Modes

Blend mode components control how an entity's pixels combine with the background. Each blend mode is a separate tag component. Entities without a blend component use normal alpha blending.

Blend mode components are stored in tecs2d.gfx.blend:

teal
local gfx = require("tecs2d.gfx")
local blend = gfx.blend
ComponentDescription
AdditiveBlendAdds source and destination colors. Use for glow, fire, light beams, particles.
AdditiveBlendPremulAdditive blend for pre-multiplied alpha textures.
MultiplyBlendMultiplies source and destination colors. Use for shadows, tinting, darkening.
MultiplyBlendPremulMultiply blend for pre-multiplied alpha textures.
ScreenBlendInverse of multiply; lightens the image. Use for soft glow and highlights.
ScreenBlendPremulScreen blend for pre-multiplied alpha textures.
SubtractBlendSubtracts source from destination. Use for color removal and special darkening.
SubtractBlendPremulSubtract blend for pre-multiplied alpha textures.
LightenBlendTakes the maximum of each color channel. Use for dodge and highlight effects.
LightenBlendPremulLighten blend for pre-multiplied alpha textures.
DarkenBlendTakes the minimum of each color channel. Use for burn and shadow effects.
DarkenBlendPremulDarken blend for pre-multiplied alpha textures.
ReplaceBlendOverwrites the destination completely. Use when you need exact color output.
ReplaceBlendPremulReplace blend for pre-multiplied alpha textures.
Why 14 blend mode components?

Blend modes are tag components rather than a single component with a "mode" field. This allows the ECS to group entities by blend mode into separate archetypes. Since all entities in an archetype share the same blend mode, the renderer can batch them into a single draw call without state changes.

Using Blend Modes

Blend modes are mutually exclusive: adding a blend component automatically removes any existing blend component from the entity.

Adding a blend mode when spawning:

teal
world:spawn(
    tecs.builtins.Transform(100, 100),
    gfx.Circle(30),
    gfx.Color(1, 0.8, 0.2, 0.9),
    blend.AdditiveBlend()
)

Changing a blend mode on an existing entity (the old blend component is removed automatically):

teal
world:set(id, blend.MultiplyBlend())

Removing a blend mode to return to normal alpha blending:

teal
world:remove(id, blend.MultiplyBlend)

Materials

Blend mode components are not compatible with Materials. Materials render through the deferred G-buffer pipeline, while blend modes use a forward pass after lighting. Entities with both a Material and a blend mode component will not render.

Unlit

The Unlit tag component opts an entity out of dynamic lighting. Entities with Unlit render at full brightness regardless of nearby light sources or ambient light. Use it for UI elements, light emitters that already encode their own brightness in their color, and any entity where shading would be wrong.

teal
world:spawn(
    tecs.builtins.Transform(10, 10),
    gfx.Rectangle(200, 50),
    gfx.Color(0.2, 0.2, 0.2, 0.9),
    gfx.Unlit
)

Unlit carries no data, so adding it is a single argument. To toggle at runtime:

teal
world:add(entityId, gfx.Unlit)     -- make unlit
world:remove(entityId, gfx.Unlit)  -- restore normal lighting

Unlit composes with the per-layer unlit mask (pipeline:setLayerUnlit(layer, true)): an entity is unlit if either the tag is on the entity or the layer is unlit.

Geometry style

SDF shapes (Circle, Rectangle, Ellipse, Arc, Line) render with anti-aliased edges by default. The pipeline-level roughGeometry flag flips the entire scene to hard pixel cutoffs, suitable for pixel-art aesthetics where AA gradients would soften the look.

teal
local pipeline = gfx.newPipeline({
    world = world,
    roughGeometry = true,  -- hard pixel edges
})

Toggle at runtime:

teal
pipeline:setRoughGeometry(true)
local rough = pipeline:getRoughGeometry()

roughGeometry is independent of pixelMode (the integer-scale upscale). Combine them for retro pixel-art, or use either alone.

Pivot

The Pivot component controls the origin point for rendering and rotation. Values are normalized (0-1 range).

teal
gfx.Pivot(x?, y?)

Parameters:

ParameterTypeDefaultDescription
xnumber0.5Horizontal pivot (0=left, 0.5=center, 1=right)
ynumber0.5Vertical pivot (0=top, 0.5=center, 1=bottom)

Examples:

teal
-- Rectangle anchored at top-left
world:spawn(
    tecs.builtins.Transform(0, 0),
    gfx.Rectangle(100, 50),
    gfx.Pivot(0, 0)
)

-- Sprite rotating around its feet
world:spawn(
    tecs.builtins.Transform(200, 300, 0, 1, { rotation = math.pi / 4 }),
    gfx.Sprite.fromAseprite("character.png", "idle"),
    gfx.Pivot(0.5, 1)  -- Bottom-center
)

Dirty Tracking

Changing Pivot at runtime requires dirty tracking to take effect.

Aseprite Slices

Sprites loaded with pivotSlice option use per-frame pivot points from Aseprite slices, which override the Pivot component.

Combining Styling Components

Styling components can be combined freely:

teal
-- Glowing additive circle
world:spawn(
    tecs.builtins.Transform(100, 100),
    gfx.Circle(30),
    gfx.Color(1, 0.8, 0.2, 0.9),
    gfx.blend.AdditiveBlend()
)

-- UI button (unlit, rounded, with custom pivot)
world:spawn(
    tecs.builtins.Transform(400, 20),
    gfx.Rectangle(120, 40),
    gfx.RoundedCorners(8),
    gfx.Color(0.3, 0.5, 0.9, 1),
    gfx.Unlit,
    gfx.Pivot(1, 0)  -- Anchor top-right
)

See Also

  • Materials - GPU-batched fragment shader injection for per-entity visual effects (dissolve, glow, tinting)