Waffle Racing Tutorial

Waffle Racing Tutorial

Back to Top

Why Make Games

Why not? Some people build bird houses, so I make games. Its just something to do for me. Its fun. To me its like doing a puzzle and trying to make all the pieces fit. Or, maybe its like making playing with a model railroad set ... never really sure how it will all fit together, but its fun to do. Its always a challange. I don't make any money at this, and don't expect to. There are many people much more talented than me, so I write my little "clumsy" games and give them away.

A main reason I got envolved with writing games was school. When I was in high school (1979?) it was a weekend hobby for my friends and I. Mostly when we could not think of anything to do (D&D) and between farming jobs or mowing yards. Later, school wanted programs to demonstraight programming concepts and those would turn into mini-games. Then, after C++, I returned to game writing just to stay in practice. My C++ was never very good, so I took other kinds of jobs ... Then, DBC caught my eye. So I play with DBC because I need not learn anything truely new. It is easy to crunch up a basic project in a few days or less. Now, I am retired, so I have lots of idle time and this is my something to do.

Back to Top

Why A Tutorial

Again, something to do. I have not done an open source project in a long time. TutWaffle.zip (my multiplayer tutorial) was well received in its time, a few years ago. It was based on the monster hunt tutorial by Lee Bamber. I had upgraded it to support 9 players online(or AI players offline) and even did a DBPro version (still available). I have had to stop using DBPro because my 3D card is only 8MB and although DBPro claims it will run on my system, well, it does run ... but it chokes. FPS stays around 2. DBC plays very well. FPSC .... chokes bad.

So, for fun, I thought to do another tutorial. Something more involved than just a tech-demo. My plan here, is to do a full-size project from begining to end showing all the important things that are needed. Some things, nobody wants to talk about. This will also bring extra attention to my IDE. Not because its good, but because I will be using here, along with my other tools. So, this is a tutorial that will promote my tools, and my website. Won't make me any money, but will give me more something to do. And, maybe, someone with more experience using Scintilla will tell me how I can improve those areas which seem to be slow? or just poorly written.

Back to Top

Why A Racing Game

I am not too found of FPS, and there are just too many of those out now. Puzzle Games are hard to "invent". RTS is mostly just to complex. I may do a "Sports" game later to experiment with RTS ideas. So I'll do a racing game. And, since I tend to go for stratedgy, I'll try for something almost realistic. I do hope that does not translate to "boaring". My ski race (snow mobile) game tended to be that. Even though the race was limited to 10 laps and supported 10 players online, and featured 9 AI players, each with different skill levels. But, it did not support collisions and did not have a level editor. It was more of a tech-demo than a project. I coded it very quickly for a XMass competition (my first and last one) and was going mostly for the deadline than features. I think mine was the only multi-player offering, but was not "FUN" offline. So, maybe this will be my attempt to do a more complete racing game.

Back to Top

Where to Begin A new Game


There, I said something nobody wants to talk about.


My list of notes is over 10 pages at this time. Some might call this a design document, but its not. That comes later. You should always start on paper. Paper is easy to throw away if you decide thats not a good idea. Also, paper is easy to cary. I have a notebook I carry everywhere. Whenever I sit down, waiting for a bus, at dinner/lunch, no electricity (I use solar power and have no internet access) I open my book. Add/delete/ update notes. Play with your notes until you can't think of anything else for your game or application to do. Also remember to devide your notes up into a plan of attack.

The reason paper is so important is you need to have your notes as a reference for writing your design document. So, in your notes, you need to list how many #Include files you will need. Detail any problems you need to deal with and describe how you will tackle those problems.

I want the user to select from a list of maps to play
So, I need to decide how to manage that. Will it be a list of Map names? Use map pictures (how to get map images)? Have the user press a key (1-9)? yucky ...

Details, details details. These decisions need to be in your notes, along with at least some idea of HOW to do it. If doing a team project, this becomes even more important. Because then you need to decide who does what, and need to make lots of place holders so that these improvements can be added when done, while still permitting work to be done on other parts of the game. These are the kind of things I wish to demonstrate. How to setup a placeholder.

Also, in your notes, try and come up with a plan for managing all your variable. This may seem very simple, but when you deal with a very large project and need to ask "What was the name for that again?" you will get the idea. My method for this is to sort my global variables based on usage and then declare them in an #Include file using a Macro and then call the Macro from the main source. This way, I can quickly jump to the Macro definition to add more, and jumping from section to section is very quick.

Back to Top

My Design Doc

Yet another Car Racing Game by Waffle

We will make a multiplayer car race game using only media provided by DBC. This helps simplify project creation because all media is free to use and distribute.

WaffleMat will be use for map creation and WaffleIDE for the code editor. This is important because WaffleMat can export maps in *.wmt or in *.dba format; and WaffleIDE can support compiler directives so that from the IDE we can make custom builds to enable the end user to edit the maps or not.

And then spruce it up a bit by adding extra decorations and animations for Pit Crews, Emergency Crews and so on.

We will procede from a top down perspective. Begin by picking 3 textures for the map and blending them together in WaffleMat and then create an autotexture array. Then, using this, create 3 test levels. From here we procede to first operational test. Just simply load the level into our game and setup the basic commands to move our car around and see how it looks.

Next we expand the basic game to support all Directives via CompilerIF ... and place lots of placeholders for extra code and begin updating Globals, Media, Text and so on as we progress.

Begin making the dialog tree. We will use text at first just to get the sequence working right.
Note: the Host, Offline, and Join-Host dialogs are all the same, but with different features turned off or on. Offline players can't chat, Joining players can't change maps and so on.

That should cover the basics. Gives a pretty thorough description of what I am trying to write and how I expect to write it. Not all problems are known or solved, but as many are described as I can think of. Also, by using #Include files, we can test our functions all alone in the include file without messing up our game as we write. Another benifit of the map editor is rapid game creation. We can have a working level in a few hours. Probably in less time than it took me to write this Doc. But, its still a good idea to write all this stuff out. For example, I originally thought I only needed 3 waypoints for the AI to work. But while explaining about a figure 8 track I noticed that there was always a way for the AI to pick the wrong direction at a crossroad. So, I needed 5 (1 more than the number of directions) to prevent the AI from becoming confused. I think I have tranfered all of my notes to this design document, so now we can procede to the fun part .... Level Creation.

Back to Top

Level Creation

First we locate 3 textures to use. DBC provides many, but they are 256x256 which takes up alot of 3D memory. My laptop only has 8MB so I need to be concerned about that. I capture the first 32x32 from each texture (using MSPaint) and save them as ...

Then we start up WaffleMat and blend the 3 textures to make an autotexture array. Since we are using road tiles, we need to blend the tiles in a specific order. First we select the grass texture. Next the dirt texture and lastly the road texture. This order means grass can be used anywhere and is blended with the dirt texture. The road texture is not blended. Instead, road tiles are textured with the road texture, and then dirt is used for the sides of the road. So our tiles look like:

Now, we set up our height map by setting the height levels for the first 3 tiles ... Grass to 5, Dirt to 0, and Road to 10. The theory here is to have the dirt look sorta like a ditch around the track.

Now, to do a basic oval track ...
Make a medium size world, about 50x50 tiles
and our textures are 32x32 and lets keep the world size the same as the tiles for simplicity...

Then check the Autotexture box. WaffleMat will then generate a full heightmap for all tiles based on our settings. Now we simply draw our map using a road tile. And enlarge the screen and we get something like ...

Now we add some AI markers to identify the direction around the map. Also, found a new problem. How to mark the pit lane and pit stops? The track AI will use markers 1-5, for the pit lane, 6 marks the start of the lane, and marker 7 marks the "slow down" point. Players going to fast past this point get a 1 lap penaulty. so, 6-9 and repeat for the lane, then use marker 0 to identify end of pit lane (Players can begin speeding as they head towards this point). Use letters A - J to mark actual pit stop locations, A=Player 1, J=Player 10. K - T for car starting locations on the track. So, we setup the overlay like so ....

Notice the size is set to 100. This is a percentage of tile size. So, 100= 32 pixcels in our case. We do this because only one object per tile location is actually saved. It helps prevent crowding. Then repeat for the other 2 levels, figure 8 and double 8. The complicated thing here, for level design, is AI. The is especialy complex for the double 8 track. We need to ensure that the AI does not get comfused in decideing which AI point to follow. So, we need to watch for two points of the same number being close together. Not to worry too much though, because when we test the levels, we can watch the AI and adjust any waypoints later.

During testing, I found some problems with the AI and how the waypoints were used. To improve precision I decided to place the waypoints into the bitmap (texture map). This method I do not need to ensure the waypoints are on the road, or even visible to each other. But, I will need to modify the bitmap during loading to remove the odd colors:
The overlay system is still in place and is used for actual pit locations.

Back to Top

Operational Test

This is where the fun begins. This part is pretty simple too. The #INCLUDE file Download wmat.dba contains most of the code required to load the level, so we need only focus on specific items such as controls, camera start, models and other details. Since this is just an operational test and not a full game, the code is very simplistic. And we'll do our test using the oval track.

optest.dba contains our operational test code. You can see I already adjusted the size of the model and have a working game, sorta. You can drive anywhere and the map scrolls properly. The camera controls work as does the basic controls. Left/Right to turn, Up/Down to move. No physics or math. Nothing fancy, just an operational test load of a level.

This verifies that all our #Include files seem to work. Also shows us what it looks like. I did find a few problems with the heightmap. Because there is no transition tiles between the road and the grass, dirt and road need to be the same height, to look right. So, I adjusted the heightmap to set grass at 10, dirt and road at 5. This seems to look pretty good. I also adjusted the model to make it face properly. But all these things are pretty simple and straight forward. The key step here was proof of concept. Getting the roads right, the maps right, and finding the start point.

The car still does not start on the track, but we were expecting that. Remember we placed our markers off the track by 1 tile? That was to have the car start on the side of the road, so the next car could be placed next to this car. We will wait on that for when we start placing all objects, and correctly placing all the AI points. For now, the operational test is done.

Next we need to copy our design document into our project. This sounds complicated, or redundant, but is very crucial. In truth, it may be redundant, but its also very easy. Its part of how to take a very large project and break it down into smaller chunks that are easy to do.

The simplest way would be to copy our design doc as commented text into the source module. For most, that would be enough. Others may settup a to-do list (the DBPro IDE has one with fancy check boxes). What we will do here is a combination of the two. Lots of comments, place lots of dummy functions and line labels, and setup all the compiler directives. No real code, just lots of place holders for things to do.

So we set up all those compiler directives:

And I thought of a few extra to add in ... AIMode(1)=1 to enable all cars as AI only, so we just watch the race. ViewAI(1)=car to show the the strategy in use for that car. NoBank(1)=1 to turn off road banking. I'm sure you might be able to think of a few more. This is just to get started.

Next we need to actually place the COMPILERIF .... for each directive we wish to use. That's why I do them in the IDE first, so I can quickly see the list at any time while adding them into code. Adding to code then to the IDE is much harder ... if you can't remember where in the code ..

We will also stick in our template dialogs. We will use ****DLG1() to identify this is only tempory dialog functions. The first draft is just your basic text list option where the player presses 1-9 for an answer in the dialog. Very basic stuff. Later we upgrade to using a list of buttons and such. For example, our display options.

So, a typical dialog looks like Download typdlg.dba or you can examine any ***DLG.dba source file and examine the function ***DLG1() function. And our source with the Design Doc included looks like Download withdoc.dba. You'll notice I added another directive; UseEscapeKey(1). When this is turned on, a race can be terminated using the EscapeKey and another map can be selected. If not used, the ControlKey fills in for this purpose. I also started codeing for the advanced keys, although not tested yet. As you read through the code, you'll see lots of placeholders. Much of the math will be done using trial and error, to see what works and what does not. Same with the collision system. I wanted to demonstrate the perfomance differences between using math methods and using DBC methods.

Back to Top

Player Options

For me, this is the hardest area to write. Not because its is difficult to code, but because its not fun to write. I much prefer to delve into the math and memory management things that involve the game instead of trying to make the game pretty looking.

So, for this part I sorta cheat and write a general dialog system that can easily be imported into any game without any effort. The #Include file Download DialogMan.bas contains my general use functions for creating simple buttons based dialogs. By Dialog I refer to a complete list of options for a user to select from. After the user selects and option, the dialog closes and frees up any media used by the dialog and returns the option chosen by the user to the main program.

If you examine the typical dialog Download typdlg.dba you'll see it basically performs this purpose, badly perhaps, but does the job. For home use, or among friends, this may be just fine. But for the internet, you need something friendly that works via a mouse. So, to start, we need to draw up some buttons and we need a background image to use. You could use screen shots, or just go with the basic black, or get as fancy as you like.

For my buttons, I like to use a fontsize of 20, and make the buttons 30 high and wide enough for the text, but so all buttons are the same size. It just helps simplify the layout if everything is the same size. So, my basic button (240x60) looks like :

And then I simply stick in the text. Use Dark Grey for disabled options, and White for enabled. For a background image, we will start simple with a basic black with text at the top and my logo at the bottom. Later, we can switch to random screen shots for variety. So, the background looks like :

I use 512x512, even though that's sorta big, but its a power of 2 (not required for DBC, but for other DX versions it is(DBPro). Also, 512x512 helps retain detail when the image is stretched to fit the screen (640x480). But if you use a screen shot, then this does not matter. There are GUI builders available, but I prefer to use paper to get the Idea then then tinker until everything fits. For simple dialogs, this works best. For complex ones, such as the Game Setup dialog, the paper needs to be exact. For me, GUI builders always seem to lack something. As I walk you through the dialogs, you can see what works well, and what does not. The Main dialog is very simple, and looks like:

And the code is in function MainDLG2() in Download maindlg.dba The test for the code is at the top. You'll notice that the required #Include(s) are there too. My IDE (Waffle's IDE) will ensure that duplicate #Include files are ignored. This makes it very easy to include test code inside of the #Include file. In a large project with lots of macro's and lots of code and so on .... build times can become quite large. So, we need to find ways to test our code inside the #include where the build time is smaller. Sometimes, that's not easy to do, but it is important to learn the idea as soon as possible.

Yes, I said I would try and say a few things nobody else wants to talk about. Testing code snipets in an #include file is very important and should be done often. That is how a new programmer builds a code library. And, experienced programmers build projects from this code library. This concept is important to use as this is the only way you can compete in all those online game programming compitions. The larger your library, the more generic the code, the better your documentations, the greater your chances at completing a project in time. Notice I say completing, not winning. But, if you can't finish, you will not win at all. So, focus on completion, and do not try to include too many KEWL things or you will be doomed. Unless, all those things are already in your library and have been tested over and over and are just waiting for the right moment to be shown.

The Display Dialog look just like the Main Dialog, so I won't cover that one. Only it uses 2 state buttons, to show the currently used display mode with a blue background. The next one is more complex. We need a Join dialog that needs to accept user text for an internet address and player name. It still need a few buttons before proceding, but thats about it. Its more complex, but not that much more. We just set the background sprite to transparent, and use our A$=GetChar$(A$) function from the Text.BAS module and done. One minor problem ... Setting the sprite to transparent cause DBC to change the backdrop color, so we need to reset it to black again.

And the code is in function JoinDLG2() in Download hosttest.dba

Our final dialog is more complex. Its the game setup dialog. This one gets pretty fancy because its used during multiplayer as well as single player. So it needs to support internet chat, player colors for the car, and map selection.

After drawing all our buttons, we need a silly place holder for our map selections. It looks very very silly, just some hand drawn lines using MSPaint, but conveys the point. Later we will have some much better images. For now, this is fine. The completed dialog looks like

In case you were wondering how I did the layout, I roughed it (Just guessed where they go) and then called the AdjustDialog() function instead of the usual DialogMouse() function in the Download DialogMan.bas module. This function (hide the mouse before using) will permit you to move and resize the buttons (see docs inside function) and provide the actual location and size of the buttons. Then, I write them on paper to re-enter into the code. The full code is in function MapDLG2() in Download maptest.dba

Thats it for our dialogs. We still need to hook them up, but thats for later. For now, we just want the interface working. Next we will add our multiplayer code and while doing that, we will finish up adding the remaining code to the interface.

During testing, I found a DBC bug that results in a GPF during run time. If a matrix is loaded, then deleted, and you then try to change display settings, BOINK GPF crash. So in the Setup() macro I added a variable "BlockDisplay" and set this to a 1 if any level gets loaded. Then call MainDLG2(BlockDisplay) to enable/disable the change display mode button. There is always something. If you should incounter any odd errors, try and duplicate them in a seperate code module. Its amazing what you learn when trying to duplicate a strange error.

Back to Top


Many try to do this part last, and have many many problems. We will do this first !! Its much easier to take a multiplayer game and play it as single player, than take a single player game and make it multiplayer. The most common problem is not the code for creating a game, but rather trying to get the correct data sent and received at the correct time. If you code as single player first, the problem becomes compounded by: As you can see, there are many concerns to worry about. But, most can be handled easily. The main intent here is to get things mostly working so we can focus directly on actual game play later. Also, by focusing on multiplayer first, we build up our code library so that in later projects, as many problems as possible have already been solved.

And be sure to check out the message loop MapMessage() in mapdlg.dba. This is used to manage all data while inside this dialog. Once the game has begun, this function needs to be copied to the main program for extra costomizations. A version is kept with the dialog just because it could be reused in another project with very few changes. Remember, Always try to write reusable code!

Now that we can setup a game, we need to load the players. Again, we will keep things simple and leave the special things for later. So, we need to revisit our Download withdoc.dba to see LoadCar() Macro to add in AI/Multiplayer things to identify the PLAYER using the car, and the PC that shall be controlling the AI for the car. If playing offline, this PC controls the car and all cars except player 1 (unles in AI mode) are AI cars. Or owned by player 0.

Then add in a message loop to LetsPlay() and we should have a working multiplayer game that supports up to 4 players and does map seletions. Sure, it still needs alot of work, but this gives us something fun to play with while we work. You'll notice the message loop looks alot like the one from the MapDLG. Thats because it was simply copied, and then edited to remove the dialog references, and to add in extra code for moving objects. We have barely scratched the surface on multiplayer stuff, but this covers the basics. We still need to compensate for lag times, but thats for when we a ready to fine-tune the game. Its really too early for that kind of adjusting. The priority here is to get it working. Then, add our required features, and then tweak it for anomalies. Anomalies are not always bug, but rather a feature that does not work as desired.

And lastly, add a waiting screen to the end of the PlayLevel() function before it calls the LetsPlay() function. This just locks up the game until all players are ready. And then starts a count-down to start the race. Again, nothing too fancy. Most of the code is in the Download mapdlg.dba to see PauseGameStart() function. It just uses text to clearly state the status of each player. After all players have completed the PlayLevel() function, the game starts automatically. So, we added a 5 second delay after the PauseGameStart() function for players to get fully ready to race. And we need to copy the car/map placement code from LetsPlay() to just before our PauseGameStart() to get the car actually on the map. Now we are done with multiplayer, except for the tweaking we will do later.

Back to Top

Math Problems

Now that we have a working game, we can start making it better.

Lets start by ensuring the camera stays above the map to hide those wierd clippings when hills get too close. I'll do that by moving the camera 10 back from the car, and up 5. This gives a 30 degree down angle for the view. Now we just point the camera at the car. While we are at it, lets shift the car up by 0.1 so its not stuck in the matrix.

Next up lets add some pitch and roll. This gets a little wierd, but we can hide all the wierd stuff so we don't need to deal too much with it. All that wierd stuff is in the Download objectmacros.bas to see TiltObject() macro. All we need to is change the Download carrace.dba to see LoadCar() macro to add in a tilt object to the car. And then when we update the cars position, we just call the macro to do the dirty work.

Notice how the car starts far away from the road. Lets fix that. When we detect the marker, we need to locate the intended road. And then place the car on the side of the road. To do this, we can sorta cheat. Remember, odd players are on one side, while even on the other? So, the road center will always be at the mid-point. Thats a simple math equation using the average of two points to obtain the center point. Also, we need to shift our markers by 1/2 our tile size to re-center our markers on the tile. This error was caused by our setting the object size to 100% to limit only one object per tile in Waffle Matrix Editor. It does simplify level loading, because if we load the level using the #Include method, we get the same error. So, we add 1/2 to X# and subtract 1/2 from Z#. Not sure why the difference, but from trial and error, this seems to work.

Now, set the NoCars(1) Compiler directive to 0 to show all cars. Verify all are on the road. Now, we are ready to begin collision testing. The cars don't move yet (No AI) so its easy to test ideas.

Since we are crazzy, and want to compare different collision methods for performance; lets start with DBC rotated box colision for the cars. No other objects get collision testing. The pitcrew and other such things are handled in a different manor. This method seems fine, for now, but lets see what happens when we load all objects in and start checking collision data between many cars....
You'll notice that I used DBC for Compiler Directive DBCHits(1)=1,2,3 and Sphere for DBCHits(1)=4. Here is what I learned from this test: The lesson here, always be prepared to experiment.

Next, lets fix-up the cars a bit. Sure, we could just re-color the limb that is the main body, but that's just too easy. Also, we may want exploding cars and other effects later. We could try loading the .X object into our favorite modeler (Milkshape for me) but thats violates our design doc. Remember, this is a tutorial, the premis was to use only what's available out of the box, plus Waffle's IDE. DBPro has a "MAKE MESH FROM LIMB" command, that DBC is lacking. So, lets make one and then take our car and break it up into separate objects, and then remake the car.

This seems kinda silly, but its the best way to show what you can do with what you have. Also shows many other tricks. As a benifit, you can have the cars wheels fall off, bounce, whatever. Also, we want to do some mesh deformation to the main body as the car takes damage. The technic shown here is too slow for actual animation (although it is possible) but is something fun to play with if you have time.

So, we call up Waffle Viewer and then examine the limbs. We need to divide up this into And soo, we add a special function RebuildCar(ID) to solve this. Now, for a flat tire, we can either lower or hide the respective limb. Also makes a pit stop for a tire change look better. And, for a crash, we can make each wheel fly about randomly.

Since this is supposed to be a tutorial, I need to talk about some things. The reason for all the placeholders is to provide a basic structure to our game. And, to try and keep our code a clean as possible. You'll notice the dialog loop is pretty clean and straight forward. All the complex stuff is hidden in the respective #Include file. In our original design doc, we were supposed to create an #Include file for our game functions. For simplicity (so I don't need to rebuild the #Include files every time) I am deviating from the doc. I am also deviating by not testing multiple collision methods (at this time). The DBC method seems pretty resonsive with only 10 objects to check.

For complete beginners, this tutorial may seem very complex. But, you can easily modify various areas and even use the level editor to make your own maps. As you delve into the code; the key areas are PlayLevel() and LetsPLay() functions. The things to learn here are Functions, Globals, Macros and Select - Case. And also CompilerIF ..... You'll see how I tryied to keep the event loop (What happens when you press a key) clean and simple. Sometimes that is not possible. If you check out the PlayLevel() function you'll see that I use the LoadCar() macro to keep things simple. But I failed to do that with Player 1 car. Technicaly, that was a mistake on my part.

During initial developement, all we had to do was Load a Car and show it. As more features were added, this got more complex. Then, we were ready for the other cars, I copied player 1 code to the macro and then trimmed out code not required. Another factor was Player 1 is used as a reference point for all the other cars. Therefore it required special code. I mention this here to show that although some code looks complex, many times its just poorly written. The other extream is the MakeMeshFromLimb() function in the MediaFunctions module.

More advanced users may wish to examine that function. Its very messy, but thats mostly because of how I chose the solve the problem. To make a mesh from a limb required
and so, that function got pretty messy. But shows what we can do if we don't mind getting very dirty and doing a wee bit of reading. And don't mind if the docs are not exactly clear (In the help files it states that face data is 2 bytes, stored as a DWORD , so, is that a WORD or DWORD?) It took some trial error to find out. Hows that for a little trick for the more advanced users to check out? I said I would hide a few things here and there for you. Also, check out the SaveMesh() function too. I used that to make a mesh from each limb, and to divide up the wheels into 1 each, and save each limb as its own *.x object. whewww! A wee bit of work on that.

But, now when our cars crash, we can copy the body mesh into a memblock, shift the vertexes around to show damage, and then use the new mesh for the body. A bit slow work to do often, but we can manage our time by skipping all the other math items during that update. See the WreckCar() function for details.

Also found a few more bugs with DBC? or maybe my PC? Either way its was fun to try and work around them. The problem had to do with how DBC manages memory. Sometimes using my MakeMeshFromLimb function I would get a "Severe Exception" error. It would work fine when I used lots of delays or break statements, but die in my main project. So, my work around was to save the meshes as *.x objects and simply load these meshes into the main project. Yes, you need to expect things like that with DBC and even DBPro from time to time. It goes with the job. Just accept it. If you can duplicate the error, then you can learn to work around the error. That's when the fun begins. Don't just give up .... Step up. Lee Bamber has dropped support for DBC because DBPro is better in some ways. But for older PCs, DBC is still the best thing going .... bugs and all.

Lets make our cars drift a wee bit when sliding. See the Drift(1) compiler directive for the full example. In short, we calculate the ammount of "lateral" movemement our car makes during a "regular" move. Then we simply add a "factor" to this to get a sorta drift. The full math is rather complex (requires computing multiple directions of movement) so I took a few shortcuts. I compute slipping as a rate of turning. That required changing the method of turning. Instead of:
if LEFT KEY() then AY#=AY#-1
we have
if LEFT KEY() then Turning#=Turning#-1
and then later in code we update AY# by
This is redundant, but simplifies the slipping code because I simply use COS(Turning#)*Factor*Speed# to determine slipping distance per update. Its not perfect or even accurate, but it does produce some interesting effects that are predictable. Perfect math is nice, but slower. Another example of an optimizing method. And we take front/rear trim and use that to modify what happens when the car slips. Again, not truely accurate, but fun.

Now, to give each car its own unique color. This could be done by using the color limb function, but thats too easy. Also, that would create a 256x256 texture that's only one color, and that consumes alot of 3D memory and I like to keep that under control. So, we will do a bunch of 4x4 textures. See the PaintCar() function.

That is a slight deviation from our design doc. The original plan called for permitting players to change the colors of the cars. But, that gets hard to manage (like if two cars want the same color) so I went with changing the colors based on player UID (order that players entered the game). Just thought to point out that even if you have a design doc, and even though you should try and follow it, sometimes, you need to change something because of an unforseen problem.

Only one last complex thing to do before proceding on. We need a way to detect if we are on the road or not. We'll do this by making a memblock of the matrix texture and then examine the colors in the memblock. This method is very fast since we do not need to use the slow DBC bitmap commands. See the OnRoad() function. Now we are ready to procede to AI and make this a functional game.

Back to Top


So we are ready to get rid of our Text based Gages and move onto something much nicer. In my helicopter game "Pilot" or "Raid on Bungling Bay" I made the gages using MilkShape3D. But, we can do the same thing (almost) and much simpler using code and MSPaint. We will just do a basic blue background with a few markers. So, make a round blue dot, set the outside to black (for transparency) and do some white lines from the center for the markers. Done. It looks like...
Then, we just stick a basic arrow on it and thats our basic gage. We will use this for RPM and Speed/Fuel. And ghost it so it looks less tacky on the screen. The code for this is just
Make Object Plane ....
Texture Object .... for our background
Make Object Triangle ... for our pointers
so, nothing overly complex. We just set the sizes to 1x1 to make adjusting easy. Then, using the CLI (pressing the Escape key during execution) we can try adjusting the gages until we are happy with them. Its just trial and error.

Then, to make things extra fancy, I added a detecter for the G keypress to shift the gages to the top or bottom of the display. This lets the player shift the gages around based on how the player likes it. Cool eh? And not too complicated.

Back to Top

Map display

Lets provide an overview of where the player is on the map. The complex stuff is hidden in the functions MakeMap() and CopyCars(). Lets start at the _MakeHUD: marker in the source.

Here, all we need do is reserve space for 2 memblocks and the texture image for our Map display "Gage_Map(0)". The image we use is 128x128. Add the header of 12 bytes and the total size is 128*128*PBytes(1) + 12. PBytes(1) is set when we read the image used for texturing the matrix. It will either be set to 2 or to 4. 2 means 2 bytes per pixcel (WORD) or 4 means 4 bytes per pixcel (DWORD). This is determined by BBytes(1) where it will be set to 16 (16 bits, 2 bytes) or 24 (24 bits). Then we just call the MakeMap(),CopyCars() functions and texture the Gage_Map(0). Thats all the complex stuff at this label.

At the _MapSync: Label, we do a few more things. We use a MapSync variable to step through the map update. This is to reduce the system stress (frame rate) by doing each thing on a different sync. Nothing overly complex. On Sync 1, we just
Copy Memblock Gage_Map(1),Gage_Map(2)
to copy the source map memblock Gage_Map(1) to the copy map Gage_Map(2).
On sync 2, we call CopyCars() to add cars to the Gage_Map(2).
On sync 3, we delete the image Gage_Map(3). Otherwise, the make image will fail. On sync 4 we
Make Image From Memblock Gage_Map(3),Gage_Map(2)
and finally on sync 5 we
Texture Object Gage_Map(0),Gage_Map(3).

As you look at the MakeMap() function, you'll notice it does some odd things. Rather than scan the entire map for colors, we only go by the waypoints and just color them. The strangest thing is the Map_Color() and the Map_Car() arrays. Tile 35 in the image used for the matrix has our color key. Do to bad planning, these colors are not in the same order as the cars. The cars are in Quick-Basic color order. The Map_Color() is in a convient order, and was used for detecting waypoints. But, we need a way to color the cars quickly, if the image format is in 16bit format. DBC only directly supports 24bit. So, I save the color values used in tile 35 to the map_Color array. But, this needs to be used for cars .... So, I manually convert the order. But, the problem is Car 8 is Black, and Black is tranparent. Ooops. Thats why the
That just makes a dark color, but not black.

The CopyCars() function works much the same way. We only need to update cars and use the Map_Car() array to color the cars. Nothing too fancy. Try not to let concepts such as memblocks scare you. Think of them as something new to learn. Much faster than bitmap access.

Now that we have a map display, we need to save the map image for use with our Map Dialog (remember way back there?). This is where we use our SaveMap(1) compiler directive. To make it look better, we copy Gage_Map(1) to Gage_Map(2) and then add color to all black areas (Green) and there is our map icon display for the Map Dialog. We just need to test out each Level and then turn off the directive.

Back to Top

AI, or Making the cars race

After spending about 2 weeks working on getting the waypoint system working by using WaffleMat to place the waypoints and then to adjust them with code and so forth, I found some serious errors kept popping up. I could get everything working on one map, only to have the AI get lost on the next map. What seems to be happening is the waypoints sometimes are out of order so the AI can't turn fast enough to get to the waypoint, at current speed. So I shall place the markers into the tiles as color spots and then remove them after the level is loaded. This will let me place the waypoints pixcel perfect and fully automate level creation.

Lets make this a fully functional game. All we really need to do is make the cars follow the waypoints we placed on the map. Sounds pretty easy, but we have some minor things to do first. So, lets break down the things we need to do to get the AI working.

During testing, we can view the waypoints by using the ViewAI(1) compiler directive. We will place a cone at the waypoint and place numbers about it. At first I markered all waypoints, but that was too much information. I trimmed it out to simulate AI conditions. Only the next waypoint and the alternate waypoint will be displayed. The top number is the original ID of the waypoint (from the map editor) followed by the waypoint number, next waypoint,previous waypoint,alternate waypoint. How the waypoint gets changed can be seen in the UpdateAI() function.

In the UpdateAI() function, we also manage collision avoidance for the AI. The methode is not perfect. Sometimes a car slips and that can still cause an accident. The avoidance methode is really just a car lane assignment. Each AI car is assigned a lane for each waypoint. As long as each car is in it's own lane, accidents can be avoided. If two cars try to stay in the same lane, the second car to a waypoint will change lanes. Players can change lanes at any time, and players are ignored as far as lane assignments are concerned. Generally, players can drive faster and much more recklessly than the AI, so the burden of accident prevention for players, falls on players instead of the AI. Too many slight hits will cause a flat tire and that will reduce the maximum speed until a pit stop is done.

Also, you can see how the onroad() and UpdateAI() functions interact with each other in the main loop. If you are not getting closer to the expected waypoint, the game assumes you are going the wrong way. This will also flash if you pass the Pit Exit marker when the marker has been flagged. Currently, it flags automatically, but later it will be flagged by the pit crew messages. If you are offroad for over 20 seconds (maybe lost?) the wrong way turns off. It will remain off until you are on the road again, and then will look for the closest marker near to player. They may be a lag before a valid waypoint is found, but that should be OK.

Also, I set up the wrong way flag for 5 seconds. So, if going the wrong way over 5 seconds, a new marker is found, and the timer resets for another 5 seconds. This way, if you take a corner very short, you may miss a marker and thus the wrong way flag turns on. But, if you continue, it will turn off as a new marker is found ahead of you. Not perfect, but seems reasonable. You can experiment with setting this to a smaller value, but I feel that searching for a close by waypoint should be avoided to reduce game lags later. This shows a method of trading game performance for game feature....

Pit stops are special waypoints. We need to add extra waypoints to manage these. I added extra data to the globals module just so each player has a quick refrence to the new markers. They are just like regular waypoints, but not all players need to use them. So, each pit stop gets an entry and exit marker in addition to the actual pit stop marker. When an AI reaches the Pit Entry marker, the Player_Stopping flag get set. This will cause the car's speed to drop to 0 until the pit-stop time has elapsed. Then this flag is cleared and the car may proceed to the exit marker.

Back to Top

Car Damage Effects

Most things here have already been explained elsewhere, but this is a good place to consolidate alot of things.

Back to Top

Pit Crew Messages

And also quick chat messeges.
Both messages, although for different purposes, are selected in a simular way. Control - P (Ctrl-P) or (Ctrl-C) for quick Pit or Chat messeges. The initial Ctrl-P (Ctrl_C) causes all quick messeges to be listed. When a selection (1-9) is made, the messege is sent and the list gets closed. The list also gets closed if 0 or Ctrl-P (Ctrl-C) or Tab is pressed. Tab is used to toggle full chat messeges. Enter or Tab will close a chat window and send the messege.

The first problem is we are already using the control key as a substitute for the escape key during testing. So, we modify that to use Control-E to exit a level.

For standard chat, we place a marker (>) on the left and another marker (<) that flashes at the end of the chat. For reading chat strings, we use the GetChar$() function. So the code looks something like

ShowChat(50,50,25) ChatMSG$=GetChar$(ChatMSG$)
if Blink
text 0,300,">"+ChatMSG$+"<"
text 0,300,">"+ChatMSG$
if right$(ChatMSG$,1)="*"

We used "10" with SendChat because for our messege IDs we used 10 to indicate a chat messege memblock in the function GameMSG() where we check for any incoming game messeges.

But, Pit Messeges should be two way. So, lets add some detectors to see if anything needs fixing on the car. In the Download Globals.dba module, we add

Dim Repair_Flag(9)
Dim Repair_Name$(9)
Dim Repair_Time(9)

If PlayerCar_Traction#(MyCar,Tire)<2
and that will trigger an
which will then display the messege from the pit crew. Now add an extra check for
if PlayerCar_Fuel#(MyCar)<10
and all done.

Repair_Time() is checked against the current time and against Repair_Flag() determine if a message (AddChat) is triggerred.

Repair_Flag() gets set based on messeges to the pit crew.
Repair_Time() gets set based on messages from the crew.
Repair_Name$() is set in the globals module and is the actual message to be displayed either in the chat or as a flag.

Back to Top

In Game Help

I always forget this until last. Sure, most say this should be first, but I'm pretty lazy. My thinking is, if I do this last, then the help text is direct, and not subject to lots of revisions. Others say do it first and design your game to comply with the help docs. How you do it is up to you. I kept mine very simple: when the race starts, press F1 and lots of useless information will appear. I say useless, but, hey, it does tell the player what keys do what. But scince this is a tutorial, the information is not 100% usefull. One methode would be to add help to your dialogs. That is the best way. Really. I did not do it that way at this time to show the mistake. If I designed the help text first, I would have remembered to reserve a dialog button for the text display. I forgot. And, I'm too lazy to fix it. Instead, I added a one line help text to the main dialog instructing players to press F1 during a game for more helpfull information. Very yucky.

OK, lots more to do, but I decided drop this project here. It is fully playable. Nearly all directives are working. Its fully documented and very easy to edit or to create custom maps. I think thats prety good for a tutorial.

The Ingame Help States:
Welcome to Waffle's Car Race
Press Up/Down Arrows for Faster/Slower
Press Left/Right Arrows to turn
Press SpaceBar for breaking
Z/X to shift gears Down/Up.
     RPM over 50% to shift up
Q to change camera position
G to shift gage location
Control - P   to toggle Pit Messeges
Control - C   to toggle quick chat messages
Tab key toggles standard chat
F1 displays the very fancy help window
This program must be provided for free to all
If you paid for this then email mycat98501@yahoo.com
Visit www.archonrpg.com for more free stuff

Back to Top

Packing Options

Here is some more information nobody wants to talk about. You have finished your masterpiece, and you want to share it with the world. There are lots of options, but all have there own targeted end user (Players).

Before you pick a distribution methode, first create a testing area. If when you build your game, you only use relative paths, things are somewhat easier to manage. Just make a temp directory (Folder) and move your builds there. Each build in its own test folder. Then, test to ensure the build needs nothing other than what you provide to the folder. Some builds will require very little (Make Final EXE with media) while others will require lots (Source only builds).

We will basically only worry about 2 build types: Project Builds and Full EXE builds. Each has a different target.
Instructions are usually in the form "ReadMe.TXT". And just provide an overview of how to test or play the game and any special requirements for the game. Any special credits. Also, the Final Build folder will need a dummy *.dba file for storing compiler directives (*.dir) for the compression stuff. For simplicity, you could just copy the FullSource.dba file to the folder for your builds, but that is not really needed because all the compiler directives are internal to the *exe which you do not need to re-build, but only to compress. And, its only the compression options we need to save.

The important things here to remember are to test in a different folder and see if it works. Only then, do you pack it. Sure, for a simple project, we could have just packed it from within the main folder, but our CarRace game is rather complex and is being distributed in multiple formats. If you chose to use *.zip format, follow the same steps, except for the final packing step would be using your ZIP tool.

And, there is a final step nobody talks about. That is you need a website. I use geocities. And, then, on your website, you need to tell people what or how to use your *.zip or my self-extracting *.exe. For the most part, they both work the same way:
they both unpack files and create directories as needed based on where the *.zip or *.exe is located. But, you need to explain that it will do that. Also, if using the *.exe, you also need to explain that it does not work directly from the internet. If you try to run it directly from the website, your browser will create a temp folder and run the *.exe there and it will then crash because it is unable to create more temp folders. And, even if it did, all files would be deleted by windows afterwords. So, both require a full download to a valid directory before installation can be done.

For fun, I uploaded the Final Build in a *.Zip and in my *.EXE just to see the difference in size.
carrace.zip = 2.68MB
Pcarrace.exe = 2.83MB
carrace.exe = 10.5MB

Back to Top


Visit my website www.archonrpg.com online for more fun free things.
Or email waffle if you have any questions.

Back to Top

No Source Links

All links to source codes have been dissabled in this HTML doc. There are 3 downloads available. Back to Top