A game developer's guide to the RLX Common Lisp Game Engine
Table of Contents
- How to use this document
- Introducing RLX
- Setting up your development environment
- Design overview
- CLON: Common Lisp Object Network
- RLX: A Common Lisp Game Engine
How to use this document
This document explains the RLX roguelike engine's general design and programming interface from the game developer's perspective, and is meant to assist in writing RLX game modules. The document is written in Emacs Org-mode syntax, with active links to functions and documentation in relevant RLX source code files. You are probably reading the HTML version of this page, which includes clickable links to nicely formatted source code—thanks to a Lisp program called htmlfontify.
These links help us avoid duplicating documentation from the codebase: some sections of this Guide explain an area of functionality at length before diving into implementation code, while other sections consist of just a link to the relevant Lisp code and documentation.
The final part of the developer's guide links to an example game module, "Blast Tactics", which demonstrates all the basics.
Notes
- This document is incomplete and under construction.
- Familiarity with Common Lisp is assumed.
- The links below are specified as relative to the location of the present file; for cross-file references to work, you should have CLON and RLX installed in the same directory, and open this file in Emacs.
Introducing RLX
RLX is a portable free-software graphical roguelike engine written in Common Lisp. The system provides a number of tools to build graphical roguelikes.
The engine supports tile-based 2.5-D worlds with a limited z-axis and interacting, independent agents called "cells". These cells are programmed with the help of a library called CLON (Common Lisp Object Network) which adds prototype-oriented objects to Common Lisp with a custom syntax. Both turn-based and pseudo-realtime play (i.e. a simple timeout) are supported. RLX uses LISPBUILDER-SDL for cross-platform graphics and audio; the example game Blast Tactics works on GNU/Linux, Mac OSX, and Windows so far. (Precompiled binaries are not yet available for all platforms.) Other features include basic random terrain generation tools, simple AI support, GUI widgets with flexible keybindings, mouse and joystick support, ray-casting based lighting and shadow effects, Bresenham's line-of-sight implementation. Features in the process of being ported from an older Emacs Lisp version include A-star pathfinding, rule-based map generation, map editor, and a menu system.
Setting up your development environment
First you need an operating system. Because RLX runs on Linux, Mac, and Windows, you can develop games on any of those platforms. (All the development tools and libraries mentioned below are cross-platform).
- GNU Emacs is strongly recommended for developing RLX games, because of CLON's special syntax requirements (see below) and also because SLIME allows you to develop and debug interactively from within the editor.
- SLIME (Superior Lisp Interaction Mode for Emacs)
- clon.el — a short Emacs Lisp program that adds support for CLON syntax editing to Emacs
- SBCL (Steel Bank Common Lisp) is recommended.
- LISPBUILDER-SDL and its prerequisites are required.
- SDL, SDL-IMAGE, SDL-MIXER, SDL-GFX are required .
- GIMP, Audacity, Ardour, and so on can be used to edit audio and image files used as game resources.
Visit the repository to see the latest installation instructions for getting started with LISPBUILDER-SDL, CLON, and RLX. Installing GNU Emacs, SLIME, or the other programs mentioned above is beyond the scope of this document, but most of them will have downloads available on their websites.
TODO add links to all those sites
Design overview
In this section we take a brief tour of the main areas of functionality from a design perspective. After that, we will delve into API details from a programmer's point of view.
CLON
CLON is an object system designed especially for games. It uses prototypes instead of classes, and has built-in support for serialization. Messages (i.e. method invocations) can be queued and pre-processed before sending to their recipients. Message forwarding (i.e. doesNotUnderstand) is also supported.
See also clon.lisp.
The console
The "console" is an imaginary video game machine whose native language is Common Lisp. RLX games are implemented as "modules" that plug in to the system, akin to old cartridges or tapes. All services of the engine (opening the screen, drawing text and images, playing sounds, joystick input) are provided here with a platform-neutral Common Lisp interface. Currently the console uses LISPBUILDER-SDL as a backend, but other backends are possible.
See also console.lisp.
Interactive graphical widgets
Widgets are CLON graphical user interface objects. The console is designed to draw a set of "active widgets" to the screen for each video frame. The console also delivers event data to these widgets. Events are things like keystrokes, joystick buttons, timers, or mouse clicks. The keybinding system (where events are mapped to responses) draws inspiration from Emacs.
Also included in widgets.lisp:
- basic layout widgets
- an interactive command prompt
- an Emacs-like formatter with fontification and inline images
- scrolling text box widget
- a "pager" to switch between different active widget layouts using hotkeys.
Cells
"Cells" are CLON objects. Each cell represents some in-game entity; player characters, enemies, weapons, items, walls and floors are all different types of cells. Game play occurs in a three-dimensional grid of cells called a World (see below).
Cells may be stacked along the z-axis, and may also contain other cells. Cells interact by sending messages to one another and to other objects in the environment; these messages are queued and processed by the world for delivery to their recipients.
In cells.lisp you will find some basic roguelike logic built into cells.
- Basic features like name, description, and discovery.
- Unified container, inventory, and equipment system.
- Cells have an optional weight in kilograms, and the calculation recursively includes containers and equipment.
- The "action points" system allocates game turns to different cells.
- Basic melee and ranged combat support.
- Equipment slot system (i.e. "paper doll") not restricted to humanoid actors.
- "Proxying", a feature used to implement drivable vehicles and/or demonic possession.
- "Stats", for numeric-valued attributes susceptible to temporary and permanent effects (i.e. stat increases and drains, or encumbrance). Also supports setting minimum and maximum values, and keeping track of units (meters, kilograms.)
- "Categories" allow arbitrary tagging of objects, with some categories having special interpretation by the engine.
These are in effect a basic set of roleplaying rules or "physics". By defining new prototypes based on cells, you can change the rules and run the game the way you want.
Worlds composed of cells
A World object ties together all the elements of RLX into a playable situation. A World is a 2.5D grid of interacting cells. This object performs the following tasks:
- Keeps track of a single player and delivers command messages to the player cell
- Time and turns for player and CPU (the "Action Points system")
- Lighting and sound propagation
- Generating the map and placing cells on maps.
- Queueing and processing messages
There are also Universe objects composed of interlinked worlds.
See also worlds.lisp.
Math routines
- Basic dice rolls
- Distance, compass directions
- Drawing shapes made of cells
- Bresenham's line algorithm
- Random midpoint displacement "plasma"
See also math.lisp.
Pathfinding
Not yet fully ported. See path.lisp.
CLON: Common Lisp Object Network
Overview
CLON stands for Common Lisp Object Network. CLON is a prototype-based object system for Common Lisp. It is different from CLOS in several important ways:
- CLON is prototype-based, not class-based. A prototype is a template object from which other objects are "cloned".
- Method invocation happens via message-passing, not generic functions; messages are conceptually different from synchronous function calls and may be freely queued, forwarded, and filtered.
- Built-in support for serialization.
- Simple and small: as of December 2008, clon.lisp contains about 750 lines of code and commentary.
-
Special syntax support for message sending:
[method-name object arg1 arg2 ...]
and for accessing fields (i.e. "slots" in CLOS terminology):
(setf <slot-name> value)
clon.el: Emacs editing support for CLON
CLON includes a small Emacs Lisp program that adds optional support for CLON syntax, complete with fontification.
To set up clon.el, add the following to your Emacs initialization file:
(add-to-list 'load-path "~/clon") ;; Change this to where you installed CLON (require 'clon) (add-hook 'lisp-mode-hook #'clon-do-font-lock)
Code examples
What is an object in CLON?
Defclass-like prototype definitions
First we must define a prototype and name its fields:
(define-prototype rectangle () x y width height)
See also clon.lisp, "Defining prototypes"
We could also have provided initialization forms for the slots, and documentation strings:
(define-prototype rectangle ()
(x :initform 0
:documentation "The x-coordinate of the rectangle's top-left corner.")
(y :initform 0
:documentation "The y-coordinate of the rectangle's top-left corner.")
(width :documentation "The width of the rectangle.")
(height :documentation "The height of the rectangle."))
Single inheritance
And if there was a "shape" prototype, from which we would like "rectangle" to inherit data and methods, we might have written:
(define-prototype rectangle (:parent =shape=)
(x :initform 0
:documentation "The x-coordinate of the rectangle's top-left corner.")
(y :initform 0
:documentation "The y-coordinate of the rectangle's top-left corner.")
(width :documentation "The width of the rectangle.")
(height :documentation "The height of the rectangle."))
Notice the equals signs surrounding the parent object's name; all objects made with define-prototype are accessible via special variables with such names.
The reason for this is that usually you want to call a widget a widget, but if that name is taken for a special variable "widget" whose value was the prototype for all widgets, then you will have to use some other probably less effective name for the binding, like "w" or "wt" or "wydget", everywhere you want to just talk about a "widget" in your code. So instead we only reserve the equals-sign-delimited name:
=WIDGET=
Cloning objects
The function CLON:CLONE is used to create new objects from these prototypes. Now we write an initializer, which is passed any creation arguments at the time of cloning.
(define-method initialize rectangle (&key width height) (setf <width> width) (setf <height> height))
See also clon.lisp, "Cloning objects".
Notice how field accesses can be written with the angle brackets; this works both for reading and for writing, so long as you use "setf" for the latter.
See also "Field reference syntax".
Now when you say:
(setf rectangle (clone =rectangle= :width 5 :height 12))
The rectangle's initializer method is invoked with those arguments, and a rectangle of the correct height and width is created.
Basic field access
(field-value :width rectangle) (setf (field-value :height rectangle) 7)
Methods
Now we define a few methods:
(define-method area rectangle ()
(* <width> <height>))
(define-method print rectangle (&optional (stream t))
(format stream "height: ~A width: ~A area: ~A"
<height> <width>
[area self]))
See also clon.lisp, "Methods and messages"
And invoke them with the aforementioned square bracket notation.
(defvar rect (clone =rectangle= :width 10 :height 8)) [print rect]
The result:
"height: 8 width: 10 area: 80"
Message queueing
CLON also supports a concept called message queueing. When there is an active message queue, messages may be entered into the queue instead of directly invoking a method:
[queue>>render widget] [queue>>attack self :north]
The sender, receiver, method name, and arguments are all recorded in the queue. The developer can then filter or process them before sending.
Message forwarding
And finally, I will mention message forwarding, which handles the case that an object has no handler for a particular method. This is akin to Smalltalk's "doesNotUnderstand" concept.
RLX: A Common Lisp Game Engine
The "console" is a pretend home computer in 80's style
Basic input and output functions
- LISPBUILDER-SDL
- Drawing to the screen (list of active widgets)
- Responding to key press events
Resources and Modules
- From "driver-dependent objects" to string handles
- The PAK file format
- Load-on-demand
- The different resource types and their loading handlers
- Not just links to other files: the "data" field
- Standard resources (colors, icons)
- Resource aliases and transformations
Widgets: interactive graphical elements with offscreen drawing
Widget basics
Keymaps
Formatted text display
Command prompts
Cells: the atoms of the game world
Overview
Statistics
Categories
Managing turns with the "Action Points System"
Cell movement
Containers
Manipulating and picking up objects
Modeling player knowledge (not yet ported)
Equipment
Simple combat
Proxying (not yet ported)
Worlds composed of cells
The center of the action: space, time, events
Space: the grid
Time: action points and turns
Events and narration
Environmental conditions
Lighting
Schemes for automatic world generation
Viewports
Mathematics
Geometry calculations
Shape tracing
Line of sight
Lighting
Plasma
Pathfinding with A*
Date: 2009-11-18 03:47:54 EST
HTML generated by org-mode 6.30trans in emacs 23