How to Program a Game: Lesson 1

Introduction by Lachie Dazdarian
The objective of this series of lessons is to help newbies who know very 
little of BASIC to learn the basics of programming in FreeBASIC necessary 
to create any computer game. Some elementary BASIC knowledge would help a 
lot, though I believe that people who don't know BASIC at all should 
comprehend these lessons too. I'm using here the word (well, it's an 
acronym) "BASIC" and not FreeBASIC, because if you know the basics of 
QuickBASIC, Visual BASIC or any other variant of BASIC, these lessons 
should be easy to comprehend.

I'm starting this series because I feel that tutorials of this kind were 
always something what our community was lacking, even before FreeBASIC. 
I've corresponded during my programming lifetime with quite few programming 
newbies, and they all had almost identical problems when trying to program 
a game. So I think I'm able to detect what beginners need quite well and on 
what way the stuff needs to be explained to them. I also remember my 
beginnings and the problems I had with using separated routines that were 
never meant to be combined and used to create a game. The breaking point 
for me was the moment when I discovered RelLib (a QuickBASIC graphics 
library by R.E.Lope) and the scrolling engine that was created with it. 
That scrolling engine motivated me to explore its mechanics and expand on 
it (with some help from R.E.Lope). In one single moment I acquired the 
ability to program most of the stuff (necessary to complete a game) by 
myself. It's like driving a bike. The moment when you acquire the actual 
skill lasts for one second.

So that's my goal with this series. To teach you enough so you would be 
self-sufficient in 90% of cases. And the best way to learn new things is to 
see them applied. Many tutorials fail in this by being too generic. You 
will always need help from more expert programmers, but the point is that 
you don't need it on every step. Have in mind that this depends on the type 
of game you are developing and the graphics library / tools you are using.

The example programs and mini-games we'll create will be coded in GFXlib 
(the FreeBASIC's built-in graphics library). Lynn's Legacy, ArKade, Mighty 
Line and Poxie were coded in it (among many others), and I think those 
games are good references. But don't worry. Switching from one graphics 
library to another is relatively easy when you know how to code in at least 
one.

This tutorial will not deal with raycasting engines (3D programming) or 
something "advance" like that. If you want that but are a beginner, you 
NEED the following lessons FIRST.

Since we are going to code in FreeBASIC you need to get FreeBASIC first (if 
you don't have it yet) from http://www.freebasic.net (the examples were 
compiled with version 0.18b), and one of the FreeBASIC IDEs available. I 
recommend FBIDE or FBEdit.

Example #1: A simple program - The circle moves!

We'll start with some elementary stuff. The first program we'll code will 
not feature external graphics, because loading graphics from external files 
(usually BMP images) is always a dirty business and will confuse you at 
this point. Trust me on this. Be patient.

The program we'll create will allow you to move a circle around the screen. 
A very simple program, but through making it we'll learn important facts 
and a lot of elementary statements and methods necessary to create any game 
with GFXlib.

As we are using GFXlib you need to be aware of the gfxlib.txt file(GFXlib's 
documentation) placed in the /FreeBASIC/docs directory. That's our Bible 
and very useful with these lessons since I will not explain every parameter 
of every statement used in the example programs (most likely). This 
document is somewhat outdated as FreeBASIC moved on with new versions, so 
be sure to refer to this online FreeBASIC manual too (part of the FreeBASIC 
Wiki).

Open a new program in FBIDE. First thing we'll do is set the graphic mode. 
What's setting a graphic mode? Choosing the program's graphic resolution 
and color depth in bits (8-bit, 16-bit, ...). For example, 8-bit color 
depth is the standard 256 colors mode (8 bits per pixel). The graphic mode 
is set with the SCREEN statement like this:

      Screen 13,8,2,0

13 means 320*200 graphic resolution, 8 means 8-bit graphics, 2 means two 
work pages, and 0 means window mode (input 1 for full screen mode). Minimum 
of 2 work pages is recommended for any graphics dependant program. These 
things will become clearer a little bit later. For more details about the 
SCREEN statement refer to GFXlib's documentation or FreeBASIC Wiki (a more 
"advanced" version of the SCREEN statement is SCREENRES).

The next thing we'll do is set a loop that plays until the user pushes the 
letter Q on the keyboard. Loops are foundation of any program, not just a 
computer game. Coding a program on a way it would stop/halt every now and 
then and wait for the user to type something in is a BAD and WRONG way to 
program anything you want for other people to play. We'll use loops as 
places where the program waits for the user to do something (clicks with 
mouse or pushes a key) and where the program executes some routine 
according to user's action. It will also be used as a place where objects 
not controlled by the player (enemies) are managed/moved. Loops are a must 
have.

If you are aware of all these things, you can skip to the end of this 
section and download the completed example (with comments). If there is 
something in it you don't understand, then get back here.

We can set a loop on more ways (with WHILE:WEND statements, using the GOTO 
statement - Noooo!), but the best way is to use DO...LOOP. This type of 
loop simply repeats a block of statements in it until the condition is met. 
You set the condition(s) after LOOP with UNTIL. Check the following code:

      Screen 13,8,2,0 ' Sets the graphic mode
      Do
      ' We'll put our statements here later
      Loop Until Inkey$ = "Q" Or Inkey$ = "q"

(Editors Note: The suffixes on commands such as the $ on INKEY$ is:
obligatory in the -lang qb dialect.
optional in the -lang fblite dialect.
forbidden in the -lang fb dialec 
Check the documentation for the specific keyword)
 
If you compile this code and run it, you'll get a small black empty 320*200 
window which you can turn off by pushing the letter Q (you might need to 
hold it). The program simply loops until you press "Q or "q". I used both 
upper and lower case "Q" symbol in case Caps Lock is turned on on your 
keyboard. INKEY$ is a statement that returns the last key pushed on the 
keyboard. I will explain later why it shouldn't be used with games and 
what's a better substitute.

To draw a circle we'll use the CIRCLE statement (refer to GFXlib's 
documentation). Check the following code:

      Screen 13,8,2,0 ' Sets the graphic mode

      Do

      Circle (150, 90), 10, 15 

      Loop Until Inkey$ = "Q" Or Inkey$ = "q"

The last code draws a small circle on coordinates 150, 90 with a radius of 
10 and color 15 (plain white) in a loop, which you can check if you compile 
the code. So how to move that circle? We need to connect its coordinates 
with VARIABLES. For this we'll use two variables named circlex and circley. 
Check the following code:

      Dim Shared As Single circlex, circley

      Screen 13,8,2,0 ' Sets the graphic mode

      circlex = 150 ' Initial circle position
      circley = 90

      Do

      Circle (circlex, circlex), 10, 15 

      Loop Until Inkey$ = "Q" Or Inkey$ = "q"

This makes no change in the result of our program, but it's a step to what 
we want to accomplish. You can change the amounts to which circlex and 
circley equal to change the circle's initial position, but that's not what 
we really want. In order to move the circle we need to connect circlex and 
circley variables with keyboard statements.

We declared first two variables in our program. All variables in FreeBASIC 
programs MUST be declared, although if you use -lang qb command line during 
compiling you can compile using old QBasic compatibility dialect (I don't 
recommend it as it will keep you deprived of possible advances and 
extensions which default FB compatibility already provides and will 
provide). For more info on this check the appropriate page of the FreeBASIC 
wiki - Using the command line. Variables are declared (dimensioned) on this 
way:

      Dim variable_name [As type_of_variable] 

Or...

      Dim [As type_of_variable] variable1, variable2, variable3, ... 

The data inside [ ] is optional and the brackets are not used. Types of 
variables available in FreeBASIC are BYTE, SHORT, INTEGER, STRING, SINGLE, 
DOUBLE and few others, but I don't find details about them important on 
this level. What you need to know now is that you should declare variables 
or arrays AS INTEGER when they hold graphics data (memory buffers holding 
graphics) or when they represent data which doesn't need decimal precision 
(number of lives, points, etc.). Variables that need decimal precision are 
declared AS SINGLE or DOUBLE. Those are usually variables used in games 
which rely on physics formulae like arcade car driving games or jump 'n run 
games (gravity effect). Simply, the difference between the speed of two 
pixels per cycle and the speed of one pixel per cycle is most often too 
large, and in those limits you can't emulate effects like fluid movement on 
the most satisfactory way. Also, behind DIM you should put SHARED which 
makes that the specific variable readable in the entire program (all 
subroutines). Don't use SHARED only with variables declared inside 
subroutines (I do it very rarely). If you are going to declare ARRAYS 
inside a subroutine, I advise you to replace DIM with REDIM. Strings are 
used to hold text data. Like YourName = "Dodo", but you need to declare 
YourName AS STRING first.

Now I will introduce a new statement instead of INKEY$ which can detect 
multiple keypresses and is much more responsive (perfect response) than 
INKEY$. The flaw of INKEY$, as well as being very non-responsive (which you 
probably were able to detect when trying to shut down the previously 
compiled examples), is that it can detect only one keypress at any given 
moment which renders it completely unusable in games.

The substitute we'll use is MULTIKEY (a GFXlib statement) which features 
only one parameter, and that's the DOS scancode of the key you want to 
query. You might be lost now. DOS scancode is nothing but a code referred 
by the computer to a certain keyboard key. If you check Appendix A of the 
GFXlib's documentation, you will see what each code stands for. For 
example, MULTIKEY(&h1C) queries if you pushed ENTER. GFXlib allows you to 
replace these scancodes with "easy to read" constants like it's explained 
in Appendix A. To use GFXlib you need to include its .bi file (fbgfx.bi) 
into your source. What's a .bi file? Well, it can be any kind of module you 
would attach to your source code and which can feature various subroutines 
(if you don't know what a subroutine is, we'll get on that later) and 
declarations used in your main module. The code you need to add are these 
two lines as it follows:

      #include "fbgfx.bi" 
      Using FB

It's best to put these two lines somewhere on the beginning of your program 
(before or after the sub declarations). You don't need to set a path to 
fbgfx.bi since it's placed in the /FreeBASIC/inc directory. You only need 
to set a path to a .bi file if it's not in that directory or not in the 
directory where the source code is. Using FB tells the program that we will 
be accessing GFXlib symbols without namespace, meaning, without having to 
put 'FB.' in front of every GFXlib symbol. Refer to FreeBASIC Wiki on 
USING.

Now the fun starts.

We will add a new variable named circlespeed which flags (sets) how many 
pixels the circle will move in one cycle (loop). The movement will be done 
with the arrows key. Every time the user pushes a certain arrow key we will 
tell the program to change either circlex or circley (depends on the pushed 
key) by the amount of circlespeed. Check the following code:

      #include "fbgfx.bi"
      Using FB

      Dim Shared As Single circlex, circley, circlespeed 

      Screen 13,8,2,0 ' Sets the graphic mode

      circlex = 150   ' Initial circle position
      circley = 90
      circlespeed = 1 ' Circle's speed => 1 pixel per loop

      Do

      Circle (circlex, circley), 10, 15

      ' According to pushed key we change the circle's coordinates.
      If MultiKey(SC_RIGHT) Then circlex = circlex + circlespeed
      If MultiKey(SC_LEFT) Then circlex = circlex - circlespeed
      If MultiKey(SC_DOWN) Then circley = circley + circlespeed
      If MultiKey(SC_UP) Then circley = circley - circlespeed

      Loop Until MultiKey(SC_Q) Or MultiKey(SC_ESCAPE)

As you see we also changed the condition after UNTIL since we are using 
MULTIKEY now. Now you can exit the program by pressing ESCAPE too (I added 
one more condition).

If you compile the last version of the code, two things we don't want to 
happen will happen. The program will run so fast you won't even notice the 
movement of the circle, and the circle will "smear" the screen (the circles 
drawn on different coordinates in previous cycles will remain on the 
screen). To avoid smearing you need to have the CLS statement (clears the 
screen) in the loop so that in every new cycle the old circle from the 
previous cycle is erased before the new is drawn.

To reduce the speed of the program the quickest fix is the SLEEP command. 
What it does? It waits until the specified amount of time has elapsed (in 
milliseconds) or a key is pressed. To escape the key press option use SLEEP 
milliseconds, 1. This statement is also an efficient solution for the 100 % 
CPU usage problem. You see, if you don't use that statement any kind of 
FreeBASIC program with a loop (even the simplest one) will hold up all the 
computer cycles and make all the other Windows tasks you might be running 
to crawl. It also makes difficult for you to operate with other tasks while 
that kind of FreeBASIC program is running. Err...this is not a huge problem 
and a fair amount of programmers that have released FreeBASIC games so far 
did not bother to fix it.

Copy and paste the following code and compile it:

      #include "fbgfx.bi"
      Using FB

      Dim Shared As Single circlex, circley, circlespeed  

      Screen 13,8,2,0 ' Sets the graphic mode

      circlex = 150   ' Initial circle position
      circley = 90
      circlespeed = 1 ' Circle's speed => 1 pixel per loop

      Do

      Cls
      Circle (circlex, circley), 10, 15

      ' According to pushed key we change the circle's coordinates.
      If MultiKey(SC_RIGHT) Then circlex = circlex + circlespeed
      If MultiKey(SC_LEFT) Then circlex = circlex - circlespeed
      If MultiKey(SC_DOWN) Then circley = circley + circlespeed
      If MultiKey(SC_UP) Then circley = circley - circlespeed

      Sleep 10, 1

      Loop Until MultiKey(SC_Q) Or MultiKey(SC_ESCAPE)

Viola! Our circle is moving and "slow enough".

The last version of the code does not represent the desirable way of 
coding, but I had to simplify the code in order to make this lesson easy to 
understand. What we need to do next is declare our variables on the way 
they should be declared in any "serious" program, and show why we are 
having two work pages and what we can do with them.

The way variables are declared in the above code is not the most convenient 
in larger projects where we have huge amount of variables usually 
associated to several objects (an object can be the player, enemy or 
anything that is defined with MORE THAN ONE variable).

So first we'll define a user defined data type with the statement TYPE that 
can contain more variables/arrays (stay with me). We'll name this user data 
type ObjectType. The code:

      Type ObjectType
          x As Single
          y As Single
          speed As Single
      End Type

After this we declare our circle as an object:

      Dim Shared CircleM As ObjectType
      ' We can't declare this variable with "Circle"
      ' since then FB can't differ it from 
      ' the statement CIRCLE, thus "CircleM".

How is this method beneficial? It allows us to manage the program variables 
on a more efficient and cleaner way. Instead of (in this example) having to 
declare each circle's characteristic (it's position, speed, etc.) 
separately, we'll simply use a type:def that includes all these variables 
and associate a variable or an array to it (in this case that's CircleM). 
So now the circle's x position is flagged with CircleM.X, circle's y 
position with CircleM.Y and circle's speed with CircleM.speed. I hope you 
see now why this is better. One user defined type can be connected with 
more variables or arrays. In this example you can add another object with 
something like DIM SHARED EnemyCircle(8) AS ObjectType which would allow us 
to manage 8 "evil" circles with a specific set of routines (an AI of some 
sort) using the variables from the ObjectType type:def (x, y, speed), and 
these circles could "attack" the user's circle on some way. In the next 
lesson all this will become more clear. Have in mind that not ALL variables 
need to be declared using a type:def. This is only for "objects" in your 
game that are defined (characterized) with more variables (like a hero 
determined by health, money, score, strength, etc.).

After the change the final version of the code looks like this:

      #include "fbgfx.bi"
      Using FB 

      ' Our user defined type.
      Type ObjectType
          x As Single
          y As Single
          speed As Single
      End Type

      Dim Shared CircleM As ObjectType
      ' We can't declare this variable with "Circle"
      ' since then FB can't differ it from 
      ' the statement CIRCLE, thus "CircleM".

      Screen 13,8,2,0 ' Sets the graphic mode
      SetMouse 0,0,0 ' Hides the mouse cursor

      CircleM.x = 150   ' Initial circle's position
      CircleM.y = 90
      CircleM.speed = 1 ' Circle's speed => 1 pixel per loop

      Do

      Cls
      Circle (CircleM.x, CircleM.y), 10, 15

      ' According to pushed key we change the circle's coordinates.
      If MultiKey(SC_RIGHT) Then CircleM.x = CircleM.x + CircleM.speed
      If MultiKey(SC_LEFT) Then CircleM.x = CircleM.x - CircleM.speed
      If MultiKey(SC_DOWN) Then CircleM.y = CircleM.y + CircleM.speed
      If MultiKey(SC_UP) Then CircleM.y = CircleM.y - CircleM.speed

      Sleep 10, 1 ' Wait for 10 milliseconds.

      Loop Until MultiKey(SC_Q) Or MultiKey(SC_ESCAPE)

You will notice I added one more statement in the code. The SETMOUSE 
statement positions the system mouse cursor (first two parameters) and 
shows or hides it (third parameter; 0 hides it). You should input this 
statement with these parameters in every program AFTER the SCREEN statement 
(IMPORTANT!) by default, because even if your program is going to feature a 
mouse controllable interface, you will most likely draw your own cursor. 
Trust me on this. Uh, I using that line way too often.

Download the completed example with extra comments inside the source: 
move_circle.zip

Phew, we are done with the first example. Some of you might think I went 
into too many details, but I feel all this dance was needed to make the 
next examples and lessons a more enjoyable adventure.

Nevertheless, this example is far from what we want, right? So the next 
chapter will learn you how to load graphics from external files among other 
things. 

Example 2: A warrior running around a green field

In the next example we will be applying all the knowledge from the first 
example, so don't expect for this example to go into every statement again. 
I will explain every new statement and just brush off the old ones.

In this section we'll start to code our mini-game which won't be completed 
in this lesson. In this lesson we'll just create a program where a warrior 
runs around a green field (single screen).

First I'll show you what graphics we'll be using. We are going to work in 
8-bit color depth mode, so the images that we are going to use need to be 
saved in that mode (256 colors mode). For warrior sprites I'll use the 
sprites of the main character from my first game Dark Quest.

http://hmcsoft.org/fb/htpagl1-sprites.png
(Editors Note: The link above is not active anymore. The game Dark Quest is 
still available for download at http://lachie.phatcode.net/downloads.php)

As you see this image features 12 sprites of our warrior, each 20*20 pixels 
large. Two for each direction (walk animation) and one sprite for each 
direction when the warrior is swinging with his sword. Sword swinging won't 
be implemented in the first lesson but will become necessary later.

Second image is the background image which you can check/download if you 
click here (320*200 pixels large, 8-bit BMP image).

Download both images and place them where you will place the source, or 
just download the completed example at the end of this section.

On the beginning of our program we should include fbgfx.bi, same as in the 
first example, and then set the same graphic mode. The code:

      #include "fbgfx.bi" 
      Using FB

      Screen 13,8,2,0 ' Sets the graphic mode
      SetMouse 0,0,0  ' Hides the mouse cursor

Now we will declare two memory pointers that will point to memory buffers 
where our graphics will be stored (one for the sprites and one for the 
background).

The first pointer we'll name background1 and declare it with the following 
line:

      Dim Shared background1 As Any Ptr

ANY PTR tells us that background1 will actually be a memory pointer. A 
pointer defined as an ANY PTR disables the compiler checking for the type 
of data it points to. It is useful as it can point to different types of 
data. We'll use pointers because we will allocate memory for our graphics 
using the IMAGECREATE statement. IMAGECREATE allocates the right amount of 
memory for a piece of graphics (sprite/image) if we input its height and 
width. Otherwise we would have to do it manually, meaning, calculate the 
needed amount of memory as the result of the sprite size, bit-depth and 
variable size. IMAGECREATE does this for use. As IMAGECREATE results with a 
pointer, we need to refer a pointer to it and not a variable. Don't worry 
if you don't know anything about pointers. You don't need to (to comprehend 
this tutorial).

The next pointer we'll declare will point to the memory buffer that holds 
the 12 warrior sprites. We will dimension this pointer as a single 
dimension array, each element in the array representing one sprite.

      Dim Shared WarriorSprite(12) As Any Ptr

Both these lines should be put in the code before the SCREEN statement. 
That's the way you'll write every program. Subroutine declarations, then 
variable declarations, then extra subroutine declarations if needed, and 
then the real code. The beginning of our program should now look like this:

      #include "fbgfx.bi" 
      Using FB

      Dim Shared background1 As Any Ptr ' A pointer that points to a memory
                                ' buffer holding the background graphics
      Dim Shared WarriorSprite(12) As Any Ptr ' A pointer that points to a memory
                                    ' buffer holding the warrior sprites

      Screen 13,8,2,0 ' Sets the graphic mode
      SetMouse 0,0,0  ' Hides the mouse cursor

After the screen resolution, color depth and number of work pages are set, 
we will hide our work page before loading graphics onto it since we don't 
want for the user to see all of the program's graphics every time he or she 
starts our program. To accomplish that we'll use the SCREENSET statement. 
What it does? It sets the work page (first parameter) and the visible page 
(second parameter). In our case we will set page 1 as the work page and 
page 0 as the visible page. After using 'SCREENSET 1, 0' every time we draw 
or load something on the screen it will be loaded/drawn on the work page 
and won't be visible to the user until we use the statement SCREENCOPY or 
SCREENSET with different parameters (SCREENSET 1, 1). This allows us to 
load graphics onto the screen we don't want for the user to see and delete 
it before coping the content on the work page to the visible page. This 
page flipping is also useful in loops with "graphics demanding" programs to 
avoid flicker or some other unwanted occurrence.

