This chapter extends the subjects of variable data types and looping processes, which were covered in previous chapters, bringing them together in a new light. As you will learn in more detail shortly, data sequences allow you to add data directly to a program without loading that data from a file or other means, which can be very useful in a game. Whereas a complete game might use data files to store information about the objects in the game (such as characters, places, ships, weapons, monsters, and so on), this type of data can be inserted into the game right from the start using data sequences. This can be very useful when you are designing an initial prototype of a game or game engine and you don't want to spend time creating game editors to design your game worlds, characters, and such.
Arrays work hand-in-hand with data sequences and even more with looping commands, with which they are able to iterate through long lists of information quickly and efficiently. While looping with variables or functions allows you to perform repeat processes easily, looping with data sequences allows you to read and apply custom values to your game. For example, suppose you are writing your own roleplaying game with a character that explores dungeons and fights against monsters. The character and the monsters will have traits, such as strength, endurance, hit points, and so on. Storing monster names along with traits in a data sequence is quick and easy and allows you to make changes to the traits right inside the source code at any time. For larger projects, you would want to store traits in data files, but a data sequence will work just as well in smaller projects.
A data sequence can store any data in sequential order. Data is read one item at a time, like an assembly line. A stack is another way of describing a data sequence. A stack keeps track of data in a first in, last out order. Alternatively, this can be described as last in, first out. Figure 7.1 illustrates a commonplace example of a stack, in which plates coming from the dishwasher are added to the stack and immediately removed for service. The first plate added to the stack might never even be used!
Figure 7.1: A software stack works like a stack of plates at a buffet, where the last plate added is the first plate removed—first in, last out or last in, first out.
Comparing a data sequence to a stack is not entirely accurate because you can't add new items to the data sequence after the program starts running (during the run time); you can only add new items when you are editing the source code (during the design time). However, if you type in the data statements in a data sequence in order, it does resemble a design-time stack, so I thought this analogy would help you visualize how data sequences work.
Another example that is perhaps more accurate is comparing a data sequence to a sequential disk file. Chapter 15, "Loading and Saving Information Using Files," explains how to work with files, so I won't get into them in detail here other than to summarize how they work. There are two basic types of files—sequential and random-access. Sequential files are like text files that you can edit with Notepad. A program settings file, such as win.ini, is also a sequential text file that you can read with Notepad. Another type of sequential file is a binary file that stores bytes or other types of numbers. Random-access files are structured files that read and write records of data rather than a single character at a time. DarkBASIC does not support structured files because it knows how to read most game-related files natively (such as 3D Studio Max files, wave files, texture files, and so on).
The difference between a sequential file and a stack is that you must read and interpret a file using number or string variables, and you must keep track of file numbers, names, lengths, and so on. Data sequences are much easier to use! Just think of a data sequence as a simple kind of sequential file.
All things being equal, data sequences are really only effective for limited amounts of data. When the data in a data sequence becomes too large, it is difficult to maintain the data in your source code file. One way around the problem of lengthy source code listings is to use the #INCLUDE command to include one or more source code files in your main program file. You can then move the data sequences over to a separate file and keep your main program listing more tidy.
What types of data can you imagine storing in a data sequence inside your program? I can think of many.
The one drawback to using a data sequence instead of a disk file is that when the game is finished, compiled, and put up for sale or into the public domain, there won't be any way to add new levels and missions or monsters to the game later. Mission packs, add-ons, and modifications ("mods") are very popular in the gaming community. If even a simple game supports player-made levels, it will generate a fan following. Players enjoy creating their own levels and add-ons for games and then sharing their work with others. It involves a little fame, a little prestige, and a lot of fun overall.
The tradeoff is whether you want to go through the additional work required to load data into your program from disk files, rather than just storing the data inside your program. Perhaps one benefit to a data sequence is that it is like a built-in array, so you don't need to use an additional array to use it. Data stored in a file, however, would probably need to be loaded into an array, at which point it would somewhat resemble a data sequence.
In this section I will show you how to create a data sequence in a DarkBASIC program. Next I'll show you the various commands for reading the data sequence and putting the data to good use. Finally, I'll show you how to reset the data sequence and start reading it from the beginning again. This might be useful when you want to restart the game, for instance.
The DATA statement defines data in a data sequence. It is specifically called a sequence because you can have many DATA statements in your program. In fact, you can insert a DATA command anywhere in your program, but I would recommend keeping all DATA statements at the top of the source code file for clarity. It is very confusing when you have DATA statements strewn all about your program.
One solution that you can employ with multiple data sequences is to label specific data sequences, and then jump to the labels to read them. I'll explain data labels shortly, when I cover the RESTORE statement.
The DATA statement supports strings, integer numbers, and decimal numbers. Here is a sample of a data sequence that includes a last name, first name, and age.
DATA "Jones", "Bob", 34 DATA "Anderson", "Julie", 19 DATA "Smith", "John", 26 DATA "Wells", "Joanne", 8
See how you can design the data sequence any way you want? This data can accommodate numbers and strings, but there is no way to store binary numbers or bytes—the sort of data you would need to store bitmap pixels, for instance.
The READ command reads data stored in a data sequence. The data is read one item at a time (where individual data items are separated by commas) from beginning to end. The READ command is flexible in that it supports integer numbers, decimal numbers, and strings (just like the DATA statement does).
The following code snippet demonstrates how to read the data sequence and print out the information. Note that this code will use the DATA statements that were shown in the previous section.
FOR n = 1 to 4 READ LastName$, FirstName$, Age PRINT FirstName$; ", "; LastName$; ", "; Age NEXT n
Do you see how the variables are printed out in a different order than they are read? When you have read the data, you can do anything you want with it! Take note also of the FOR loop, which performs the READ command four times. Even though there are 12 items in the data sequence, three of them are read each time through the loop: LastName$, FirstName$, and Age.
Now I'll show you a simple program I wrote that uses a data sequence filled with contact information. The data includes the following fields.
I know you aren't particularly interested in databases, but this simple contact program is a great way to demonstrate how data sequences work. Later I'll show you a more interesting program that uses DATA statements and a modified version of the Conditions program from Chapter 6. Figure 7.2 shows the output of the ContactData program.
Figure 7.2: The ContactData program demonstrates how to use the DATA statement and the READ command.
Now for the source code for the ContactData program. There are four DATA statements that provide the contact information used by the program. There is also a single DATA statement at the beginning of the sequence that provides the number of records that should be read. This is an important thing to remember! Without some sort of total value, you will have to hard-code the total number of data statements into your code, and then it will be more difficult to add new DATA statements later (something that is fairly common when working with information).
For your convenience, this project is located on the CD-ROM in the SourcesCH07ContactData folder.
REM --------------------------------- REM Beginner's Guide To DarkBASIC Game Programming REM Copyright (C)2002 Jonathan S. Harbour and Joshua R. Smith REM Chapter 7 - ContactData Program REM --------------------------------- REM Declare the data sequence DATA 4 DATA "Jones","Bob","1234 Somewhere St","Oblivion","MD","10023","916-555-1212" DATA "Smith","John","5678 Super St","Anchorage","WY","33992","414-555-1234" DATA "Anderson","Julie","9293 Prairie Dr","Orson","MI","83923","212-555-8382" DATA "Wells","Joanne","3283 Oak Tree Ln","Wichita","KS","22939","623-555-3928" REM Declare some variables NumData = 0 FirstName$ = "" LastName$ = "" Address$ = "" City$ = "" State$ = "" Zip$ = "" Phone$ = "" REM Initialize the program SYNC ON REM Read the record count READ NumData REM Read and print out the data sequence FOR n = 1 to NumData REM Read the data sequence READ LastName$, FirstName$, Address$, City$, State$, Zip$, Phone$ REM Print the contact information PRINT "CONTACT "; n PRINT " NAME "; FirstName$; " "; LastName$ PRINT " ADDRESS "; Address$; ", ";City$; ", "; State$; " "; Zip$ PRINT " PHONE "; Phone$ PRINT SYNC NEXT n REM Wait for key press WAIT KEY REM End the program END
Have you noticed that I always include a variable declarations section in the sample programs? Although DarkBASIC doesn't require it, I highly recommend that you include a section at the top of your program for important variables so you can identify them more easily. It can be very confusing when variables are declared at random throughout the source code. This is a good programming practice rather than a requirement of DarkBASIC. Most programming languages require you to declare variables anyway, so it's a good habit to acquire.
The RESTORE command moves the internal data sequence position to the beginning of the list where the first DATA statement is located, so the next READ command will pick up the first item of data again. The RESTORE command also supports data labels that allow you to group related items of data.
You can call the RESTORE command on its own, in which case the data sequence position will move to the top of the list. If you want to move the position to a data label, just add the name of the label after the RESTORE command. The syntax of this command is RESTORE data label.
Using the RESTORE Command: The MonsterList Program
The RESTORE command might be simple, but it is actually a very powerful command that can give your programs some interesting capabilities. I have written a program called MonsterList that demonstrates using the RESTORE command with data labels. Figure 7.3 shows the output from the MonsterList program.
Figure 7.3: The MonsterList program demonstrates how to use the RESTORE statement with data labels.
The source code for the MonsterList program follows. Pay attention to the placement of the labels (shown in bold) and also the first DATA statement, which just includes the name of the program. This is a filler data item to demonstrate how the RESTORE command will jump to a label rather than starting from the top if you use it in that manner. It is just a coincidence that there are four monsters and four heroes in the program; feel free to add more data to the list to see how the program easily supports additional data. Just be sure to update the NumData value to reflect the number of data records used.
REM --------------------------------- REM Beginner's Guide To DarkBASIC Game Programming REM Copyright (C)2002 Jonathan S. Harbour and Joshua R. Smith REM Chapter 7 - MonsterList Program REM --------------------------------- REM Misc data DATA "Monster List Program" REM Hero data heroes: DATA 4 DATA "Warrior", 15, 12, 10 DATA "Archer", 8, 8, 15 DATA "Valkyrie", 14, 13, 12 DATA "Wizard", 4, 3, 8 REM Monster data monsters: DATA 4 DATA "Troll", 18, 16, 3 DATA "Goblin", 5, 5, 5 DATA "Ghost", 10, 13, 6 DATA "Berserker", 16, 2, 8 REM Declare some variables NumData = 0 Name$ = "" Attack = 0 Defend = 0 Speed = 0 REM Initialize the program SYNC ON PRINT "NAME, ATTACK, DEFEND, SPEED" PRINT REM Print out the monsters PRINT "MONSTERS" RESTORE monsters PrintData REM Print out the heroes PRINT "HEROES" RESTORE heroes PrintData REM Wait for key press WAIT KEY REM End the program END FUNCTION PrintData() REM Read the record count READ NumData REM Read and print out the data sequence FOR n = 1 to NumData REM Read the data sequence READ Name$, Attack, Defend, Speed REM Print the information PRINT Name$; ", "; PRINT Attack; ", "; PRINT Defend; ", "; PRINT Speed SYNC NEXT n PRINT ENDFUNCTION
Wouldn't you agree that data sequences are fun and an interesting way to add information to a game? I can certainly appreciate the number of uses for data statements and commands that DarkBASIC provides. This section is related to data sequences, although arrays are more focused on real-time data and are more useful when reading data from disk files. In this section, I'll show you what arrays can do and how you can use them to improve your programs’ capabilities. The last demonstration program in this chapter is called BouncingBalls—I'll leave what the program does to your imagination.
An array is a sequential list of values with the same data type, such as integer, decimal, or string. Unlike data sequences, in which you can mix and match data, arrays must be filled with the same kind of data. When creating the array, you must use the dollar sign or pound sign to specify a string or decimal array, respectively. You don't use any character to specify the default integer data type for the elements of the array.
Given enough memory, there is no set maximum number of elements you can allocate in an array. It is perfectly legal to create an array with a hundred million elements! However, you will want to make sure enough memory is available before allocating a huge array, by using the memory function SYSTEM TMEM AVAILABLE or SYSTEM SMEM AVAILABLE.
Creating an Array with DIM
You use the DIM command to create an array variable. This is a keyword in DarkBASIC that you might recognize if you have ever used Visual Basic (which uses DIM for all variables, not just arrays). The DIM statement syntax is DIM VariableName(Number of Elements).
VariableName can be any name of your choosing; it is similar to any ordinary variable name. The number of elements in the array is the parameter that goes inside the parentheses. DarkBASIC is a forgiving language in that the starting number for arrays can be 0 or 1, and the upper range of the array is still the number you specified. In most programming languages, the Number of Elements value for an array can mean 1 to 30 or 0 to 29. In other words, there are exactly 30 elements to the array. However, DarkBASIC always allocates one extra index for the array, so you can use 0 or 1 and still go up to the value you specified for the upper limit.
Since DarkBASIC is lenient in this way, take care to be consistent in how you use arrays. If you treat the first element of the array as 0, then stick to it throughout your program to avoid confusion. If you prefer 1, then stick to that.
Deleting an Array with UNDIM
If for any reason you need to delete an array after you have created it with DIM, you can use the UNDIM command to remove the array from memory. This statement is provided so you can recreate the array with DIM. There are some cases in which this might be useful, such as when you are restarting the game, but I personally avoid using UNDIM and prefer to use an array throughout the program or create additional arrays when needed. One possible benefit to the UNDIM command is in a situation in which you no longer need an array and you want to free up memory to improve your game's performance. If you created a really big array and you no longer need it at some point in the program, go ahead and delete it with UNDIM. However, you don't need to delete any arrays at the end of the program because DarkBASIC will de-allocate memory automatically; you don't need to worry about memory in DarkBASIC.
To fully demonstrate how arrays work and how you can use them to their greatest potential, I have written a program called BouncingBalls. This program starts by creating several arrays with the DIM command to keep track of the position and speed of 30 balls on the screen. It then creates the balls, each with a random position, speed, and diameter. The main part of the program moves and draws all 30 balls over and over, resulting in a more advanced version of the Conditions program that you saw in Chapter 6.
Testing the Program
The BouncingBalls program creates 30 balls on the screen by keeping track of each ball in an array. It then uses a loop to draw each ball before updating the screen. The result is a high-speed demonstration of balls bouncing off the edges of the screen. Each ball is a different size—some are very small and some are very large. Figure 7.4 shows the program running at standard resolution, and Figure 7.5 is a tweaked version running at 16001200.
Figure 7.4: The BouncingBalls program demonstrates the use of arrays to store the position and velocity of balls on the screen.
Figure 7.5: The BouncingBalls program running at 16001200.
Writing the Source Code
The source code for the BouncingBalls program follows. I have bolded the section of code that actually creates the arrays, as well as the section that animates the balls on the screen (further down in the listing). This program is similar to the Conditions program from Chapter 6, but I have moved the logic that bounces the balls off each of the four edges of the screen into a function.
I have also added a new function called DrawRect, which draws the border around the screen using LINE commands. The most important function in the program is MoveBall, which has a Num parameter that identifies the elements of the ball arrays that should be used to draw the balls on the screen.
Tip |
To really see what this program can do, go through the code and replace the 30-element arrays with some larger number, such as 1,000, and see what happens! |
REM --------------------------------- REM Beginner's Guide To DarkBASIC Game Programming REM Copyright (C)2002 Jonathan S. Harbour and Joshua R. Smith REM Chapter 7 - BouncingBalls Program REM --------------------------------- REM Create some variables DIM BallX(30) DIM BallY(30) DIM BallSize(30) DIM SpeedX(30) DIM Speedy(30) DIM Color(30) REM Initialize the program SET DISPLAY MODE 640, 480, 32 SYNC ON REM Initialize the balls InitializeBalls REM Start the main loop DO REM Clear the screen CLS REM Set the border color INK RGB(255,255,255), 0 REM Draw the screen border DrawRect(0,0,639,479) REM Move and draw the balls FOR n = 1 TO 30 MoveBall(n) NEXT n REM Redraw the screen SYNC LOOP REM End program END FUNCTION InitializeBalls() FOR n = 1 TO 30 BallX(n) = RND(640) BallY(n) = RND(480) BallSize(n) = RND(20) + 5 SpeedX(n) = RND(12) - 6 SpeedY(n) = RND(12) - 6 Color(n) = RGB(RND(256), RND(256), RND(256)) NEXT n ENDFUNCTION FUNCTION MoveBall(Num) REM Move the ball BallX(Num) = BallX(Num) + SpeedX(Num) BallY(Num) = BallY(Num) + SpeedY(Num) REM Check conditions for the BallX IF BallX(Num) > 640 - BallSize(Num) BallX(Num) = 640 - BallSize(Num) SpeedX(Num) = SpeedX(Num) * -1 ELSE IF BallX(Num) < BallSize(Num) BallX(Num) = BallSize(Num) SpeedX(Num) = SpeedX(Num) * -1 ENDIF ENDIF REM Check conditions for BallY IF BallY(Num) > 480 - BallSize(Num) BallY(Num) = 480 - BallSize(Num) SpeedY(Num) = SpeedY(Num) * -1 ELSE IF BallY(Num) < BallSize(Num) BallY(Num) = BallSize(Num) SpeedY(Num) = SpeedY(Num) * -1 ENDIF ENDIF REM Draw the ball INK Color(Num), 0 CIRCLE BallX(Num), BallY(Num), BallSize(Num) ENDFUNCTION FUNCTION DrawRect(Left,Top,Right,Bottom) LINE Left, Top, Right, top LINE Right, Top, Right, Bottom LINE Right, Bottom, Left, Bottom LINE Left, Bottom, Left, Top ENDFUNCTION
This chapter explained the benefits and uses for data sequences and arrays in DarkBASIC. You learned how to add items of data to your programs using the DATA and READ commands, including the ability to use data labels to group related data items. You also learned all about arrays and how to use them. Several sample programs graced the pages of this chapter, including the ContactData and MonsterList programs, which showed you how to use data sequences.
The BouncingBalls program was a great demonstration of using arrays. Although you might not have realized it at the time, the program very closely resembles a real game! Later chapters on bitmaps and sprite animation will take this to another level, but this chapter provided you with a taste of what is to come. If you found the BouncingBalls program intriguing, then you will really enjoy Chapter 9, "Basic Graphics Commands," which goes into detail about how to use all of the 2D drawing commands supported by DarkBASIC.
The chapter quiz will help you retain the information that was covered in this chapter, as well as give you an idea about how well you're doing at understanding the subjects. You will find the answers for this quiz in Appendix A, "Answers to the Chapter Quizzes."
1. |
Which command or statement is used to declare a data sequence?
|
|
2. |
How does the READ command read a data sequence?
|
|
3. |
Which command can be used to move the pointer in a data sequence back to the beginning?
|
|
4. |
What is a list of related data stored in sequential order within the source code called?
|
|
5. |
If an array is created with DIM A(10), how many elements are usable in the array?
|
|
6. |
What types of data can you store inside a data sequence or an array?
|
|
7. |
Arrays can't store strings, just integers or decimals.
|
|
8. |
What is the largest number of elements that you can allocate in an array?
|
|
9. |
Which command did the BouncingBalls program use to draw each ball?
|
|
10. |
What does the RESTORE monsters command accomplish in the MonsterList program?
|
Answers
1. |
D |
2. |
A |
3. |
D |
4. |
B |
5. |
A |
6. |
B |
7. |
B |
8. |
D |
9. |
C |
10. |
C |
Part I - The Basics of Computer Programming
Part II - Game Fundamentals Graphics, Sound, Input Devices, and File Access
Part III - Advanced Topics 3D Graphics and Multiplayer Programming
Epilogue
Part IV - Appendixes