Tutorial XIII: Wrapping Paper
We're not wrapping Christmas presents, but making Pac-Man wrap to the other side of the maze once he walks off one side. This is an easy feature, all we need to do is reposition a sprite.
Find collision()
Remember, this is the function that does collision between Pac-Man and ghosts, dots, and fruits. This was supposed to handle maze collision too, but we've already implemented an easier way to do collision in the move_pac() function.
We'll be checking Pac-Man's sprite positions and repositioning him, so let's cache the positions.
Put this code at the top of collision()
Put the code in the second box after "`maze wrapping"
local xpos as word
local ypos as word
xpos = sprite x(spr_pac)
ypos = sprite y(spr_pac)
`check if Pac-Man is out of bounds
sprite spr_pac, xpos, ypos, img_pac
Again, we can simply change the cached variables and set the sprite's position to them when we're finished. Now we need to check if Pac-Man's coordinates are out of bounds, and if they are, we need to change them. When checking if Pac-Man is going too far left, we need to check if xpos is less than 32, not 0 (0 is as far left as you can go without going outside the screen.) This is because of the 32 pixel margin that we made while setting up the maze array, we want Pac-Man to stop at that margin, not walk out into it to the edge of the window! We'll also need to do this for ypos in case we change the maze so there's a up/down wrapping portal. If Pac-Man gets out of bounds going up or down, we need to position him at the maze's size (number of rows or columns) * 32 pixels so he appears on the other side. We do the opposite if Pac-Man goes too far down or right, we check if the coordiantes are greater than the maze size * 32, and if they are, we set them to 32.
Put this code above the sprite command
`check if Pac-Man is out of bounds
if xpos < 32 then xpos = maze_columns * 32
if xpos > maze_columns * 32 then xpos = 32
if ypos < 32 then ypos = maze_rows * 32
if ypos > maze_rows * 32 then ypos = 32
Compile and Run
Well that was easy.
Try going through the portal; instead of walking out into limbo Pac-Man safely appears on the other side of the maze. While we've been coding Pac-Man's movement, a few problems have piled up:
If the player were to press the opposite arrowkey that Pac-Man is moving in, Pac-Man becomes slightly unresponsive.
The dots appear above Pac-Man, not below.
Let's fix the unresponsivness. Pac-Man can't reverse directions very fast because his direction can only change when he's pixel perfect. We can fix that by adding code that executes when he's not pixel perfect. All we need is something that checks if pac_try and pac_dir are opposite directions, and if they are, make pac_dir equal to pac_try. We already have enough select statementblocks in this function, here's an easier way to see if they're opposites:
Remember our directions list? Here it is repeated 3 times, can you see how we'll decide if 2 numbers are oppsite directions?
1 - up
2 - right
3 - down
4 - left
1 - up
2 - right
3 - down
4 - left
1 - up
2 - right
3 - down
4 - left
Adding 2 will get us to the opposite direction, but since there is no 5 or 6 we can subtract 4 from them to get 1 ot 2. The 0 direction (stationary) might mess this up, so we need to put an if statement before this code to stop it from executing when the direction is 0. We're not going to check that pac_dir and pac_try are opposites, we're going to check that pac_dir and the
opposite of pac_try are
equal. (Remember these are opposite directions, not mathematical opposites.) Once we know that they're equal, we can set pac_dir to pac_try.
Change the endif in move_pac() to an else
Add this code below the else
Put the variable declaration at the top of move_pac()
Compile and Run
if pac_try
reverse = pac_try + 2
if reverse > 4 then dec reverse, 4
if reverse = pac_dir then pac_dir = pac_try
endif
endif
Now that's better! Pac-Man is now very responsive.
If the player were to press the opposite arrowkey that Pac-Man is moving in, Pac-Man becomes slightly unresponsive.
The dots appear above Pac-Man, not below.
This next problem is because of
sprite priority. Sprites are drawn in the opposite order they're created, and if you follow the program flow you'll see that the dots were created after Pac-Man. We can override this tendancy by using the command
set sprite priority. The sprites with higher priorities are drawn last, and since all sprites start with priority 0, setting Pac-Man's priority to 1 will make him appear on top.
Switch to Init Game.dba
Add this code to load_media()
set sprite priority spr_pac, 1
It works now, but the fact that we could even
see Pac-Man correctly underneath the dots eariler reveals another problems: the dots' transparency it turned on. The dots don't need to be transparent because there's nothing to see underneath them, this is using up performance time for no reason. We can fix this by using the command
set sprite, but that leads to another problem:
If the player were to press the opposite arrowkey that Pac-Man is moving in, Pac-Man becomes slightly unresponsive.
The dots appear above Pac-Man, not below.
The dots' transparency is turned on
The dots' backsave is turned on
The set sprite command handles backsave in addition to transparency. When
backsave is turned on, Dark Basic Professional will make sure that the sprite does not leave an images of itself as it moves. The dots don't even move, so we can turn this off and gain another performance boost.
Switch to Game.dba
Find make_dot()
Add this code to make_dot()
`turn off backsave and transparency
set sprite sprs_dots().id, 0, 0
This is the first time we've used
set sprite, isn't it? That means the walls also have transparency and backsave on, they don't move and there's nothing underneath them to see. Also, even though Pac-Man moves, we can turn off backsave for him too because now that the dots and walls are constantly drawing black, which will cover up any left over image.
If the player were to press the opposite arrowkey that Pac-Man is moving in, Pac-Man becomes slightly unresponsive.
The dots appear above Pac-Man, not below.
The dots' transparency is turned on
The dots' backsave is turned on
The walls' transparenct is turned on
The walls' backsave is turned on
Pac-Man's backsave is turned on
Let's begin with Pac-Man.
Switch over to Init Game.dba
Find load_media()
Add this code to load_media()
We don't set transparency off because if we did Pac-Man would have a black box around him, this would be inivisble (black is the background color, remember) but it would overlap the dots.
Now we need to do the walls.
Find position_maze()
Add this code after the sprite-making line
`turn off backsave and transparency
set sprite sprs_maze(column, row).id, 0, 0
If the player were to press the opposite arrowkey that Pac-Man is moving in, Pac-Man becomes slightly unresponsive.
The dots appear above Pac-Man, not below.
The dots' transparency is turned on
The dots' backsave is turned on
The walls' transparenct is turned on
The walls' backsave is turned on
Pac-Man's backsave is turned on
There we go. Your complete code should look like this:
(Main.dba)
Rem Project: Pac-Man
Rem Created: [date] [time]
Rem ***** Main Source File *****
`global arrays
dim chunk(2, 2) as byte
dim map(2, 2) as byte
`setup
sync on
sync rate 60
hide mouse
main_menu()
(Init Game.dba)
Rem *** Include File: Init Game.dba ***
Rem Created: [date] [time]
Rem Included in Project: Pac-Man.dbpro
`load the game
function init_game()
set dir "Media"
load_media()
get_maze_data()
position_maze()
set dir ".."
game_inited = 1
endfunction
`load the game images and make sprites
function load_media()
local xpix as word
local ypix as word
local img_ptr as dword
local img_maze as word
dim sprs_dots() as yum
dim imgs_fruits(11) as fruit
dim imgs_maze(28) as word
dim imgs_ghosts(5) as word
dim sprs_ghosts(4) as word
img_maze = load_img("Maze.bmp", 1)
paste image img_maze, 0, 0
`get the maze images from the pasted image
for ypix = 0 to 159 step 32
for xpix = 0 to 191 step 32
`skip the blank
if img_ptr < 29
`capture the 32*32 image at these coordinates
imgs_maze(img_ptr) = get_img(xpix, ypix, xpix+32, ypix+32, 1)
`move the current wall image pointer
inc img_ptr
endif
next xpix
next ypix
`we're done with Maze.bmp
delete image img_maze
`load dot images
img_bdot = load_img("Bdot.bmp", 1)
img_sdot = load_img("Sdot.bmp", 1)
`the rest of the images have the purple transparent background
set image colorkey 255, 0, 255
`load Pac-Man
img_pac = free_img()
spr_pac = free_spr()
create animated sprite spr_pac, "Pac-Man.bmp", 2, 4, img_pac
sprite spr_pac, 0, 0, img_pac
set sprite priority spr_pac, 1
set sprite spr_pac, 0, 1
endfunction
`get the maze data from a file
function get_maze_data()
local file_maze as byte
local str as string
local row as dword
local column as dword
`open maze file
file_maze = open_to_read("Maze.map")
`get maze dimensions
read string file_maze, str
csv$(str)
maze_columns = val(csv$(""))
maze_rows = val(csv$(""))
`create maze array
dim sprs_maze(maze_columns + 1, maze_rows + 1) as wall
`load maze array data
for row = 1 to maze_rows
`read entire row
read string file_maze, str
for column = 1 to maze_columns
`select type of tile
select mid$(str, column)
case "#"
`wall
sprs_maze(column, row).solid = 1
endcase
case "."
`small dot
sprs_maze(column, row).food = 1
endcase
case "*"
`big dot
sprs_maze(column, row).food = 2
endcase
case "G"
`ghost gate
sprs_maze(column, row).solid = 2
endcase
case "P"
`ghost pen area
pos_ghost_pen.x = column
pos_ghost_pen.y = row
endcase
case "S"
`Pac-Man spawn point
pos_pac_spawn.x = column
pos_pac_spawn.y = row
endcase
case "W"
`wrap point
sprs_maze(column, row).solid = 1
`change the maze tile adjacent to this tile's solid state to 1
if row = 1
`upper tile needs to be set
sprs_maze(column, 0).solid = 1
else
if row = maze_rows
`lower tile needs to be set
sprs_maze(column, maze_rows + 1).solid = 1
else
if column = 1
`left tile needs to be set
sprs_maze(0, row).solid = 1
else
if column = maze_columns
`right tile needs to be set
sprs_maze(maze_columns + 1, row).solid = 1
endif
endif
endif
endif
endcase
case "R"
`"Ready!" point
pos_ready.x = column
pos_ready.y = row
endcase
endselect
next column
next row
`close the map file
close file file_maze
endfunction
`make and position the maze sprites using the data collected from get_maze_data()
function position_maze()
local column as dword
local row as dword
local img_ptr as dword
for row = 1 to maze_rows
for column = 1 to maze_columns
`select image
select sprs_maze(column, row).solid
case 0
img_ptr = 11
endcase
case 1
img_ptr = pick_img(column, row)
endcase
case 2
img_ptr = 27
endcase
endselect
`make sprite for this tile
sprs_maze(column, row).id = make_spr(column * 32, row * 32, imgs_maze(img_ptr))
`turn off backsave and transparency
set sprite sprs_maze(column, row).id, 0, 0
next column
next row
endfunction
`decide which wall piece goes in the location given by the parameters
function pick_img(column as dword, row as dword)
local img as byte
local x as dword
local y as dword
local try as byte
local cell as dword
local pass as boolean
`copy the maze solid data into a chunk of memory for easier access
for x = 0 to 2
for y = 0 to 2
chunk(x, y) = sprs_maze(column + x - 1, row + y - 1).solid
next y
next x
`go back to the beginning of the data
restore pick_imgs
`try each map
for try = 1 to tries
`map matches by default
pass = 1
`copy the map solid data into the map chunk
for cell = 0 to 8
`read cell data
read map(cell)
`compare it with the maze data if it isn't the middle square
`if map doesn't match set passing variable to
if cell <> 4 and map(cell) <> 3 and map(cell) <> chunk(cell) then pass = 0
next cell
if pass
`map matches
img = map(4)
exitfunction img
endif
next try
`no matching piece found
img = 4
endfunction img
pick_imgs:
#constant tries = 27
data 3, 1, 1
data 0, 0, 1
data 3, 1, 1
data 0, 0, 3
data 1, 1, 0
data 1, 1, 0
data 3, 0, 3
data 0, 2, 1
data 3, 0, 3
data 3, 0, 3
data 1, 3, 1
data 0, 1, 0
data 1, 1, 1
data 1, 4, 1
data 1, 1, 1
data 3, 0, 3
data 1, 5, 0
data 0, 1, 3
data 3, 0, 3
data 1, 6, 1
data 1, 1, 1
data 1, 1, 0
data 1, 7, 0
data 0, 0, 3
data 3, 0, 3
data 0, 8, 0
data 3, 1, 3
data 3, 1, 3
data 0, 9, 0
data 3, 1, 3
data 3, 0, 3
data 0, 10, 0
data 3, 0, 3
data 1, 1, 3
data 1, 12, 0
data 1, 1, 3
data 0, 1, 1
data 0, 13, 1
data 3, 0, 0
data 3, 1, 0
data 0, 14, 1
data 3, 1, 0
data 0, 1, 0
data 1, 15, 1
data 0, 1, 0
data 3, 0, 3
data 1, 16, 1
data 3, 0, 3
data 0, 1, 3
data 1, 17, 0
data 0, 1, 3
data 1, 1, 1
data 1, 18, 1
data 3, 0, 3
data 3, 0, 0
data 0, 19, 1
data 0, 1, 1
data 3, 1, 0
data 0, 20, 1
data 3, 0, 3
data 0, 1, 0
data 1, 21, 1
data 3, 0, 3
data 3, 0, 3
data 1, 22, 0
data 3, 0, 3
data 3, 1, 3
data 0, 23, 0
data 3, 0, 3
data 0, 1, 3
data 1, 24, 0
data 3, 0, 3
data 3, 0, 3
data 0, 25, 1
data 3, 1, 0
data 3, 0, 3
data 1, 26, 2
data 3, 0, 3
data 3, 0, 3
data 2, 28, 1
data 3, 0, 3
(Game.dba)
Rem *** Include File: Game.dba ***
Rem Created: [date] [time]
Rem Included in Project: Pac-Man.dbpro
global game_inited as boolean
global img_pac as word
global spr_pac as word
global img_sdot as word
global img_bdot as word
global spr_fruit as word
global maze_rows as word
global maze_columns as word
global pos_ghost_pen as coordinate
global pos_pac_spawn as coordinate
global pos_ready as coordinate
global pac_try as byte
global pac_dir as byte
type yum
id as word
size as boolean
endtype
type fruit
id as word
eaten as boolean
endtype
type wall
id as word
solid as byte
food as byte
endtype
type coordinate
x as integer
y as integer
endtype
`play the game
function play()
`if menu is not already loaded, load it
if game_inited = 0 then init_game()
level_up()
ready()
game_loop()
endfunction
`move to the next level
function level_up()
make_dots()
respawn_pac()
respawn_ghosts()
endfunction
`fill the board with dots
function make_dots()
local x as dword
local y as dword
`check each tile
for y = 1 to maze_rows
for x = 1 to maze_columns
`make a dot at this tile if there's supposed to be one
if sprs_maze(x, y).food > 0
make_dot(sprs_maze(x, y).food - 1, x*32 + 16, y*32 + 16)
`make dots halfway between tiles
if sprs_maze(x+1, y).food > 0
make_dot(0, (x+1) * 32, y*32 + 16)
endif
if sprs_maze(x, y+1).food > 0
make_dot(0, x*32 + 16, (y+1) * 32)
endif
endif
next x
next y
endfunction
`make a dot
function make_dot(size as boolean, xpix as word, ypix as word)
`make a new dot array slot
array insert at bottom sprs_dots()
`copy size argument into array
sprs_dots().size = size
`make dot sprite
if size
sprs_dots().id = make_spr(xpix, ypix, img_bdot)
else
sprs_dots().id = make_spr(xpix, ypix, img_sdot)
endif
`center dot on coordinates
offset sprite sprs_dots().id, sprite width(sprs_dots().id) / 2, sprite height(sprs_dots().id) / 2
`turn off backsave and transparency
set sprite sprs_dots().id, 0, 0
endfunction
`place pac-man in the spawn area
function respawn_pac()
sprite spr_pac, pos_pac_spawn.x*32, pos_pac_spawn.y*32, img_pac
endfunction
`place the ghosts in the ghost pen
function respawn_ghosts()
endfunction
`play the music and display "Ready" at the start of the game
function ready()
endfunction
`the game's main loop
function game_loop()
do
cls
get_pac_move()
get_ghost_move()
move_pac()
move_ghosts()
collision()
hud()
text 0, 0, "Frames Per Second: " + str$(screen fps())
sync
loop
endfunction
`get player input
function get_pac_move()
select pac_try
case 0
if upkey() then pac_try = 1
if rightkey() then pac_try = 2
if downkey() then pac_try = 3
if leftkey() then pac_try = 4
endcase
case 1
if upkey() = 0 then pac_try = 0
endcase
case 2
if rightkey() = 0 then pac_try = 0
endcase
case 3
if downkey() = 0 then pac_try = 0
endcase
case 4
if leftkey() = 0 then pac_try = 0
endcase
endselect
endfunction
`do ghost AI
function get_ghost_move()
endfunction
`move pac-man
function move_pac()
local xpos as word
local ypos as word
local row as dword
local column as dword
local reverse as byte
`cache Pac-Man position
xpos = sprite x(spr_pac)
ypos = sprite y(spr_pac)
`find current tile Pac-Man is in
column = xpos/32
row = ypos/32
`check if Pac-Man is completely in a tile
if xpos/32.0 = column and ypos/32.0 = row
`Pac-Man is completely in a square, now give player chance to change direction
`if there's an open tile
select pac_try
case 1
if sprs_maze(column, row-1).solid = 0 then pac_dir = 1
endcase
case 2
if sprs_maze(column+1, row).solid = 0 then pac_dir = 2
endcase
case 3
if sprs_maze(column, row+1).solid = 0 then pac_dir = 3
endcase
case 4
if sprs_maze(column-1, row).solid = 0 then pac_dir = 4
endcase
endselect
`stop Pac-Man from moving if he's about to walk into a wall
select pac_dir
case 1
if sprs_maze(column, row-1).solid => 1 then pac_dir = 0
endcase
case 2
if sprs_maze(column+1, row).solid => 1 then pac_dir = 0
endcase
case 3
if sprs_maze(column, row+1).solid => 1 then pac_dir = 0
endcase
case 4
if sprs_maze(column-1, row).solid => 1 then pac_dir = 0
endcase
endselect
else
if pac_try
reverse = pac_try + 2
if reverse > 4 then dec reverse, 4
if reverse = pac_dir then pac_dir = pac_try
endif
endif
select pac_dir
case 1
dec ypos
endcase
case 2
inc xpos
endcase
case 3
inc ypos
endcase
case 4
dec xpos
endcase
endselect
sprite spr_pac, xpos, ypos, img_pac
endfunction
`move ghosts
function move_ghosts()
endfunction
`do collision
function collision()
local xpos as word
local ypos as word
xpos = sprite x(spr_pac)
ypos = sprite y(spr_pac)
`maze wrapping
`check if Pac-Man is out of bounds
if xpos < 32 then xpos = maze_columns * 32
if xpos > maze_columns * 32 then xpos = 32
if ypos < 32 then ypos = maze_rows * 32
if ypos > maze_rows * 32 then ypos = 32
sprite spr_pac, xpos, ypos, img_pac
`eat ghosts
`get eaten
`eat dots
`eat fruits
endfunction
`update the heads up display
function hud()
endfunction
(Free.dba)
Rem *** Include File: Free.dba ***
Rem Created: [date] [time]
Rem Included in Project: Pac-Man.dbpro
function free_img()
local id as word
repeat
inc id
until image exist(id) = 0
endfunction id
function free_spr()
local id as word
repeat
inc id
until sprite exist(id) = 0
endfunction id
function free_sound()
local id as word
repeat
inc id
until sound exist(id) = 0
endfunction id
function free_file()
local id as byte
repeat
inc id
if id = 33
id = 0
exitfunction id
endif
until file open(id) = 0
endfunction id
(Media Allocate.dba)
Rem *** Include File: Media Allocate.dba ***
Rem Created: [date] [time]
Rem Included in Project: Pac-Man.dbpro
function load_img(filename as string, textureflag as boolean)
local id as word
id = free_img()
load image filename, id, textureflag
endfunction id
function get_img(left as word, top as word, right as word, bottom as word, textureflag as boolean)
local id as word
id = free_img()
get image id, left, top, right, bottom, textureflag
endfunction id
function load_sound(filename as string)
local id as word
id = free_sound()
load sound filename, id
endfunction id
function make_spr(xpos as word, ypos as word, img as word)
local id as word
id = free_spr()
sprite id, xpos, ypos, img
endfunction id
function open_to_read(filename as string)
local id as byte
id = free_file()
open to read id, filename
endfunction id
function open_to_write(filename as string)
local id as byte
id = free_file()
open to write id, filename
endfunction id
(Menu.dba)
Rem *** Include File: Menu.dba ***
Rem Created: [date] [time]
Rem Included in Project: Pac-Man.dbpro
global menu_inited as boolean
`run the main menu
function main_menu()
`if menu is not already loaded, load it
if menu_inited = 0 then init_menu()
`*** Temporary Code ***
`play the game
play()
`*** Temporary Code ***
`menu loop
do
`get user selection
`process user selection
loop
endfunction
`load the menu's images and sprites
function init_menu()
menu_inited = 1
endfunction
(String.dba)
Rem *** Include File: String.dba ***
Rem Created: [date] [time]
Rem Included in Project: Pac-Man.dbpro
global csv_start as word
global csv_values as string
`extracts individual values from a comma separated values string
function csv$(called_values as string)
local count as word
local return_value as string
`if the function was called with values, replace the saved values
if called_values <> ""
csv_values = called_values
csv_start = 1
exitfunction ""
endif
`if all the tokens have been taken return end of string
if csv_start > len(csv_values) then exitfunction "END OF STRING"
`look for a comma
for count = csv_start to len(csv_values)
`if the character's a comma, return the token
if mid$(csv_values, count) = ","
return_value = right$(left$(csv_values, count - 1), count - csv_start)
csv_start = count + 1
exitfunction return_value
endif
next count
`no comma found, so return last token
return_value = right$(csv_values, count - csv_start)
csv_start = count
endfunction return_value