Xgame: An X11 game/animation library, Version 0.95, 30 oct 1998. This version includes Xgame main module Mk10, Xgame timer module Mk2, Xzoom zoom function Mk1, keysym definition file Mk1, X-plode Mk8, Wormdemo Mk3, Scroll demo Mk1, Xcircuits Mk8, Cubes demo Mk2. The files in this package are Copyright (c) 1998 by B.W. van Schooten, all rights reserved. This software is distributed under a generic version of the Perl Artistic License. The software comes with no warranty whatsoever. See file 'artistic.license' for more details. Background Origin Xgame originally started as an experiment to see how well suited the X window system is for action games. As a software developer, I found X attractive because it is proven technology and has a large user base which is now quickly becoming even larger, thanks to the increased efforts that have lately been put in free Unices. X originally has the reputation of being very slow and unsuited for flashy-looking games or demos. Now, several computer generations and rivalling Wimp systems later, it actually turns out to be quite efficient in comparison. Oddly, there haven't been many games for the X platform (though it appears this is changing!). Since I'm quite a fan of games, especially action games of the 'classic' type, I decided to try and write some myself, evolving a suitable library fulfilling my needs along the way. Glorified vectrex machines Contrary to some of the older machines I was used to (Amiga or XT with CGA), I recently found out just how much faster vector (line) graphics are on modern Intel-based boxes, as compared to (transparent) bitmap graphics. This is apparently because of the very much reduced throughput through the slow PC buses, and the relative ease of the required processing. There is no source bitmap or mask, and vector graphics are relatively sparse, so there's very little to be put through the bus. In typical PC screen modes, pixels can simply be drawn by writing a single byte or word, so no bit slicing routines are needed. Gradient calculation is fast, because the CPU can do it in its code cache. Also, one cannot optimise drawing of typical lines by writing multiple memory-contiguous pixels at a time, so it doesn't pay decreasing the screen depth, like it does with bitmaps, so it doesn't really matter whether your vectors are in 8 or 24 bits colour. So, at last, the Vectrex has returned, and this time it has 16 million colours instead of just two! I've added support for faster vector graphics in the new version (you can turn off the background pixmap and use the new XgameDrawVecObj function), though the buffering is still done by copying bitmaps (which is slow) in order to prevent flickering. The new cubes demo illustrates this; compare it in speed to the worm demo. I plan to do a vector game in the near future as well. What is Xgame? Xgame is a small 'widget' or 'toolkit' that is supposed to make life easier for those who would like to try their hand at X programming and/or game/demo programming. It is meant to function under as many systems as possible, as a library which should be (almost) ready to plug in and use, and as an illustration of how to use Xlib or do computer animations. Note that it does NOT include Wimp stuff, like requesters, scrollbars and buttons. Xgame relies only on Xlib (the 'kernel' of the X window system) and the Xpm library (which is an unofficial but widespread standard library for handling XPM files). Xgame is (or should be) able to do the following things: - open windows on a display of arbitrary depth, and automatically handle each window's redraw requests - draw pixels, lines and polygons, and print text - load and draw (optionally transparent) colour bitmaps using the XPM library - provide a separate buffer for drawing animated objects (like sprites), and a separate buffer for background graphics, with support for scrolling - have dirty rectangle handling built in, which can be used for updating the screen and clearing the sprite buffer - keep track of the state of the keyboard in keymap and lastkey variables, and keep track of the mouse position and buttons. - automatically re-scale a window's coordinate system on opening by supplying the desired scaled x and y size. Xgame has been tested on: Solaris, FreeBSD (X86), NetBSD (X86), Linux (X86), XFree86, Solaris X server, Exceed, X-Win32, Accelerated X, 1-bit, 8-bit, 16-bit and 24-bit display depth. What has not been tested is whether the display update causes glitches/flickering on bitplane video systems (since some of the existing documentation reports irritating glitches on some X servers). One would expect that video systems with packed-pixel memory organisation won't easily flicker, because any pixel can be changed with a single write, while bitplane systems will, because a pixel update takes at least as many writes as the screen depth, and maybe more if the X server decides to write data on a by-plane basis. I have never seen any flicker during screen updates, but unfortunately, I haven't been able to find any bitplane system to test this code on. If you report any irritating glitches, I would be interested to hear from you. Changes with version 0.9 Bugfixes: The dirty rectanges generated for vector objects were too large and even partially on-screen. Now, off-screen objects are skipped completely. Keymap should be signed char, not char. XComposeStatus passed to XLookupString should be NULL. The keymap is now cleared on a changefocus event, so it won't get badly inconsistent. Interface changes: Deleted XgameClearSprite and ShowSprite. These two functions did not seem useful enough to warrant the amount of duplicated code they were starting to generate. The keyboard reading routines have been much improved. It is now possible to use special keys, like cursor keys, modifiers (like the shift key) and function keys. Modifiers used to make the keymap inconsistent. Now, both the shifted and unshifted keysyms are always activated in the keymap. Instead of strings, the lastkey* variables now contain the XgameKeySym values. There are now separate lastkey* variables for the unshifted, shifted, and actual key symbols corresponding to the last key events. The new files keysyms.c and .h now contain Xgame's keysym definitions and X-to-Xgame remappings. XgameDrawText has been improved: it now returns the position of the first pixel right of the printed text. This allows printing a line of text in separate substrings, and placing a cursor at the end of a line. Also, it now calculates the exact metrics of the printed text instead of safe margins. Mouse support. It is now possible to read the mouse buttons and position. Off-screen positions are not notified, except when a button is being held (this appears to be a property of the X server). Full-screen support: root window mode (for convenience, vroot.h, Copyright by Andreas Stolcke, has been included in the distribution). Some window managers won't give focus to root. This is worked around by setting the focus on a mouse enter event. It is not possible to read the mouse buttons when using the root. Off-screen window border mode (this does not have the disadvantages of root mode). Automatic scaling support: there are two extra parameters to XgameOpenWindow, where you can supply the scaled x and y size. Everything (pixmaps, fonts, mouse position, drawing coordinates) is scaled transparently to fit the given x and y size. However, it's not (very well) possible to select a font with a non-square aspect ratio. When scaling changes the aspect ratio, the font is now chosen according to minimum of the x and y scale factor. The new file xzoom.c contains a stand-alone pixmap zooming function. Changes to other programs: Minor maintenance of all programs to fit the new interface. Programs now have extra parameters to select scaling and full-screen support. X-plode has one more enemy type, and 13 more levels. New animated-fractal demo fastifs.c. Changes with version 0.8 Bugfixes: Better error handling and recovery, especially for sprites and fonts. Two of the shorthand text alignments were wrongly defined. Margins used for fonts were wrong: sometimes, the dirty rect would not quite enclose the text drawn. No safe margin of topleft of the rect around a line in some cases. Interface changes: XgameOpenWindow has some extra parameters. These allow for multiple fonts, custom pen colours, autorepeat on/off, colour choice for B&W screens, background on/off. Sprites loaded without hotspot now have their default center at topleft (this is what is used most often). The lists of names you supply should now be terminated by an empty string. struct XgameDirtyRectList has a new public member, nonotify, which disables all dirty rect notification for that list (except those generated by expose events). Turning off the background causes all functions that would operate on it to be ignored, and speeds up flushing the bgrectlist. The background is assumed to be a single colour (the first colour in the window's colour list), which can be changed by a new function XgameChangeBGCol. A couple of superfluous functions (XgameDrawTile and XgameDrawText) have been deleted. XgameDrawFText is renamed to XgameDrawText. XgameDrawText has changed parameters; there is an extra parameter fontnr. lastkey also registered keyreleases, while usually one would only need, and in fact expect, keypresses here. Now, the registering of the last key has been split into lastkeyevent (which has exactly the same behaviour as lastkey had previously). There are now two extra char members in the window struct: lastkeyp (last keypress) lastkeyr (last keyrelease). The macro symbols have been renamed to X standard names, starting with Xgame... There is a new function XgameDrawVecObj, which allows for quick&easy (2-D) vector drawing. Changes in other programs: There is a new cubes demo, illustrating the use of vector graphics. Based on a patch by Bill Adams (bill@evil.nwpacifica.net) X-plode now has a high score, level skip, and a game-over screen. Bugfix: the player was immune to rockets! No more... You now get 25 points for rockets and remaining bombs. Changes with version 0.7 I've put most of my efforts in support for scrolling. It works now, but it's still a bit slow, though I already have some ideas on how to speed it up by a factor of 1.5-2. The parameter order of some functions has changed to improve the interface consistency. There is a new text draw function XgameDrawFText (draw flushed text), a replacement of XgameDrawCText (draw centered text) which is more flexible. X-Plode is nearer to completion, and a new game Xcircuits has been added (it's a port of a very old game I never finished. Xgame actually evolved from the graphics routines found in the original version). Future plans This is version 0.95, and there's still some things I'd like to add/do before/when I dare call it 1.0. - better window feature handling. Currently, the window is called 'Untitled' and can be resized even though the program doesn't adapt when you do. Also, it cannot be re-parented. - speed up the scrolling algorithm. This should be possible with only few or no changes in the interface. - restoring of the function XgameSpriteFromBitmapFile, in case I can get hold of the missing Xlib function (see source). - make it available as a package/library/widget in some distribution(s). This also includes making proper man pages. - mouse cursor shape control. At the very least, one should be able to turn the mouse cursor off. - (by request) make it possible to define a sprite as a smaller area out of a bigger pixmap, so you can have all sprites in one big pixmap. More ambitious plans include: - adding a sound module, which can play samples and music (like MOD or MIDI) - support for directly-manipulable images (XImages) - adding higher-level modules, like a game-object engine with built-in sprite collision, a 3D-isometric engine, a (simple) Wimp module. How to compile, install, and run This version consists of the following files: xgame.h - definition and explanation of Xgame routines and data structures xgame.c - implementation of Xgame routines xzoom.c and .h - pixmap zoom function used by xgame.c keysyms.h - contains all keysyms supported by Xgame keysyms.c - contains table for remapping `alternative' keysyms to standard Xgame keysyms vroot.h - (Copyright (C) by Andreas Stolcke) a standard program for handling virtual root windows. See source for full license timer.c and .h - a separate module with some handy timer functions stdargs.c and .h - parses window settings from command line for example applications stdcolor.c - some colour definitions used by x-plode.c The package comes with some example programs: cubes (cubes.c) - 3D animated cubes wormdemo (worm.c, XPMs are in xpms-worm) - 3D flying worm demo scroll (scroll.c, XPMs in xpms-scroll) - An illustration of scrolling x-plode (x-plode.c, XPMs in xpms-x-plode) - A blow-em-up game (unfinished) xcircuits(xcircuits.c, XPMs: xpms-xcircuits)- A Zenji clone (unfinished) fastifs (fastifs.c) - animated IFS fractal For the time being, Xgame is not installed as a library, but is simply linked statically with the example programs. In order to use it for your own purposes, it may be easiest just to put the source or object files in your own source tree (it's less than 100k source). This way, you ensure you always have the right version (some of Xgame's programming interface may still change), and easily make your own hacks in it. I hope to have a more library-like setup when I get to version 1.0. To compile the programs, type: xmkmf (convert the Imakefile to a Makefile) make (compile and link) The executables have names as given above. All require the path to their XPM files to be known. Instead of using an environment variable, a run control file, or compile-time configuration, the path is supplied as the fourth command line parameter (the first three are window parameters). This way, you can install them easily by putting a small batch file in your bin directory which calls the executable and supplies the directory to the XPMs. Contacting the author The current Xgame homepage can be found on http://www.geocities.com/TimesSquare/Alley/3583/Xindex.html http://wwwhome.cs.utwente.nl/~schooten/software/vic-20/Xindex.html If you have any questions, suggestions or bugfixes, you can contact me at vicman@dds.nl Programmer's manual The software was made so as to speak for itself: functions, structures, and macros are documented in the header files (xgame.h, timer.h, and keysyms.h), their implementation considerations are documented in the source files, and the example programs are meant to illustrate their use in some concrete situations. What is still to be explained, however, are the main principles behind the code. Drawing fluid animations Principles The library encourages the programmer to make extensive use of temporary buffers and buffer copying to prevent screen flickering during animation and to make animating sprites over a static or scrolling background easy. The idea is to use the following basic procedures to display one frame of animation: (I) Drawing on the screen without flicker: 1. Draw whatever you want to display on a separate buffer. 2. Copy the buffer to the screen. If you would have drawn directly to the screen, you might be caught by the video beam in the act of drawing, and your unfinished drawing would be displayed on screen, which may look terribly flickery/glitchy, depending on how you have chosen to draw it. It is sometimes possible but usually not practical to choose a drawing method that causes only little flicker when done directly on the screen. Otherwise, a buffering method, like (I), is needed. (II) Drawing animated sprites on any background: 1. (If necessary) make any changes to the background. 2. Copy the background to your drawing area. 3. Draw the sprites on the drawing area. Note that this drawing method would cause flickering sprites when the drawing area would have been the screen, since all old sprites are cleared away completely before any new sprites are drawn. So, this method requires method (I) as well. Dirty rectangle updating in Xgame It's obviously a lot of waste copying two complete screenfuls just to display a few small sprites. It is much more efficient to copy only what has changed since the last copy. This requires the program to keep track of any changes it has made. Xgame does this automatically: any drawing function determines the smallest rectangular area that contains the object drawn, and puts this area in a list (a XgameDirtyRectList struct), using XgameAddRect or XgameAddBGRects. Copying now amounts to going through this list, copying only the areas that are listed in it. This method is called 'dirty rectangle' updating. Dirty rectangle notification can be turned off by setting the nonotify switch in the appropriate DirtyRectList structure. All operations that would have added entries to that list will no longer do that. It is then the responsibility of the programmer to make sure the proper areas will be copied. This can be done using the functions XgameClearArea, XgameClearSprite, XgameShowArea and XgameShowSprite. This feature may be useful for special cases where the Xgame dirty rectangle system is not desirable, for example, partial-screen scrolling (like in Defender), status displays which are not drawn on the background but which should be retained, and specific graphics which should be updated with a higher or lower frame rate than others. Doing animations in Xgame Xgame has 2 bitmap buffers, called the background and the buffer. The background corresponds to the background in (II). The buffer corresponds to the buffer in (I) and the drawing area in (II). The dirty rectangle lists used for the copying as found in resp. (I) and (II) are resp. bufrectlist and bgrectlist in the XgameWindow struct. Either copy operation can be done by calling XgameFlushRectList, supplying the desired rectlist as the second argument. The background can also be turned off, in which case all functions that operate on the background are ignored, and the program acts as if the background were a single colour. The colour of the background is initially the first colour in the window's colour list, and can be changed afterwards using XgameChangeBGCol. The drawtobuffer switch in struct XgameWindow can be used to control the destination of the drawing routines. If it is 0, everything is drawn only on the background, otherwise everything is drawn only on the buffer. ------------+ / / / / Screen / / +------------ ^ XgameFlushRectList(windowhandle, bufrectlist) ----|-------+ / | / / / Buffer / / +------------ ^ XgameFlushRectList(windowhandle, bgrectlist) ----|-------+ / | / / / Background / / +------------ Scrolling in Xgame In Xgame, it is possible to create a background that is larger than the screen, and determine which part of the background is shown on screen using the panning function XgameChangeBgOfs. Scrolling can be seen as one of the possible ways to fill in step 1 of algorithm (II). There are several ways in which you can use the panning mechanism for scrolling. The simplest (but also the most memory-expensive) is to make the background as big as the whole playing area within which to scroll. Now, you can choose which part of the background to show on screen by setting the offset of the topleft corner of the screen using XgameChangeBGOfs (see picture below). X offset | V +---------------------+ | background| Y offset -> | +------+ | | |screen| | | | | | | +------+ | | | | | +---------------------+ Unfortunately, this method costs a *lot* of memory, unless the background area is small. An alternative is to make the background about the same size as the screen, and draw bits of background on demand, as required by the amount of pixels you want to scroll in a certain direction. For example, if you want to scroll one pixel to the left, you draw one column of pixels on that part of the background that is just past the left edge of the screen, and then pan the screen one pixel to the left. In order to enable this method to continue scrolling in one direction indefinitely, the background wraps around at its edges (see picture below). . . . . . . | | | ...---+----------+----------+---... |background| | | | | | +----+---+ | | | screen | | ...---+-----+----+---+------+---... | | | | | | +----+---+ | | | | | | | ...---+----------+----------+---... | | | . . . . . . However, it is often hard or inefficient to draw columns or rows of any given number of pixels, as required by the method explained above, because it usually does not correspond to the form of the data you want to draw on the background. Usually, a background is defined as a grid of tiles, each tile being a pixmap of a fixed size (for example, 16x16 or 32x32). In our previous example of scrolling left one pixel, this would require drawing only one thin vertical slice of pixels out of each tile found in the column just past the leftmost part of the screen. This would require a lot of small drawing operations, and possibly a lot of administrative overhead. A way around this problem is to make the background slightly bigger than the screen, and use the now-available off-screen margins as a staging-area to prepare your data just before it is needed. An implementation of this method in the case of a tile-based map is found in the example program scroll.c. Note that XgameChangeBGOfs actually marks the whole buffer as dirty, which means that scrolling requires the whole background to be copied to the buffer (and naturally, the whole buffer to be copied to the screen). In future versions, I will try to minimize the former copy operation to only a few margins (this would cause a speed increase of nearly a factor 2), but I am still thinking of the most elegant way to implement this. In case you are drawing a lot of sprites on the buffer anyway, this optimisation will make less difference. Timing and frame skipping Principles In my own opinion, drawing 60 or 70 frames per second looks best by far, even though many modern games tend to run at lower frame rates, like 35 fps or less. Usually, these games actually run at 70 fps, but as soon as they find out their frames start running behind on schedule, they start skipping the actual drawing routines while the internal position updates still continue (frame skipping). Because the actual drawing is often by far the most CPU-expensive part of updating a frame, the program is able to get back on schedule by means of frame skipping. This way, the game speed remains constant, while the actual frame rate adapts itself automatically to the speed of the machine. Note that frame skipping is applicable if and only if everything is redrawn every frame, otherwise a skip may actually affect what will be displayed on future frames. So, operations done to the drawing area in method (II) can always be skipped, but things drawn incrementally, for example, the occasional updating of a relatively stationary background, cannot be skipped. Using checktimeelapsed The package provides a separate timer routine (checktimeelapsed in timer.c) which is meant just for this purpose. You can set up a wait loop in which you call checktimeelapsed, supplying as parameter the time (in microseconds) that a frame is supposed to take. It returns a 0 as long as there is still time left to kill. In this case, stay in the wait loop. After enough time has elapsed, it returns a 1, and decrements its internal timer. Now, you can exit the loop, draw your frame, and return to the wait loop again. If you start running behind with drawing frames, checktimeelapsed behaves the same as usual, but will return 2 instead of 1, indicating that you should start frame skipping or saving time in other ways until you are back on schedule again. If you start lagging behind *very* much (or don't call the function for awhile) it will reset its internal timer so the frame skipping will not get out of hand. Keeping the OS happy: usleep() and the sleepallowed flag Spending so much time in a wait loop is obviously a waste of CPU, and not very friendly to other processes either. In fact, the OS will start scheduling out your process pre-emptively when it takes up too much CPU, sometimes in a very coarse time grain (about once a second), causing jerky animation. It would be preferable if you could tell the OS when you'd like to be scheduled out. This is what checktimeelapsed's sleepallowed flag is for. If you set the flag to a value other than 0, it will call usleep() whenever it decides it is safe to do so. frame skipping in Xgame The XgameWindow structure contains the skipdraw switch, which, when set to any other value than 0, causes all draw functions to skip completely. Note that the dirty rectangle copy function XgameFlushRect is NOT affected. If you want to skip a frame, you can simply set this switch at the beginning of your sprite draws, automatically skipping them without needing any conditional statements in your code. Note that you still need to skip the appropriate dirty rectangle copies, and see if you may or may not skip anything drawn on the background. Keeping in touch with events The event-polling function XgameHandleEvents() There are a number of events you'll want to keep track of, in particular keyboard events, mouse events, and expose events. This is done by XgameHandleEvents. The function first waits until all the draw requests since the last call are actually drawn by the X server, and then processes any input events that occurred since. The variables keymap, lastkeyp, lastkeyr, and lastkeyevent in the XgameWindow struct can be used to read the keyboard state. The variables mousex, mousey, and mousebtn can be used to read the mouse state. Expose events Expose events are redraw requests, which are generated as soon as some portion of your window was garbled in some way and needs to be refreshed. In Xgame, the areas that need to be redrawn are stored in the buffer's dirty rectangle list, and will be redrawn only if that list is flushed. This means that you have to call XgameHandleEvents and flush the list periodically to keep your window neat. Entering and leaving focus and mouse pointer Some special things happen here: when your Xgame window obtains or loses the focus, the keymap is cleared to make sure it doesn't get inconsistent. When the mouse pointer leaves the window, the mouse variables are no longer being updated until it reenters, except when one of the buttons is being held down. Screen space usage in Xgame Two new features have been added, which allow applications to have more control over the usage of screen space. Scaling The automatic scaling feature enables the application to rescale each window, so that it can be run comfortably on screen resolutions other than the one it was designed for, without any extra effort from the programmer. Scaling is done very easily: just supply the size you want the window to be as XgameOpenWindow's scaledxsize and scaledysize parameters, while keeping the xsize and ysize parameters as usual. Everything (pixmaps, fonts, mouse position, drawing coordinates) is scaled transparently. However, some things are not quite perfect: (1) Since it's not very well possible to select a font with a non-square aspect ratio, fonts always have square aspect, with sizes chosen according to the smallest of the x and y scale factors. Even though fonts are not scaled perfectly this way, they are guaranteed to stay within the area they normally would. (2) The scaled pixmaps are not anti-aliased, which means non-integer scaling factors may result in ugly pixmaps. The pixmaps look best at scaling factors such as 0.5, 0.75, 1.25, 1.5, or 2.0. Remember that, when scaling down, any text contained in pixmaps may become unreadable. If the text is important, it is better to use fonts instead. Taking over the screen Especially with video games and demos, it is sometimes desirable to be able to take over the whole screen, and not have any window borders or other windows that spoil the effect. In Xgame, there are two ways to do this. (1) Using the root window instead of a regular window. This can be done by opening a window with the flag XgameWinSetRoot set. The drawing area, as given by scaledxsize and scaledysize, may be smaller or bigger than the root window, but is always centered. Any borders around the drawing area are drawn using the standard root window tile. There are some drawbacks to using the root: the root is typically a shared resource. This means that: (a) Other programs may be drawing on the root at the same time. This should however not cause any problems except for messy graphics. It's also possible to run multiple instances of an Xgame program on the root. (b) It is generally not possible to read the mouse buttons, because, for any window, the button events are process-exclusive, and the root's button events are usually taken by the window manager. (c) There are typically some things the window manager always draws on top of the root, like icons. (2) Opening a screen-sized window, and placing the window borders just outside the screen borders. This can be done by opening a window with the flag XgameWinSetFullScreen set. Like (1), the drawing area is centered, but the colour used for borders is the first colour in the colour list. This method does not have the disadvantages of using the root window, but it is an `aggressive' way of using screen resources, and may be considered offensive by some users. Remember that it is not possible to move or close a full-screen window in the regular manner, and it may be possible the window can't be closed at all except by exiting the program. So, it should be made very clear how the program can be exited as soon as it is started, or the full-screen mode should be an option which is off by default.