Allegro Vivace ************** Copyright (C) 1997-1999 George Foot Permission is granted to distribute this documentation verbatim in any form without restriction and to distribute modified copies provided it is stated that modifications have been made. This is the seventh draft. The Texinfo source was updated on 26 August 1999 and built on 25 August 2000. Table of Contents ***************** - Legal issues - Distribution - Disclaimer 1. Introduction 1.1 About the tutorial 1.2 Aim 1.3 Target audience 1.4 Requirements 1.5 Before we start... 2. Getting, installing and using Allegro 2.1 What is Allegro? 2.2 Where to find Allegro 2.3 How to install Allegro 2.4 Testing the installation and building the Vivace example programs 2.5 Using Allegro 3. A basic game structure 3.1 What does a game need to do? 3.2 Proposed structure 3.3 Multi-file projects 4. Introducing graphics 4.1 Selecting a graphics mode 4.2 Drawing things 4.2.1 The BITMAP struct 4.2.2 Plotting pixels 4.2.3 Some other primitives 4.2.4 Writing text 4.3 Palette manipulation 4.3.1 Palette explanation 4.3.2 Changing a single logical colour's physical appearance 4.3.3 Changing the entire palette 4.3.4 Fading in and out 4.4 Simple animation 4.4.1 What is animation? 4.4.2 Making things appear to move 4.4.3 Reducing flicker 4.4.3.1 Synchronising to the vertical retrace 4.4.3.2 Maximising drawn time 4.4.3.3 Optimising drawing order 4.4.3.4 Double buffering 4.4.3.5 Dirty rectangles 4.4.3.6 Alternate line blitting 5. Making several things happen at once 5.1 Moving more circles 5.2 Now moving a square as well 5.3 Keeping track of things 5.3.1 Dynamic allocation 5.3.2 Linked lists 5.3.2.1 Singly linked lists 5.3.2.2 Doubly linked lists 5.3.2.3 Circularly linked lists 5.3.2.4 Using linked lists 5.4 Object Oriented Programming 6. User input 6.1 Keyboard input 6.1.1 Why the standard PC keyboard routines are useless 6.1.2 Allegro's keyboard routines 6.1.3 Moving something using the keyboard 6.1.4 Letting the user choose which keys to use 6.2 Joystick input 6.2.1 Digital readings 6.2.2 The fire buttons 6.2.3 Analogue readings and calibration 6.3 Mouse input 6.3.1 Point-and-click 6.3.1.1 Initialising the mouse 6.3.1.2 Reading the mouse 6.3.1.3 Displaying the mouse pointer 6.3.1.4 Controlling the mouse pointer 6.3.2 Direct mouse control 6.4 Generic input 6.5 Ways of interpretting input 7. More 2D graphics 8. Sound 8.1 Sound configuration 8.1.1 Initialising sound drivers 8.1.2 Using a configuration file 8.2 Digital sound 8.2.1 Loading sound files 8.2.2 Playing samples 8.2.3 Adjusting a playing sample's parameters 8.2.4 Unloading samples 8.2.5 Voice functions 8.3 MIDI music 8.3.1 Loading MIDI music 8.3.2 Playing MIDI music 8.3.3 Controlling MIDI music 8.3.4 Unloading MIDI music 9. Timers 9.1 Uses of timers 9.2 Setting up timer callbacks 9.2.1 Initialising the timer system 9.2.2 Locking and volatility 9.2.3 Installing timer callbacks 9.2.4 Removing timer callbacks 9.3 Limitations of timers 9.4 Examples of timers 9.4.1 Timing a game 9.4.2 Measuring the frame rate 9.4.3 Regulating game speed 10. Datafiles 10.1 Concept 10.2 Creating a datafile 10.2.1 The grabber 10.2.2 The DAT utility 10.3 Using a datafile in your program 10.3.1 Loading an entire datafile 10.3.2 Individually loading datafile components 10.3.3 Reading a datafile component as a normal packfile - Index Legal issues ************ Distribution ============ You may freely distribute this document in any form without modifying the text; I don't expect anything in return, but an email would be nice. You may also distribute modified copies in any form, provided it is made clear that such copies have been modified. Disclaimer ========== I accept no liability for any damage use or abuse of this tutorial may cause to anything at all. The examples presented as part of this package are not guarranteed to work, and are not guarranteed to be safe. In general you shouldn't compile programs you don't understand anyway. 1. Introduction *************** 1.1 About the tutorial ====================== This tutorial is available in several different formats - plain text, Info format (which also works with RHIDE) and as Texinfo source. The source distribution includes utility files to create the other formats, including DVI and Postscript, which can be printed on paper. Support for HTML format is planned. The tutorial is accompanied by a suite of example programs, mostly written by Grzegorz Adam Hanciewicz. They demonstrate the functions and techniques discussed in the tutorial text, and are a valuable resource if you get stuck trying to understand something. All parts of Allegro Vivace - the tutorial in the main formats, the tutorial source distribution, and the example programs - can be downloaded, individually or in groups, from the web site: http://www.canvaslink.com/gfoot/vivace/ If you want to contact me, my email address is: gfoot@users.sourceforge.net Comments, suggestions and complaints are, as always, much appreciated. 1.2 Aim ======= The aim of this tutorial is to guide newcomers to game programming and Allegro through the process of writing a simple game. 1.3 Target audience =================== As stated above, this tutorial is aimed at newcomers to game programming and Allegro. It is not aimed at complete newcomers to the C language. By this, I mean some knowledge of and experience using the C language will be assumed, and I expect you to already have a working compiler that Allegro supports. 1.4 Requirements ================ * Working compiler (recommended: gcc v2.7.2.1 or better; use djgpp v2.01 or better for DOS) * Allegro version 4.0 or compatible (see below for comments on other versions) * GNU Make utility (for building Allegro, and because it's useful anyway) By "working compiler" I mean one that is capable of compiling C programs and linking them to executables for your platform. It also needs to be able to work with the Allegro library, of course, but we'll go through that below. If your compiler does not work properly, you need to troubleshoot the problem, and this is not the place for me to explain this in detail. For djgpp, read `readme.1st' and the djgpp FAQ; if this doesn't help follow the instructions given in the FAQ for posting a help request to the djgpp newsgroup/mailing list. If your version of djgpp is v2.00, you are strongly advised to upgrade it. This tutorial was last updated during the WIP phase of Allegro 4.0, but does not yet fully cover the new features. Nevertheless, Allegro 4.0 is the primary version this tutorial should be consistent with, and I am updating it. Most things should still work for older versions, but some things may not -- such is life. I'll do my best to note changes needed for Allegro 3.x. Versions of Allegro older than 3.0 all require C++ support to build. You don't have to program in C++ but you must have the support. If you don't, either upgrade Allegro to the latest WIP (recommended, they're very stable indeed) or install C++ support (for djgpp, see `readme.1st'). If you don't upgrade your Allegro version, you _will_ need to edit parts of the sample code in this tutorial. Hopefully more recent versions of the above packages will retain backward-compatibility with these versions; if not, you will again have to figure the changes out for yourself. You _will_ need the GNU make utility, despite the fact that djgpp's `readme.1st' marks it as optional. It is a highly useful package, and both Allegro and the examples accompanying this tutorial are designed to be built (painlessly) using it. The latest version for djgpp (as of writing) is `mak3761b.zip', which is compiled with version 2.01 of djgpp and will _not_ work correctly with version 2.00. Most people seem to program using one of three systems: some people use IDEs like RHIDE, some prefer Emacs, and others use simple text editors and the Make utility. This tutorial will not attempt to force any of these systems onto you; you should program in whatever environment you like best. The examples should be built using the Make utility, but you could easily create RHIDE projects for them. 1.5 Before we start... ====================== This tutorial is aimed at people of a range of programming abilities, from those who have only just learned to those who have much experience already. If you feel a section is at all patronising, don't read all of it -- most likely that section is aimed at people below your standard and all you need to do is skim it to familiarise yourself with the commands it introduces. I have enclosed some sections' titles with [ and ], indicating that the topic is advanced, complicated, tedious to implement or just a bit of a tangent. Don't read it if you don't want to. A big issue in writing games is inspiration -- if you can't think of something to write, then you can't write it. At first you will probably either not think of anything, or think of too many ideas which seem far too complicated to implement. If you can't think of anything, relax and read this tutorial. Use the example programs as a base, add new things to them and personalise them. As you read through you will get a better idea of what can and cannot be done. Look at commercial games, too -- see if you can firstly see how they do what they do, and secondly improve upon them. Don't assume it's completely out of reach -- as you gain experience you will be able to see different ways of doing things. Remember, the authors wrote it somehow; it must be possible. If your ideas are too complicated, the above point applies again -- the more games you write, the better you are at writing them and the more games you are able to write. In almost all situations for beginners, the key point is to start simple and get a working game early on, then build up from there. This technique does have its disadvantages but for now it should be fine. Last of all, games are supposed to be fun; that's their purpose. You cannot write a fun game if you're not enjoying writing it. If you find this tutorial boring then I've written it badly and it should be changed, so please tell me. This tutorial is for you and others like you; enjoy it! 2. Getting, installing and using Allegro **************************************** 2.1 What is Allegro? ==================== Allegro is a game programming library, mainly written and coordinated by Shawn Hargreaves. It is a very good library; it is fast, and has a large number of features, and with version 4 it makes a great cross-platform compatibility layer, letting you write code that works on platforms you've never touched. For full information, see its web pages: http://www.talula.demon.co.uk/allegro/ 2.2 Where to find Allegro ========================= Allegro is available primarily from the links on its web site (see Section 2.1: What is Allegro), and also as part of the djgpp distribution at `ftp.simtel.net'. The versions included with djgpp are usually the most recent stable ones; if you want the latest work-in-progress versions you must go to the Allegro web site. I do recommend that you get a WIP version, because this tutorial is written with that in mind. They are very stable. As of this writing, 3.9.25 has been out for a few weeks and I don't recall any major problems with it. 2.3 How to install Allegro ========================== These instructions are for guidance only - the procedure does vary from platform to platform, and is explained more fully in the corresponding readme files that come with the version you download. Create a directory for it, anywhere you like, and unzip it preserving its directory structure (pass `-d' to `pkunzip' under DOS; Unix unzippers tend to handle this automatically so no switches are necessary). Make sure it has, for instance, created a directory called `docs'. When that's done, go to Allegro's base directory. If you are using a WIP, you now need to fix the distribution to suit your platform. For DOS with djgpp, type: fixdjgpp For Unix systems, type: chmod 700 fixunix.sh ./fixunix.sh Next you can configure the build. See the readme files for your version and platform for details here. To build the library and utilities, run `make': make This should then go all by itself, and finish with a short message telling you it worked. If this does not happen, read the Allegro documentation if you have not already done so (in the allegro directory -- `allegro.txt', `faq.txt' and particularly `readme.txt'). Finally, you need to install the library file and header files, so that your programs can easily use them: make install In Unix you probably need to be the root user to do this. If you're still stuck, try asking on the Allegro mailing list -- `allegro@canvaslink.com'. See the `Contact Info' section at the end of Allegro's `readme.txt' for subscription information. Most people just reply to the mailing list, though, so if you're not subscribed you should point this out and ask people to Cc: their replies to you as well. 2.4 Testing the installation and building the Vivace example programs ===================================================================== The installation is pretty much self-testing, because it compiles all the Allegro example programs and utilities. However, for completeness I have included a short program to check the header file and library can be found. To perform this test, cd to this tutorial's `examples' directory and type: make test If there are no errors, great - you can now build the rest of the Vivace example programs by rerunning `make' without the `test' argument. If, however, it couldn't find `allegro.h', `liballeg.a' or `-lalleg', the installation didn't work correctly and you should reread the documentation in more detail. 2.5 Using Allegro ================= This duplicates briefly what the Allegro documentation says. Firstly, any source file which used Allegro functions or variables must `#include '. Put this _after_ any standard header files -- in particular, it must go after `#include ' -- to prevent clashes between the header files. Secondly, you should call `allegro_init()' near the start of your program. This initialises some important general things in the library and is essential. You also need to initialise any individual subsystems that you will be using, but we'll come to that later. I suggest that you make `allegro_init()' the first thing in your `main' function. Thirdly, you must [in Allegro 4 and WIPs] write `END_OF_MAIN()' after the closing brace of your main function. If you don't do this, your program will not link properly with the Allegro library. Finally, at link stage you must link in the Allegro library. This is accomplished by adding `-lalleg' to the _end_ of your linking command. The linking command is the one which produces the executable file; it may be the only command you use. During this tutorial, though, I will encourage you to split your projects between multiple files. For an example of all of this, see `test.c' in the `examples/test/ex_1' subdirectory of the main tutorial directory. Anyway, enough of that -- let's get on to the interesting bits. 3. A basic game structure ************************* 3.1 What does a game need to do? ================================ Most games are built around the same basic concepts. There are, of course, exceptions, and I am not a professional game coder, but in my experience the following system generally works very well. 1. Initialisation First of all we need to put the game into a known state, so that it always starts in the same way. For example, you might want to start the player in the middle of the screen, make a maze, or create a random map. All that would go in here. 2. Main game loop The game will need to keep doing things, over and over again, until the player dies, wins, gives up, or whatever. So we have a main game loop. It has three main components: A. Input -- getting input from the player B. Processing -- moving things around, responding to the input C. Output -- sending information back to the player, usually by putting it on the screen but sometimes by other means, for example playing sounds 3. After the game When the game has finished, we may need to do some other things, like tell the player why it finished, update a high score table, or something like that. In reality the above sequence is usually also contained in a larger loop, so that the game can be played over and over without dropping to the OS in between, and there's some initialisation before the outer loop and tidy-up code after it, e.g. loading the high score table from disk on startup and saving it again on exit. At first, though, we'll just do a single run of the game per execution. 3.2 Proposed structure ====================== Here, then, is the above structure in C code: #include #include "game.h" int end_game; /* flag we'll set to end the game */ int main (void) { allegro_init(); /* initialise the Allegro library */ init(); /* initialise the game */ end_game = 0; /* reset flag */ do { /* loop */ input(); /* get input */ process(); /* process it */ output(); /* give output */ } while (end_game == 0); /* until the flag is set */ shutdown(); /* shut down anything that needs it */ allegro_exit(); /* just for luck */ return 0; /* tell the OS that all went well */ } END_OF_MAIN() In the above example, `game.h' would be a header file prototyping the functions `init', `input', `process', `output' and `shutdown' which must be defined somewhere else in the project. 3.3 Multi-file projects ======================= I mentioned earlier that I was going to encourage you to split your source between files. A lot of people find this hard to do at first, and don't see why it is worthwhile. In this section of the tutorial I intend to justify it and present what I consider to be a good system for organising a project. Justification ------------- Firstly, then, why are multi-file projects a good thing? They appear to complicate things no end, requiring header files, extern declarations, and meaning you need to search through more files to find the function you're looking for. In fact, though, there are strong pros to this argument. When you modify a line of your code, gcc has to recompile everything to create a new executable. However, if your project is in several files and you modify one of them, gcc already has the object files it generated from your source last time you compiled it, and so it only needs to recompile the file that was changed. In a large project this can mean the difference between a lengthy (5 minutes or more, depending on computer speed) recompile and a twenty second adjustment. With a little organisation, splitting a project between files can make it much easier to find the piece of code you are looking for. It's simple -- you split the code between the files based on what it does. Then if you're looking for the routine to draw the player, you know it will be in the graphics source file. Also, since your program is very modular with the minimum amount of sharing between files, bugs are easier to track down; if not many files have access to a certain variable, an erroneous value must be a bug in one of those files. Suggested system ---------------- This is strictly IMHO; other people may prefer to lay things out differently. But since I find these guidelines useful, I suggest you follow them. * Don't make header files which span several source files (exception: library header files). It's much easier to track and usually more efficient if each header file only declares symbols from one source file. * Where appropriate, do use more than one header file for a source file. * Don't duplicate information in several header files. If you need to, `#include' one in the other, but don't write out the same header information twice. This way, if you change the information in the future you will only need to change it once, rather than hunting for duplicates which would also need modifying. * Make each source file includes all the header files which declare information in the source file. Doing this means that the compiler is more likely to pick out mistakes, where a declaration in a header file does not match the definition in a source file. I hope you can see the advantages of splitting up your projects, but if you don't, by all means keep your project in a single file. The examples I will use later on will normally be multi-file, based around the guidelines above and the game structure discussed earlier (see Section 3.2: Proposed structure), so there will be plenty more opportunities to see the benefits of this. If you are still confused about this, please have a look at the article I wrote for the electronic magazine C-Scene: http://www.cscene.org/ The article is in issue 2, entitled `Multi-file projects and the GNU Make utility'. It duplicates the information here, in more detail, and explains some common problems newcomers have to this technique. 4. Introducing graphics *********************** Note ==== This section assumes you are familiar with the idea of graphics programming, i.e. coordinate systems, resolution, etc. If you are not, try reading it anyway; if you don't understand you should get a book on computer graphics. 4.1 Selecting a graphics mode ============================= To put the computer into a graphics mode, you use this function: int set_gfx_mode (int CARD, int W, int H, int V_W, int V_H); For CARD, you put one of the `GFX_*' constants, normally `GFX_AUTODETECT'. W and H are the minimum width and height of screen space you want; the actual mode selected might have a slightly larger display area. For now just pass 0 for V_W and V_H. 4.2 Drawing things ================== 4.2.1 The BITMAP struct ----------------------- Allegro's graphics routines aren't restricted to writing to the screen; they write to "bitmaps". A bitmap could be the screen, it could be a block of memory, or it could be a "subbitmap" (part of another bitmap). When you call Allegro's graphics routines you pass them a pointer to a `BITMAP' struct, which contains information about the bitmap you want to write to. It's best to think of your pointer simply as a "handle" to the bitmap -- it's just like a reference number. Allegro will assign it initially, and you then pass it back to the graphics routines to tell them where to draw. The only interesting parts of the `BITMAP' struct as far as we're concerned are the `w' and `h' fields; these hold the width and height of the bitmap respectively, in pixels. Allegro defines the variable `screen' to be a pointer to a `BITMAP' struct representing the screen, so for example: clear_to_color (screen, 5); will clear the screen to colour 5 (often magenta). 4.2.2 Plotting pixels --------------------- To plot a pixel you use the Allegro function: void putpixel (BITMAP *BMP, int X, int Y, int COLOR); This sets the pixel in the bitmap referenced by BMP at coordinates X,Y to the colour COLOR. Simple enough. See the example program in `examples/chap_04/ex_2_2' for an example of the use of `set_gfx_mode', `clear_to_color' and `putpixel'. 4.2.3 Some other primitives --------------------------- Functions like putpixel are called graphics "primitives", because they are the basic graphics output functions. Some other simple graphics routines are: void vline(BITMAP *bmp, int x, int y1, int y2, int color); void hline(BITMAP *bmp, int x1, int y, int x2, int color); void line(BITMAP *bmp, int x1, int y1, int x2, int y2, int color); void triangle(BITMAP *bmp, int x1, y1, x2, y2, x3, y3, int color); void rect(BITMAP *bmp, int x1, int y1, int x2, int y2, int color); void rectfill(BITMAP *bmp, int x1, int y1, int x2, int y2, int color); void circle(BITMAP *bmp, int x, int y, int radius, int color); void circlefill(BITMAP *bmp, int x, int y, int radius, int color); Try modifying the example program from the last section (`examples/chap_04/ex_2_2') to use some of these. For information about them, read the file `allegro.txt' in the `docs' subdirectory of your Allegro directory. If you have trouble with this, look at the example `examples/chap_04/ex_2_3'. 4.2.4 Writing text ------------------ To write text onto the screen, you use the following functions: void textout(BITMAP *bmp, FONT *f, unsigned char *s, int x, y, int c); void textout_center(BITMAP *bmp, FONT *f, unsigned char *s, int x, y, int c); void textprintf `bmp' is, of course, the bitmap onto which you want to write. The `FONT' struct is defined in `allegro.h' and is used to refer to different fonts in memory. You use it in much the same way that you use the `BITMAP' struct -- most of the time you just pass pointers to them to various functions, and don't need to know anything about what is actually in the struct. You can create your own fonts, use the ones from the distribution of GRX (another graphics library), which originally come from XFree86 (a windowing system for Unix), and convert fonts from Windows's TTF format. For all of these you need to know how to use Allegro's Grabber utility and datafiles. You can see `grabber.txt' for more information, but datafiles aren't discussed until much later in this tutorial. For now, though, you can just pass `font' to these routines; it's a `FONT *' declared in `allegro.h' and refers to the 8x8 BIOS font. `s' is the string you want to print, `x' and `y' are the coordinates, and `c' is the colour. If `c' is -1 and the font is proportional, Allegro will use the colour information stored in the font, allowing multicolour fonts. `textout_center' is almost identical; the only difference is that it centres text about the x-coordinate. `textprintf' is a wrapper around `textout' which takes a format string in the same way as `printf'. `textout' is faster, but `textprintf' is more versatile. The background colour for text output can be set with: void text_mode(int mode); where `mode' is the new background colour. If mode is negative the background will not be drawn, and what was there before will show through. Play around with these for yourself, or look at `examples/chap_04/ex_2_4' which demonstrates them. 4.3 Palette manipulation ======================== Note ---- For most purposes, palettes only have meaning in 8-bit (256 colour) modes. You can also set modes with higher colour depths; if you do then the rest of this section won't have much effect. See Shawn Hargreaves's Pot of Gold, linked from the Documentation part of the Allegro web pages, for information on higher colour depths. 4.3.1 Palette explanation ------------------------- When you draw to the screen in an 8-bit mode, you specify a colour number from 0 to 255. Conventionally the lower 16 colours are black, blue, green, cyan, red, magenta, brown, light grey, dark grey, bright blue, bright green, ..., bright magenta, yellow, white (but that may not be the case). The remaining 240 colours are not always the same. Before using them you should tell the computer what colour you want to appear on the screen when you set pixels to the given colour number. It's safest to explicitly set all colours you use, including the lower 16 colours; that way you can be sure that they'll appear how you want them to appear. I will call the colour numbers you send to the graphics functions "logical" colours and the actual colour which appears on the screen for each will be the associated "physical" colour. So we need functions to assign physical colours to logical colours. Logical colours are expressed as numbers from 0 to 255. Each possible physical colour is expressed as three numbers, each of which ranges from 0 to 63. These numbers represent the strengths of red, green and blue in the colour. 63 is full strength of course. To store physical colours, use the RGB struct which is defined in `allegro.h': typedef struct RGB { unsigned char r, g, b; } RGB; Needless to say, `r', `g' and `b' are the red, green and blue intensities of the colour, as described above. 4.3.2 Changing a single logical colour's physical appearance ------------------------------------------------------------ To change the appearance of a logical colour, use the function: void set_color (int index, RGB *p); `index' is the logical colour number and `p' points to an RGB record as above. The example program `examples/chap_04/ex_3_2' demonstrates the use of this function by setting colour 0 (the background colour) to be different colours as you press keys. Press to quit. Note that there is a slight complication here, in that if you intend to change colours repeatedly over a period of time, you should call the Allegro function `vsync' between changes (which will also add a certain delay). The main reason for this is that some video cards don't like you fiddling with the palette when they are trying to display things, and they display the wrong colours from time to time, resulting in "snow" appearing. `vsync' waits for the monitor to reach the end of the frame, which removes the problem. We'll see more of `vsync' later on. 4.3.3 Changing the entire palette --------------------------------- To change the whole palette at once we could call `set_color' repeatedly, but this would be slow and awkward. Instead, `allegro.h' defines the type `PALETTE' to be an array of 256 RGB records, one for each logical colour. You can create your entire palette in this array and then set it with a single function call: void set_palette (PALETTE p); The example `examples/chap_04/ex_3_3' demonstrates this function by drawing a rainbow of colours and then switching the palette to a greyscale one. 4.3.4 Fading in and out ----------------------- In low colour-depth modes (e.g. 256 colours) this is simple: to fade out you keep making the palette darker and darker until it's all black, and to fade in you do it the other way around. Allegro has several functions to do this; the most basic are: void fade_in (PALETTE p, int speed); void fade_out (int speed); Note that `fade_in' requires you to say what palette you are fading to; `fade_out' doesn't need a palette because it assumes you mean to fade from the current palette. `speed' is the speed of the fade, ranging from 1 to 64 with 64 being an instantaneous change. `examples/chap_04/ex_3_4' demonstrates these functions. 4.4 Simple animation ==================== 4.4.1 What is animation? ------------------------ Animation is making things appear to move. This could mean making a pixel move around the screen, but it is usually taken to mean repeatedly changing something's appearance so that it seems to propel itself. For now we will just look at how to make simple things move around the screen; the second interpretation will be dealt with later on when we look at sprites. 4.4.2 Making things appear to move ---------------------------------- The simplest way to make something appear to move is to erase it from where it is and put it on the screen again somewhere else. It's a philosophical matter whether it has in fact moved; since it doesn't exist anyway there's not a lot of point worrying about it. So we'll just say that it has moved. To make a solid circle move across the screen you could do something like this: int x; install_timer(); /* required for `rest' */ circlefill (screen, 0, 100, 5, 15); /* draw first time */ for (x = 1; x < 320; x++) { /* loop across the screen */ rest (10); /* slow it down */ circlefill (screen, x - 1, 100, 5, 0); /* erase from last place */ circlefill (screen, x, 100, 5, 15); /* redraw at new place */ } Try this out to see that it works. Now make the circle bigger (radius 50, say) and see what happens. 4.4.3 Reducing flicker ---------------------- Okay, so that started flickering quite a bit. If it didn't, make the circle bigger. If it still doesn't, well, it would if you had used a high-resolution screen mode (try it). If you can't make it flicker at all then congratulations, you have a supercomputer. Us mere mortals, though, have to do something about the flickering. There are many approaches; some are more complicated than others, some are more effective, and some are simply good because they don't take much CPU time (and so the game runs at a higher frame rate). 4.4.3.1 Synchronising to the vertical retrace ............................................. This is the most important thing to do. The vertical retrace is the time period during which your monitor is preparing to draw a new frame. During this time nothing is drawn on the screen, so if we can erase the object and redraw it before the monitor starts the next frame our problem is solved. The `vsync' function waits until the start of the next vertical retrace period. When this happens, we want to update the screen display as quickly as possible. So, replace the `rest(10)' with `vsync()' and try it again. Much better. Note that the vertical retrace is like a timer in a way; it occurs at a fixed rate (for each video mode; it varies between modes). Consequently, since we have to sync with it anyway, it can be a useful way to regulate the game speed. We'll find better ways in chapter 9, though, when we deal with Allegro's timer system. 4.4.3.2 Maximising drawn time ............................. If we are drawing so much that we can't get it all done during the retrace, we need to reduce flicker in some other way as well. Consider what the situation would be if we had a lot of circles, and we erased all of them before redrawing them all. They would all be undrawn for a relatively long time; there's a high chance that the monitor will try to display their area of screen when they aren't drawn. We could solve this problem by erasing and redrawing them one by one. This would help reduce flicker, but it would introduce other problems. The point, though, is that maximising the amount of time things are drawn for is a good aim. For example, if you erase all your objects and then work out where you should redraw them, they're off the screen while you're calculating. If you can do the calculations before erasing them, they'll spend more time on the screen and less time off it. 4.4.3.3 Optimising drawing order ................................ As we noted above, sometimes we might not get all our drawing done during the retrace period. So what happens next? The monitor begins displaying the screen, from the top down. So, if we're only going to get some of the drawing done in time, it would be better to get the stuff at the top ready so that the monitor can draw it. In fact, the important thing is not to erase something while the monitor is trying to display it. We can't know for sure which part of the screen is being updated at any time though, unless we use a very high-resolution timer, which has other drawbacks. Several drawing orders are sensible. You can draw things strictly from the top down. This also improves the frame redraw speed in higher resolution/colour depth modes by reducing video bank switches. You could consider how complex each object is, and perhaps draw the simplest ones first. Another technique is to draw from the _bottom up_. This may sound odd, but if you do this you'll only run into the retrace once, and only for a short period. Using this system there will be some flicker, but it's likely to be less severe. Think about travelling quickly along a motorway in a car, looking sideways out of the window. Better make sure somebody else is driving though. You're trying to get a good view of the hedge. Cars travelling in the same direction as you have a low relative velocity, and so they block your view of the hedge for long periods. Cars travelling in the opposite direction will only block your view for very short periods, though. 4.4.3.4 Double buffering ........................ This is a very popular, simple system, which works well in fast (i.e. low resolution) video modes. Newer graphics cards (AGP and to a lesser extent PCI) can also handle it in higher resolutions. The technique of double buffering involves writing all the graphics to a temporary bitmap, until a whole frame has been updated, and then copying the final image to the real screen memory in one big chunk. This can be effective for several reasons. Firstly, it means that we are no longer erasing anything from the screen -- we are just replacing one image (with everything drawn) with another (still with everything drawn). Secondly, video memory is rather slow. If we tend to overwrite areas of the screen a lot when drawing the frame, it will be faster to do all these writes to the (fast) system memory and then copy that to the (slow) screen memory than it would be to perform the writes directly to the screen memory. In addition, reading from screen memory is often even slower; so if we will want to read from the image it is far better to read from a copy in system memory. This is very much like disk caching, where copies of bits of the disk are kept in system memory to avoid having to access the disk directly. So, instead of drawing to the screen, we draw to another bitmap in memory. Then, when we're ready, we `vsync()' and then copy the entire buffer onto the screen. In the 320x200x256 mode this fits well inside the retrace interval, so there should be no flicker at all. In higher resolutions or colour depths there is of course more image data to copy, so it takes longer. The effects of this range from shearing (where the top part of the screen shows one frame and the bottom part shows another) to drops in frame rate (where it takes so long that we miss the start of the next retrace period, so `vsync' waits for the one after that). See the example program `examples/chap_04/ex_4'. We won't look too deeply at memory bitmaps and the blit function yet; they'll come up in more detail in chapter 7. 4.4.3.5 Dirty rectangles ........................ This is a variant of double buffering that doesn't require such a fast video mode, but it is trickier to implement. The theory is that you do exactly as you would when double-buffering, i.e. do all your screen updates to a memory bitmap, but you only copy to the screen those regions which have changed. This involves keeping track of which areas have changed; the simplest way is to mark rectangles as "dirty" when you write to them. Then you can easily blit all of these rectangles to the screen. This is better than double buffering if not much has changed, but if a lot has changed it can be worse, partly because a single blit of a large area is more efficient than a lot of blits of smaller areas, and partly because you may have two dirty rectangles which overlap; unless you take care to avoid this, that region of the image will be copied twice. However, if you can detect situations where double buffering would be more efficient, you can simply blit the whole image in one chunk, as you would when double buffering. The two techniques have a lot in common. Dirty rectangles are more awkward to implement, and if you make small mistakes you can get strange results. We'll return to these in chapter 7 too. 4.4.3.6 Alternate line blitting ............................... Again, this is a variant of double buffering. The idea here is to copy to the screen only every other line. You can copy just the even numbered lines, and leave the odd ones blank; then you get a slightly darker picture with effectively half the vertical resolution. You could also copy the odd lines in one frame and the even lines in the next; this way you don't lose any brightness, but some lines are `older' than others, resulting in some blur. These techniques work best for things like video. 5. Making several things happen at once *************************************** 5.1 Moving more circles ======================= This sounds pretty simple, and it would be, but we're also going to alter it in a few more ways. Our goal here is to fit it into the game structure presented earlier. This time we're going to use a struct to hold together all the elements which describe each circle -- its location, its colour, its radius, and the speed with which it is moving in each direction. It will also be convenient to remember what location it was drawn at, to simplify the erasing function. So let's start by defining the struct: struct circle_t { int x,y,r; /* x, y, radius */ int col; /* colour */ int xs,ys; /* x speed, y speed */ int dx,dy; /* drawn x, drawn y */ }; Now, a good way to write modular code like this is to first think about the interface a litte; decide what you want the layer that's calling the module to see. Then you can write the functions themselves, having decided roughly what they should do -- or you can write the calling routines, even if you haven't yet written the functions that they're calling. An advantage of this is that if you have more than one person working on the game, one of you could write the object's routines and the other could write the caller. Another advantage, which we'll see later, is that it's easy to add new objects to this sort of system -- all the objects will use the same interface functions. So the person who's writing the coordinator, which calls all the objects' routines, could start by using dummy objects that don't do much, to check their routines work, then plug in the real objects later. Let's decide what functions each object should have. Ideally, we want to have the following routines for a circle: a function to draw it, a function to erase it, and a function to update its internal variables. These will be called frequently, from the main game loop. In addition we require a function to create it, initialising its internal variables, along with a function to destroy it, which won't do much in this case but would, for example, deallocate any memory which may have been allocated for this circle. We'll prototype our functions like this: void circle_draw (struct circle_t *circle, BITMAP *bmp); void circle_erase (struct circle_t *circle, BITMAP *bmp); void circle_update (struct circle_t *circle); struct circle_t *circle_init (int new_x, int new_y, int new_radius, int new_col, int new_xs, int new_ys); void circle_destroy (struct circle_t *circle); Note that I've prefixed these functions' names with `circle_'. This is a useful thing to do, because these functions will be global, and we don't want clashes with functions for updating other types of object. Also, the prefix reminds us that they're in the circle module. If we weren't sure of this, we could of course look in the header files to find out which module they were from, but it's faster if you know just by looking at the functions' names. We'll have an array of pointers to `circle_t' structs, and our main `init' function will initialise them all by calling `circle_init' with various parameters. Having initialised them, we'll draw them all, before we return from the `init' function. For now the `input' function will not do anything other than setting the end-of-game flag if is pressed. The use of the keyboard will be explained in the next section. In `process', we will calculate the new positions of all the circles. To do this, we need to set up a for loop stepping through the array of circles, and call `circle_update' for each. In `output' we need to set up a similar loop, calling `circle_draw' for each circle. In most cases (depending upon what animation technique we're using) we need to call the `circle_erase' functions first. Finally, when we exit it is tidy to deallocate the circles created by `circle_init', using `circle_destroy'. I suggest now that you try to implement this yourself, for a single circle, then expand the program to move more of them. The example programs `examples/chap_05/1_a' and `examples/chap_05/1_b' demonstrate these. If you have trouble making your own program to move just one circle, I suggest you look at the first example, and try to modify that to move more than one. Note though that when moving more than one circle, if you erase and draw them one by one, the erasing of the later circles can rub out parts of the earlier ones, which have already been drawn. To solve this, erase all the circles at once, and then redraw them all. However, this could increase the amount of flicker seen (remember maximising the drawn time?), unless you use a double buffer. 5.2 Now moving a square as well =============================== The above technique works fine for many similar objects. In a real game, though, there are usually many different types of object. So let's look at some ways of dealing with squares as well as circles, for example. One solution is to add more information to the struct, enabling it to describe a circle or a square, with a field to say which. Then we could modify the functions to differentiate between the two and draw them differently. Some functions might not need changing at all. Another solution is to create a new struct for the squares, and a new set of functions to deal with them. These two techniques both have their advantages. The first one works well when the two types are very similar; in these cases many functions wouldn't need changing. All the objects could be stored in one array, and only one loop would be needed to draw them, move them, etc. In addition, the similarities can be exploited so that we can make generic routines for things like collision detection. The second technique works well when the objects are quite different. In this case it gets awkward to make a struct that can hold the data for any of the several objects, and the functions no longer share much code. With this technique you need a separate array for each object type, and you need to scan the arrays one by one when drawing/erasing/updating them. In practise, a set of many different object types can be partitioned into classes of object type. For example, the criterion could be behaviour, appeareance, or which player owns the object. Each class can then be treated using the first mentioned system, with a general struct for each class capable of describing any object in that class, and these separate classes are treated like separate objects in the second system. If you have understood this, try to adapt the multiple circle program above to deal also with squares, using each of the first two methods. In case you get stuck, there are some examples for you to look at: `examples/chap_05/ex_2_a' and `examples/chap_05/ex_2_b'. If you do not manage it on your own, look at these and try to add another object type to each. As an exercise based on the mixed technique, try making a program to move circles, squares and... er... triangles (running out of shapes!) around, with the circles and squares moving as before, but with the triangles not bouncing; make them come back on from the opposite side to where they went off. An example of this is `examples/chap_05/ex_2_c'; you might want to run it to see what it does, and then try to mimic its behaviour with your own program. 5.3 Keeping track of things =========================== Now suppose we want to be change the number of shapes at run-time. In all the examples above we have had a set number of each shape moving around. We could make it animate less shapes by reducing the loop count, but we'd always be destroying the last shape in the list, and we could never add extra shapes (unless we'd destroyed some first). In short, what the game could do was limited at compile-time by the sizes of the arrays. There are two techniques I want to introduce here: dynamic allocation and linked lists. Linked lists depend on dynamic allocation, so you should probably read that section first. 5.3.1 Dynamic allocation ------------------------ During this section we'll see what dynamic allocation is, and look at how we can use it to extend the linear array system we've been using so far. Dynamic allocation allows us to ask for memory at run-time (i.e. when the program is running, as opposed to compile-time). This means, for example, that we could ask the user at the start of the program how many of each shape to animate, and then create the arrays however large they need to be. Better still, we can resize the arrays while the game is running, so if we ever run out of room we can just make the arrays a bit bigger. To do this we use the function `malloc'. This standard C function takes a parameter of type `size_t' (basically, a number) and allocates that many of bytes of memory. The return value is a `void *', pointing to the block of memory allocated. So, we want to create an array of `num_circles' circles. Each circle will be represented by a `struct circle_t'. We can use the `sizeof' keyword to find out how many bytes we need for each struct, and allocate the memory like this: struct circle_t *circles; ... circles = (struct circle_t *) malloc (num_circles * sizeof (struct circle_t)); The cast to `struct circle_t *' is optional in C programs; a C++ compiler will complain if you don't use it, though. Its use is largely a matter of taste. If the allocation failed (e.g. out of memory), `malloc' will return `NULL'. Under some circumstances you might like to detect this and inform the user. It's good practice to do this, but often people don't bother because there may be no useful way to recover. If you're using djgpp with a robust DPMI host (e.g. CWSDPMI) it will terminate your program if you ever try to use the NULL pointer. Some other DPMI hosts just let your program carry on, which hides the problem from you; it's best to avoid these when developping, or at least to use CWSDPMI sometimes (i.e. from plain DOS, no Windows) to check that things still seem OK. After the allocation, we can refer to the circles as circles[0], circles[1], etc, as if we'd made a real array. What we have is not an array in the strict C sense; it's just a pointer to a block of memory. In many ways this behaves much like an array, though. Finally, when we've finished with the memory we should free it so that malloc can reuse it later if necessary: free (circles); Now try modifying your program from section 5.1 (just circles, no squares or triangles) to allow the user to specify the number of circles at the start of the program. If you get stuck, have a look at `examples/chap_05/ex_3_1'. Modifying `examples/chap_05/ex_2_a' in the same way should be fairly simple. Modifying `examples/chap_05/ex_2_b' will need more mallocs. Now, what about letting the game expand the array as it runs? This isn't possible with a real array, but as noted earlier our `array' is not really an array. It was dynamically allocated, so we can use the realloc() function to reallocate it with a different size. The data that was in it before will still be there when we expand the allocation. If we're shrinking it, the data that's still in the new block stays there of course but the data that is cut off the end of the block is lost. Even if you reexpand the block, you shouldn't rely on regaining the data. The problem with reallocating the block like this often is that if the memory area just after our block is already taken then realloc needs to copy the whole block to somewhere else. This isn't particularly slow (memory copy operations are pretty fast actually) but it's not the sort of thing you want to happen too often. Most implementations of `malloc' put some empty space at the end of each block, but we shouldn't rely too heavily on this. It's better to reduce the number of reallocations some other way. The simplest way to do this is to always request plenty of extra space - then we don't need to call `realloc' so often. So, we need to keep track of a few things: int num_objs; /* how many are active */ int alloced_objs; /* how many have been allocated */ int block_size; /* how many we should allocate at a time */ Then when we want to add a new object, we first check whether we already have enough space (is `alloced_objs > num_objs'?). If so, we can just increase `num_objs' and use that space. Otherwise, we need to increase `alloced_objs' by block_size and realloc the block like this: objects = (struct obj_t *) realloc (objects, alloced_objs * sizeof (stuct obj_t)); Note that realloc's syntax is like malloc's, but you must pass the pointer's old value. After calling realloc, you shouldn't use the pointer's old value any more -- if realloc has moved the block, the old value is now invalid. If you'd assigned another pointer the value of `objects' and realloc had to move the block somewhere else, the other pointer would be pointing at a bad block. After reallocating the new block size, we can of course increase `num_objs' and use the newly allocated objects. 5.3.2 Linked lists ------------------ Linked lists are an extremely useful data structure. Unfortunately, for some reason many people find them hard to understand. If you can get a good visualisation of them in your mind, though, they're really very simple, and when you've used them a few times you get an intuitive feel for how to maintain them. In this section we'll look at three types of linked list, and try using them in the circles/squares/triangles programs from earlier on. 5.3.2.1 Singly linked lists ........................... Firstly you'll need to be comfortable with pointers. Pointers are just objects that can point to other objects. Try not to think of them exactly as memory addresses; this is a common analogy, but it isn't necessarily correct, and pointer arithmetic becomes confusing if you think of them in this way. So pointers can point at other data objects. Most pointers are designed to point at a certain type of data. In particular, they can point at user-defined structs: struct little_struct { int number; } *ptr; Here `ptr' can point at any object of type `struct little_struct'. To access the field `number' in the object that `ptr' is pointing at we can write `(*ptr).number' or use the shorthand `ptr->number', which means the same thing. Note that the above definition doesn't actually create an object for `ptr' to point at; it just creates the object `ptr', which is capable of pointing at objects of type `struct little_struct' but initially points nowhere (of course it points somewhere, but where exactly depends upon other circumstances; you certainly can't rely on it being a valid pointer though). Now how about: struct linked_list_node { int number; struct linked_list_node *next; } *ptr; Here the `linked_list_node' struct contains not only an integer `number', but also a pointer `next' which can point at an object of type `struct linked_list_node'. So if we create an object of this type, and point `ptr' at it, `ptr->next' can point at another object of this type. And `ptr->next->next' can point at another... so you see we can have a whole list of these structures, allocated one at a time (rather than in one large block), and we can keep track of them all using just one pointer to the start of the list. To mark the end of the list we can set the last node's `next' pointer to NULL. ptr --> +------------+ +------------+ +---------------+ | number : 7 | | number : 3 | | number : 285 | | next : ----> | next : ----> | next : NULL | +------------+ +------------+ +---------------+ The diagram above shows `ptr' pointing to the first of a list of three records. The first record, with `number' 7, has its `next' pointer pointing at the second, with `number' 3. That record's `next' pointer points at record 3, which has `number' 285, and record 3's `next' pointer is NULL because it's the last record in the list. The whole list is storing the numbers 7, 3, and 285, in that order. You're probably wondering why we bother with this system. It has several advantages and several disadvantages when compared to the linear dynamic block storage mentioned above. Firstly, it's very easy to manipulate things in the linked list. New records can be added anywhere in the list, old records can be removed, existing records can be reordered, or moved from list to list, and lists can be split or joined with little hassle; you just need to change the values of some of the `next' pointers. Any of these operations would be awkward and time consuming to perform on a linear block -- we might have to move large amounts of data. For example, imagine removing the second data item in a large sequential list -- we'd have to move the rest of the list forward one place. In a linked list, we just have to set the first item's `next' pointer to whatever the second item's `next' pointer is set to (i.e. point it to the third item, or NULL if there is no third item), and then we can `free' the second item's struct. Similarly, adding a new item to the list is simple. Assuming we have a pointer to the item in the list before the place where we want to put our item, we just set our item's `next' pointer to that item's `next' pointer, and then set that item's `next' pointer to point at the new item. The disadvantages of linked list are memory usage and inefficiency in finding an item by number. The memory usage is greater because of all the pointers in the list, and because it's generally a large number of small chunks (in particular, as of writing this, djgpp's `malloc' function has a minimum block size of 4096 bytes). Finding an item by number is inefficient because we need to step through the whole list from the beginning to get to it, whereas in a sequential list we can jump straight to it with a single addition. As always, which system you'll want to use will depend upon what you're using it for. It often simplifies code if the first item in a linked list is a "dummy" item (sometimes referred to as the "list head"), whose data has no meaning. The reason for this is that it makes storing empty lists simpler -- they become lists with only one item, the dummy. I suggest at this point that you have a look at `examples/chap_05/ex_3_2a' for some examples of adding, removing and finding items in linked lists. I think you'll probably see now why linked lists can be so useful -- we can create as many shapes as we like, whenever we like, and just add them to the list in an efficient way. We don't really care what their number is in the list; if we need to refer to a specific item in the list for some reason we can use a pointer to that item, rather than its ordinal number. 5.3.2.2 Doubly linked lists ........................... Assuming you understood the preceeding section, this should be fairly easy to grasp. In the singly linked list above, each node had one pointer to the next node in the list. In a doubly linked list, each node has two pointers -- one to the next item in the list, and one to the _previous_ item in the list. struct dllnode { struct dllnode *next; struct dllnode *prev; int number; } *ptr; ptr --> +--------+ +--------+ +--------+ | next -----> | next -----> | next ---> NULL NULL <--- prev | <----- prev | <----- prev | | number | | number | | number | +--------+ +--------+ +--------+ Now it is possible to find the whole list given a pointer to any node in it. It is still often useful to make the first node in the list (the one with `prev == NULL') a dummy node whose data is not used, for the same reason as above. It can act as a "handle" to the list -- something to hold on to, that will always be part of the list. The other nodes will be added and removed at times, remember; the head element is the only one that's guarranteed to be there all the time. One advantage of using a doubly linked list is that you can remove a node from a list without needing to know which list it is in. Another advantage is that if an entity in the list needs to scan the list it can do so without needing to be given a pointer to the head of the list. Also, given any item in the list you can add an item on either side without needing to scan the list. Doubly linked lists are generally easier (and more efficient) to maintain, but they do of course have a little more overhead. `examples/chap_05/ex_3_2b' is an example of a doubly linked list and routines to manipulate it. 5.3.2.3 Circularly linked lists ............................... Again, if you've understood linked lists so far this shouldn't be too hard. In our singly linked list we had the `next' pointer of the last node set to NULL, marking the end of the list. In a circularly linked list, the last node's `next' pointer points back to the start of the list -- so there isn't really a last node. Think of it as a ring of nodes, each pointing to the next. If the circular list is doubly linked, the first node's `prev' pointer points to the last node. Empty circular lists are also a problem; using a dummy node is useful here too, but you must remember to skip over it when you're stepping around the loop. Alternatively you can make your pointer to the list NULL when it's completely empty (as you can with any of these linked lists), but you still have to make special cases. Circular lists are useful for different things to linear linked lists; in game programming you generally want to do something to everything in the list once per game cycle, and for this purpose a linear linked list is often better. I mentioned circular lists here for completeness, and because they're not very different to linear lists. For an example, see `examples/chap_05/ex_3_2c'. 5.3.2.4 Using linked lists .......................... Try modifying your shape moving program, making it put the shapes into a linked list and animate them from there. You could also make it add a new shape every 100 game cycles, or make it delete one. See `examples/chap_05/ex_3_2d' for an example of this. This method of putting all game entities into a linked list and animating them is very useful. You can put the player's ship/man/being in there too; if the player's character is similar in action to the enemies then this can work well. 5.4 Object Oriented Programming =============================== This section has not yet been written, sorry. 6. User input ************* An input device is anything you use to give information to the computer. The standard PC only has a keyboard, although most have mice and many have joysticks. In a way I suppose a disk drive is also a sort of input device, as are microphones, scanners, and modems, but they're not generally good things to use to control a game. We'll look at each of the main three in detail, then consider abstracting our input system. Finally we'll look at different ways to use the input information. 6.1 Keyboard input ================== 6.1.1 Why the standard PC keyboard routines are useless ------------------------------------------------------- The standard PC keyboard interface is almost, but not quite, entirely useless as a game control device. There are several reasons for this; firstly, whether you hold a key down or just tap it the game would only see one keypress for a certain period (usually a quarter of a second). Then if you held the key down it would fill the buffer with many keypresses, which means the game might still be reading them after you let go of the key. Also, if you hold one key down and tap another, the first key will seem to have been released. These combine to make the standard PC keyboard routines useless for controlling action games. Thankfully Allegro provides a set of much better routines. 6.1.2 Allegro's keyboard routines --------------------------------- In your Allegro programs, you should call `install_keyboard()' during initialisation. Then you can use Allegro's keyboard routines. Most importantly, there is an array of chars called `key' which has one element for each key on the keyboard. It is indexed by scancode (not ASCII code), and you can get the scancode for a key using the `KEY_*' constants defined in `allegro.h'. For example, to see whether the space bar is currently down, test whether `key[KEY_SPACE]' is non-zero. You probably remember seeing the input routine earlier on checking whether or not the key was pressed. After installing Allegro's keyboard handler, the BIOS routines won't work anymore. This includes conio's `getch', stdio's `getchar', `gets', `scanf' and anything else using stdin. Allegro provides a replacement for `getch' called `readkey', which waits for a key to be pressed and returns an integer composed of its keyboard scancode and the ASCII value; to split it up, you must do something like this: int value, scancode, ascii; value = readkey(); scancode = value >> 8; ascii = value & 0xff; Similarly, `keypressed' is a replacement for conio's `kbhit', returning nonzero if there's a keypress in the input buffer. See the Allegro documentation for more information. 6.1.3 Moving something using the keyboard ----------------------------------------- There are several ways in which the game objects can respond to the input. For now we will make the objects move at a constant speed in the direction the user indicates. We'll discuss other models later on. So, what we require is something like this: if (key[KEY_LEFT]) x--; /* if the user is pressing left arrow, go left */ if (key[KEY_RIGHT]) x++; /* same for right */ if (key[KEY_UP]) y--; /* same for up; note that y _decreases_ to go up */ if (key[KEY_DOWN]) y++; /* and for down y _increases_ */ where `x' and `y' are the object's X and Y coordinates. Try modifying the moving (single) circle program to allow the user to move it around. Try to fit your program into the suggested structure, too. Example program `examples/chap_06/ex_1_3' demonstrates this. 6.1.4 Letting the user choose which keys to use ----------------------------------------------- There are a few problems with using explicit `KEY_*' constants in your games. Generally the best use of the `key' array is when you don't care what the key normally means. If you ask somebody to press the key then you should avoid using the `key' array to test this, because the mapping of `KEY_*' constants to physical keys is independent of the software keyboard mapping. `KEY_Q' refers to the key next to , but foreign and alternative keyboard mappings may not have the key in that position. This means that you can use the `KEY_*' constants to choose control keys in comfortable layouts, but you shouldn't use them if the symbol on the key is important (e.g. Q for Quit). Since the PC keyboard wasn't really designed as a game control device, it cannot track all the keys independently. It doesn't even come close. There are certain key combinations which, when pressed, mask out other keys -- even when you have good keyboard routines like Allegro's. It's a hardware limitation. Example `examples/chap_06/ex_1_4' is a simple program which repeatedly prints a list of all keys currently flagged by Allegro as pressed; run this and try holding down keys, until you find that a keypress is not detected. On my keyboard, holding down , and together means pressing goes unnoticed. Even more unfortunately, not all keyboards "clash" in the same places. So how are you supposed to choose keys for your game if they might not work on all systems? The answer is, of course, to allow the users to define their own keys. Then if there is a keyclash they can choose some other keys. To do this, you will want to replace the `KEY_*' constants in your input routines with variables, which you can initialise to some default values, and allow the user to change them if they want to. Naturally you shouldn't expect the user to know what all the scancodes are; you should write a routine to check this for them. Using the `key' array this is fairly trivial; you simply cycle from 1 to `KEY_MAX', the highest scancode, and note which key is pressed. Put this into your `userkey_left' variable, wait for the user to let go of it, and then ask for a right key, and so on. It's safe to assume the first pressed key you find is the only one; what stupid luser would press two keys together when asked for a single key? :) We'll see an example program that lets the user redefine the control keys a bit later on. 6.2 Joystick input ================== 6.2.1 Digital readings ---------------------- Reading the joystick digitally is simple. Make sure the user has the joystick centred (i.e. tell them to centre it and wait for them to acknowledge that they've done this), then call `initialise_joystick()'. Now whenever you want to read from the joystick, call `poll_joystick()'. This reads from the joystick and, among other things, updates the global variables `joy_left', `joy_right', `joy_up' and `joy_down', each indicating whether or not the joystick is pushed in its direction. The input code example given in the keyboard section earlier now becomes: poll_joystick(); if (joy_left) x--; /* if the joystick is pushed to the left, go left */ if (joy_right) x++; /* same for right */ if (joy_up) y--; /* same for up; note that y _decreases_ to go up */ if (joy_down) y++; /* and for down y _increases_ */ Make sure, though, that the joystick is centred when you initialise it, though. The `initialise_joystick' function returns zero only if there is a joystick present, so you could check this, then ask the user to centre the joystick, and finally call the `initialise_joystick' function again to update the centre position. Try modifying the previous example to read the joystick instead of the keyboard. If you get stuck, look at the example program `examples/chap_06/ex_2_1'. 6.2.2 The fire buttons ---------------------- These are easy to read; the average joystick has two, and their status is stored in `joy_b1' and `joy_b2', which go TRUE (non-zero) when the buttons are pressed. As with all of the `joy_*' variables, they are only updated when you call the `poll_joystick' function. If you never call that function, they'll always stay the same; you don't need to call it too often, though. Once per game cycle should be plenty, except when you're checking the following... Apparently, most joysticks do not have their fire buttons debounced, so you should be aware that pressing a fire button can cause several changes from `off' to `on' and back; this has never caused me any problems but maybe my joystick just doesn't suffer from this. In most games where this is a problem, setting a maximum fire rate will get around this anyway (i.e. not allowing the user to fire a second time until, say, 20 game cycles have passed). One thing bouncing can really mess up is double-clicks on the joystick button; single presses may look like two (or more) presses. Note though that this is only an issue if the time between joystick polls is very short. Try modifying the digital joystick example to make the circle change colour when the fire button is pressed. Try to make it only change once on each press (by waiting for the button to release each time -- remember to poll the joystick inside your waiting loop, otherwise the variables won't get updated). If the colours don't change in the right order, the button may be bouncing; try to debounce it. Now (harder) make it still let you move the circle around while you're holding down fire. Examples of all the above are in `examples/chap_06/ex_2_2'; look particularly at the technique used in the last case. 6.2.3 Analogue readings and calibration --------------------------------------- [Note: this information is out of date. It'll still work, but needs updating for Allegro 4.] The PC joystick is supposed to be an analogue device; in other words, you can find out not only which way the stick is pointing but also how far it is pointing in that direction. However, as Shawn points out in `allegro.txt': ...the exact results of this can vary depending on the type of joystick, the speed of your computer, the temperature of the room, and the phases of the moon. If you want to get meaningful input you have to calibrate the joystick before using it, which is a royal pain in the arse. Calibration isn't really that hard, but it is a little irritating to the user. Most PC gamers are pretty used to calibrating their joysticks by now, though. The point about calibration is that the joystick routines need to find out what range of values the joystick gives, in both the X and Y axes. Allegro does this using three functions: `initialise_joystick', as mentioned before, marks the centre values; `calibrate_joystick_tl' notes the values given when the stick is in the extreme top-left position, and `calibrate_joystick_br' does the same for bottom-right. Again, normally you'll want to call `initialise_joystick' once on startup, to see whether or not a joystick is present. If one is, and you later want to calibrate it, follow this sequence: 1. Ask the user to centre joystick, then wait for them to acknowledge (for example, by pressing a key) 2. Call the `initialise_joystick' function to note the centre position 3. Ask the user to push it to the top left and acknowledge 4. Call the `calibrate_joystick_tl' function 5. Ask the user to pull it to the bottom right and acknowledge 6. Call the `calibrate_joystick_br' function Now Allegro has everything it needs to know to be able to give you analogue positions. For aesthetic reasons it's a good idea to now have the joystick returned to its centre position; otherwise you might dive into something important with them still holding it in the bottom right hand corner. Either ask them to centre it and acknowledge once again, or keep reading it until they do (see below). For an example of this in code, have a look at `examples/chap_06/ex_2_3a'. Having calibrated the joystick we can at last read its position in more detail. The variables `joy_x' and `joy_y' give its X and Y positions, from -128 to 128, with (-128,-128) being the top left corner, (0,0) being the centre, and (128,128) the bottom right corner. Have a go at modifying the circle-moving program to move the circle at a speed relative to how far the stick is moved. `examples/chap_06/ex_2_3b' demostrates this in case you can't do it. Note the `#define' near the start; it controls the maximum speed for the circle, which corresponds to full deflection of the joystick. Using a `#define' or a constant variable rather than just a number makes it simpler to change the speed in the future. 6.3 Mouse input =============== You can view the mouse in one of two ways -- either it is a point-and-click device, or it is somewhat like an analogue joystick, giving direct control over a character. 6.3.1 Point-and-click --------------------- The point-and-click approach involves putting a pointer of some sort on the screen, and making the mouse move it around. When a button is pressed you do something, depending on what the mouse is pointing at. Examples of this are WIMP GUIs (like Windows), Dune 1 and 2, Warcraft 1 and 2, numerous front-ends (menus), etc. It is a very popular technique. 6.3.1.1 Initialising the mouse .............................. To initialise the mouse, call `install_mouse()'. As noted in allegro.txt, this returns -1 on failure, or the number of buttons on the mouse if it succeeds. 6.3.1.2 Reading the mouse ......................... Now you can get the mouse pointer's X and Y coordinates using the variables `mouse_x' and `mouse_y' (which should be screen coordinates) and the button status is in `mouse_b', which is a bit field like this: Bits: 2 1 0 . . X = left button flag . X . = right button flag X . . = middle button flag The remaining bits are unused at present. The three button flag bits are set if the corresponding button is pressed, clear if not. If there is no middle button, its flag bit is always clear. So, to read each button you bitwise-AND (&) `mouse_b' with 1, 2 or 4 like so: if (mouse_b & 1) printf ("Left "); if (mouse_b & 2) printf ("Right "); if (mouse_b & 4) printf ("Middle "); printf ("\n"); Note though that the value of `mouse_b' can (and does) change `behind your back'; the following sort of test might give strange results: if (mouse_b) { printf ("Buttons pressed: "); if (mouse_b & 1) printf ("Left "); if (mouse_b & 2) printf ("Right "); if (mouse_b & 4) printf ("Middle "); printf ("\n"); } Imagine what happens if initially one of the buttons is pressed, so that the `if' condition passes, but then the button is released; the string "Buttons pressed:" will be printed, but since now none of the buttons are pressed neither "Left" nor "Right" nor "Middle" will be printed. If this sort of oddity could be a problem, you can take a copy of the `mouse_b' variable and work from that; this is a bit like polling the joystick, in a way. my_mouse_b = mouse_b; if (my_mouse_b) { printf ("Buttons pressed: "); if (my_mouse_b & 1) printf ("Left "); /* etc */ } 6.3.1.3 Displaying the mouse pointer .................................... To show the pointer on the screen, you simply call the function `show_mouse', telling it which bitmap to draw onto, for example: show_mouse (screen); Whenever the mouse is moved, Allegro will update the mouse pointer (unless you've told it not to -- see the Allegro documentation). Because of this, you have to hide the mouse before writing anything to the bitmap it is shown on, otherwise Bad Things happen to the display. To do this, call `show_mouse(NULL)'. Note, though, that the functions which actually draw the mouse pointer require Allegro's timer routines to be installed (as do several other Allegro components) -- that's (partly) how they manage to do things in the background. See Chapter 9: Timers, and in particular Subsec 9.2.1: Initialising the timer system, for more information. 6.3.1.4 Controlling the mouse pointer ..................................... If you need to adjust the range of the mouse, you can call: set_mouse_range (min_x, min_y, max_x, max_y); where `min_x' and `min_y' are the minimum X and Y values, and `max_x' and `max_y' are the maximum values. If for any reason you want to move the mouse pointer to a certain location on the screen, call the function `position_mouse', passing the new X and Y coordinates of the mouse. The example program `examples/chap_06/ex_3_1' demonstrates the point-and-click mouse. 6.3.2 Direct mouse control -------------------------- This technique involves, at each game cycle, seeing how far the mouse has moved in each direction and treating this in the same way as analogue joystick data. Examples of games that do this are: * All of id Software's recent games -- e.g. Wolfenstein 3D, and all of the Doom, Heretic and Quake games. * Other first person perspective shoot-'em-ups. Well, id Software deserved to have a class to themselves since (until recently, at least) they have always excelled at creating gameplay and atmosphere. Examples here: the Descent series, Duke Nukem 3D, Dark Forces. * Flight simulators -- e.g. Jetfighter II, the Wing Commander games, X-Wing, Tie Fighter, etc. I don't think it's a coincidence that this list contains only first person perspective games (i.e. games where the display is from the player's point of view). That's not to say that this method can't work well for other games; I think it's more obvious when this type of game benefits from it. When using this system, you initialise the mouse in the same way as for the point-and-click system (see Subsubsec 6.3.1.1: Initialising the mouse). To make this sort of control system you ask the mouse driver for a mickey update. A "mickey" is a very fine measure of mouse movement, and is given relative to last time you asked. Allegro provides the `get_mouse_mickeys' function to do this: void get_mouse_mickeys (int *mickeyx, int *mickeyy); Pass this function two pointers to `int' variables, and it will fill the variables pointed to with the change in the mouse's X and Y positions since the last call to this function. For example, if `dx' and `dy' are `int' variables, get_mouse_mickeys (&dx, &dy); will put the mickey differences into `dx' and `dy'. Note that the units are not the same as the units of `mouse_x' and `mouse_y' -- the mickey is a much smaller unit. The example for this section, `examples/chap_06/ex_3_2', uses this technique to move our wonderful circle around by tying the mouse movement to acceleration. As [ed: will be] discussed below, this is a common model for more realistic games, effectively linking mouse movement and force applied to the object. You ought to take a look at this example, just to see how it works. 6.4 Generic input ================= Given all the above input types, we have written several programs allowing us to control things with each. However, how can we write a game which allows the user to use whichever device they want to? We could write versions of the reaction code to interpret each type of information, and only use the ones corresponding to the user's choice of device, but it would be better to minimise the number of links between our input routine and the reaction routine. The example in `examples/chap_06/ex_4' shows a way of doing this; I suggest you refer to it while reading this. Look in particular at `input.c' and `input.h'. We need a common format for the data which is sent to the reaction routines. We need information about how far the input device is in each direction, and information about the status of any fire buttons. I created this struct to hold the information: struct input_t { int dx, dy; int fire1, fire2; }; Let's define `dx' and `dy' to be from -128 to 128, as in the joystick routines, indicating which direction the device is pointing and how much. `fire1' and `fire2' will be non-zero when the fire button in question is pressed. We're limited to two, because most joysticks only have two; it's often sensible to aim for the lowest common denominator when writing code to work over a variety of systems or configurations. The input routine will need to fill in the information required every time it is called. For joystick input, this is simple; just copy the values across, since the magnitudes match up. If the joystick is digital, though, we should check `joy_left', `joy_right', etc, using the same system as for the keyboard, which follows. For the keyboard, we will need to work out where it is "pointing'". Given a `left_key' and a `right_key', we can work out the "rightness" as follows: rightness = (key[left_key] ? -1 : 0) + (key[right_key] ? 1 : 0); Think about what this means in each situation: If neither key is pressed, both brackets are zero and it returns 0. If both are pressed, the first bracket given -1 and the second gives 1; so it returns 0 again. If only left is pressed, the first bracket gives -1 and the second gives 0, so it returns -1, and if the right key is pressed it returns 0+1, which is 1. Now we can adapt this to fit our structure by saying: dx = (key[left_key] ? -128 : 0) + (key[right_key] ? 128 : 0); dy = (key[up_key] ? -128 : 0) + (key[down_key] ? 128 : 0); fire1 = key[fire1_key]; fire2 = key[fire2_key]; Hence any keyboard input is always like full deflection of the joystick. As for the mouse, we'll use the direct control method; if we wanted a point-and-click interface we would be pretty much dependent on the user using a mouse. You could use these input routines to simulate that system with any input device, but we won't go into that here. To map the `dx' and `dy' from the mouse code onto the range [-128,128] that we are using here, we need to divide by `max_mousespeed' and multiply by 128, where `max_mousespeed' is a variable controlling the sensitivity of the mouse. We will do this the other way around, though, because then we can leave it as an integer calculation. Where possible, you should avoid casting between floating point types and integer types -- it's slow to convert between them. The complete code for what we have discussed so far is in function `input_getinput'. Before we can use that, though, we need a way of telling this input module what sort of input we're looking for. This will be by a call to `input_create_*', which may take some parameters (depending on what `*' actually is), make an input structure, and return a pointer to it. The joystick input routine doesn't need any extra information, but the mouse input routine needs to know what value to use for `max_mousespeed', and the keyboard input routine needs to know which keys to use. We could have used a single function with a variable number of parameters, but that's a bit complicated for this tutorial. So we define these functions: input_t *input_create_joystick (); input_t *input_create_mouse (int max_mousespeed); input_t *input_create_keybd (int left_key, int right_key, int up_key, int down_key, int fire1_key, int fire2_key); We also need a function to initialise the input module, a shutdown function for it, and a function to destroy previously-registered input devices (e.g. if you change the keys); we'll prototype these: void input_init (); void input_shutdown (); void input_destroy (input_t *what); In summary, programs will call `input_init' initially, to set up the module. They will then call some of the `input_create_*' functions to register input devices for the players; the `input_create_keybd' may be called more than once, with different keys, to allow for more than one player on the keyboard. The `input_create_*' functions return pointers to `input_t' structs, whose fields will be updated on each call to `input_getinput'. Registered devices can be unregistered using `input_destroy' by passing the `struct input_t' pointer; this will also deallocate the structure pointed to. The `input_shutdown' function will unregister all devices and deallocate any memory still allocated. The input_t struct contains fields `dx', `dy', `fire1' and `fire2'. `dx' and `dy' range from -128 to 128, indicating the amount of movement in each direction, and `fire1' and `fire2' are non-zero if the corresponding fire button is pressed. Once again, see the program in `examples/chap_06/ex_4' for an example of how to use this system. 6.5 Ways of interpretting input =============================== This section isn't written yet. When it is written, it will contain information about physical models (no, it's not as boring as it sounds!) and how you can use the input data. 7. More 2D graphics ******************* This section isn't written yet. When it is, it will talk about: * memory bitmaps * sprites * moving things without disturbing the background * double buffering and dirty rectangles again, and introducing page flipping 8. Sound ******** 8.1 Sound configuration ======================= 8.1.1 Initialising sound drivers -------------------------------- To install the sound drivers, you call the function: install_sound (digi_driver, midi_driver, NULL); putting a `DIGI_*' constant in place of `digi_driver' and a `MIDI_*' constant in place of `midi_driver'. The third parameter is obselete and you should just pass NULL. The function returns zero if it was successful. A non-zero return probably means that the driver you requested is not available. If you used a `*_AUTODETECT' setting for either driver, this may mean that a config file specified a precise driver to use, and that driver was unusable. I think there's rarely any good reason not to use `DIGI_AUTODETECT' and `MIDI_AUTODETECT'. My reasoning here is that if you specify a device explicitly the game won't work if that device is not available, and won't be able to make use of a better device if one is available. Even if you have some hardware conflict which makes these options fail normally, that's no reason to stop your game working properly on other people's machines -- you should create a configuration file (see below). Config files always override the autodetection routines, so the conflict should disappear -- you're relying on this to be true so that the end-users can fix such problems if they occur. If you hardcode values here people won't be able to override the settings without rebuilding -- bad news! So unless there's a very good reason (I can't think of any) just use the `*_AUTODETECT' constants here. Even the `play.exe' example program in Allegro's tests directory is borderline in my mind -- it uses a hardcoded table of driver numbers and names. That's pretty bad; the justification (I think) is that its purpose is to test drivers quickly. Not much justification really... One further thing to note is that the MIDI player uses Allegro's timer routines to operate, so you must initialise those before you'll be able to play MIDI files. See *Note Initialising the timer system::, for more details. 8.1.2 Using a configuration file -------------------------------- Allegro supports a wide range of features in config files; sound configuration is just part of the standard data, and you can add data of your own for your game's internal use. For full details on using config files, see Allegro's help system (type `info allegro config' at a DOS prompt, if you use Info). Here I'll only mention what applies to the sound drivers. Unless you say otherwise, the files `allegro.cfg' and `sound.cfg' will be checked for in the program's home directory. To specify a different filename, pass it to the `set_config_file' function. The sound configuration information is in the `[sound]' section of the file, and holds information about which drivers to use if you ask for autodetection and the settings for those drivers, among other things. The way the autodetection works in Allegro is such that if you specify a driver explicitly in the config file then that driver will be used. If you don't specify one then Allegro will start poking around trying to see what's there; this _may_ have adverse effects, and in addition some things (like an external MIDI device) are never autodetected for technical reasons. The safest and easiest way to write config files is through the setup utility. This is designed to be packageable with your games, and lets the user choose some settings (or autodetect them) and try them out. It then saves the settings to a config file ready for your program to read. The simplest way to distribute this, then, is to put the `setup.exe' file in the same directory as your program's executable, so that the output from the setup program is right where your program expects to see it. There are many things about the setup program that you can customise; or you can just leave it alone. See `setup.txt' for full information (it's in the same directory as the rest of the setup program). 8.2 Digital sound ================= 8.2.1 Loading sound files ------------------------- Allegro can read digital samples from WAV and VOC files. Both types of file must be mono. WAV files can be 8 or 16 bit; VOC files must be 8 bit. Loading a sample couldn't be easier -- you pass the filename of the WAV or VOC file (it must have the right extension) to the `load_sample' function, and it returns a `SAMPLE *' which you can use to refer to the sample, or NULL if it could not load the sample (e.g. it didn't recognise the extension, the file was not found, or the format of the file was incorrect or not supported). You can also call `load_voc' or `load_wav' directly, in the same way. The benefit of doing this is that if the file does not have a .WAV or .VOC extension it will still be processed. 8.2.2 Playing samples --------------------- Allegro's digital sound player can manipulate samples in several ways whilst playing them. The parameters you can set at the basic level are the volume, pan and frequency. You can also tell the sound player whether or not to loop back to the beginning of the sample when it reaches the end. To start a sample playing, call the play_sample function. You pass the following parameters: * the sample to play (a `SAMPLE *') * the volume of the sample, from 0 (min) to 255 (max) * the pan position of the sample, from 0 (left) to 255 (right) * the frequency of the sample relative to its original frequency -- 1000 would play it at the original frequency, 2000 would double that frequency (raise the pitch by one octave, musically speaking) and 500 would halve the frequency (lower the pitch by one octave) * a loop flag; 1 = loop, 0 = don't loop Note that offsetting a sample's frequency can introduce distortion of the sample -- Allegro is a game programming library, not a sophisticated audio suite. Also, playing sounds at generally low volumes but with your stereo system's volume knob turned right up will also introduce distortion, not to mention noise ;). Try to use the full range of volume values, and also use samples which internally use the full range of values, if possible. A typical call to play_sample, then, would be: play_sample (gun_sound, 255, 128, 1000, 0); which plays `gun_sound' (previously loaded as above) at full volume, centre pan, and its original frequency, without looping it. play_sample (some_sound, 192, 96, 1200, 1); would play `some_sound' at 3/4 volume, panned slightly left, a little above its normal pitch, and set it to loop back to the start each time it reached the end. To stop a sample playing, use the `stop_sample' function: stop_sample (gun_sound); This is especially useful on looped samples. 8.2.3 Adjusting a playing sample's parameters --------------------------------------------- You can also adjust these parameters while a sound is playing. To do this, you call `adjust_sample'. It takes the same parameters as `play_sample', and searches through the list of playing samples for the one you pass. So if `some_sound' is still playing from above, we could make it stop looping like this: adjust_sample (some_sound, 192, 96, 1200, 0); We could also have changed some of the other parameters, of course. If you want to adjust samples while they're playing, avoid playing more than one copy of the sample at a time -- `adjust_sample' will only change the first sample it finds, so the others will carry on the way they were. For a more sophisticated way to play and control samples, which does not suffer from the above problem, see *Note Voice functions::. 8.2.4 Unloading samples ----------------------- When you have finished with a sample you can unload it from memory using the `destroy_sample' function like this: destroy_sample (some_sound); If the sample is playing at the time, it will automatically be stopped. 8.2.5 Voice functions --------------------- This section has not yet been written. It will contain information on using the voice functions directly to gain more control over sounds. There is an example program though: `examples/chap_08/ex_2_5' 8.3 MIDI music ============== 8.3.1 Loading MIDI music ------------------------ At present Allegro supports only MIDI music, which it can load from type 0 or type 1 (i.e. most) .MID files. To load MIDI files you pass the filename to the `load_midi' function, which returns a `MIDI *', or NULL if it's not successful. 8.3.2 Playing MIDI music ------------------------ To start a MIDI object playing you simply call `play_midi': play_midi (title_theme, 0); The second parameter is a loop flag; 0 means no looping, anything else causes the file to loop back to the start when it ends. If you want your file to loop at other positions (for example, if it has an introduction you might want it to skip that when it loops) you can call: play_looped_midi (background_music, loop_start, loop_end); When position `loop_end' is reached the player will jump back to `loop_start'. If `loop_end' is -1 the end loop point is the very end of the music. The units for `loop_start' and `loop_end' are beats, as measured by the `midi_pos' variable mentioned below. In either case you can stop the music by calling `stop_midi': stop_midi(); Since Allegro can only play one piece of music at a time this function needs no parameters. Starting a piece of music while other music is already playing will stop the first piece immediately. Calling the `stop_midi' function has the same effect as calling `play_midi' with a NULL first parameter. 8.3.3 Controlling MIDI music ---------------------------- While a MIDI file is playing, Allegro increases the global variable `midi_pos' once per beat. If there is no MIDI file playing (or a non-looped file has finished) it will be set to -1. Note that it won't necessarily increase on every beat -- if the music doesn't play a note on the beat, it won't be updated until the next note is played. If you want to synchronise events with this you need to make sure there's a note on the beat; you could put in a zero volume note, for instance, just to trigger the event. You can pause and resume playback using the `midi_pause' and `midi_resume' functions. You can jump to a certain position in the file by passing the target `midi_pos' value to the `midi_seek' function. Be aware that seeking backwards involves rewinding the file to the beginning, and then seeking forwards; consequently doing a lot of small reverse seeks isn't a good idea. Lastly, if the file is looping you can change its loop points on the fly. The two variables are `midi_loop_start' and `midi_loop_end', and are measured in `midi_pos' units. A value of -1 for `midi_loop_start' or `midi_loop_end' indicates the start or end of the file, respectively. I mentioned earlier that you might have some music with an introduction that you don't want to loop every time, and the `play_looped_midi' function can solve that problem; adjusting the loop points can allow you to do a similar thing if the piece has an ending that you don't want to play while it's looping -- i.e. you want to play the introduction once, then loop the main body of the music a few times, and finally play the ending. To do this you'd start the music using the `play_looped_midi' function, passing the position of the start of the main part of the music as the loop start parameter and the position of the end of the main part as the loop end parameter. Then you'd let the music play; it will loop each time it reaches the loop end. Later on you'd then set the loop end parameter to the end of the music. There are a few caveats though: * `midi_loop_start' must always be earlier in the file than `midi_loop_end'; if your new start value is greater than the old end value then you must alter the end value first, and if your new end value is less than the old start value then you should alter the start value first. * Excepting the above, it's generally better to change `midi_loop_end' before `midi_loop_start' if the end point is moving later in the file, and to change them the other way around if the end point is moving earlier in the file. This reduces the possibility of extra skipping during the change. The chances of either of the above being a serious problem are pretty small, but it's better to be safe than sorry. The second one is purely cosmetic. 8.3.4 Unloading MIDI music -------------------------- To unload a MIDI file use the `destroy_midi' function: destroy_midi (background_music); If it's playing at the time, the music will be stopped first. 9. Timers ********* 9.1 Uses of timers ================== This is a list of some uses of timers. Of course it's not complete -- it's just meant to give you some idea of their most common uses. Some or all of these will appear as examples later on. * Timing a game This is probably the most obvious use. You can use a timer to count how long a player has been on a level, perhaps to give them a bonus later for completing it quickly, or to kill them for taking too long. * Counting a game's frame rate If you increase a global variable each time you draw a frame to the screen, you can set up a timer to copy this value elsewhere once per second. The place to which it is copied will then show the number of frames drawn in the previous second -- in short, it's the FPS (frames per second) number, or frame rate, of the game. This is very useful when you're finding out how well your game runs on other people's systems; if the number is high, they are getting smooth graphics; if not, the graphics are more jerky. * Regulating game speed Earlier you may remember that we briefly tried using a constant delay when moving a circle across the screen, then switched to using `vsync' to control the speed. The former system is fatally flawed in that the speed of the circle still depends on the speed of the computer -- if we delay 10 ms between draws, and the draws take 5 ms, we'll have one draw every 15 ms; but if the draws take 10 ms, we'll only have one every 20 ms. So much for speed regulation. Using `vsync' is better; the vertical blanking intervals occur at regular intervals, so provided our drawing and movement always takes less time than a full retrace (or more accurately, always takes the same number of retraces to complete) we'll get a constant speed. If we take a bit too long on the occasional frame, the game will seem to stop for a fraction of a second, because `vsync' will have missed one VBI and will have to wait for the next one. This actually happens quite a lot under some semi-multitasking operating systems when the OS takes control of the computer just before the VBI -- `vsync' then misses that VBI and, again, waits for the next one. The effect is pretty severe (the game seems to jerk a lot, and worst of all (IMHO) it loses time). The better system is to use a timer to keep the game running at a constant speed. The video updates must still be controlled by `vsync', to reduce flicker, but our timer can effectively see exactly how long `vsync' had to wait, and we can make up for the delay by running through the game logic an appropriate number of times later on. We'll look at exactly how this is accomplished after seeing how timers themselves work. Or you can skip straight to that section now -- it's up to you. See Subsec 9.4.3: Regulating game speed. * Getting input at regular intervals If we set up a timer running frequently we can get input at regular intervals. So what? Well, some game systems (such as the one described above) tend to run through the game loop several times in a row, then go into a long graphics updating routine. They spend a great deal of their time updating graphics, and rush through a number of game updates very quickly when that's finished. If we're reading the input state exactly when the game update is occuring, we'll get just about the same input state for all of the updates. As an extreme example, imagine that the graphics routine took a whole second to complete, and the game update routine was then executed say 10 times, to make up for it. If the user tapped a key during the graphics routine's execution, or even held it down for most of the period, the game update routine wouldn't notice, because it would only be checking for input at the end of the graphics routine. But if the user just tapped the key at the end of the graphics routine, all 10 of the game's updates would see the key as being pressed, which is bad too. What we want is for each game update to see a different input state, taken from samples occuring at regular intervals. 9.2 Setting up timer callbacks ============================== 9.2.1 Initialising the timer system ----------------------------------- Before you can use any of the timer functions, you must initialise Allegro's timer system. As noted earlier on, many other parts of Allegro need this anyway, so perhaps you have already initialised it. These other components include the mouse pointer updating code and the MIDI player. To initialise the timer system, just call: install_timer(); After making this call, the libc `delay' function will no longer work; you should use Allegro's `rest' function, which operates in almost the same way. 9.2.2 Locking and volatility ---------------------------- Allegro's timer routines are driven by interrupts. These are generated on certain events, and "interrupt" the execution of your program. At this point, the CPU starts running the code of an ISR (interrupt service routine). Allegro hooks itself in here in various ways so that it is informed whenever certain interrupts occur -- in this case, the timer interrupt. Allegro's timer interrupt handler calls various functions you specify, called "callbacks", at certain intervals (which you also specify). The key point is that these callbacks are called "inside" an interrupt. For various technical reasons, it's a very bad idea to try to page things to and from disk during an interrupt. This means that you must ensure that your callbacks are never paged out to disk -- if they were, they'd need paging back in during the interrupt. You must also ensure that any data they touch -- global variables, etc -- is never paged out to disk. You do this by "locking" the memory in which they are located. Some functions are provided by djgpp to do this, and Allegro provides some nicer wrappers for them in the form of the following macros: END_OF_FUNCTION (function_name); LOCK_FUNCTION (function_name); LOCK_VARIABLE (variable_name); `LOCK_VARIABLE' locks a static or global variable and `LOCK_FUNCTION' locks a function. `LOCK_VARIABLE' can work on its own, but `LOCK_FUNCTION' needs you to mark the end of the function in question using `END_OF_FUNCTION'. If you forget the `END_OF_FUNCTION' you'll get some odd-looking (but fairly self-explanatory) compiler warnings and linker errors. The other issue that comes up when dealing with interrupts is volatility. Any decent compiler will try to optimise the code it generates. In doing so, it will normally assume that if you don't write to a variable then its value won't change. Normally this is true, but if there's an interrupt-called function that modifies the variable then the variable's value might change without the compiler noticing. To solve this problem you must mark all such variables as being "volatile". `volatile' is a keyword in the C language that directs compilers to make no assumptions about the contents of the variable -- in particular, it will never cache the variable's value in a register. You don't want to make all of your variables volatile, of course. If you did this you'd be restricting the compiler's ability to optimise your code, and would probably end up with slower code. Try to only make variables volatile when they're written to or read from in an interrupt context (that is, inside a timer routine or other interrupt callback). 9.2.3 Installing timer callbacks -------------------------------- When you've initialised the timer system, locked your callback and any data it touches, and marked any such data as `volatile', you can at last tell Allegro to call it. install_int (callback, msecs); Replace `callback' with the name of a function returning void that takes no parameters (in C) or a variable number (in C++). `msecs' is the time between calls to your function. Here's an example function for C: volatile int counter = 0; /* We'll increase this in the callback, so it must be volatile. */ void my_callback_func() { counter++; /* Nice and simple */ } END_OF_FUNCTION (my_callback_func); /* Note the syntax here */ Note that the callback function must be very simple. It must not take a long time, it must not call any C library functions, it must not do anything fancy like calling DOS routines. If it calls Allegro routines, they must be reentrant ones that obey these same rules. If we were using C++, we'd have to change the function definition to: void my_callback_func(...) otherwise we'd get an error. Next, here's the code we'd use to lock the things mentioned above: LOCK_FUNCTION (my_callback_func); /* Lock the function */ LOCK_VARIABLE (counter); /* It touches this variable, so we lock it too. */ Finally, the following line asks Allegro to call our function ten times per second (once per hundred milliseconds): install_int (my_callback_func, 100); Note that even though `my_callback_func' is a function, we don't write parentheses (`(' and `)') after its name -- if we did, the function would be called and its (non-existant) return value would be passed to `install_int'. We're passing the entry point of the function to `install_int', so we don't write the parentheses. Incidentally, you can call `install_int' again later on to change the rate of calls to the function. For more control over your callback functions, you can use the `install_int_ex' function. The parameter to this function is given in hardware ticks, but you can use some macros to convert to this format from more useful units -- seconds or milliseconds per call, or calls per second or minute. See the Allegro documentation for this function for full details, and more information on what timer callbacks should and should not try to do. 9.2.4 Removing timer callbacks ------------------------------ Removing timer callbacks is simple -- just call this function: remove_int (my_callback_proc); In theory, you could now unlock the memory of the function, but in practice there's little point. 9.3 Limitations of timers ========================= Listed here are some things you should and should not do when writing and using timer callbacks (and interrupt callbacks in general). Do: + Do lock all memory your callbacks touch -- lock the callbacks themselves and also any variables they use. If they call other functions, you must lock those too, as well as anything they touch; if those call other functions, you must lock them too, etc. + Do make used variables volatile. If you don't do this, strange things will happen. There won't be any compile-time warnings or errors, and there won't be any specific ones at run-time either, but you'll find that your program doesn't do what it should do. A typical symptom of this is having a loop that waits for a variable to be changed by a timer, and seeing this loop never terminate. Don't: - Don't call DOS functions inside your callbacks. DOS is not in general reentrant, and this causes big problems under most circumstances. - Don't call C library functions -- unless you really know what you're doing. C library functions might call DOS, and as mentioned above that's normally a very bad idea in an interrupt context. - Don't do anything too fancy. Your callbacks should execute very quickly. Just because they're executing out of the main flow of the program, it doesn't mean they aren't burdenning the CPU. The longer your callback takes to run, the less CPU time is being spent on your mainstream code. It's no coincidence that all the examples below just modify a few variables then quit -- that's the sort of level you should think on. Set some variables, then let your mainstream code do the hard work. - Don't ask to have your callbacks called more frequently than you need. Note that under Windows the time between calls must be a multiple of 5 milliseconds; consequently if you want your game to work reasonably well in a variety of operating systems you should definitely keep your times multiples of 5 milliseconds. - Don't install too many timer callbacks. There are a limited number (16) and keep it in mind that Allegro needs some of these for itself. 9.4 Examples of timers ====================== 9.4.1 Timing a game ------------------- This one's fairly simple. We set up a global variable to contain the number of seconds elapsed. We make our timer callback, which increases that number. We lock the counter and the callback routine. Then, when we want to start timing we install the callback routine to be called once per second. After that we can read from the (volatile!) counter variable to find out how much time has elapsed. When the period we're timing has ended, we can either take a copy of the counter, or just remove the callback. If we do remove it, the counter won't be changed any more. As a variation, we could implement a time limit. In this case we'd set the counter to the number of seconds allowed (for example, to complete a level). The callback would now be made to decrement that value. In the main game loop we'd check on each cycle whether or not the counter is positive. If it's not, the player has run out of time. At this stage we ought to remove the callback routine, to stop the number decreasing further. See `examples/chap_09/ex_4_1'. 9.4.2 Measuring the frame rate ------------------------------ This, too, is pretty simple. First of all, we make two volatile global variables -- one called `last_fps' and the other `frame_counter'. Then we make a timer callback that simply copies `frame_counter' to `last_fps' and then sets `frame_counter' to zero. We now lock both variables and the timer callback, then install the callback to be executed once per second. Now we can display the variable `last_fps' on the screen somewhere, or log it, or do what we like with it. At the moment it will be zero. All we need to do to make it count the frames in each second is to make our graphics routines increment the `frame_counter' variable once per frame. This works because the callback routine is called every second, and it copies the value into `last_fps', zeroing `frame_counter'. Next we draw some frames, and as we do so the `frame_counter' variable increases. One second after its previous call, the callback is called again, and it copies the number of frames drawn in that second from `frame_counter' to `last_fps'. See `examples/chap_09/ex_4_2'. 9.4.3 Regulating game speed --------------------------- This topic is my nomination for FAQ of the year on the Allegro mailing list. It's very important. Games that do not run at the same speed on different computers or under different circumstances annoy me. I don't mean that the frame rate should be the same on all computers -- that's not possible, of course. I'm referring to the actual game speed -- the rate of movement of the game characters, for instance. To make this happen, you want ideally to move the characters at regular intervals. Inside a timer callback? No way. That's far too complicated; remember, timer callbacks must be simple. Besides, think how much of a problem it would be to try to lock everything touched by such a callback! The best thing we can do realistically is to make a timer increment a variable, which holds the number of game cycles which _should_ have elapsed by now. Then we can make the game loop compare this to the number of game cycles which really have elapsed. If we're behind target, we need to call the game logic function one or more times, until we're back on target. If not, we don't need to do any more game cycles at the moment. If we are up-to-date then we can go off and do other things, like display a frame of graphics. Displaying graphics is often a slow process, partly because it's just a slow thing to do and partly because it often involves waiting around for the VBI, using `vsync'. While we're drawing graphics, and waiting for the VBI, our timer callback will still be called at the right intervals. We won't be dealing with that immediately, of course; it will have to wait until after we've done the graphics. But when we have done the graphics, we know exactly how many game updates we need to do to get back on target. So, in summary, we set up two counters -- one being increased at a constant rate (the target game cycle rate) by a timer callback, and the other being increased once per actual game cycle, by the function that performs a game cycle. Initially these counters start at zero. On every pass through our main game loop, we first draw a frame of graphics. This will take a relatively long time, in most games. Then we test whether the target game cycle is greater than the actual game cycle (it probably is), and if so we keep doing game cycles until it is no longer the case. Then we loop again. See `examples/chap_09/ex_4_3'. 10. Datafiles ************* The file `grabber.txt' in the `tools' subdirectory of Allegro gives a good description of almost every aspect of datafiles. If any point mentioned here is unclear, or if you want more information on something not covered fully here, that is the place to look. 10.1 Concept ============ By now you can see that a game can require many different files; there is the executable file the users run, then there are the graphics, which could be in several large files with many graphics on each, or could be each in its own file (I tend to use the former approach). We also have the sound effects (one file for each) and the music (one file for each piece). That's just the generic files that nearly all games need. On top of that, each game may have other files describing for example level designs, enemy AI, or other data needed. In total there can be a large number of files. Due to clustering arrangements under DOS, each file must be stored in a number of blocks of a certain size. On partitions of just under 1Gb these "clusters" are about 16K each. Most files won't fully fill their last cluster, and so the remaining space in it is wasted -- on average, assuming evenly distributed file lengths, this would be 8K per file, but it's probably more in practice. This wasted space can mount up, especially if you store many files which are much smaller than even one cluster. If you have Windows 95, try typing `dir /s/v' in your `djgpp\zoneinfo' directory, and comparing `bytes' to `bytes allocated'. After doing this you might consider deleting the contents of that directory, or at least zipping it up -- you probably don't need it. Having many files with `.PCX', `.WAV' or `.MID' extensions also encourages people to `borrow' them for their own purposes, or modify them, changing the game. If you don't want them to do this, it's wise not to leave those files lying around. Quite apart from the above two reasons, it's just messy to have all those files stored `loose', and your game will need to load them all when it starts. Datafiles provide a way of packaging all or some of your files into one big datafile, which can be loaded all at once, optionally compressing and encrypting the data at the same time. 10.2 Creating a datafile ======================== Datafiles are created from files on disk. There are two main utilities provided with Allegro to handle them: the Grabber and the DAT utility. They are both well documented in the file `grabber.txt', in the `tools' directory of Allegro, but I'll give brief instructions here too. 10.2.1 The grabber ------------------ (not yet written) 10.2.2 The DAT utility ---------------------- (not yet written) 10.3 Using a datafile in your program ===================================== There are a number of ways you can read back the data from a datafile. Firstly, you can read in the whole datafile, all at once. Secondly you might want to read one component of the datafile on its own. Lastly, you can open a component of the datafile as if it were a normal file. 10.3.1 Loading an entire datafile --------------------------------- This is the obvious way to do things. With this system, you issue just one function call and Allegro loads in all the objects you put in the datafile. The function to call for this is `load_datafile': DATAFILE *load_datafile (char *filename); You pass it the filename of your datafile, and it returns a pointer an array of DATAFILE structs, one for each object. So if `data' is a `DATAFILE *' and you write: data = load_datafile ("datafile.dat"); then `data[0]' will be the first object in the datafile, `data[1]' the second, etc. The most important field in the DATAFILE struct is the `dat' field. It is a pointer which points to the actual data for the object. If the object was a bitmap, then this field will point to a BITMAP struct -- so you could write: blit (data[0].dat, screen, ...); If the object is a sound sample then this is a SAMPLE struct: play_sample (data[1].dat, 255, 128, 1000, 0); The same is true for MIDI files, palettes, animations and a few other things. In the grabber or dat utility you can see what type of data each object is just to the left of the object name in the list (type `dat -l ', or look at the left side of the grabber screen). You can also tell what type of data each object holds by its `type' field. This will be set to a constant like `DAT_BITMAP', `DAT_SAMPLE' or `DAT_MIDI'. If the data isn't in a special format, this will be `DAT_DATA' -- this indicates that `dat' just points to a block of data. You can get the size of this block from the `size' field of the DATAFILE struct: load_map_data (data[2].dat, data[2].size); would pass the pointer to the binary block and its size to the `load_map_data' function (which would be one you'd write yourself). Now, how do you know which object number in the datafile corresponds to which object you put into the file? There are three ways you can do this, and as always which way you choose is entirely up to you -- depending on the circumstances you might want to mix together all three methods. The first way is to use the header file optionally created by the grabber or dat utility. This file will contain one macro for each object, #defining the name of the object to its index in the datafile. So instead of using what I wrote above you can say: blit (data[MY_BITMAP].dat, screen, ...); play_sample (data[MY_SAMPLE].dat, 255, 128, 1000, 0); load_map_data (data[MY_MAP].dat, data[MY_MAP].size); This way, if you add other objects to the datafile and they get put before the old objects you won't have to change all the indices in your code. The text of each #define is exactly what you entered in the grabber as the object's name. If you used the dat utility to create the object its name will probably be its original filename in upper case, with the `.' replaced by `_'. This replacement is done so that the resulting object name is a valid thing to #define in C -- dots aren't allowed. Along the same lines, when you're making your object names you should bear in mind that they'll be used as #defines in your program -- don't pick anything you might want to use for something else (e.g. `main', `BITMAP', etc). You can use the `Prefix' option of the grabber or dat utility to prefix all the #defines in the header file with something, e.g. `DATAFILE' -- then you'd have: blit (data[DATAFILE_BITMAP].dat, screen, ...); if the object's name was `BITMAP'. The second way of knowing where an object is in the datafile is by relying on the fact that they are stored in alphabetical order. According to the author, this is true now and always will be. I don't recommend overusing this feature, but it can be useful if, for example, you have a number of bitmaps (all frames of the same sprite, perhaps) called `ENEMY_000', `ENEMY_001', `ENEMY_002', etc. You can use the #defines to refer to these, of course, but it's more convenient to be able to pick one of these bitmaps by number based on a variable. Because of the way preprocessing works (`pre' = before, i.e. before compilation) it isn't possible to change `ENEMY_000' to `ENEMY_xxx' at run-time. But, since the objects are in alphabetical order you know that `ENEMY_000' will be stored first in the file, and `ENEMY_001' will be immediately after it -- i.e. the nth enemy bitmap will be object number `ENEMY_000 + n' if, as all sane programmers do, you start counting from 0 (so `ENEMY_000' is the 0th object). Then the following will work: void draw_nth_enemy (int n, BITMAP *where, int x, int y) { draw_sprite (where, data[ENEMY_000 + n], x, y); } The third way of finding an object in the datafile involves searching through the array for an object with the right name. The object names are stored as "properties" of the objects. For full details on properties, see the `grabber.txt' file. Briefly, though, to get the name of an object you write: name = get_datafile_property (&dat[obj_number], DAT_ID ('N','A','M','E')); Note the `&' -- you pass a pointer to the object in the array. If the string you are returned is empty (i.e. name[0] == '\0', _not_ name == NULL) then the object doesn't have a `NAME' property. This probably means you stripped the properties from the datafile; in this case you'll have to use one of the first two methods to find your objects. A couple more things are worth mentioning here. Firstly, if you're using C++ then the compiler won't like you passing the `dat' field (a void pointer) to functions which expect some other type of pointer, so you have to explicitly cast it to whatever type the function wants: play_midi ( (MIDI *) data[MUSIC].dat, 0); Secondly (and somewhat linked to the above) it is often convenient to alias all the objects in the datafile, by creating global variables or arrays, something like this: BITMAP *enemy_bitmap; MIDI *background_music; SAMPLE *crash_sound; then setting them to point to bits of the datafile: data = load_datafile ("DATAFILE.DAT"); if (!data) barf(); /* loading failed */ enemy_bitmap = (BITMAP *) data[ENEMY_BITMAP]; background_music = (MIDI *) data[MUSIC]; crash_sound = (SAMPLE *) data[CRASH_SOUND]; and finally using them in the game: draw_sprite (screen, enemy_bitmap, x, y); play_midi (background_music, 1); play_sample (crash_sound, 255, 128, 1000, 0); The advantages of this are that it gets rid of the annoying casts needed for C++, it makes the later code neater and slightly faster, and it also makes it easier to change the code from being datafile-based to being file-based and vice versa. Thirdly, the datafile array is terminated by an object of type `DAT_END'. You may or may not find this very useful; it's semi-redundant information, a bit like the fact that `argv[argc]' is NULL. If you want to search the whole datafile for something (perhaps an object with a certain property) you could use this fact to know where to stop. Finally, when you've finished with the datafile you can unload it using the `unload_datafile' function: unload_datafile (data); Needless to say, after doing this you must not use the data array any more, and if you have any aliases to objects in the array (as above) you must not use them either. See the example: `examples/chap_10/ex_3_1' 10.3.2 Individually loading datafile components ----------------------------------------------- You can load an individual datafile object on its own provided the name information is still in the datafile (i.e. you didn't strip all the properties in the grabber, or apply `-s2' in the dat utility). The function to do this is `load_datafile_object' and it is used like this: data_object = load_datafile_object ("datafile.dat", "object_name"); `data_object' should be a `DATAFILE *', just as if you were loading a whole datafile. To access the object you just dereference this pointer, e.g.: music_object = load_datafile_object ("datafile.dat", "MUSIC"); play_midi (music_object->dat); /* or music_object[0].dat */ Since this loads only one object, it returns a pointer to a single DATAFILE struct, not an array of them. So you don't use the object's index from the header file, and also there is no `DAT_END' object after the returned object. To unload an object loaded in this way use `unload_datafile_object': unload_datafile_object (data_object); See the example: `examples/chap_10/ex_3_2' 10.3.3 Reading a datafile component as a normal packfile -------------------------------------------------------- A handy feature of Allegro's packfile routines is the ability to read a datafile object as if it were a file on disk. To do this you simply use the encoded filename format `datafile_filename#object_name', for example: fp = pack_fopen ("datafile.dat#MUSIC", F_READ); Then if `fp' is not NULL you can read from it as you would read from a normal packfile. You can't write to the file. To close it you use `fclose' as normal. See the example: `examples/chap_10/ex_3_3' Index ***** This section is generated automatically, but I haven't bothered to mark any index entries yet, so it's empty at the moment. Table of Contents 1. Introduction 1.1 About the tutorial 1.2 Aim 1.3 Target audience 1.4 Requirements 1.5 Before we start@dots{} 2. Getting, installing and using Allegro 2.1 What is Allegro? 2.2 Where to find Allegro 2.3 How to install Allegro 2.4 Testing the installation and building the Vivace example programs 2.5 Using Allegro 3. A basic game structure 3.1 What does a game need to do? 3.2 Proposed structure 3.3 Multi-file projects 4. Introducing graphics 4.1 Selecting a graphics mode 4.2 Drawing things 4.2.1 The BITMAP struct 4.2.2 Plotting pixels 4.2.3 Some other primitives 4.2.4 Writing text 4.3 Palette manipulation 4.3.1 Palette explanation 4.3.2 Changing a single logical colour's physical appearance 4.3.3 Changing the entire palette 4.3.4 Fading in and out 4.4 Simple animation 4.4.1 What is animation? 4.4.2 Making things appear to move 4.4.3 Reducing flicker 4.4.3.1 Synchronising to the vertical retrace 4.4.3.2 Maximising drawn time 4.4.3.3 Optimising drawing order 4.4.3.4 Double buffering 4.4.3.5 Dirty rectangles 4.4.3.6 Alternate line blitting 5. Making several things happen at once 5.1 Moving more circles 5.2 Now moving a square as well 5.3 Keeping track of things 5.3.1 Dynamic allocation 5.3.2 Linked lists 5.3.2.1 Singly linked lists 5.3.2.2 Doubly linked lists 5.3.2.3 Circularly linked lists 5.3.2.4 Using linked lists 5.4 Object Oriented Programming 6. User input 6.1 Keyboard input 6.1.1 Why the standard PC keyboard routines are useless 6.1.2 Allegro's keyboard routines 6.1.3 Moving something using the keyboard 6.1.4 Letting the user choose which keys to use 6.2 Joystick input 6.2.1 Digital readings 6.2.2 The fire buttons 6.2.3 Analogue readings and calibration 6.3 Mouse input 6.3.1 Point-and-click 6.3.1.1 Initialising the mouse 6.3.1.2 Reading the mouse 6.3.1.3 Displaying the mouse pointer 6.3.1.4 Controlling the mouse pointer 6.3.2 Direct mouse control 6.4 Generic input 6.5 Ways of interpretting input 7. More 2D graphics 8. Sound 8.1 Sound configuration 8.1.1 Initialising sound drivers 8.1.2 Using a configuration file 8.2 Digital sound 8.2.1 Loading sound files 8.2.2 Playing samples 8.2.3 Adjusting a playing sample's parameters 8.2.4 Unloading samples 8.2.5 Voice functions 8.3 MIDI music 8.3.1 Loading MIDI music 8.3.2 Playing MIDI music 8.3.3 Controlling MIDI music 8.3.4 Unloading MIDI music 9. Timers 9.1 Uses of timers 9.2 Setting up timer callbacks 9.2.1 Initialising the timer system 9.2.2 Locking and volatility 9.2.3 Installing timer callbacks 9.2.4 Removing timer callbacks 9.3 Limitations of timers 9.4 Examples of timers 9.4.1 Timing a game 9.4.2 Measuring the frame rate 9.4.3 Regulating game speed 10. Datafiles 10.1 Concept 10.2 Creating a datafile 10.2.1 The grabber 10.2.2 The DAT utility 10.3 Using a datafile in your program 10.3.1 Loading an entire datafile 10.3.2 Individually loading datafile components 10.3.3 Reading a datafile component as a normal packfile