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, the arrows, or by holding the left mouse
button (tap and hold on touch screens) 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, the arrows, or by holding the left mouse
7button (tap and hold on touch screens) and watch the grid scroll underneath it while
8the cube stays near the centre.
9
10# /// simvx
11# tags = ["2d", "camera"]
12# web = { root = "CameraDemo", width = 800, height = 600, responsive = true }
13# ///
14
15## What it demonstrates
16
17- `Camera2D` + `camera.target = node` -- the renderer tracks the target's position.
18- `smoothing` -- ease toward the target instead of snapping (0 = instant).
19- `zoom` -- scale the whole view.
20- `limit_left/right/top/bottom` -- clamp the camera so it never shows past the world edges.
21- World-space drawing (the grid, camera-affected) vs a screen-fixed HUD (a `Text2D` node).
22"""
23
24import math
25
26from simvx.core import Camera2D, Input, Key, MouseButton, Node2D, Text2D, Vec2
27from simvx.graphics import App
28
29WIDTH, HEIGHT = 800, 600
30WORLD = 700 # half-size of the playable world
31
32# World-space landmarks (x, y, colour) so the scroll has something to read against.
33LANDMARKS = [
34 (-400, -300, (0.9, 0.4, 0.4, 1)),
35 (400, 300, (0.4, 0.9, 0.5, 1)),
36 (400, -300, (0.9, 0.8, 0.3, 1)),
37 (-400, 300, (0.7, 0.5, 0.95, 1)),
38]
39
40
41class Player(Node2D):
42 SPEED = 320.0
43
44 camera: Camera2D | None = None # set by CameraDemo; used to map the pointer into world space
45
46 def on_update(self, dt: float):
47 dx = Input.get_strength("move_right") - Input.get_strength("move_left")
48 dy = Input.get_strength("move_down") - Input.get_strength("move_up")
49 self.position += Vec2(dx, dy) * self.SPEED * dt
50 # Mouse/touch: hold the left button (a touch on the web) to steer toward the
51 # pointer; the camera converts the screen position into world coordinates.
52 if self.camera is not None and Input.is_mouse_button_pressed(MouseButton.LEFT):
53 target = self.camera.screen_to_world(Input.mouse_position, Vec2(self.app.width, self.app.height))
54 offset = target - self.position
55 dist = math.hypot(offset.x, offset.y)
56 if dist > 4.0: # dead zone so the cube settles under the pointer
57 self.position += offset * (min(self.SPEED * dt, dist) / dist)
58 self.position.x = max(-WORLD, min(WORLD, self.position.x))
59 self.position.y = max(-WORLD, min(WORLD, self.position.y))
60
61 def on_draw(self, renderer):
62 renderer.draw_rect(
63 (self.position.x - 20, self.position.y - 20),
64 (40, 40),
65 colour=(0.4, 0.8, 1.0, 1.0),
66 filled=True,
67 )
68
69
70class CameraDemo(Node2D):
71 dynamic = True # world-space grid scrolls as the followed camera moves
72
73 input_actions = {
74 "move_left": [Key.A, Key.LEFT],
75 "move_right": [Key.D, Key.RIGHT],
76 "move_up": [Key.W, Key.UP],
77 "move_down": [Key.S, Key.DOWN],
78 }
79
80 def on_ready(self):
81 self.player = self.add_child(Player(position=Vec2(0, 0)))
82
83 cam = self.add_child(Camera2D())
84 cam.target = self.player # follow the player
85 cam.smoothing = 6.0 # ease after it (0 = snap)
86 cam.zoom = 1.0
87 # Stop the camera at the world edges (view half-size is screen/2 / zoom).
88 # These are computed from the launch size, so a resized window shows a little
89 # past the edge; recompute them from the live size if that matters to you.
90 cam.limit_left = -WORLD + WIDTH / 2
91 cam.limit_right = WORLD - WIDTH / 2
92 cam.limit_top = -WORLD + HEIGHT / 2
93 cam.limit_bottom = WORLD - HEIGHT / 2
94 self.player.camera = cam # lets the player map pointer clicks into world space
95
96 # Screen-fixed HUD: a Text2D node renders as an overlay, ignoring the camera.
97 self.add_child(
98 Text2D(
99 text="Camera2D: WASD/arrows or click/tap to move (camera follows)",
100 position=Vec2(20, 20),
101 font_scale=1.5,
102 colour=(1, 1, 1, 1),
103 )
104 )
105
106 def on_draw(self, renderer):
107 # World-space grid + landmarks: these are drawn in world coordinates, so the
108 # camera transform scrolls them as the player moves.
109 for gx in range(-WORLD, WORLD + 1, 100):
110 for gy in range(-WORLD, WORLD + 1, 100):
111 renderer.draw_circle((gx, gy), 3, colour=(0.3, 0.3, 0.36, 1.0), filled=True)
112 for lx, ly, col in LANDMARKS:
113 renderer.draw_rect((lx - 30, ly - 30), (60, 60), colour=col, filled=True)
114
115
116if __name__ == "__main__":
117 App(title="Camera2D", width=WIDTH, height=HEIGHT).run(CameraDemo())