[From Pompi Pompi Developers and Gamers Blog ]
The source code of revision 10 can be found here. Notice the disclaimer\agreement on the download page.
The code has several .cs(C# source code), each one is dealing with another aspects of the game.
Game1.cs is a file that was created by visual C# when I was selecting the Windows Game project. It is the "game" file, in which all updates to the game logic and all drawings of the graphics will be called from.
Program.cs contains the main of the program, and it will create and run the game class that was defined in Game1.cs.
Object.cs define objects class. Objects could be anything, but in this case these objects are the ground, the banana, the player and in the future also the barrel. I think we can say these are physical objects, because the base object class contains all the physics calculations. In this game, every physical object contains the physics, the logic update and the drawing call, all in one object. In Labyrinthicathings are not as simple as that, but maybe I should try thinking of doing it as such in bigger projects as well. I am not sure it is easy to do so though. You can try and study the hirarechy of objects inside Object.cs to get a sense of how inheritance can be used in this case.
Camera.cs contains a camera class. A camera in a 2D game is also 2D, the camera points where the center of the screen would be in the virtual 2D world. The camera class also have functionality to tell if an object is visible or not. In this game the camera class also moves to the right in a constant speed.
Section.cs contains a class with a functionality to check if two rectangle physical objects intersect with each other.
Input.cs contain classes to deal with input from the player. I defined an interface called PlayerControl. This interface describe the possible commands for the player to send the game. However, it doesn't have any implementation and it doesn't limit which input device will be used to send these commands. KeyboardInput implements PlayerControl and allows the player to use the keyboard to send these commands. In the future I could add other input devices, such as an XBOX360 gamepad, to send these commands by simply implementing the PlayerControl interface.
The last file is Level.cs. This file contain classes to manage groups of objects and help create them.
Patterns level design
Patterns level design is about design decisions regarding a small group of classes that relate to each other. It's a lower level than a high level, concept level overview of the game. But it's higher than a single function or a single class. These kind of design decisions are useful for almost any project and are not project specific. They may be used for project specific classes, but the concept can be used for other projects as well.
I have already talked about the interface ControlPanel, but it's an example of how you separate the concept of a game control from a specific game device that implement the ControlPanel interface.
The classes PlayerObject and PlayerKicker from Objects.cs are related to each other. The player in this game can run, jump or kick. When in kick state, the player actually change it's collision rectangle dimensions and change it's color. These two classes inherit from a base class that has an update logic and draw virtual functions. For the normal state in which the player run and jump, we could use PlayerObject. For the state of kicking we could use PlayerKicker. But we need an object that contain both states! What I have done to solve this is called delegating. Delegate is when a class with certain virtual methods, is using another class with the same virtual methods. It is as if the class outsource part of it's method. This is not an accurate description, but for lack of better description I will use it.
I have made PlayerObject create and keep a reference to a PlayerKicker object. In the update method of PlayerObject, I have set the PlayerKicker position to be the same as the updated PlayerKicker position. In the draw method, if the player is in normal state, I will draw it normally. If the player is in kick state, I will call the draw method of PlayerKicker instead.
Spartan programming are several metrics that should be attempted to minimize simultaneously. Minimizing these metrics suppose to help you create a more readable code and make better use of the terms available to name classes, methods and functions. It is supposedly regarding the function level, but it affects the structure of the whole program, in a sense. I didn't invest a lot of effort to "spartanize" BJBK, but I will give one simple example of how spartan programming might be useful. Take a look at the following method:
virtual public void Update(float t, Camera c)
Vector2 a = new Vector2(0, PixelsPerMeter*9.8f);
Velocity = Velocity + a * t;
Position = Position + Velocity * t; // Like n*(n-1)/2
Position.Y = MathHelper.Min(FloorY, Position.Y);
Velocity.Y = (Position.Y==0.0)?MathHelper.Min(0, Velocity.Y):Velocity.Y;
In this method you can see variables and parameters such as 'a', 't' and 'c'. If you would use these as global variables, it would minimize the amount of names(specifically those 3 letters) you can use in a smaller scope. Since this method is pretty small(one of the spartan metrics) it is also possible to use short names(another spartan metric) for these specific variables and parameters that exist only inside the method's scope. Other names such as 'Velocity' and 'Position' are longer, perhaps because they are class members and are used in a bigger scope. I have used the 'Term?a:b' idiom instead of an if. Ifs are basically very bad for readability. In many cases it make sense to use this idiom instead of an if. Notice all the lines of code are at a reasonable length.
There isn't a lot more I can tell about this function right now, but I hope you got a feeling of how spartan programming can help.
As you can see in the previous section, each frame I recalculate the velocity and position of every physical object in the game. The position equals integration of velocity over time, which equals integration of acceleration over time. If t is the time variable, a1 is a constant acceleration, v1 is a constant velocity and x1 is a constant position, then:
a = a1
v = v1 + a1*t
x = x1 + v1*t + a1*t*t/2
We could use the last equation to calculate the position. We could get along with this in BJBK, but if the velocity or acceleration are not constant and do not behave so nicely, then the integration would be more difficult. Instead every frame I make the following calculation:
v = v + a*t
x = x + v*t
Notice these are assignments and not equations! But where did the 1/2disappear? Lets check if it make sense. Lets say we have n frames of a constant T each frame. Lets say x1=0 and v1=0. Then at time n*T the first equation will give us: x = a1*n*n*T*T/2
In the second case, we need to iterate the assignments n times until we get the result at time n*T. v is initialized to v1(0), which means v at frame i out of n will be equal to a1*i*T.
That means x at frame n will be equal to: Sum (i=0 to n) of a1*i*T*T. The result of this sum is actually a1*n*(n-1)*T*T/2.
As you can see, the result is almost the same and we can also have non constant acceleration and velocity if we want to.
What are your thoughts about this code review? Is it too long? Too difficult? Did you like it? Should I separate it into several articles?