This question is mainly targeted towards BC_Programmer because of his BaseBlock game that I'm pretty sure also uses GDI+. So my questions are: How are the classes laid out? How do you update the OnPaint event? Is it double buffered? How do you make the GDI+ more efficient? Do you have a new class for each different type of object?
BASeBlock runs game logic on a separate thread, This separate thread runs the
gameproc() routine, (which is ridiculously gigantic). Essentially, the thread constantly repeats, and basically does:
1. Update all Game Objects
2. force the display to refresh
The second step is performed with something like this:
PicGame.Invoke((MethodInvoker)(() =>
{
PicGame.Invalidate();
PicGame.Update();
}));
Since the GameProc() routine is running on a separate thread, it cannot access UI Controls without causing an exception. The code uses Invoke on a UI control (in this case PicGame) to run a piece of code on the UI thread itself. This logic invalidates the picturebox and updates it, which results in the Paint Event of the Picturebox being fired. The Paint event is more complex, because for performance reasons Blocks that don't need to be redrawn aren't. The result is that there is a buffer for the background, one for blocks, and one for animated blocks. the block bitmap is only changed if a block has changed. Since most blocks are static and don't move or change very often, this is a rather big improvement speed-wise.
The GameProc thread sleeps for a set period of time every iteration, which is calculated based on how fast the FPS currently is. Sadly I forget most of the logic involved. It's not much different from the core idea behind the
GDI+ Particle "tutorial" I posted to youtube. BASeBlock's source, though not 100% up to date, is available on my
github. In order for the project to open,Build, you need VS2012 (not sure if it will open in express, either), as well as the BASeBlock resources installed (which are installed by the game installer in the desired location). You will also need
BASS.NET and of course BASS itself for Sound support. (optionally, it also supports IrrKlang but I've not used that particular "driver' class in a long time)
As for how the classes are laid out...Basically there are some base classes, Block, GameObject, cBall, and cParticle. Each Block in the game can trace it's inheritance tree to Block; for example, BrickBlock derives from TexturedBlock which derives from ImageBlock which derives from Block, EllipseBlock derives from PolygonBlock which derives from Block, etc. Each one naturally simply adds special behaviours to it's parent; ImageBlock makes a Block draw from an Image, TexturedBlock makes that Image a Texture, and BrickBlock uses Textures as well as a special destruction behaviour that spawns certain Particles/GameObjects, etc.
Powerups are implemented via interfaces, and the Paddle can have a set of PaddleBehaviours Applied to it. Over time I've added a lot of utility methods and classes to make certain things easier.
For your current implementation, I would probably make a few changes. The first would be to move your startup code into the Load Event handler for the form; the second would be to change the Game object to not accept a Form, but instead an implementation of an interface that your Form can implement. In BASeBlock, I used a interface called "iGameClient", which is implemented by the Form. The purpose of this is to provide the capability for changing what implements the interface, and prevent strong coupling with the Form itself.
One of the issues originally was preventing cross-thread calls, and most importantly preventing blocks from being removed while the collection was being enumerated, since that would throw an exception. Originally I had a very gross hack that basically passed by reference lists of objects to remove, but I changed that to allow for a collection of delegates to be called before the next frame "tick" occurs. Removing blocks, balls, GameObjects/etc are usually done within the logic for them, but now they defer actual removal until the beginning of the next frame's logic. Also because I was using multiple threads, I had the paint routine working with the game's objects and the "tick" routine as well, so I had to perform some locking to prevent the game "updates" from removing or adding elements (such as in the previous delegate listing) while the painting routine was iterating over them.
Slowdown: With logs of stuff on the screen, the game simply cannot necessarily process each tick as fast as would be ideal. As a result, I've implemented logic that prevents the entire game from slowing down; basically 30fps is the desired speed; if the FPS is higher than that, movement will be smoother but still travel the same distance in the same timescale. if the FPS is 15fps, then objects will move about twice as fast per frame to make up for it. There are of course some issues with this. I f ound heavy particle usage also caused performance issues so I cap the number of particles that can exist, using a Queue. Some particles are tagged as "critical" and won't be removed in this manner, but most particles are used for aesthetic purposes so most are able to be removed if this limit is reached.
Anyway, the most common approach to this I've seen seems to always employ a Timer of some sort, and use the Timer event to handle each tick. But the problem is that that tick logic is occuring in the UI thread and while it operates the UI is completely blocked, so it's almost always best to keep everything that isn't drawing related outside of the UI thread.