CPAL updated

June 19th, 2008

Latest version here.

Includes basic bindings for Common Lisp (not much different from original PAL but might be faster with some implementations, e.g CLisp), PLT Scheme and Haskell (tested on GHC). Feel free to comment or send mail in case you have any questions or critique.

CPAL

February 19th, 2008

Altough PAL is working great for me with Common Lisp I occasionally like to play with other languages (Haskell and PLT Scheme currently) and it would be nice to have some easy to use graphics library when testing out a new language. So I decided to make a slimmed down C version of PAL that would be easy to interface from higher level languages.
Like PAL, CPAL is a fairly thin layer on top of SDL and OpenGL with a procedural interface for graphics, text (like PAL it currently uses Haaf’s Game Engine’s fontbuilder) and basic sfx. It is probably a bit unconvenient to use from C or C++ but if you want a fast and easy to use 2d graphics library for your favorite HLL it shouldn’t take more than an hour to write the bindings.

Here are the sources with prebuilt MSVC binaries (if you use a dynamic FFI like Pythons Ctypes or Common Lisp CFFI all you need is the .dll). It is only one source file and should work with at least Windows XP and Linux (tested on Ubuntu 7.10). Primitive form of documentation is also included.

Note: original PAL is still actively developed and recommended for your Common Lisp graphics needs.

Bermuda demo

February 4th, 2008

Year ago I played around implementing a little sidescrolling shootemup but the project got stalled, mostly because of lack of graphics. Few days ago I was digging some of my older projects and updating them to work with the latest PAL and I thought about building a distributable binary of the little shooter prototype, as a demo of PAL.
Bermuda

So, here is the resulting Windows binary. Note that it is built with SBCL which doesn’t yet have a stable Window version, so don’t be surprised if it doesn’t work on your box.

Now I’ll just have to clean up the sources a bit and add them to PAL examples…

PAL Space

November 13th, 2007

I noticed a nice little example about writing a game in PyGlet at http://www.learningpython.com/2007/11/10/creating-a-game-with-pyglet-and-python/ and it seemed like something worth stealing so I decided to do a PAL version of it.

The final results of this little experiment can be found here

To begin with

Altough not strictly necessary for a small project like this, just to demonstrate, I will first define some standard infrastructure for our project. First I’ll make a directory pal-space/ to hold all our code and then I’ll add a data/ directory in there where I’m going to put all the resources like sound, graphics etc. Next we will need a package for our little game

;; In pal-space/package.lisp 

(defpackage :pal-space
  (:use :cl :pal)
  (:export :pal-space))

this will create a PAL-SPACE package for us, importing only one symbol (PAL-SPACE) which is going to be our main function for the game. To help us load and compile the code we are going to need an ASDF definition file:

;; In pal-space/pal-space.asd 

(in-package #:asdf) 

(defsystem pal-space
    :components
  ((:file "package")
   (:file "sprites" :depends-on ("package"))
   (:file "pal-space" :depends-on ("sprites" "package")))
  :depends-on ("pal"))

This declares the dependancies in our code and allows us to load them all in the right order with (asdf:oos ‘asdf:load-op :pal-game) or SLIMEs slime-load-system command.

All right. Now we can start coding the actual game.

;; In pal-space/pal-space.lisp
(in-package :pal-space)

(defun pal-space ()
  (with-pal (:paths '("data/"
                      "C:/Documents and Settings/tomppa/Omat tiedostot/Downloads/pal-space/data/"))
    (event-loop ()
       (clear-screen 0 0 0))))

Here we just open a PAL window with the default settings, start the event loop which just draws a blank window on the screen. Only interesting bit is the :paths part; here we define where PAL should look for the resources that we load (with load-image etc.). We have two paths defined, the latter absolute path being just an convinience so I don’t need to change the working directory of my lisp system. You might want fix it to suit your system or completely remove it.

;; In pal-space/sprites.lisp we have nothing yet, it is just a placeholder
;; to get everything compile smoothly with asdf.

Now let’s start adding the actual functionality:

;; In space-ship/sprites.lisp 

(in-package :pal-space) 

(defparameter *sprites* nil) 

(defclass sprite ()
  ((pos :accessor pos-of :initarg :pos)
   (vel :accessor vel-of :initarg :vel :initform (v 0 0))
   (image :accessor image-of :initarg :image)))     

(defmethod initialize-instance :after ((s sprite) &key &allow-other-keys)
  (push s *sprites*))     

(defgeneric width-of (sprite))     

(defmethod width-of ((s sprite))
  (image-width (image-of s)))     

(defgeneric height-of (sprite))
(defmethod height-of ((s sprite))
  (image-height (image-of s)))     

(defgeneric draw (sprite))
(defmethod draw ((s sprite))
  (draw-image (image-of s) (pos-of s)))     

(defgeneric update (sprite))
(defmethod update (s)
  (setf (pos-of s)
	(v+ (pos-of s) (vel-of s))))     

(defgeneric kill (sprite))
(defmethod kill ((s sprite))
  (setf *sprites* (remove s *sprites*)))     

(defun rectangles-overlap-p (a a-width a-height b b-width b-height)
  (let ((ax (vx a))
	(ay (vy a))
	(bx (vx b))
	(by (vy b)))
    (not (or (> ax (+ bx b-width))
	      (< (+ ax a-width) bx)
	       (> ay (+ by b-height))
		(< (+ ay a-height) by)))))     

(defgeneric intersectsp (sprite-a sprite-b))
(defmethod intersectsp ((a sprite) (b sprite))
  (rectangles-overlap-p (pos-of a) (width-of a) (height-of a)
			(pos-of b) (width-of b) (height-of b)))     

(defgeneric collide (sprite-a sprite-b))
(defmethod collide :around ((a sprite) (b sprite))
  (when (intersectsp a b)
    (call-next-method)))     

(defmethod collide ((a sprite) (b sprite))
  nil)     

(defclass space-ship (sprite)
  ()
  (:default-initargs :image (tag 'space-ship)
                     :pos (v (/ (get-screen-width) 2)
			     (get-screen-height))))     

(defmethod draw ((s space-ship))
  (call-next-method)
  (draw-fps))     

(defmethod update ((s space-ship))
  (setf (pos-of s) (get-mouse-pos)))

Here we just define SPRITE and SPACE-SHIP classes that have a few methods for handling the collisions, updates and drawing the FPS counter with PAL’s DRAW-FPS function. Created instances all automatically get added to the list of *SPRITES*s in the :AFTER method for INITIALIZE-INSTANCE. We also subclass our SPACE-SHIP sprite with suitable default initarguments and add the mouse following behaviour to it in UPDATE method. The TAG function just evaluates (when needed) the resource loading form named SPACE-SHIP, the tags are defined in our main file:

;; In pal-space/pal-space.lisp

(in-package :pal-space)

(define-tags bullet (load-image "bullet.png")
             space-ship (load-image "ship.png")
	     monster (load-image "monster.png"))     

(defun pal-space ()
  (with-pal (:width 640
	     :height 480
	     :paths '("data/"
                      "C:/Documents and Settings/tomppa/Omat tiedostot/Downloads/pal-space/data/"))
    (setf *sprites* nil)
    (set-cursor nil)
    (make-instance 'space-ship)
    (event-loop ()
       (clear-screen 0 0 0)
       (dolist (s *sprites*)
	 (draw s)
	 (update s)))))

Important part here is the DEFINE-TAGS form which defines symbol->resource mappings to ease the handling of images. Now we should have the player sprite on screen following mouse and our main function is mostly finished. Since we are following the style of the original Python version the rest is going to be done by just refining the behaviour of the SPRITE class and its subclasses.

Now to wrap things up we will just add the BULLET and MONSTER classes:

;; In pal-space/sprites.lisp

(defclass monster (sprite)
  ()
  (:default-initargs :image (tag 'monster)
                     :pos (v (random (get-screen-width)) 0)
                     :vel (v (- (random 5) 2) 2)))     

(defmethod initialize-instance :after ((s monster) &key &allow-other-keys)
   (incf *monster-count*))     

(defmethod kill ((s monster))
  (call-next-method)
  (decf *monster-count*))     

(defmethod update ((s monster))
  (call-next-method)
  (with-accessors ((pos pos-of) (vel vel-of)) s
    (when (> (vy pos) (get-screen-height))
	 (kill s))
    (when (or (> (vx pos) (get-screen-width))
	 (< (vx pos) 0))
    (setf vel (v (- (vx vel))
	         (vy vel))))))     

(defclass bullet (sprite)
  ()
  (:default-initargs :image (tag 'bullet) :vel (v 0 -2)))     

(defmethod update ((s bullet))
  (call-next-method)
  (when (< (vy (pos-of s)) 0)
	(kill s))
  (dolist (sprite *sprites*)
    (collide s sprite)))

(defmethod collide ((b bullet) (m monster))
  (incf *score* 10)
  (kill m)
  (kill b))

And finally

After a few other adjustments our final sprites.lisp looks like this:

(in-package :pal-space)

(defparameter *sprites* nil)
(defparameter *max-monsters* 30)
(defparameter *monster-count* 0)
(defparameter *score* 0)

(defclass sprite ()
  ((pos :accessor pos-of :initarg :pos)
   (vel :accessor vel-of :initarg :vel :initform (v 0 0))
   (image :accessor image-of :initarg :image)))

(defmethod initialize-instance :after ((s sprite) &key &allow-other-keys)
  (push s *sprites*))

(defgeneric width-of (sprite))
(defmethod width-of ((s sprite))
  (image-width (image-of s)))

(defgeneric height-of (sprite))
(defmethod height-of ((s sprite))
  (image-height (image-of s)))

(defgeneric draw (sprite))
(defmethod draw ((s sprite))
  (draw-image (image-of s) (pos-of s)))

(defgeneric update (sprite))
(defmethod update (s)
  (setf (pos-of s)
        (v+ (pos-of s) (vel-of s))))

(defgeneric kill (sprite))
(defmethod kill ((s sprite))
  (setf *sprites* (remove s *sprites*)))

(defun rectangles-overlap-p (a a-width a-height b b-width b-height)
  (let ((ax (vx a))
        (ay (vy a))
        (bx (vx b))
        (by (vy b)))
    (not (or (> ax (+ bx b-width))
             (< (+ ax a-width) bx)
             (> ay (+ by b-height))
             (< (+ ay a-height) by)))))

(defgeneric intersectsp (sprite-a sprite-b))
(defmethod intersectsp ((a sprite) (b sprite))
  (rectangles-overlap-p (pos-of a) (width-of a) (height-of a)
                        (pos-of b) (width-of b) (height-of b)))

(defgeneric collide (sprite-a sprite-b))

(defmethod collide :around ((a sprite) (b sprite))
  (when (intersectsp a b)
    (call-next-method)))

(defmethod collide ((a sprite) (b sprite))
  nil)

(defclass space-ship (sprite)
  ()
  (:default-initargs :image (tag 'space-ship)
                     :pos (v (/ (get-screen-width) 2)
                             (get-screen-height))))

(defmethod draw ((s space-ship))
  (call-next-method)
  (draw-fps)
  (draw-text (prin1-to-string *score*) (v 300 0)))

(defmethod update ((s space-ship))
  (setf (pos-of s) (get-mouse-pos))
  (when (key-pressed-p :key-mouse-1)
    (make-instance 'bullet :pos (pos-of s))
    (make-instance 'bullet :pos (v+ (pos-of s) (v 10 0)))))

(defclass monster (sprite)
  ()
  (:default-initargs :image (tag 'monster)
                     :pos (v (random (get-screen-width)) 0)
                     :vel (v (- (random 5) 2) 2)))

(defmethod initialize-instance :after ((s monster) &key &allow-other-keys)
  (incf *monster-count*))

(defmethod kill ((s monster))
  (call-next-method)
  (decf *monster-count*))

(defmethod update ((s monster))
  (call-next-method)
  (with-accessors ((pos pos-of) (vel vel-of)) s
    (when (> (vy pos) (get-screen-height))
      (kill s))
    (when (or (> (vx pos) (get-screen-width))
              (< (vx pos) 0))
      (setf vel (v (- (vx vel))
                   (vy vel))))))

(defclass bullet (sprite)
  ()
  (:default-initargs :image (tag 'bullet) :vel (v 0 -2)))

(defmethod update ((s bullet))
  (call-next-method)
  (when (< (vy (pos-of s)) 0)
    (kill s))
  (dolist (sprite *sprites*)
    (collide s sprite)))

(defmethod collide ((b bullet) (m monster))
  (incf *score* 10)
  (kill m)
  (kill b))

And as nice finishing touch we will add a scrolling background to our pal-space.lisp:

;; Final pal-space/pal-space.lisp

(in-package :pal-space)

(defparameter *scroll* 0)

(define-tags bullet (load-image "bullet.png")
             space-ship (load-image "ship.png")
             monster (load-image "monster.png")
             background (load-image "bg.png"))

(defun pal-space ()
  (with-pal (:title "PAL Space"
	     :fullscreenp nil
             :width 640
             :height 480
             :paths '("data/"
                      "C:/Documents and Settings/tomppa/Omat tiedostot/Downloads/pal-space/data/"))
    (setf *sprites* nil
          *scroll* 0
          *monster-count* 0
          *score* 0)
    (set-cursor nil)
    (make-instance 'space-ship)
    (event-loop ()
      (draw-image* (tag 'background)
                   (v 0 0)
                   (v 0 (- *scroll* 128))
                   (get-screen-width) (+ (get-screen-height) 128))
      (setf *scroll* (mod (incf *scroll*) 128))
      (when (< *monster-count* *max-monsters*)
        (make-instance 'monster))
      (dolist (s *sprites*)
        (draw s)
        (update s)))))

What is wrong with it?

There are still a lot of things missing or wrong with our little game, most obviously being that the player can’t die. Collision detection is also extremely slow, which is easy to see if you tune up the bullet rate. The Python version uses separate lists for bullets and monsters while our version compares every bullet against every sprite. This is easy to fix by providing a way to classify the sprites by their type when needed and caching the values, so that implementing something like (do-sprites (s class) …) should be relatively efficient.
Sound effects and music would be also nice to have, not to mention huge particle explosions… Maybe I’ll get back to it when I have more time.

New releases

November 1st, 2007

PAL release 1.1 and PAL-GUI 0.1 are now available from the usual place.

PAL-GUI still has some look’n’feel glitches, like clipping problems and funny artifacts with some Linux drivers but all in all things are progressing quite well. There are a few working widgets and writing new ones shouldn’t be too hard, I’ll add some documentation after the dust settles a bit.

Starting with the GUI

October 18th, 2007

Lately I have been looking for a gui library for Common Lisp that I could easily integrate with PAL to implement level editors and other kinds of little tools.

Most obvious candidates were Ltk, Lambda-GTK (seems to be the most stable of the GTK bindings for CL), McCLIM and Garnet. After some experimentation it turned out that all of them lacked some features that I would need; most importantly good support for loading and displaying various image formats and a way to integrate the gui’s mainloop with my own (no threading since I’m stuck on Windows here). Which is too bad since McCLIM and Garnet both offer a very interesting take on the user interface design and at least McCLIM seems work fine in Win32 with XMing.
I also tried the IUP toolkit which is very easy to bind with but eventually I lost interested in it since it didn’t actually fit my needs. If somebody out there is interested in writing a CL interface to a portable gui toolkit it could be worth a look.

So, while chanting the magic mantra* I set out to spend some of my copius free time to write yet another game oriented OpenGL gui toolkit. And while it is still very much in its infancy it has been been a quite fun, frustrating and rewarding process that I’ll post more details about later.

Release 3

July 3rd, 2007

Release 3 of PAL is now available at http://common-lisp.net/project/pal

- some minor & major bugfixes. Most importantly it _should_ now work on SBCL, CLisp, Allegro CL and Lispworks :)
- bitmap loading should be a lot faster
- few changes in the API, check changes.txt
- some draft documentation is available on the website, not much yet though

New home

June 30th, 2007

Pixel Art Library is now located on the glorious common-lisp.net.

Summer bugs

June 13th, 2007

Just a quick note about PAL and Lispworks compatibility: since I have been a bit lazy on testing with LW some ugly bugs have crawled in, especially in the bitmap loading code. Fix for that and some other minor cleanups will take place ASAP.

Meanwhile, it’s summer so get out and enjoy the radiation.

Or, if you are a Smalltalker you might be interested in Jeff Massungs Unstdio.

Pixel Art Library, Release 2

May 19th, 2007

As it says in the title, PAL R2 is here. Actually not much has changed in it and for a brief overview of the few changes and fixes see the changes.txt file.

The most important part is probably that it now should work smoothly on Linux. And in case it has problems locating the foreign .so files make sure you have SDL, SDL_image and SDL_mixer packages installed and possibly apply some changes at the beginning of the sdl.lisp file so it can find the required dynamic library files.