@menu * Kernel:: The fundamental components. * Math:: Linear algebra 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)} 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 sdl)} module: @code{run-game/sdl}. @deffn {Procedure} run-game/sdl [#: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. 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. * Matrices:: Transformation matrices. * Quaternions:: Rotations. * Rectangles:: Axis-aligned bounding boxes. * Easings:: Easing functions for interesting animations. @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 @node Matrices @subsection Matrices @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 Rectangles @subsection Rectangles @node Easings @subsection Easings @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 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 * Rendering Engine:: Rendering state management. * Textures:: 2D images. * Sprites:: Draw 2D images. * Tile Maps:: Draw 2D tile maps. * Lines and Shapes:: Draw line segments and polygons. * Fonts:: Drawing text. * Blending and Depth Testing:: Control how pixels are combined. * Vertex Arrays:: Create 2D/3D models. * Shaders:: Create custom GPU programs. * Framebuffers:: Render to texture. * Viewports:: Restrict rendering to @end menu @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{Vertex Arrays}) 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 {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 and Depth Testing}). @end deffn @deffn {Procedure} current-depth-test Return @code{#t} if depth testing is currently enabled (@pxref{Blending and Depth Testing}). @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 and Depth Testing}). @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 and Depth Testing}). @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 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 diving 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 @node Fonts @subsection Fonts Unlike the traditional TrueType font format that many are accustomed to, Chickadee loads and renders bitmap fonts in the @url{http://www.angelcode.com/products/bmfont/doc/file_format.html, Angel Code format}. But why use this seemingly obscure format? It's easy to find TTFs but not easy to find FNTs (the canonical file extension used for Angel Code fonts) and bitmap fonts don't scale well. The reason is efficiency. If all of the glyphs of a font are pre-rendered and packed into an image file then it becomes possible to use a texture atlas (@pxref{Textures}) and a sprite batch (@pxref{Sprites}) when rendering, which is a more efficient way to render fonts than using, say, @url{https://www.libsdl.org/projects/SDL_ttf/, SDL_ttf} or other solutions that involve using the FreeType library directly. Now what about scaling? In libraries that use TTF fonts, one must choose the size that the glyphs will be rasterized at up front. To use @code{n} sizes of the same font, one must load @code{n} variants of that font. If the size of the text is dynamic, some kind of texture scaling algorithm must be used and the text will inevitably look blurry. At first glance, using bitmap fonts seem to have an even worse issue. Instead of just loading the same font @code{n} times at different sizes, one would need to generate @code{n} image files for each font size needed. This is where the ``signed distance field'' rendering technique comes in. Introduced by @url{http://www.valvesoftware.com/.../2007/SIGGRAPH2007_AlphaTestedMagnification.pdf, Valve} in 2007, signed distance field fonts can be efficiently stored in a bitmap and be rendered at arbitrary scale factors with good results. While Chickadee does not yet offer a tool for converting TTF fonts into FNT fonts, tools such as @url{https://github.com/libgdx/libgdx/wiki/Hiero, Hiero} may be used in the meantime. The following procedures can be found in the @code{(chickadee render font)} module. @deffn {Procedure} load-font @var{file} Load the Angel Code formatted XML document 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{font} @var{text} @var{position} [#: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}. @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 Blending and Depth Testing @subsection Blending and Depth Testing @node Vertex Arrays @subsection Vertex Arrays @node Shaders @subsection Shaders Shaders are programs for the GPU to evaluate. They are written in the OpenGL Shading Language, or GLSL. Chickadee does not currently provide a Scheme-like domain specific language for writing shaders. Since shaders must be written in GLSL and not Scheme, they are considered an advanced feature. @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 @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 {Procedure} channel-get @var{channel} Wait for a message from @var{channel}. @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