|
-
May 30th, 2013, 04:18 PM
#12
Re: Jump'n'Run Game - 2 parallel for-Loops needed?
Oh, man, this is gonna be a long post...
About structs vs classes:
Well, let me first make sure you understand what they are before I explain the difference.
Both are used to define new types. In C#, some of the "predefined" types, like int and double are actually structs. For example, int is an alias for the Int32 struct (32 just stands for how many bits it takes up in memory - 32bits = 4 bytes).
So, when you write something like
int x = 0;
the C# compiler pretty much sees this:
Int32 x = new Int32();
In fact, you can write it that way yourself, and there will be no difference in the program. So, you see, you've been using structs all along.
Now, what you need to understand is the difference between a type, and a specific instance of that type. In the example above, the type is, as you know, int. It represents integer numbers, and it also guarantees that you can apply various operations on them, like +, -, *, /, etc. These operations are actually defined as (special purpose) methods within the Int32 struct.
An instance of an integer type is some concrete integer number, like 0, 1, 12, -5, or whatever. So, a type represents all integers, while an instance is some specific integer. Instances are also called object (thus "object-oriented programming").
Hope I'm making sense.
But, what if you need something more sophisticated then numbers for your application? It is inconvenient to represent everything as numbers and strings, isn't it? It would be helpful if you could make new types to represent more complicated things, like cars, pictures, bank accounts, user information, ui windows, etc., or if you are making a game, things like the player, dragons, weapons, loot chests, items, and so on.
That is what structs and classes are for. For example, you can use one of them to create a new type, say Item, that will represent game items and will define operations you can do with them (by defining some methods). Then you can create concrete items out of that type, items that will actually appear in the game. This is called instantiating a type. To instantiate (create an object of) the Item type, you'd simply write:
Item someItem = new Item();
So, this is what structs and classes are on a conceptual level. But in code, they are nothing more than a collection of variables and methods that need to do something on those variables. Data and operations on that data (more or less). Each separate instance gets its own copy of the data. When you want to modify some data on a specific instance, you call the appropriate method, and only the data of that instance is changed.
Assume for the moment that the Item type contains an internal string member that stores its name. Let's say that you can set the name of an Item by calling a method SetName(string):
Item item1 = new Item();
Item item2 = new Item();
item2.SetName("Blah");
Only the name of item2 is changed, because you called that method through the item2 variable. So, think of it like this: instead of having a bunch of variables laying around your code trying to work together to represent several different items, making a complete mess, now you have all of them neatly packed into Item objects; item1 contains within it all the variables related to item 1, and item2 contains all related to item 2. The rest is the same. To make the code do something, you call some methods.
THE DIFFERENCE between the two (in C#; in C++, there's almost no difference):
It's a fairly important one. After you read this, you should probably create a simple console application just so that you can test it in practice - best way to learn. This is something your teacher should explain eventually. Anyway, while, when used, both structs and classes mostly "feel" exactly the same, there's a difference in where the data itself is stored, and how it's passed around.
You can think of it like this; when you create a variable of a struct, the variable directly contains the instance (the value). A class variable does not, instead, it contains a "pointer" (a.k.a. a reference) to some memory location where the computer actually keeps it.
This is important because of the following; if you do this:
Item item1 = new Item();
Item item2 = item1;
what happens depends on whether Item is a struct or a class.
If it's a struct, item2 will contain a separate, independent copy of item1. Because item1 directly contained its value, the value itself got copied.
If Item is a class, item2 will "point" to the same instance as item1; they will basically be two names for the same data. If you do something to item2, you'll see the change if you check it via item1. This is because item1 contained a "pointer" to the value, not the value itself, so what got copied was this "pointer".
These exact same rules apply when you pass variables as function parameters. You'll find on the web that structs are passed by value (copied), while classes are passed by reference ("pointers" are passed).
This might seem strange, but it is actually very usefull, and you'll get used to it. In fact, when coding in C#, for the most part you'll be creating and working with classes, not with structs. And often, when you have big objects, containing a lot of data (for example, a bitmap image), you don't want a new copy to be made every time you pass it to a function. The class behavior prevents this.
Structs are normally used for small types, often relatively simple types, containing only a few data members, like it was the case with Point, Line, Ellipse, Rectangle. Classes are used for everything else, or if you need that "pointer" behavior.
In Visual Studio, when you hover the mouse over a type name (such as int or Item), you'll get a popup that will tell you if it's a struct or a class. Create a test project in VS (console app) and mess around to see all this in action, and to get a better understanding.
But, this doesn't really answer why I made GameObject be a class, and not a struct. The reason has to do with default values. Structs all have predefined default values, and you cannot change this. For example, if you write
int x;
x will have the default value of 0 (all numeric types default to zero).
If you have a struct composed of several other simpler types, when you create a variable, with in the default way, all internal data will be initialized to their own default values. This is why, in the attached examples from a few posts back, when you see:
Point p; //--> which is the same as:
Point p = new Point();
both p.X and p.Y will initially be zero.
Now, the default value for a class variable is null; this simply means that it "points" to nothing, it has no value associated with it.
GameObject o; //--> is the same as:
GameObject o = null;
Now, just as a struct may contain ints, bools, doubles, strings and other structs, it may also contain classes. They will all be initialized to their default values. This behavior cannot be changed for structs, but you are allowed to change it for classes.
GameObject contains two List<T> members, and List<T> is a class. If GameObject was a struct, both the list of lines and the list of ellipses would be null (would not "point" to any list object), and you wouldn't be able to add and remove things from it. I could have written additional code to make up for this, but this would make the GameObject type more complicated and more dificult for you to understand, and I was trying to keep things as simple as possible, considering that you haven't yet studied any of this.
How does he even know that he has to "move" his body? I'm sincerly too stupid to find that or to get it..
Where is the GFX. ...(..)?
Don't get confused by all these new types. The code functions in the exact same way as before - the way you're familiar with already. The execution starts in the Main() method, and then within it things happen, variables are set and method calls are made that make it all happen. It's exactly the same basic flow as before.
He doesn't "know" anything, unless you (the programmer) "tell" him, and this is where you do that:
Look (some code omitted):
Code:
static void Main(string[] args)
{
// ====== (1) IT ALL STARTS HERE ======
GFX.CreateDrawing(WIDTH, HEIGHT, Color.Black);
//...
while (lastKey != (int)'Q')
{
// ===== (2) IT STARTS TO LOOP FROM HERE ON, OVER AND OVER ========
// ===== ONE CYCLE PER FRAME, UNTILL YOU PRESS 'Q' ========
GFX.Clear();
lastKey = GFX.getLastKeyDown(); // <=== (3) THIS gets last key
MovePlayer(lastKey); // <==== (4) THIS "tells" the player to move
// ...
player.Draw(); // <=== (5) THIS Draw() calls GFX. ...(..)?
// ...
GFX.Refresh(); // <==== (6) THIS actually shows the drawing
// ...
// ===== (7) HERE IT JUMPS BACK TO (2) ========
}
// ===== (8) IT GETS TO HERE ONLY AFTER YOU PRESS 'Q'
GFX.Close(); // <=== (9) THIS closes the window
// ====== (10) THE PROGRAM ENDS RIGHT HERE
}
So, when it gets to (3), the code can get the key you're currently pressing. At (4) the code calles the MovePlayer() method, which is how the code knows when to move the player. This method is called every time (for every frame), even if no keys are pressed. It updates the player variable which is defined as static at the bottom of the Game.cs file, which means that the player exists the entire time the program runs. Inside MovePlayer(), the keycode is checked, and if it's a keycode for one of the control buttons, the player's Location variable is updated. This, in effect, moves the player, but [i]nothing is drawn yet[/t].
When MovePlayer() returns, the code continues from where it left off in the while loop, and then it reaches the line (5). There, the Draw() method of the GameObject class is called (remember player is a variable of the GameObject class!).
Draw() doesn't know, nor does it need to know, anything about what key was pressed, or if the player should move or not. It just draws the player at the place player.Location says the player is now.
Code:
public void Draw()
{
foreach (Ellipse el in Ellipses)
{
Ellipse copy = el;
copy.Center = Point.Add(el.Center, Location);
copy.Draw(); // <==== FOLLOW THIS METHOD CALL (in the Ellipse struct)
}
foreach (Line line in Lines)
{
Line copy = line;
copy.Start = Point.Add(line.Start, Location);
copy.End = Point.Add(line.End, Location);
copy.Draw(); // <==== FOLLOW THIS METHOD CALL (in the Line struct)
}
}
In GameObject's Draw() method, a copy of each shape if first moved to the player.Location, by moving it's defining points (via the Point.Add() method). Since this is inside the GameObject class, and since Draw() was called via the player variable, in this code Location means player.Location.
Each of the shape structs defines its own Draw() method. For example, in the Line struct, you have:
Code:
public void Draw()
{
GFX.AddLine(Start.X, Start.Y, End.X, End.Y);
}
This is finally where your "GFX. ...(..)" calls are made.
After all that's done, it returns all the way back to the main while loop, shows the drawing, and repeats all that over and over and over and over, ..., and over, until you tell the application to exit.
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|