Blend Modes¶
Multiply day/night overlay + additive flash via Draw2D.
▶ Run in browserTags: 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())