Table of Contents
Scene Tree
The systems pyssel/scene and pyssel/scene-2d hold nodes in a tree, as
Godot does, and call their callbacks each frame:
(asdf:load-system :pyssel/scene-2d)
(defpackage #:walk
(:use #:cl)
(:local-nicknames (#:sc #:pyssel/scene) (#:s2d #:pyssel/scene-2d) (#:p2d #:pyssel/2d)
(#:gm #:cl-gmath)))
(in-package #:walk)
(sc:define-node player (s2d:node-2d)
((velocity :initarg :velocity :initform 120.0 :accessor player-velocity
:type single-float :property t :hint (:range 0 500)))
(:signals reached-edge))
(defmethod s2d:draw ((player player) canvas)
(p2d:fill-rect canvas -10 -10 20 20 '(1 0.6 0.2) :radius 4))
(defmethod sc:process ((player player) delta)
(let ((step (* (player-velocity player) delta)))
(when (sc:action-pressed-p :right) (incf (s2d:node-x player) step))
(when (sc:action-pressed-p :left) (decf (s2d:node-x player) step))
(when (> (s2d:node-x player) 300)
(sc:emit-signal player 'reached-edge))))
(sc:define-node level (s2d:node-2d) ())
(defmethod sc:ready ((level level))
(sc:connect (sc:find-node level "player") 'reached-edge level 'back-to-start))
(defun back-to-start (level)
(setf (s2d:node-x (sc:find-node level "player")) 20))
(defvar *tree*
(let ((level (make-instance 'level :name "level")))
(sc:add-child level (make-instance 'player :name "player"
:position (gm:make-dvec2 :x 20 :y 100)))
(s2d:run level :title "Walk" :width 320 :height 200
:input-map '((:left (:key :left) (:key :a))
(:right (:key :right) (:key :d))))))
Hold the right arrow, or D, and the square walks right, and back to the
start when it reaches the edge. Redefine draw or process at the REPL and the
next frame uses the new method; redefine player with another slot and
the node gets it. The game's state is in its nodes, as the player's
velocity; *tree*, the tree run returns, is only the REPL's way in to
it (see Getting Started).
- Node types are CLOS classes defined with
define-node, whose metaclass knows more thandefclassdoes::property tmarks the slots a saved scene keeps and the inspector shows, each with an:initarg, and:hintsays how to edit them;(:signals ...)names the signals the nodes emit;(:tool t)marks a class whose behaviour runs while editing; and(:internal t)a class of parts that controls make of themselves, or of the editor's own nodes, which the editor does not offer for scenes, as Godot's internal classes.class-properties,property-hint,class-signals,tool-class-pandinternal-class-pread them.make-instancechecks what the initargs give the properties against their types, as a saved scene's loading does, and takes any real for a float property, as:velocity 200; a wrong one is refused in words read from the type, as "PYSSEL/UI:CONTROL's :GROW-HORIZONTAL is a grow direction: :START, :END or :BOTH; not :UP." A slot's:copy tkeeps a copy, bycopy-seq, of what an initarg gives it, and:copy fwhat the functionfreturns of it, as a 2D node keeps copies of the vectors it is given. - Names: a boolean a program sets takes a plain name, after Godot's
property where there is one, as
embedded-window-modal,viewport-fullscreenand pyssel/ui'sbutton-pressed; one an object reports about its own state ends in-p, asnode-freed-p,tree-quitting-pandevent-handled-p. - Callbacks are generic functions:
enter-tree,ready(once the children are ready) andexit-treeas a node joins and leaves a tree;processeach frame, with the seconds since the last;physics-processat a fixed rate, 60 a second by default;inputandunhandled-inputfor input events. A canvas item'sdrawdraws it. The tree's tweens step after each physics step and afterprocess. After processing, the root'sui-processis the user interface's turn, where it lays out its controls. - The tree:
add-child,remove-child,move-childandqueue-free, which frees a node at the end of the frame.find-nodefollows a path such as"hud/score","../player"or"/root/level";node-pathgives a node's. Groups (add-to-group,nodes-in-group,node-groups) gather nodes across the tree. Metadata, as Godot'sset_meta, is data kept on a node beside its class's properties, by a symbol:(setf (node-meta node :edit-lock) t)sets it,node-metareads it, with a second value saying whether the node has it,remove-node-metatakes it away, andnode-meta-nameslists the names. It is saved with the scene and copied with the node, and changes nothing of how the node behaves: the editor keeps there whether a node is locked or grouped, and the scene's guides, as Godot's does. A node's parts, such as a scroll container's scrollbars, are internal children, added with:internal :frontor:internal :back: they go before or after its ordinary children, are processed and drawn as the others, are left out ofnode-childrenand the counting and indexing of children unless asked with:internal t, and scenes do not save them. - Changing scenes: a tree's current scene,
tree-current-scene, is the scene that runs under its root beside the autoloads, as Godot'scurrent_scene, whichrunand a play set.change-scene-to-filechanges it to a new instance of a scene file, relative to the project's directory, from a menu to a level and back, andchange-scene-to-packedto one of a packed scene: the scene leaves the tree at once, and at the end of the frame's processing it is freed and the new one joins, after which the tree emitsscene-changed, a signal of the tree.reload-current-sceneplays a level again from its file, andunload-current-scenefrees the current scene at once. The tree is by default the one whose callback calls them. - Signals:
(connect source signal target function)andemit-signal, which Signals describes for nodes, resources and other objects. Give the function as a symbol with a target node, as above, and add:persist tfor a saved scene to keep the connection; a function object with no target suits code that connects at run time.signal-connectionslists a node's connections. Every node emitstree-entered,readyandtree-exiting. - Input, actions and shortcuts reach nodes as Input and Actions says, and the 2D nodes, a viewport, canvas items and nodes with a position, are on 2D Nodes.
- Modes: a tree in
:playmode runs every node; in:editmode, as an editor's, only nodes of tool classes behave, though every canvas item draws, so that an editor shows a scene as it will play; and the engine draws only when something changes, or whenwake-treeasked for a frame after a delay, for timed work such as a tooltip's.(setf tree-mode)switches.:low-processor-mode, an option ofmake-scene-treeandrun, as Godot's, has a tree in:playmode draw only so too, as a user interface that changes only as it is used wants: the editor's own tree is one. - Quitting:
(sc:quit)in a callback ends the tree's run, as Godot'sget_tree().quit()does: its engine stops after the frame. A game ends so wherever it plays, andtree-quitting-psays it asked, for a tree without an engine. A tree in:editmode does not quit. - Errors: an error in a callback stops that node, with a
node-stoppedwarning, and the rest of the tree goes on. Redefining a node class or a callback's method resumes it, as doesresume-node. Stopping and resuming callbehaviour-changedfor the node, as a change of the tree's mode callsmode-changed: a canvas item draws again, since itsdrawmethod may draw less while it does not behave. A system that calls callbacks of its own calls them inbehave, which does the same.tree-error-versionmoves on as a node stops or resumes, and as a canvas item's drawing fails or draws again, so what shows which nodes failed notices without looking;errors-changedmoves it on, and asks for a frame, for a system whose nodes report errors of their own.
A tree is used from its own thread: its callbacks run there, and code on
another thread, such as the REPL's, goes through call-in-tree:
(sc:call-in-tree *tree*
(lambda ()
(setf (player-velocity (sc:find-node (sc:tree-root *tree*) "level/player")) 300.0)))
call-in-tree waits for the function's values. send-to-tree hands a
function over and returns at once, for code that must not wait on a tree
that may hang, as an editor must not wait on a game; its :reply, a
function of the values and the error, runs at the next frame of the tree
whose callbacks sent it, or straight after, from outside every tree. A
tree destroyed already makes it signal an error, unless :if-destroyed nil has it return nil instead, for a thread that hands a tree what it
finished while the tree may have gone, as a loader does:
(sc:send-to-tree game-tree (lambda () (length (sc:nodes-in-group game-tree :enemies)))
:reply (lambda (values error)
(if error
(format t "The game could not count: ~A~%" error)
(format t "~D enemies~%" (first values)))))
For now that thread is the engine's render thread, which processes the
tree before drawing each frame; whether it stays so is an open question,
so code should not rely on it. make-scene-tree makes a tree for an
engine you started yourself, or for none, to process with advance-tree
in tests and tools. A tree for an engine takes the engine's window, or
its offscreen target: the window's input, and its root drawing there.
Made with :window nil, as a tree drawn into a texture by another
tree's control is, it takes none of the window's input, and its root's
part has no window, while the engine's frames process it as any other;
tree-window tells. Its root viewport has the size it is given, as
set-viewport-size gives it at once, embeds the popups shown in it, and
draws where draw-viewport is called, as in the :draw function of a
render target, as a viewport-container
shows it; prepare-viewport, the target's :prepare, has it drawn only
when what it shows changed. Its quit only says so, in tree-quitting-p, as its engine is
not its own to stop. check-tree-thread refuses a call from another
thread than a tree's, for a system built on the tree whose functions are
called on its thread only. Processing nodes and dispatching input
allocate nothing in steady state.
Getting started
The core
Drawing
Scenes
User interface
Games and the editor
- Playing a Game
- Projects
- The Editor
- The File System Dock
- The Script Screen
- The REPL
- The Debugger
- Lisp Text
- Code Services
- Project Manager
- Undo and Redo
- Exporting
The project