Got comments ?!
Your name :

Your comment :

view entries

 
Inside The Game

   

Wha.. ?

 

This is a "in depth" guide to my Game's technology. I have no experience building adventure games, so I still have to learn alot. This guide might be interesting to game developers.

   
   
News

- Sept 10, 2001 Added a full description of the Path finding algorithm
- Sept 2, 2001 I set up this site ! Nuf' said.



How does it work ?

Introduction

Well, let's jump into the technology. First off, thanks to Microsoft guys for DirectDraw. No DirectDraw - no 2D and 3D games for Widows. DirectX allows to access the graphic resources of a computer system on the lowest level (= very fast)
The other good thing about DirectDraw is that it allows you to store pictures and sprites in the graphic card's memory. This way, the pictures don't have to be transfered all the way from the system RAM. All chunks of graphic card memory are called "
Surfaces". So far - no problems.

Surfaces

Every game that uses DirectDraw must have at least one DirectDraw Surface - that is what you see on the monitor. As I said, a Surface is nothing, but a chunk of memory that contains the picture data. So, if you know where this "Surface" is (a pointer to this memory location), you can write something to this memory and you'd see changes on your screen.

So if the resolution you chose for the game is 640x480, you will have 640 * 480 pixels, right ? So, if one pixel represents one byte, you'd need 640 * 480 bytes, correct ? I am using the 16 bit color model (the higher the bit number, the more colors can the monitor show, the more memory is required) which means that 1 pixel is made of 2 bytes, rather than 1 byte (16 bits = 2 bytes).
Saddly, we have to multiply the amount of required memory times 2. So our primary surface occuppies 640 * 480 * 2 = 307, 200 bytes ! That's a lot of memory ! But, don't cheer up yet. For smooth animation, we need a backbuffer surface, which is the exact duplicate of the primary surface. Now let's add 'em up - (640
width * 480 height * 2 bitcount) * 2 surfaces = 1.17 MB of graphic RAM is required to run the program.

The Burger

So, what do we do now ? We load the images into memory. To do that, we create a surface for each pic and blit the picture on it ! Let's say we want to put a picture of a mushroom (60 x 30) on the screen. Here are the steps :

1) Init DirectDraw (create The Primary Surface)
2) Create a 60 x 30 Surface
3) Load a picture
4) Blit the picture onto the Surface
5) Blit the Surface onto the Primary Surface
6) Destroy the Surface and The Primary Surface

And this game works the same way. Except, we have more Surfaces to blit. Let's see what surfaces I use :

1 ) A Surface that contains the animation cells with Gadget
2 ) A Surface that contains the background picture
3 ) Many, many smaller Surfaces that contain objects on the screen


First, the Background is blitted onto the Backbuffer Surface, then the Sprite and other Objects are drawn over the whole thing. As simple as a hamburger.

Main Loop

Now we've got to set the whole process in motion. How ? I'm using a Windows multimedia timer. It generates an event every n milliiseconds, so we use this event to update the screen. How fast should the timer operate ? Well, the standard refresh rate is 60 frames per second (TV cartoons run at 24 frames per second). There are 1000ms in a second. Therefore, to get a 60 FPS rate, we need to wait 1000ms / 60 = 16.6 ms each time we refresh the screen. So set up your timer with a 17 ms interval, or use Sleep(17) Win32 function.

Gadget Hackwrench

How do we animate Gadget ? Take a look at the original JPEG, I'm using in the game :

You can see that Gadget is walking from right-to left. You might have guessed that we need to perform some transformations before drawing it onto the surface. If Gadget walks to the right, we need to reflect the Surface, if she walks near a tree (she is waaay smaller than the tree) we need to scale the Surface first.

Also, the frames in this animation are not always displayed the way they are. They follow an animation pattern, which is
[1, 2, 3, 4, 5, 6, 5, 4, 3, 2] to produce smooth walking animation. In future, I'll append more animation cells for talking etc.

Advanced Overlays

Suppose, you're at Scene 31 (Park Level), where the HQ tree is. What happens if Gadget walks behind the tree ? She'd become partly or totally invisible. How can such effect be achieved ?

1) We could create a sparate Surface with the image of the tree and draw it on top of Gadget. (
requires memory for the surface, losing speed when blitting it on top)

2) Do it the way I did ! Ha !
(Read ahead)

To understand overlay masking, you need to know one thing - the bit-operations. It's actually not as complicated as it sounds. All numbers can be represented as bytes. 1 byte can be represented as 8 bits (that are either 0 or 1). A Color can be represented as bits, too ! You can modify these bits with AND, OR, NOT etc operators.

Let's take a look at AND operator and keep in mind, that Black = 0, not Black = 1:

0 and 0 = 0
0 and 1 = 0
1 and 1 = 1


It means, if both colors you're mixing are black, you'll get black as a result. If you mix, say, blue with black, you'll get black anyways. But if you mix, say, blue with red, you'll get anything, but black ! White and black colors are special ones ! Look at their binary values :

White = 11111111 11111111 11111111
Black = 00000000 00000000 00000000

So, if we AND any color with Black, we'll get black. If we AND any color with white - we'll get the same color.

AND =
nifty, eh ? Gadget's covered with a black rectangle !

Now we only have to draw a black'n'white bitmap that contains black shapes that represent areas covering Gadget ! Then AND the sprite with this mask, blit the resulting image onto the background - and voila !



How to AND a pic with another one ?
There are at least two ways (I'm using the first one for now) :
Win32 function "BitBlt"
it's excellent for combining 2 device contexts (HDC). Not only can you AND them, you can also OR, XOR and INVERT 'em!

BitBlt(DestinationBitmap.Canvas.Handle, 0, 0, DestinationBitmap.Width, DestinationBitmap.Height, SourceBitmap.Canvas.Handle, 0, 0, SRCAND)

Write an Assembler routine
I'm not going to start explaining how to write one, eventhough this routine'd be the fastest of all.. To write such routine, create a loop that gets a byte from the destination source, ANDs it with the source byte and writes it back. If you need help, e-mail me.

Walking The Path (updated : Sept 9. 2001)

Yikes! This algorithm was one of the most complicated parts. Say, you clicked on the path near a tree and you want Gadget to happily jog to this point. It might look like a simple task, but don't forget about the tree (the blue-shaded area represents the region, where the sprites can move) ! Or, in another level there might be a bunch of crates, boxes or trashcans - you can't let Gadget bump into all those things ! To find a good path between two points requires some knowledge of AI (Artificial Intellegence) path-finding algorithms. Let's see if I can explain it in simple terms :

Game releases 0.1 to 0.7 featured rectangular areas, that would let Gadget walk in rectangular regions only (boring and unprofessional). I researched the A* (A-star) and "BestFind" (which I'm going to use) algorithms to make all kinds of walkable areas possible. "BestFind" algorithm, is the fastest one. It usually takes about 245-600 milliseconds to find a good path between 2 points (without bumping into walls !)


Let's start ! So, what is the core of the whole process ? We need :

1) A map (black = wall, white = free space)
2) The Start-point
3) The End-point

The starting point is basically where Gadget is right now, and the End point is the location where you want Gadget to go to. A map is just a 2-color bitmap (or 2 dimensional Array) that represents walkable areas. Then we find the path between those 2 points, save each vertex of the path in an array and let Gadget walk it step-by-step.

Now, how do I write the algorithm ? The first thing, you need to understand some basic concepts.


if a point is not surrounded by walls, we can go to any of the 8 surrounding cells (expanding a node)

if we hit a wall, we just ignore the point, and move to another one. Here we expand in only 5 directions.

using the a2+b2=c2 distance formula, we try to find a point that is closer to the End. In this example, it's point B. Forget the other 7 points for now - they are too far away !

Then we expand point closest to the End (Point B). Notice that we only create 5 points E,F,G,H,I, because points above (gray shading) have already been "researched". Point H is the closest one to the End, so we expand it and so forth, until one of the points hits the End.

There is a major difference between Open points and Closed points. An Open point is a fresh point that we haven't expanded yet. After we expand it (creating 8 or less child-points), it becomes a Closed point. We need to maintain a list (use TList in Delphi) that will contain our points (simple records) for each type of those points. So, we end up with an OpenList and a ClosedList. And here's the basic pseudo-code structure of the BestFirst algorithm :

1) Create EndPoint
2)
EndPoint.X = CursorX; EndPoint = CursorY; EndPoint.F = 0; EndPoint.Parent = NULL
3) Create
StartPoint
4)
StartPoint.X = Gadget.X; StartPoint.Y = Gadget.Y; StartPoint.F = PythagorDistance(EndPoint); StartPoint.Parent = NULL
5) Add
StartPoint to OpenList //now we have 1 point to expand !//
6) While not
OpenList.Empty do
7) x = FindLowestF (
OpenList) //find the point with the shortest distance to the End//
8)
CurrentPoint = OpenList[x] //expand the closest point//
9) If
CurrentPoint = EndPoint {EndPoint.Parent = CurrentPoint; break}//we reached the End !//
10) Create 8 Childpoints
11) For each
Childpoint do
12) If
ChildPoint.Parent <> NULL {if ChildPoint = Parent {skip this point}} //if the ChildPoint's coordinates are equall to its parent's, we don't need it//
13) If
ChildPoint is in OpenList or in ClosedList {skip} //we don't want to go to points where we've been already//
14) If
ChildPoint is on a black cell {skip} //avoiding the walls//
15)
ChildPoint.Parent = CurrentPoint; ChildPoint.F = PythagorDistance(EndPoint) //set the parent point and the distance to the End//
16)Add
ChildPoint to OpenList
17) ---end (Foreach ChildPoint)
18)
OpenList.Remove (CurrentPoint); Add CurrentPoint to ClosedList //we expanded it, now it's a Closed point !//
19)---end (While not OpenList.Empty)
20) Done !

The record that represents a point holds 4 values : X, Y : coordinates, F : distance to the End and Parent : represents from what point it derived. After we find the path, make a short while loop that will get the chain of points from the EndPoint using the "Parent" field. Then, make a loop that takes a vertex from this point chain, assign a character's X and Y coordinates to it and voila !

As you can see, it's a very powerfull and fast search algorithm. Just look at the screenshot left, how long would it take you to find the exit of that maze ? BestFirst did it in 270 milliseconds. If you have any questions with Delphi implementation of BestFirst or A*,
e-mail me

Tthese links are quite helpful :
Justin Heyes' Introduction to A* -
go there-
Game AI -
go there-
Thomas Grubb's Delphi Pathfinding demo -
go there-

Classes And Objects

Object-oriented environment is a cool feature when building games. Here is the list of Classes I created for the Game :

"Tag", one of the most important properties, is defined in the very beginning - in the "TGameThing" class. "Tag" is the number of the object in the game. If a thermometer object has a Tag number 23, we can always look up the phrase Gadget would say if you click on it.

function LookUpGadgetPhrase( tag : integer) : string;
begin
case tag of
1 : Result := '
This is an apple';
2 : Result := '
This hotdog is yucky !';
...
23 : Result := '
Hmm... A thermometer !';
...
end; //case
end;

begin
Gadget.Say(
LookUpGadgetPhrase( 23 ));
// so she tells us what she thinks about the thermometer !
end.

More stuff coming soon !


   
You are visitor #52639  

Rescue Rangers (c) Disney. Used without permission.
The rest of the material presented on this site (c) Mister Pi (3.14) 2001
e-mail