PowerBuilder Tips, Tricks, and Techniques

Berndt Hamboeck

Subscribe to Berndt Hamboeck: eMailAlertsEmail Alerts
Get Berndt Hamboeck: homepageHomepage mobileMobile rssRSS facebookFacebook twitterTwitter linkedinLinkedIn

Related Topics: Artificial Intelligence Journal, Game Developer

Artificial Intelligence: Article

XNA, Game Development for Everyone

Writing games sure isn't what it used to be

If you try to run the current project you'll see a cornflower-blue window of 800x600 pixels pop up and that's all, but if you set breakpoints for all of the functions I mentioned you'll see that the XNA Framework's logic flow works something like this:

  1. The main application calls the Game Constructor.
  2. The XNA Framework calls the game's Initialize method.
  3. The XNA Framework calls the game's LoadContent method.
  4. The XNA Framework calls the game's Update method.
  5. The XNA Framework calls the game's Draw method.
  6. Steps 4 and 5 are repeated many times each second.
  7. If the application closes then a call is made to UnloadContent.

We can clearly see that the game loop exists and that the update and draw methods are the places for our game logic and graphics drawing.
Bringing Something Up on the Screen

If you're not already excited about game programming maybe you will be as soon as you see more than a blue background screen. To get started we need to create or import a few images (see Figure 3):

  • The actual track - I borrowed the track and took screenshots from the existing VW Lupo Cup game for this article.
  • An image of any size with a black background and another one with a silver background (these can be easily done in your preferred painting program).
  • An image of a nice car that you'd like to drive around.

If you don't want to create these on your own, feel free to download the source code for this article, which includes all of the images used.

We have to import these images into the project's content subfolder (another new feature of the XNA Framework 2.0) where all the content files should be. Your Solution Explorer should look like the one in Figure 4.

The next step is to show the background and a scoreboard to display the player's name and current and best lap time. To do this we start by defining some instance variables that will hold our textures of the track and scoreboard - where we also need two rectangles that define the size of the scoreboard and its border:

Texture2D scoreBoard;
Texture2D scoreBorder;
Texture2D track;
Rectangle scoreRect = new Rectangle(50, 450, 120, 70);
Rectangle scoreRectAround = new Rectangle(45, 445, 130, 80);

The next step is to code the LoadContent function and load the images. We use an image with a black background for the scoreboard and surround it with a gray border to make it look nicer not forgetting our real background, the track itself. We do this simply by telling the 'Content Pipeline' to load our textures (from the Content subdirectory). The XNA content pipeline is used to load 3D objects (that you've exported from Blender or 3DStudio), textures (this is exactly what we want to do), effects (light, bump mapping), or Microsoft Cross-Platform Audio Creation Tool (XACT) projects. This kind of different content can be created with many different tools and saved in many different file formats. Using the content pipeline built to import most of the common file formats, this content is processed automatically into a managed code object that can be loaded and used by XNA Game Studio games on Windows and the Xbox 360 at runtime (all our content files are compiled into .xnb files and our audio files are compiled into .xgs, .xwb, and .xsb files for the actual sound project, wave bank, and sound bank content).

// TODO: use this.Content to load your game content here
scoreBoard = Content.Load<Texture2D>("Black");
scoreBorder = Content.Load<Texture2D>("Grey");
track = Content.Load<Texture2D>("Track");

Now before we test our cool game we code the draw function, where we simply draw the track, the border, and the actual scoreboard - in that order - so that everything appears on the screen. This is done in a sprite batch because by using it we send a single draw call to the graphics card.

If we did it without using a sprite batch we'd put a load on the gaming machine that would slow things down and maybe result in an unplayable game since all the drawing commands are sent separately to the graphics card every time we draw something to the screen - and that's the last thing we want.

// TODO: Add your drawing code here
spriteBatch.Draw(track, new Rectangle(0, 0, track.Width, track.Height), Color.White);
spriteBatch.Draw(scoreBorder, scoreRectAround, Color.White);
spriteBatch.Draw(scoreBoard, scoreRect, Color.White);

Note that we haven't coded anything in the update function so far because we don't have any game logic yet. If we run the application we see nearly the same thing as in Figure 1 - only the cars are missing and there's no text on our scoreboard. Fine, but what's a racing game without any players? OK, let's create the cars and put them on the starting line.

The first object we should create now is a player class. It's always good to have a class with the player-specific values. You might think that's unnecessary, but it's not. The more you work on a game the more features you want to build into the application. For example, in the beginning you might only have a player name and the laps driven, as well as the current position (x, y coordinates and rotation) of the car. Later on you might want to keep the track record and current points if you stage some kind of championship season, or - even simpler - the keyboard should be configurable, and so on. You can see my current player class in Listing 1 (I'm quite sure that this will be far bigger by the time you read this article).

Another class we need is the LevelHandler class (see Listing 2) that holds configuration options for different players (the default keyboard layout for each player) and for each of our levels (even if we implement only one track this time) the starting positions of the cars and their initial rotation - if you compare my car image and Figure 1, you'll see that the car is facing right, but we want to drive clockwise, so the car should face left - and we'll need different color for each car.

We have to add additional instance variables to our game class so we can bring the car up on the screen. The car has height and width like a real car, but in our world we can easily rescale our car - for example, if the image of your car is way too big. And last but not least we'd like to have four players (or cars) in the game who we hold in an array (we could also use a collection or list, but the garbage collector on the Xbox 360 works differently than Windows - it uses the compact framework here - and the foreach loop could create additional overhead that we don't want and don't need - on top of which we don't add or remove players dynamically so an array is perfect).

int carHeight;
  int carWidth;
  double carScale = 1;
  Player[] players = new Player[4];

Using the LevelHandler class we initialize our players in the LoadContent function. Each player becomes an index needed to map the player to a joystick. We load the texture for the cars and define the initial starting position and rotation of the cars. Our cars should have different colors and maybe the keyboard should be used, so we need keys for accelerating, braking, and steering. See Listing 3.

Note the code in the comment. If you use it, you'd be able to control all the cars all at once with the cursor keys.

Now, when the players are initialized, the only piece missing is to actually draw the cars. And I bet you've figured it out already, the next code snippet goes into the draw method (just before the call to the spriteBatch.End() method):

foreach (Player player in players)
new Rectangle((int)player.carPosition.X,
(int)player.carPosition.Y, carWidth, carHeight),
new Rectangle(0, 0, player.car.Width, player.car.Height),
player.color, player.carRotation,
new Vector2(player.car.Width / 2, player.car.Height / 2),
SpriteEffects.None, 0);

If you run the game now you'll see the cars appear next to the start line. Perfect? Well, not really, the cars aren't moving and don't react to any keyboard or joystick input. This means we have to change the position of the cars depending on the elapsed time and rotate accordingly to the keyboard input from the player. To get our hands on the keyboard or game pad state is a single function call for each. But of course we have to do it for every player. For now the cars always move the same distance. There's no acceleration or braking yet. See Listing 4.

NOW it's really cool when we start up the application. The screen pops up, the track is there, four different cars appear and they start moving and moving and.....whoops! They're off the track and move off the screen (one good thing is that if you use the cursor keys (or joystick) the gold car reacts to your input). Damn, and we've done such a good job up to now, but here's the problem. We need to detect when the cars are leaving the track or when one car is crashing into another car - which means some kind of collision detection. This will be the facility we're going to add to our racing game next month, I hope you like what we did up to now. (See Figure 5) But wait, one thing we still want to do together is fill in the scoreboard with some useful information. We need to create a new spritefont by right-clicking on the Content folder and choosing New Item under the Add menu. We give it the name Courier New.spritefont. This adds an XML file to our project describing how to build a texture map for the new Courier font (it's a good idea to change the size to 10 point type or it won't fit into the scoreboard rectangle) and as with all content this file will also be compiled into a xnb file. To use the font we need another instance variable.

SpriteFont font;

And again, since it's content, the content pipeline should hold the information about the font, and we need to load it and fill our instance variable in the LoadContent function:

font = Content.Load<SpriteFont>("Courier New");

Last but not least we have to draw the text, in other words the player's name as well as his current and best lap time and the number of laps he's driven. First we need some coordinates for where the strings should be drawn, so we add either instance or local variables to the draw function:

int x1ScoreBoard = 55;
int y1ScoreBoard = 450;
int nextLineScoreBoard = 15;

Finally the actual information has to be printed out, so we add the code within the draw function within the foreach loop for the players as shown in Listing 5.

Now hit F5 and, voilà, we see the time running, but as with programming in general it's the same here. Finish one thing and another problem pops up or did we already implement how a car moves across the start or finish line? Again, this is something for next month.

Suggested Reading
Well, this really depends on whether you're completely new to C# and game development. If you're looking for more information on game development in general the book series Game Development Gems is great. If you want to dive deeper into the XNA Framework in general a very good place to start - besides the usual MSDN help content, which has very interesting tutorials - is the creators.xna.com web site where you'll find a lot of samples, starter kits, and, even better, a forum where the pros are watching and helping people out.

When I was watching the XNA Tour here in Europe (Vienna), one guy in the audience said to a friend, "Imagine what great games we would have built if we had these tools when we were young." Well, this guy was as old as me and he clearly grew up with a Commodore C64 and an Amiga like me (so the time he was talking about was close to the Stone Age - at least that's what my younger readers will say) and the great times he was talking about include titles like Summer/Winter Olympics, Ghost and Goblins (damn, it really was a cool time). But, back to XNA, I don't want to give you the wrong idea. I agree that game development is a lot easier today than it was 10 or 15 years ago, but one now has to be more of a graphics designer than a programmer. You see that in this article. It's easy to paint a track and cars on the screen (the content pipeline is great and the Draw method the place to code) and even making them move isn't a problem (the Update method is the place to code that), but think about it, how much time does it take to draw the different tracks? And this is the real challenge today. One needs a good idea and super-sexy graphics. If this isn't available you're sure to fail (I know that this isn't always true, but it is most of the time). Anyway, I hope you like what we built here and next month we'll continue with our racing game and you'll be able to have a real race with your friends. See you then.

More Stories By Berndt Hamboeck

Berndt Hamboeck is a senior consultant for BHITCON (www.bhitcon.net). He's a CSI, SCAPC8, EASAC, SCJP2, and started his Sybase development using PB5. You can reach him under [email protected]

Comments (0)

Share your thoughts on this story.

Add your comment
You must be signed in to add a comment. Sign-in | Register

In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.