Logo
Windows XP
Windows Vista
Windows 7
Windows Azure
Windows Server
Windows Phone
World Cup news
 
 
Windows Phone

Microsoft XNA Game Studio 3.0 : Creating Game Components - Adding 100 Killer Tangerines

2/29/2012 4:16:22 PM
Your younger brother (who seems to have taken on the role of lead game designer) has come to you with an idea for 100 "killer tangerines" that appear on the screen once the player has scored 500 points. He is not sure what they would do, or how they would work, but he reckons that they would "scare the player to death," as he puts it. Because you are making up the game as you go along, you reckon that this might be a fun thing to do, and you can work out what to do with them once you have created them. The first thing you need is a picture of a tangerine to use for a sprite. Fortunately, the Great Programmer likes oranges, and so you are able to take a picture of one and convert it for use as a game texture. You decide to call this new type of sprite a KillerSprite for now; you can always change its name later if you need to.

1. Creating a KillerSprite Class

You use the MovingSprite as the basis of your KillerSprite sprite:

public class KillerSprite : MovingSprite
{
    public KillerSprite(
        Texture2D inSpriteTexture,
        float widthFactor, float ticksToCrossScreen,
        float inMinDisplayX, float inMaxDisplayX,
        float inMinDisplayY, float inMaxDisplayY)
        : base(inSpriteTexture, widthFactor, ticksToCrossScreen,
            inMinDisplayX, inMaxDisplayX,
            inMinDisplayY, inMaxDisplayY,
            0, 0) // set the initial position to 0,0 for now
    {
        // TODO: Calculate a random initial position for the Killer Sprite
    }

}

The big scary lump of code in the class is the call of the constructor of the MovingSprite class. This needs to be given all the information it needs to create the sprite rectangle and set up the movement of the sprite. At the moment, the constructor for KillerSprite doesn’t actually do anything—it just passes all the values to the base constructor of its parent class.

The base constructor for the parent MovingSprite class must be given an initial position for the sprite. At the moment we don’t know where to put the sprite, so all KillerSprite objects are initially placed at 0,0. The constructor code for the KillerSprite will calculate a random position of this sprite, so there is a TODO comment in the constructor to remind us to add this code later.

Your game could construct a KillerSprite instance like this:

KillerTangerine  = new KillerSprite(
    Content.Load<Texture2D>("Images/Tangerine"),
    0.03f,    // a tangerine takes 0.03 of the screen width
    1000,     // tangerine takes 200 ticks to cross the screen
    minDisplayX, maxDisplayX, minDisplayY, maxDisplayY);

2. Positioning the KillerSprites Using Random Numbers

Your younger brother wants the tangerine killer sprites to appear at random all over the screen. This means that you need a source of random numbers to position them. Computers are carefully designed to do exactly the same thing when given the same sequence of instructions. A computer that did not do this would be called a "broken" one. From a programming perspective, getting truly random behavior is difficult. Fortunately, the Microsoft .NET Framework provides a way of getting "pseudorandom" numbers very easily.

2.1. Pseudorandom Numbers

A source of pseudorandom numbers is not completely random, but it is random enough to be useful. It uses the previous random number to generate the next one and so produces a sequence of numbers that appear random.

The sequence starts with a particular "seed" value. The process always produces the same sequence from the same seed. This is why it’s called "pseudorandom" rather than "completely random." Pseudorandom numbers are actually quite useful, in that they can produce complex but repeatable behavior. You are going to use this feature so that the killer sprites always appear in the same places on the screen. This would make it possible for a keen player to learn these locations and use this to improve their game play.

2.2. The .NET Random Class

The .NET Framework provides a class called Random which exposes a number of methods that can be used to obtain random numbers in a variety of ways. The first thing the program must do is create an instance of the Random class:

Random rand = new Random(1);  // create a random number generator seeded at 1

					  

This statement creates a new Random instance and sets the variable rand to refer to it. The instance has been seeded with the value 1, so that it will always produce exactly the same sequence of values. The program can now call methods on this reference to get a hold of random numbers from it:

int diceSpots;
int winner;

diceSpots = rand.Next(1, 7);   // get a value between 1 and 6
winner = rand.Next(100);       // get a value between 0 and 99

The constructor for the KillerSprite must generate random numbers that will place the sprite somewhere within the boundary of the playfield. To do this it can use the maximum and minimum values for the screen size along with the width and height of the sprites that will be drawn:

initialX = rand.Next((int)minDisplayX,                             // min value
                     (int)(maxDisplayX - spriteRectangle.Width));  // max value
initialY = rand.Next((int)minDisplayY,                             // min value
                     (int)(maxDisplayY - spriteRectangle.Height)); // max value

					  

The previous code sets the initial position for a KillerSprite so that it is random but within the screen boundary. Note that the Next method requires integer parameters, and so the values must be cast to int before being passed into it.

2.3. Creating a Static Random Generator for the KillerSprite

The KillerSprite class must have one random number generator which is shared among all instances of the class. If the program made a new random number generator each time it made a new KillerSprite instance this would not work. All the sprites would be placed in the same location because they would each contain a brand-new random number generator seeded with the value 1. They would then set their positions by using the same first two numbers produced by the identically seeded generator.

You can get around this problem by making the random number generator a static member of the class. When a class member is made static it is not held inside an instance, but is actually part of the class, and shared by all the instances.

static Random rand = new Random(1);

Whenever an instance of KillerSprite uses the rand member variable, it will use the single, static variable which is part of the class.

The KillerSprite constructor uses the random number generator to position a sprite at a random position, as shown here in bold:

public class KillerSprite : MovingSprite
{
    static Random rand = new Random(1);

    public KillerSprite(
        Texture2D inSpriteTexture,
        float widthFactor, float ticksToCrossScreen,
        float inMinDisplayX, float inMaxDisplayX,
        float inMinDisplayY, float inMaxDisplayY)
        : base(inSpriteTexture, widthFactor, ticksToCrossScreen,
            inMinDisplayX, inMaxDisplayX,
            inMinDisplayY, inMaxDisplayY,
            0, 0)  // set the initial position to 0,0 for now
    {
        initialX = rand.Next((int)minDisplayX,
							(int)(maxDisplayX - spriteRectangle.Width));
							initialY = rand.Next((int)minDisplayY,
							(int)(maxDisplayY - spriteRectangle.Height));
    }
}

2.4. Getting a Random Sequence of Numbers

Your younger brother has been reading this section with interest. He is obviously planning some kind of card game program where he can know exactly what cards the other players are holding, because of the way that he can get a predictable sequence of values from the Random class. If you need to get truly random behavior you can create a Random instance without giving it an initial seed value:

Random rand = new Random();  // create a truly random number generator

If you do not provide a seed value the .NET Framework uses a seed that’s obtained from the precise time that the program runs, so that the game program gets a different random sequence each time the game is played.

3. Using Lists of References

The next problem to solve is how to store all the KillerTangerine variables that you are going to create. Arrays are, at the moment, the only way we know to hold large numbers of things. 

You could use arrays to hold all the game objects, but the C# libraries provide a much better mechanism for doing this. It is called a List. The List is a "collection" class, which is designed to hold lists of things. It is so useful that you feel like taking it home to meet your parents.

The List collection uses a C# feature called generics, where a program construction can be designed and made to work on items independent of their type. The great thing about a List is that you can create it and add items to it without worrying about it filling up. Some clever software behind the scenes (that you don’t have to know about) reserves extra space when required. The List collection seems to have arrived just in time. For now, you need to create something that can manage a very large number of sprites.

3.1. Creating a List Collection

You declare a List collection as you would any other variable, as shown in bold here:

// The Game World
public BreadSprite Bread;
public CheeseSprite Cheese;
public DeadlyPepper Pepper;
public TomatoRowSprite Tomato;
public TitleSprite Title;
public BaseSprite Background;

public List<BaseSprite> GameSprites = new List<BaseSprite>();

This is the game world for our game. It includes all the original sprites, plus the list variable called GameSprites, which is going to hold all the sprites that are on the screen when the game is active. The type of the items you want to put in the list is given between the left-angle bracket (<) and the right-angle bracket (>) characters. In this case, you are creating a list of BaseSprite references, but you can create lists to hold any type. Note that because a reference to a parent class is able to refer to any of the child classes of that parent, we can add any of our sprite types to the list.

3.2. Adding Items to a List

The List class provides a method called Add, which can be used to add things to the List, as shown here in bold:

for (int i = 0; i < 100; i++)
{
    KillerSprite Tangerine;
    Tangerine = new KillerSprite(
        Content.Load<Texture2D>("Images/Tangerine"),
        0.03f,   // a tangerine takes 0.03 of the screen width
        1000,    // tangerine takes 200 ticks to cross the screen
        minDisplayX, maxDisplayX, minDisplayY, maxDisplayY);
    GameSprites.Add(Tangerine);
}

This code creates 100 tangerines and adds them to the GameSprites list. Note that if we wanted 1,000 of them (which would really scare the player), we just have to change the upper limit of the for loop and hope that the Xbox can keep up.

3.3. Accessing List Elements

Getting a hold of elements from a list turns out to be very easy. You can use subscripts just like an array, as follows:

for (int i = 0; i < 100; i++)
{
    GameSprites[i].Draw(spriteBatch);
}

This code would call the Draw method on all the tangerines in the list. If the program tries to access an element that is not present (perhaps the one with a subscript of 100), then the program fails with an exception, just as an array would. The List class provides a Count method, which can be used to find out how many items the list contains:

for (int i = 0; i < GameSprites.Count(); i++)
{
    GameSprites[i].Draw(spriteBatch);
}

This version of the loop would work correctly for any size of list. In this respect, the Count method is directly analogous to the Length property of an array.

3.4. Working Through List Elements Using foreach

Lists (and indeed arrays) can also be used with another form of C# loop construction called foreach. This provides a really neat way of performing an operation on a large number of items in a collection. It removes the need for you to create a control variable and worry about the size of the collection:

foreach (BaseSprite sprite in GameSprites)
{
    sprite.Draw(spriteBatch);
}

The foreach construction takes each item out of a collection and feeds it into the statements to be repeated. This code asks all the sprites in the game to perform their Draw operation. The elements of the List collection are fetched by the foreach loop in the same order that they were added. You can also use foreach to work through the elements of an array in the same way. Note however that other collection classes might not return the elements in the same order that you stored them; only lists and arrays are guaranteed to do this.

3.5. Setting Up the Game Sprites

It makes sense to add all the sprites into the game into the GameSprites list. This means that the Draw and Update methods can be made much simpler because they just have to use a foreach construction. The sprites would be added to GameSprites when they are created:

CheeseBall = new BallSprite(
    Content.Load<Texture2D>("Images/Cheese"),
    0.07f,   // a cheese takes 0.07 of the screen width
    200,     // cheese takes 200 ticks to cross the screen
    minDisplayX, maxDisplayX, minDisplayY, maxDisplayY,
    displayWidth / 4,   // a quarter across the screen
    displayHeight / 4); // a quarter down the screen

GameSprites.Add(CheeseBall);

Note that you now have two ways that to get to the BallSprite instance that represents the cheese in the game. The program can either follow the CheeseBall reference, or use the reference stored in the GameSprites list. You can now remove the use of the individual references in the Draw and Update methods so that they are now even simpler, as shown in the complete Draw method below:

protected override void Draw(GameTime gameTime)
{
    GraphicsDevice.Clear(Color.CornflowerBlue);

    spriteBatch.Begin();

    switch (state)
    {
        case GameState.titleScreen:
            Title.Draw(spriteBatch);
            drawHighScore();
            break;
        case GameState.playingGame:
            foreach (BaseSprite sprite in GameSprites)
            {
                sprite.Draw(spriteBatch);
            }
            drawScore();
            break;
    }

    spriteBatch.End();

    base.Draw(gameTime);

}

The only sprite that is not added to the list of GameSprites is the Title sprite, which is drawn when the title screen must be displayed.

3.6. Extra List Features

The List collection also provides Remove methods that let you remove elements from a list. When an element is removed, the list is "shuffled down" to so that there is no empty slot. This would be a very useful way of removing items from game that have been destroyed. If they are removed from the list, they are not drawn.

Figure 1 shows how the tangerines are drawn. Note that because the code uses a random number generator seeded with the same number each time, the tangerines are placed in exactly the same position each time the program runs.
Figure 1. 100 tangerines (count them if you like) in the Bread and Cheese game

Top Search -----------------
- Windows Server 2008 R2 : Work with RAID Volumes - Understand RAID Levels & Implement RAID
- Windows Server 2008 R2 Administration : Managing Printers with the Print Management Console
- Configuring Email Settings in Windows Small Business Server 2011
- Windows Server 2008 R2 : Configuring Folder Security, Access, and Replication - Implement Permissions
- Monitoring Exchange Server 2010 : Monitoring Mail Flow
- Windows Server 2008 R2 :Task Scheduler
- Windows Server 2008 R2 : File Server Resource Manager
- Windows Server 2008 R2 : Installing DFS
- Exchange Server 2010 : Managing Anti-Spam and Antivirus Countermeasures
- Windows Server 2008 R2 : Configuring Folder Security, Access, and Replication - Share Folders
Other -----------------
- Windows Phone 7 : Using the Microphone in the Funny Repeater Application
- Windows Phone 7 Advanced UI Development : The Microsoft Advertising SDK
- Microsoft XNA Game Studio 3.0 : Creating Game Components - Constructing Class Instances
- Microsoft XNA Game Studio 3.0 : Creating Game Components - Objects and Abstraction
- Creating Transitions and Interactivity (part 2) - Visual State Manager
- Creating Transitions and Interactivity (part 1) - Toolkit Page Transitions
- Silverlight for Windows Phone 7 Toolkit (part 4) - LongListSelector Control
- Silverlight for Windows Phone 7 Toolkit (part 3) - WrapPanel Control
- Silverlight for Windows Phone 7 Toolkit (part 2) - ContextMenu , DatePicker, TimePicker, ListPicker & ToggleSwitch Control
- Silverlight for Windows Phone 7 Toolkit (part 1) - AutoCompleteBox Control
 
 
Most view of day
- Exchange Server 2007 : Deploying a Cluster Continuous Replication Mailbox Cluster (part 2)
- Accessing and Using Your Network : Sharing Resources with the Network (part 2) - Sharing a Resource
- Windows Server 2008 : Designing the Active Directory Administrative Model (part 1) - Delegating Active Directory Administration
- Working with E-mail, Contacts, and Events : Add a Signature
- Windows Server : Network Access Policy and Server and Domain Isolation (part 3) - Planning NAP VPN Enforcement, Planning NAP 802.1x Enforcement
- SharePoint 2010 : Configuring Search Settings and the User Interface - The Preferences Page: An Administrator's View
- BizTalk 2010 : WCF LOB SQL Adapter - Consuming ASDK SQL Adapter in Visual Studio (part 1)
Top 10
- Windows Server 2012 : Configuring IPv6/IPv4 interoperability (part 7) - ISATAP
- Windows Server 2012 : Configuring IPv6/IPv4 interoperability (part 6) - Configuring a DHCPv6 server, IPv6 transition technologies
- Windows Server 2012 : Configuring IPv6/IPv4 interoperability (part 5) - Stateless address autoconfiguration,Stateful address autoconfiguration
- Windows Server 2012 : Configuring IPv6/IPv4 interoperability (part 4) - IPv6 address assignment - Manual address assignment
- Windows Server 2012 : Configuring IPv6/IPv4 interoperability (part 3) - IPv6 address representation
- Windows Server 2012 : Configuring IPv6/IPv4 interoperability (part 2) - Default IPv6 functionality
- Windows Server 2012 : Configuring IPv6/IPv4 interoperability (part 1) - IPv6 concepts and terminology
- Multi-Tenancy in SharePoint 2013 (part 2) - Multi-Tenant Use Cases, Partitioning in the Enterprise
- Multi-Tenancy in SharePoint 2013 (part 1) - Managing Service Application Groups, Creating a Site Subscription
- Sharepoint 2013 : Service Application Administration (part 4) - Setting Up the Farm Trust, Publishing a Service Application
Windows XP
Windows Vista
Windows 7
Windows Azure
Windows Server
Windows Phone
Cars Review