Back2BASIC

You are here: Home > Issues > Issue #1 > Multi-room Games

Multi-room Games

By angros47

In many first-generation games, the goal was to explore a huge maze (a dungeon, a building, a forest), to find objects, fight enemies and so on. Since the playground was BIG, only a portion of it was visible on the screen (so, you really had to explore, since you weren't able to see all the scenario); to draw only a portion of the scenario, there were two solutions: the player character was always in the center (or close to the center), and the background scrolled the playground was subdivided in many rectangles (rooms): when the player went to a side of the screen, he would enter the next room

The former solution was good for external environments (a forest, a desert, a river...), and could be used also in shoot-em-up games, but it was a bit complex to implement (enemies and objects had to be scrolled with the background, and had to be updated even when they weren't in the viewport); the latter solution was simpler (the program had to care only about one room at time: dealing with enemies was a lot easier, for the programmer), and was perfect for interiors: you couldn't look inside a room until you entered it.

In this tutorial, we'll learn how a room-based game can be made in FreeBasic. Our game (at least at first stages) will be text-based (like ZZT and Rogue).

First, we need to draw the player: in the example, we will use a simple “*”

We could do it with a simple:

PRINT “*”

Well, since we are in the console mode, maybe we should clean it, with CLS; also, a SLEEP will pause the program, so it won't end abruptly:

CLS
PRINT”*”
SLEEP

But... we want to move it, right? So, we'll have to choose a starting location:

dim as integer X = 12, Y = 40

we will start from row 12, column 40: let's show the player:

locate x,y 
print "*"

Ok, so we have our “player”; now, we need to move it. To move it, we are going to use the cursor keys. But how to do this in FreeBasic?

To detect if a key has been pressed, we can use MultiKey; to move the player, we simply can change the value of X and Y.

if MultiKey(&h48) then x = x - 1 'Cursor up
if MultiKey(&h50) then x = x + 1 'Cursor down
if MultiKey(&h4b) then y = y - 1 'Cursor left
if MultiKey(&h4d) then y = y + 1 'Cursor light

After that, we don't need to “remember” which key has been pressed: so, we can discard it, with:

while inkey<>"":wend

But, we don't want a single step, we want to move the player in all the game: so, we need a loop (that will be the main game loop):

do
...
loop until MultiKey(1)

(so, when ESC is pressed, the game will stop)

Here is the program, up to now:

cls
dim as integer X = 12, Y = 40
do 
locate x,y 
print "*" 
sleep
if MultiKey(&h48) then x = x - 1 
if MultiKey(&h50) then x = x + 1 
if MultiKey(&h4b) then y = y - 1 
if MultiKey(&h4d) then y = y + 1
while inkey<>"":wend 
loop until MultiKey(1)

Whoops! What happened? That trail was not supposed to be here! Simply, we have drawn the player, but we forgot to erase the player in the old position. We could do it with:

locate x,y 
print " "   ' a blank space will overwrite the asterisk

but we need to do it before we change value of X and Y, otherwise we won't erase the old player position, but the new one.

So:

cls
dim as integer X = 12, Y = 40
do 
locate x,y 
print "*" 
sleep
locate x,y 
print " "
if MultiKey(&h48) then x = x - 1 
if MultiKey(&h50) then x = x + 1 
if MultiKey(&h4b) then y = y - 1 
if MultiKey(&h4d) then y = y + 1
while inkey<>"":wend 
loop until MultiKey(1)

Well, now we can start drawing our room: to draw a wall, we can use the “#” character, so we only need to use the PRINT statement. So, after the CLS, let's add:

print "     ###################################################################" 
print "     #                                                                 #"

but, maybe we should draw the room many times: so, we'd like to be able to call the routine that draw the room everytime we need it. A subroutine is the best solution: since our subroutine will draw the room, we'll call it DrawRoom:

sub DrawRoom
cls 
print 
print 
print 
print "     ###################################################################" 
print "     #                                                                 #" 
print "     #                                                                 #" 
print "     #                                                                 #" 
print 
print 
print 
print 
print "     #                                                                 #" 
print "     #                                                                 #" 
print "     #                                                                 #" 
print "     ###################################################################"
end sub

If we add it to the program, nothing happens; in fact, the subroutine is available, but won't work, unless we call it.

So, we need to add a call: it can be done simply by adding:

DrawRoom

So, here is the program:

dim as integer X = 12, Y = 40
sub DrawRoom
cls 
print 
print 
print 
print "     ###################################################################" 
print "     #                                                                 #" 
print "     #                                                                 #" 
print "     #                                                                 #" 
print 
print 
print 
print 
print "     #                                                                 #" 
print "     #                                                                 #" 
print "     #                                                                 #" 
print "     ###################################################################"
end sub
DrawRoom
do 
locate x,y 
print "*" 
sleep
locate x,y 
print " "
if MultiKey(&h48) then x = x - 1 
if MultiKey(&h50) then x = x + 1 
if MultiKey(&h4b) then y = y - 1 
if MultiKey(&h4d) then y = y + 1 
while inkey<>"":wend 
loop until MultiKey(1)

Let's try it: it works, but... try to walk into a wall: what happened? The player can go through the wall! This is not good! In fact, we haven't done anything to prevent it: we need to implement something to detect collision. To detect if the player is walking in a wall, we need to know where is a wall: so, we need to keep wall position in memory: since we used string to represent walls, wa can use a string array.

In the first section, we create the array with

dim shared as string Roomtext (23) 	'since on a text screen we won't use more than 23 rows

(the 'shared' keyword means that the array will be used everywhere, in the main program and in subroutines) Then, we need to change all the PRINT statements in the DrawRoom subroutines with array assignments:

Roomtext( 1)= "" 
Roomtext( 2)= "" 
Roomtext( 3)= "" 
Roomtext( 4)= "     ###################################################################" 
Roomtext( 5)= "     #                                                                 #" 
.......

Of course, now, if we run our program, no room is displayed (because room data are in memory, but with no PRINT command are not shown) We should PRINT every line: of course, instead of writing:

PRINT Roomtext(1)
PRINT Roomtext(2)
PRINT Roomtext(2)
....

We can use a loop:

for I as integer=1 to 23
print Roomtext(i) 
next

Ok, so we are back: the room is shown, but player can go through walls. Now, anyway, we can check if the place where player is moving is free or not: we know that the place is located at X,Y. X is the row containing the player, and the walls that are in this row are stored in Roomtext(X), since Roomtext(1) is the first row, Roomtext(2) the second... The character stored at Y position in the Roomtext string represent the place where the player is (or will be): we can get it using the MID function; so, with 'mid(Roomtext(x),y,1)', we can know if there is a wall in player position.

Let's try it: after the 'if multikey ...' section, let's add:

locate 1,1:print mid(Roomtext(x),y,1)

The program will be:

cls
dim shared as string Roomtext (23)
dim as integer X = 12, Y = 40
sub DrawRoom
cls 
Roomtext( 1)= "" 
Roomtext( 2)= "" 
Roomtext( 3)= "" 
Roomtext( 4)= "     ###################################################################" 
Roomtext( 5)= "     #                                                                 #" 
Roomtext( 6)= "     #                                                                 #" 
Roomtext( 7)= "     #                                                                 #" 
Roomtext( 8)= "" 
Roomtext( 9)= "" 
Roomtext(10)= "" 
Roomtext(11)= "" 
Roomtext(12)= "     #                                                                 #" 
Roomtext(13)= "     #                                                                 #" 
Roomtext(14)= "     #                                                                 #" 
Roomtext(15)= "     ###################################################################"
for I as integer=1 to 23 
print Roomtext(i) 
next
end sub
DrawRoom
do 
locate x,y 
print "*" 
sleep 
locate x,y 
print " "
if MultiKey(&h48) then x = x - 1 
if MultiKey(&h50) then x = x + 1 
if MultiKey(&h4b) then y = y - 1 
if MultiKey(&h4d) then y = y + 1
locate 1,1:print mid(Roomtext(x),y,1) 
while inkey<>"":wend 
loop until MultiKey(1)

In the upper left corner, we will see a “#” if the player is walking in a wall. Let's change it into:

if mid(Roomtext(x),y,1)="#" then end

The program will end if the player touch a wall. Well, it's a bit exaggerated, isn't it? We only want to stop player, not to stop the game. We need to cancel the move: to do so, we should restore old value for X and Y: how to do that? Two new, “helper” variables are required: old_X and old_Y. First of all' let's define them, by changing:

dim as integer X = 12, Y = 40

into:

dim as integer X = 12, Y = 40, old_X, old_Y

Then, just before the 'if multikey ...' section (do you remember? In this section, values of X and Y change), let's add:

old_X=X: old_Y=Y

to “backup” old values; and also, after the 'if multikey ...' section:

if mid(Roomtext(x),y,1)="#" then x=old_X: y=old_Y

to restore old values (thus cancelling the move).

If we test the program, we'll see that player cannot go through walls anymore, but can still go outside the room, since there are two openings. Actually, when the player exit a room, should enter next room from the opposite side: this is not difficult to implement:

if y<6 then y=72 
if y>72 then y=6 
if x<3 then x=22 
if x>22 then x=3

(these lines must be placed after the 'if multikey...' section). Now, when player go left, he will disappear, and come back from the right side, and vice-versa (it's like an infinite corridor)

Ok, it's time to add new rooms! It's not difficult: we only need to modify the 'DrawRoom' routine, to be able to draw different rooms: we need replace the section with all the 'Roomtext ( 1)...' with another one, with the map of the new room, and then we need to select wich room we need to display: we can use the SELECT CASE command:

select case Room 
case 1 
Roomtext( 1)= "" 
Roomtext( 2)= "" 
Roomtext( 3)= "" 
Roomtext( 4)= "     ###################################################################" 
Roomtext( 5)= "     #                                                                 #" 
Roomtext( 6)= "     #                                                                 #" 
Roomtext( 7)= "     #                                                                 #" 
Roomtext( 8)= "" 
Roomtext( 9)= "" 
Roomtext(10)= "" 
Roomtext(11)= "" 
Roomtext(12)= "     #                                                                 #" 
Roomtext(13)= "     #                                                                 #" 
Roomtext(14)= "     #                                                                 #" 
Roomtext(15)= "     ###################################################################" 
case 2 
Roomtext( 1)= "" 
Roomtext( 2)= "" 
Roomtext( 3)= "                                         ###############################" 
Roomtext( 4)= "                                         #                             #" 
Roomtext( 5)= "                                         #                             #" 
Roomtext( 6)= "                                         #                             #" 
Roomtext( 7)= "                                         #                             #" 
Roomtext( 8)= "                                         #" 
Roomtext( 9)= "                                         #" 
Roomtext(10)= "                                         #" 
Roomtext(11)= "                                         #" 
Roomtext(12)= "                                         #                             #" 
Roomtext(13)= "                                         #                             #" 
Roomtext(14)= "                                         #                             #" 
Roomtext(15)= "                                         ###############################" 
end select

of course, the 'FOR...NEXT loop that actually draws the room has to be outside of the SELECT...END SELECT block, since it's used by every room. Another FOR...NEXT can be used to clean all 'RoomText' from previous data; at the beginning of 'DrawRoom' we can add something like:

for I as integer=1 to 23 
Roomtext(i)="" 
next

Also, we used the Room variable (SELECT CASE Room): so, we have to declare it (we need to know the room we are in, and this apply to every subroutine, so, that variable has to be global):

DIM SHARED Room=1

Ok, but... it didn't work! In fact, if we try our program, the player will always be in room 1! We wish that, when a player exit a room, he enter another room. Let's consider how a room is made:

There are four exits (not always all available: the first room, for example, has only East and West)

And, let's consider how many rooms are placed:

We can see that, to the west of room 1, there is room 2; going east, from room 1, we can reach room 3, and so on.

So, for each room, we need to set all exits: first, let's declare a variable for each exit:

dim shared as integer N, S, E, W  ' North, South, East, West

Then, in DrawRoom, we set them: in first room

CASE 1 
..... 
W=2:E=3

In second room:

CASE 2 
.... 
E=1

Of course, in the main loop, we have to locate the section that is triggered when the player go outside a room:

if y<6 then y=72  
if y>72 then y=6  
if x<3 then x=22  
if x>22 then x=3

And use it to set the new room:

if y<6 then y=72: Room=W: DrawRoom  
if y>72 then y=6: Room=E: DrawRoom  
if x<3 then x=22: Room=N: DrawRoom  
if x>22 then x=3: Room=S: DrawRoom

basically, when the player go north, the room defined by the N variable became the current room: since it still has to be drawn, 'DrawRoom' is called. The same happend for east, west and south, of course.

Ok, so, we can add all the nine rooms: for every room, we only need to look at the map, to see how many exits there are, and to which room they point.

The complete program is in the file 'rooms.bas'

And that's all, for now!

As exercise, you can try to improve the program. Here are some suggestions: try adding more rooms (easy) add some traps: you can do it with 'O' character, if the player touch them, he will die (easy) as above, but make moving enemies (medium) use a graphic mode instead of console mode (medium) put some objects in rooms (keys, weapons...) and make the player able to take it.(medium) Add some obstacles, that require a certain item: i.e., a door that can be opened only with a key.(hard) port the game in graphic mode, instead of text mode (hard) add enemies with different behavior: some will chase the player, some will guard an area, some will sleep and attack only if awaken... (hard)

Good work!


| top |