
Beginning CSharp Game Programming (2005) [eng]
.pdf
70 Chapter 4 Advanced C#
c is a Spaceship. Why is the compiler too dumb to realize that c is actually a CombatShip in disguise? It turns out that this is the way the compiler is supposed to work, and that making it work the way you really want it to requires using virtualism.
Welcome to Virtualism
Virtual functions are a great invention. They’re not even really complicated, either. Before I get into explaining them, let me change the class definitions for Spaceship and CombatShip from the previous section, to add a few keywords in the function declarations:
class Spaceship
{
virtual public void LaserHit()
{
// lots of damage
}
}
class CombatShip : Spaceship
{
override public void LaserHit()
{
// less damage
}
}
Two little things have been changed: the word virtual has been added to the
Spaceship.LaserHit function, and override has been added to CombatShip.LaserHit. Now, if you run this code, it will do exactly what you want it to do:
Spaceship |
s |
= new |
Spaceship(); |
|||
Spaceship |
c |
= new |
CombatShip(); |
|||
s.LaserHit(); |
// |
lots |
of damage |
|||
c.LaserHit(); |
// |
less |
damage now. Hooray! |
Why does this work? Declaring a function virtual tells the compiler that the function might be replaced with a different version in a child class later on. It says, “Hey, this laser hit function works for all spaceships, but some spaceships later on may want to change it.”
Likewise, declaring a function to be override tells the compiler that this function is overriding an earlier version. It says, “Hey, I know this function was declared earlier, but this version is better so use it instead.”

FLY |
Polymorphism 71 |
TEAM |
|
n o t e |
|
If you don’t use the override keyword when declaring CombatShip.LaserHit, then you’ll run into the same problem you had before. All combat ships, when treated as spaceships, will use SpaceShip. LaserHit instead of the function you want it to. You must explicitly declare that a function is overriding an older version. Languages such as C++ and Java don’t require this, so it may be a bit confusing to you at first.
n o t e
The opposite of the override keyword is the new keyword. To revert to the original behavior, you would write new public void LaserHit() rather than override public void LaserHit(). This prevents the Spaceship.LaserHit function from being replaced with the new version; the old version will be called whenever you’re working on a Spaceship reference, and the new version will be called whenever you’re working on a CombatShip reference.
Abstraction
You’re occasionally going to have a situation in which you don’t know what the default behavior for a base class is going to be. Maybe you’ll realize that saying “all spaceships will get hit by lasers in this particular way” is kind of stupid because every spaceship is different and you’re just going to end up overriding the LaserHit function in every child class, anyway.
So why define a Spaceship.LaserHit function in the first place? In this case, you want to use a feature called abstraction. The Spaceship.LaserHit function is abstract—you don’t know how ships are going to get by lasers, but you still know that every ship can get hit by lasers.
If you simply remove the LaserHit function from the Spaceship class, you’ll make things a pain in the butt:
Spaceship s = new CargoShip();
s.LaserHit(); // ERROR! Spaceships don’t know how to get hit by lasers.
Luckily, C# gives you a way of saying, “All spaceships know how to get hit by lasers, but I’m not sure how, yet.” Here’s a redefined Spaceship class:
abstract class Spaceship
{
abstract public void LaserHit();
}
Now you have a Spaceship class, and you know that all spaceships can get hit by lasers, even if you have no idea how at this point in time. How does this affect things, though? Is the following code valid?
Spaceship s = new Spaceship(); // ERROR!

72Chapter 4 Advanced C#
Oops. You can’t create SpaceShips anymore. That’s okay, though, because you probably don’t want to be doing that anyway—you probably want to create CombatShips or CargoShips instead:
Spaceship s1 = new CargoShip();
Spaceship s2 = new CombatShip();
s1.LaserHit();
s2.LaserHit();
n o t e
You cannot instantiate abstract classes. Furthermore, if you have any abstract functions in the class, the class must be declared abstract as well.
n o t e
Any function that is declared to be abstract must be declared as an override in child classes. If you don’t do this, you’ll get a compiler error.
Polymorphism and Functions
I thought I’d add a small note on using polymorphism with function parameters, in case the concept still isn’t quite clear to you.
Let’s say you create a function that works on all kinds of spaceships. Something like this:
class Foo
{
static void ProcessSpaceship( Spaceship s )
{
// some code s.LaserHit();
}
}
You can pass cargo ships or combat ships into the function and it won’t mind—as long as you’re passing in some kind of spaceship:
CargoShip cargo = new CargoShip();
CombatShip combat = new CombatShip();
Foo.ProcessSpaceship( cargo );
Foo.ProcessSpaceship( combat );
The Foo.ProcessSpaceship function doesn’t care what kind of spaceship you use because all spaceships have the same basic capabilities. This is the power of polymorphism.

Polymorphism 73
Objects
In C#, there’s a class called object, from which everything inherits automatically. This allows you to store objects in containers easily (you’ll see this later in the chapter).
Look at this code, for example:
object o = new CargoShip(); o = new int();
o = new float();
o = new WeebulCapacitorInfluxGasket();
Objects can hold anything.
Using the object class is an easy way of turning value-types—such as the built-in numerics and your own structures—into reference types. That’s because objects can be used to box value-types. Whenever you put a value-type into an object, the object immediately allocates memory for that value-type and points itself at the new memory.
Figure 4.3 shows this process.
Figure 4.3 This shows how you can use an object class to box a value-type and make it a reference-type.
Look at this code:
int x = 10; object o = x; x = 20;
x = (int)o;
//o is now a reference to a copy of the integer 10
//change x; o shouldn’t change since it was copied
//unbox o, x is now 10 again
That code shows you how to perform basic boxing and unboxing.

74 Chapter 4 Advanced C#
n o t e
Whenever you unbox an object, you must explicitly cast it back to its original type (or some related type, as long as it’s compatible). Implicit conversion is not possible.
Arrays
You know, I really can’t believe it’s taken me this long to get to arrays. What can I say—C# is one hell of a complex language.
Arrays are containers that allow you to store many objects in them. Basically, you need some way to store lots of data, and using plain variables to store all your game objects becomes very tedious, very quickly:
Spaceship s1; |
// ok |
|
||
Spaceship |
s2; |
// |
meh |
|
Spaceship |
s3; |
// |
ok |
this is getting annoying |
...
Spaceship s20; // my fingers hurt
...
Spaceship s42; // DEAR LORD MAKE IT STOP
That’s a terrible way of storing your game data. Don’t do that. Ever. Or I will send a rabid squirrel to your house to chew the power cable of your coding computer right before you hit Save after you spent the past 10 hours coding in a Mountain Dew-inspired frenzy. I mean it.
Instead of doing all that stupid typing, create an array, which is a chunk of data that you can access by number.
A Basic Array Example
Here is an example of using array:
int[] array = new int[10]; |
|||
array[0] = 0; |
// first |
item is 0 |
|
array[1] = 10; |
// |
second item is 10 |
|
... |
|
|
|
array[9] = 90; |
// |
last |
item is 90 |
Now you have ten integers, and you can access them easily by using the bracket notation after the name of the array.

Arrays 75
n o t e
Arrays use zero-based indexing, meaning that the first object in any array has an index of 0 rather than 1, as many would suspect. This means that the array in the previous example has valid indexes of 0 through 9, and 10 is invalid.
What Is an Array?
An array, like I said before, is just a chunk of data. When you write the following code, it says that you’re creating a variable named a, which is a reference to an array of integers:
int[] a;
All arrays are reference types, which means you must use the new keyword to actually create an array:
a = new int[8];
That line of code creates a new array of eight integers, as shown in Figure 4.4.
Figure 4.4 |
This shows an array of eight integers. |
For all intents and purposes, you can treat a just like any other reference type. Here’s some code showing you how you can use arrays:
int[] a = new int[10];
int[] b = a; |
// b points to the same array now |

76 |
Chapter 4 |
|
Advanced C# |
|
|
b[0] = 10; |
|
|
// changing b also changes a |
|
int i = a[0]; |
|
// i is now 10 |
|
|
b = null; |
|
|
// b doesn’t point to anything anymore |
|
a = new int[20]; |
// the old array is lost, to be garbage-collected later |
||
|
object c = a; |
|
// you can even make it an “object” type |
Arrays are pretty easy to use, as you can see.
Inline Initialization
You can initialize the values of an array directly when you create it, by using this code:
int[] array = new int[] { 1, 2, 3, 4, 5 };
This creates a new integer array with five indexes, with the indexes containing the values 1, 2, 3, 4, and 5.
References versus Values
In the previous section, I showed you an array of integers, which are value types. That’s pretty easy to understand. But what happens when you create an array of reference types, like a Spaceship?
Spaceship[] s = new Spaceship[5];
Does this create an array of five spaceships? No, it doesn’t. It actually creates an array of five spaceship references. The following code produces the situation that Figure 4.5 depicts:
Spaceship s = new Spaceship[5];
s[0] = new Spaceship();
s[2] = new Spaceship();
Figure 4.5 |
An array of references, where some are valid and others are null. |

Arrays |
77 |
Indexes 0 and 2 are taken up by spaceships, but the rest are null pointers.
You can see that an array of reference types really just holds references, and not the actual types. You have to manually create each object in an array yourself if you need to use them. As you learned from Chapter 2 though, you can use for-loops to make this process very easy.
Inheritance and Arrays
One of the best things about arrays is that they fully support inheritance. Look at this code, for example:
Spaceship[] s = new Spaceship[4]; s[0] = new CombatShip();
s[1] = new CargoShip(); s[2] = new CargoShip(); s[3] = new CombatShip();
for( int i = 0; i < 4; i++ ) s[i].LaserHit(); // hit each ship
This code creates an array of four spaceships, and then fills it in with two cargo ships and two combat ships. The last two lines show you a for loop that goes through the array and hits every ship with a laser beam. This works beautifully because the compiler knows that all Spaceships know how to get hit with a laser, and it doesn’t care whether the spaceship is a combat ship or a cargo ship!
Multidimensional Arrays
So far, all I’ve shown you are one-dimensional arrays. If you know your geometry, you know that something in one dimension can only have a length defined; there’s no width or height. In one dimension, you’re pretty much limited to drawing straight lines. It’s the same with arrays: a one-dimensional array can be viewed as a straight line.
If you take the geometry concept and expand it to two and three dimensions, you can imagine arrays looking like those in Figure 4.6.
A 2D array can be thought of as a square grid, like a chessboard. A 3D array can be thought of as a voluminous grid, much like a Rubik’s cube.
You can have arrays of other dimensions too, such as 4D, 5D, or even up to 32D, but for most people, visualizing such arrays can be quite difficult, and they’re really not seen much.
The Easy Way
C# differs from C/C++/Java in dealing with multidimensional arrays. If you’re already familiar with those languages, then this may throw you off a little, but it’s really not difficult.

78 |
Chapter 4 |
|
Advanced C# |
|
|||
|
Figure 4.6 |
This figure shows you a visual layout of three different dimensional arrays. |
Basically, to declare a 2D and a 3D array of ints, you would write this:
int[,] array2d;
int[,,] array3d;
And a 32D array:
int[,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,] array32d;
You simply put n-1 commas inside the parentheses to declare an n-dimensional array.
The next step is to actually create an array:
array2d = new int[5,5]; // 5x5 array, as seen in Figure 4.6 array3d = new int[5,5,3]; // 5x5x3 array, as seen in Figure 4.6
You can change the dimensions to whatever you need to fit your purposes. Accessing the elements in the array is easy, too:
array2d[0,0] = 100; array2d[2,2] = 200; array2d[0,4] = 300; array3d[0,0,0] = 400; array3d[2,2,1] = 500; array3d[4,0,2] = 600;
//top left square, see Figure 4.6 for reference
//middle square
//bottom left square
//top left front cube
//middle cube
//top right back cube

Arrays 79
The Hard Way
There is another way to create arrays, but it isn’t as easy as the first method I showed you. This is the approach that languages like Java take, and it’s really quite a pain in the butt sometimes. Basically, the idea is that a 2D array is just an array of 1D arrays. Sounds weird, doesn’t it? But it makes sense. Look at Figure 4.7 for reference.
Figure 4.7 A slightly awkward way of creating a 2D array— by using a 1D array to store other 1D arrays.
An array can hold anything, so why not make it hold other arrays? Here’s how you would declare a 2D and a 3D array in this fashion:
int[][] array2d;
int[][][] array3d;
Allocating the array, however, is a tricky task. You can’t just say something like this:
int[][] array2d = new int[5][5]; // ERROR
int[][][] array3d = new int[5][5][3]; // ERROR
In the first example, you’re trying to allocate six different arrays at one time (one array of arrays and five arrays of ints), and C# just doesn’t allow you to do that. Instead, you need to do a little bit more work, first by allocating the array of arrays:
int[][] array2d = new int[5][];