Camera2D¶
follow a target with smoothing, zoom, and edge limits.
▶ Run in browserTags: 2d camera
Add a Camera2D and point its target at a node; the view then follows that node.
smoothing adds lag so the camera eases after the player instead of snapping, zoom
scales the view, and the limit_* properties stop the camera from showing past the
edges of your world. Move the cube with WASD or the arrows and watch the grid scroll
underneath it while the cube stays near the centre.
What it demonstrates¶
Camera2D+camera.target = node– the renderer tracks the target’s position.smoothing– ease toward the target instead of snapping (0 = instant).zoom– scale the whole view.limit_left/right/top/bottom– clamp the camera so it never shows past the world edges.World-space drawing (the grid, camera-affected) vs a screen-fixed HUD (a
Text2Dnode).
Source¶
1"""Camera2D: follow a target with smoothing, zoom, and edge limits.
2
3Add a `Camera2D` and point its `target` at a node; the view then follows that node.
4`smoothing` adds lag so the camera eases after the player instead of snapping, `zoom`
5scales the view, and the `limit_*` properties stop the camera from showing past the
6edges of your world. Move the cube with WASD or the arrows and watch the grid scroll
7underneath it while the cube stays near the centre.
8
9# /// simvx
10# tags = ["2d", "camera"]
11# web = { root = "CameraDemo", width = 800, height = 600, responsive = true }
12# ///
13
14## What it demonstrates
15
16- `Camera2D` + `camera.target = node` -- the renderer tracks the target's position.
17- `smoothing` -- ease toward the target instead of snapping (0 = instant).
18- `zoom` -- scale the whole view.
19- `limit_left/right/top/bottom` -- clamp the camera so it never shows past the world edges.
20- World-space drawing (the grid, camera-affected) vs a screen-fixed HUD (a `Text2D` node).
21"""
22
23from simvx.core import Camera2D, Input, Key, Node2D, Text2D, Vec2
24from simvx.graphics import App
25
26WIDTH, HEIGHT = 800, 600
27WORLD = 700 # half-size of the playable world
28
29
30class Player(Node2D):
31 SPEED = 320.0
32
33 def on_update(self, dt: float):
34 dx = Input.get_strength("move_right") - Input.get_strength("move_left")
35 dy = Input.get_strength("move_down") - Input.get_strength("move_up")
36 self.position += Vec2(dx, dy) * self.SPEED * dt
37 self.position.x = max(-WORLD, min(WORLD, self.position.x))
38 self.position.y = max(-WORLD, min(WORLD, self.position.y))
39
40 def on_draw(self, renderer):
41 renderer.draw_rect((self.position.x - 20, self.position.y - 20), (40, 40), colour=(0.4, 0.8, 1.0, 1.0), filled=True)
42
43
44class CameraDemo(Node2D):
45 input_actions = {
46 "move_left": [Key.A, Key.LEFT],
47 "move_right": [Key.D, Key.RIGHT],
48 "move_up": [Key.W, Key.UP],
49 "move_down": [Key.S, Key.DOWN],
50 }
51
52 def on_ready(self):
53 self.player = self.add_child(Player(position=Vec2(0, 0)))
54
55 cam = self.add_child(Camera2D())
56 cam.target = self.player # follow the player
57 cam.smoothing = 6.0 # ease after it (0 = snap)
58 cam.zoom = 1.0
59 # Stop the camera at the world edges (view half-size is screen/2 / zoom).
60 cam.limit_left = -WORLD + WIDTH / 2
61 cam.limit_right = WORLD - WIDTH / 2
62 cam.limit_top = -WORLD + HEIGHT / 2
63 cam.limit_bottom = WORLD - HEIGHT / 2
64
65 # Screen-fixed HUD: a Text2D node renders as an overlay, ignoring the camera.
66 self.add_child(Text2D(text="Camera2D: WASD to move (camera follows)", position=Vec2(20, 20), font_scale=1.5, colour=(1, 1, 1, 1)))
67
68 def on_draw(self, renderer):
69 # World-space grid + landmarks: these are drawn in world coordinates, so the
70 # camera transform scrolls them as the player moves.
71 for gx in range(-WORLD, WORLD + 1, 100):
72 for gy in range(-WORLD, WORLD + 1, 100):
73 renderer.draw_circle((gx, gy), 3, colour=(0.3, 0.3, 0.36, 1.0), filled=True)
74 for (lx, ly, col) in [(-400, -300, (0.9, 0.4, 0.4, 1)), (400, 300, (0.4, 0.9, 0.5, 1)), (400, -300, (0.9, 0.8, 0.3, 1)), (-400, 300, (0.7, 0.5, 0.95, 1))]:
75 renderer.draw_rect((lx - 30, ly - 30), (60, 60), colour=col, filled=True)
76
77
78if __name__ == "__main__":
79 App(title="Camera2D", width=WIDTH, height=HEIGHT).run(CameraDemo())