Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Beginning CSharp Game Programming (2005) [eng]

.pdf
Скачиваний:
151
Добавлен:
16.08.2013
Размер:
4.97 Mб
Скачать

60 Chapter 3 A Brief Introduction to Classes

3.5.Is the data in x and y the same after the following code is executed

(assume class Foo exists and has a function named change which changes data)?

Foo x = new Foo();

Foo y = new Foo();

y = x;

y.change();

3.6. Where does the old data of y go when you execute the following line of code?

Foo x = new Foo();

Foo y = new Foo();

y = x;

3.7. What parts of the following function definition are “the signature?”

int function1( int x, int y )

3.8. Can a class have the following two functions at the same time?

int function1( int x, int y )

float function1( int x, int y )

3.9.Why is it a good idea to create constructors?

3.10.Are destructors really needed in C#? Why or why not?

3.11.When a class contains data, that is called the has-a relationship. A class

has a float, and so on. When a class inherits from another class, what is the relationship called?

3.12.What is the primary reason for using inheritance?

3.13.Why would you want to hide your data?

3.14.What can access x from the following class?

class foo {

public int x;

}

3.15. What can access x from the following class?

class foo {

protected int x;

}

3.16. What can access x from the following class?

class foo {

private int x;

}

Summary 61

3.17.When you don’t specify an access level (protected, private, public), what is the default level?

3.18.Why are accessors and properties a good thing?

3.19.How do enumerations make your code cleaner?

On Your Own

Play around and create your own classes. You should be able to create multiple constructors, functions, hidden data, and properties. Try modeling an object from a game you want to make—a spaceship from a space shooter, for example.

This page intentionally left blank

chapter 4

Advanced C#

In Chapter 3, I showed you the basics of classes and other related topics. That’s some pretty heavy stuff for a beginner to learn, but now it’s time for something even more complex.

Fortunately, the most complex features in C# aren’t really general-purpose. You’ll probably use some of them only rarely, so I’m not going to bother covering some topics. I’ll list the important topics that I’ve skipped at the end of Chapter 5, however.

This chapter will give you a brief introduction to some of the more complex ideas used in C#, such as polymorphism, namespaces, and data storage techniques. In this chapter, you will learn:

How namespaces segment your programs.

How to create namespaces.

How to alias namespaces.

How polymorphism makes your programs more flexible.

How to use virtual functions and overriding.

How to use abstraction.

How to use objects to box and unbox value-types.

How to use arrays.

How to use multidimensional arrays.

How to use the foreach loop on collections.

How to use strings.

63

64 Chapter 4 Advanced C#

Namespaces

Namespaces are a relatively new concept in computer languages, but they are very useful, and some might argue that their existence is essential these days.

One of the biggest problems in programming is name overlapping. Say you create a bunch of classes for your program, and then you decide to import someone else’s library to help your program. What happens if some of that person’s classes have the same names as yours, but do different things? It happens quite a lot, unfortunately.

For example, both Direct3D and DirectSound have classes called Device, and you obviously can’t have two classes with the same name. Namespaces makes it easy, so you can refer to the different devices as Direct3D.Device and DirectSound.Device.

You can think of a namespace sort of like a city. If you tell someone merely that you live on Main Street, you aren’t telling him much, as there are thousands of Main Streets across the country. In order to really pinpoint where you live, you need to tell that person what city you live in, too. Creating a namespace is like specifying your city and street in that you can place a certain class (street) inside a specific namespace (city), therefore neatly segmenting your programs.

You can put almost anything into a namespace, including a class, a struct, an enumeration, and even another namespace! Figure 4.1 shows an example of namespaces.

Figure 4.1 Here are two namespaces, System and Chapter04. Within the namespaces are sub-namespaces and classes.

Namespaces are cool because you can nest them—you can put more namespaces inside existing namespaces. For example, the .NET framework comes with the System namespace, and within this namespace are other namespaces, such as System.Data and System.Collections.

Namespaces 65

To expand upon the previous analogy, nested namespaces allow you to create even larger hierarchies, like the United States exists inside North America, California exists within the United States, Los Angeles exists within California, and Main Street exists within Los Angeles.

Creating Namespaces

Here’s some code demonstrating the use of a namespace:

namespace Chapter04

{

class Spaceship

{

// real code would go here

};

class Spacestation

{

// real code would go here

};

}

And then, outside of the namespace, you would access those classes like this:

Chapter04.Spaceship s = new Chapter04.Spaceship();

Another great feature of namespaces is that they can be split up into multiple sections. For example, you can have this in one file:

namespace Chapter04

{

class Spaceship // blah blah

}

and then put the space station in a different file:

namespace Chapter04

{

class Spacestation // blah blah

}

The C# compiler will automatically join the namespaces for you, so you don’t have to put everything into one large file.

66 Chapter 4 Advanced C#

Using Namespaces

When you are within a namespace, you can use anything in that namespace without having to qualify it. If you were to access the spaceship class inside of the spacestation class in the example I showed you before, you could just type in Spaceship and C# would assume you were talking about Chapter04.Spaceship because you’re in the same namespace.

If you’re outside of the namespace, however, you must qualify the namespace by putting in Chapter04. first.

Of course, typing Chapter04.Spaceship over and over can get annoying after a while, especially if you know that you’re only going to be using spaceships from Chapter 4 and nowhere else. Luckily, you’re allowed to tell your C# compiler, using the using keyword, that you want to use everything within a specific namespace. It looks like this:

//at the top of the source file: using Chapter04;

//later on in the file: Spaceship s = new Spaceship();

n o t e

The using keyword can be placed only in certain places. The keyword cannot be placed inside of classes, structs, or enums, but it can be placed almost anywhere else. It’s usually good practice to place your using statements at the top of your source code files, so that you know immediately what other libraries your file needs.

Namespace Aliasing

Nested namespaces can be a large pain in the butt. You haven’t seen them yet, but when you come across them while hot and heavy into DirectX, you will be screaming, “Damn you, Microsoft!” at the top of your lungs…that is, unless you know about namespace aliasing.

Everything related to Direct3D is inside the Microsoft.DirectX.Direct3D namespace. So if you want to access a Direct3D device, you would have to type Microsoft.DirectX.Direct3D.Device. Ugh, right? Luckily, namespace aliasing makes it all better! Basically, you can take one namespace and tell C# to use an alias for it.

Here is an alias for the Direct3D namespace:

using D3D = Microsoft.DirectX.Direct3D;

D3D.Device d; // instead of: Microsoft.DirectX.Direct3D.Device d;

See how much easier namespace aliasing makes things?

Polymorphism 67

Polymorphism

The topic of polymorphism is large and complex—universities offer entire courses on the topic. I can only give you a limited glimpse into the subject in this modest book. But you don’t need to learn any of the really complex parts of polymorphism, anyway.

Literally, the word polymorphism means “many forms.” In computer programming, polymorphism allows you to interact with many different objects without worrying about what those objects really are.

For a real-world example of polymorphism in the computer-programming sense, think of a car. You can get into a car, turn it on, and hit the gas, and you know what’s going to happen: The car is going to move! Now get out of the car, get into a totally different car, and do the same thing: That car is going to move, too! Both cars have the same interface, and you really don’t care how the engines work underneath. Whether you’re riding in a four-cylinder dorkmobile, an eight-cylinder racing beast, or a politically correct electric car, you know that when you press on the gas, the car is going to go. This is polymorphism at its best. The computer tells an object to work, and the object, no matter what it is, goes to work.

Let’s look at polymorphism in a gaming situation. Let’s say you’re programming a simple shooter-type game—you’re in a spaceship and you’re flying around, shooting lasers at everything. Whenever a laser blast hits something, the object reacts somehow, right? In a polymorphic system, it would make sense for each object to know how it should react when it gets hit. A computer-controlled spaceship would sustain some damage, and tell its AI to attack whoever shot the laser. A player-controlled spaceship would sustain damage and maybe send some force-feedback signals to the player’s joystick. An asteroid would split into many pieces. The point is that the actual game engine really doesn’t care how objects react when they get hit by lasers—all it needs to do is tell the object that it got hit in the first place, and let the object take care of the details. I’ll expand on this concept a bit more later on.

Basic Polymorphism

Say you have a very basic inheritance tree: one root and two leaves. The root is a Spaceship and the leaves are CombatShip and CargoShip, as seen in Figure 4.2.

You can play around with them as usual:

Spaceship s = new Spaceship();

CargoShip c = new CargoShip();

This is nothing new, of course. But the fact that a CargoShip is a Spaceship allows you to perform some neat tricks. Look at this line of code, for example:

Spaceship s = new CargoShip();

68

Chapter 4

 

Advanced C#

 

 

Figure 4.2

A simple inheritance tree

That code is perfectly legal. A CargoShip is a Spaceship, after all, so it would make sense to be able to make a Spaceship reference point to a CargoShip, right?

There is one downside to this: The Spaceship reference is not allowed to access any specific parts of the CargoShip class that it didn’t inherit from the Spaceship. Assume that Spaceships have a Refuel function, and CargoShips add a LoadCargo function, and then look at this code example:

Spaceship

s = new CargoShip();

 

s.Refuel();

// ok

 

// s.LoadCargo();

// COMPILER

ERROR. Spaceships can’t load cargo.

CargoShip

c = (CargoShip)s; //

so turn it into a cargo ship

c.LoadCargo();

// ok

 

Whenever you have a reference to a class, you can only access the features of that specific class, even if the object it’s really pointing to supports more features.

n o t e

Note that you can’t use polymorphism the other way around. If you tried writing CargoShip c = new Spaceship();, you would get a compiler error. A Spaceship is not a CargoShip.

Virtual Functions

One of the most important aspects of polymorphism is the idea of a virtual function. A virtual function basically allows you to define a function in a base class and then change it later on. Say that by default, all spaceships handle getting hit by lasers one way, so you define that behavior inside a base Spaceship class. Then, later on, you decide that combat ships should handle getting hit differently because they have better armor.

Polymorphism 69

Virtual functions allow you to handle this situation easily, which you’ll see in the next few sections.

Without Virtual Functions

Here is some code that will make clear what would happen in my example without a virtual function:

class Spaceship

{

public void LaserHit()

{

// lots of damage

}

}

class CombatShip : Spaceship

{

public void LaserHit()

{

// less damage

}

}

What I’ve done here is create a Spaceship class that, by default, causes the ship to sustain a lot of damage when it’s hit by lasers. I wanted the CombatShip it to sustain less damage because it’s more armored, so I created a new LaserHit function that sustains less damage.

This code does exactly what you think it does:

Spaceship s = new Spaceship();

CombatShip c = new

CombatShip();

s.LaserHit();

//

lots

of damage

c.LaserHit();

//

less

damage

No tricks there. But what about the following code?

Spaceship

s

= new

Spaceship();

Spaceship

c

= new

CombatShip();

s.LaserHit();

//

lots

of

damage

c.LaserHit();

//

lots

of

damage... why?

What’s the difference? Instead of using a CombatShip reference like in the first example, I used a Spaceship reference instead, so why would the combat ship take damage just like a regular space ship? The reason is that the spaceship’s LaserHit function never disappeared —it’s still there. When you say c.LaserHit(), it calls Spaceship.LaserHit() because it knows