OK, first some disclaimers: this is my first "project" in C# and while I have some previous experience in graphics coding it has been in Direct3D using c++, where different issues are to be considered. I wrote this just as an exercise in C# and for almost exclusive purpose at posting at www.codeproject.com so if you are to use this somewhere else please mention this site.
Now about code itself. I wanted to create a set of classes that will handle basic sprite manipulation, using nothing more than GDI+. I started with class
SpriteCanvas.Canvas. This class encapsulates image file used to create sprites, including sprite layout, sprite size calculations etc. It is a pretty simple class, and I put it in a user control so you can just drag it onto a form, and select image file and select a layout. Here is an example of a canvas image, having layout of 10X6:
After some thought (and try-and-error approaches) I created following classes:
Sprite, that encapsulates individual sprite properties like position, size, is it animated etc.
SpriteLibrary, which is really just a collection of sprites,
WorldView, which does rendering and
World which serves as a glue between sprites (
SpriteLibrary) and rendering class (
WorldView) and also has a number of methods to manipulate sprites, like resizing and moving. There is also a Rectangle collection
RectLibrary, because I got tired of type casting all the time. I put all these classes into a namespace called
SpriteWorld, in case you are wondering where they are. I also created functions for custom clipping (word about that later), z-ordering, time-stamped animation etc.
Approach to creating an application using above mentioned classes should be like this: you create a form and drag
Canvas controls onto it and set image and layout for them. In this app. I used only one canvas but you can use any number you like. Next you put a
PictureBox on form, whose image will be used as a background for a viewport. Next you put some controls that you want to draw on. It could be a
Panel or whatever, as long as you can get it's Graphics (hDc) property. You can have more than one of these as demo shows. You will also need at least one timer, although I strongly recommend two: one that has interval as short as possible (let's say 1ms) that we will use to check if any rendering needs to be done, and other one with interval of around 33ms that will update animated sprites.
Word about animated sprites: all animations are time stamped, so duration of animation sequence is the same no matter how often you update it. Meaning, if you have sprite animation composed of 10X6 = 60 frames and you set it's
oFPS property to 30 fps it will take two seconds to do complete animation sequence. Each time you call
UpdateAnimation method it calculates which frame should be shown based on time stamp, and shows appropriate frame. You can see how it works in demo app. by playing with slider that adjusts sprite's fps.
K, back to creating an app. I should really have created a user control based on
World class, but I didn't so you have to declare it somewhere in your main app. like this:
public SpriteWorld.World myWorld=new SpriteWorld.World();
So now you have everything you need (class wise) and you should do the following: create some viewports, create some test objects and start timers. Your Form constructor should look like this:
You create objects like this:
private void CreateTestObjects()
myWorld.AddSprite(canvas1,new Point(20,60),new Point(0,59),30,true);
myWorld.AddSprite(canvas1,new Point(175,60),new Point(0,59),60,true);
You also need to create timer callbacks:
private void timer_Tick(object sender, System.EventArgs e)
private void AnimationTimer_Tick(object sender, System.EventArgs e)
Some painting override in case we minimize app. or draw some other window over it and it needs repainting:
private void pictureBox1_Paint(object sender,
World contains some functions to manipulate sprites, like resize them, select them and move them. I didn't implement collision detection as it is really trivial, instead I allowed overlapping and did z-ordering of overlapping sprites. In demo it is implemented like this: you click once on a sprite to "select" it (then release button) then move it around and click again to drop it in a new place. As sprites can overlap I decided to implement picking of all sprites underneath the cursor and put them on top of z-order while maintaining their own z-order. Again if you want to choose just one sprite from top it is trivial. It actually works like this: if you have a pile of sprites and you click on them you will select all of them for move, while bringing them on top of z-order, so they are drawn on top of other sprites. Be aware that actual sprite area extends beyond visible area of sprite so it is possible that you will select more than one sprite when they are close together, even if you intended to pick only one. In order to fix this we would have to test cursor position against each sprite's alpha mask, which is too involved right now.
For the end, word about clipping. It was probably the hardest thing to do. Consider the following picture:
Let's say that we got rendering request from sprite1 and sprite3, and we want to create one clip region. So we create a clip region (yellow outline) that contains both sprite1 and sprite3. But wait, if we draw background and only these two sprites, other sprites 2,4 and 5 will get also partially erased when we draw background in this clip. Clearly, we end up with much larger clip region and instead of drawing two sprites now we have to draw five. That is why I have created variable number of clips in
WorldView class and keep that number in variable
pClips. Depending on your application, how close sprites are to each other, how many of them are animated etc. you may decide to use more than one clips, or you can decide to give each sprite it's own clip in which case you set
pClips=0. If you want to redraw complete scene each time (no clipping) you should use call to function
RequestAll(), which will request background and all sprites but it is very inefficient. Function that reduces number of clips does it by merging rectangles together in a loop, finding merged one with min. area which is a keeper and eliminating two from which it originated, and continue merging until we have desired number of clips left. Of course, each time we merge two clips we have to check for possible adds in form of overdrawn sprites and put that into consideration as well
If you are interested into the exact implementation of the above please look up the source code, as I do have a tendency to get long-winded and you may find it easier just to look it up yourself. I am aware of at least couple shortcomings right now, like I didn't correctly implement rendering in case you don't want to have background image, and sprite selection (used to move sprites around) doesn't check where does call comes from, so only first viewport works (it should be easy to fix it, every viewport has reference to it's parent, so you should be able to detect from where did call came from) etc. If you do find something useful feel free to use it, also if you have some improvements it would be nice if you let me know in a forum bellow or at: firstname.lastname@example.org