Tagged as lisp, game-dev, mac, windows, linux
Written on 2019-08-06 19:00:00
Let's say you've created an awesome desktop game using Common Lisp. Good job. Now, how will you share your creation?
Unfortunately lisp has yet to take over the world, so if you want widespread use of your game you must produce a binary for each platform you want to support.
This post will cover:
Huge thanks to /u/gluaxspeed for providing his azure-pipelines yaml. This post would not have been possible without him.
Our sample game is not actually a game, but a function which prints some info about its runtime and runs the cl-sdl2 example app.
(in-package :cl-sample-game)
(defun main ()
;; osx drawing calls must occur on the main thread or the app will crash
(sdl2:make-this-thread-main
(lambda ()
(format t
"Sample app running.~%Lisp info: ~A : ~A~%"
(lisp-implementation-type)
(lisp-implementation-version))
(format T "SDL info: ~D.~D.~D~%"
sdl2-ffi:+sdl-major-version+
sdl2-ffi:+sdl-minor-version+
sdl2-ffi:+sdl-patchlevel+)
(format t
"Platform: ~A~%"
(cond #+darwin(t "OSX")
#+win32(t "Windows")
#+linux(t "Linux")))
(sdl2-examples:basic-test))))
Additionally we'll also define an asdf system.
(in-package :asdf-user)
(defsystem cl-sample-game
:name "cl-sample-game"
:version "0.1"
:author "Ark"
:pathname "src/"
:serial T
:components ((:file "packages")
(:file "cl-sample-game"))
:depends-on (:sdl2/examples))
The Common Lisp spec does not include building binaries. Each CL implementation provides its own means to accomplish this. The specifics vary, but in practice the approaches are very similar. The implementation usually exposes a function which dumps a binary for the platform you're running on and exits the program. This post uses sbcl's save-lisp-and-die
function. For other implementations consult your manual, or try one of the cross-lisp libraries such as asdf:make, buildapp, or roswell.
With that in mind here's a script to build our game.
(ql:quickload :cl-sample-game)
(in-package :cl-sample-game)
(sb-ext:save-lisp-and-die
(or
#+win32"sample-game-windows.exe"
#+linux"sample-game-linux"
#+darwin"sample-game-osx"
(error "Unsupported OS for building. Got: ~A" *features*))
:purify T
:toplevel
#'main
:executable t
:save-runtime-options t)
To run the script:
~/prog/cl-sample-game $ sbcl --load build-game.lisp
To load "cl-sample-game":
Load 1 ASDF system:
cl-sample-game
; Loading "cl-sample-game"
..................................................
[package cl-sample-game]
~/prog/cl-sample-game $ ls | grep sample-game-
sample-game-linux
This produces a binary for the platform the script runs on (in my case, linux). Building for other platforms requires running the build script on the target platforms (or some kind of emulator).
Ultimately, supporting a platform requires direct testing on the OS with real hardware. Video game bugs are too subtle to be 100% dealt with by automation. That being said, setting up CI will greatly simplify your life.
Without CI, testing and building your game may look like this:
For every platform:
It may not seem like much, but repeating it on every platform is a chore, and bugs may creep in if your dependencies are not the same across every platform. For example, if a third-party quicklisp library is not updated when you make your OSX build you could be running with a very different codebase than the one you developed on. Not good!
Without CI, you'll tend to build frequently on your development OS and rarely for everything else. You might find yourself hunting a subtle bug that broke your Windows build sometime between now and 50 commits ago. Not fun!
Azure Pipelines offers a free runners with OSX, Windows, and Linux hosting. I assume there's some limit before they start charging you, but so far I've been able to produce builds for our sample game without any hassle (other than generally trying to figure out Azure's yaml settings).
I won't post the full code here, but here's the full yaml: https://github.com/realark/cl-sample-game/blob/master/azure-pipelines.yaml
Generally the process looks like this:
cl-sample-game
project as a local quicklisp projectbuild-game.lisp
scriptThere are a lot of specific details for each platform. Notably, I'm running the linux build using Azure's container
option. This is because SDL2 on linux requires a later version of glib. This may not be required for your needs (especially if your application is not a game or has no libraries).
Source code: https://github.com/realark/cl-sample-game
Azure Pipeline: https://dev.azure.com/arktheprogrammer/cl-sample-game
Cl-Sample-Game Build: https://dev.azure.com/arktheprogrammer/_apis/resources/Containers/371573?itemPath=drop&$format=zip