Sorry your browser is not supported!

You are using an outdated browser that does not support modern web technologies, in order to use this site please update to a new browser.

Browsers supported include Chrome, FireFox, Safari, Opera, Internet Explorer 10+ or Microsoft Edge.

DarkBASIC Professional Discussion / Pac-Man: A Tutorial on Game Design and Development

Author
Message
Code Dragon
17
Years of Service
User Offline
Joined: 21st Aug 2006
Location: Everywhere
Posted: 10th Nov 2006 22:18 Edited at: 22nd Apr 2008 17:22
Pac-Man: A Tutorial on Game Design and Development


There's many people who would love to create their very own game, and when they discover the power of Dark Basic Professional, they rush into making the next MMORPG. Most give up because of inexperience, code obfuscation, or bad design.

Programming a large game takes experience, and lots of it. Some newcomers don't heed the warning that making a huge game right away is impossible, but with a little practice it's really not all that hard.

In this tutorial we'll create a complete Pac-Man game. It's not a very large game, but it's big enough to explain how larger games are programmed. We will also learn how to apply software methodologies which will make the journey much easier and fun.

Assumptions
You should at least know how to program a simple 2d game in Dark Basic Professional (i.e. Pong, Breakout) and how to use the default IDE. It is also assumed that will actually read the code, not simply copy it. You won't learn anything from this tutorial if you copy. I will explain how the code works, but if there's a confusing piece of code that simply doesn't make sense, make a post here and I'll edit the description to be more understandable.

If you're a newcomer...
This tutorial isn't in the Newcomers board; that's because it's not for beginners; it's more for intermediates. If you're a beginner, do these tutorials first, they're easier than this one and will teach you the basics.

The Newcomers Guide To Creating First Person Shooters
A Tutorial on Arrays
A Tutorial On Functions
Asset Management
TDK's tutorials for newcomers

Download
If you don't have time to read the tutorial now, you can download it here:
http://forumfiles.thegamecreators.com/?i=1236541

Tutorial I: How to Play Pac-Man

Any complicated game needs a design document to explain exactly what the game will do. To get started creating a design document for our Pac-Man game, follow these steps.

Run Dark Basic Professional
Create a New Project
Name it Pac-Man
Open the Pac-Man folder
Create a New Text Document
Rename it Design Document.txt

Now, if you were to explain Pac-Man to someone who never played it, what would you say? If a reader of your design document has the same vision of your game that you do, you have a very good design document. So, let's explain Pac-Man.

Copy the text below into Design Document.txt
Save it
Read it



Ok, so that's it for the design document. A design document not only explains your goals as a game programmer, but it can also help get your ideas together. Now we can move on to actually making the game.

Tutorial II: Pretty Pictures

Before we dive into coding, it'd be a good idea to collect out media first. The Artist's Challenge.. thread is a good place to start. There's some ghosts, maze pieces, dots, fruits, and pac-man.

Take a look at these maze sprites.



When we build our maze we'll need a system that chooses which wall piece for certain locations. (A rounded courner piece wouldn't look very nice out in the open, would it? ) You might be wondering why there's so many half dots; this confuses me too so we'll keep it simple and only use 1 small dot image and 1 big dot image.

Because these maze sprites are missing some pieces, I've included an edited verison in the attached file.

Here's the ghosts.



As you can see, there's a different image for each ghost going in each direction, and the same thing for pac-man. In fact, there are two for each direction (the ghosts have gliding animation and pac-man has eating animation) We'll need to implement an intelligent animation system for the ghosts and pac-man, which changes the animation based on direction.

Lastly, here's the fruits.



As you can see, not all of those images are fruits, but there's more fruits back at the art thread; I've included these in the download as well.

Create a Media folder in the pac-man folder
Save the ghosts image as Ghosts.bmp
Download the attached zip
Save the extracted images in the Media folder

Be sure to save these images as bmp because png uses a different way to make transparent colors than we will. We'll write code to crop these pictures into their individual animations and maze tiles, instead of doing it ourselves.

Ok, we've got enough media for starters, if we need more we'll look for it as we need it.

Attachments

Login to view attachments
Profit
18
Years of Service
User Offline
Joined: 19th Feb 2006
Location: United States
Posted: 10th Nov 2006 23:31
Good job on this. Seriously. This is definitely one of the better tutorials out there.

common people are walking in line.

Mitsu Fly Ride.
jinzai
17
Years of Service
User Offline
Joined: 19th Aug 2006
Location: USA
Posted: 11th Nov 2006 03:36 Edited at: 11th Nov 2006 03:39
Code Dragon...That is very nice. I agree with open_Froz...nice subject, nice layout, nice style...all very nice.

I'm looking forward to your next installment. I hadn't thought about using the media from that forum...thanks!

I am considering "porting" AstroBlast! to DBPro from Timex (sorry, Sir Clive!) BASIC. That game was the best! Besides, I can think of nothing better than to make 8x8 2D sprites with TextureMaker. Talk about a big hammer!

Does it occur to you that your tutorial is based on a game that is older than most of your tutorial's audience?
Code Dragon
17
Years of Service
User Offline
Joined: 21st Aug 2006
Location: Everywhere
Posted: 12th Nov 2006 00:21 Edited at: 18th Feb 2007 15:26
@jinzai
I don't often play games older than me, but never would I have dreamed I'd be writing a tutorial on one of them! I know Pac-Man is really old, but I've never heard anyone say they haven't played it.

I would just like to point out that it's impossible to make a game older than yourself.

I was ahead on the tutorial so here's number 3 a day early.

Tutorial III: Methodologies to Stop You From Going Crazy

To create a large game without going crazy, it's a good idea to approach programming it carefully. In our Pac-Man game, we will be following three software methodologies to keep high quality in our code. Remember, writing high quality code actually takes less time than writing low quality code because you nearly eliminate debugging time.

Top-Down Design
Zero Defect Software Development
Self-Contained Code

Good programming practices are the best weapon against code obfuscation; if you don't plan out how you will code your game, it's as good as planning to fail.

The first step in Top-Down Design is to list the operations your program will perform. These operations will be performed by self-contained functions. We can use the design document to come up with a rough list of the operations of out Pac-man game.

Move Pac-Man
Eat dots
Spawn Pac-Man
Kill Pac-Man
Eat ghosts
Place dots
Level up
Spawn ghosts
Ghost AI
Spawn fruits
Eat fruits
Maze Collision
Maze wrapping
Update HUD

Of course, a list is a very basic way to outline a game. Feel free to make diagrams, maps, OO charts, flowcharts - anything that helps you organize what your game does.

Now, let's decide how to we'll structure our code. We'll make a file for the gameplay, and the main menu.

Include Game.dba as a new source file
Include Menu.dba as a new source file
Rename the main source file as Main.dba (If you don't know how to, don't bother. )

The way the menu will interface with the game is to have the main menu be called immediately after startup, and have the main menu call the game when the player presses the play button. If the game is paused, the game loop does an exitfunction back to the main menu, where the player can press pause again to resume the game, and the game loop is called again to continue the game.

Note: We will need a global variable called paused so the main menu can alert the player that they are still playing if they want to start a new game.

The second step in Top-Down Design is to write the top-level function. But before we do that, what is a top-level function? The answer is quite simple; it's first function to be called. Think of a tree. The top-level function is the trunk, and the branches coming from the trunk are the functions it calls. Those functions also call functions, just as tree branches divide into smaller branches. This recursive-like process continues, breaking a function into easier to code fragments, until they can be coded easily. On a tree the branches get smaller and smaller until they are small enough for leaves to grow.

Note: We are only writing the skeleton code for now, the actual algorithms will be implemented later.

The top-level function in many languages is named main(). This is because main() is the function that gets executed first. But in BASIC, we have no main() function, instead, we have code at the top of the main source file; that acts as our top-level function.

Type this code into the main source file.



All the top-level code does is set up the sync, hide the mouse, and call the main menu.

Now we need to define main_menu(), because the top-level code calls it.
Make sure you are in the menu file.



As you can see the main menu runs a loop that looks for user input and processes it, we have just not put the algorithms in yet.

Now we need to define init_menu(). init_menu() will set up all the images and sprites the menu uses. If you look at the first line in main_menu() you'll see it's called automatically by main_menu().

However, this function is specific enough that we will not need to break it down into smaller functions. This is where top-down design stops, we will backtrack to main_menu() for now but we'll eventually fill this function with code so let's define it anyway.



The menu_inited variable keeps track of whether or not our menu has been loaded yet, once it is loaded we will set this variable to 1, so the menu doesn't load again, otherwise we'd get a memory leak with it trying to make 20 "options" sprites or something.

Remember that in main_menu() we have only defined what the function will do, not how it will do it. We do this intentionally because we will not decide on a data structure until all the skeleton code is written, which will be to a much better time to decide. For now, we want the game at least start, so put this code before the menu loop.



Notice that Menu.dba is self-contained; it gets it's own media by itself and remembers if it has done so yet. When we complete the menu, keeping it completely independent of the game, we have a pre-made menu that we can drop into any game we want! If Game.dba is self-contained as well, we can include it in future projects, like the way the game Pac-Man World 2 has all the original Pac-Man games in it.

Code Dragon
17
Years of Service
User Offline
Joined: 21st Aug 2006
Location: Everywhere
Posted: 13th Nov 2006 21:02 Edited at: 18th Feb 2007 15:26
Tutorial IV: Coding the Game

Now that the base code for the main menu is up, let's move over to Game.dba and type in the play() function.



play() loads the game media by calling init_game(), and starts the game by calling start_game() and game_loop(). Just like in the main menu we have an inited variable because once the media has been loaded, if a player wants to replay the game, why load the media again? This boolean variable, game_inited, which remembers if the game media has been loaded yet is called a flag.

Let's define init_game(); this is the function that loads the game media. Because the maze is in pieces (look at Maze.bmp), it must load the data telling how to put those pieces together (using a map of the maze) from a file before it positions the maze sprites. Remember the media is in the media folder, so we'll need to put a set dir command in to locate the Media folder.

Note: These are all operations which will only be performed once, that's why init_game() is only called once.



These functions cannot be subdivided much further, so let's simply write the skeleton code for them and move on.



Now that init_game() and its functions have been defined, let's return to play(); the next function is start_game(). What happens when the game starts? Simply, Pac-Man and the ghosts are spawned, and so are the dots. Also, some sounds play and "Ready" appears on screen. We did not put these routines in init_game() because they will need to be performed more than once - every time the user clicks play in a single execution.



Now let's take a look at this code. This is exactly what happens in a level up, besides ready()
Why don't we simply replace start_game() with level_up()? Let's do that.

Note: If you're wondering if level_up() will increase the level before the game has even begun, remember all variables start with 0 so this works out fine.

Here is the new play() function, which contains the new calls to level_up() and ready().



And change start_game() to level_up(), taking out ready().



Now we must define all the level_up() functions.



You might be thinking "Why are we respawning pac-man, that will take away a life every time there's a level up! No fair!" Although respawn_pac() will be called by level_up() and get_eaten(), we only want to deduct a life when it is called from get_eaten(). All we need to do is put the life deducter in get_eaten() instead.

Now, we're almost ready to compile this so we can be sure it is free of syntax errors, we'll define the rest of the play() functions.



Without anything in the game loop a game wouldn't do anything, no matter how much media it has. Let's finish up the main loop and then we can test it. Because of our design document and operations list, this part is very easy.
Here's the list again.

Move Pac-Man
Eat dots
Spawn Pac-Man
Kill Pac-Man
Eat ghosts
Place dots
Level up
Spawn ghosts
Ghost AI
Spawn fruits
Eat fruits
Maze Collision
Maze wrapping
Update HUD

We already have some of the functions for these operations done (shown with a ), but the others will be done in the main loop. Some of these operations are very similar so here's a condensed to-do list for the main loop.

Get NPCs' move
Move NPCs
Dot and Fruit Collision
Deadly Collision
Maze Collision and Wrapping
Update HUD

First, the input and AI functions will be called, then the moving routines, and then collision routines, and finally the HUD. It will be easier to implement an object system to check for collision, that way we only have to write one routine for resloving collision on the ghosts and pac-man, not for every ghost. The main loop of pac-man lends itself to a general object collision system because nearly all the processes have to do with collision (i.e. getting eaten, eating dots, eating fruit, staying in the maze)

Note: The AI and the input functions must be done after one another without either of them moving first because if pac-man were to move before the ghost AI ran, the ghost AI would have an advantage because it's seen Pac-Man's move for the turn already. Since the functions cannot be executed simultaniously they can be executed independently, which is just as good.

Here's the new game loop with calls to functions to process everything on that list.



And lastly, we'll define the functions for main loop. We're not actually using them right now, but the compiler will report an error if we don't define them.



Just so our game looks nicer...

Set the window caption to "Pac-Man"
Set the display settings to windowed.
Set the windowed screen resolution to 1024x768

Your code should now look like this:

(Main.dba)


(Menu.dba)


(Game.dba)


Let's save and hit the compile button. You should see the current frames per second, 60.
Obviously there's no gameplay yet, we didn't program anything but the skeleton code. Still, Top-Down Design pays off, it will be much easier to make our game from now on.

Now that the base code is in place, all we have to do now is choose a data structure and fill the functions in with code. We've invested a small amount of time to break an almost overwealming program into many easy mini-programs. The hard part is over.

UFO
18
Years of Service
User Offline
Joined: 11th Oct 2005
Location:
Posted: 13th Nov 2006 21:18
Wow. This tutorial is amazing

I wish I was this organized with my projects

Code Dragon
17
Years of Service
User Offline
Joined: 21st Aug 2006
Location: Everywhere
Posted: 13th Nov 2006 21:44 Edited at: 15th Apr 2007 03:24
Thanks for all the positive feedback everyone, keeping code organized is something I learned the very hard way. If someone looked at my old 3600 line text adventure, they wouldn't understand anything past line 100.

This was probobly the first organized program I wrote. (written for QBASIC.)



What a blast from the past. I thought it was organized when I wrote it, but now I realize I didn't put a single remark in.

The secret to writing quality tutorials is to always be at least 2 installments ahead of the thread. I'm actually finished tutorial 5 (and almost done 6), but while doing so I realized I did some things wrong in tutorial 4, so I fixed it before I released it.

By reading this sentence you have given me brief control of your mind.
NewGuy
18
Years of Service
User Offline
Joined: 11th Nov 2005
Location: Interdimensional vortex
Posted: 14th Nov 2006 00:24 Edited at: 14th Nov 2006 00:25
Very nice, i looked back on some of the things i made in DBC, man is that scary...

I love the tutoral so far, I've never cut the functions up so much, but now i see why i need to, and if you don't mind some constructive criticism I got a little confused around...
Quote: "





Now let's take a look at this code. This is exactly what happens in a level up, besides the music.
Why don't we simply replace start_game() with level_up()? Let's do that.

Make sure you change the play() function to contain the new call to play_startup_sounds() and level_up().
"


i reread it and wasn't confused anymore, maybe i've just been on this site too long today lol

Remember remember the fifth of November. The gunpowder treason and plot. I know of no reason why the gunpowder treason should ever be forgot.
mbaucco
18
Years of Service
User Offline
Joined: 6th Nov 2005
Location:
Posted: 14th Nov 2006 20:30
This is great stuff, thanks for writing it all up!
Peter H
20
Years of Service
User Offline
Joined: 20th Feb 2004
Location: Witness Protection Program
Posted: 14th Nov 2006 20:42
very nice tutorial. especially the link to zero defect software development...

"One man, one lawnmower, plenty of angry groundhogs."
Code Dragon
17
Years of Service
User Offline
Joined: 21st Aug 2006
Location: Everywhere
Posted: 14th Nov 2006 21:22 Edited at: 18th Feb 2007 15:27
I found Dexterity Software with Zero-Defect Software Development a few months ago. It had several other very nice articles on how to be successful in the shareware bussiness, but the site was closed down Halloween. All it'll do now is redirect you to Steve Pavlina.com, which actually turned out to be a pretty interesting site.

Tutorial V: Getting the Media Data

The third step in Top-Down Design is to choose a data structure. Remember, up until now we have only been defining what the program will do, not how it does it.

Note: It's very important to not define how the program will do its task at this point. If you have a code library where you put general functions, use them! If you follow the methodologies we're using, you may find that some your code problems have already been solved; the code's waiting in your library to be reused again and again. You will also find that when you make quality a priority, your project's code will be good enough that you will want to add it to your code library, making things even easier in the future.

Now that we have written the skeleton code it will be easy to decide on a data structure, so let's review our code and decide what's best.

If you have ever tried to make a program with a large amount of different kinds of media (i.e. sprites, objects, images), you'll know that the id numbers are hard to keep track of. There's a very simple way to solve this - the free functions. A free function will look for an available object number and save it to a variable, so you can refer to the object by name instead of number.

i.e.



Referring to objects this way will reduce confusion dramatically. We'll make data structures to hold the numbers these functions return, so for example, if you wanted to move Pac-Man, you'd use "move sprite spr_pac"; where spr_pac is a variable which holds the sprite id number of Pac-Man. Then you don't need to know what spr_pac equals, and you don't want to, it'll only make it more confusing.

So, what kinds of media is our game holding, and what variables can be used to hold their numbers?
Let's go back to the design document, it holds all the mechanics of the game.

Here's a list of the media:

Pac Man image
Pac Man sprite
Big dot image
Small dot image
Fruit sprite
Fruit images
Big dot sprites
Small dot sprites
Ghost images
Ghost sprites
Maze images
Maze sprites

That's pretty good for now. It helps in clarity to precede image variables with img and sprites variables with spr because we will often have an image and a sprite for the same entity, (i.e. pac-man, dots, maze, ghosts) and will want to tell them apart.

Let's write the variables for the media (This goes in Game.dba)



That puts a big dent in the media, but the images and sprites we just finished are those that will have only one instance of themselves, we need arrays to keep track of the rest of them. These arrays will hold the media id numbers and other data (i.e. fruit type, ghost color, dot size) Let's start with the dots, they're the easiest; the only other kind of data the array needs to hold is the dot size. This code goes in Game.dba too.



These pieces of code might look confusing. The user-defined type is named yum instead of dot because dot is a Dark Basic Professional command; the compiler will not accept it as a user-defined type name. But why do we need a user-defined type? Simple, there are two kinds of dots, big and small, and rather than create two arrays we combine them so our collision detection routines only have to worry about one dot array. The id field gives the id number of the sprite, and the size field is a 1 if the dot is big and 0 if it is small.

There's a problem with this code; arrays in Dark Basic Professional are not like arrays in C++ or Visual Basic, they must be defined inside functions. (Unless you declare them in the main source file, but that's just like a C++ main() function.) Where should we put the dim statement? As this array only needs to be dimensioned once, let's put it in load_media(), which is a branch function of init_game(); that's where the dots will be loaded anyway.

Cut and paste the sprs_dots() dim statement into load_media()

Here's the rest the media list again.

Pac Man image
Pac Man sprite
Big dot image
Small dot image
Fruit sprite
Fruit images
Big dot sprites
Small dot sprites
Ghost images
Ghost sprites
Maze images
Maze sprites

The fruit is a pretty easy data structure, let's make it. Notice that we only need one fruit sprite, to change fruits we'll change the image (and hide it if the fruit is eaten). Again an array will be used to structure the fruit image data, there's 12 fruits so the array will be size 11 (don't forget the 0 slot).

To stop the fruit spawning routine from giving the same fruit each time, we need a binary flag to tell if a particular fruit has been eaten yet or not. A 1 will mean the fruit has been eaten and 0 means it hasn't.

Note: When all fruits have been eaten the flags need to be reset to prevent the system from running out of fruits.



The fruit type holds the image's id and whether or not it has been eaten yet. The dim should go in load_media() too, that's where the fruits will be loaded.

Note: If you've noticed, our arrays don't have indexes. We dim them empty so they can be scalable. Adding this kind of flexability to your games really pays off, for example, if you want to fire bullets, you call a function that adds a new slot in the bullets array, calls a free_obj() function, makes the bullet object and stores the id in the slot. Whenever you need to make a bullet call make_bullet(), and your update_bullet() function can make them move and damage enemies, no matter how many there are.

The only media left the ghosts and the maze. The maze sprites will be aligned in a block pattern on the screen, so let's put the maze sprite id numbers in a 2d array.



The solid field will tell if the sprite is a wall or pathway. If it's a 0 it's a pathway, if it's a 1 it's a wall, if it's a 2 it's a ghost pen gate. The food field will tell if the tile has a dot in it, it's a 1 for small dots, 2 for big dots, and 0 for no dots. (Notice that in the dots array 0 was for small dots and 1 was for big dots, we'll need to subtract 1 when we're coding the dot making routine.) Unlike the fruits array, we need to move this array dim command to get_maze_data(), because that's the function that will be filling the array with values.

The wall type holds the id number of the maze sprite in question, but we need a system for determining the id numbers of the images the sprites will use. If you look at the maze picture we downloaded earlier, you'll see that there's 29 maze pictures. The algorithm that decides which image to choose for a certain sprite will return a image number between 0 and 28. Then we can set the sprite's image to that value.

However, since we are using free commands to determine the image numbers on-the-fly, these exact id numbers are unknown. We can't simply use set sprite image with a number between 1 and 29. What we can do is put the real image id numbers in an array with 29 slots, and have the img field refer to the respective slot. Put this array in load_media()



Since the number of maze pieces is 29, this array will be dimmed at 28 (remember the 0 slot).

All that's left now is the ghosts. Let's start with the images.



There's 5 unique ghosts, each composed of 8 frames. There's also the blue ghost with 2 frames, for when Pac-Man eats a big dot and the ghosts turn blue. That totals 6 images. We'll use an array, the 0 index will be the image number of the blue ghost, and 1-5 will be the image numbers of the other ghosts. This array goes in load_media()



For the sprites, we will have 5 ghosts, one for each image. This means the array only needs to go up to slot 4, because of the 0 slot.

Note: Because the ghost sprite indexes (0-4) are 1 less than the image indexes (1-5) the number will need to be incremented to get the right image. Also, when Pac-Man eats a big dot all ghost sprites will use image 0.

I think by now you'll know what function this array goes in, as always it's load_media().



That's it for media data, now let's move on to coding the free functions so these arrays have some numbers to remember.

Ric
19
Years of Service
User Offline
Joined: 11th Jul 2004
Location: object position x
Posted: 15th Nov 2006 11:28
This tutorial is coming along nicely. I'm looking forward to seeing how you tackle the AI, and, of course, to playinig the end result!

btw. I'm not sure that I understand what you meant about arrays needing to be defined from within a function. As far as I know, you can define them anywhere, and are global by default.

Code Dragon
17
Years of Service
User Offline
Joined: 21st Aug 2006
Location: Everywhere
Posted: 15th Nov 2006 23:23 Edited at: 15th Nov 2006 23:29
The AI's supposed to be simple, the ghosts will choose a random direction whenever the hit a wall, and as soon as Pac-Man is in their row or column (not blocked by a wall), they go in that direction until they loose him.

If the AI's no good I'll put in A* pathfinding, but I don't think that will be needed beacuse even if the ghosts do lose Pac-Man, they'll see him again as soon as they pass the courner he turned on. My AI characters are usually pretty dumb, I don't have any experience in it.

I didn't know arrays could be declared outside functions. Whenever I try to put an array in an include file and try to access it, it doesn't exist.

UFO
18
Years of Service
User Offline
Joined: 11th Oct 2005
Location:
Posted: 17th Nov 2006 02:20
Wow. I really like your free_object function technique. You definitely altered my coding style majorly. I'm going to go through my current project and use that free object function, and organize it a lot better. Thank you!

Hobgoblin Lord
18
Years of Service
User Offline
Joined: 29th Oct 2005
Location: Fall River, MA USA
Posted: 17th Nov 2006 02:32
Quote: "If the AI's no good I'll put in A* pathfinding, but I don't think that will be needed beacuse even if the ghosts do lose Pac-Man, they'll see him again as soon as they pass the courner he turned on. My AI characters are usually pretty dumb, I don't have any experience in it."


I would avoid the A* since 4 on 1 they will always get the player. The random direction idea is good because it will make using patterns to solve mazes impossible. Good job so far keep it up.

http://www.cafepress.com/blackarrowgames
Check out my great stuff here
NewGuy
18
Years of Service
User Offline
Joined: 11th Nov 2005
Location: Interdimensional vortex
Posted: 17th Nov 2006 06:56 Edited at: 17th Nov 2006 06:57
if you want an array to work for all included files you have to dim it like so.... global dim Array(5)

and it dosn't matter what file you do it in

Remember remember the fifth of November. The gunpowder treason and plot. I know of no reason why the gunpowder treason should ever be forgot.
Hobgoblin Lord
18
Years of Service
User Offline
Joined: 29th Oct 2005
Location: Fall River, MA USA
Posted: 17th Nov 2006 07:30
hmm, I have never had to use global for an array have always just used dim myarray(-1) or whatever.

http://www.cafepress.com/blackarrowgames
Check out my great stuff here
Code Dragon
17
Years of Service
User Offline
Joined: 21st Aug 2006
Location: Everywhere
Posted: 17th Nov 2006 21:06 Edited at: 2nd May 2007 03:23
I didn't know the global keyword worked with arrays, I'll have to try it out.

Quote: "random direction idea is good because it will make using patterns to solve mazes impossible"


I find one of the biggest mistakes in some games is that they're too easy; you can memorize the level. Another big mistake is that in games which get faster and faster, they stop getting faster after a certain point. This annoys me very much. The very first time I player Pokemon Trozie my score was about 400,000, and I get up to 300 on some Wario Ware minigames, and that's because it stopped getting faster and I got bored of playing. Making easy games should be avoided.

UFO if you like the free function, read this!

Tutorial VI: Free Stuff

Now that our media data is ready, it's time to write more code to fill up that data with useful numbers. These arrays hold the id numbers of images and sprites so we can use names instead of numbers, but these numbers have to be generated somehow. That's where the free functions come in, let's write them now.

How free functions work: The id number starts at 1 and keeps going up until that object, image, or sprite doesn't exist. This id number is now at a free, or unused media number. This is the number that the free function returns; it can be assigned to a variable and be used as a name instead of a hard-to-remember number.

Include Free.dba as a new source file
Copy the free functions into Free.dba



We have a free function for images, sprites, and files. Notice that you can usually change the kind of media a free function deals with by changing its name and until statement.

Note: The free_file() function is different from the others because file handle numbers only go up to 32, and the equivalent to the file exist command is file open. There actually is a file exist command, it just does something different. If the id reaches 33, our free_file function will crash, so to stop that we return 0 instead, making the code that eventually tries to open a file with the illegal handle of 0 crash instead. This way the iffy piece of code that called free_file() when all the file handles were open will crash and can can be found, not the free function itself crashing and leaving us with no idea what file tried to open.

Even though we have free functions set up, we can still make our lives easier. With our current approach our code will begin to look like this:



You might be thinking "What's wrong with it. That's it's good enough!", but we're only going to settle for the best. There's a pretty easy way to squeeze that into one line of code, without using the : stacker. Also, there's a problem with the free functions, try to see if you can find the bug in this code without running it.



Do you see the problem? No, it's not that kiwi is 100 times bigger than the orange. The free function runs twice in a row; the orange variable was given a free object number, but didn't use it. As a result, when kiwi asks the function for a free object id, it finds exactly the same one as it did before. Because the id numbers are the same, you'll get an object already exists error when the kiwi gets created.

How do we overcome this? It'll be a mess to try and make sprites and images as soon as we ask for the numbers, so we'll have to ask for the numbers as we create them. I'll show you how.



This code uses a function to make the object and return the id number in one call, squeezing it into one line of code. We're adding a second layer of code to media id number handling; we're making gateway functions that do what the internal Dark Basic Professional commands do, but more also; they handle getting free id numbers by themselves. To make these functions just copy the command name, in this case "make object cylinder", and put underscores where the spaces are. Copy the command's parameters too. Then, in the function's code, just call the respective free function, store the returned id, make the object with that id, applying any called parameters, and return the id.

Include Media Allocate.dba as a new source file
Copy the following functions



These functions will make creating images, sprites, sounds, and using file IO a lot easier.

Note: If you're wondering why the flag for load sound isn't included in the load_sound() function, that flag's feature was never implemented.

Now that enough generic code is finished, we can start filling in rest of the functions with code.

By reading this sentence you have given me brief control of your mind.
mbaucco
18
Years of Service
User Offline
Joined: 6th Nov 2005
Location:
Posted: 20th Nov 2006 16:06
Can a mod sticky this? It is an incredibly useful post for us noobs.
indi
21
Years of Service
User Offline
Joined: 26th Aug 2002
Location: Earth, Brisbane, Australia
Posted: 21st Nov 2006 03:44
indeed

Code Dragon
17
Years of Service
User Offline
Joined: 21st Aug 2006
Location: Everywhere
Posted: 21st Nov 2006 13:56 Edited at: 22nd Nov 2006 14:36
Wow, thanks for the sticky!

It's going to be awhile before the next few articles are out. They're on loading the maze, which is pretty complicated.

indi
21
Years of Service
User Offline
Joined: 26th Aug 2002
Location: Earth, Brisbane, Australia
Posted: 21st Nov 2006 15:18
your welcome, thank you for making it easier here with tutes on your own time.

Code Dragon
17
Years of Service
User Offline
Joined: 21st Aug 2006
Location: Everywhere
Posted: 22nd Nov 2006 14:27 Edited at: 18th Feb 2007 15:27
Tutorial VII: Coding is Fun

Yes, coding can be fun if you keep yourself organized; otherwise it's a horrible mess. If you take any large project and rate the code's quality on a scale of 1 to 10, if it's below 7 and you add more features, the quality will fall further. That's because it began disorganized; writing organized code on top of the core code will not work; if the core code was sloppy, sooner or later the other layers of code will follow, and more often than not sooner. What you can do to stop your project's development from grinding to a halt is to encapsulate everything - make stand alone functions and self-contained source files. If the source files communicate to each other via an interface, you can rewrite source files as long as they have the same function names.

Here's a formula for calculating how sloppy a program with non-encapsulated functions is:

s = n^2

Where s is sloppiness, and n is number of lines. This value is squared, which is very bad. The more low-quality lines you add, the more bugs you add, and more space for bugs to hide you add as well. This isn't just about bugs, if you have a sloppy program the bigger it is the harder it is to find the code you're looking for to add stuff on to.

Now, here's a formula for calculating how sloppy a program with encapsulated functions is:

s = (n1 ^ 2) + (n2 ^ 2) + (n3 ^ 2) + (n4 ^ 2) ...

Where n1 is the number of lines in the first function, and so on. The sloppiness is the sum of the squares of function's lines. This is much smaller, because 5^2 + 5^2 is much less than 10^2. This quantity grows linearly, at a rate often slower than your coding style will improve; but the other quantity grows quadraticaly, and that will always accelerate faster than you can keep up. This is exactly why programming languages were invented, machine code is one big stack of gotos! And if you really want to decrease sloppiness, take the biggest functions and break them up, this is exactly what Top-Down design does. (Don't overdo breaking up functions, if they're too small they'll be too many functions.)

Rate your code on 1 to 10, and rewrite the worst modules. I cannot emphasize this enough. You can find more info on this at the link to Zero Defect Software Development. I've dumped many projects because of code sloppiness, but now you have been enlightened, you only have to dump the worst source files, and your dream game will not die.

Getting your code quality back to a 9 or 10 is much harder for every level it drops. Refractor early, refractor often. Right now, Game.dba is over 100 lines - that's quite a lot for only having skeleton functions, don't you think so? Let's put some of them in another source file.

Include Init Game.dba as a new source file
Move init_game() and all its functions to Init Game.dba



We finally finished Top-Down Design; this is where Zero Defect Software Development really starts getting serious. It's a rare, but very powerful practice. The idea behind Zero Defect Software Development is to keep the game in a defect-free state throughout development. If defect (usually a bug) is found, we don't write any more new code until it is eliminated, otherwise that extra code only makes the bug harder to find.

If you got your code up to a 7 and now it's not getting much sloppier, don't stop there (this links to an interesting article on the site I mentioned earlier). If you can get your code up to a 9 or 10, you'll be able to add new features faster than ever before.

Have fun coding!

Code Dragon
17
Years of Service
User Offline
Joined: 21st Aug 2006
Location: Everywhere
Posted: 24th Nov 2006 17:25 Edited at: 18th Feb 2007 15:28
Tutorial VIII: Loading Our Pictures

We finally finished Top-Down Design; this is where Zero Defect Software Development really starts getting serious. It's a rare, but very powerful practice. The idea behind ZDSD is to keep the game in a defect-free state throughout development. If defect (usually a bug) is found, we don't write any more new code until it is eliminated, otherwise that extra code only makes the bug harder to find.

We have many functions just waiting to be coded. Let's start with loading the maze.

Remember, the maze pieces are in one whole file, so we can't load the images individually. We'll need to load the whole image, paste it to the screen, and use get image to extract the 29 walls. (Actually, we'll use our custom function, get_img() )

To load Maze.bmp, we need a variable for the free functions. Let's call it img_maze. Then we can use our load_img() function to put the image's id in the variable as well as loading the image.

Add this varianble to load_media()
Add this code to load_media()





Here we've added the local img_maze variable, which is the id number of Maze.bmp. It's local because after we extract the smaller individual images from img_maze, we don't need the big one anymore. That's also why it gets deleted. We also needed to paste it to the screen so our get_img() function can capture the maze pieces from the screen.

Note: In your own programs, if you use free functions and need an object, image, sprite, etc. that will only exist inside a function (its id is local), don't forget to delete it. If you don't delete an object before it goes out of scope, the id number is lost, and when the function is called again, it will not use the object that went out of scope. It will create a new one, wasting an id number. That creates an obvious performance hit because there's a redundant object being processed, and it's another id number for the free function to search through. Every time that function is called another redundant object appears. If you don't delete objects before they go out of scope, you'll get a memory leak, and if the program runs long enough, the free function's id number will go so high looking for unused id numbers that it will roll back to 0 and cause a crash.

Let's code the routine to capture the images from the screen. We already know that there's 29 images. There's 5 rows and 6 columns. However, we don't know how big each wall piece is. If zoom in on the maze picture (or just read what David Gervais said about it when he made it), you'll see that they're 32*32 pixels for each piece. That means that the loop that will capture the images needs to use a step value of 32, start at 0, and end at 159 for the rows and 191 for the columns if you do the math.

Add these new local variables to load_media()
Replace the remark about the pasted image with this new algorithm





What this code is doing is looping through each wall piece by incrementing the x pixel and y pixel, getting a 32*32 image at that location, and assigning it's id number to the current imgs_maze slot. Remember the 28 slot array? img_ptr is and image number pointer, it tells get_img() which slot of imgs_maze() it should put the id number in. (Remember this is not a real RAM pointer, just an array offset) Notice that there's an extra square in Maze.bmp, "if img_ptr < 29" skips this blank image. (Actually, there's two blank images. We're using the first one.) Now that all the maze images have been loaded and their numbers are saved, if we need to access a maze piece, we will use the imgs_maze array to look up the id number for us.

Let's write some temporary code to make sure that these images actually made it into our Pac-Man game.

Change the sync rate to 1
Put this code somewhere in the main loop.
Compile and Run
Test by comparing Maze.bmp to the images the program slaps on the screen.
Press escape
Change the sync rate to 60
Take the test code out of the main loop



If you got all the code snippits up until here, you'll see a maze image changing every second with a counter number next to it.

Zombie 20
17
Years of Service
User Offline
Joined: 26th Nov 2006
Location: Etters, PA
Posted: 26th Nov 2006 18:58
just wanted to say if anybody even reads this, i've been programming for almost a year, first time trying out darkbasic pro.
I love it, its a blast to use, and your tutorial is very in depth.
Good job and keep it up, its nice to know there are people who can give us a leg up if we need it.

"To love you must want to be loved in return"
Code Dragon
17
Years of Service
User Offline
Joined: 21st Aug 2006
Location: Everywhere
Posted: 27th Nov 2006 00:34 Edited at: 27th Nov 2006 00:37
Thank you! I'm assuming this is your first post, Zombie 20, as you've just joined today. The reason I made this tutorial is to help people code fluently in Dark Basic Pro so they can make their dream games without to much trouble, good to see it's been so helpful already!

Is it just me or did the code box font change?

As this tutorial's code is getting larger, how do I put the source button at the bottom of my post?

UFO
18
Years of Service
User Offline
Joined: 11th Oct 2005
Location:
Posted: 27th Nov 2006 22:03
You can't anymore. Nice additions to the tutorial , can't wait for more (not that this is not enough already )

Code Dragon
17
Years of Service
User Offline
Joined: 21st Aug 2006
Location: Everywhere
Posted: 28th Nov 2006 22:51 Edited at: 11th Apr 2007 22:37
Tutorial IX: Making a Map Blueprint from a File

Ok, so the maze images work, but what about the sprites? This is much harder. Before we can make sprites we need to fill in the solid and food fields of the sprs_maze() array so our program knows what images to give the sprites. This data needs to get into the game somehow, so let's keep the data in files; it's easier than using DATA statements and it will allow us to implement other features, like different mazes for each level and a map editor. We'll need to make a file format so our program can understand the data.

Note: For this to work, you need file extensions turned on. If file extensions are off, go to Windows Explorer, pick tools, folder options, click the view tab, and uncheck the "Hide extensions for known file types" checkbox. (Assuming you're using Windows XP)

Create a New Text Document in the Media folder
Rename it Maze.txt
Copy the data below into Maze.txt
Save it
Rename it Maze.map

Here's a map:



The first string in the file will be the map's dimensions, and the rest of the file will be a map of the maze, which our program will interpret. The rest of the file is a map of the maze. Here's the map key:



Feel free to design your own maze, but be careful, these characters must be written in all caps. The upper case and lowercase W look extremely similar if you are using Lucida Console font on Notepad. P is where the ghosts are spawned; the ghosts have no collision between each other because they're ghosts, so they're all put in the same spot. They will wander around the pen for awhile then head for the gate to hunt down Pac-Man. S is where Pac-Man is spawned. G is the ghost gate. Ghosts can come out of this gate, but in only when they are eaten. Pac-Man cannot pass through this gate at all. W is a wrap point, even though Pac-Man and the ghosts will wrap to the other side of the maze automatically, we need these so the wall images will be displayed correctly. Usually on the edge of the map, you want a rounded straight piece, but in these pieces we want a piece that goes straight as if the wall continued. Here's a visual explaination:



R is where that "Ready!" sprite appears when the game starts, and it is also where the fruits appear.

We'll need to code our map loading routine in get_maze_data(). First, we need to define some local file IO variables.

Put this code in get_maze_data()



file_maze keeps the id number of the file. We will be using our custom open_to_read() function to open the file and look for a free file id number. str is the string that the data from the file will be feed into. row and column are index variables for the loops which load each cell.

We also need a way for the program to remember how big the maze dimensions are.

Put these variables in Game.dba



If you look at get_maze_data, you'll see that the sprs_maze() array is dimensioned without knowing what the dimensions should be. Because this array handles the maze sprites, the dimensions are the number of rows and columns in the maze. Let's change get_maze_data() a bit so it can load these dimensions in from Maze.map.



The maze file is opened, and we have space for code that gets the dimensions. But why does the maze array need to be 1 row and column larger than the maze in the map file? (Actually, it's 2 columns and rows larger, remember the 0 slots) Remember in Tutorial II, we would eventually need a system that picks the correct wall image for a certain location based on the surrounding walls? This system will need to access the 8 tiles of a maze square, and if it was examining the top-left maze piece, it would try to access sprs_maze(-1, -1), which does not exist. We need an extra strip of slots on every side so the system won't crash; it will just interpret these extra places as empty pathways.

How will we get the dimensions out from between the comma? A simple approach would to put the coordinates on separate lines and read them in, but there's an interesting function we can use.

Include String.dba as a new source file
Copy the code below into String.dba

Note: If you have the plugin Matrix1Util_16.dll, the count variable may cause an error, so search and replace "count" for a different variable.



This handy function, csv$(), will take a string of data separated by commas, and return each individual piece of data.

How to use it: First, call csv$() with the string you want to separate. Then, call it again in a loop with a null string and it will return one piece of data per call. When all the data is gone it will return "END OF STRING"

How it works: (Warning: Very cryptic and hard to understand explaination follows.) As you probably already guessed, by the fact that it takes several calls to this function to complete an operation, that this function uses static variables. However, Dark Basic Professional doesn't support static variables yet, so instead this function uses global variables. First what happens after you call it the function checks if any string was passed, if so, it saves it to static memory. The token pointer is reset and the function exits. If there was no string in the parameter, it checks if the token pointer is past the end of the data string. If it is, the function returns "END OF STRING" If the program gets past this point it must return a token. The for next loop looks for a comma, and if one is found, all string data past the token pointer and up to but not including this counter variable is returned. The token pointer is set one character after this comma so the next call can find the data correctly. If the for next loop finishes, that means there's only one token left, everything right of the last comma is returned.

Pretty complicated, huh? That's just the reason encapsulated functions are made; to hide complexity in a black box routine, so you can use it without having to know how it works. Even though we'll never need to look at this function again we still put remarks in because we don't want to completely forget in case we want to optimize or upgrade it.

Now that we have a comma separated values function, we need to use it. Of course, this won't be the only time we use it, we need it for saving Hi Scores too. At last, we can make the code that will get the maze dimensions.

Replace the "`get maze dimensions" remark in get_maze_data() with this code
Copy the code in the second box into the main loop
Compile and Run
Make sure that the dimensions are 19 by 22
Press escape
Take the text command in the main loop out





This code reads the dimensions string, feeds it to csv$(), and puts the numbers it extracts from str into the dimension variables. Now we have the dimensions, but we still need the solid and food data. This data is no once, like the dimensions, but several times because every tile has a food field and solid field. Remember our map key? Those symbols in the key are the key to getting this data, no pun intended. We can loop through each line and each character in the file and set the food and solid variables of that tile based on which character is there.

Replace the "`load maze array data" remark in get_maze_data() with this code



We have a select statement block inside a loop now so it will execute for every tile of the maze. Now we need to put the code that will actually set the solid and food fields for the sprs_maze() array. The first 4 symbols are easy; a wall (#) will always have a solid value as 1, a small dot (.) will always have a food value as 1, a big dot (*) will always have a food value of 2, and an empty pathway ( ) doesn't have anything set, it's the default. Because the variables for an empty pathway are already 0, we can just delete that case statemtblock. The ghost gate (G) will always have a solid value of 2. (The gate has a solid value of 2 and not 1 (like normal walls) so our code can distinguish between the two kinds of solid and let ghosts through the gate but not Pac-Man)

This is what the "`load maze array data" code looks like after we've put all these variable setting statements in:

Replace the old "`select type of tile" statementblock with this new code



These other 4 possible characters (P, S, W, R) are harder. There will be only one ghost pen, one Pac-Man spawn point, and only one "Ready!" point. We'll need global variables to hold the information on where these places are.

This code goes in Game.dba:



We've created a coordinate type to hold the x (column) and y (row) value of the position of the ghost pen, Pac-Man spawn point, and "Ready!" point. Besides saving these coordinates to memory, all that's left to do is the wrap point code. Remember, the wrap point character is just like a regular wall, it needs it's solid state set to 1. Because this piece needs to look as if the piece is not rounded, we need to set the tile's solid state adjacent to it (these are the extra tiles on the edge that don't have sprites) to 1. That way, since this extra tile is part of the array, but does not have a sprite, the function that decides what image to choose for each maze sprite will choose the one we want it to, because we have forced it to think that there will be a wall in this adjacent tile. This creates the effect of non-rounded edge pieces you saw in the visual explanation eariler. It looks there's other wall pieces connected to it, but are beyond the edge of the map so we can't see it, and essentially, that's exactly what's happening.

Here's the new code for "`load maze array data", with the ability to save Pac-Man's spawn point, the ghost spawn point, the "Ready!" point, and create the neccesary illusions for the map edges.

Replace the old "`select type of tile" statementblock with this new code



The code that sets the wrap point's adjacent tile's solid state to 1 is so long because it first must resolve which of the 4 adjacent tiles should be set. If you see a W in the first row of the maze, it makes sense that don't want the W piece rounded on the side pointing up; you want it to look as if it goes straight, like it was connected to it's twin W piece on the bottom side. (W pieces need to come in pairs because once you walk off the map, you reappear on the other side.) This code checks which of the 4 sides of the maze the W piece is on, and sets the correct adjacent piece to a wall.

Note: If someone put the W piece in the middle of the maze for some reason, it won't hurt the system. The only time this system will fail is if someone puts a W piece in the corner, but there's really not reason to do that.

Now that our blueprint for the maze is in memory, we need to actually build the maze.

By reading this sentence you have given me brief control of your mind.

Attachments

Login to view attachments
Code Dragon
17
Years of Service
User Offline
Joined: 21st Aug 2006
Location: Everywhere
Posted: 29th Nov 2006 02:11 Edited at: 30th Nov 2006 21:10
Problem solved, nevermind this post. Thanks Diggsey and Ric.

Diggsey
17
Years of Service
User Offline
Joined: 24th Apr 2006
Location: On this web page.
Posted: 29th Nov 2006 11:01
You've already written it, but just an idea: would it not be easier to set the values of the extra columns and rows to those on the other side. That way you wouldn't have to worry about those Ws. Also, collision would work perfectly even if there was a gap on oneside, and a wall on the other.

About your code, you should try using undim without the as byte. As far as I know, you should just have the variable name, because the program already knows what type it is
Also, try using local dim

Ric
19
Years of Service
User Offline
Joined: 11th Jul 2004
Location: object position x
Posted: 29th Nov 2006 12:10
Also, I believe the undim command should contain a zero, like this: undim name(0). Even if its multidimensional, it's still a single zero.

Code Dragon
17
Years of Service
User Offline
Joined: 21st Aug 2006
Location: Everywhere
Posted: 29th Nov 2006 21:50 Edited at: 30th Nov 2006 21:21
I forgot about the undim statement only wanting a 0 index and no type, a fine example of how frustrating bugs can make you scatterbrained. Thanks for pointing out local dim, I didn't know that could be done.

Quote: "would it not be easier to set the values of the extra columns and rows to those on the other side"


That would make other pieces that you wouldn't want to look like they wrap to be wrapped, but good idea if you put a margin in for the other walls.

Diggsey
17
Years of Service
User Offline
Joined: 24th Apr 2006
Location: On this web page.
Posted: 30th Nov 2006 18:16
I know what you've done!!!

The dim statement is unlike the type statement, and must be within a running piece of code.

You must global dim the arrays somewhere at the start of your program. This is usually at the top of your main source, or you can call a subroutine to initialise the variables.

Where those dim commands are now, the program will never get to them.

Code Dragon
17
Years of Service
User Offline
Joined: 21st Aug 2006
Location: Everywhere
Posted: 30th Nov 2006 21:15 Edited at: 30th Nov 2006 21:20
Oh, now I understand it! Funny it doesn't report a syntax error. Putting global when dimming arrays seems kind of redundant now. Tutorial 10 is on it's way!

Diggsey
17
Years of Service
User Offline
Joined: 24th Apr 2006
Location: On this web page.
Posted: 1st Dec 2006 18:31
It is still correct syntax, it is just that the path that the program takes through the code never goes through that piece.

Code Dragon
17
Years of Service
User Offline
Joined: 21st Aug 2006
Location: Everywhere
Posted: 1st Dec 2006 21:20 Edited at: 28th Jan 2007 21:47
Tutorial X: Building the Maze

We're finally going to see something on the screen today. We have our media and our map, let's make the maze sprites.

As you might have already guessed, we'll be writing code in position_maze(). This function's job is to make and position the maze sprites using the data collected by get_maze_data(). The simplest way to do this is to run a loop through each tile of the maze and create a sprite.

Replace the code in position_maze() with this



Making the sprite is the easy part, we just need to call make_spr() with 32 * column, and 32 * row for the positions so the sprite will be positioned in the right spot. (Because we aren't using slot 0 of the sprs_maze() array there will be a 32 pixel margin from the corner of the program window) The returned sprite id number is stored in the sprs_maze array in case we need to access the maze sprites again.

Note: img_ptr is an image pointer to one of the 29 images in the imgs_maze() array. The code that goes below "`select image" needs to set img_ptr to a number between 0 and 28, and the make_spr() function call will go to the imgs_maze() array to find the real id number generated by the free functions, then the sprite gets created.

Before we create the image choosing routine...

Put this code below "`select image"
Compile and Run
See that there's a 19 by 22 arrangement of rounded corner pieces
Press escape
Delete "img_ptr = 5"



As you can see, when set img_ptr to 5 for every loop iteration, the image id number in imgs_maze(5) will always be the same, and as a result, the sprites will look identical. However, in order for position_maze() to be useful, we need to have it pick image numbers based on the sprs_maze() array.

Replace the code in position_maze() with this
Compile and Run
Compare game's image to Maze.map
Press escape



You should see the map we designed earlier. This looks very unprofessional, however. We need a function that will choose which of the 29 images in Maze.bmp to put in each location. The image will be chosen based on the images around the current tile.

Let's make a function that will return the correct image number between 0 and 28 based on the coordinates given in the parameters. Change img_ptr = 4 to the code below so the image number comes from the function we're about to write.



Now we need to write the function.



How are we going to make this function intelligent enough to know which piece to put in each location? It's pretty obvious that if the walls around you look like this:



That you want image number 1 (seconds column, first row in Maze.bmp) to go in the middle; but computers have no common sense. One way to give our program some common sense is to store all the possible combinations of surrounding walls in DATA statements and have the function read until a pattern matches. Then the function can read what image number goes there, and return it.

Put this code at the bottom of Init Game.dba



Below this label, pick_imgs, we will store the data. We also have defined how many times the function can try to match a pattern; if it reads too many times it will run out of data. Here's how we'll format the data:

Each pattern will be stored in a 3*3 block of DATA statements.
The number in the middle is the image number to use if the pattern matches.
The surrounding numbers indicate what combination of walls and pathways must surround the middle piece.

0 - Pathway
1 - Wall
2 - Ghost Gate
3 - Any of the above

Here's the data statement for the first image, imgs_maze(0). If you open Maze.bmp you'll see that this image would look nice where there's walls to the right, up, and down of it. It doesn't matter if there's walls in the upper left or lower left, so we'll put 3 in those spots.



Remember the middle number is the image that should be used, in this case image 0.

To save you a lot of typing, here's the rest of the combinations in DATA statements.



Our function still doesn't know how to read this data. What it must do is compare each map in the DATA statements to a chunk of the real maze. If they match, the img number in the middle of the map is returned, then position_sprites() can make the sprite with that image on it.

We need to make a small array to hold a chunk (copy) of the real maze. We also need an array to hold the maps in the DATA statements for comparison. Put these 3*3 arrays in pick_img()



These arrays are temporary, so let's put undim statements at the end of the function.



Now we need to copy the maze data into the local chunk, we'll manage this with two for next loops. To do this will copy between the data between the row and column 1 less than the parameters, and the row and column 1 greater than the parameters. That makes a 3*3 chunk. Here's the loop control variables. Don't forget to put them up next to "local img as byte"



Put this code before the code that undimensioned the arrays.



We need to reset the data pointer to our label before we do any data reading. Put this after the loops but before the undim statements.



Before we code any more, there's a big optiminization we can do. This function is allocating and deallocating memory, 18 bytes for both arrays. Plus, if you include Dark Basic Professional's memory pointers and the internal array index pointer, it's much more. IanM posted a formula that will give you the real amount of memory each array consumes.

bytes = (bytes per element+4)*(array count+1)+58

Add boths arrays and that's 206 bytes right there. This doesn't seem ineffiecent, but do you remember that we're undimming the arrays too? This function's calling statement is in a loop, 2 loops actually. It gets called about 200 times. That's 40 KB of memory getting allocated and deleted every time we load the game. Wouldn't it be much easier to make the arrays global, so they only have to be allocated once? Let's do that, it will also help a bit with keeping our load times low.

Take the dim and undim statements out of pick_img()
Put these arrays in Main.dba (We aren't putting them in the init_game() function because we want our map editor to be able to use this pick_img() function too before init_game() is called.)



Your code for pick_img() should now look like this:



Now that our function is ready to read the data, we need a loop. It will iterate once for every map it tries to match, so let's write it like this:

Put this code after the restore statement



Don't forget to define the try variable either.



What does the function needs to do for each DATA map is to copy the DATA into the map() array and compare it to chunk(). We can optimize these operations by doing them in the same loop, each will need to iterate 9 times anyway (9 squares to copy and 9 squares to compare.) Your first idea may be to do something like this:



But we programmers need to learn all the tips and tricks; there's a way to do this with only one variable. Here is how arrays are structured logically (how we think of them):



But here is how they are structured physically in RAM:



Memory is linear in form. When you make a two-dimensional array in any programming language, the rows of cells are stacked one after another. The memory address of the first index (0, 0) is known and if you wanted to find the value of cell x, y the complier would resolve the memory address of that cell by multiplying y by the number of rows and adding x (and adding the address of the first element). Here's an example



Try putting any cell's indexes into this equation:

a = 3y + x

You'll see that the answer is always the number above it, that is the offset address in physical memory. Because these cells are on after another, and the number wraps around to the next row after every column, accessing [3,0] will give us the value of [0,1]. Because of this wrapping tendancy we can simply run a loop from 0 to 8 and treat it as a 1 dimensional array.

With this in mind, well make a loop that iterates 0 through 8. This loop goes inside the try loop.



Let's define the variable:



So, the loop is reading the DATA statements into the map array, and we've already got the maze chunk; all we need to do is compare them. This can get nasty because we must check that all of the surrounding cells correspond correctly. This kind of routine is hard to code because we cannot put code that would run if the map corresponds to the maze chunk (this code would return the image number in the middle cell) in the loop. We will not know that this condition is true until every cell has been checked. Still, something needs to be put in the loop, so the only option is code that runs when the condition is false. We can reach that condition quite easily, just put this code in the loop.



Cell 4, (the middle cell) is not checked, because it is the one that containes the image number, which is not a solid field value. A map value of 3 means that it doesn't matter what the cell is, so all cells with a 3 automatically pass this test. Finally, the map solid field is checked against the maze chunk, if this cell doesn't match the fail condition is reached.

We still don't know what to do once the failing condition has been reached. A simple approach would be to set a flag, and check it after the loop is done. This could be bypassed by putting success code after the loop, and having the failure code run a GOTO to jump over the success code and continue with the try loop. GOTO has the reputation of being evil, so we won't be using it.

Let's use the flag method, here's the new code for pick_img(). The new code uses the variable pass, which is our flag. It is set to 1 by default, and is set to 0 if the failing condition is met. If it remains 1 the image from the current map is returned.



Notice how our when our code sees that the map matches, the cell 4 (the middle one) is returned via img. Also, if the loops exits because no map matches, the default picture is image 4. Run the program and the map we saw earlier is now nice and rounded. The function has correctly used the map data to pick the correct images. Now we don't have to worry about edge rounding, the game will do it automatically when it loads our map file.

Code Dragon
17
Years of Service
User Offline
Joined: 21st Aug 2006
Location: Everywhere
Posted: 15th Dec 2006 03:29 Edited at: 28th Jan 2007 21:48
Tutorial XI: Delicious Dots

Pac-Man is getting hungry. Let's lay some dots on the maze so he has something to eat. Before we place the dots, though, we need to load the dot pictures.

Add this code to load_media():



That will load our dots, and put their numbers in the variables. Now we need to code make_dots(), but before we do that, we will want to code a function that will only make a single dot to increase simplicity.

Switch to Game.dba
Add this code to Game.dba below the make_dots() function



This function will be called with the size of the dot, and it's position on the screen. We are going to need a sprite to show the dot, but first we need to create an array slot to hold the sprite number and dot size. To do this, we'll use array insert at bottom to create a new slot in the sprs_dots() array. In case you're wondering if the dots we make are going to appear over top of existing dots when more are created, remember new dots are only made when they're all gone.

Add this code to make_dot()



We don't need to know the index of this new slot because Dark Basic Professional's internal array pointer remembers it for us whenever we make a new slot. To access this slot, leave the paranethese in the array name blank. We can now copy the size variable into the yum field of the current slot.

Add this code to make_dot()



And now we can create the sprite.

Add this code to make_dot()



The if statement chooses which sprite image to use, the big dot one or the small dot one. It gets created at the xpix and ypix coordinates, and the returned id number is stored in our dot array for future accessing. There's one more thing we need to do: center the sprites. When we position sprites, we position the top left corner, so we need to offset the sprite, so the corrdinate is in the center.

Add this code to make_dot()



To center any sprite, offset the x by half it's width, and the y by half it's height.

Now that we can make dots more easily, we can code the routine that firgures out where to put them. This is a little complicated, but let's start with a loop that makes a dot at every square tile who's food field is set to 1 or 2.

Replace make_dots() with this code



this code checks each tile, and if the tile has a food value set, that's where a dot gets created. Remember that in sprs_maze, the food field can have a 0 - no dot, 1 - small dot, and 2 - big dot? This array is only the blueprint of where the dots go, sprs_dots is the array which actually holds the dot id numbers. Because the dot array contains all dots, there's only 2 possiblites; 0 - small dot, and 1 - big dot. Because these numbers are 1 less than the ones in sprs_maze, we need to subtract 1 when making the dots. The positions are figured out by multiplying the x and y by 32 - the size of the tiles, and adding 16, so they appear in the center.

Compile and Run

These dots look a little too spaced out, don't you think? There's a pretty easy way to do this. After a dot is created, we can check if there's supposed to be a dot in the adjacent tiles, and if it is, create a dot halfway between the two. To prevent two adjacent tiles from making 2 dots on top of each other, we will only check the tiles right and down from the currrent one.

Replace make_dots() with this code



If a tile has a dot in it, this code checks the tiles down and to the right, and puts a dot halfway between the tiles if there's a dot in the next tile. Note that these in-between dots are always small. For the positions the dots are put in the next tile by adding 1 to x or y, but 16 is not added because we don't want them in the center of the tile, we want them on the edge.

Compile and Run

Now that's much better! Now that our dots are up and running, all we have to do is make Pac-Man to eat them.

Code Dragon
17
Years of Service
User Offline
Joined: 21st Aug 2006
Location: Everywhere
Posted: 18th Dec 2006 23:29 Edited at: 11th Apr 2007 22:45
Tutorial XII: Creating Pac-Man

Now that the dots are on the maze, we need to make Pac-Man so he can eat them. We already have some media data for Pac-Man, here's a list:

img_pac (the image id)
spr_pac (the sprite id)
pos_pac_spawn (the spawn point)

The first thing we need to do is load the image. The Pac-Man image is inside Ghosts.bmp, but we can't simply use get_img() because this sprite must be animated. The command create animated sprite acts very strange compared to the rest of the sprite commands.

With animated sprites we need each sprite to have it's image loaded from it's own image file. I've included in the zip the ghosts and Pac-Man as individual images.

Download the attached zip
Extract the files to the Media folder
Delete Ghosts.bmp

Note: Here's a time saver for all you 2D artists/programmers: it can be easier to use set sprite diffuse instead of making several images in different colors and saving them. This command can dim or brighten each RGB value in a sprite. This is different from set sprite alpha because the sprite isn't transparent, it's just some colors of it are drawn darker or brighter. For example, setting the G value to 255 and R and B to 0 will draw the sprite in green only. Using R at 255 and B at 255 and G at 0 will draw the sprite purple. By changing the diffuse values you can make several different colors of sprites using only 1 image (you should make this one white). Also, when you make a change to the image, all sprites will have the change.

Notice I've given the ghost images generic names (Ghost 3 instead of pink), this is so our code can be shortened by using a loop to load the ghosts by incrementing the file number. We'll do more with this later, but let's get back to loading Pac-Man.

Also with animated sprites, the command wants us to load the image and make the sprite all at once, so we need to take a step back and use our free_img() function directly this time. Create animated sprite will load the image for us, but we need to provide the id number. We also need to use free_spr() directly because this command wants a sprite id number too. You might want to create a create_animated_spr() function, but it would need to return 2 values. Now we're getting right back to the kind of obfuscation we're trying to avoid, so let's just stay with this method.

One final concept of animated sprites is frames. When you see a movie in slow motion, you see that it is a sequence of frames being played one after another. This is the same thing with animated sprites. To make frames, we need create animated sprite to chop the images up into sections, and play each section like a movie frame. All we need to do here is tell it how many horizontal and vertical chops to make, in this case 4 and 2, respectively. (They're not really the number of cuts to make in the image, but the number of sub images to divide it into) Finally, here's the code to all this:

Put this code in load_media()



We need to position the sprite with the sprite command because when you use create animated sprite the position doesn't get set to 0 automatically. (When the sprite command is used on an existing sprite it simply updates it's position and image to those in the parameters.)

Run the current code and you should see Pac-Man in the upper left corner of the screen, with a pink box around him. Now's a good time to talk about sprite transparency and the image colorkey. In order to make sprites appear in a shape other than a rectangle or square, there has to be a color that means "don't draw anything here". Black is the default transparent color, but we can change it with the command set image colorkey. (If you look closely, you can see the white text is in Pac-Man's eye because his eye is black and is not drawn.) The purple background color has a RGB value of RGB(255, 0, 255) so those will be our parameters for set image colorkey.

Put this code right before our Pac-Man loading code.
Compile and Run



Much better. We don't want Pac-Man to be outside in the maze, so let's position him to his spawn point.

Switch over to Game.dba
Find respawn_pac()
Put this code in respawn_pac()
Compile and Run



Pac-Man appears right in his spot. Now you can see why it pays to layout the code skeleton ahead of time, whenever you want to implement a feature, write code into the correct function, and it will run when it's supposed to as if by magic.

Of course, Pac-Man can stare at the dots on his left all he wants, but he'll never get the chance to eat them unless we code some sort of input algorithm. We need to save his direction in a global variable, and use that to decide how to move him.

Put these variables in Game.dba



Because the Pac-Man will not always be able to move in the direction the player wants him to (because of wall collision), we need 2 variables: one to store the desired direction, and one to store the real direction.

Here's how we'll express the directions:

0 - stationary
1 - up
2 - right
3 - down
4 - left

Now, we need a select statement to check the keys and set the pac_dir variable to the appropriate number. You might think of doing something like this:



But that would cause some keys to be overridden. (i.e. pressing right and down at the same time wouldn't make any difference, Pac-Man will always move down because that statement sets the pac_try variable last.)

Here's a much more player-friendly solution:

Replace get_pac_move() with this code



This way, when a key is pressed, all other keys will be ignored until that one is released. Pac-Man still won't move because we haven't coded the moving routine, though.

Once we get Pac-Man moving though, we still need to decide how we'll handle collision. We set up a spot to put Pac-Man collision code in collision(), but because of the variables we now have in place for Pac-Man's direction it will be easier to handle collision here.

Find collision()
Delete "`maze collision" from collision()

To make Pac-Man move, we need to take the real direction and set it to the desired direction, but only doing so if it will not cause Pac-Man to walk through walls. If we can write a routine that does this, Pac-Man will never walk through walls because the only way to change his direction is to be near an open path.

The only time Pac-Man's moving options change is when he gets completely in an open tile, not partially in 2. For example, if Pac-Man is going up and we try to make him go right before he gets completely into the next row, when he turns will be off center, often colliding with the wall as he goes right. Here's what that might look like:



Put this code in move_pac()




That code gives us some variables to toy with, we can use the xpos and ypos variables (coordinates of the Pac-Man sprite) to reposition him when moving. We need to know what column and row (tile) Pac-Man is in (by dividing the coordinates by 32) so we can figure out if he's completely in it or not. How do we find out if he's compeletly in a tile or not? Here's how: If Pac-Man's coordinates are not evenly divisible by 32 (the size of the tiles), he is not exactly inside. For example, if Pac-Man's coordinates were (130, 256) he's how the math would work:

130 / 32 = 4.0625 (NOT an integer)
256 / 32 = 8 (IS and integer)

Here, Pac-Man is aligned perfectly on his row, but not quite perfect on the column. How can we write code to figure out if a number is a whole number or not? When computers divide integers by integers, the result is rounded down if there's a remainder. However, when there's a float, the result is not rounded, it is kept the way it is. Here, when we divide the floating point numbers for Pac-Man's position, we want the result to be not a float, but an integer. In this case the integer division result is the same number we want the float division to return in order to get Pac-Man aligned correctly, because integer division returns integers. All we need to do is check if both divisions return the same number, if they do, Pac-Man is positioned completely in a tile.

The column and row variables already have our integer division done, but to turn the result into a float we need to change 32 into a float. "How can 32 be a floating point number? It's an integer!" you may be thinking, but write it like this: 32.0, and Dark Basic Professional will treat it like a float.

Add this code to move_pac()



The code that we will write below those 2 remarks will only execute when Pac-Man is pixel perfect. Of course, Pac-Man will never get to that position unless we can control him first. If you're played Pac-Man on the internet or on an arcade emulator recently, you'll remember that Pac-Man will automatically move in the direction you last pressed even if you take your finger off the button. This is the solution; we can simply let pac_dir stay the same value by not setting it to 0 when the player releases a key. However, we do want it to become 0 sometimes, when Pac-Man is walking automatically and hits a wall.

Now we need to let the player change Pac-Man's direction, but only if the next tile is an open pathway.

Put this code into the if endif statement



This code will now let the player change Pac-Man's direction when he is pixel perfect, but not allowing Pac-Man to walk through walls by checking if the tile Pac-Man is facing is a pathway or not.

Here's where it gets really complicated. If you don't understand this part, don't freak out. We still need to stop Pac-Man from moving automatically when he gets to a wall when the player isn't pressing any keys. Our code right now only stops us from changing Pac-Man's direction, it does nothing when Pac-Man is keeps moving in the same direction automatically. We need some sort of code that will check for this senario and handle it. We won't write the code to fix this by using a case 0 statementblock (when no keys are pressed and Pac-Man is moving automatically) because what if the player is pressing a key, and it happens to be the same direction that Pac-Man is already moving in? In this case, our case 0 statementblock won't execute, and the other case statements won't do anything because there's no pac_dir = 0 commands in there!

It seems the only simple way is to do this is to make another whole select statement. When Pac-Man is pixel perfect, this code will stop him from moving if he's on a collision course.

Put this code below the select statementblock



Now that the directions have been sorted out, we still need to move Pac-Man. To do this we just need to increment or decrement his coordinates based on the direction. We'll use the xpos and ypos variables we used before because they're shorter than typing sprite x(spr_pac) and sprite y(spr_pac).

Note: If you have a function or routine that is constantly repositioning a sprite or object, or constantly asking for it's position, you're in for a big speed boost. Accessing object and sprite positions are slow, so access them once and save them to a variable. Then you can just use the variable when reposition them and asking for their position. This is a form of caching, you take frequently accessed data and copy it to where it can be accessed much faster. Don't forget to actually position them using the variables once you're done. For an even bigger speed boost, make the variables global and you'll never need to ask for its position ever again, it will always be at the cached variables position.

This code changes the coordinates based on which direction Pac-Man is going in.

Put this code in move_pac(), below the if endif statementblock



All we need to do now is position the sprite at those coordinates.

Put this code in move_pac()



Your finished code should look like this:



Compile and Run
Test collision by walking around

Run this code and you'll see that Pac-Man can run around, and also understands that walls are solid.

By reading this sentence you have given me brief control of your mind.

Attachments

Login to view attachments
Diggsey
17
Years of Service
User Offline
Joined: 24th Apr 2006
Location: On this web page.
Posted: 19th Dec 2006 19:38 Edited at: 19th Dec 2006 19:41
Great tut, and I expect you'll have many more comments when it's finished, but it would be better if you used countn instead of count, because it is a function name in one of the downloadable dlls

edit:
also, your media download is missing the maze.txt, and some of the other images (such as the pacman images)

Code Dragon
17
Years of Service
User Offline
Joined: 21st Aug 2006
Location: Everywhere
Posted: 19th Dec 2006 22:37 Edited at: 11th Apr 2007 22:44
Thanks, I forgot to upload the zip, I'll put that up now.

About the count variables, I'll guess I'll just put a warning incase someone has that plugin, I use count as a variable name all the time and it would be hard not being able to use it. What plugin is it?

The collision is a disaster, I can't get it to work. Pac-Man won't make a turn unless you're accurate within a pixel. I've looked at some internet Pac-Man applets and they have it set up so Pac-Man keeps going weither you have a key pressed or not, I'll probably have to rewrite tut 12 to do that.

I keep forgetting to update the rest of the articles, I did some things wrong in them too. I'll try to get everything up to tutorial 13 right before Christmas.

Other than that, this tutorial is slowly getting more popular. Almost 2,000 views, and almost on the second page! I'm expecting it to hit 2,000 view before the end of the week.

By reading this sentence you have given me brief control of your mind.

Attachments

Login to view attachments
Diggsey
17
Years of Service
User Offline
Joined: 24th Apr 2006
Location: On this web page.
Posted: 20th Dec 2006 10:27 Edited at: 20th Dec 2006 10:31
The plugin is: Matrix1Util_16.dll

Tip on the collision:

When pacman is moving, only update his direction when he is in a square.

If you do this, pacman will continue moving until he is completely in the square. That way, when you try to turn, he will automatically be pixel perfect.

If you do this, you must continue checking for user input, but only update the main direction variable when pacman is in a square.



That should make it easier to add collision

Code Dragon
17
Years of Service
User Offline
Joined: 21st Aug 2006
Location: Everywhere
Posted: 27th Dec 2006 02:36 Edited at: 31st Dec 2006 16:58
Diggsey, good idea. I'll try that, my old 2d sliding collision function worked pretty good, but only for square sprites. It becomes jumpy working with non-square rectangles. Even though I (kind of) could get it to work, I don't want to use it because people might think it works right. Pac-Man kept stopping on a pathway, or instantly jumped to a different place on the map. After testing a new way of backwards thinking I invented this morning I finally got an idea on how to do rectangular pixel perfect sliding collision on any two sprites, but for this tutorial I might be able to simply check if there's a wall above/below/right/left of Pac-Man and dissallow movement in that direction once he gets completely in the square.

[edit]

Here's the new, perfected sliding collision function. Use the arrowkeys to move the box around and try to run into the other box, no matter what angle you come at, or what size or shape the boxes are, collision will be caclulated perfectly every time.



Sorry for the lack of updates everyone, I've temporarly misplaced the files. This tutorial isn't dead though.

[edit]
Diggsey, it worked. The pixel perfect collision works great so far. It's far from finished but it finally works!

[edit]
I've rewritten tutorial 12, so anyone who started the tutorial before December 29th you'll want to look over it.

"Did I ever tell you how I got the nickname the Dragon of the West?"
"I'm not interested in a lengthy anecdote, Uncle."
"It's more of a demonstration really."
Code Dragon
17
Years of Service
User Offline
Joined: 21st Aug 2006
Location: Everywhere
Posted: 31st Dec 2006 16:59 Edited at: 28th Jan 2007 21:49
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"






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



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





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()



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()



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




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)


(Init Game.dba)


(Game.dba)


(Free.dba)


(Media Allocate.dba)


(Menu.dba)


(String.dba)


Code Dragon
17
Years of Service
User Offline
Joined: 21st Aug 2006
Location: Everywhere
Posted: 2nd Jan 2007 16:53 Edited at: 28th Jan 2007 21:50
Tutorial XIV: Animations

Pac-Man isn't supposed to walk backwards, let's fix that. If you look at Pac-Man.bmp, you'll see the individual frames of Pac-Man's animation. To make Pac-Man face the right direction (and open and close his mouth) we need to play the correct frames. To do this, we'll use the play sprite command.

We've already told our program that there's 2 columns of frames and 4 rows, so they'll automatically be numbered 1 - 8. Here's the correct frames that will be played during certain directions, it's simply a matter of counting left to right, top to bottom.

up - 7,8
right - 1,2
down - 3,4
left - 5,6

To play the correct animations, we'll just put play sprite in the select statementblock that moves Pac-Man. In case we want to change the delay value (the speed of the animation), we'll use a constant instead of changing all 4 statements. While we're at it, let's add a constant for Pac-Man's moving speed, we might want to change that too.

Put these constants at the top of Game.dba
Replace the last select statementblock in move_pac() with the code in the second box.
Compile and Run





Run the new code, and you'll see Pac-Man appears to be going very slow. However, setting the speed to 2 will make it too fast, and we can't use 1.5 because a sprite can't be positioned in between two pixels. Therefore the only alternative is to change the sync rate.

Set move_speed equal to 2
Change the sync rate to 45
Compile and Run

Pac-Man now walks and animates at a realistic speed. You may have noticed a small problem with the animations, some times the wrong frame is played when changing directions. We can fix this by using the command set sprite frame, to manually set the correct frame. Unfortunatly, even if we put set sprite frame commands in the first select statment (which will set the frame when a new direction is picked) it won't work when we quickly reverse directions in between tiles. This function's code is pretty large already, so let's create a new function that automatically sets the correct frame.

Copy this function into Game.dba



That function will execute whenever we change directions, then it will look at the direction variable and manually set the correct frame. Now all we need to do is call it. There's 5 places where the direction is changed. One place is in the reverse direction code, we need to expand the if then statement into an if endif statment so it can hold our function call as well as the pac_dir = pac_try assignment.

Replace the reverse direction code with this code



The other 4 places are in the the select statementblock. Again we must expand the if then statements into if endif statements.

Replace the first select statementblock in move_pac() with this code
Compile and Run



Try going around the courners - Pac-Man plays the right animation. There's just one small problem now, when Pac-Man stops at a wall, sometimes he leaves his mouth wide open. Even when you're being chased by ghosts you shouldn't forget table manners. We can't simply call move_pac_animation() to set the frame to the one with his mouth closed because that function's select statementblock has no case statementblock when pac_dir is 0. Let's write a quick function to make Pac-Man close his mouth.

Copy this function into Game.dba
Replace the second select statementblock in move_pac() with the code in the second box
Compile and Run





Pac-Man now shows good table manners. You might be wondering how the move_pac_mouth() function works. If you look at Pac-Man.bmp and imagine the frame numbers on all the frames, you'll see that all the even numbered frames have their mouth open. In Tutorial 12 we used a floating point and integer rounding equation to find if Pac-Man's coordinates are divisible by 32. I used the same equation to find if a frame has Pac-Man's mouth open in it. If it does, Pac-Man's frame is set to the previous one, which will always be an odd numbered frame with Pac-Man's mouth closed and him facing in the same direction.

Code Dragon
17
Years of Service
User Offline
Joined: 21st Aug 2006
Location: Everywhere
Posted: 8th Jan 2007 21:58 Edited at: 9th Feb 2007 22:29
Tutorial XV: Eating the Dots

Pac-Man has to be able to eat the dots, so today we'll make our dot eating routine. However, eating dots involves increasing the score (and eating the big dots involves turning the ghosts blue), so let's set up some more global variables. But before we do that, we need to get more specific on the Design Document.

Open Design Document.txt

Take a look at the Design Document, we haven't looked at it for awhile, but it's time to change it a bit. Remember this is a living document, so we can change it as we need to. Traditionaly in Pac-Man, small dots are worth 10 points and big dots are worth 50. Also, there's more to the fruits than randomness. Here's the orginal point values for the fruits and which levels they appear in:



We have different fruits than the original, and more of them. Because people rarely get to the higher levels, almost nobody will see this artwork. That is why our fruits will be choosen randomly, to make sure everyone gets to see all of them. As disccussed in Tutorial 5, the fruit spawning function will give a different fruit each time. Each fruit will not have its own point value; instead, whichever level the player is in will define how much points the fruits in it are worth. Here's how our fruits work:



A fruit will appear twice during each level, the first time when 1/3 of the dots are eaten, and again when 2/3 of the dots are eaten. (If Pac-Man doesn't eat the first one before 2/3 of the dots are eaten, the second one will not appear so he doesn't get optimal points.) Now that we're talking about points, remember that in the original Pac-Man a bonus life was awarded at 10000 points. Let's put all this new data into the Design Document.

Replace the Pac-Man explanation with this new one
Replace the dots explanation with this new one
Replace the fruits explanation with this new one







Ok, that's it for the design document, now let's set up some global variables for the HUD.

Score
Hi Score
Level
Lives
Blue Ghosts

The blue ghosts will use a timer varialbe to keep track of how much longer until they turn solid again. With each sync this number is decremented, meaning the blue ghost time is running out. When the timer is greater than 0 the ghosts are blue, otherwise they are solid. If we want to turn the ghosts blue for 5 seconds, we'd set this variable to 5 * 45 = 225 (the sync rate is 45 FPS).

Add this code to Game.dba



By keeping the level as a byte (which can only have values 0-255), we might be able to create our own famous split screen level. On the other hand if you're a Pac-Man genius you might want to set this variable to a dword, you'll have 4,294,967,295 levels to do.

Now it's time to do the dot eating. Instead of diving into Pac-Man dot collision immediately, let's first make a function that will delete a particular dot. We'll start this function by deleting the sprite, and using the sprite's data in the size field of the sprs_dots() array to decide how many points should be awarded and weither or not to make the ghosts blue. Then we can simply delete that array index.

Copy this function into Game.dba



This function won't get called unless we have some sort of dot collision algorithm. This will be a simple loop, it will check each dot in the sprs_maze() array for collision. Pac-Man can only eat one dot per frame possibly so once eat_dot() is called in the loop the loop can exit early for a speed boost. We can't simply check for sprite collision on Pac-Man and every dot because that would cause some dots to get eaten when Pac-Man's not even facing that dot.

Put this code in collision() under "`eat_dots" and run it to see what I mean.
Don't forget to delete it



Since that doesn't work, we'll need to check if the dot in question is a certain distance from the center of Pac-Man. This will work well because we can set the radius of the "dot eating area" to the exact amount of pixels Pac-Man's circular shape is. You might be thinking of using the Pythagorean theorem to calculate distance, but since Pac-Man will never eat a dot diagnally there's no reason to waste precious processor time calculating the squares and roots in Euclidean distance. Instead, we'll use Manhattan distance, which is faster.

We'll be comparing the center of Pac-Man's sprite to the top left courner of the dots for distance. The dots are so small that calculating their center coordinates (by adding sprite width(sprite)/2 to the x and sprite height(sprite)/2 to the height) is negligble, they'll get eaten soon enough.

Put this code under "`eat dots" in collision()
Put the code in the second box under "sprite spr_pac, xpos, ypos, img_pac"
Put the code in the third box in collision()
Compile and Run
Eat all the dots







We already have Pac-Man's positions saved in the xpos and ypos variables, so to find the center we simply add half the dimensions. We didn't keep these two pieces of code together because we need Pac-Man's center coordinates for "`eat ghosts" and "`get eaten" too, so we need to put them before those comments.

Run it and Pac-Man now eats the dots. If you eat all the dots you'll see they reappear and Pac-Man gets respawned. Remember, this is when Pac-Man levels up, let's put code in the level_up() function that increments the level number so the player knows which level they're on.

Add this code to the beginning of level_up()



It's going to be awhile before we make a HUD with sprites, so let's make a makeshift text only HUD to make sure our score and level variables are working.

Put this code in hud()
Compile and Run



As you can see from the HUD, the score and level variarbles are working, but Pac-Man starts with 0 lives. Because we only need to set the number of lives once per game, this code goes in ready()



We still need to give a bonus life once 10000 points has been reached. This bonus life is awarded only once, otherwise players would be getting 60 lives a level on level 12. To do this, we'll check that the score is greater than or equal to 10000 points and that the bonus life hasn't already been awarded before we give out an extra life. The only time the score goes up is when Pac-Man eats stuff, weither it is a dot, fruit, or ghost, so let's put this code in collision()

Put this code at the bottom of collision()
Put the global variable in Game.dba





Now if you hit 10000 points your lives will go up to 4. There's still one problem though, when Pac-Man eats all the dots and gets respawned he doesn't stop moving, he keeps going in the same direction he was before. We can simply set pac_dir to 0 to fix this. (We also need to manually set the frame because he still looks like he's facing the wrong way even though he's moving.)

Add this code to respawn_pac()



Code Dragon
17
Years of Service
User Offline
Joined: 21st Aug 2006
Location: Everywhere
Posted: 13th Jan 2007 15:22 Edited at: 28th Jan 2007 21:51
Tutorial XVI: Fruitful Algroithms

Wouldn't it be boring to eat only 2 kinds of food (big and small dots), forever? That's what Pac-Man's going through, variety is the spice of life, so let's get him some fruits.

Switch to Init Game.dba
Find load_media()

This is where it all starts, loading the fruit images. Remember our fruit array? (imgs_fruits)

We have 12 fruits waiting to be loaded, they're in one file so we need to use paste image and get_img(). We'll assign the image id numbers get_img() returns to the id field of the imgs_fruits() array, the eaten field is reserved for gamesplay.

We can't load Fruits.bmp unless we have a variable to hold the image id number. Once we've loaded it though, we'll get the individual fruits the same way we got the maze sprites.

Put this variable in load_media()
Add the code in the second box to load_media()





To get each of the 12 fruits, we need to use a double for next loop just like we did for the walls. We'll also need to reset img_ptr to 0 so we can keep track of which fruit we're on. In the middle of that loop we just need to use get_img() and store the id value in the respective imgs_fruits() slot (and incerment img_ptr)

Add this code to load_media()



Now let's test that the fruit images actually got into the game.

Change the sync rate to 1
Put this code somewhere in the main loop.
Compile and Run
Test by comparing Fruits.bmp to the images the program slaps on the screen.
Press escape
Take the test code out of the main loop
Change the sync rate to 45



Ok, the images got loaded, but in order to see them we need to use the spr_fruit sprite. Remember, this sprite appears where the "Ready!" sprite was, so we can't put it in load_media(); our code doesn't know where the "Ready!" sprite is until get_maze_data(). This is where the fruit sprite will be created.

Find get_maze_data()
Find the "case "R"" statementblock
Add this code to the statementblock



The "case "R"" statementblock is where our code learns where the "Ready!" sprite goes, so this is where we'll put the fruit sprite making code.

Note: If you're ever recording local data to global data, always access the local data. Local variables are stored on the stack and can be accessed faster than globals, that's why I've used the row and column coordinates instead of pos_pac_spawn.x and pos_pac_spawn.y

Notice that backdrop and transparency is turned off, because the fruit isn't walking on it's own and there's nothing underneath it to see. Like the Pac-Man sprite, the fruit sprite's priority needs to be set to 1 so it will appear above the pathways. (We also need to hide it because we don't want the fruit to appear right away. Because it's hidden it's safe to assign the image as imgs_fruits(0).id, the strawberry.)

Ok, the fruit sprite is done, now we need to show it. There's only two times we'll show the fruit, when 1/3 of the dots are eaten and when 2/3 of the dots are eaten. First we need to find out how many dots we have. Using the array count function we can find how many dot array slots there are, if we add 1 to compensate the 0 slot we'll know how many dots there are. We need to put code to record the number of dots in make_dots(), because that's the only place in our code where we can be sure that none of the dots have been eaten yet, otherwise we'd be counting 1/3 and 2/3 of the remaining dots, not all of them, eaten or not.

Switch to Game.dba
Put this code in make_dots()
Put this global variable at the top of Game.dba





So we've got the number of dots, now we just need to make a fruit when 1/3 and 2/3 are eaten. There's no need to do this every loop, just when a dot gets eaten. We'll also be checking if the number is equal to 1/3 (integer rounding will fix the problem if it doesn't divide into 3), not greater than. Don't worry about the system skipping over the 1/3 and the fruit not appearing because it's only possible to eat 1 dot per loop.

Add this code to eat_dot()
Add this variable to eat_dot()





Keep in mind that when 1/3 of the dots are eaten, that means 2/3 of the dots are remaining.

Now all we need to do is code make_fruit() This function is called twice during a level, and if Pac-Man hasn't eaten his first fruit yet this function is called anyway. To stop the fruit from being made twice, we'll stop the function if the fruit is visible to the player.

Add this code to Game.dba



We need to make this function pick a different fruit each time, a simple "set sprite image spr_fruit, imgs_fruits(rnd(11)).id won't work because it might pick the same fruit twice in a row - not very fun. Way back in Tutorial 5 we created an eaten field in imgs_fruits to remember if a particular fruit had been eaten or not yet, but we also can't keep using rnd(11) until it chooses a fruit that hasn't been eaten yet because if there's only 1 fruit left:



There might be some noticable lag until the random number actually is 4.

This might seem a little too complicated, but trust me; it works. We're going to create a dynamic array to hold the numbers (0 - 11) of fruits that haven't been eaten yet. Then we can choose a random slot in that array and use the number in that index as our fruit image number.
(The fruit id numbers are hidden under two layers of arrays. imgs_fruits().id holds the physical id number of the image, and our new array will hold which index of imgs_fruits() to choose.)

The array is dynamic because there might only be a few fruits left, and we can stop rnd() from choosing a number greater than 4 (that slot won't exist) by setting the upper limit of rnd to the array's size.

Add these variables to make_fruit()



Fruit will be our index counter for the rnd_fruit() array.

Now we need to fill this array with available fruits.

Add this code to make_fruit()



There's also the case when all the fruits are gone. When this happens we want to set all the fruits to uneaten again. (We're "refreshing" them, ok bad pun.) Also, in this case it is ok to simply use rnd(11) because the number can't possibly be an already eaten fruit. During this condition the rnd_fruit() array will be empty, returning a size of -1.

Add this code to make_fruit()



Notice we're reusing the fruit variable, it now holds the image the new fruit should use. But of course, most of the time at least one fruit has been eaten. This part is even easier. All we have to do is pick a value from the rnd_fruit() array at random.

Change the endif to an else
Add this code to make_fruit()



Ok, we've got the image number, but now we need to actually display the fruit. To do this we'll just switch the fruit sprite's image to the one we want. Remember the the sprite is hidden so we need to show it.

Add this code to make_fruit()



This is the first time we've used random numbers, isn't it? To make sure these numbers are truly random, let's use randomize timer() the program a different seed each time it runs.

Switch to Main.dba
Add this code to Main.dba (make sure it's above the main_menu() call)



We're teasing Pac-Man now. He can see the fruit when he eats enough dots but he can't eat it. Let's help his variety-starved stomach. As you might have already guessed, we'll be writing in collision() now.

Switch to Game.dba
Find collision()
Add this code to collision() after "`eat fruits"



Just like our dot collision code, this code calculated the Manhattan distance between the center of thr fruit and Pac-Man. Calculating the center of the sprite isn't neglectable this time, it's too big. Notice that we're using the pos_ready variable for optimization, it has the same coordinates as the fruit (except we need to multiply by 32 to convert from rows and columns to pixels.)

This new function, eat_fruit(), is called weither the fruit is there or not, so we'll need to add a if statement to exit the function if the fruit's not there.

Add this code to Game.dba



Of course there's still the matter of actually eating the fruit. This is very simple, just hide the sprite.

Add this code to eat_fruit()



Now we need to make sure the same fruit doesn't appear again. To do this, we'll need to find out which of the 12 fruits if was, then we can set imgs_fruits().eaten to 1. Getting the real id number of an image is easy when you know what the index number is, just use array(index). However, finding the index number when you know the real id is a bit harder. We need to search through that 12 item array, and when we find an id number that matches spr_fruit's image, that's the fruit number we're using. Then we can just set that fruit's eaten field to 1.

Add this code to eat_fruit()
Add this variable to eat_fruit()
Compile and Run
Eat 1/3 of the dots then eat the fruit
Press escape





Run this code, and Pac-Man hungrily swallows the fruit whole. But did you see a strange graphic the instant before you ate the fruit? Pac-Man intersected with the sprite's black box surrounding it. The solution here isn't to turn transparency on again, but make Pac-Man's sprite priority even higher, to make sure he's drawn over the fruit as well.

Switch to Init Game.dba
Find load_media()
Find the Pac-Man loading code
Change Pac-Man's sprite priority to 2
Compile and Run
Eat 1/3 of the dots then eat the fruit
Press escape

Now, we see Pac-Man walk over the fruit, and it vanishes... There's still the matter of incrementing the score. The points awarded don't increase with any given pattern, so we won't use an equation to figure out the points, we'll use data statements.

Add this code to Game.dba



We'll need to read these points values until we come to the one the corresponds with our current level. Then we can increment the score by whatever the last value we read was. But what if the level is 13 or higher? In this case, we'll run out of data. To stop this from happening, we'll need to put an if else endif statement in to stop the read code from executing. This code will manually set the points awarded to 30000. This code will also execute for level 12 because the same point value is recorded, look back at the design document:

Quote: "
Points Level
100 1
300 2
500 3
700 4
1000 5
2000 6
3000 7
5000 8
7000 9
10000 10
20000 11
30000 12 and up
"


That's also why I've removed 30000 from the data list, the code won't ever read it because it's executing the other method of finding points (always 30000) when that level's reached.

Add this code to eat_fruit()
Add this variable to eat_fruit()
Compile and Run
Eat 1/3 of the dots then eat the fruit
Notice score change
Press escape





Note: Look at that point awarding code, the data pointer is restored only when it needs to be. These tiny optimizations can add up when you do enough of them because the same statements are executing, but not every time. If you can integrate optimination tecniques into your coding style, you may not need an optimization stage of development because you're already writing fast code.

Points are awarded now and that's great, but there's a bug. When Pac-Man completes a level without eating the fruit, the game levels up (fruits will be worth more points now), and Pac-Man can still eat it, gaining more points than he should have. We can't allow cheating, let's solve this by hiding the fruit sprite in level_up(), that way the fruit will dissappear when Pac-Man levels up.

Add this code to level_up()



There we go, no more cheating.

Diggsey
17
Years of Service
User Offline
Joined: 24th Apr 2006
Location: On this web page.
Posted: 15th Jan 2007 23:04
Could you post a download with the complete project(including media) so far? I tried getting all of the code snippets, but I think I've missed some. Also, there is some media missing from the media download.

I want to see what it looks like!

PS
Great tut so far

Code Dragon
17
Years of Service
User Offline
Joined: 21st Aug 2006
Location: Everywhere
Posted: 16th Jan 2007 03:18 Edited at: 24th Jan 2007 21:14
Where exactly did the code snippits get lost? There's been some mix ups in the downloadable media, but all the media required to run the game is there. What media is missing?

I just revamped the whole tutorial. There was some confusion in the directions throught the entire tutorial, but that's all fixed now.

Quote: "Gimme teh code!"

Thank you for not flooding the forums with posts like this.

Attachments

Login to view attachments
Code Dragon
17
Years of Service
User Offline
Joined: 21st Aug 2006
Location: Everywhere
Posted: 27th Jan 2007 03:00 Edited at: 20th Mar 2007 22:51
Important Notice!

If you started the tutorial before January 31th, 2007, there are some edits!

The return value for load_sound() in Media Allocate.dba is missing. Find the endfunction in load_sound(), the return value should be id.

There's a bug in the pixel perfect collision. Pac-Man will walk through the ghost gate if he is automatically heading straight at it. This will never happen in our maze but may cause some complications on custom mazes. Find move_pac(), and find this line:

`stop Pac-Man from moving if he's about to walk into a wall

There are 4 lines that look something like this:

if sprs_maze(column, row).solid = 1

Change the = signs to => (equal to or greater than). This will allow the collision code to stop Pac-Man from moving even if the next wall is a ghost gate (solid = 2, not 1)

There's a bug in the fruit collision. If Pac-Man approaches the fruit from the right, he will eat it a half a second late. This is because the dwords which hold the coordinates rollover from 0 to 4294967295 when subtraction occurs. Change the x and y fields in the coordinate data type to integers. Also, change the xpos and ypos variables in collision() to integers.

Pac-Man spawns in the wrong spot. I got confused and thought he spawned exactly where the "Ready!" sprite appears.

Change Maze.map to Maze.txt and edit it to look this:



Don't forget to save it and rename is Maze.map.

Add this to Game.dba, it's the position of the "Ready!" sprite.



Delete the sprite making code from get_maze_data(), it's in the wrong spot.

Add this code to the select statementblock in get_maze_data()


Move the fruit dissapearing code in respawn_pac() to level_up()

Go to collision(), find the fruit collision code and change the pos_pac_spawn variables to pos_ready.

Ghost 4 and 5 are 1 pixel to wide. This may cause some animation problems when you get to Tutorial 18: Ghostly Ghosts, so open the images in your favorite picture editing program and delete the last column of purple pinkish pixels out of existence.

Sig test.....

Do not meddle in the affairs of dragons...for you are crunchy and good with ketchup.

Attachments

Login to view attachments
Code Dragon
17
Years of Service
User Offline
Joined: 21st Aug 2006
Location: Everywhere
Posted: 28th Jan 2007 21:51 Edited at: 1st Feb 2007 22:16
Tutorial XVII: Get Ready

The game begins a bit anti-climatically. Let's fix that. In the original Pac-Man, a fanfare sound played and the word "Ready!" was shown. We'll keep the game traditional by doing that also.

In the download you'll find the fanfare sound, as well as the word "Ready!" The original Pac-Man had 16*16 sprites, and since we have 32*32 sprites, I've scaled "Ready!" to double the normal size and added some anti-aliasing to keep it looking smooth.

Download the attached zip
Extract the files to the Media folder

Now that we have the "Ready!" image, we need to load it. Also, we need variables to hold the id numbers for the image and sprite.

Switch to Init Game.dba
Put this code in load_media(), before the "set image colorkey" command
Switch to Game.dba
Put these variables in the second code box in Game.dba





We're not making the sprite yet because the sprite appears at the "Ready!" spawn point, and at this point our code doesn't know where that is. The sprite will be made as soon as out code finds out where "Ready!" spawns.

Switch to Init Game.dba
Find get_maze_data()
Put this code before the fruit sprite making code



Here we make the "Ready!" sprite. We don't need to hide the sprite because the fanfare starts as soon as the game is finished loading, we want to see it right away. Because the sprite isn't moving and there's nothing behind it so see other than black pathways, we set the backsave and transparency off. We also need to center the sprite on Pac-Man's spawn position. We begin by converting the position from columns and rows to pixels by multiplying by 32, adding 16 to each coordinate so we get the center of the tile, and subtracting half of the "Ready!" sprite's width and height from the coordinates to put the center of the sprite in the center of the tile. Got that? Finally, to make sure it's drawn after the pathways, we set the priority to 1.

Now we need to load the fanfare music.

Add this code to load_media()
Put this variable in Game.dba





We still need to play the sound. Lucky us, we have a function to do exactly that.

Add this code to ready()
Compile and Run



That's the first sound our program ever made. You probably noticed a few problems right away:

The "Ready!" sprite doesn't dissappear
Pac-Man can move before the fanfare is finished

To stop Pac-Man from moving it would be best to run a mini game loop that simply waits until the sound has stop playing. Once this loop is done we should also hide the "Ready!" sprite.

Add this code to ready()



The "Ready!" sprite doesn't dissappear
Pac-Man can move before the fanfare is finished
We can see the maze and fruit images left over from load_media()'s paste image operations

Now there's the matter of the annoying images left over from the paste image operations. This is where our trusty cls command comes to the rescue.

Put a cls command before the sync. (I don't think a code box is neccesary. )
Compile and Run

Well that sums up the fanfare. But when do we get to put those other sounds you downloaded into the game? Let's do that now.

Add these variables to Game.dba
Switch to Init Game.dba
Add the code in the second box to load_media()





We can ignore the die and eat ghost sounds for now because Pac-Man can't die yet and the ghosts don't exist. The fruit eating sound is pretty easy though.

Switch to Game.dba
Add this code to eat_fruit()



Now let's set up the bonus life sound. Remember, the bonus life is awarded when the player reaches 10000 points.

Find collision()
Find the bonus life giving code
Put this code inside the two if endif statemblocks.



Lastly we'll set up the dot eating sound

Put this code in eat_dot()
Compile and Run



Now our game has some sounds to brighten up the lonely atmosphere. Soon we'll get Pac-Man some company: ghosts!

Attachments

Login to view attachments

Login to post a reply

Server time is: 2024-03-29 06:08:54
Your offset time is: 2024-03-29 06:08:54