6 2D Nodes
dxtr edited this page 2026-10-20 10:09:08 +02:00

2D Nodes

The system pyssel/scene-2d gives the scene tree its 2D nodes, which draw with pyssel/2d in the tree's order: the walk game of the Scene Tree page is made of them.

  • 2D nodes: run starts an engine whose tree's root is a viewport. A canvas-item draws itself in draw with pyssel/2d's functions, and keeps the drawing until queue-redraw; a node-2d adds a position, rotation and scale, which apply to its children. Its position and scale are cl-gmath dvec2 values, vectors of double-floats, as Godot's are in its double-precision builds, so that a node a million units out still moves by a hundredth: node-position returns a copy, or fills a vector given, and (setf node-position) copies one in, a dvec2 and not a vec2. node-x, node-y, node-scale-x, node-scale-y and node-rotation read one number as a double-float and set it from any real, as (incf (s2d:node-x player) step) does. They are inline, so that the doubles stay unboxed and moving a node allocates nothing. A real is taken as the double-float it is: Lisp reads 0.1 as a single-float, 0.10000000149011612 as a double, so write 0.1d0 for a tenth. Canvas items are drawn in the tree's order, children over their parents. Hiding a node draws it again; moving, turning or stretching one draws nothing again, as its canvas is placed anew by its global transform as it is drawn, but what it drew on the pixels, as raster text, when it moves off them, and what it drew for the scale, as a polygon's anti-aliased edges, when it turns or stretches. That placement is worked out in double-floats, so a node a million units out, under a view panned there, is drawn as precisely as one near the origin. A canvas item draws in either mode, whatever its class, so draw draws what the node's properties say: while a game's scene is edited, as in the editor, its nodes do not behave and ready has not run, so a draw method that draws what ready makes asks node-behaves-p first and draws less while it is false. A drawing that fails keeps what the item drew before, canvas-item-draw-error says why, and the editor's Scene dock marks the node. A viewport's size, viewport-size, is its content's, which its controls are laid out in: its window's, unless its content scale stretches it (below). Its title is its window's, viewport-title. A game sets both through its tree's root rather than its engine, so it does the same wherever it plays: (setf viewport-title), and set-viewport-size, which asks for the window's size, as set-engine-size does (see Engines). So it goes fullscreen, with (setf viewport-fullscreen), and holds the pointer, with (setf viewport-pointer-mode): :confined keeps it in the window, :captured hides it and keeps it still, its motion coming as a motion-event's event-relative-x and event-relative-y, the mouse's own, as a camera that follows the mouse wants. The window holds the pointer while it has the focus, so the mode lasts until set again. A canvas-item-clip method clips a canvas item's descendants to a rectangle in its coordinates, as a scroll container shows part of its content; the clips of nested items intersect. global-transform returns the cl-gmath dmat3x2, of double-floats, that takes a node's coordinates to its canvas's, as Godot's get_global_transform: the canvas of the canvas layer it is under, or else its viewport's own; canvas-transform takes the canvas on to the viewport's coordinates, and global-transform-with-canvas does both. local-transform is a node's own.

  • Picking, as Godot's editor picks: (s2d:canvas-items-at viewport x y) lists the canvas items under a point of a viewport's coordinates, topmost first, children over their parents and the items of higher canvas layers over those of lower ones, each through its canvas's transform, and (s2d:canvas-items-in-rect viewport x y width height) those whose rectangles lie wholly inside a rectangle, as a drag selects them. Both meet only what the viewport draws: its shown items, not those of the viewports inside it, as popups', nor internal children; and only within their ancestors' clips, as they are drawn: an item is under a point only where a clip shows it, and in a rectangle only what a clip shows of it counts, so one a clip hides wholly, as one scrolled out of sight, is not taken, where Godot's box selection takes it. An item's rectangle, canvas-item-rect, in its own coordinates, is the box around what it drew, which turns with it, unless its class's canvas-item-edit-rect method gives one, as Godot's _edit_get_rect does: a control gives its own, so that it is picked where it draws nothing. Through global-transform-with-canvas, a turned item's rectangle is the parallelogram it is, and so it is tested. An item without one, as an empty 2D node, is picked as Godot's editor picks it when asked: under a point within canvas-items-at's :grab-distance of its origin, and in a rectangle by its origin with canvas-items-in-rect's :origins t.

  • Zooming and panning: a viewport's canvas transform, as Godot's Viewport.canvas_transform, a camera's, is a zoom, viewport-canvas-scale, a double-float, and a pan, viewport-canvas-offset, a dvec2 copied in and out as node-position is: a point of the viewport's own canvas, of its canvas items outside canvas layers, is drawn at the point times the scale plus the offset, in the viewport's coordinates. It applies to the viewport's own canvas and to the canvas layers that follow it, not to the other layers nor to the windows embedded in it. Its global canvas transform, as Godot's Viewport.global_canvas_transform, viewport-global-canvas-scale and viewport-global-canvas-offset, zooms and pans everything it draws, canvas layers too, from its coordinates to the units of what it draws into, as the editor's view of a scene does. A new zoom of either draws the items again for the new size, as a new display scale does, so raster text stays sharp; a pan draws nothing again, however far it goes, so a camera may follow a player through a level hundreds of thousands of units long. Controls are laid out in the viewport's size, whatever its transforms, and get the pointer through them, as Godot's get it through their canvas's transform: a control takes the pointer where it is drawn, in its own coordinates, and a popup, a tooltip or the input method's caret goes where the control is drawn, as large as it is drawn. target-to-viewport takes a point of what the viewport draws into, such as the pointer's, back through the global canvas transform to the viewport's coordinates, and viewport-to-target the other way; target-to-canvas takes it on to a node's canvas, as a game finds the point of its world under the pointer, and canvas-to-target back. The points of the viewport and of canvases are double-floats, as a world far out needs; those of what the viewport draws into, single-floats. canvas-zoom is the zoom a node's canvas is drawn at.

  • Content scale, as Godot's window has it: a viewport with a window of its own, the engine's or a popup's, stretches its content over the window from a base size, viewport-content-scale-width by viewport-content-scale-height, as viewport-content-scale-mode says: :disabled, by default, leaves it as it is; :canvas-items draws it stretched at the window's resolution, so text stays sharp; :viewport draws it at the base size and stretches the picture without smoothing, as pixel art wants. viewport-content-scale-aspect keeps its shape, :ignore by default, :keep, with black bars where the window's shape differs, :keep-width and :keep-height, which let it grow the other way, or :expand, which lets it grow either way; :integer viewport-content-scale-stretch stretches it by whole times only; viewport-content-scale-factor makes it larger besides, and alone scales it while the mode is :disabled. viewport-size is then the content's size, which its controls are laid out in, and the pointer reaches its nodes and controls in its coordinates. The windows embedded in it, its popups and dialogs, are placed in its coordinates and stretched with it; a popup in a window of its own is drawn at its own scale, as Godot's is. A game's project gives its root these (see Projects). target-to-viewport takes a point of the window back through the stretch, its margins and the global canvas transform.

  • Canvas layers: a canvas-layer, as Godot's CanvasLayer, draws the canvas items under it on a canvas of its own, over the viewport's own canvas items if its canvas-layer-layer is above 0, by default 1, or under them below 0, and through a transform of its own rather than the viewport's canvas transform, so that an interface over a game stays put as a camera moves:

    (let ((hud (make-instance 's2d:canvas-layer :name "hud")))
      (sc:add-child root hud)
      (sc:add-child hud (make-instance 'ui:label :text "Score: 0")))
    

    Its transform is canvas-layer-offset, canvas-layer-rotation and canvas-layer-scale, as a 2D node's, in double-floats too, applied as it is drawn, so that moving or turning it draws nothing again, and a new scale only records its items again for the new size. With canvas-layer-follow-viewport-enabled it moves with the viewport's canvas transform too, canvas-layer-follow-viewport-scale times as much about the middle of the viewport, as a background further away does; a tree being edited leaves that scale out, as Godot's editor does. canvas-layer-final-transform takes its canvas to the viewport's coordinates. canvas-layer-visible hides its items, and the layer emits visibility-changed. Its items' global transforms start at it, so the 2D nodes above it move them not and the canvas items above it hide them not. Their controls take the pointer through its transform, those of the topmost canvas first; layers of one layer are drawn in the tree's order, those at 0 over the viewport's own items. map-canvases goes through a viewport's canvases in the order they are drawn. Godot draws a following layer's scale about the viewport's middle but gives its controls the pointer without it; Pyssel gives them the pointer as the layer is drawn.