Click here to Skip to main content
15,867,686 members
Articles / Programming Languages / C#
Article

Sprite manipulation in C#

Rate me:
Please Sign up or sign in to vote.
4.88/5 (23 votes)
25 Feb 20026 min read 215.7K   5.5K   82   17
Basic sprite animation, clipping, z-ordering in C# using GDI+

Sample Image - Sprites.gif

Introduction

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 PictureBox, 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:

C#
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:

C#
public Form1()
{
    InitializeComponent();
    //create two viewports, one default size and origin
    myWorld.CreateViewport(pictureBox1,Background.Image);
    //... the other somewhat smaller and showing different 
    // portion of "world"
    myWorld.CreateViewport(View2,View2.CreateGraphics(),
                               new Point(145,25),
                               new Rectangle(30,30,120,120),
                               Background.Image);
    //create some sprites
    CreateTestObjects();
    //main loop timer
    timer.Start();
    //animation loop timer
    AnimationTimer.Start();
}       

You create objects like this:

C#
private void CreateTestObjects()
{
    //animated
    myWorld.AddSprite(canvas1,new Point(20,60),new Point(0,59),30,true);
    //also animated
    myWorld.AddSprite(canvas1,new Point(175,60),new Point(0,59),60,true);
    //static
    myWorld.AddSprite(canvas1,new Point(80,50));
    //static
    myWorld.AddSprite(canvas1,new Point(70,80));
    //start updating FPS monitor
    FPStimer.Start();
    //show some numbers
    textBox3.Text=Convert.ToString(myWorld.Library.Item(1).oFPS);
    textBox1.Text=Convert.ToString(myWorld.Library.Item(2).oFrame);
            
}

You also need to create timer callbacks:

C#
private void timer_Tick(object sender, System.EventArgs e)
{
    //check if we need some rendering
    myWorld.RenderingLoop();
}
        
private void AnimationTimer_Tick(object sender, System.EventArgs e)
{
    //we animate sprites here
    myWorld.UpdateAnimated();
}
Some painting override in case we minimize app. or draw some other window over it and it needs repainting:
C#
private void pictureBox1_Paint(object sender, 
                               System.Windows.Forms.PaintEventArgs e)
{
    //this is used only when we have some area that needs to be 
    // redrawn like when we start, or min. then restore app, or 
    // bring it to front from behind some other app
    myWorld.RePaint(sender,e.Graphics, e.ClipRectangle);
}

Now, class 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: djurovic@nyc.rr.com

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralCompact Framework Pin
ksmithers13-Apr-10 12:18
ksmithers13-Apr-10 12:18 
GeneralVery illuminating Pin
ToreUp23-Mar-09 17:16
ToreUp23-Mar-09 17:16 
GeneralCanvas on tab panel Pin
Daniel Goodwin19-Nov-07 3:09
Daniel Goodwin19-Nov-07 3:09 
GeneralStrange Bug Pin
SaphuA4-Mar-07 9:43
SaphuA4-Mar-07 9:43 
QuestionHow to reproduce the image file Pin
caln21-Nov-06 1:18
caln21-Nov-06 1:18 
Generalhi there Pin
sreejith ss nair15-Aug-04 19:40
sreejith ss nair15-Aug-04 19:40 
hi,

i nevar knew this that C# is this much powerful. please send me the source code for this article. The source code attached is correcpted.

**************************
S r e e j i t h N a i r
**************************
GeneralRe: hi there Pin
Sasha Djurovic19-Aug-04 11:26
Sasha Djurovic19-Aug-04 11:26 
General100% CPU Pin
mutherFX20-May-04 22:53
mutherFX20-May-04 22:53 
GeneralGreat Article Pin
TLWallace28-Jan-04 18:38
TLWallace28-Jan-04 18:38 
QuestionWhat is a sprite? Pin
guy9923-Oct-03 9:10
sussguy9923-Oct-03 9:10 
AnswerRe: What is a sprite? Pin
Paul Evans27-Oct-03 0:47
Paul Evans27-Oct-03 0:47 
Generaltanks Pin
giugio14-Apr-03 6:29
giugio14-Apr-03 6:29 
GeneralPlagarism is a terrible thing Pin
Anonymous8-Nov-02 8:25
Anonymous8-Nov-02 8:25 
GeneralRe: Plagarism is a terrible thing Pin
Sasha Djurovic8-Nov-02 10:48
Sasha Djurovic8-Nov-02 10:48 
GeneralRe: Plagarism is a terrible thing Pin
Sasha Djurovic8-Nov-02 11:30
Sasha Djurovic8-Nov-02 11:30 
GeneralExcellent Pin
27-Feb-02 3:57
suss27-Feb-02 3:57 
GeneralRe: Excellent Pin
Sasha Djurovic27-Feb-02 16:31
Sasha Djurovic27-Feb-02 16:31 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.