Blend Modes

Multiply day/night overlay + additive flash via Draw2D.

▶ Run in browser

Tags: 2d

Demonstrates the blend= keyword on the Draw2D colour ops:

  • "multiply" (dst * src): a full-screen tint that darkens and colour-shifts the scene like a day/night cycle. A 50% grey multiply halves the scene; a blue-tinted multiply pushes it toward night.

  • "add" (dst + src): an additive flash burst that brightens toward white, the standard glow / muzzle-flash / explosion idiom.

  • "alpha" (default): the normal over-composited scene underneath.

The scene is a row of opaque sprites (coloured rects) painted with the default alpha blend; the multiply overlay and additive flash are drawn on top in submission order.

Controls:

  • SPACE triggers an additive flash

  • ESC quits

Source

  1"""Blend Modes: Multiply day/night overlay + additive flash via Draw2D.
  2
  3Demonstrates the ``blend=`` keyword on the Draw2D colour ops:
  4
  5* ``"multiply"`` (dst * src): a full-screen tint that darkens and colour-shifts
  6  the scene like a day/night cycle. A 50% grey multiply halves the scene; a
  7  blue-tinted multiply pushes it toward night.
  8* ``"add"`` (dst + src): an additive flash burst that brightens toward white,
  9  the standard glow / muzzle-flash / explosion idiom.
 10* ``"alpha"`` (default): the normal over-composited scene underneath.
 11
 12The scene is a row of opaque sprites (coloured rects) painted with the default
 13alpha blend; the multiply overlay and additive flash are drawn on top in
 14submission order.
 15
 16# /// simvx
 17# web = { width = 960, height = 540, root = "BlendDemo" }
 18# screenshot_frame = 70
 19# ///
 20
 21Controls:
 22  - SPACE triggers an additive flash
 23  - ESC quits
 24"""
 25
 26import math
 27
 28from simvx.core import Input, InputMap, Key, Node2D
 29from simvx.graphics import App
 30
 31WIDTH, HEIGHT = 960, 540
 32
 33# A palette of opaque scene sprites (drawn with default alpha blend).
 34_SPRITES = [
 35    (0.90, 0.30, 0.25),
 36    (0.95, 0.70, 0.20),
 37    (0.30, 0.80, 0.40),
 38    (0.25, 0.55, 0.95),
 39    (0.65, 0.40, 0.90),
 40]
 41
 42
 43class BlendDemo(Node2D):
 44    """Multiply day/night tint + additive flash over an alpha-blended scene."""
 45
 46    # The day/night tint and flash burst animate every frame -> opt into per-frame
 47    # redraw so the retained 2D renderer re-runs on_draw instead of freezing.
 48    dynamic = True
 49
 50    def __init__(self, **kwargs):
 51        super().__init__(name="BlendDemo", **kwargs)
 52        self._t = 0.0
 53        self._flash = 0.0  # decaying additive-flash strength in [0, 1]
 54
 55    def on_ready(self):
 56        InputMap.add_action("flash", [Key.SPACE])
 57        InputMap.add_action("quit", [Key.ESCAPE])
 58
 59    def on_update(self, dt: float):
 60        self._t += dt
 61        # Auto-pulse a flash every ~3s so the demo animates without input, plus
 62        # the SPACE trigger for interactive use.
 63        if Input.is_action_just_pressed("flash") or math.fmod(self._t, 3.0) < dt:
 64            self._flash = 1.0
 65        self._flash = max(0.0, self._flash - dt * 1.8)
 66        if Input.is_action_just_pressed("quit"):
 67            self.app.quit()
 68
 69    def _night_factor(self) -> float:
 70        """0 at noon (no darkening) -> 1 at midnight (heavy blue multiply)."""
 71        return 0.5 - 0.5 * math.cos(self._t * 0.6)
 72
 73    def on_draw(self, renderer):
 74        # 1) Light background + a row of opaque sprites (default alpha blend).
 75        renderer.draw_rect((0, 0), (WIDTH, HEIGHT), filled=True, colour=(0.85, 0.88, 0.92))
 76        n = len(_SPRITES)
 77        gap = WIDTH / (n + 1)
 78        size = 120
 79        for i, col in enumerate(_SPRITES):
 80            cx = gap * (i + 1)
 81            renderer.draw_rect(
 82                (cx - size / 2, HEIGHT * 0.5 - size / 2),
 83                (size, size),
 84                filled=True,
 85                colour=col,
 86            )
 87
 88        # 2) Day/night MULTIPLY overlay. A blue-grey tint that deepens toward
 89        #    "midnight": multiply darkens and colour-shifts everything beneath.
 90        night = self._night_factor()
 91        tint = (
 92            1.0 - 0.75 * night,  # red drops most
 93            1.0 - 0.65 * night,
 94            1.0 - 0.35 * night,  # blue survives -> cool night tone
 95        )
 96        renderer.draw_rect((0, 0), (WIDTH, HEIGHT), filled=True, colour=tint, blend="multiply")
 97
 98        # 3) Additive FLASH burst, centred, brightening toward white.
 99        if self._flash > 0.001:
100            f = self._flash
101            renderer.draw_rect(
102                (0, 0),
103                (WIDTH, HEIGHT),
104                filled=True,
105                colour=(0.9 * f, 0.85 * f, 0.6 * f),
106                blend="add",
107            )
108
109        # HUD (default alpha text).
110        renderer.draw_text("BLEND MODES", (20, 16), scale=3, colour=(0.1, 0.1, 0.12))
111        renderer.draw_text(
112            "multiply = day/night tint   add = flash (SPACE)   ESC = quit",
113            (20, 60),
114            scale=2,
115            colour=(0.2, 0.2, 0.25),
116        )
117
118
119if __name__ == "__main__":
120    App(title="Blend Modes", width=WIDTH, height=HEIGHT).run(BlendDemo())