trivial-gamekit
is a very simple toolkit for making games in Common Lisp. Its goal is to be as
simple as possible while allowing a user to make a complete game s/he can distribute.
It manages system resources for you by creating and initializing a window, abstracting away
keyboard and mouse input and any other system-dependent configuration. trivial-gamekit
also
spawns a default game loop for you allowing to hook into it for drawing and other per frame
activities via certain methods described below.
It strives to fully support Common Lisp’s interactive development approach where you change your application on the fly and allowed to gracefully recover from programming errors via restarts without crashing or rerunning an application.
gamekit
’s central idea is a game object you define via defgame
macro. It
contains all the state gamekit
needs to run your game. It is instantiated by
#'start
which also bootstraps the whole application and opens a window, runs
your game logic, rendering, etc. Only single game object can be run at a time, so subsequently
calling #'start
for the same or different game objects is disallowed - you
need to stop game execution first. To stop a running game you would need to call
#'stop
.
You can put game initialization code into
#'post-initialize
. Specifically, it is probably a
best place to bind input via #'bind-cursor
or
#'bind-button
or any gamepad-related functions as
described below, but you are free to do any other preparations that require
fully initialized game instance. If you need to release any unmanaged resources
(e.g. foreign memory you allocated during a game or in
#'post-initialize
) to avoid leaks you can override
#'pre-destroy
function which is called after last game
loop iteration.
#'start
also invokes a default game loop. To hook into it
you can use #'act
and #'draw
methods. #'act
is used for updaing your game state and
#'draw
should be used exclusively for
drawing/rendering. :draw-rate
and :act-rate
options of defgame
can be used
to specify rate for #'draw
and #'act
in invocations per second separately,
meaning if you set :draw-rate
to 60 your framerate would be set to 60 frames
per second.
For moving objects around, defining their place on a canvas and giving them a color we need a
bit of a math applied. gamekit
points and positions are represented as two-dimensional vectors
created with #'vec2
. Colors consist of four elements: red, green, blue and
alpha, so they are represented by four-dimensional vecors created via #'vec4
.
Some vector operations are exported: #'mult
for element-wise multiplication,
#'add
for addition, #'subt
for subtraction and
#'div
for element-wise division.
Element accessor methods for setting or getting values out of vectors are also exported:
#'x
to access first element, #'y
for a second,
#'z
- third and #'w
to access fourth.
Resources are very important for games. Textures, models, sounds, fonts,
animations, tiles - you name it. gamekit
has several routines prepared for you
to ease the handling of those.
First of all, we need to tell gamekit
where to find resources via
#'define-image
,
#'define-sound
and
#'define-font
macros. First argument to them is a
symbol that would become an identificator for the resource and the second
argument is a absoulute or relative path to the resource.
For absolute path, be sure to use helper functions like
asdf:system-relative-pathname
to avoid hardcoding paths to your resources in
the code.
To use relative resource path, you first need to specify resource root
directory. Resource roots are associated with Common Lisp packages. Any package
can have associated filesystem directory to find resources in. Call
#'register-resource-package
as a
toplevel form to bind a directory to a package. Absolute paths required here.
Once done, you can specify relative path in define-*
forms and gamekit
would
merge resource root pathname associated with a package of a symbol (first
argument to define-*
) with relative pathname you provided.
Resources are used by functions that requires them to operate, like
#'draw-image
, #'make-font
,
#'play-sound
and others.
When defgame
option :prepare-resources
is set to nil
,
you would need to request resources manually by calling
#'prepare-resources
with resource ids you
need. Preparation can take a while, so
#'prepare-resources
asynchonously requests
resources and returns immediately for you to be notified later with
#'notice-resources
. This is useful, when you
don’t want to wait until all assets are loaded and start rendering some loading
or start screen right away and continue with game after all resources are ready.
If you have a lot of resources and would like to get rid of unneeded ones to free
some memory, you can use #'dispose-resources
.
Sometimes you would want to ship a game with custom resources not supported by
gamekit
directly. To accomplish that, you can use
define-text
to include text files and
define-binary
to include any file basically, which
would be represented as a simple array of bytes. To access those resources you
would need to use #'get-text
and
#'get-binary
correspondingly.
gamekit
provides simple to use but versatile drawing API. If you know how
HTML5 canvas API is organized you will find gamekit
’s one quite
similar. Several functions with names starting with draw-
exists to help you
draw primitives like lines, rectangles, ellipses, arcs, polygons or even bezier
curves. Images can be drawn via #'draw-image
and text
with customizable font can be put onto canvas via
#'draw-text
.
Oftentimes it is useful to know dimensions of the visual resources to position
them correctly. #'image-width
and
#'image-height
can help you with retreiving image
dimensions while #'calc-text-bounds
will allow
you to calculate text bounding box.
Canvas transformation operations are supported too. You can scale, translate, rotate a canvas
with #'scale-canvas
,
#'translate-canvas
and
#'rotate-canvas
accordingly. If you need to keep canvas state for
nested drawing operations you will appreciate existence of
#'with-pushed-canvas
macro, that keeps canvas state to the
dynamic extent of its body. This means that upon returning from the macro canvas transformation
state will be returned to its original state before the macro and all transformation operations
inside its body would be canceled out.
All drawing operations should be performed inside #'draw
.
Unlike many other drawing APIs gamekit
uses bottom left corner as an origin (0,0) with y-axis
pointing upwards which is more convenient mathematically.
All origins or positions are represented by two-dimensional (x,y) vectors created via
#'vec2
. Colors are passed around as four-dimensional vectors made with
#'vec4
consisting of red, green, blue and alpha channels each within 0.0-1.0
range.
Audio can substantially boost game’s atmosphere, and gamekit
is ready to serve you well in this
regard too. #'play-sound
will help with getting sounds to reach your
users ears. On the other hand, #'stop-sound
can be used to stop this
process.
There’s no game without some sort of interaction with a player. gamekit
allows you to grab
keyboard and mouse input to listen for player actions. You can pass a callback to
#'bind-button
which will be called upon pressing or releasing
keyboard/mouse buttons. Callback passed to #'bind-cursor
is going to
be invoked when user moves a mouse. Callbacks provided are not stacked together, meaning if you
try to bind multiple callbacks to the same button only last callback is actually going to be
invoked. Same goes for cursor input.
gamekit
also support gamepads and exposing them as a generic xbox
controllers. You can listen to
gamepads being connected and disconnected with
#'bind-any-gamepad
. Unfortunately, gamekit
doesn’t have a reliable way to numerate gamepads, so gamepad-related functions
operate on opaque gamepad references and you need to manage player<->gamepad
relationship yourself.
For listening to gamepad buttons you can use
#'bind-gamepad-button
and
#'bind-gamepad-any-button
.
#'bind-gamepad-dpad
can help to catch changes in
d-pad state. #'bind-gamepad-stick
and
#'bind-gamepad-trigger
can help tracking
position of right and left sticks and values of left and right triggers
accordingly.
Sometimes gamekit
wouldn’t be able to recognize your gamepad. In this case you
need to specify path to custom controller mapping file in SDL2
format
in BODGE_GAMECONTROLLERDB
environment variable and restart the game.
Sharing a Common Lisp game in a binary form amongst users was always a bit of a
pain. But fear no more! gamekit
includes a mechanism for delivering your game
packaged using only a single function - #'deliver
. It will
build an executable, pack all required resources, copy needed foreign libraries
used by trivial-gamekit
and compress all that into a shippable archive.
To gain access to this function you need additionally load :trivial-gamekit/distribution
system, and then you would be able to find it in gamekit.distribution
package.
(ql:quickload :trivial-gamekit/distribution)
For building these packages for Windows
, GNU/Linux
and macOS
with just a
single push to a github
or gitlab
respository check out Advanced
Features section.
Common Lisp is well known for its superior interactive development capabilities,
and when things go left you often can fix problem live without restarting an
image. gamekit
strives to embrace this approach as much as possible. But
beware, under the hood gamekit
uses quite an intricate machinery and it still
is possible to break it beyond live repairement.
Important thing to keep in mind: don’t invoke 'abort
restart that quit
threads. It’s too easy, because q
button in slime
or sly
is bound to quit
action in debugger which invokes most destroying restart possible. It often
works in other applications, but in complex multithreaded environments it can be
disastrous. It’s quite hard (if impossible) to restore gamekit
state after any
of its internal threads are killed. Just don’t press q
.
sly
and slime
has other more useful button bindings available: c
- for
'continue
restart and a
for topmost 'abort
restart. gamekit
binds those
exact restarts to gracefully handle failures. 'continue
will try to skip the
offending block of code and 'abort
will try to shutdown engine gracefully
altogether after which you should be able to start your game with
#'gamekit:start
. The latter one is not very interactive-friendly obviously,
and should be the last resort before restarting the whole image (lisp process).
Another restart that isn’t bound to a key but super useful is
'rerun-flow-block
. Ivoking it will prompt gamekit
to rerun a code the error
was in. Fixing the error, reevaluating the code and invoking this restart should
bring your game back to working state. This is the restart you might want to use
the most.
'skip-flow-block
restart does the same thing as 'continue
- skips the code
with the error.
Invoke (gamekit:stop)
in REPL and then try running your game again by invoking
#'gamekit:start
with required params. If that didn’t help - reload a lisp
image.
trivial-gamekit/distribution
or
trivial-gamekit/documentation
#'gamekit:start
inisde and call it from REPL manuallymacro defgame
(name (&rest classes) &body ((&rest slots) &rest opts))
Defines a game class that can be passed to #'start
to run
a game. name
is the name of a class generated. classes
are names of
superclasses, slots
- standard class slots and opts
are class options. So,
pretty much standard class definition except it does configure a class in
certain ways specifically for gamekit
use and allows passing additional
options in opts
apart from standard :documentation
, :default-initargs
and
so others.
Additional options that can be passed in opts
are:
:viewport-width
- width of the window and canvas:viewport-height
- height of the window and canvas:viewport-title
- title of the window:prepare-resources
- boolean value that indicates whether gamekit
should
load resources automatically on startup or if not, user prefers to load them dynamically on request. Defaults to t
.Example:
(gamekit:defgame example ()
;; some game related state
((world :initform (make-instance 'world))
(game-state))
;; options
(:viewport-width 800)
(:viewport-height 600)
(:viewport-title "EXAMPLE")
(:prepare-resources nil))
function start
(classname &key (log-level info) (opengl-version '(3 3)) samples blocking viewport-resizable (viewport-decorated
t) (autoscaled
t) swap-interval properties)
Bootsraps a game allocating a window and other system resources. Instantiates
game object defined with defgame
which can be obtained via
#'gamekit
. Cannot be called twice -
#'stop
should be called first before running start
again.
Example:
(gamekit:start 'example)
function stop
(&key blocking)
Stops currently running game releasing acquired resources.
Example:
(gamekit:stop)
function gamekit
()
Returns instance of a running game or nil
if no game is started yet.
Example:
(gamekit:gamekit)
generic post-initialize
(system)
This function is called after game instance is fully initialized, right
before main game loop starts its execution. Put initialization code for your
application into method of this function. For example, it would be logical to
bind input via #'bind-cursor
or
#'bind-button
here.
Example:
(defmethod gamekit:post-initialize ((this example))
(init-game)
(bind-input))
generic pre-destroy
(system)
This function is called just before shutting down a game instance for you to free all acquired resources and do any other clean up procedures.
Example:
(defmethod gamekit:pre-destroy ((this example))
(release-foreign-memory)
(stop-threads))
generic act
(system)
Called every game loop iteration for user to add any per-frame behavior to
the game. NOTE: all drawing operations should be performed in
#'draw
method.
Example:
(defmethod gamekit:act ((this example))
(report-fps))
generic draw
(system)
Called every game loop iteration for frame rendering. All drawing operations should be performed in this method.
Example:
(defmethod gamekit:draw ((this example))
(gamekit:draw-text "Hello, Gamedev!" (gamekit:vec2 10 10)))
function viewport-width
()
Returns width of a gamekit viewport (window) if there’s an active gamekit
instance (started via #'start
) or nil otherwise.
Example:
(gamekit:viewport-width)
function viewport-height
()
Returns height of a gamekit viewport (window) if there’s an active gamekit
instance (started via #'start
) or nil otherwise.
Example:
(gamekit:viewport-height)
function vec2
(&optional (x 0.0) (y 0.0))
Makes a two-dimensional vector.
Example:
(gamekit:vec2 0 0)
function vec3
(&optional (x 0.0) (y 0.0) (z 0.0))
Makes a three-dimensional vector.
Example:
(gamekit:vec3 1 1 2)
function vec4
(&optional (x 0.0) (y 0.0) (z 0.0) (w 0.0))
Makes a four-dimensional vector.
Example:
(gamekit:vec4 1 1 2 3)
function mult
(arg0 &rest args)
Element-wise multiplication. Accepts both vectors and scalars.
Example:
(gamekit:mult 2 (gamekit:vec2 1 1) 0.5)
function add
(arg0 &rest args)
Element-wise addition. Accepts both vectors and scalars.
Example:
(gamekit:add 1 (gamekit:vec2 1 1) -1)
function subt
(arg0 &rest args)
Element-wise subtraction. Accepts both vectors and scalars.
Example:
(gamekit:subt 1 (gamekit:vec2 1 1) (gamekit:vec2 -1 -1))
function div
(arg0 &rest args)
Element-wise division. Accepts both vectors and scalars.
Example:
(gamekit:div (gamekit:vec2 1 1) 2 (gamekit:vec2 0.5 0.5))
function x
(vec)
Reads first element of a vector.
Example:
(gamekit:x (gamekit:vec2 1 1))
function (setf x)
(value vec)
Stores first element of a vector.
Example:
(setf (gamekit:x (gamekit:vec2 1 1)) 0)
function y
(vec)
Reads second element of a vector.
Example:
(gamekit:y (gamekit:vec2 1 1))
function (setf y)
(value vec)
Stores second element of a vector.
Example:
(setf (gamekit:y (gamekit:vec2 1 1)) 0)
function z
(vec)
Reads third element of a vector.
Example:
(gamekit:z (gamekit:vec4 1 1 2 3))
function (setf z)
(value vec)
Stores third element of a vector.
Example:
(setf (gamekit:z (gamekit:vec4 1 1 2 3)) 0)
function w
(vec)
Reads fourth element of a vector.
Example:
(gamekit:w (gamekit:vec4 1 1 2 3))
function (setf w)
(value vec)
Stores fourth element of a vector.
Example:
(setf (gamekit:w (gamekit:vec4 1 1 2 3)) 0)
function register-resource-package
(package-name path)
Associates resource package with filesystem path. For proper resource handling it is recommended to put it as a top-level form, so resources could be located at load-time.
First argument, a package name, must be a valid Common Lisp package name that could be used to locate package via #’find-package. Second argument is a filesystem path to a directory where resources can be found.
Example:
(gamekit:register-resource-package :example-package
"/home/gamdev/example-game/assets/")
macro define-image
(name path &key use-nearest-interpolation)
Registers image resource by name that can be used by
#'draw-image
later. Second argument is a valid path to
the resource. Only .png images are supported at this moment.
Name must be a symbol. Package of that symbol and its associated path (via
#'register-resource-package
) will be
used to locate the resource, if relative path is given as an argument to this
macro.
Example:
(gamekit:define-image example-package::logo "images/logo.png")
macro define-sound
(name path)
Registers sound resource by name that can be used by #'play-sound
later.
Second argument is a valid path to the resource. Formats supported: .wav,
.ogg (Vorbis), .flac, .aiff.
Name must be a symbol. Package of that symbol and its associated path (via
#'register-resource-package
) will be
used to locate the resource, if relative path is given as an argument to this
macro.
Example:
(gamekit:define-sound example-package::blop "sounds/blop.ogg")
macro define-font
(name path)
Registers font resource by name that can be passed to #'make-font
later.
Second argument is a valid path to the resource. Only .ttf format is supported
at this moment.
Name must be a symbol. Package of that symbol and its associated path (via
#'register-resource-package
) will be
used to locate the resource, if relative path is given as an argument to this
macro.
Example:
(gamekit:define-font example-package::noto-sans "fonts/NotoSans-Regular.ttf")
macro define-text
(name path &key encoding)
Registers text resource by name that can be retrieved with #'get-text
later.
Second argument is a valid path to the resource. You can specify encoding via
:encoding
keywrod argument. :utf-8
is used by default.
Name must be a symbol. Package of that symbol and its associated path (via
#'register-resource-package
) will be
used to locate the resource, if relative path is given as an argument to this
macro.
Example:
(gamekit:define-text example-package::example-text "dialog.txt" :encoding :utf-8)
macro define-binary
(name path)
Registers binary resource by name that can be retrieved with #'get-binary
later.
Second argument is a valid path to the resource.
Name must be a symbol. Package of that symbol and its associated path (via
#'register-resource-package
) will be
used to locate the resource, if relative path is given as an argument to this
macro.
Example:
(gamekit:define-binary example-package::example-blob "blob.data")
function make-font
(font-id size)
Makes a font instance that can be later passed to #'draw-text
to
customize text looks. font-id
must be a valid resource name previously registered with
define-font
. Second argument is a font size in pixels.
Example:
(gamekit:make-font 'example-package::noto-sans 32)
function prepare-resources
(&rest resource-names)
Loads and prepares resources for later usage asynchronously. resource-names
should be symbols used previously registered with define-*
macros.
This function returns immediately. When resources are ready for use
#'notice-resources
will be called with names that
were passed to this function.
gamekit
by default will try to load and prepare all registered resources on
startup which might take a substantial time, but then you don’t need to call
#’prepare-resources yourself. If you prefer load resources on demand and have a
faster startup time, pass nil to :prepare-resources option of a
defgame
macro which will disable startup resource
autoloading.
Example:
(gamekit:prepare-resources 'example-package::noto-sans
'example-package::blop
'example-package::logo)
function dispose-resources
(&rest resource-names)
Disposes prepared resources asynchronously. resource-names
should be symbols used previously registered with define-*
macros.
This function returns immediately. Attempts to use disposed resources will raise
an error. To use resources again you would need to load them with
#'prepare-resources
.
Example:
(gamekit:dispose-resources 'example-package::noto-sans
'example-package::blop
'example-package::logo)
generic notice-resources
(game &rest resource-names)
Called when resource names earlier requested with
#'prepare-resources
which indicates those
resources are ready to be used.
Override this generic function to know when resources are ready.
Example:
(defmethod gamekit:notice-resources ((this example) &rest resource-names)
(declare (ignore resource-names))
(gamekit:play-sound 'example-package::blop)
(show-start-screen))
function get-text
(resource-id)
Get text resource (a string) by id. resource-id
must be a valid resource id
previously registered with 'define-text
.
(gamekit:get-text 'example-package::example-text)
function get-binary
(resource-id)
Get binary resource (a byte vector) by id. resource-id
must be a valid
resource id previously registered with 'define-binary
.
(gamekit:get-binary 'example-package::example-blob)
function draw-line
(origin end paint &key (thickness 1.0))
Draws a line starting from coordinates passed as first argument to
coordinates in second parameter. Third parameter is a color to draw a line
with. :thickness
is a scalar floating point value controlling pixel-width of a
line.
Example:
(gamekit:draw-line (gamekit:vec2 8 5) (gamekit:vec2 32 11)
(gamekit:vec4 1 0.5 0 1)
:thickness 1.5)
function draw-curve
(origin end ctrl0 ctrl1 paint &key (thickness 1.0))
Draws a bezier curve from coordinates passed as first argument to coordinates
in second parameter with two control points in third and fourth parameters
accordingly. Fifth argument is a curve’s color. :thickness
is a scalar
floating point value controlling pixel-width of a curve.
Example:
(gamekit:draw-line (gamekit:vec2 8 5) (gamekit:vec2 32 11)
(gamekit:vec2 0 5) (gamekit:vec2 32 0)
(gamekit:vec4 1 0.5 0 1)
:thickness 1.5)
function draw-rect
(origin w h &key (fill-paint nil) (stroke-paint nil) (thickness 1.0) (rounding
0.0))
Draws a rectangle with origin passed in first argument, width and height -
second and third arguments accordingly. :fill-paint
key is a color to fill
insides of a rectangle with. If you pass color to :stroke-paint
, edges of the
rectangle are going to be struck with it. :thickness
controls pixel width of
struck edges. Use :rounding
in pixels to round rectangle corners.
Example:
(gamekit:draw-rect (gamekit:vec2 0 0) 314 271
:fill-paint (gamekit:vec4 1 0.75 0.5 0.5)
:stroke-paint (gamekit:vec4 0 0 0 1)
:rounding 5.0)
function draw-circle
(center radius &key (fill-paint nil) (stroke-paint nil) (thickness 1.0))
Draws a circle with center in first argument and radius in second argument.
Provide color with :fill-paint
paramater to fill the inner area of the circle
with. If :stroke-paint
color is provided, circle’s border is going to be
struck with it. :thickness
controls pixel width of struck border.
Example:
(gamekit:draw-circle (gamekit:vec2 100 500) 3/4
:fill-paint (gamekit:vec4 1 1 1 1)
:stroke-paint (gamekit:vec4 0 0 0 1)
:thickness 3)
function draw-ellipse
(center x-radius y-radius &key (fill-paint nil) (stroke-paint nil) (thickness
1.0))
Draws an ellipse with center provided in first argument, x and y radii as
second and thrid arguments accordingly. Pass a color as :fill-paint
paramater
to fill the inner area of the ellipse with. If :stroke-paint
color is
provided, ellipse’s border will be struck with it. :thickness
controls pixel
width of struck border.
Example:
(gamekit:draw-ellipse (gamekit:vec2 128 128) 16 32
:fill-paint (gamekit:vec4 0 0 0 1)
:stroke-paint (gamekit:vec4 1 1 1 1)
:thickness 1.1)
function draw-arc
(center radius a0 a1 &key (fill-paint nil) (stroke-paint nil) (thickness 1.0))
Draws an arc from a0
to a1
angles (in radians) with center passed in
first argument and radius in second. If provided, color in :fill-paint
will be
used to fill the area under an arc confined between a circle’s curve and a line
connecting angle points. :fill-paint
and :stroke-paint
colors are, if
provided, used to fill insides and stroke arc’s border correspondingly.
Example:
(gamekit:draw-arc (gamekit:vec2 256 256) 128
(/ pi 4) (* (/ pi 2) 1.5)
:fill-paint (gamekit:vec4 0.25 0.5 0.75 1)
:stroke-paint (gamekit:vec4 0.75 0.5 0.25 1)
:thickness 2.0)
function draw-polygon
(vertices &key (fill-paint nil) (stroke-paint nil) (thickness 1.0))
Draws a polygon connecting list of vertices provided in first
argument. :fill-paint
is a color to fill insides of a polygon and
:stroke-paint
color is used to stroke polygon edges. :thickness
controls
pixel-width of a stroke.
Example:
(gamekit:draw-polygon (list (gamekit:vec2 10 10) (gamekit:vec2 20 20)
(gamekit:vec2 30 20) (gamekit:vec2 20 10))
:fill-paint (gamekit:vec4 0.25 0.5 0.75 1)
:stroke-paint (gamekit:vec4 0.75 0.5 0.25 1)
:thickness 3.0)
function draw-polyline
(points paint &key (thickness 1.0))
Draws a polyline connecting list of vertices provided in first
argument. Second argument is a color to stroke a line with. :thickness
controls pixel width of a line.
Example:
(gamekit:draw-polyline (list (gamekit:vec2 10 10) (gamekit:vec2 20 20)
(gamekit:vec2 30 20) (gamekit:vec2 20 10))
(gamekit:vec4 0.75 0.5 0.25 1)
:thickness 3.0)
function draw-image
(position image-id &key origin width height)
Draws an image at coordinates specified in first argument. Second argument is
image-id
used in #'define-image
earlier. Optional
:origin
key is a point within image to start drawing from, if you want to
render only a part of image. :width
and :height
keys tell width and height
of a subimage to draw. They are optional and could be skipped to draw a subimage
with full height and width available.
Example:
(gamekit:draw-image (gamekit:vec2 314 271) 'example-package::logo
:origin (gamekit:vec2 0 0)
:width 320
:height 240)
function draw-text
(string origin &key (fill-color *black*) (font *font*))
Draws text on the canvas starting at coordinates passed as second argument.
Use :fill-color
key parameter to change text’s color. To change a font, pass
object created with #'make-font
via :font
parameter.
Example:
(gamekit:draw-text "Hello, Gamekit!" (gamekit:vec2 11 23)
:fill-color (gamekit:vec4 0 0 0 1)
:font (gamekit:make-font 'example-package::noto-sans 32))
function translate-canvas
(x y)
Moves drawing origin to the specified position making the latter a new
origin. All following draw operations will be affected by this change unless
wrapped with with-pushed-canvas
macro.
Example:
(gamekit:translate-canvas 100 500)
function rotate-canvas
(angle)
Rotates current canvas for specified number of radians. All following drawing
operations will be affected by this change unless wrapped with
with-pushed-canvas
macro.
Example:
(gamekit:rotate-canvas (/ pi 2))
function scale-canvas
(x y)
Scales current canvas by x and y axes accordingly. All following drawing
operations will be affected by this change unless wrapped with
with-pushed-canvas
macro.
Example:
(gamekit:scale-canvas 0.5 1.5)
macro with-pushed-canvas
(nil &body body)
Saves current canvas transformations (translations, rotations, scales) before entering its body and restores previous transformations upon exit from the body. All transformation operations within this macro don’t affect outer canvas transformations outside of a body of this macro.
Example:
(gamekit:translate-canvas 400 300)
(gamekit:with-pushed-canvas ()
(gamekit:rotate-canvas (/ pi 4)))
function image-width
(image-id)
Returns width of an image by its id (defined with
#'define-image
).
Can only be called when gamekit instance is active (started via
#'start
).
Example:
(gamekit:image-width 'example-package::logo)
function image-height
(image-id)
Returns height of an image by its id (defined with
#'define-image
).
Can only be called when gamekit instance is active (started via
#'start
).
Example:
(gamekit:image-height 'example-package::logo)
function calc-text-bounds
(text &optional (font *font*))
Calculates text bounds with the font provided or the default one otherwise and returns several values: origin as vec2, width, height and calculated advance
Example:
(gamekit:calc-text-bounds "hello there")
function play-sound
(sound-id &key looped-p)
Plays a sound defined earlier with define-sound
. Pass t
to
:looped-p
key to play sound in a loop.
Example:
(gamekit:play-sound 'example-package::blop
:looped-p t)
function stop-sound
(sound-id)
Stops a playing sound by provided sound id.
Example:
(gamekit:stop-sound 'example-package::blop)
Binds action
to specified key
state
. When key state changes to the one specified,
action callback is invoked with no arguments. #'bind-button
function should be
called when there’s active game exists started earlier with
#'start
. state
can be either :pressed
, :released
or
:repeating
.
Actions are not stacked together and would be overwritten for the same key and state.
Can only be called when gamekit instance is active (started).
Supported keys:
:space :apostrophe :comma :minus :period :slash
:0 :1 :2 :3 :4 :5 :6 :7 :8 :9
:semicolon :equal
:a :b :c :d :e :f :g :h :i :j :k :l :m
:n :o :p :q :r :s :t :u :v :w :x :y :z
:left-bracket :backslash :right-bracket
:grave-accent :world-1 :world-2
:escape :enter :tab :backspace :insert :delete
:right :left :down :up
:page-up :page-down :home :end
:caps-lock :scroll-lock :num-lock :print-screen :pause
:f1 :f2 :f3 :f4 :f5 :f6 :f7 :f8 :f9 :f10 :f11 :f12
:f13 :f14 :f15 :f16 :f17 :f18 :f19 :f20 :f21 :f22 :f23 :f24 :f25
:keypad-0 :keypad-1 :keypad-2 :keypad-3 :keypad-4
:keypad-5 :keypad-6 :keypad-7 :keypad-8 :keypad-9
:keypad-decimal :keypad-divide :keypad-multiply
:keypad-subtract :keypad-add :keypad-enter :keypad-equal
:left-shift :left-control :left-alt :left-super
:right-shift :right-control :right-alt :right-super
:menu
:mouse-left :mouse-right :mouse-middle
Example
(gamekit:bind-button :enter :pressed
(lambda ()
(start-game-for *player*)))
function bind-cursor
(action)
Binds action callback to a cursor movement event. Everytime user moves a cursor callback will be called with x and y of cursor coordinates within the same coordinate system canvas is defined in: bottom left corner as (0,0) origin and y-axis pointing upwards.
Actions doesn’t stack together and would be overwritten each time
#'bind-cursor
is called.
Can only be called when gamekit instance is active (started).
Example:
(gamekit:bind-cursor (lambda (x y)
(shoot-to x y)))
function bind-any-gamepad
(action)
Binds action
to a gamepad connection and disconnection events. Once one of
those events happen, action
is called with two arguments: gamepad
- opaque
reference to a gamepad that will be supplied as an argument to other
gamepad-related actions, and state
- which can be either :connected
or
:disconnected
to catch connection and disconnection of a gamepad accordingly.
If there were gamepads already connected before call to #'bind-any-gamepad
,
action
is called for each one of those upon invocation.
Example:
(gamekit:bind-any-gamepad (lambda (gamepad state)
(if (eq :connected state)
(add-player-for-gamepad gamepad)
(pause-game-and-wait-for-player gamepad))))
Binds action
to specified gamepad’s button
state
. When button state
changes to the one specified, action callback is invoked with gamepad opaque
reference as an argument. state
can be either :pressed
or :released
.
Actions are not stacked together and would be overwritten for the same button and state.
Can only be called when gamekit instance is active (started via
#'start
).
Gamekit’s gamepad is a generic xbox controller with the same layout of controls.
Supported buttons:
:a :b :x :y
:left-bumper :right-bumper
:start :back :guide
:left-thumb :right-thumb
Example
(gamekit:bind-gamepad-button :start :pressed
(lambda (gamepad)
(declare (ignore gamepad))
(start-game)))
Binds action
to all buttons of gamepads. When any button state of any
gamepad changes, action callback is invoked with gamepad opaque reference as a
first argument, gamepad’s button as a second and button’s state as a third argument.
See #'bind-gamepad-button
for available button
values and states.
Actions are not stacked together and would be overwritten on each function invocation.
Can only be called when gamekit instance is active (started via
#'start
).
Example
(gamekit:bind-gamepad-any-button (lambda (gamepad button state)
(when (and (eq button :start) (eq state :pressed))
(join-party (make-player-for-gamepad gamepad)))))
function bind-gamepad-dpad
(state action)
Binds action
to gamepad’s dpad. When dpad state changes, action callback is
invoked with gamepad opaque reference as a first argument and new dpad state as a
second.
Dpad states:
:up :down :left :right
:left-up :left-down
:right-up :right-down
:centered
Actions are not stacked together and would be overwritten for the same dpad state.
Can only be called when gamekit instance is active (started via
#'start
).
Example
(gamekit:bind-gamepad-state :up (lambda (gamepad)
(declare (ignore gamepad))
(jump *player*)))
function bind-gamepad-stick
(stick action)
Binds action
to gamepad’s left or right stick. When position of the
specified stick changes, action callback is invoked with gamepad
opaque
reference as a first argument, position’s x
and y
as second and third
arguments. x
and y
values are in [-1;1] range: stick up (0;1), stick
down (0;-1), stick left (-1;0) and stick right (1;0).
Sticks: :left
and :right
.
Actions are not stacked together and would be overwritten for the same stick.
Can only be called when gamekit instance is active (started via
#'start
).
Example
(gamekit:bind-gamepad-stick :left (lambda (gamepad x y)
(declare (ignore gamepad))
(move-player *player* x y)))
function bind-gamepad-trigger
(trigger action)
Binds action
to gamepad’s left or right triggers. When value of the
specified trigger changes, action callback is invoked with gamepad
opaque
reference as a first argument and new trigger value as second. Trigger values
are in [0;1] range.
Triggers: :left
and :right
.
Actions are not stacked together and would be overwritten for the same trigger.
Can only be called when gamekit instance is active (started via
#'start
).
Example
(gamekit:bind-gamepad-trigger :right (lambda (gamepad value)
(declare (ignore gamepad))
(setf (shot-power *player*) value)))
function deliver
(system-name game-class &key build-directory (zip *zip*) (lisp *lisp*))
Builds an executable, serializes resources and packs required foreign
libraries into a .zip archive for distribution. system-name
is a name of
asdf
system of your application and game-class
is a game class defined with
defgame
(the one that could be passed to
#'start
to start your game). By default, it builds all
artifacts into build/
directory relative to system-name
system path, but you
can pass any other path to :build-directory
key argument to put target files
into it instead.
This routine uses zip
and lisp
(‘sbcl’ Steel Bank Common
Lisp is the default) to build a distributable package on
various platforms. If those executables are not on your system’s PATH
, you
would need to provide absolute paths to them via :zip
and :lisp
key
arguments accordingly.
You can load this function into an image via :trivial-gamekit/distribution
system.
Example:
(ql:quickload :trivial-gamekit/distribution)
(gamekit.distribution:deliver :example-asdf-system 'example-package::example
:build-directory "/tmp/example-game/"
:zip "/usr/bin/zip"
:lisp "/usr/bin/sbcl")