@menu * Kernel:: The fundamental components. * Math:: Linear algebra, spatial partitioning, and more. * Graphics:: Eye candy. * Scripting:: Bringing the game world to life. @end menu @node Kernel @section Kernel At the very core of Chickadee, in the @code{(chickadee game-loop)} module, lies an event loop. This loop, or ``kernel'', is responsible for ensuring that the game is updated at the desired interval, rendering the current state of the game world, and handling errors if they occur. The kernel implements what is known as a ``fixed timestep'' game loop, meaning that the game simulation will be advanced by a fixed interval of time and will never vary from frame to frame, unlike some other styles of game loops. The appropriately named @code{run-game*} and @code{abort-game} procedures are the entry and exit points to the Chickadee game loop kernel. On its own, the kernel does not do very much at all. In order to actually respond to input events, update game state, or render output, the programmer must provide an engine. But don’t worry, you don’t have to start from scratch! Chickadee comes with a simple engine that uses SDL to create a graphical window and handle input devices, and OpenGL to handle rendering. This default engine is enough for most users to get started writing games quickly. More advanced users may want to write a custom engine that uses a different I/O system. Perhaps you are writing a text adventure or roguelike that reads from and writes to a terminal instead of a graphical window. The game loop kernel makes no assumptions. @deffn {Procedure} run-game [#:update] [#:render] [#:time] [#:error] @ [#:update-hz 60] Start the game loop. This procedure will not return until @code{abort-game} is called. The core game loop is generic and requires four additional procedures to operate: @itemize @item @var{update}: Called @var{update-hz} times per second to advance the game simulation. This procedure is called with a single argument: The amount of time that has passed since the last update, in milliseconds. @item @var{render}: Called each iteration of the loop to render the game to the desired output device. This procedure is called with a single argument: A value in the range [0, 1] which represents how much time has past since the last game state update relative to the upcoming game state update, as a percentage. Because the game state is updated independent of rendering, it is often the case that rendering is occuring between two updates. If the game is rendered as it was during the last update, a strange side-effect will occur that makes animation appear rough or ``choppy''. To counter this, the @var{alpha} value can be used to perfrom a linear interpolation of a moving object between its current position and its previous position. This odd trick has the pleasing result of making the animation look smooth again, but requires keeping track of previous state. @item @var{time}: Called to get the current time in milliseconds. This procedure is called with no arguments. @item @var{error}: Called when an error from the @var{update} or @var{render} procedures reaches the game loop. This procedure is called with three arguments: The call stack, the error key, and the error arguments. If no error handler is provided, the default behavior is to simply re-throw the error. @end itemize @end deffn @deffn {Procedure} abort-game Stop the currently running Chickadee game loop. @end deffn Since most users will want to write 2D/3D games with hardware accelerated graphics rendering, controlled via keyboard, mouse, or game controller, Chickadee comes with an easy to use engine just for this purpose in the @code{(chickadee)} module: @code{run-game}. @deffn {Procedure} run-game [#:window-title "Chickadee!"] @ [#:window-width 640] [#:window-height 480] @ [#:window-fullscreen? @code{#f}] [#:update-hz 60] @ [#:load] [#:update] [#:draw] [#:quit] @ [#:key-press] [#:key-release] [#:text-input] @ [#:mouse-press] [#:mouse-release] [#:mouse-move] @ [#:controller-add] [#:controller-remove] [#:controller-press] @ [#:controller-release] [#:controller-move] [#:error] Run the Chickadee game loop using the SDL engine in OpenGL mode. A new graphical window will be opened with @var{window-width} x @var{window-height} as its dimensions, @var{window-title} as its title, and in fullscreen mode if @var{window-fullscreen?} is @code{#t}. @itemize @item @var{load}: Called with zero arguments when the game window has opened but before the game loop has started. Can be used to perform initialization that requires an open window and OpenGL context such as loading textures. @item @var{update}: Called @var{update-hz} times per second with one argument: The amount of time to advance the game simulation. @item @var{draw}: Called each time a frame should be rendered with a single argument known as the @code{alpha} value. See the documentation for @code{run-game} for an explanation of this value. @item @var{quit}: Called with zero arguments when the user tries to close the game window. The default behavior is to exit the game. @item @var{key-press}: Called with four arguments when a key is pressed on the keyboard: @enumerate @item @var{key}: The symbolic name of the ``virtual'' key that was pressed. For example: @code{backspace}. It's called a virtual key because the operating system may map a physical keyboard key to another key entirely, such as how the author likes to bind the ``caps lock'' key to mean ``control''. @item @var{scancode}: The symbolic name of the physical key that was pressed. @item @var{modifiers}: A list of the symbolic names of modifier keys that were being held down when the key was pressed. Possible values include @code{ctrl}, @code{alt}, and @code{shift}. @item @var{repeat?}: @code{#t} if this is a repeated press of the same key. @end enumerate @item @var{key-release}: Called with three arguments when a key is released on the keyboard: @enumerate @item @var{key}: The symbolic name of the ``virtual'' key that was released. @item @var{scancode}: The symbolic name of the physical key that was released. @item @var{modifiers}: A list of the symbolic names of modifier keys that were being held down when the key was released. @end enumerate @item @var{text-input}: Called with a single argument, a string of text, when printable text is typed on the keyboard. @item @var{mouse-press}: Called with four arguments when a mouse button is pressed: @enumerate @item @var{button}: The symbolic name of the button that was pressed, such as @code{left}, @code{middle}, or @code{right}. @item @var{clicks}: The number of times the button has been clicked in a row. @item @var{x}: The x coordinate of the mouse cursor. @item @var{y}: The y coordinate of the mouse cursor. @end enumerate @item @var{mouse-release}: Called with three arguments when a mouse button is released: @enumerate @item @var{button}: The symbolic name of the button that was released. @item @var{x}: The x coordinate of the mouse cursor. @item @var{y}: The y coordinate of the mouse cursor. @end enumerate @item @var{mouse-move}: Called with five arguments when the mouse is moved: @enumerate @item @var{x}: The x coordinate of the mouse cursor. @item @var{y}: The y coordinate of the mouse cursor. @item @var{dx}: The amount the mouse has moved along the x axis since the last mouse move event. @item @var{dy}: The amount the mouse has moved along the y axis since the last mouse move event. @item @var{buttons}: A list of the buttons that were pressed down when the mouse was moved. @end enumerate @item @var{controller-add}: Called with a single argument, an SDL game controller object, when a game controller is connected. @item @var{controller-remove}: Called with a single argument, an SDL game controller object, when a game controller is disconnected. @item @var{controller-press}: Called with two arguments when a button on a game controller is pressed: @enumerate @item @var{controller}: The controller that triggered the event. @item @var{button}: The symbolic name of the button that was pressed. Possible buttons are: @itemize @item @code{a} @item @code{b} @item @code{x} @item @code{y} @item @code{back} @item @code{guide} @item @code{start} @item @code{left-stick} @item @code{right-stick} @item @code{left-shoulder} @item @code{right-shoulder} @item @code{dpad-up} @item @code{dpad-down} @item @code{dpad-left} @item @code{dpad-right} @end itemize @end enumerate @item @var{controller-release}: Called with two arguments when a button on a game controller is released: @enumerate @item @var{controller}: The controller that triggered the event. @item @var{button}: The symbolic name of the button that was released. @end enumerate @item @var{controller-move}: Called with three arguments when an analog stick or trigger on a game controller is moved: @enumerate @item @var{controller}: The controller that triggered the event. @item @var{axis}: The symbolic name of the axis that was moved. Possible values are: @itemize @item @code{left-x} @item @code{left-y} @item @code{right-x} @item @code{right-y} @item @code{trigger-left} @item @code{trigger-right} @end itemize @end enumerate @item @var{error}: Called with three arguments when an error occurs: @enumerate @item @var{stack}: The call stack at the point of error. @item @var{key}: The exception key. @item @var{args}: The arguments thrown with the exception. @end enumerate The default behavior is to re-throw the error. @end itemize @end deffn @node Math @section Math Chickadee contains data types and procedures for performing the most common computations in video game simulations such as linear algebra with vectors and matrices and axis-aligned bounding box collision detection. @menu * Basics:: Commonly used, miscellaneous things. * Vectors:: Euclidean vectors. * Rectangles:: Axis-aligned bounding boxes. * Grid:: Spatial partitioning for bounding boxes. * Matrices:: Transformation matrices. * Quaternions:: Rotations about an arbitrary axis. * Easings:: Easing functions for interesting animations. * Bezier Curves:: Cubic Bezier curves and paths in 2D space. * Path Finding:: Generic A* path finding. @end menu @node Basics @subsection Basics @defvar pi An essential constant for all trigonometry. @code{@U{03C0}} is the ratio of a circle's circumferences to its diameter. Since @code{@U{03C0}} is an irrational number, the @var{pi} in Chickadee is a mere floating point approximation that is ``good enough.'' @end defvar @defvar pi/2 Half of @var{pi}. @end defvar @deffn {Procedure} cotan @var{z} Return the cotangent of @var{z}. @end deffn @node Vectors @subsection Vectors Unlike Scheme's vector data type, which is a sequence of arbitrary Scheme objects, Chickadee's @code{(chickadee math vector)} module provides vectors in the linear algebra sense: Sequences of numbers specialized for particular coordinate spaces. As of now, Chickadee provides 2D and 3D vectors, with 4D vector support coming in a future release. Here's a quick example of adding two vectors: @example (define v (vec2+ (vec2 1 2) (vec2 3 4))) @end example Since vectors are used so frequently, the reader macro @code{#v} is used to cut down on typing: @example (define v (vec2+ #v(1 2) #v(3 4))) @end example @subsubsection A Note About Performance A lot of time has been spent making Chickadee's vector operations perform relatively efficiently in critical code paths where excessive garbage generation will cause major performance issues. The general rule is that procedures ending with @code{!} perform an in-place modification of one of the arguments in order to avoid allocating a new vector. These procedures are also inlined by Guile's compiler in order to take advantage of optimizations relating to floating point math operations. The downside is that since these are not pure functions, they do not compose well and create more verbose code. @subsubsection 2D Vectors @deffn {Procedure} vec2 @var{x} @var{y} Return a new 2D vector with coordinates (@var{x}, @var{y}). @end deffn @deffn {Procedure} vec2/polar @var{r} @var{theta} Return a new 2D vector containing the Cartesian representation of the polar coordinate (@var{r}, @var{theta}). The angle @var{theta} is measured in radians. @end deffn @deffn {Procedure} vec2? @var{obj} Return @code{#t} if @var{obj} is a 2D vector. @end deffn @deffn {Procedure} vec2-x @var{v} Return the X coordinate of the 2D vector @var{v}. @end deffn @deffn {Procedure} vec2-y @var{v} Return the Y coordinate of the 2D vector @var{v}. @end deffn @deffn {Procedure} vec2-copy @var{v} Return a fresh copy of the 2D vector @var{v}. @end deffn @deffn {Procedure} vec2-magnitude @var{v} Return the magnitude of the 2D vector @var{v}. @end deffn @deffn {Procedure} vec2-dot-product @var{v1} @var{v2} Return the dot product of the 2D vectors @var{v1} and @var{v2}. @end deffn @deffn {Procedure} vec2-normalize @var{v} Return the normalized form of the 2D vector @var{v}. @end deffn @deffn {Procedure} vec2+ @var{v} @var{x} Add @var{x}, either a 2D vector or a scalar (i.e. a real number), to the 2D vector @var{v} and return a new vector containing the sum. @end deffn @deffn {Procedure} vec2- @var{v} @var{x} Subtract @var{x}, either a 2D vector or a scalar, from the 2D vector @var{v} and return a new vector containing the difference. @end deffn @deffn {Procedure} vec2* @var{v} @var{x} Multiply the 2D vector @var{v} by @var{x}, a 2D vector or a scalar, and return a new vector containing the product. @end deffn @deffn {Procedure} set-vec2-x! @var{v} @var{x} Set the X coordinate of the 2D vector @var{v} to @var{x}. @end deffn @deffn {Procedure} set-vec2-y! @var{v} @var{y} Set the Y coordinate of the 2D vector @var{v} to @var{y}. @end deffn @deffn {Procedure} set-vec2! @var{v} @var{x} @var{y} Set the X and Y coordinates of the 2D vector @var{v} to @var{x} and @var{y}, respectively. @end deffn @deffn {Procedure} vec2-copy! @var{source} @var{target} Copy the 2D vector @var{source} into the 2D vector @var{target}. @end deffn @deffn {Procedure} vec2-add! @var{v} @var{x} Perform an in-place modification of the 2D vector @var{v} by adding @var{x}, a 2D vector or a scalar. @end deffn @deffn {Procedure} vec2-sub! @var{v} @var{x} Perform an in-place modification of the 2D vector @var{v} by subtracting @var{x}, a 2D vector or a scalar. @end deffn @deffn {Procedure} vec2-mult! @var{v} @var{x} Perform an in-place modification of the 2D vector @var{v} by multiplying it by @var{x}, a 2D vector or a scalar. @end deffn @subsubsection 3D Vectors @deffn {Procedure} vec3 @var{x} @var{y} Return a new 2D vector with coordinates (@var{x}, @var{y}). @end deffn @deffn {Procedure} vec3? @var{obj} Return @code{#t} if @var{obj} is a 3D vector. @end deffn @deffn {Procedure} vec3-x @var{v} Return the X coordinate of the 3D vector @var{v}. @end deffn @deffn {Procedure} vec3-y @var{v} Return the Y coordinate of the 3D vector @var{v}. @end deffn @deffn {Procedure} vec3-z @var{v} Return the Z coordinate of the 3D vector @var{v}. @end deffn @deffn {Procedure} vec3-copy @var{v} Return a fresh copy of the 3D vector @var{v}. @end deffn @deffn {Procedure} vec3-magnitude @var{v} Return the magnitude of the 3D vector @var{v}. @end deffn @deffn {Procedure} vec3-dot-product @var{v1} @var{v2} Return the dot product of the 3D vectors @var{v1} and @var{v2}. @end deffn @deffn {Procedure} vec3-normalize @var{v} Return the normalized form of the 3D vector @var{v}. @end deffn @deffn {Procedure} vec3+ @var{v} @var{x} Add @var{x}, either a 3D vector or a scalar (i.e. a real number), to the 3D vector @var{v} and return a new vector containing the sum. @end deffn @deffn {Procedure} vec3- @var{v} @var{x} Subtract @var{x}, either a 3D vector or a scalar, from the 3D vector @var{v} and return a new vector containing the difference. @end deffn @deffn {Procedure} vec3* @var{v} @var{x} Multiply the 3D vector @var{v} by @var{x}, a 3D vector or a scalar, and return a new vector containing the product. @end deffn @deffn {Procedure} set-vec3-x! @var{v} @var{x} Set the X coordinate of the 3D vector @var{v} to @var{x}. @end deffn @deffn {Procedure} set-vec3-y! @var{v} @var{y} Set the Y coordinate of the 3D vector @var{v} to @var{y}. @end deffn @deffn {Procedure} set-vec3-z! @var{v} @var{z} Set the Z coordinate of the 3D vector @var{v} to @var{z}. @end deffn @deffn {Procedure} set-vec3! @var{v} @var{x} @var{y} @var{z} Set the X, Y, and Z coordinates of the 3D vector @var{v} to @var{x}, @var{y}, and @var{z}, respectively. @end deffn @deffn {Procedure} vec3-copy! @var{source} @var{target} Copy the 3D vector @var{source} into the 3D vector @var{target}. @end deffn @deffn {Procedure} vec3-add! @var{v} @var{x} Perform an in-place modification of the 3D vector @var{v} by adding @var{x}, a 3D vector or a scalar. @end deffn @deffn {Procedure} vec3-sub! @var{v} @var{x} Perform an in-place modification of the 3D vector @var{v} by subtracting @var{x}, a 3D vector or a scalar. @end deffn @deffn {Procedure} vec3-mult! @var{v} @var{x} Perform an in-place modification of the 3D vector @var{v} by multiplying it by @var{x}, a 3D vector or a scalar. @end deffn @node Rectangles @subsection Rectangles The @code{(chickadee math rect)} module provides an API for manipulating axis-aligned bounding boxes (AABBs). AABBs are often used for collision detection in games. Common use-cases are defining ``hitboxes'' in platformers or using them for the ``broad phase'' of a collision detection algorithm that uses a more complex (and thus slower) method of determining the actual collisions. Like some of the other math modules, there exists a collection of functions that do in-place modification of rectangles for use in performance critical code paths. @deffn {Procedure} rect @var{x} @var{y} @var{width} @var{height} @deffnx {Procedure} make-rect @var{x} @var{y} @var{width} @var{height} Create a new rectangle that is @var{width} by @var{height} in size and whose bottom-left corner is located at (@var{x}, @var{y}). @end deffn @deffn {Procedure} rect? @var{obj} Return @code{#t} if @var{obj} is a rectangle. @end deffn @deffn {Procedure} rect-within? @var{rect1} @var{rect2} Return @code{#t} if @var{rect2} is completely within @var{rect1}. @end deffn @deffn {Procedure} rect-intersects? @var{rect1} @var{rect2} Return @code{#t} if @var{rect2} overlaps @var{rect1}. @end deffn @deffn {Procedure} rect-contains? @var{rect} @var{x} @var{y} Return @code{#t} if the coordinates (@var{x}, @var{y}) are within @var{rect}. @end deffn @deffn {Procedure} rect-contains-vec2? @var{rect} @var{v} Return @code{#t} if the 2D vector @var{v} is within the bounds of @var{rect}. @end deffn @deffn {Procedure} rect-x @var{rect} Return the X coordinate of the lower-left corner of @var{rect}. @end deffn @deffn {Procedure} rect-y @var{rect} Return the Y coordinate of the lower-left corner of @var{rect}. @end deffn @deffn {Procedure} rect-left @var{rect} Return the left-most X coordinate of @var{rect}. @end deffn @deffn {Procedure} rect-right @var{rect} Return the right-most X coordinate of @var{rect}. @end deffn @deffn {Procedure} rect-bottom @var{rect} Return the bottom-most Y coordinate of @var{rect}. @end deffn @deffn {Procedure} rect-top @var{rect} Return the top-most Y coordinate of @var{rect}. @end deffn @deffn {Procedure} rect-center-x @var{rect} Return the X coordinate of the center of @var{rect}. @end deffn @deffn {Procedure} rect-center-y @var{rect} Return the Y coordinate of the center of @var{rect}. @end deffn @deffn {Procedure} rect-width @var{rect} Return the width of @var{rect}. @end deffn @deffn {Procedure} rect-height @var{rect} Return the height of @var{rect}. @end deffn @deffn {Procedure} rect-area @var{rect} Return the surface area covered by @var{rect}. @end deffn @deffn {Procedure} rect-clamp-x @var{rect} @var{x} Restrict @var{x} to the portion of the X axis covered by @var{rect}. @end deffn @deffn {Procedure} rect-clamp-y @var{rect} @var{y} Restrict @var{y} to the portion of the Y axis covered by @var{rect}. @end deffn @deffn {Procedure} rect-clamp @var{rect1} @var{rect2} Return a new rect that adjusts the location of @var{rect1} so that it is completely within @var{rect2}. An exception is thrown in the case that @var{rect1} cannot fit completely within @var{rect2}. @end deffn @deffn {Procedure} rect-move @var{rect} @var{x} @var{y} Return a new rectangle based on @var{rect} but moved to the coordinates (@var{x}, @var{y}). @end deffn @deffn {Procedure} rect-move-vec2 @var{rect} @var{v} Return a new rectangle based on @var{rect} but moved to the coordinates in the 2D vector @var{v}. @end deffn @deffn {Procedure} rect-move-by @var{rect} @var{x} @var{y} Return a new rectangle based on @var{rect} but moved by (@var{x}, @var{y}) units relative to its current location. @end deffn @deffn {Procedure} rect-move-by-vec2 @var{rect} @var{v} Return a new rectangle based on @var{rect} but moved by the 2D vector @var{v} relative to its current location. @end deffn @deffn {Procedure} rect-inflate @var{rect} @var{width} @var{height} Return a new rectangle based on @var{rect}, but expanded by @var{width} units on the X axis and @var{height} units on the Y axis, while keeping the rectangle centered on the same point. @end deffn @deffn {Procedure} rect-union @var{rect1} @var{rect2} Return a new rectangle that completely covers the area of @var{rect1} and @var{rect2}. @end deffn @deffn {Procedure} rect-clip @var{rect1} @var{rect2} Return a new rectangle that is the overlapping region of @var{rect1} and @var{rect2}. If the two rectangles do not overlap, a rectangle of 0 width and 0 height is returned. @end deffn @deffn {Procedure} set-rect-x! @var{rect} @var{x} Set the left X coordinate of @var{rect} to @var{x}. @end deffn @deffn {Procedure} set-rect-y! @var{rect} @var{y} Set the bottom Y coordinate of @var{rect} to @var{y}. @end deffn @deffn {Procedure} set-rect-width! @var{rect} @var{width} Set the width of @var{rect} to @var{width}. @end deffn @deffn {Procedure} set-rect-height! @var{rect} @var{height} Set the height of @var{rect} to @var{height}. @end deffn @deffn {Procedure} rect-move! @var{rect} @var{x} @var{y} Move @var{rect} to (@var{x}, @var{y}) in-place. @end deffn @deffn {Procedure} rect-move-vec2! @var{rect} @var{v} Move @var{rect} to the 2D vector @var{v} in-place. @end deffn @deffn {Procedure} rect-move-by! @var{rect} @var{x} @var{y} Move @var{rect} by (@var{x}, @var{y}) in-place. @end deffn @deffn {Procedure} rect-move-by-vec2! @var{rect} @var{v} Move @var{rect} by the 2D vector @var{v} in-place. @end deffn @deffn {Procedure} rect-inflate! @var{rect} @var{width} @var{height} Expand @var{rect} by @var{width} and @var{height} in-place. @end deffn @deffn {Procedure} rect-union! @var{rect1} @var{rect2} Modify @var{rect1} in-place to completely cover the area of both @var{rect1} and @var{rect2}. @end deffn @deffn {Procedure} rect-clip! @var{rect1} @var{rect2} Modify @var{rect1} in-place to be the overlapping region of @var{rect1} and @var{rect2}. @end deffn @deffn {Procedure} rect-clamp! @var{rect1} @var{rect2} Adjust the location of @var{rect1} in-place so that its bounds are completely within @var{rect2}. An exception is thrown in the case that @var{rect1} cannot fit completely within @var{rect2}. @end deffn @deffn {Procedure} vec2-clamp-to-rect! @var{v} @var{rect} Restrict the coordinates of the 2D vector @var{v} so that they are within the bounds of @var{rect}. @var{v} is modified in-place. @end deffn @node Grid @subsection Grid The @code{(chickadee math grid)} module provides a simple spatial partitioning system for axis-aligned bounding boxes (@pxref{Rectangles}) in 2D space. The grid divides the world into tiles and keeps track of which rectangles occupy which tiles. When there are lots of moving objects in the game world that need collision detection, the grid greatly speeds up the process. Instead of checking collisions of each object against every other object (an O(n^2) operation), the grid quickly narrows down which objects could possibly be colliding and only performs collision testing against a small set of objects. In addition to checking for collisions, the grid also handles the resolution of collisions. Exactly how each collision is resolved is user-defined. A player bumping into a wall may slide against it. An enemy colliding with a projectile shot by the player may get pushed back in the opposite direction. Two players colliding may not need resolution at all and will just pass through each other. The way this works is that each time an object (A) is moved within the grid, the grid looks for an object (B) that may possibly be colliding with A. A user-defined procedure known as a ``filter'' is then called with both A and B. If the filter returns @code{#f}, it means that even if A and B are colliding, no collision resolution is needed. In this case the grid won't waste time checking if they really do collide because it doesn't matter. If A and B are collidable, then the filter returns a procedure that implements the resolution technique. The grid will then perform a collision test. If A and B are colliding, the resolver procedure is called. It's the resolvers job to adjust the objects such that they are no longer colliding. The grid module comes with a very simple resolution procedure, @code{slide}, that adjusts object A by the smallest amount so that it no longer overlaps with B. By using this filtering technique, a game can resolve collisions between different objects in different ways. @deffn {Procedure} make-grid [@var{cell-size} 64] Return a new grid partitioned into @var{cell-size} tiles. @end deffn @deffn {Procedure} grid? @var{obj} Return @code{#t} if @var{obj} is a grid. @end deffn @deffn {Procedure} cell? @var{obj} Return @code{#t} if @var{obj} is a grid cell. @end deffn @deffn {Procedure} cell-count @var{cell} Return the number of items in @var{cell}. @end deffn @deffn {Procedure} grid-cell-size @var{grid} Return the cell size of @var{grid}. @end deffn @deffn {Procedure} grid-cell-count @var{grid} Return the number of cells currently in @var{grid}. @end deffn @deffn {Procedure} grid-item-count @var{grid} Return the number of items in @var{grid}. @end deffn @deffn {Procedure} grid-add @var{grid} @var{item} @var{x} @var{y} @ @var{width} @var{height} Add @var{item} to @var{grid} represented by the axis-aligned bounding box whose lower-left corner is at (@var{x}, @var{y}) and is @var{width} x @var{height} in size. @end deffn @deffn {Procedure} grid-remove @var{grid} @var{item} Return @var{item} from @var{grid}. @end deffn @deffn {Procedure} grid-clear @var{grid} Remove all items from @var{grid}. @end deffn @deffn {Procedure} grid-move @var{grid} @var{item} @var{position} @var{filter} Attempt to move @var{item} in @var{grid} to @var{position} (a 2D vector) and check for collisions. For each collision, @var{filter} will be called with two arguments: @var{item} and the item it collided with. If a collision occurs, @var{position} may be modified to resolve the colliding objects. @end deffn @deffn {Procedure} for-each-cell @var{proc} @var{grid} [@var{rect}] Call @var{proc} with each cell in @var{grid} that intersects @var{rect}, or every cell if @var{rect} is @code{#f}. @end deffn @deffn {Procedure} for-each-item @var{proc} @var{grid} Call @var{proc} for each item in @var{grid}. @end deffn @deffn {Procedure} slide @var{item} @var{item-rect} @ @var{other} @var{other-rect} @var{goal} Resolve the collision that occurs between @var{item} and @var{other} when moving @var{item-rect} to @var{goal} by sliding @var{item-rect} the minimum amount needed to make it no longer overlap @var{other-rect}. @end deffn @node Matrices @subsection Matrices The @code{(chickadee math matrix)} module provides an interface for working with the most common type of matrices in game development: 4x4 transformation matrices. @subsubsection Another Note About Performance Much like the vector API, the matrix API is commonly used in performance critical code paths. In order to reduce the amount of garbage generated and improve matrix multiplication performance, there are many procedures that perform in-place modifications of matrix objects. @subsubsection Matrix Operations @deffn {Procedure} make-matrix4 @var{aa} @var{ab} @var{ac} @var{ad} @ @var{ba} @var{bb} @var{bc} @var{bd} @ @var{ca} @var{cb} @var{cc} @var{cd} @ @var{da} @var{db} @var{dc} @var{dd} Return a new 4x4 matrix initialized with the given 16 values in column-major format. @end deffn @deffn {Procedure} make-null-matrix4 Return a new 4x4 matrix with all values initialized to 0. @end deffn @deffn {Procedure} make-identity-matrix4 Return a new 4x4 identity matrix. Any matrix multiplied by the identity matrix yields the original matrix. This procedure is equivalent to the following code: @example (make-matrix4 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1) @end example @end deffn @deffn {Procedure} matrix4? @var{obj} Return @code{#t} if @var{obj} is a 4x4 matrix. @end deffn @deffn {Procedure} matrix4* . @var{matrices} Return a new 4x4 matrix containing the product of multiplying all of the given @var{matrices}. Note: Remember that matrix multiplication is @strong{not} commutative! @end deffn @deffn {Procedure} orthographic-projection @var{left} @var{right} @ @var{top} @var{bottom} @ @var{near} @var{far} Return a new 4x4 matrix that represents an orthographic (2D) projection for the horizontal clipping plane @var{top} and @var{bottom}, the vertical clipping plane @var{top} and @var{bottom}, and the depth clipping plane @var{near} and @var{far}. @end deffn @deffn {Procedure} perspective-projection @var{fov} @ @var{aspect-ratio} @ @var{near} @var{far} Return a new 4x4 matrix that represents a perspective (3D) projection with a field of vision of @var{fov} radians, an aspect ratio of @var{aspect-ratio}, and a depth clipping plane defined by @var{near} and @var{far}. @end deffn @deffn {Procedure} matrix4-translate @var{x} Return a new 4x4 matrix that represents a translation by @var{x}, a 2D vector, a 3D vector, or a rectangle (in which case the bottom-left corner of the rectangle is used). @end deffn @deffn {Procedure} matrix4-scale @var{s} Return a new 4x4 matrix that represents a scaling along the X, Y, and Z axes by the scaling factor @var{s}, a real number. @end deffn @deffn {Procedure} matrix4-rotate @var{q} Return a new 4x4 matrix that represents a rotation about an arbitrary axis defined by the quaternion @var{q}. @end deffn @deffn {Procedure} matrix4-rotate-z @var{theta} Return a new 4x4 matrix that represents a rotation about the Z axis by @var{theta} radians. @end deffn @deffn {Procedure} matrix4-identity! @var{matrix} Modify @var{matrix} in-place to contain the identity matrix. @end deffn @deffn {Procedure} matrix4-mult! @var{dest} @var{a} @var{b} Multiply the 4x4 matrix @var{a} by the 4x4 matrix @var{b} and store the result in the 4x4 matrix @var{dest}. @end deffn @deffn {Procedure} matrix4-translate! @var{matrix} @var{x} Modify @var{matrix} in-place to contain a translation by @var{x}, a 2D vector, a 3D vector, or a rectangle (in which case the bottom-left corner of the rectangle is used). @end deffn @deffn {Procedure} matrix4-scale! @var{matrix} @var{s} Modify @var{matrix} in-place to contain a scaling along the X, Y, and Z axes by the scaling factor @var{s}, a real number. @end deffn @deffn {Procedure} matrix4-rotate! @var{matrix} @var{q} Modify @var{matrix} in-place to contain a rotation about an arbitrary axis defined by the quaternion @var{q}. @end deffn @deffn {Procedure} matrix4-rotate-z! @var{matrix} @var{theta} Modify @var{matrix} in-place to contain a rotation about the Z axis by @var{theta} radians. @end deffn @deffn {Procedure} matrix4-2d-transform! @var{matrix} [#:origin] @ [#:position] [#:rotation] @ [#:scale] [#:skew] Modify @var{matrix} in-place to contain the transformation described by @var{position}, a 2D vector or rectangle, @var{rotation}, a scalar representing a rotation about the Z axis, @var{scale}, a 2D vector, and @var{skew}, a 2D vector. The transformation happens with respect to @var{origin}, a 2D vector. If an argument is not provided, that particular transformation will not be included in the result. @end deffn @deffn {Procedure} transform! @var{matrix} @var{v} Modify the 2D vector @var{v} in-place by multiplying it by the 4x4 matrix @var{matrix}. @end deffn @node Quaternions @subsection Quaternions In game development, the quaternion is most often used to represent rotations. Why not use a matrix for that, you may ask. Unlike matrices, quaternions can be interpolated (animated) and produce a meaningful result. When interpolating two quaternions, there is a smooth transition from one rotation to another, whereas interpolating two matrices would yield garbage. @deffn {Procedure} quaternion @var{x} @var{y} @var{z} @var{w} Return a new quaternion with values @var{x}, @var{y}, @var{z}, and @var{w}. @end deffn @deffn {Procedure} quaternion? @var{obj} Return @code{#t} if @var{obj} is a quaternion. @end deffn @deffn {Procedure} quaternion-w @var{q} Return the W component of the quaternion @var{q}. @end deffn @deffn {Procedure} quaternion-x @var{q} Return the X component of the quaternion @var{q}. @end deffn @deffn {Procedure} quaternion-y @var{q} Return the Y component of the quaternion @var{q}. @end deffn @deffn {Procedure} quaternion-z @var{q} Return the Z component of the quaternion @var{q}. @end deffn @deffn {Procedure} make-identity-quaternion Return the identity quaternion. @end deffn @node Easings @subsection Easings Easing functions are essential for animation. Each easing function provides a different path to go from an initial value to a final value. These functions make an excellent companion to the @code{tween} procedure (@pxref{Tweening}). Experiment with them to figure out which function makes an animation look the best. Pro tip: @code{smoothstep} provides nice results most of the time and creates smoother animation than using @code{linear}. @deffn {Procedure} linear @var{t} @end deffn @deffn {Procedure} smoothstep @var{t} @end deffn @deffn {Procedure} ease-in-quad @var{t} @end deffn @deffn {Procedure} ease-out-quad @var{t} @end deffn @deffn {Procedure} ease-in-out-quad @var{t} @end deffn @deffn {Procedure} ease-in-cubic @var{t} @end deffn @deffn {Procedure} ease-out-cubic @var{t} @end deffn @deffn {Procedure} ease-in-out-cubic @var{t} @end deffn @deffn {Procedure} ease-in-quart @var{t} @end deffn @deffn {Procedure} ease-out-quart @var{t} @end deffn @deffn {Procedure} ease-in-out-quart @var{t} @end deffn @deffn {Procedure} ease-in-quint @var{t} @end deffn @deffn {Procedure} ease-out-quint @var{t} @end deffn @deffn {Procedure} ease-in-out-quint @var{t} @end deffn @deffn {Procedure} ease-in-sine @var{t} @end deffn @deffn {Procedure} ease-out-sine @var{t} @end deffn @deffn {Procedure} ease-in-out-sine @var{t} @end deffn @node Bezier Curves @subsection Bezier Curves The @code{(chickadee math bezier)} module provides an API for describing cubic Bezier curves in 2D space. These curves are notably used in font description, vector graphics programs, and when it comes to games: path building. With Bezier curves, it's somewhat easy to create a smooth looking path for an enemy to move along, for example. Bezier curves become particularly interesting when they are chained together to form a Bezier ``path'', where the end point of one curve becomes the starting point of the next. Currently, the rendering of Bezier curves is rather crude and provided mostly for visualizing and debugging curves that would be unseen in the final game. See @xref{Lines and Shapes} for more information. @deffn {Procedure} make-bezier-curve @var{p0} @var{p1} @var{p2} @var{p3} Return a new Bezier curve object whose starting point is @var{p0}, ending point is @var{p3}, and control points are @var{p1} and @var{p2}. All points are 2D vectors. @end deffn @deffn {Procedure} bezier-curve? @var{obj} Return @code{#t} if @var{obj} is a Bezier curve. @end deffn @deffn {Procedure} bezier-curve-p0 @var{bezier} Return the starting point of @var{bezier}. @end deffn @deffn {Procedure} bezier-curve-p1 @var{bezier} Return the first control point of @var{bezier}. @end deffn @deffn {Procedure} bezier-curve-p2 @var{bezier} Return the second control point of @var{bezier}. @end deffn @deffn {Procedure} bezier-curve-p3 @var{bezier} Return the end point of @var{bezier}. @end deffn @deffn {Procedure} bezier-path . @var{control-points} Return a list of connected bezier curves defined by @var{control-points}. The first curve is defined by the first 4 arguments and every additional curve thereafter requires 3 additional arguments. @end deffn @deffn {Procedure} bezier-curve-point-at @var{bezier} @var{t} Return the coordinates for @var{bezier} at @var{t} (a value in the range [0, 1] representing how far from the start of the curve to check) as a 2D vector. @end deffn @deffn {Procedure} bezier-curve-point-at! @var{dest} @var{bezier} @var{t} Modify the 2D vector @var{dest} in-place to contain the coordinates for @var{bezier} at @var{t}. @end deffn @node Path Finding @subsection Path Finding Most game worlds have maps. Often, these games have a need to move non-player characters around in an unscripted fashion. For example, in a real-time strategy game, the player may command one of their units to attack something in the enemy base. To do so, the unit must calculate the shortest route to get there. It wouldn't be a very fun game if units didn't know how to transport themselves efficiently. This is where path finding algorithms come in handy. The @code{(chickadee math path-finding)} module provides a generic implementation of the popular A* path finding algorithm. Just add a map implementation! The example below defines a very simple town map and finds the quickest way to get from the town common to the school. @example (define world-map '((town-common . (town-hall library)) (town-hall . (town-common school)) (library . (town-common cafe)) (school . (town-hall cafe)) (cafe . (library school)))) (define (neighbors building) (assq-ref town-map building)) (define (cost a b) 1) (define (distance a b) 1) (define pf (make-path-finder)) (a* pf 'town-common 'school neighbors cost distance) @end example In this case, the @code{a*} procedure will return the list @code{(town-common town-hall school)}, which is indeed the shortest route. (The other possible route is @code{(town-common library cafe school)}.) The @code{a*} procedure does not know anything about about any kind of map and therefore must be told how to look up neighboring nodes, which is what the @code{neighbors} procedure in the example does. To simulate different types of terrain, a cost procedure is used. In this example, it is just as easy to move between any two nodes because @code{cost} always returns 1. In a real game, perhaps moving from from a field to a rocky hill would cost a lot more than moving from one field to another. Finally, a heuristic is used to calculate an approximate distance between two nodes on the map. In this simple association list based graph it is tough to calculate a distance between nodes, so the @code{distance} procedure isn't helpful and always returns 1. In a real game with a tile-based map, for example, the heuristic could be a quick Manhattan distance calculation based on the coordinates of the two map tiles. Choose an appropriate heuristic for optimal path finding! @deffn {Procedure} make-path-finder Return a new path finder object. @end deffn @deffn {Procedure} path-finder? @var{obj} Return @code{#t} if @var{obj} is a path finder. @end deffn @deffn {Procedure} a* @var{path-finder} @var{start} @var{goal} @ @var{neighbors} @var{cost} @var{distance} Return a list of nodes forming a path from @var{start} to @var{goal} using @var{path-finder} to hold state. @var{neighbors} is a procedure that accepts a node and returns a list of nodes that neighbor it. @var{cost} is a procedure that accepts two neighboring nodes and returns the cost of moving from the first to the second as a real number. @var{distance} is a procedure that accepts two nodes and returns an approximate distance between them. @end deffn @node Graphics @section Graphics Chickadee aims to make hardware-accelerated graphics rendering as simple and efficient as possible by providing high-level APIs that interact with the low-level OpenGL API under the hood. Anyone that has worked with OpenGL directly knows that it has a steep learning curve and a lot of effort is needed to render even a single triangle. The Chickadee rendering engine attempts to make it easy to do common tasks like rendering a sprite while also providing all of the building blocks to implement additional rendering techniques. @menu * Textures:: 2D images. * Sprites:: Draw 2D images. * Tile Maps:: Draw 2D tile maps. * Lines and Shapes:: Draw line segments and polygons. * Fonts:: Drawing text. * Particles:: Pretty little flying pieces! * Blending:: Control how pixels are combined. * Framebuffers:: Render to texture. * Viewports:: Restrict rendering to a particular area. * Rendering Engine:: Rendering state management. * Buffers:: Send data to the GPU. * Shaders:: Create custom GPU programs. @end menu @node Textures @subsection Textures @deffn {Procedure} load-image @var{file} [#:min-filter nearest] @ [#:mag-filter nearest] [#:wrap-s repeat] [#:wrap-t repeat] Load the image data from @var{file} and return a new texture object. @var{min-filter} and @var{mag-filter} describe the method that should be used for minification and magnification when rendering, respectively. Possible values are @code{nearest} and @code{linear}. @var{wrap-s} and @var{wrap-t} describe how to interpret texture coordinates that are greater than @code{1.0}. Possible values are @code{repeat}, @code{clamp}, @code{clamp-to-border}, and @code{clamp-to-edge}. @end deffn @node Sprites @subsection Sprites For those who are new to this game, a sprite is a 2D rectangular bitmap that is rendered to the screen. For 2D games, sprites are the most essential graphical abstraction. They are used for drawing maps, players, NPCs, items, particles, text, etc. In Chickadee, bitmaps are stored in textures (@pxref{Textures}) and can be used to draw sprites via the @code{draw-sprite} procedure. @deffn {Procedure} draw-sprite @var{texture} @var{position} @ [#:origin] [#:scale] [#:rotation] [#:blend-mode alpha] @ [#:rect] [#:shader] Draw @var{texture} at @var{position}. Optionally, other transformations may be applied to the sprite. @var{rotation} specifies the angle to rotate the sprite, in radians. @var{scale} specifies the scaling factor as a 2D vector. All transformations are applied relative to @var{origin}, a 2D vector, which defaults to the lower-left corner. Alpha blending is used by default but the blending method can be changed by specifying @var{blend-mode}. The area drawn to is as big as the texture, by default. To draw to an arbitrary section of the screen, specify @var{rect}. Finally, advanced users may specify @var{shader} to change the way the sprite is rendered entirely. @end deffn It's not uncommon to need to draw hundreds or thousands of sprites each frame. However, GPUs (graphics processing units) are tricky beasts that prefer to be sent few, large chunks of data to render rather than many, small chunks. Using @code{draw-sprite} on its own will involve at least one GPU call @emph{per sprite}, which will quickly lead to poor performance. To deal with this, a technique known as ``sprite batching'' can be used. Instead of drawing each sprite immediately, the sprite batch will build up a large of buffer of sprites to draw and defer rendering until the last possible moment. Batching isn't a panacea, though. Batching only works if the sprites being drawn share as much in common as possible. Every time you draw a sprite with a different texture or blend mode, the batch will be sent off to the GPU. Therefore, batching is most useful if you minimize such changes. A good strategy for reducing texture changes is to stuff many bitmaps into a single image file and create a ``texture atlas'' (@pxref{Textures}) to access the sub-images within. Taking advantage of sprite batching in Chickadee is easy, just wrap the code that is calling @code{draw-sprite} a lot in the @code{with-batched-sprites} form. @deffn {Syntax} with-batched-sprites @var{body} @dots{} Use batched rendering for all @code{draw-sprite} calls within @var{body}. @end deffn With a basic sprite abstraction in place, it's possible to build other abstractions on top of it. One such example is the ``nine patch''. A nine patch is a sprite that can be rendered at various sizes without becoming distorted. This is achieved by dividing up the sprite into nine regions: @itemize @item the center, which can be scaled horizontally and vertically @item the four corners, which can never be scaled @item the left and right sides, which can be scaled vertically @item the top and bottom sides, which can be scaled horizontally @end itemize The one caveat is that the bitmap regions must be designed in such a way so that they are not distorted when stretched along the affected axes. For example, that means that the top and bottom sides could have varying colored pixels vertically, but not horizontally. The most common application of this technique is for graphical user interface widgets like buttons and dialog boxes. By using a nine patch, they can be rendered at any size without unappealing scaling artifacts. @deffn {Procedure} draw-nine-patch @var{texture} @var{rect} @ [#:margin 0] [#:top-margin margin] [#:bottom-margin margin] @ [#:left-margin margin] [#:right-margin margin] @ [#:origin] [#:scale] [#:rotation] [#:blend-mode alpha] @ [#:shader] Draw a nine patch sprite. A nine patch sprite renders @var{texture} as a @var{width} x @var{height} rectangle whose stretchable areas are defined by the given margin measurements @var{top-margin}, @var{bottom-margin}, @var{left-margin}, and @var{right-margin}. The @var{margin} argument may be used to configure all four margins at once. Refer to @code{draw-sprite} (@pxref{Sprites}) for information about the other arguments. @end deffn @node Tile Maps @subsection Tile Maps A tile map is a scene created by composing lots of small sprites, called ``tiles'', into a larger image. One program for editing such maps is called @url{http://mapeditor.org,Tiled}. Chickadee has native support for loading and rendering Tiled maps in the @code{(chickadee render tiled)} module. @deffn {Procedure} load-tile-map @var{file-name} Load the Tiled formatted map in @var{file-name} and return a new tile map object. @end deffn @deffn {Procedure} draw-tile-map @var{tile-map} [#:layers] [#:region] @ [#:origin] [#:position] [#:scale] [#:rotation] Draw the layers of @var{tile-map}. By default, all layers are drawn. To draw a subset of the available layers, pass a list of layer ids using the @var{layers} keyword argument. Refer to @code{draw-sprite} (@pxref{Sprites}) for information about the other arguments. @end deffn @node Lines and Shapes @subsection Lines and Shapes Sprites are fun, but sometimes simple, untextured lines and polygons are desired. That's where the @code{(chickadee render shapes)} module comes in! @deffn {Procedure} draw-line @var{start} @var{end} @ [#:thickness 0.5] [#:feather 1.0] [#:cap round] [#:color] @ [#:shader] Draw a line segment from @var{start} to @var{end}. The line will be @var{thickness} pixels thick with an antialiased border @var{feather} pixels wide. The line will be colored @var{color}. @var{cap} specifies the type of end cap that should be used to terminate the lines, either @code{none}, @code{butt}, @code{square}, @code{round}, @code{triangle-in}, or @code{triangle-out}. Advanced users may use the @var{shader} argument to override the built-in line segment shader. @end deffn @deffn {Procedure} draw-bezier-curve @var{bezier} [#:segments 32] @ [#:control-points?] [#:tangents?] @ [#:control-point-size 8] @ [#:control-point-color yellow] @ [#:tangent-color yellow] @ [#:thickness 0.5] [#:feather 1.0] @ [#:matrix] Draw the curve defined by @var{bezier} using a resolution of N @var{segments}. When @var{control-points?} is @code{#t}, the control points are rendered as squares of size @var{control-point-size} pixels and a color of @var{control-point-color}. When @var{tangents?} is @code{#t}, the tangent lines from terminal point to control point are rendered using the color @var{tangent-color}. All line segments rendered use @code{draw-line}, and thus the arguments @var{thickness} and @var{feather} have the same effect as in that procedure. A custom @var{matrix} may be passed for applications that require more control over the final output. @end deffn @deffn {Procedure} draw-bezier-path @var{path} [#:segments 32] @ [#:control-points?] [#:tangents?] @ [#:control-point-size 8] @ [#:control-point-color yellow] @ [#:tangent-color yellow] @ [#:thickness 0.5] [#:feather 1.0] @ [#:matrix] Render @var{path}, a list of bezier curves. See the documentation for @code{draw-bezier-curve} for an explanation of all the keyword arguments. @end deffn @node Fonts @subsection Fonts Printing text to the screen is quite easy: @example (draw-text "Hello, world" (vec2 100.0 100.0)) @end example Chickadee loads and renders bitmap fonts in the @url{http://www.angelcode.com/products/bmfont/doc/file_format.html, Angel Code format}. A default font named ``Good Neighbors'' is built-in to Chickadee and is used for all text rendering operations where a font is not specified, as is the case in the above example. The following procedures can be found in the @code{(chickadee render font)} module: @deffn {Procedure} load-font @var{file} Load the Angel Code font (in either XML or FNT format) in @var{file} and return a new font object. @end deffn @deffn {Procedure} font? @var{obj} Return @code{#t} if @var{obj} is a font object. @end deffn @deffn {Procedure} font-face @var{font} Return the name of @var{font}. @end deffn @deffn {Procedure} font-line-height @var{font} Return the line height of @var{font}. @end deffn @deffn {Procedure} font-line-height @var{font} Return the line height of @var{font}. @end deffn @deffn {Procedure} font-bold? @var{font} Return @code{#t} if @var{font} is a bold font. @end deffn @deffn {Procedure} font-italic? @var{font} Return @code{#t} if @var{font} is an italicized font. @end deffn @deffn {Procedure} draw-text @var{text} @var{position} [#:font] [#:origin] [#:scale] [#:rotation] [#:blend-mode] [#:start 0] [#:end @code{(string-length text)}] Draw the string @var{text} with the first character starting at @var{position} using @var{font}. If @var{font} is not specified, a built-in font is used. @example (draw-text font "Hello, world!" (vec2 128.0 128.0)) @end example To render a substring of @var{text}, use the @var{start} and @var{end} arguments. Refer to @code{draw-sprite} (@pxref{Sprites}) for information about the other arguments. @end deffn @node Particles @subsection Particles Effects like smoke, fire, sparks, etc. are often achieved by animating lots of little, short-lived sprites known as ``particles''. In fact, all of these effects, and more, can be accomplished by turning a few configuration knobs in a ``particle system''. A particle system takes care of managing the many miniscule moving morsels so the developer can quickly produce an effect and move on with their life. The @code{(chickadee render particles)} module provides an API for manipulating particle systems. Below is an example of a very simple particle system that utilizes nearly all of the default configuration settings: @example (use-modules (chickadee render particles)) (define texture (load-image "particle.png")) (define particles (make-particles 2000 #:texture texture)) @end example In order to put particles into a particle system, a particle ``emitter'' is needed. Emitters know where to spawn new particles, how many of them to spawn, and for how long they should do it. Below is an example of an emitter that spawns 16 particles per frame at the coordinates @code{(320, 240)}: @example (use-modules (chickadee math vector)) (define emitter (make-particle-emitter (vec2 320.0 240.0) 16)) (add-particle-emitter particles emitter) @end example To see all of the tweakable knobs and switches, read on! @deffn {Procedure} make-particles @var{capacity} [#:blend-mode @code{alpha}] @ [#:color white] [#:end-color transparent] [#:texture] @ [#:animation-rows 1] [#:animation-columns 1] [#:width] [#:height] @ [#:speed-range (vec2 0.1 1.0)] [#:acceleration-range (vec2 0.0 0.1)] @ [#:direction-range (vec2 0 (* 2 pi))] [#:lifetime 30] [#:sort] Return a new particle system that may contain up to @var{capacity} particles. Achieving the desired particle effect involves tweaking the following keyword arguments as needed: - @var{blend-mode}: Pixel blending mode. @code{alpha} by default. (@pxref{Blending} for more about blend modes). - @var{start-color}: The tint color of the particle at the beginning of its life. White by default. - @var{end-color}: The tint color of the particle at the end of of its life. Completely transparent by default for a fade-out effect. The color in the middle of a particle's life will be an interpolation of @var{start-color} and @var{end-color}. - @var{texture}: The texture applied to the particles. The texture may be subdivided into many animation frames. - @var{animation-rows}: How many animation frame rows there are in the texture. Default is 1. - @var{animation-columns}: How many animation frame columns there are in the texture. Default is 1. - @var{width}: The width of each particle. By default, the width of an animation frame (in pixels) is used. - @var{height}: The height of each particle. By default, the height of an animation frame (in pixels) is used. - @var{speed-range}: A 2D vector containing the min and max particle speed. Each particle will have a speed chosen at random from this range. By default, speed ranges from 0.1 to 1.0. - @var{acceleration-range}: A 2D vector containing the min and max particle acceleration. Each particle will have an acceleration chosen at random from this range. By default, acceleration ranges from 0.0 to 0.1. - @var{direction-range}: A 2D vector containing the min and max particle direction as an angle in radians. Each particle will have a direction chosen at random from this range. By default, the range covers all possible angles. - @var{lifetime}: How long each particle lives, measured in updates. 30 by default. - @var{sort}: @code{youngest} if youngest particle should be drawn last or @code{oldest} for the reverse. By default, no sorting is applied at all. @end deffn @deffn {Procedure} particles? @var{obj} Return @code{#t} if @var{obj} is a particle system. @end deffn @deffn {Procedure} update-particles @var{particles} Advance the simulation of @var{particles}. @end deffn @deffn {Procedure} draw-particles @var{particles} Render @var{particles}. @end deffn @deffn {Procedure} draw-particles* @var{particles} @var{matrix} Render @var{particles} with @var{matrix} applied. @end deffn @deffn {Procedure} make-particle-emitter @var{spawn-area} @ @var{rate} [@var{duration}] Return a new particle emitter that spawns @var{rate} particles per frame within @var{spawn-area} (a rectangle or 2D vector) for @var{duration} frames. If @var{duration} is not specified, the emitter will spawn particles indefinitely. @end deffn @deffn {Procedure} particle-emitter? @var{obj} Return @code{#t} if @var{obj} is a particle emitter. @end deffn @deffn {Procedure} particle-emitter-spawn-area @var{emitter} Return the spawn area for @var{emitter}. @end deffn @deffn {Procedure} particle-emitter-rate @var{emitter} Return the number of particles that @var{emitter} will spawn per frame. @end deffn @deffn {Procedure} particle-emitter-life @var{emitter} Return the number of frames remaining in @var{emitter}'s lifespan. @end deffn @deffn {Procedure} particle-emitter-done? @var{emitter} Return @code{#t} if @var{emitter} has finished spawning particlces. @end deffn @deffn {Procedure} add-particle-emitter @var{particles} @var{emitter} Add @var{emitter} to @var{particles}. @end deffn @deffn {Procedure} remove-particle-emitter @var{particles} @var{emitter} Remove @var{emitter} to @var{particles} @end deffn @node Blending @subsection Blending Rendering a scene often involves drawing layers of objects that overlap each other. Blending determines how two overlapping pixels are combined in the final image that is rendered to the screen. Chickadee provides the following blend modes: @itemize @item @code{replace} Use the latest color, ignoring all others. @item @code{alpha} Blend pixels according to the values of their alpha channels. This is the most commonly used blend mode and thus is Chickadee's default mode. @item @code{add} Add all pixel color values together. The more colors blended together, the more white the final color becomes. @item @code{subtract} Subtract all pixel color values. The more colors blended together, the more black the final color becomes. @item @code{multiply} @item @code{darken} @item @code{lighten} @item @code{screen} @end itemize @node Framebuffers @subsection Framebuffers A framebuffer is a chunk of memory that the GPU can render things onto. By default, the framebuffer that is used for rendering is the one belonging to the game window, but custom framebuffers can be used as well. A common use-case for custom framebuffers is applying post-processing effects: The entire scene is rendered to a framebuffer, and then the contents of that framebuffer are applied to a post-processing shader and rendered to the game window. The post-processing shader could do any number of things: scaling, antialiasing, motion blur, etc. @deffn {Procedure} make-framebuffer @var{width} @var{height} [#:min-filter 'linear] [#:mag-filter 'linear] [#:wrap-s 'repeat] [#:wrap-t 'repeat] Create a new framebuffer that is @var{width} pixels wide and @var{height} pixels high. @var{min-filter} and @var{mag-filter} determine the scaling algorithm applied to the framebuffer when rendering. By default, linear scaling is used in both cases. To perform no smoothing at all, use @code{nearest} for simple nearest neighbor scaling. This is typically the best choice for pixel art games. @end deffn @deffn {Procedure} framebuffer? @var{obj} Return @code{#t} if @var{obj} is a framebuffer. @end deffn @deffn {Procedure} framebuffer-texture @var{fb} Return the texture backing the framebuffer @var{fb}. @end deffn @deffn {Procedure} framebuffer-viewport @var{fb} Return the default viewport (@pxref{Viewports}) used by the framebuffer @var{fb}. @end deffn @deffn {Procedure} null-framebuffer The default framebuffer. @end deffn @node Viewports @subsection Viewports A viewport represents a subset of the screen (or framebuffer). When rendering a frame, the resulting image will only appear within that viewport. These aren't often needed, and Chickadee's default viewport occupies the entire screen, but there are certain situations where they are useful. For example, a split-screen multiplayer game may render to two different viewports, each occupying a different half of the screen. For information about how to set the current viewport, see @code{with-viewport} in @ref{Rendering Engine}. The @code{(chickadee render viewport)} module provides the following API: @deffn {Procedure} make-viewport @var{x} @var{y} @var{width} @var{height} @ [#:clear-color] [#:clear-flags] Create a viewport that covers an area of the window starting from coordinates (@var{x}, @var{y}) and spanning @var{width} @code{x} @var{height} pixels. Fill the viewport with @var{clear-color} when clearing the screen. Clear the buffers denoted by the list of symbols in @var{clear-flags}. Possible values for @var{clear-flags} are @var{color-buffer}, @var{depth-buffer}, @var{accum-buffer}, and @var{stencil-buffer}. @end deffn @deffn {Procedure} viewport? @var{obj} Return @code{#t} if @var{obj} is a viewport. @end deffn @deffn {Procedure} viewport-x @var{viewport} Return the left edge of @var{viewport}. @end deffn @deffn {Procedure} viewport-y @var{viewport} Return the bottom edge of @var{viewport}. @end deffn @deffn {Procedure} viewport-width @var{viewport} Return the width of @var{viewport}. @end deffn @deffn {Procedure} viewport-height @var{viewport} Return the height of @var{viewport}. @end deffn @deffn {Procedure} viewport-clear-color @var{viewport} Return the clear color for @var{viewport}. @end deffn @deffn {Procedure} viewport-clear-flags @var{viewport} Return the list of clear flags for @var{viewport}. @end deffn @node Rendering Engine @subsection Rendering Engine Chickadee defines rendering using a metaphor familiar to Scheme programmers: procedure application. A shader (@pxref{Shaders}) is like a procedure for the GPU to apply. Shaders are passed arguments: A vertex array containing the geometry to render (@pxref{Buffers}) and zero or more keyword arguments that the shader understands. Similar to how Scheme has @code{apply} for calling procedures, Chickadee provides @code{gpu-apply} for calling shaders. Additionally, there is some dynamic state that effects how @code{gpu-apply} will behave. Things like the current viewport, framebuffer, and blend mode are stored as dynamic state because it would be tedious to have to have to specify them each time @code{gpu-apply} is called. The following procedures and syntax can be found in the @code{(chickadee render)} module. @deffn {Syntax} gpu-apply @var{shader} @var{vertex-array} @ [#:uniform-key @var{uniform-value} ...] @deffnx {Syntax} gpu-apply* @var{shader} @var{vertex-array} @ @var{count} [#:uniform-key @var{uniform-value} ...] Render @var{vertex-array} using @var{shader} with the uniform values specified in the following keyword arguments. While @code{gpu-apply} will draw every vertex in @var{vertex-array}, @code{gpu-apply*} will only draw @var{count} vertices. @end deffn @deffn {Syntax} gpu-apply/instanced @var{shader} @var{vertex-array} @ @var{n} [#:uniform-key @var{uniform-value} ...] @deffnx {Syntax} gpu-apply/instanced @var{shader} @var{vertex-array} @ @var{count} @var{n} [#:uniform-key @var{uniform-value} ...] Render @var{vertex-array} @var{n} times using @var{shader} with the uniform values specified in the following keyword arguments. Instanced rendering is very beneficial for rendering the same object many times with only small differences for each one. For example, the particle effects described in @ref{Particles} use instanced rendering. While @code{gpu-apply/instanced} will draw every vertex in @var{vertex-array}, @code{gpu-apply*} will only draw @var{count} vertices. @end deffn @deffn {Procedure} current-viewport Return the currently bound viewport (@pxref{Viewports}). @end deffn @deffn {Procedure} current-framebuffer Return the currently bound framebuffer (@pxref{Framebuffers}). @end deffn @deffn {Procedure} current-blend-mode Return the currently bound blend mode (@pxref{Blending}). @end deffn @deffn {Procedure} current-depth-test Return @code{#t} if depth testing is currently enabled (@pxref{Blending}). @end deffn @deffn {Procedure} current-texture Return the currently bound texture (@pxref{Textures}). @end deffn @deffn {Procedure} current-projection Return the currently bound projection matrix (@pxref{Matrices}). @end deffn @deffn {Syntax} with-viewport @var{viewport} @var{body} ... Evaluate @var{body} with the current viewport bound to @var{viewport} (@pxref{Viewports}). @end deffn @deffn {Syntax} with-framebuffer @var{framebuffer} @var{body} ... Evaluate @var{body} with the current framebuffer bound to @var{framebuffer} (@pxref{Framebuffers}). @end deffn @deffn {Syntax} with-blend-mode @var{blend-mode} @var{body} ... Evaluate @var{body} with the current blend mode bound to @var{blend-mode} (@pxref{Blending}). @end deffn @deffn {Syntax} with-depth-test @var{depth-test?} @var{body} ... Evaluate @var{body} with the depth-test disabled if @var{depth-test?} is @code{#f}, or enabled otherwise (@pxref{Blending}). @end deffn @deffn {Syntax} with-texture @var{texture} @var{body} ... Evaluate @var{body} with the current texture bound to @var{texture} (@pxref{Textures}). @end deffn @deffn {Syntax} with-projection @var{projection} @var{body} ... Evaluate @var{body} with the current projection matrix bound to @var{projection} (@pxref{Matrices}). @end deffn @node Buffers @subsection Buffers Alright, let's brush aside all of those pretty high level abstractions and discuss what is going on under the hood. The GPU exists as a discrete piece of hardware separate from the CPU. In order to make it draw things, we must ship lots of data out of our memory space and into the GPU. The @code{(chickadee render buffer}) module provides an API for manipulating GPU buffers. In OpenGL terminology, a chunk of data allocated on the GPU is a ``vertex buffer object'' or VBO. For example, here is a bytevector that could be transformed into a GPU buffer that packs together vertex position and texture coordinates: @example (use-modules (chickadee render buffer) (srfi srfi-4)) (define data (f32vector -8.0 -8.0 ; 2D vertex 0.0 0.0 ; 2D texture coordinate 8.0 -8.0 ; 2D vertex 1.0 0.0 ; 2D texture coordinate 8.0 8.0 ; 2D vertex 1.0 1.0 ; 2D texture coordinate -8.0 8.0 ; 2D vertex 0.0 1.0)) ; 2D texture coordinate @end example This data represents a textured 16x16 square centered on the origin. To send this data to the GPU, the @code{make-buffer} procedure is needed: @example (define buffer (make-buffer data #:stride 16)) @end example The @code{#:stride} keyword argument indicates how many bytes make up each element of the buffer. In this case, there are 4 floats per element: 2 for the vertex, and 2 for the texture coordinate. A 32-bit float is 4 bytes in length, so the buffer's stride is 16. Within a VBO, one or more ``attributes'', as OpenGL calls them, may be present. Attributes are subregions within the buffer that have a particular data type. In this case, there are two attributes packed into the buffer. To provided a typed view into a buffer, the @code{make-typed-buffer} procedure is needed: @example (define vertices (make-typed-buffer #:buffer buffer #:type 'vec2 #:component-type 'float #:length 4)) (define texcoords (make-typed-buffer #:buffer buffer #:type 'vec2 #:component-type 'float #:length 4 #:offset 8)) @end example To render a square, the GPU needs to draw two triangles, which means we need 6 vertices in total. However, the above buffer only contains data for 4 vertices. This is becase there are only 4 unique vertices for a square, but 2 of them must be repeated for each triangle. To work with deduplicated vertex data, an ``index buffer'' must be created. @example (define index-buffer (make-buffer (u32vector 0 3 2 0 2 1) #:target 'index) (define indices (make-typed-buffer #:type 'scalar #:component-type 'unsigned-int #:buffer index-buffer)) @end example Note the use of the @code{#:target} keyword argument. It is required because the GPU treats index data in a special way and must be told which data is index data. Now that the typed buffers representing each attribute have been created, all that's left is to bind them all together in a ``vertex array object'', or VAO. Vertex arrays associate each typed buffer with an attribute index on the GPU. The indices that are chosen must correspond with the indices that the shader (@pxref{Shaders}) expects for each attribute. @example (define vertex-array (make-vertex-array #:indices indices #:attributes `((0 . ,vertices) (1 . ,texcoords)))) @end example With the vertex array created, the GPU is now fully aware of how to interpret the data that it has been given in the original buffer. Actually rendering this square is left as an exercise to the reader. See the @ref{Shaders} section and the @code{gpu-apply} procedure in @ref{Rendering Engine} for the remaining pieces of a successful draw call. Additionally, consider reading the source code for sprites, shapes, or particles to see GPU buffers in action. Without further ado, the API reference: @deffn {Procedure} make-buffer @var{data} [#:name "anonymous"] @ [#:length] [#:offset 0] [#:stride 0] [#:target @code{vertex}] @ [#:usage @code{static}] Upload @var{data}, a bytevector, to the GPU. By default, the entire bytevector is uploaded. A subset of the data may be uploaded by specifying the @var{offset}, the index of the first byte to be uploaded, and @var{length}, the number of bytes to upload. If @var{data} is @code{#f}, allocate @var{length} bytes of fresh GPU memory instead. @var{target} and @var{usage} are hints that tell the GPU how the buffer is intended to be used. @var{target} may be: @itemize @item @code{vertex} Vertex attribute data. @item @code{index} Index buffer data. @end itemize @var{usage} may be: @itemize @item @code{static} The buffer data will not be modified after creation. @item @code{stream} The buffer data will be modified frequently. @end itemize @var{name} is simply an arbitrary string for debugging purposes that is never sent to the GPU. @end deffn @deffn {Procedure} buffer? @var{obj} Return @code{#t} if @var{obj} is a GPU buffer. @end deffn @deffn {Procedure} index-buffer? @var{buffer} Return @code{#t} if @var{buffer} is an index buffer. @end deffn @defvar null-buffer Represents the absence of a buffer. @end defvar @deffn {Procedure} buffer-name @var{buffer} Return the name of @var{buffer}. @end deffn @deffn {Procedure} buffer-length @var{buffer} Return the length of @var{buffer}. @end deffn @deffn {Procedure} buffer-stride @var{buffer} Return the amount of space, in bytes, between each element in @var{buffer}. @end deffn @deffn {Procedure} buffer-target @var{buffer} Return the the intended usage of @var{buffer}, either @code{vertex} or @code{index}. @end deffn @deffn {Procedure} buffer-usage @var{buffer} Return the intended usage of @var{buffer}, either @code{static} for buffer data that will not change once sent to the GPU, or @code{stream} for buffer data that will be frequently updated from the client-side. @end deffn @deffn {Procedure} buffer-data @var{buffer} Return a bytevector containing all the data within @var{buffer}. If @var{buffer} has not been mapped (see @code{with-mapped-buffer}) then this procedure will return @code{#f}. @end deffn @deffn {Syntax} with-mapped-buffer @var{buffer} @var{body} @dots{} Evaluate @var{body} in the context of @var{buffer} having its data synced from GPU memory to RAM. In this context, @code{buffer-data} will return a bytevector of all the data stored in @var{buffer}. When program execution exits this form, the data (including any modifications) is synced back to the GPU. This form is useful for streaming buffers that need to update their contents dynamically, such as a sprite batch. @end deffn @deffn {Procedure} make-typed-buffer #:buffer #:type @ #:component-type #:length [#:offset 0] [#:divisor] @ [#:name "anonymous"] Return a new typed buffer view for @var{buffer} starting at byte index @var{offset} of @var{length} elements, where each element is of @var{type} and composed of @var{component-type} values. Valid values for @var{type} are: @itemize @item @code{scalar} single number @item @code{vec2} 2D vector @item @code{vec3} 3D vector @item @code{vec4} 4D vector @item @code{mat2} 2x2 matrix @item @code{mat3} 3x3 matrix @item @code{mat4} 4x4 matrix @end itemize Valid values for @var{component-type} are: @itemize @item @code{byte} @item @code{unsigned-byte} @item @code{short} @item @code{unsigned-short} @item @code{int} @item @code{unsigned-int} @item @code{float} @item @code{double} @end itemize @var{divisor} is only needed for instanced rendering applications (see @code{gpu-apply/instanced} in @ref{Rendering Engine}) and represents how many instances each vertex element applies to. A divisor of 0 means that a single element is used for every instance and is used for the data being instanced. A divisor of 1 means that each element is used for 1 instance. A divisor of 2 means that each element is used for 2 instances, and so on. @end deffn @deffn {Procedure} typed-buffer? @var{obj} Return @code{#t} if @var{obj} is a typed buffer. @end deffn @deffn {Procedure} typed-buffer->buffer @var{typed-buffer} Return the buffer that @var{typed-buffer} is using. @end deffn @deffn {Procedure} typed-buffer-name @var{typed-buffer} Return the name of @var{typed-buffer}. @end deffn @deffn {Procedure} typed-buffer-offset @var{typed-buffer} Return the byte offset of @var{typed-buffer}. @end deffn @deffn {Procedure} typed-buffer-type @var{typed-buffer} Return the data type of @var{typed-buffer}. @end deffn @deffn {Procedure} typed-buffer-component-type @var{typed-buffer} Return the component data type of @var{typed-buffer} @end deffn @deffn {Procedure} typed-buffer-divisor @var{typed-buffer} Return the instance divisor for @var{typed-buffer}. @end deffn @deffn {Syntax} with-mapped-typed-buffer @var{typed-buffer} @var{body} @dots{} Evaluate @var{body} in the context of @var{typed-buffer} having its data synced from GPU memory to RAM. See @code{with-mapped-buffer} for more information. @end deffn @deffn {Procedure} make-vertex-array #:indices #:attributes @ [#:mode @code{triangles}] Return a new vertex array using the index data within the typed buffer @var{indices} and the vertex attribute data within @var{attributes}. @var{attributes} is an alist mapping shader attribute indices to typed buffers containing vertex data: @example `((1 . ,typed-buffer-a) (2 . ,typed-buffer-b) ...) @end example By default, the vertex array is interpreted as containing a series of triangles. If another primtive type is desired, the @var{mode} keyword argument may be overridden. The following values are supported: @itemize @item @code{points} @item @code{lines} @item @code{line-loop} @item @code{line-strip} @item @code{triangles} @item @code{triangle-strip} @item @code{triangle-fan} @end itemize @end deffn @defvar null-vertex-array Represents the absence of a vertex array. @end defvar @deffn {Procedure} vertex-array? @var{obj} Return @code{#t} if @var{obj} is a vertex array. @end deffn @deffn {Procedure} vertex-array-indices @var{vertex-array} Return the typed buffer containing index data for @var{vertex-array}. @end deffn @deffn {Procedure} vertex-array-attributes @var{vertex-array} Return the attribute index -> typed buffer mapping of vertex attribute data for @var{vertex-array}. @end deffn @deffn {Procedure} vertex-array-mode @var{vertex-array} Return the primitive rendering mode for @var{vertex-array}. @end deffn @node Shaders @subsection Shaders Shaders are programs that the GPU can evaluate that allow the programmer to completely customized the final output of a GPU draw call. The @code{(chickadee render shader)} module provides an API for building custom shaders. Shaders are written in the OpenGL Shading Language, or GLSL for short. Chickadee aspires to provide a domain specific language for writing shaders in Scheme, but we are not there yet. Shader programs consist of two components: A vertex shader and a fragment shader. A vertex shader receives vertex data (position coordinates, texture coordinates, normals, etc.) and transforms them as desired, whereas a fragment shader controls the color of each pixel. Sample vertex shader: @example @verbatim #version 130 in vec2 position; in vec2 tex; out vec2 fragTex; uniform mat4 mvp; void main(void) { fragTex = tex; gl_Position = mvp * vec4(position.xy, 0.0, 1.0); } @end verbatim @end example Sample fragment shader: @example @verbatim #version 130 in vec2 fragTex; uniform sampler2D colorTexture; void main (void) { gl_FragColor = texture2D(colorTexture, fragTex); } @end verbatim @end example This manual will not cover GLSL features and syntax as there is lots of information already available about this topic. One way to think about rendering with shaders, and the metaphor Chickadee uses, is to think about it as a function call: The shader is a function, and it is applied to some ``attributes'' (positional arguments), and some ``uniforms'' (keyword arguments). @example (define my-shader (load-shader "vert.glsl" "frag.glsl")) (define vertices (make-vertex-array ...)) (gpu-apply my-shader vertices #:color red) @end example @xref{Rendering Engine} for more details about the @code{gpu-apply} procedure. Shaders are incredibly powerful tools, and there's more information about them than we could ever fit into this manual, so we highly recommend searching the web for more information and examples. What we can say, though, is how to use our API: @deffn {Procedure} strings->shader @var{vertex-source} @var{fragment-source} Compile @var{vertex-source}, the GLSL code for the vertex shader, and @var{fragment-source}, the GLSL code for the fragment shader, into a GPU shader program. @end deffn @deffn {Procedure} load-shader @var{vertex-source-file} @ @var{fragment-source-file} Compile the GLSL source code within @var{vertex-source-file} and @var{fragment-source-file} into a GPU shader program. @end deffn @deffn {Procedure} make-shader @var{vertex-port} @var{fragment-port} Read GLSL source from @var{vertex-port} and @var{fragment-port} and compile them into a GPU shader program. @end deffn @deffn {Procedure} shader? @var{obj} Return @code{#t} if @var{obj} is a shader. @end deffn @defvar null-shader Represents the absence shader program. @end defvar @deffn {Procedure} shader-uniform @var{shader} @var{name} Return the metadata for the uniform @var{name} in @var{shader}. @end deffn @deffn {Procedure} shader-uniforms @var{shader} Return a hash table of uniforms for @var{shader}. @end deffn @deffn {Procedure} shader-attributes @var{shader} Return a hash table of attributes for @var{shader}. @end deffn @deffn {Procedure} uniform? @var{obj} Return @code{#t} if @var{obj} is a uniform. @end deffn @deffn {Procedure} uniform-name @var{uniform} Return the variable name of @var{uniform}. @end deffn @deffn {Procedure} uniform-type @var{uniform} Return the data type of @var{uniform}. @end deffn @deffn {Procedure} uniform-value @var{uniform} Return the current value of @var{uniform}. @end deffn @deffn {Procedure} uniform-default-value @var{uniform} Return the default value of @var{uniform}. @end deffn @deffn {Procedure} attribute? @var{obj} Return @code{#t} if @var{obj} is an attribute. @end deffn @deffn {Procedure} attribute-name @var{attribute} Return the variable name of @var{attribute}. @end deffn @deffn {Procedure} attribute-location @var{attribute} Return the binding location of @var{attribute}. @end deffn @deffn {Procedure} attribute-type @var{attribute} Return the data type of @var{attribute}. @end deffn @node Scripting @section Scripting Game logic is a web of asynchronous events that are carefully coordinated to bring the game world to life. In order to make an enemy follow and attack the player, or move an NPC back and forth in front of the item shop, or do both at the same time, a scripting system is a necessity. Chickadee comes with an asynchronous programming system in the @code{(chickadee scripting)} module. Lightweight, cooperative threads known as ``scripts'' allow the programmer to write asynchronous code as if it were synchronous, and allow many such ``threads'' to run concurrently. But before we dig deeper into scripts, let's discuss the simple act of scheduling tasks. @menu * Agendas:: Scheduling tasks. * Scripts:: Cooperative multitasking. * Tweening:: Animations. * Channels:: Publish data to listeners. @end menu @node Agendas @subsection Agendas To schedule a task to be performed later, an ``agenda'' is used. There is a default, global agenda that is ready to be used, or additional agendas may be created for different purposes. The following example prints the text ``hello'' when the agenda has advanced to time unit 10. @example (at 10 (display "hello\n")) @end example Most of the time it is more convenient to schedule tasks relative to the current time. This is where @code{after} comes in handy: @example (after 10 (display "hello\n")) @end example Time units in the agenda are in no way connected to real time. It's up to the programmer to decide what agenda time means. A simple and effective approach is to map each call of the update hook (@pxref{Kernel}) to 1 unit of agenda time, like so: @example (add-hook! update-hook (lambda (dt) (update-agenda 1))) @end example It is important to call @code{update-agenda} periodically, otherwise no tasks will ever be run! In addition to using the global agenda, it is useful to have multiple agendas for different purposes. For example, the game world can use a different agenda than the user interface, so that pausing the game is a simple matter of not updating the world's agenda while continuing to update the user interface's agenda. The current agenda is dynamically scoped and can be changed using the @code{with-agenda} special form: @example (define game-world-agenda (make-agenda)) (with-agenda game-world-agenda (at 60 (spawn-goblin)) (at 120 (spawn-goblin)) (at 240 (spawn-goblin-king))) @end example @deffn {Procedure} make-agenda Return a new task scheduler. @end deffn @deffn {Procedure} agenda? @var{obj} Return @code{#t} if @var{obj} is an agenda. @end deffn @deffn {Procedure} current-agenda @deffnx {Procedure} current-agenda @var{agenda} When called with no arguments, return the current agenda. When called with one argument, set the current agenda to @var{agenda}. @end deffn @deffn {Syntax} with-agenda @var{agenda} @var{body} @dots{} Evaluate @var{body} with the current agenda set to @var{agenda}. @end deffn @deffn {Procedure} agenda-time Return the current agenda time. @end deffn @deffn {Procedure} update-agenda @var{dt} Advance the current agenda by @var{dt}. @end deffn @deffn {Procedure} schedule-at @var{time} @var{thunk} Schedule @var{thunk}, a procedure of zero arguments, to be run at @var{time}. @end deffn @deffn {Procedure} schedule-after @var{delay} @var{thunk} Schedule @var{thunk}, a procedure of zero arguments, to be run after @var{delay}. @end deffn @deffn {Procedure} schedule-every @var{interval} @var{thunk} [@var{n}] Schedule @var{thunk}, a procedure of zero arguments, to be run every @var{interval} amount of time. Repeat this @var{n} times, or indefinitely if not specified. @end deffn @deffn {Syntax} at @var{time} @var{body} @dots{} Schedule @var{body} to be evaluated at @var{time}. @end deffn @deffn {Syntax} after @var{delay} @var{body} @dots{} Schedule @var{body} to be evaluated after @var{delay}. @end deffn @deffn {Syntax} every @var{interval} @var{body} @dots{} @deffnx {Syntax} every (@var{interval} @var{n}) @var{body} @dots{} Schedule @var{body} to be evaluated every @var{interval} amount of time. Repeat this @var{n} times, or indefinitely if not specified. @end deffn @node Scripts @subsection Scripts Now that we can schedule tasks, let's take things to the next level. It sure would be great if we could make procedures that described a series of actions that happened over time, especially if we could do so without contorting our code into a nest of callback procedures. This is where scripts come in. With scripts we can write code in a linear way, in a manner that appears to be synchronous, but with the ability to suspend periodically in order to let other scripts have a turn and prevent blocking the game loop. Building on top of the scheduling that agendas provide, here is a script that models a child trying to get their mother's attention: @example (script (while #t (display "mom!") (newline) (sleep 60))) ; where 60 = 1 second of real time @end example This code runs in an endless loop, but the @code{sleep} procedure suspends the script and schedules it to be run later by the agenda. So, after each iteration of the loop, control is returned back to the game loop and the program is not stuck spinning in a loop that will never exit. Pretty neat, eh? Scripts can suspend to any capable handler, not just the agenda. The @code{yield} procedure will suspend the current script and pass its ``continuation'' to a handler procedure. This handler procedure could do anything. Perhaps the handler stashes the continuation somewhere where it will be resumed when the user presses a specific key on the keyboard, or maybe it will be resumed when the player picks up an item off of the dungeon floor; the sky is the limit. Sometimes it is necessary to abruptly terminate a script after it has been started. For example, when an enemy is defeated their AI routine needs to be shut down. When a script is spawned, a handle to that script is returned that can be used to cancel it when desired. @example (define script (script (while #t (display "hey\n") (sleep 60)))) ;; sometime later (cancel-script script) @end example @deffn {Procedure} spawn-script @var{thunk} Apply @var{thunk} as a script and return a handle to it. @end deffn @deffn {Syntax} script @var{body} @dots{} Evaluate @var{body} as a script and return a handle to it. @end deffn @deffn {Procedure} script? @var{obj} Return @code{#t} if @var{obj} is a script handle. @end deffn @deffn {Procedure} script-cancelled? @var{obj} Return @code{#t} if @var{obj} has been cancelled. @end deffn @deffn {Procedure} script-running? @var{obj} Return @code{#t} if @var{obj} has not yet terminated or been cancelled. @end deffn @deffn {Procedure} script-complete? @var{obj} Return @code{#t} if @var{obj} has terminated. @end deffn @deffn {Procedure} cancel-script @var{co} Prevent further execution of the script @var{co}. @end deffn @deffn {Procedure} yield @var{handler} Suspend the current script and pass its continuation to the procedure @var{handler}. @end deffn @deffn {Procedure} sleep @var{duration} Wait @var{duration} before resuming the current script. @end deffn @deffn {Syntax} forever @var{body} @dots{} Evaluate @var{body} in an endless loop. @end deffn @node Tweening @subsection Tweening Tweening is the process of transitioning something from an initial state to a final state over a pre-determined period of time. In other words, tweening is a way to create animation. The @code{tween} procedure can be used within any script like so: @example (define x 0) (script ;; 0 to 100 in 60 ticks of the agenda. (tween 60 0 100 (lambda (y) (set! x y)))) @end example @deffn {Procedure} tween @var{duration} @var{start} @var{end} @var{proc} [#:step 1 #:ease @code{smoothstep} #:interpolate @code{lerp}] Transition a value from @var{start} to @var{end} over @var{duration}, sending each succesive value to @var{proc}. @var{step} controls the amount of time between each update of the animation. To control how the animation goes from the initial to final state, an ``easing'' procedure may be specified. By default, the @code{smoothstep} easing is used, which is a more pleasing default than a simplistic linear function. @xref{Easings} for a complete list of available easing procedures. The @var{interpolate} procedure computes the values in between @var{start} and @var{end}. By default, linear interpolation (``lerp'' for short) is used. @end deffn @node Channels @subsection Channels Channels are a tool for communicating amongst different scripts. One script can write a value to the channel and another can read from it. Reading or writing to a channel suspends that script until there is someone on the other end of the line to complete the transaction. Here's a simplistic example: @example (define c (make-channel)) (script (forever (let ((item (channel-get c))) (pk 'got item)))) (script (channel-put c 'sword) (channel-put c 'shield) (channel-put c 'potion)) @end example @deffn {Procedure} make-channel Return a new channel @end deffn @deffn {Procedure} channel? @var{obj} Return @code{#t} if @var{obj} is a channel. @end deffn @deffn {Procedure} channel-get @var{channel} Retrieve a value from @var{channel}. The current script suspends until a value is available. @end deffn @deffn {Procedure} channel-put @var{channel} @var{data} Send @var{data} to @var{channel}. The current script suspends until another script is available to retrieve the value. @end deffn A low-level API also exists for using channels outside of a script via callback procedures: @deffn {Procedure} channel-get! @var{channel} @var{proc} Asynchronously retrieve a value from @var{channel} and call @var{proc} with that value. @end deffn @deffn {Procedure} channel-put! @var{channel} @var{data} [@var{thunk}] Asynchronously send @var{data} to @var{channel} and call @var{thunk} after it has been received. @end deffn