|
UsingLispbuilderSDL
LISPBUILDER-SDL User Guide
About LISPBUILDER-SDLLISPBUILDER-SDL provides abstractions to graphics, sound, physics (TBD), character animation (TBD) and 3D libraries (in development) allowing the development of interactive applications and games in Common Lisp. Core functionality includes window and event loop management, support for 2D bitmap and vector graphics, 2D graphical effects such as rotation, color keying and alpha blending, many graphics drawing primitives such as circles, polygons, squares, Bezier and Cuttmull-Rom curves, vector drawing, image loading (bmp, jpeg, png, tiff, tga), sound mixing and playback of samples (WAV) and streaming music (mp3 and ogg formats), bitmap and true-type font support. Optional packages allow the core functionality described above to be enhanced to provide additional capabilities such 3D scene management, physics and character animation. IntroductionLISPBUILDER-SDL is not distributed with any Common Lisp development environment and must be downloaded and installed separately. Installation is always tricky for the Lisp newbie because Lisp packages are unlike similar packages for other languages, but we have attempted to provide comprehensive instructions for all supported platforms. Support is also provided on the Lispbuilder Mailing List. Before downloading, verify that your Lisp implementation and operating system are supported. Once installed, LISPBUILDER-SDL must be loaded into your Common Lisp environment by following the instructions here. The structure of a simple game follows the pattern;
The following example makes a box (a filled rectangle) follow the position of the mouse cursor, redrawing the box in a new color when the mouse button is depressed. Pressing any key will exit the example.
(defparameter *random-color* sdl:*white*)
(defun mouse-rect-2d ()
(sdl:with-init ()
(sdl:window 200 200 :title-caption "Move a rectangle using the mouse")
(setf (sdl:frame-rate) 60)
(sdl:with-events ()
(:quit-event () t)
(:key-down-event ()
(sdl:push-quit-event))
(:idle ()
;; Change the color of the box if the left mouse button is depressed
(when (sdl:mouse-left-p)
(setf *random-color* (sdl:color :r (random 255) :g (random 255) :b (random 255))))
;; Clear the display each game loop
(sdl:clear-display sdl:*black*)
;; Draw the box having a center at the mouse x/y coordinates.
(sdl:draw-box (sdl:rectangle-from-midpoint-* (sdl:mouse-x) (sdl:mouse-y) 20 20)
:color *random-color*)
;; Redraw the display
(sdl:update-display)))))First create a variable to hold the color. (defparameter *random-color* sdl:*white*) LISPBUILDER-SDL must be initialized prior to use. The example only draws to the screen and so there is no need to initialize the audio, joystick, or CDROM subsystems. (sdl:with-init () Next, create a 200 pixel by 200 pixel graphics window. Some kind of display (window or full-screen) must be created to receive events from the Window Manager. (sdl:window 200 200 :title-caption "Move a rectangle using the mouse") Lock the frame-rate to 60 fps. (setf (sdl:frame-rate) 60) Create the game loop to process the incoming user events. (sdl:with-events () The event loop will exit only when the :QUIT-EVENT handler returns T. Always remember to handle this event or the event loop will never exit. This handler is called when a quit event is received in the event queue. A quit event is generated when the user attempts to close the window, or any time SDL:PUSH-QUIT-EVENT is called. (:quit-event () t) The :KEY-DOWN-EVENT handler is called for each key depressed. We want to exit the example to exit when a key is pressed, therefore push the quit event onto the event queue SDL:PUSH-QUIT-EVENT causing :QUIT-EVENT to fire on the next event loop. (:key-down-event ()
(sdl:push-quit-event))Most game logic will be called from the :IDLE event. This handler is called when all other events in the event queue have been processed for the current event loop. In other words, :IDLE is called when the game loop is 'idle' and as such :IDLE is not a real system event. (:idle () Generate a random color while the right mouse button is depressed. Keeping the mouse down will cause the colors to cycle. MOUSE-LEFT-P returns T when the right mouse button is depressed. (when (sdl:mouse-left-p)
(setf *random-color* (sdl:color :r (random 255) :g (random 255) :b (random 255))))Clear the screen prior to drawing the rectangle. (sdl:clear-display sdl:*black*) Draw the box. A new rectangle is created having a midpoint at the current cursor position using RECTANGLE-FROM-MIDPOINT-*. MOUSE-X and MOUSE-Y return the current mouse cursor position. (sdl:draw-box (sdl:rectangle-from-midpoint-* (sdl:mouse-x) (sdl:mouse-y) 20 20)
:color *random-color*)The display is then redrawn at the end of the game loop. (sdl:update-display))))) Compatibility & Platforms SupportedLISPBUILDER-SDL runs on all of the popular Common Lisp implementations. The compatibility table lists the combinations of implementation and operating system that are supported. Loading LISPBUILDER-SDLAfter installation is complete, LISPBUILDER-SDL can be loaded as follows; (ASDF:OPERATE 'ASDF:LOAD-OP :LISPBUILDER-SDL) Or if you are using SLIME, enter , then l then LISPBUILDER-SDL Initializing LISPBUILDER-SDLLISPBUILDER-SDL must be initialized prior to use. Audio, video, joystick and CDROM are separate subsystems and must also be initialized prior to use. The following functions will initialize (and close) the LISPBUILDER library;
The video, audio, joystick and CDROM subsystems are automatically initialized as follows;
Individual subsystems can also be initialized using INIT-SDL and WITH-INIT. (SDL:INIT-SDL :VIDEO T :AUDIO T) or, WITH-INIT, by passing the subsystems as parameters, for example (SDL:WITH-INIT (SDL:SDL-INIT-VIDEO SDL:SDL-INIT-AUDIO) ...) The list of subsystems is as follows;
INIT-SDL will only initialize subsystems that are not already initialized. To force the initialization of subsystems regardless of current status use :FORCE T, for example (SDL:INIT-SDL :VIDEO T :AUDIO T :FORCE T) Video, audio, joystick and CDROM subsystems can be initialized at any time using the following functions;
These functions will force the initialization of the specified subsystem regardless of current status when FORCE is T. The status of the library or of any subsystem can be queried at runtime using;
>> (SDL:SDL-INIT-P SDL:SDL-INIT-VIDEO) ((LISPBUILDER-SDL-CFFI::SDL-INIT-VIDEO 32)) >> (SDL:VIDEO-INIT-P) T Subsystems can be closed at any time using the following functions;
These functions will force close of the specified subsystems regardless of current status when FORCE it T. Using WITH-INIT will automatically close lispbuilder-sdl upon exit. HardwareGraphics Hardware CapabilitiesLISPBUILDER-SDL can utilize the following capabilities of the graphics hardware if available;
The capabilities of the video hardware can be queried following initialization of the video subsystem (INIT-SDL :VIDEO T), or (INIT-VIDEO). Prior to the display being created (using WINDOW) these functions will return the best video mode available. After the display is created these functions will return the current video mode.
The video driver exposes hardware capabilities to the library. The windib display driver is used in Windows by default. This driver provides almost no support for the underlying hardware and will perform all graphical operations in software. However windib is the most compatible across hardware and operating system versions, and is usually the best choice in terms of speed especially when using color keys or alpha blending. The video driver can be specified by passing the driver name to WINDOW using :VIDEO-DRIVER, or using SET-VIDEO-DRIVER. The display driver is set prior to creating the display.
The audio driver can be specified by passing the driver name to WINDOW using :AUDIO-DRIVER, or using SET-AUDIO-DRIVER. The display driver is set prior to creating the display.
Video InfoUse VIDEO-INFO to query the capabilities of the graphics hardware. video-info &optional info => result :INFO can be one of:
For example, enter the following to determine the capabilities of the video hardware; >> (SDL:SET-VIDEO-DRIVER "windib") 0 >> (SDL:INIT-VIDEO) T >> (SDL:VIDEO-INFO)) (:WM-AVAILABLE) >> (SDL:QUIT-SDL) >> (SDL:SET-VIDEO-DRIVER "directx") 0 >> (SDL:INIT-VIDEO) T >> (SDL:VIDEO-INFO)) (:HW-AVAILABLE :WM-AVAILABLE :BLIT-SW-CC :BLIT-FILL :BLIT-HW-CC :BLIT-SW :BLIT-HW) Video MemoryUse VIDEO-MEMORY to query the amount of video memory available. Only makes sense when surfaces can be created in video memory, (HW-AVAILABLE-P) returns T, otherwise 0 video memory is reported. For example; >> (SDL:INIT-VIDEO) T >> (SDL:HW-AVAILABLE-P) NIL >> (SDL:VIDEO-MEMORY) 0 Video DimensionsVIDEO-DIMENSIONS returns the best video width and height if called before a window is created. Or returns the current video dimensions if called after a window is created. For example; >> (SDL:INIT-VIDEO) T >> (SDL:VIDEO-DIMENSIONS) #(1280 800) List ModesLIST-MODES returns a list sorted largest to smallest of the width and height of the screens that will support the type of video surface requested in :FLAGS. list-modes flags &optional surface => result FLAGS can be one or more of the following;
For example; >> (SDL:INIT-VIDEO) T >> (SDL:LIST-MODES '(SDL:SDL-HW-SURFACE SDL:SDL-FULLSCREEN SDL:SDL-DOUBLEBUF)) (#(1280 800) #(1024 768) #(800 1280) #(800 600) #(768 1024) #(640 480) #(640 400) #(600 800) #(512 384) #(480 640) #(400 640) #(384 512) #(320 200)) Video DriverSET-VIDEO-DRIVER will attempt to use the specified video driver to create the display. The video drivers supported are listed in the SDL documentation. Set the video driver after the video subsystem has been initialized, but before the display is created. Valid drivers for Windows are "windib" and "directx". The default video driver is "windib". For example; >> (SDL:SET-VIDEO-DRIVER "windib") :VIDEO-DRIVER when passed to WINDOW will also set the video driver. Audio DriverSET-AUDIO-DRIVER will attempt to use the specified audio driver. The audio drivers supported are listed in the SDL documentation. Set the audio driver after the audio subsystem has been initialized, but before the display is created. Valid drivers for Windows are "pulse", "dsound" and "waveout". For example; >> (SDL:SET-AUDIO-DRIVER "waveout") :AUDIO-DRIVER when passed to WINDOW will also set the audio driver. Video Driver NameVIDEO-DRIVER-NAME will return the name if the video driver. The video drivers supported are listed in the SDL documentation. This function is useful only after the video subsystem has been initialized. For example; >> (SDL:INIT-VIDEO) T >> (SDL:VIDEO-DRIVER-NAME) "windib" Audio Driver NameAudio-DRIVER-NAME will return the name if the audio driver. The audio drivers supported are listed in the SDL documentation. This function is useful only after the audio subsystem has been initialized. For example; >> (SDL:INIT-AUDIO) T >> (SDL:AUDIO-DRIVER-NAME) "dsound" The DisplayDescription of the DisplayGraphics are rendered to a video surface. A SURFACE represents an area of graphical memory that can be drawn or rendered to, for example the video frame buffer or an image that is loaded from disk. The video surface must be updated at least once each game loop using the function UPDATE-DISPLAY. The display can be filled with a background color using CLEAR-DISPLAY. The properties of an the vidoe surface can be queried using VIDEO-INFO. The screen resolutions supported by a particular set of video flags can be retrieved using LIST-MODES. The name of the video driver can be retrieved as a STRING using VIDEO-DRIVER-NAME. Use WINDOW to create and configure the video surface on startup. window width height
&key flags sw hw fullscreen async-blit any-format palette double-buffer
opengl opengl-attributes resizable no-frame
bpp title-caption icon-caption position fps
video-driver audio-driver => resultUse RESIZE-WINDOW to modify the video surface during program execution. resize-window width height
&key bpp flags sw hw fullscreen async-blit any-format
palette double-buffer opengl opengl-attributes
resizable no-frame title-caption icon-caption => resultFor example; (SDL:WINDOW 320 240 :TITLE-CAPTION "Random Rectangles" :ICON-CAPTION "Random Rectangles"
:DOUBLE-BUFFER T :FULLSCREEN T :POSITION t)Parameters
DimensionsThe window width and height are retrieved using VIDEO-DIMENSIONS. >> (SDL:WINDOW 320 240) >> (SDL:VIDEO-DIMENSIONS) #(320 240) Full-screen or WindowedLISPBUILDER-SDL will create a windowed display by default. However this can be changed on startup or any time during program execution by setting :FULLSCREEN. >> (SDL:WINDOW 320 240 :FULLSCREEN T) If the specified resolution is not supported in full-screen mode then the next higher resolution will be used and the display window will be centered on a black background. Color depthWhen :BPP is unspecified or 0, the surface will be created with the bits-per-pixel of the current display. Normally if a video surface of the requested bits-per-pixel is not available one is emulated using a shadow surface. Setting :ANY-FORMAT prevents this. If :ANY-FORMAT is used and the requested pixel depth (:BPP) is unavailable, a display surface having a bits-per-pixel that matches the current display is returned. For example; (SDL:WINDOW 320 240 :BPP 32 :ANY-FORMAT T) The valid values for :BPP are as follows;
A bits-per-pixel of 24 uses the packed representation of 3 bytes per pixel. For the more common 4 bytes per pixel mode, please use a bits-per-pixel of 32. Window Title and BorderUse :TITLE-CAPTION to set the caption in the window title bar. Use :ICON-CAPTION to set the caption when the window is minimized. For example; (SDL:WINDOW 320 240 :TITLE-CAPTION "A Test"
:ICON-CAPTION "A Test Minimized"))To allow the window to be resized, set :RESIZABLE. The library will allow the window manager, if any, to resize the window at runtime. When this occurs, a VIDEO-RESIZE-EVENT event is sent to the application, and the application must respond by calling RESIZE-WINDOW with the requested size (or another size that suits the application). (SDL:WINDOW 320 240 :TITLE-CAPTION "A Test"
:ICON-CAPTION "A Test Minimized")
:RESIZABLE T)To remove the title bar and border decoration from the window, set :NO-FRAME. Full-screen modes automatically have this flag set. (SDL:WINDOW 320 240 :TITLE-CAPTION "A Test"
:ICON-CAPTION "A Test Minimized")
:NO-FRAME T)Window X and Y PositionUse :POSITION to set the display window x and y position as follows;
For example; (SDL:WINDOW 320 240 :POSITION #(100 200)) Display Surface in System Memory or Video MemoryThe display surface can be created in system memory (:SW) or video memory (:HW). When the display is created in system memory a software surface is returned. When the display is created in video memory a hardware surface is returned. A surface in system memory (software surface) improves the performance of pixel level access but is incompatible with some types of hardware blitting. A surface in video memory (hardware surface) takes advantage of Video-to-Video blits which are often hardware accelerated. Generally, create your display surface in system memory unless you know what you are doing. This topic is discussed in greater detail in TBD The system may return a software surface even when a hardware surface is requested as many platforms can only provide a hardware surface when using SDL-FULL-SCREEN. For example; (SDL:WINDOW 320 240 :SW T) Synchronous or Asynchronous updates:ASYNC-BLIT enables the use of asynchronous updates of the display surface. This will usually slow down blitting on single CPU machines, but may provide a speed increase on SMP systems. An overview is provided on the SDL developers mailing list here, and here This has not been well tested on the supported Lisp platforms so use at your own risk. It may be problematic on Lisps that do not support multi-threading. For example; (SDL:WINDOW 320 240 :ASYNC-BLIT T) Exclusive palette access:PALETTE will guarantee that the colors set by SDL_SetColors() will be the colors you get. Otherwise, in 8-bit mode, SDL_SetColors() may not be able to set all of the colors exactly the way they are requested, and you should look at the video surface structure to determine the actual palette. If SDL cannot guarantee that the colors you request can be set, i.e. if the colormap is shared, then the video surface may be created under emulation in system memory, overriding :HW. For example; (SDL:WINDOW 320 240 :PALETTE T) Single or Double buffering:DOUBLE-BUFFER will try to set up two surfaces in video memory and swap between them when you call UPDATE-DISPLAY. This is usually slower than the normal single-buffering scheme, but prevents "tearing" artifacts caused by modifying video memory while the monitor is refreshing. Double buffering should only be used by applications that redraw the entire screen on every update. For example; (SDL:WINDOW 320 240 :DOUBLE-BUFFER T) OpenGL rendering:OPENGL will create an OpenGL rendering context. For example; (SDL:WINDOW 320 240 :OPENGL T) :OPENGL-ATTRIBUTES sets the following attributes for OpenGL;
:OPENGL-ATTRIBUTES must be a list of attributes as follows; (SDL:WINDOW 320 240 :OPENGL T
:OPENGL-ATTRIBUTES '((:SDL-GL-DOUBLEBUFFER 1)
(:SDL-GL-STEREO 1)))Redraw the DisplayUse UPDATE-DISPLAY to;
The display should be redrawn once per game loop. Clearing the DisplayUse CLEAR-DISPLAY to clear video display using a specific color. Determining the Properties of the Current Video Display SurfaceThe following functions return the properties of the current display surface;
Native WindowA foreign pointer pointer to the display surface can be retrieved using GET-NATIVE-WINDOW. Application StateApplication state includes mouse focus, keyboard focus and window show state. The following functions will return the current application state;
For example; >> (SDL:ACTIVE-P) T embarrassing embarrasing Key processingLISPBUILDER-SDL provides control over Unicode processing and key repeat intervals. UnicodeTo obtain the character codes corresponding to received keyboard events, Unicode translation must first be turned on using ENABLE-UNICODE. The translation incurs a slight overhead for each keyboard event and is therefore disabled by default. For each subsequently received :KEY-DOWN-EVENT, the :UNICODE keyword will contain the corresponding character code, or zero for keys that do not correspond to any character code. Note that only key press events will be translated, not release events.
Key RepeatA single :KEY-DOWN-EVENT is generated per key press regardless of how long the key is depressed. When key repeat is enabled, multiple :KEY-DOWN-EVENTs are generated per the specified delay and interval. ENABLE-KEY_REPEAT delay interval DELAY is the amount of time that elapses before characters repeat when a key is depressed. INTERVAL determines how fast characters repeat when a key is depressed. Both DELAY and INTERVAL are expressed in milliseconds. Setting DELAY or INTERVAL to NIL will set the default values of the constants SDL-DEFAULT-REPEAT-DELAY and SDL-DEFAULT-REPEAT-INTERVAL respectively. Note: ENABLE-KEY-REPEAT must be called after the SDL library has been initialized to have any effect. The functions for controlling key repeat are;
The Game LoopDescription of the Game LoopUnlike desktop application software that is usually event driven, a game is typically built around a game loop. The game loop is responsible for processing user input, executing game logic, limiting the frame rate, and redrawing the display. The WITH-EVENTS macro forms the core of the game loop and is discussed in the following sections. Introduction to Input HandlingDescription of Input EventsInteraction with the user is a significant part of a game. The game loop receives input from the user, executes the game logic based on this input, and generates output - usually in the form of graphics rendered to a display. For each input stimuli the system will generate a corresponding event. Inputs are received from the mouse, keyboard, joystick, window manager, and user (user generated). The system creates events to match these inputs; for example a key when depressed will generate a key down event. Releasing the key will generate a key up event. The WITH-EVENTS macro provides an efficient mechanism for processing these events. For example, while the mouse is moved a constant stream of :MOUSE-MOTION-EVENT events are generated. To process the x and y mouse position the user must provide a :MOUSE-MOTION-EVENT handler. WITH-EVENTS calls the handler each time this event is received. Event handlers are straight forward to define as shown in the code below; (WITH-EVENTS ()
(:MOUSE-MOTION-EVENT (:X mouse-x :Y mouse-y)
...))The only event that must be handled is :QUIT-EVENT. If :QUIT-EVENT is ignored then it becomes impossible to close the Window or exit the game loop. (WITH-EVENTS ()
(:QUIT-EVENT () T)
(:MOUSE-MOTION-EVENT (:X mouse-x :Y mouse-y)
...))Event processing is described in greater detail in section Event Processing. Description of Input StateIn addition to event processing, the system also provides functions to query the current state of the keyboard, mouse, display and joystick. Unlike events that are generated in response to input; state is the way something is with respect to its main attributes. For example a :KEY-DOWN-EVENT event may signal that a the :SDL-KEY-ESCAPE key is depressed. However the current state of the keyboard may indicate that keys :SDL-KEY-ESCAPE and :SDL-KEY-SPACE are currently depressed. Input State is described in greater detail in section Input State. Executing Game Logic:IDLE, although not technically an actual event, is executed once per game loop after all events have been processed. Place logic to be executed in the game loop that is not event driven in :IDLE. This logic may update world physics, update the state of game objects and render sprites to the display. (SDL:WITH-EVENTS ()
(:QUIT-EVENT () T)
(:MOUSE-MOTION-EVENT (:X mouse-x :Y mouse-y)
...)
(:IDLE ()
(SDL:UPDATE-DISPLAY)))Setting the Frame RateWITH-EVENTS can limit the game loop to a number of iterations a second, or maintain a constant time-step described in Frame Rate. Updating the DisplayUse UPDATE-DISPLAY to refresh the video surface or swap buffers if double buffering is enabled. The display should be redrawn once per game loop. Event ProcessingPoll or WaitWITH-EVENTS can either POLL or WAIT for events on the queue by specifying :POLL or :WAIT respectively, for example; (SDL:WITH-EVENTS (:POLL)
(:QUIT-EVENT () T)
(:KEY-DOWN-EVENT (:KEY KEY)
(WHEN (SDL:KEY= KEY :SDL-KEY-ESCAPE)
(SDL:PUSH-QUIT-EVENT)))
(:VIDEO-EXPOSE-EVENT () (SDL:UPDATE-DISPLAY)))))) If :POLL, WITH-EVENTS will continuously poll for pending events. If no events are available then the game loop is run and the forms in :IDLE are executed. If :WAIT, WITH-EVENTS will sleep indefinitely for the next available event. If no events are available then the game loop is paused. :IDLE is ignored when the event mechanism is :WAIT. EventsThe following events are supported;
Idle Event (:IDLE ()
&BODY BODY) The :IDLE event is special in that it is not a system generated event. The forms in :IDLE are executed once per game loop after all events in the event queue are processed. :IDLE is ignored when the event mechanism is :WAIT. Active Event(:ACTIVE-EVENT (:GAIN GAIN :STATE STATE) &BODY BODY) :ACTIVE-EVENT is generated when the application gains or loses mouse, key or active state. For example an :ACTIVE-EVENT event is received when the mouse is moved outside the boundary of the application window. The following functions return the current state of the application, or interpret the value of :STATE.
For example; >> (SDL:MOUSE-FOCUS-P state) T The following functions return the event type, interpreting the value of :GAIN and :STATE to determine if the application has gained or lost mouse, key or active state.
For example; >> (SDL:MOUSE-GAIN-FOCUS-P gain state) T Use :GAIN and :STATE in combination to determine the active event, for example; (:ACTIVE-EVENT (:GAIN gain :STATE state)
(WHEN (SDL:MOUSE-FOCUS-P state) ;; Mouse focus event
(IF (SDL:MOUSE-GAIN-FOCUS-P gain state)
;; Mouse over window
)))If the application window has gained state, then :GAIN will be 1, otherwise :GAIN will be 0 (zero). :STATE contains a bitmask of any or all of APP-MOUSE-FOCUS, APP-INPUT-FOCUS or APP-ACTIVE identifying the events that caused the change in state. Note: This event is not generated when an application window is first created. Keyboard Events(:KEY-DOWN-EVENT (:STATE STATE :SCANCODE SCANCODE :KEY KEY :MOD MOD :MOD-KEY MOD-KEY :UNICODE UNICODE) &BODY BODY) (:KEY-UP-EVENT (:STATE STATE :SCANCODE SCANCODE :KEY KEY :MOD MOD :MOD-KEY MOD-KEY :UNICODE UNICODE) &BODY BODY) A keyboard event generally occurs when a key is released or when a key is pressed. The information on the key that generated the event is stored in KEY and MOD. The SDL-CAPS-LOCK and SDL-NUM-LOCK keys are special cases and report an SDL-KEY-DOWN when first pressed, then an SDL-RELEASED when released and pressed again. The KEYUP and KEYDOWN events are therefore analogous to the state of the caps lock and num lock LEDs rather than the keys themselves. These special cases are required for compatibility with Sun workstations. Note: Repeating KEY-DOWN-EVENT events will occur if key repeat is enabled using SDL-ENABLE-KEY-REPEAT(#sdl-enable-key-repeat).
Mouse Motion Event(:MOUSE-MOTION-EVENT (:STATE STATE :X X :Y Y :X-REL X-REL :Y-REL Y-REL) &BODY BODY) A MOUSE-MOTION-EVENT event occurs when the mouse moves within the application window or when SDL-WARP-MOUSE is called. Both the absolute X and Y and relative X-REL and Y-REL coordinates are reported along with the current button state STATE. The button state can be interpreted using SDL-BUTTON, see SDL-GET-MOUSE-STATE. If the cursor is hidden using SDL-SHOW-CURSOR and the input is grabbed using SDL-WM-GRAB-INPUT, then the mouse will give relative motion events even when the cursor reaches the edge of the screen. This is currently only implemented on Windows and Linux/Unix-alikes.
Mouse Button Events(:MOUSE-BUTTON-DOWN-EVENT (:BUTTON BUTTON :STATE STATE :X X :Y Y) &BODY BODY) (:MOUSE-BUTTON-UP-EVENT (:BUTTON BUTTON :STATE STATE :X X :Y Y) &BODY BODY) When a mouse button press or release is detected the number of the button pressed (from 1 to 255, with 1 usually being the left button and 2 the right) is placed into BUTTON, the position of the mouse when this event occurred is stored in the X and the Y fields. Mouse wheel events are reported as buttons 4 (up) and 5 (down). Two events are generated i.e. a MOUSE-BUTTON-DOWN followed by a MOUSE-BUTTON-UP event.
Joystick Motion Event(:JOY-AXIS-MOTION-EVENT (:WHICH WHICH :AXIS AXIS :VALUE VALUE) &BODY BODY) A JOY-AXIS-MOTION-EVENT event occurs whenever a user moves an axis on the joystick.
Joystick Button Events(:JOY-BUTTON-DOWN-EVENT (:WHICH WHICH :BUTTON BUTTON :STATE STATE) &BODY BODY) (:JOY-BUTTON-UP-EVENT (:WHICH WHICH :BUTTON BUTTON :STATE STATE) &BODY BODY) A JOY-BUTTON-DOWN-EVENT or JOY-BUTTON-DOWN-EVENT event occurs whenever a user presses or releases a button on a joystick.
Joystick Hat Motion Event (:JOY-HAT-MOTION-EVENT (:WHICH WHICH :HAT HAT :VALUE VALUE)
&BODY BODY) A JOY-HAT-MOTION-EVENT event occurs when ever a user moves a hat on the joystick.
Joystick Ball Motion Event(:JOY-BALL-MOTION-EVENT (:WHICH WHICH :BALL BALL :X-REL X-REL :Y-REL Y-REL) &BODY BODY) A JOY-BALL-MOTION-EVENT event occurs when a user moves a trackball on the joystick. Trackballs only return relative motion.
Quit Event(:QUIT-EVENT () &BODY BODY) If :QUIT-EVENT is filtered or ignored then it is impossible for the user to close the window. If :QUIT-EVENT is handled and returns T then the application window will be closed. If :QUIT-EVENT is handled and returns NIL then the application window will not be closed. QUIT-REQUESTED will return non-zero if a :QUIT-EVENT is pending. Note: Screen updates will continue to report success even though the application is no longer visible. Window Resize Event(:VIDEO-RESIZE-EVENT (:W W :H H) ...) When SDL-RESIZABLE is passed as a flag to WINDOW, the user is allowed to resize the application window. When the window is resized a VIDEO-RESIZE-EVENT event is reported, with the new window width and height values stored in W and H respectively. When an VIDEO-RESIZE-EVENT event is received the window should be resized to the new dimensions using WINDOW. Video Expose Event(:VIDEO-EXPOSE-EVENT () ...) A VIDEO-EXPOSE-EVENT is generated when the screen has been modified outside of the application, usually by the window manager, and needs to be redrawn. For example the window may be 'Maximized' or 'Restored'. Note: A VIDEO-EXPOSE_EVENT is not generated when the window or screen is initially displayed. Window Manager Events(:SYS-WM-EVENT () ...) The system window manager event contains a pointer to system-specific information about unknown window manager events. If this event is enabled using SDL-EVENT-STATE, it will be generated whenever unhandled events are received from the window manager. This can be used, for example, to implement cut-and-paste in your application. If you want to obtain system-specific information about the window manager, you can fill in the version member of a SDL-SYS-WM-INFO structure using SDL-VERSION, and pass it to the function: SDL-GET-WM-INFO User Events(:USER-EVENT (:TYPE TYPE :CODE CODE :DATA1 DATA1 :DATA2 DATA2) ...) USER-EVENT is unique in that it is created by the user. USER-EVENT can be pushed onto the event queue using PUSH-USER-EVENT. The contents of the event are completely up to the programmer.
Syntax of WITH-EVENTS(WITH-EVENTS (TYPE)
(:ACTIVE-EVENT (:GAIN GAIN :STATE STATE)
... )
(:KEY-DOWN-EVENT (:STATE STATE :SCANCODE SCANCODE :KEY KEY :MOD MOD :UNICODE UNICODE)
... )
(:KEY-UP-EVENT (:STATE STATE :SCANCODE SCANCODE :KEY KEY :MOD MOD :UNICODE UNICODE)
...)
(:MOUSE-MOTION-EVENT (:STATE STATE :X X :Y Y :X-REL X-REL :Y-REL Y-REL)
...)
(:MOUSE-BUTTON-DOWN-EVENT (:BUTTON BUTTON :STATE STATE :X X :Y Y)
...)
(:MOUSE-BUTTON-UP-EVENT (:BUTTON BUTTON :STATE STATE :X X :Y Y)
...)
(:JOY-AXIS-MOTION-EVENT (:WHICH WHICH :AXIS AXIS :VALUE VALUE)
...)
(:JOY-BUTTON-DOWN-EVENT (:WHICH WHICH :BUTTON BUTTON :STATE STATE)
...)
(:JOY-BUTTON-UP-EVENT (:WHICH WHICH :BUTTON BUTTON :STATE STATE)
...)
(:JOY-HAT-MOTION-EVENT (:WHICH WHICH :HAT HAT :VALUE VALUE)
...)
(:JOY-BALL-MOTION-EVENT (:WHICH WHICH :BALL BALL :X-REL X-REL :Y-REL Y-REL)
...)
(:VIDEO-RESIZE-EVENT (:W W :H H)
...)
(:VIDEO-EXPOSE-EVENT ()
...)
(:SYS-WM-EVENT ()
...)
(:USER-EVENT (:TYPE TYPE :CODE CODE :DATA1 DATA1 :DATA2 DATA2)
...)
(:QUIT-EVENT ()
...
T)
(:IDLE ()
... )) Input StateQuerying the Keyboard State
Querying the Mouse State
Querying the Joystick StateTBD Frame Rate and TimestepIntroduction to Frame Rate and TimestepSimple games can get away with locking the physics simulation to the frame rate. For games that require an accurate physics simulation, the physics simulation must be decoupled from the display frame rate. These two modes are supported by the game loop. The logic for each mode is encapsulated within a frame rate manager;
The functions used to query and manage frame rate are as follows;
Use FRAME-RATE to set the frame rate. For example to set the frame rate to 60 frames per second; (SETF (SDL:FRAME-RATE) 60) To unlock the frame rate, effectively running the game loop as fast as the CPU will allow, set the FRAME-RATE to NIL; (SETF (SDL:FRAME-RATE) NIL) The delta between frames is retrieved using DT (SDL:DT) The maximum dt is retrieved using MAX-DT. (SDL:MAX-DT) Constant Frame RateThe game loop may be executed a fixed number of frames per second or as fast as possible. Games that implement a simple physics simulation can get away with locking the physics simulation to to the frame rate and updating the physics simulation once per game loop. In this mode the value of DT will change due to small variations in the duration of each frame. The algorithm used to maintain the frame rate is the same as described in SDL_gfx. :IDLE is executes each frame and contains game logic, physics and sprite and screen redraws. The constant frame rate manager, FPS-FIXED, must be initialized at display creation time, prior to entering the game loop (SDL:WINDOW 320 240 :FPS (make-instance 'sdl:fps-fixed)) FPS-FIXED accepts the following optional keyword arguments;
Constant Time-stepGames that implement a complex physics simulation must use a constant timestep. To maintain a constant timestep the physics simulation is be decoupled from the frame rate. The algorithm used is the same as is described in Fix Your Timestep!. The physics simulation can be updated via a callback using FPS-UNLOCKED, and within WITH-TIMESTEP using FPS-TIMESTEP. Physics InlineFPS-TIMESTEP must be initialized prior to entering the game loop, at display creation time. (SDL:WINDOW 320 240 :FPS (make-instance 'sdl:fps-timestep)) FPS-TIMESTEP accepts the following optional keyword parameters;
Use :IDLE to redraw the screen each frame. To update the physics simulation use WITH-TIMESTEP. WITH-TIMESTEP will update the physics simulation as is necessary per frame. This means that the forms within WITH-TIMESTEP are executed more often per frame on machines having a lower frame rate. ;; Idle work
(:idle
;; Clear screen
(sdl:clear-display sdl:*black*)
(sdl:with-timestep ()
;; Update the particles
(dolist (p *particles*)
(update-particle p (/ (sdl::dt) 1000))))
;; Update the particles
(dolist (p *particles*)
(sdl:draw-surface-at-* *particle-img*
(round (vec2-x (particle-pos p)))
(round (vec2-y (particle-pos p)))))
;; Flip back/front buffers
(sdl:update-display))Physics CallbackFPS-UNLOCKED uses a callback mechanism to update the physics simulation. The callback is passed to FPS-UNLOCKED as an optional keyword parameter;
#'(lambda (ticks dt)
...)For example; (defun physics (ticks dt)
(declare (ignore ticks))
(dolist (p *particles*)
(update-particle p (/ dt 1000))))
(SDL:WINDOW 320 240 :FPS (make-instance 'sdl:fps-unlocked :ps-fn #'physics))Default Keyword ArgumentsMany of the functions and macros defined in lispbuilder accept one or more keyword arguments. For example the function DRAW-PIXEL accepts :COLOR and :SURFACE keyword arguments that are bound to the symbols *DEFAULT-COLOR* and *DEFAULT-SURFACE* respectively. The objects that are bound to these symbols will be used unless the keyword arguments are overridden by the developer. The macros WITH-SURFACE, and WITH-SURFACES will bind a surface to *DEFAULT-SURFACE* within the scope of these macros. For example, instead of specifying a target surface for each draw-* function a user may bind *DEFAULT-SURFACE* to a surface once and subsequent draw-* functions will use this surface while it remains in scope. (DRAW-PIXEL (point :x x1 :y y1) :surface a-surface :color *white*) (DRAW-PIXEL (point :x x2 :y y2) :surface a-surface :color *white*) (DRAW-PIXEL (point :x x3 :y y3) :surface a-surface :color *white*) (DRAW-PIXEL (point :x x4 :y y4) :surface a-surface :color *white*) Now, we use WITH-SURFACE to bind a surface to *DEFAULT-SURFACE*. (WITH-SURFACE (a-surface) (DRAW-PIXEL (point :x x1 :y y1) :color *white*) (DRAW-PIXEL (point :x x2 :y y2) :color *white*) (DRAW-PIXEL (point :x x3 :y y3) :color *white*) (DRAW-PIXEL (point :x x4 :y y4) :color *white*)) We can use WITH-COLOR to bind *DEFAULT-COLOR* to *WHITE*: (WITH-SURFACE (a-surface)
(WITH-COLOR (*white*)
(DRAW-PIXEL (point :x x1 :y y1))
(DRAW-PIXEL (point :x x2 :y y2))
(DRAW-PIXEL (point :x x3 :y y3))
(DRAW-PIXEL (point :x x4 :y y4)))Finally, if the target surface is the display then *WITH-SURFACE* is not required as :SURFACE when NIL will be bound to *DEFAULT-DISPLAY* as a convenience. So the above code can be shortened as follows: (WITH-COLOR (*white*) (DRAW-PIXEL (point :x x1 :y y1)) (DRAW-PIXEL (point :x x2 :y y2)) (DRAW-PIXEL (point :x x3 :y y3)) (DRAW-PIXEL (point :x x4 :y y4))) But default keyword arguments have an added advantage other than keystroke savings. The macro WITH-COLOR does not bind a color to the global symbol *DEFAULT-COLOR* but rather creates a new local variable called *DEFAULT-COLOR*. The local variable in effect shadows the global symbol. Thus *DEFAULT-COLOR* is bound to the color black ONLY within the scope of WITH-COLOR. This means we are able to nest several WITH-COLOR macros creating in effect a stack. We push a new color onto the stack with each subsequent WITH-COLOR and the system pops a color from the stack when a WITH-COLOR goes out of scope. This is illustrated by the following example: (with-surface (*default-display*)
(with-color (#(0 0 0))
(with-color (#(255 255 255))
(draw-rectangle #(10 10 200 200)))
(draw-rectangle #(40 40 200 100))))This code performs the following hand-waving:
And thanks to the wonders of Lisp indentation we are able to visualize the color stack directly in the code making debugging a lot easier. Optional PackagesThe LISPBUILDER-SDL core functionality can be extended by using one or more of the following packages;
LISPBUILDER-SDL-GFXOverviewLISPBUILDER-SDL-GFX provides support for the rendering of several graphics primitives directly to SDL surfaces via a native library. Functions and symbols exported from the LISPBUILDER-SDL-GFX package are accessible using the LISPBUILDER-SDL-GFX: prefix or the shorter form SDL-GFX: nickname. Loading LISPBUILDER-SDL-GFXAfter installation is complete, LISPBUILDER-SDL-GFX can be loaded by entering the following at the REPL; (ASDF:OPERATE 'ASDF:LOAD-OP :LISPBUILDER-SDL-GFX) Loading LISPBUILDER-SDL-GFX ExamplesEnter the following at the REPL to load the examples in the LISPBUILDER-SDL-GFX-EXAMPLES package; (asdf:operate 'asdf:load-op :lispbuilder-sdl-gfx-examples)
The following examples are contained in the package LISPBUILDER-SDL-GFX-EXAMPLES: (SDL-GFX-EXAMPLES:RECURSION) (SDL-GFX-EXAMPLES:METABALLS) (SDL-GFX-EXAMPLES:POINTS-AND-LINES) (SDL-GFX-EXAMPLES:FUNCTIONS) (SDL-GFX-EXAMPLES:WIDTH-HEIGHT) (SDL-GFX-EXAMPLES:INBUILT-FONT) (SDL-GFX-EXAMPLES:BEZIER) (SDL-GFX-EXAMPLES:VERTICES) (SDL-GFX-EXAMPLES:SHAPE-PRIMITIVES) (SDL-GFX-EXAMPLES:SETUP-AND-DRAW) (SDL-GFX-EXAMPLES:DISTANCE-2D) (SDL-GFX-EXAMPLES:RANDOM-CIRCLES) (SDL-GFX-EXAMPLES:OBJECTS) Using LISPBUILDER-SDL-GFXThe LISPBUILDER-SDL-GFX APIs have the same signature as the LISPBUILDER-SDL versions. See the LISPBUILDER-SDL API Reference for the APIs supported by LISPBUILDER-SDL-GFX. LISPBUILDER-SDL-IMAGEOverviewLISPBUILDER-SDL-IMAGE supports the following image formats: TGA, BMP, PNM, PBM, PGM, PPM, XPM, XCF, PCX , GIF, JPG, TIF, LBM and PNG LISPBUILDER-SDL-IMAGE will attempt to detect the image type and load an image using the Magic Number in the image file. For image formats that have no magic number such as targa (.TGA), LOAD-IMAGE allows the image type to be specified as a parameter. Functions and symbols exported from the LISPBUILDER-SDL-IMAGE package are accessible using the LISPBUILDER-SDL-IMAGE: prefix or the shorter form SDL-IMAGE: nickname. Loading LISPBUILDER-SDL-IMAGEAfter installation is complete, LISPBUILDER-SDL-IMAGE can be loaded by entering the following at the REPL; (ASDF:OPERATE 'ASDF:LOAD-OP :LISPBUILDER-SDL-IMAGE) Loading LISPBUILDER-SDL-IMAGE ExamplesEnter the following at the REPL to load the examples in the LISPBUILDER-SDL-IMAGE-EXAMPLES package; (asdf:operate 'asdf:load-op :lispbuilder-sdl-image-examples)
The following examples are contained in the package LISPBUILDER-SDL-IMAGE-EXAMPLES: (SDL-IMAGE-EXAMPLES:IMAGE-EXAMPLE) Using LISPBUILDER-SDL-IMAGEThe LISPBUILDER-SDL-IMAGE APIs have the same signature as the LISPBUILDER-SDL versions. Note that external libraries must be installed for JPG, PNG and TIFF support:
LISPBUILDER-SDL-TTFOverviewLISPBUILDER-SDL-TTF provides support for the loading and rendering of True-Type fonts. Functions and symbols exported from the LISPBUILDER-SDL-TTF package are accessible using the LISPBUILDER-SDL-TTF: prefix or the shorter form SDL-TTF: nickname. The TrueType library is automatically initialized and uninitialized by the LISPBUILDER-SDL package. The functions INIT-TTF and QUIT-TTF are added to the lists *EXTERNAL-INIT-ON-STARTUP* and *EXTERNAL-QUIT-ON-EXIT*. LISPBUILDER-SDL iterates over these lists in calls to INIT-SDL, QUIT-SDL and WITH-INIT in order to initialize or uninitialize any external libraries such as LISPBUILDER-SDL-TTF. Loading LISPBUILDER-SDL-TTFAfter installation is complete, LISPBUILDER-SDL-TTF can be loaded by entering the following at the REPL; (ASDF:OPERATE 'ASDF:LOAD-OP :LISPBUILDER-SDL-TTF) Loading LISPBUILDER-SDL-TTF ExamplesEnter the following at the REPL to load the examples in the LISPBUILDER-SDL-TTF-EXAMPLES package; (asdf:operate 'asdf:load-op :lispbuilder-sdl-ttf-examples)
The following examples are contained in the package LISPBUILDER-SDL-TTF-EXAMPLES: (SDL-TTF-EXAMPLES:FONT-EXAMPLE) Using LISPBUILDER-SDL-TTFThe LISPBUILDER-SDL-TTF APIs have the same signature as the LISPBUILDER-TTF versions. See the LISPBUILDER-SDL API Reference for the APIs supported by LISPBUILDER-SDL-TTF. PrimitivesLISPBUILDER-SDL supports RECTANGLE and COLOR primitives. These are described below. RectangleA new rectangle is created by the function RECTANGLE. The function RECTANGLE-FROM-EDGES will create a new rectangle from the specified top left and bottom right coordinates. The function RECTANGLE-FROM-MIDPOINT-* will create a new rectangle from midpoint. The macros WITH-RECTANGLE and WITH-RECTANGLEs will create a new rectangle and free this rectangle when it goes out of scope. The rectangle position and width and height can be inspected and modified using the following functions: X, Y, WIDTH and HEIGHT. X2 and Y2 will return the (+ x width) and (+ y height) of the rectangle respectively. Additional functions that operate on rectangles are as follows:
ColorUse COLOR to create a new RGB or RGBA color. RGB/A color componets can be manipulated using the functions R, G, B, A, SET-COLOR and SET-COLOR-*. Compare colors using COLOR=. Functions that accept a color parameter will most likely bind color to *DEFAULT-COLOR* if unspecified. The macro WITH-COLOR will bind a color to *DEFAULT-COLOR* within the scope of this macro. When *DEFAULT-COLOR* is bound to a color, all subsequent drawing functions will use this implied color while it remains in scope. Additional functions that operate on colors are as follows: LISPBULDER-SDL also contains several predefined colors; DrawingLISPBUILDER-SDL provides low-level drawing support for several primitives. Most primitives can be drawn with or without alpha transparency. In addition, the filled primitives can be drawn with a single pixel outline (or stroke) that is a different color to the fill color.
Displaying TextLISPBUILDER-SDL can render text over a transparent background (Solid rendering), render text over a colored background (Shaded rendering), and anti-alias text into a transparent background (blended text). Text may be left, right or center justified. Text can be rendered using bitmaps fonts or Truetype fonts. The following table described the font capabilities of LISPBUILDER-SDL;
Shown above, LISPBUILDER-SDL is able to both render bitmap and Truetype fonts. The advantage when using only LISPBUILDER-SDL and optionally VECTO is that additional external foreign libraries are not needed. The advantage when LISPBUILDER-SDL-GFX and LISPBUILDER-SDL-TTF is speed. LISPBUILDER-SDL, LISPBUILDER-SDL-GFX and LISPBUILDER-SDL-TTF use the same text and font API function calls. VECTO uses its own API as described here. FontsInitializing fonts;
Setting the font typeface;
Drawing text to a surface;
Draw text to a new surface;
Blit a cached surface to the target surface;
Querying font attributes;
Freeing a font;
Font macros;
Bitmapped FontsLISPBUILDER-SDL supports two kinds of bitmap fonts; a "simple" font where the font data is created from a bitmapped image loaded from the drive, and a bitmap font having the font data used in SDL_gfx. The "simple" font is available using the *simple-font-4x5* font definition. The font looks as follows;
Bitmaps fonts support the SDL_gfx font format. The following bitmap font definitions are created at compile time and are always accessible in the LISPBUILDER-SDL library; *FONT-10X20*, *FONT-5X7*, *FONT-5X8*, *FONT-6X10*, *FONT-6X12*, *FONT-6X13*, *FONT-6X13B*, *FONT-6X13O*, *FONT-6X9*, *FONT-7X13*, *FONT-7X13B*, *FONT-7X13O*, *FONT-7X14*, *FONT-7X14B*, *FONT-8X13*, *FONT-8X13B*, *FONT-8X13O*, *FONT-8X8*, *FONT-9X15*, *FONT-9X15B*, *FONT-9X18* and *FONT-9X18B*. Truetype FontsSupport for Truetype fonts is provided by LISPBUILDER-SDL-TTF or VECTO. The VECTO documentation describes how to render text using this package. Once rendered, text is copied from a VECTO buffer into a LISPBUILDER-SDL SURFACE using VECTO->SURFACE. Font AttributesGlyph MetricsGET-GLYPH-METRIC returns the glyph metrics for the character CH, or NIL upon error.
For example; (SDL:GET-GLYPH-METRIC char :METRIC :MINX Font SizeGET-FONT-SIZE calculates and returns the resulting SIZE of the SURFACE that is required to render the TEXT, or NIL on error. No actual rendering is performed, however correct kerning is calculated for the actual width. The height returned is the same as returned using GET-FONT-HEIGHT.
Font StyleGET-FONT-STYLE returns the rendering style of the FONT. If no style is set then :STYLE-NORMAL is returned, or NIL upon error.
SET-FONT-STYLE sets the rendering STYLE of FONT. This will flush the internal cache of previously rendered glyphs, even if there is no change in style, so it may be best to check the current style using GET-FONT-STYLE before setting the style.
Node: Combining :STYLE-UNDERLINE with anything can cause a segfault, other combinations may also do this. Font HeightGET-FONT-HEIGHT returns the maximum pixel height of all glyphs of FONT. Use this height for rendering text as close together vertically as possible, though adding at least one pixel height to it will space it so they can't touch. Remember that LISPBUILDER-SDL doesn't handle multi-line printing so you are responsible for line spacing, see GET-FONT-LINE-SKIP as well.
Font AscentGET-FONT-ASCENT returns the maximum pixel ascent of all glyphs of FONT. This can also be interpreted as the distance from the top of the font to the baseline. It could be used when drawing an individual glyph relative to a top point, by combining it with the glyph's :MAXY metric to resolve the top of the rectangle used when blitting the glyph on the screen.
Font DescentGET-FONT-DESCENT returns the maximum pixel descent of all glyphs of FONT. This can also be interpreted as the distance from the baseline to the bottom of the font. It could be used when drawing an individual glyph relative to a bottom point, by combining it with the glyph's :MAXY metric to resolve the top of the rectangle used when blitting the glyph on the screen.
Font Line SkipGET-FONT-LINE-SKIP returns the recommended pixel height of a rendered line of text of the FONT. This is usually larger than the GET-FONT-HEIGHT of the font.
Font FacesGET-FONT-FACES returns the number of faces 'sub-fonts' available in the FONT. This is a count of the number of specific fonts (based on size and style and other typographical features perhaps) contained in the font itself. It seems to be a useless fact to know, since it cannot be applied in any other font functions.
Font Fixed Width'FONT-FIXED-WIDTH-P returns T if the font face is of a fixed width, or NIL` otherwise. Fixed width fonts are mono-space, meaning every character that exists in the font is the same width.
Font Face Family NameGET-FONT-FACE-FAMILY-NAME returns the current font face family name of FONT or NIL if the information is unavailable.
Font Face Style NameGET-FONT-FACE-STYLE-NAME returns the current font face style name of FONT, or NIL if the information is unavailable.
Free Cached SurfaceFREE-CACHED-SURFACE will free the FONT cached surface created in a RENDER-STRING call. Free FontFREE will all resources allocated for the FONT including any cached surface. Font InitializationA font must be initialized prior to use using INITIALISE-DEFAULT-FONT, or INITIALISE-FONT. These functions accept a font definition and return a font object or NIL if the font cannot be initialized. INITIALISE-DEFAULT-FONT will use *font-8x8* if a definition is unspecified. INITIALISE-DEFAULT-FONT binds the new font to *DEFAULT-FONT* to be used for subsequent font rendering or drawing operations. initialise-font font-definition => result Resources allocated to a font are freed by FREE. Font DefinitionINITIALISE-DEFAULT-FONT and INITIALISE-FONT accept a font definition and return a font object. A font definition is specific to the type of font. To load a Truetype font from disk, first create a Truetype font definition as follows; (make-instance 'SDL:ttf-font-definition
:size 32
:filename (merge-pathnames "Vera.ttf" *default-font-path*))Then pass this font definition to INITIALISE-DEFAULT-FONT, or INITIALISE-FONT to create the font object. (defparameter *vera-ttf* (make-instance 'SDL:ttf-font-definition
:size 32
:filename (merge-pathnames "Vera.ttf" *default-font-path*))
(unless (SDL:INITIALIZE-DEFAULT-FONT *vera-ttf*)
(error "Cannot create font."))To create a bitmap font definition, where :DATA is the same format of the fonts used in SDL_gfx; (make-instance 'SDL:bitmap-font-definition
:width 6
:height 9
:data #(...)))To create a simple font definition; (make-instance 'sdl:simple-font-definition
:width 4 :height 5
:character-map "abcdefghijklmnopqrstuvwxyz:'!?_-,.()#~0123456789"
:character-mask (loop for y from 0 below 1
append (loop for x from 0 below 48
collect (list (* x 4) (* y 5) 4 5)))
:color-key (sdl:color :r 99 :g 0 :b 0)
:filename (sdl:create-path "font.bmp" sdl:*default-asset-path*))Rendering TextLISPBUILDER-SDL supports the rendering of colored text over a transparent background (Solid rendering), and the rendering of colored text over a solid colored background (Shaded rendering). Text may be left, right or center justified. Text is drawn to a target surface using the DRAW-STRING functions, or rendered to a new surface of character height and string width using the RENDER-STRING functions. the RENDER-STRING functions can optionally cache the new surface in the font object. A cached surface can be accessed using CACHED-SURFACE) and can be blitted to a target surface using DRAW-FONT. The following functions draw text directly to a surface;
The following functions will draw text to a new surface, and optionally cache that surface in the FONT object;
The following functions will blit the cached surface to the target surface;
The following methods also apply to fonts; X, Y, WIDTH, HEIGHT, returns the width of the cached SURFACE. The font rendering functions accept a font object which is bound to *DEFAULT-FONT* when unspecified. The function INITIALISE-DEFAULT-FONT and the macros WITH-FONT and WITH-DEFAULT-FONT will bind a font to *DEFAULT-FONT*. All font drawing or font rendering functions that take a &KEYword or &OPTIONAL :FONT argument will use the font assigned to *DEFAULT-FONT* unless another font is specified. This makes calling these functions a bit easier as the :FONT parameter need not be explicitly passed. For example: (WITH-COLOR (*WHITE*)
(WITH-FONT (*FONT-8x8*)
(DRAW-STRING-SOLID-* "Font is 8x8." 10 10)
(WITH-FONT (*FONT-10x20*)
(DRAW-STRING-SOLID-* "Font is 10x20." 10 20))
(DRAW-STRING-SOLID-* "Font is 8x8 again." 10 40)))Note the difference between WITH-FONT and WITH-DEFAULT-FONT.
Rendering Solid Textdraw-string-solid string p1 &key justify surface font color => result draw-string-solid-* string x y &key justify surface font color => result Renders the STRING using FONT with text COLOR to the target SURFACE. The surface background is transparent and therefore can be keyed over other surfaces. Use :CACHE T to cache the new surface in the FONT object. When :FREE T any existing cached surface in FONT is automatically freed. When :FREE NIL the caller is responsible for freeing any existing cached surface in FONT. Any cached surface can be accessed using CACHED-SURFACE and can be blitted to a target surface using DRAW-FONT. For example; (SDL:DRAW-STRING-SOLID "Hello World!" :COLOR SDL:*WHITE*) Renders the STRING using FONT with text COLOR to a new SURFACE. The surface background is transparent and therefore can be keyed over other surfaces. The dimensions of the new surface are FONT height and width x STRING length. The surface background is transparent and can be keyed over other surfaces. render-string-solid string &key font color free cache => result Shadeddraw-string-shaded string p1 fg-color bg-color &key justify surface font => result draw-string-shaded-* string x y fg-color bg-color &key justify surface font => result Draw STRING using FONT with foreground color FG-COLOR and background color BG-COLOR to the target SURFACE. FONT is bound to *DEFAULT-FONT* if unspecified. Use :CACHE T to cache the new surface in the FONT object. When :FREE T any existing cached surface in FONT is automatically freed. For example; (SDL:DRAW-STRING-SHADED "Hello World!" SDL:*WHITE* SDL:*BLUE*) render-string-shaded string fg-color bg-color &key font free cache => result Draw STRING using FONT with foreground color FG-COLOR and background color BG-COLOR to a new SURFACE. FONT is bound to *DEFAULT-FONT* if unspecified. The dimensions of the new surface are FONT height and width x STRING length. The surface background is transparent and can be keyed over other surfaces. Blendeddraw-string-blended string p1 &key justify surface font color => result draw-string-blended-* string x y &key justify surface font color => result Draw the anti-aliased STRING using FONT with text COLOR to the target SURFACE. FONT is bound to *DEFAULT-FONT* if unspecified. The surface background is transparent and can be keyed over other surfaces. Use :CACHE T to cache the new surface in the FONT object. When :FREE T any existing cached surface in FONT is automatically freed. When :FREE NIL the caller is responsible for freeing any existing cached surface in FONT. Any cached surface can be accessed using CACHED-SURFACE and can be blitted to a target surface using DRAW-FONT. For example; (SDL:DRAW-STRING-BLENDED "Hello World!" :COLOR SDL:*WHITE*) Draw the anti-aliased STRING using FONT with text COLOR to the target SURFACE. The dimensions of the new surface are FONT height and width x STRING length. The surface background is transparent and can be keyed over other surfaces. SurfacesOverview of SurfacesAn SDL surface, SURFACE, represents an area of graphical memory that can be drawn or rendered to, for example the video framebuffer or an image that is loaded from disk. Creating SurfacesA surface is created:
Functions that accept a surface parameter will most likely bind surface to *DEFAULT-SURFACE* when unspecified. The macros WITH-SURFACE and WITH-SURFACES will bind a *DEFAULT-SURFACE* within the scope of these macro. *DEFAULT-SURFACE* is bound to a surface, all subsequent drawing functions will use this implied surface while it remains in scope. A surface can be explicitely freed by calling FREE-SURFACE. Video Memory Or System MemoryA surface in system memory (software surface) improves the performance of pixel level access but is incompatible with some types of hardware blitting. A surface in video memory (hardware surface) takes advantage of Video-to-Video blits which are often hardware accelerated. A hardware surface (SDL-HW-SURFACE) must be locked prior to performing per-pixel manipulations. When locked the hardware surface is copied from video memory to system memory and copied back when unlocked. This can cause a major performance hit. Use software surfaces (SDL-SW-SURFACE) to perform per-pixel manipulations, or to blit surfaces having alpha channels at a high frame rate when alpha blitting is not supported in hardware. Use hardware surfaces when per-pixel manipulations are not required, or when the graphics hardware supports blitting of surfaces having alpha channels, or when the surfaces can be stored in video memory. Many of the drawing functions require per-pixel manipulation of the destination surface. If drawing directly to the display surface then the display surface must be a software surface (in system memory) or performance will be effected. Drawing functions that require per-pixel manipulation of a destination surface include; DRAW-BEZIER, DRAW-CURVE, DRAW-SHAPE, DRAW-LINE, DRAW-PIXEL, READ-PIXEL, DRAW-FILLED-CIRCLE, DRAW-CIRCLE, DRAW-FILLED-CIRCLE, DRAW-TRIGON, DRAW-POLYGON, DRAW-ELLIPSE, DRAW-FILLED-ELLIPSE, DRAW-PIE, DRAW-FILLED-PIE, DRAW-FILLED-TRIGON, and DRAW-FILLED-POLYGON. To efficiently use the drawing functions with hardware surfaces, render to a software surface and then blit the software surface to a hardware surface using BLIT-SURFACE. ImagesBMP images are loaded from disk using LOAD-IMAGE. Support for additional image formats is provided in LISPBUILDER-SDL-IMAGE. A surface is saved to disk as a BMP image using SAVE-IMAGE (DRAW-SURFACE (LOAD-IMAGE "sdl.bmp")
:surface *default-display*)Positioning SurfacesA SURFACE stores its own position X/Y coordinates. These coordinates can be inspected and modified using the following functions: X, Y, WIDTH and HEIGHT. Additional functions and macros that manage surface coordinates are as follows:
Drawing & Blitting SurfacesThe functions BLIT-SURFACE and DRAW-SURFACE will blit or draw a source surface onto a destination surface using the position coordinates stored in the source surface.DRAW-SURFACE-AT will draw the source surface at a specified position. The composition rules that determine how the source surface is composed over the destination surface are described in the description of BLIT-SURFACE. FILL-SURFACE fill will the target surface with a single color. Drawing Primitives describes how *DEFAULT-SURFACE* is used when calling blitting operations. Use SET-COLOR-KEY, CLEAR-COLOR-KEY and SET-ALPHA to modify the key color and alpha transparency properties of a surface after the surface has been created. Set a clipping rectangle to limit the draw area on a destination surface. The clipping rectangle for a surface is manipulated using GET-CLIP-RECT and SET-CLIP-RECT. When this surface is the destination of a blit, only the area within the clip rectangle will be drawn into. Set a cell rectangle to limit the surface area on the source surface. The cell rectangle for a surface is manipulated using CLEAR-CELL and SET-CELL. When this surface is the source of a blit, only the areas within the cell rectangle will be used. The cell is useful when only a small area of the source surface needs to be blitted to the destination surface. For example a sequence of images composed into a single sprite sheet and only the current frame of animation is to be drawn to the display at any one time.
Alpha Blending and Color KeyingLISPBUILDER-SDL supports per-pixel alpha blending, per-surface alpha blending and color keying.
A surface contains red, green, blue (RGB), and optional alpha (RGBA) component, a surface alpha value, and an optional color key. A surface must have per-surface alpha enabled for per-surface alpha blending to be perfomed. Per-surface alpha is enabled and set using :ALPHA at time of surface creation, or enabled or disabled using ALPHA-ENABLED-P for existing surfaces. The per-surface alpha value is set using :ALPHA at time of surface creation, or ALPHA for existing surfaces. A surface must have both an alpha component (RGBA) and per-surface alpha must be enabled for per-pixel alpha blending to be performed. The alpha channel can only be added to a surface at time of surface creation, using :PIXEL-ALPHA. A RGB surface can be copied to a new RGBA surface using CONVERT-SURFACE, or CONVERT-TO-DISPLAY-FORMAT. A surface must have color keying enabled for color keying to be perfomed. The color key is enabled and set using :COLOR-KEY at time of surface creation, or enabled or disabled using COLOR-KEY-ENABLED-P for existing surfaces. The color key is set using COLOR-KEY for existing surfaces. Per-pixel and per-surface alpha blending are mutually exclusive with per-pixel alpha having priority over per-surface alpha, as described above and show in the table below. Per-surface alpha blending is only performed when the source source does not have an alpha channel (RGB). If the source surface has an alpha channel (RGBA) and per-surface alpha is enabled then per-pixel alpha is performed. The display will typically be an RGBA surface, so blitting a surface to the display will follow the rules defined in #3, #4 and #5 in the table below. The alpha channel and per-surface alpha of the source surface and not the destination surface are used when performing alpha blending. The following table summaries how per-pixel, per surface and color key settings are used during blitting;
Alpha blitting can be accelerated using :RLE-ACCEL. NOTE: SDL:LOAD-IMAGE will load an image having an alpha channel however the surface returned by LOAD-IMAGE is RGB only. To load the alpha channel of an image (an RGBA surface), use SDL-GFX:LOAD-IMAGE in LISPBUILDER-SDL-GFX. A workaround used in SDL-EXAMPLES:PARTICLES and show below is to use an image processor to save the alpha channel of the image as the red channel of a new 'alpha-image'. Then overwrite the (A) alpha channel of the image using the (R) channel of the 'alpha-image'. ;; Load the main image, converting the RGB surface into an RGBA surface.
(setf *particle-img* (sdl::convert-to-display-format :surface (sdl:load-image "particle.bmp"))
:inherit nil
:pixel-alpha t
:free t))
;; Then replace the alpha channel of *particle-img* with
;; the Red channel that contains the alpha channel pixel data
(sdl:with-surface (alpha (sdl:load-image "particle-alpha.bmp"))
(sdl:copy-channel-to-alpha *particle-img* alpha :channel :r))Updating Surfaces and the DisplayThe functions UPDATE-DISPLAY and UPDATE-SURFACE update the SDL display surface (or OpenGL context) and general SDL surfaces respectively. The CursorThe the current state of the cursor is returned using QUERY-CURSOR, and is set using SHOW-CURSOR. A Simple ExamplePutting this all together, we can write short example showing a white rectangle that follows the users mouse movements within an SDL Window. Exit the example by closing the window or pressing the Esc key on the keyboard. (WITH-INIT () ; Initialise SDL and the Video subsystem
(WINDOW 320 240) ; Open a window, 320 x 240
(SETF (FRAME-RATE) 30) ; Lock the frame rate to 30 fps
(WITH-EVENTS ()
(:QUIT-EVENT () T) ; Absolutely have to handle the :QUIT-EVENT,
; or the application will not exit.
(:KEY-DOWN-EVENT ()
(WHEN (KEY-DOWN-P :SDL-KEY-ESCAPE) (PUSH-QUIT-EVENT)))
(:MOUSE-MOTION-EVENT (:X mouse-x :Y mouse-y)
(CLEAR-DISPLAY *black*)
;; Draw the box with center at the mouse x/y coordinates.
(DRAW-BOX-* (- mouse-x (/ 50 2)) (- mouse-y (/ 50 2)) 50 50 :color *white*))
(:IDLE ()
(UPDATE-DISPLAY))))Object Lifecycles and Garbage CollectionTBD Package OverviewPackage ExportsFunctions and symbols exported from the LISPBUILDER-SDL package are accessible using the LISPBUILDER-SDL: prefix or the shorter form SDL: nickname. Package StructureThe cffi/ directory contains the raw CFFI bindings. These bindings may be automatically generated by SWIG or created by hand depending on the package. CFFI translation functions perform simple low-level conversions for example, converting Lisp strings to C strings and back (see `translate.lisp` for example). All functions in cffi/ accept and return foreign objects only. The base/ directory defines wrappers over the functions in cffi/. The functions in base/ accept keyword arguments and accept NIL instead of CFFI:NULL-POINTER where appropriate. Generally functions in base/ accept and return foreign objects. base/ may perform some type checks (IS-VALID-PTR SURFACE) but this layer is meant to be lean. Someone who implements a graphics engine might use this layer instead of sdl/ if speed is a concern. There are no fancy drawing primitives in this layer. The sdl/ directory defines the abstractions over cffi/ and base/. Foreign objects are passed around sdl/ neatly wrapped in CLOS objects, using `TRIVIAL-GARBAGE` for automatic garbage collection (minimize foreign objects being left on the heap). There are no functions in sdl/ that accept or return foreign objects (with the exception of the functions that create the CLOS wrapper objects). Functions in sdl/ call down into cffi/ and base/ as appropriate. All LISPBUILDER-SDL symbols available in SDL: are exported from sdl/, with symbols imported into sdl/ from cffi/ and base/ as appropriate (e.g. WITH-EVENTS). All drawing primitives are defined in this layer; circles, rectangles, lines, triangles, with-bezier etc. Functions in sdl/ implement a lot of type checking. An example of the difference between base/ and sdl/ is WITH-RECTANGLE. The WITH-RECTANGLE macro in base/ creates and destroys a foreign SDL_Rect. The WITH-RECTANGLE macro in sdl/ will create and destroy a RECTANGLE object. ExamplesEnter the following at the REPL to compile and load the examples included in the LISPBUILDER-SDL-EXAMPLES package: (asdf:operate 'asdf:load-op :lispbuilder-sdl-examples) Run the examples by entering any of the following at the REPL: (SDL-EXAMPLES:BEZIER) (SDL-EXAMPLES:BMP-SAMPLE) (SDL-EXAMPLES:CIRCLE-1) (SDL-EXAMPLES:CIRCLE-2) (SDL-EXAMPLES:CIRCLE-3) (SDL-EXAMPLES:CIRCLE-4) (SDL-EXAMPLES:CIRCLE-5) (SDL-EXAMPLES:DISTANCE-2D) (SDL-EXAMPLES:FLOOD-FILL) (SDL-EXAMPLES:INBUILT-FONTS) (SDL-EXAMPLES:LINE-DRAWING) (SDL-EXAMPLES:MANDELBROT) (SDL-EXAMPLES:METABALLS) (SDL-EXAMPLES:MOUSE-2D) (SDL-EXAMPLES:MOUSE-PAINTER) (SDL-EXAMPLES:PIXELS-1) (SDL-EXAMPLES:PIXELS-2) (SDL-EXAMPLES:PIXELS-3) (SDL-EXAMPLES:PIXELS-4) (SDL-EXAMPLES:POINTS-AND-LINES) (SDL-EXAMPLES:RANDOM-RECTS) (SDL-EXAMPLES:RANDOM-BOX-1) (SDL-EXAMPLES:RANDOM-BOX-2) (SDL-EXAMPLES:RANDOM-BOX-3) (SDL-EXAMPLES:RANDOM-BOX-4) (SDL-EXAMPLES:RECURSIVE-RECTS) (SDL-EXAMPLES:SETUP-AND-DRAW) (SDL-EXAMPLES:SIMPLE-FONT-DEMO) (SDL-EXAMPLES:STROKE) (SDL-EXAMPLES:VERTICES) (SDL-EXAMPLES:WIDTH-HEIGHT) Using OpenGLExample of using LISPBUILDER-SDL and CL-OPENGL (asdf:operate 'asdf:load-op :cl-opengl)
(defun opengl-test-1 ()
(sdl:with-init ()
(sdl:window 250 250
:title-caption "OpenGL Example"
:icon-caption "OpenGL Example"
:opengl t
:opengl-attributes '((:SDL-GL-DOUBLEBUFFER 1)))
(gl:clear-color 0 0 0 0)
;; Initialize viewing values.
(gl:matrix-mode :projection)
(gl:load-identity)
(gl:ortho 0 1 0 1 -1 1)
(sdl:with-events ()
(:quit-event () t)
(:idle ()
(gl:clear :color-buffer-bit)
;; Draw white polygon (rectangle) with corners at
;; (0.25, 0.25, 0.0) and (0.75, 0.75, 0.0).
(gl:color 1 1 1)
(gl:with-primitive :polygon
(gl:vertex 0.25 0.25 0)
(gl:vertex 0.75 0.25 0)
(gl:vertex 0.75 0.75 0)
(gl:vertex 0.25 0.75 0))
;; Start processing buffered OpenGL routines.
(gl:flush)
(sdl:update-display)))))Use the following function to set the OpenGL attributes after SDL has initialized the video subsystem, and before the window is created; (SDL:SET-GL-ATTRIBUTE <attribute> <value>) The following attributes are supported;
The following function provides access to OpenGL. Use after an OpenGL context is created. (setf cl-opengl-bindings:*gl-get-proc-address* #'sdl:sdl-gl-get-proc-address) Creating Standalone ExecutablesTutorials for creating standalone executables using the various Common Lisp development environments; |
Sign in to add a comment