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    def __init__(self, **kwargs):
 47        super().__init__(name="BlendDemo", **kwargs)
 48        self._t = 0.0
 49        self._flash = 0.0  # decaying additive-flash strength in [0, 1]
 50
 51    def on_ready(self):
 52        InputMap.add_action("flash", [Key.SPACE])
 53        InputMap.add_action("quit", [Key.ESCAPE])
 54
 55    def on_update(self, dt: float):
 56        self._t += dt
 57        # Auto-pulse a flash every ~3s so the demo animates without input, plus
 58        # the SPACE trigger for interactive use.
 59        if Input.is_action_just_pressed("flash") or math.fmod(self._t, 3.0) < dt:
 60            self._flash = 1.0
 61        self._flash = max(0.0, self._flash - dt * 1.8)
 62        if Input.is_action_just_pressed("quit"):
 63            self.app.quit()
 64
 65    def _night_factor(self) -> float:
 66        """0 at noon (no darkening) -> 1 at midnight (heavy blue multiply)."""
 67        return 0.5 - 0.5 * math.cos(self._t * 0.6)
 68
 69    def on_draw(self, renderer):
 70        # 1) Light background + a row of opaque sprites (default alpha blend).
 71        renderer.draw_rect((0, 0), (WIDTH, HEIGHT), filled=True, colour=(0.85, 0.88, 0.92))
 72        n = len(_SPRITES)
 73        gap = WIDTH / (n + 1)
 74        size = 120
 75        for i, col in enumerate(_SPRITES):
 76            cx = gap * (i + 1)
 77            renderer.draw_rect(
 78                (cx - size / 2, HEIGHT * 0.5 - size / 2), (size, size),
 79                filled=True, colour=col,
 80            )
 81
 82        # 2) Day/night MULTIPLY overlay. A blue-grey tint that deepens toward
 83        #    "midnight": multiply darkens and colour-shifts everything beneath.
 84        night = self._night_factor()
 85        tint = (
 86            1.0 - 0.75 * night,            # red drops most
 87            1.0 - 0.65 * night,
 88            1.0 - 0.35 * night,            # blue survives -> cool night tone
 89        )
 90        renderer.draw_rect((0, 0), (WIDTH, HEIGHT), filled=True, colour=tint, blend="multiply")
 91
 92        # 3) Additive FLASH burst, centred, brightening toward white.
 93        if self._flash > 0.001:
 94            f = self._flash
 95            renderer.draw_rect(
 96                (0, 0), (WIDTH, HEIGHT),
 97                filled=True, colour=(0.9 * f, 0.85 * f, 0.6 * f), blend="add",
 98            )
 99
100        # HUD (default alpha text).
101        renderer.draw_text("BLEND MODES", (20, 16), scale=3, colour=(0.1, 0.1, 0.12))
102        renderer.draw_text(
103            "multiply = day/night tint   add = flash (SPACE)   ESC = quit",
104            (20, 60), scale=2, colour=(0.2, 0.2, 0.25),
105        )
106
107
108if __name__ == "__main__":
109    App(title="Blend Modes", width=WIDTH, height=HEIGHT).run(BlendDemo())