Camera2D¶
follow a target with smoothing, zoom, and edge limits.
▶ Run in browserTags: 2d camera
A Camera2D decides which part of the 2D world is on screen. Add one to the scene and set its
target to a node, and the view keeps that node at the centre of the screen as it moves.
smoothingmakes the camera ease after its target instead of snapping to it: higher is tighter, and 0 snaps every frame.zoomscales the whole view; 1.0 is one world unit per screen pixel.limit_left,limit_right,limit_topandlimit_bottomclamp the camera’s centre. To stop the view at the edges of the world, inset them by half the visible area, which is half the viewport size divided by the zoom; this example redoes that every frame, so it stays right when the window is resized.screen_to_world(point)maps a screen position, such as the pointer, into world coordinates.
Everything a node draws in on_draw is in world space and scrolls with the camera. The help text
sits on a CanvasLayer, which pins it to the screen.
Move the square with WASD, the arrow keys or a gamepad stick, or hold the mouse button (or a finger) down and it heads for the pointer. The dots and the coloured landmarks scroll past while the camera follows, and the view stops at the edge of the world.
Source¶
1# /// simvx
2# tags = ["2d", "camera"]
3# web = { width = 800, height = 600, responsive = true }
4# ///
5"""Camera2D: follow a target with smoothing, zoom, and edge limits.
6
7A `Camera2D` decides which part of the 2D world is on screen. Add one to the scene and set its
8`target` to a node, and the view keeps that node at the centre of the screen as it moves.
9
10- `smoothing` makes the camera ease after its target instead of snapping to it: higher is
11 tighter, and 0 snaps every frame.
12- `zoom` scales the whole view; 1.0 is one world unit per screen pixel.
13- `limit_left`, `limit_right`, `limit_top` and `limit_bottom` clamp the camera's centre. To stop
14 the view at the edges of the world, inset them by half the visible area, which is half the
15 viewport size divided by the zoom; this example redoes that every frame, so it stays right
16 when the window is resized.
17- `screen_to_world(point)` maps a screen position, such as the pointer, into world coordinates.
18
19Everything a node draws in `on_draw` is in world space and scrolls with the camera. The help text
20sits on a `CanvasLayer`, which pins it to the screen.
21
22Move the square with WASD, the arrow keys or a gamepad stick, or hold the mouse button (or a
23finger) down and it heads for the pointer. The dots and the coloured landmarks scroll past while
24the camera follows, and the view stops at the edge of the world.
25"""
26
27from simvx.core import (
28 Anchor,
29 Axis2D,
30 Camera2D,
31 CanvasLayer,
32 Controller,
33 Input,
34 Label,
35 Node2D,
36 Pointer,
37)
38from simvx.graphics import App
39
40WORLD = 700 # the world spans -WORLD..WORLD on both axes
41SPEED = 320.0
42
43# (x, y, colour) landmarks so the scrolling has something to read against.
44LANDMARKS = [
45 (-400, -300, (0.9, 0.4, 0.4, 1)),
46 (400, 300, (0.4, 0.9, 0.5, 1)),
47 (400, -300, (0.9, 0.8, 0.3, 1)),
48 (-400, 300, (0.7, 0.5, 0.95, 1)),
49]
50
51
52class Player(Node2D):
53 def on_draw(self, renderer):
54 renderer.rect((-20, -20), (40, 40), fill=(0.4, 0.8, 1.0, 1.0))
55
56
57class CameraDemo(Node2D):
58 input_context = {
59 "move": [Axis2D.WASD, Axis2D.ARROWS, Controller.LEFT_STICK],
60 "steer": [Pointer.PRIMARY],
61 }
62
63 def on_ready(self):
64 self.player = self.add_child(Player())
65 self.camera = self.add_child(Camera2D(smoothing=6.0, zoom=1.0))
66 self.camera.target = self.player
67 self.add_child(CanvasLayer()).add_child(
68 Label("WASD / arrows / stick, or hold the pointer down to move", anchor=Anchor.TOP_LEFT, inset=20)
69 )
70
71 def on_update(self, dt):
72 step = SPEED * dt
73 if Input.pressed("steer"):
74 goal = self.camera.screen_to_world(Input.mouse_position)
75 position = self.player.position.move_toward(goal, step)
76 else:
77 position = self.player.position + Input.value("move") * step
78 self.player.position = position.clamped((-WORLD, -WORLD), (WORLD, WORLD))
79
80 # The limits clamp the camera's centre, so inset them by half the visible area.
81 half = self.viewport_size / (2 * self.camera.zoom)
82 self.camera.limit_left, self.camera.limit_top = -WORLD + half.x, -WORLD + half.y
83 self.camera.limit_right, self.camera.limit_bottom = WORLD - half.x, WORLD - half.y
84
85 def on_draw(self, renderer):
86 for x in range(-WORLD, WORLD + 1, 100):
87 for y in range(-WORLD, WORLD + 1, 100):
88 renderer.circle((x, y), 3, fill=(0.3, 0.3, 0.36, 1.0))
89 for x, y, colour in LANDMARKS:
90 renderer.rect((x - 30, y - 30), (60, 60), fill=colour)
91
92
93if __name__ == "__main__":
94 App(title="Camera2D", width=800, height=600).run(CameraDemo())