Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WinForms

TurboSprite: A Simple 2D Sprite Engine for .NET Framework 2.0

4.96/5 (26 votes)
26 May 2008BSD11 min read 1   1.9K  
A simple, object-oriented 2D Sprite Engine for .NET 2.0.

Image 1

Introduction

TurboSprite is a set of components that provide a complete 2D animation sprite engine to your .NET applications. A "sprite" is a movable object in a game or other application that supports animation. In TurboSprite, you can use several different types of sprites, and create your own derived types to exhibit exactly the appearance and behavior you need. TurboSprite is written completely in C# managed code. The primary components are:

  • SpriteSurface - The visual canvas that the sprite animation takes place on.
  • Sprite - An abstract base class that encapsulates a sprite. TurboSprite contains several concrete descendant sprite classes, like BitmapSprite, PolygonSprite, and AnimatedBitmapSprite.
  • SpriteEngine - Sprites the are added to a SpriteEngine will appear on the SpriteSurface. The SpriteEngine notifies you of collisions between sprites. Descendant classes of SpriteEngine allow you to define different movement capabilities of the sprites that they contain. The DestinationSpriteMover descendant that is included in TurboSprite allows you to move sprites to a destination point at varying speeds.

Background

TurboSprite began back in the mid 1990s as a Windows sprite engine written in Borland Delphi. At the time, generating responsive animation in Windows was very difficult, and TurboSprite relied an special assembly language coded functions to draw directly into the image buffer memory. Now that computers are much faster, the newest incarnation of TurboSprite leverages the drawing capability of GDI+ to perform its rendering. This enabled me to develop the package completely in C# managed code. The performance is very acceptable for simple 2D games and other applications! TurboSprite for .NET has kept the simple, object-oriented design pattern of the original library.

TurboSprite Components

SpriteSurface

SpriteSurface is the visible animation surface where sprites move and other animated effects occur. After placing a SpriteSurface on a form, set the DesiredFPS property to establish the number of frames per second of animation that you wish to achieve. In your code, set the Active property to true to begin animation. At any time, you can examine the actual animation rate of the SpriteSurface by examining the ActualFPS property.

You can produce animation through a number of different avenues, using SpriteSurface. One way is to use SpriteEngines and Sprites (see below). When you connect SpriteEngines to a SpriteSurface, the sprites contained in the SpriteEngines will be rendered and processed on the SpriteSurface.

SpriteSurface also provides events and properties you can handle to tap into the animation cycle at various points. Some of the events use PaintEventArgs, so you can use the Graphics object that is contained in the event to draw on the surface. Keep in mind that the sequence of events below occurs during each animation cycle. You are basically drawing your complete frame during every cycle. It is the slight alteration of what you draw in each frame that can produce animation effects.

  • The BeforeAnimationCycle event occurs before each frame of animation.
  • If you set the AutoBlank property to true, the first thing that happens during each animation cycle is for the surface to be cleared using the AutoBlankColor property.
  • The BeforeSpriteRender event occurs before any sprites are rendered to the surface, but after the AutoBlank processing. Use the Graphics object to perform any custom rendering to the surface during each animation frame.
  • The AfterSpriteRender event occurs after the sprites have been rendered, and allows you to draw to the surface using the Graphics object passed.

A SpriteSurface has a Width and a Height property like any other Windows Forms control, but it also has the concept of a "virtual" size, where the visible portion of the control is a viewport into the full virtual area. To enable this feature, set the UseVirtualSize property to true, and set the virtual size using the VirtualSize property. When the virtual size mode is in effect, you use the OffsetX and OffsetY properties to read and set the offsets of the upper left hand corner into the virtual space. When using virtual size, remember that a sprite's X and Y coordinates are expressed in the full virtual space, not the visible portion of the SpriteSurface.

There are a number of properties that expose a selection cursor in SpriteSurface. If CursorVisible is true, the cursor will appear as a rectangle wherever you click on the surface. The cursor's appearance is controlled by the CursorColor and CursorWidth properties, and its location by the CursorX and CursorY properties.

Additionally, a selection band will appear when you drag the mouse on the SpriteSurface, if SelectionBandVisible is true. The color of the selection band is controlled by the SelectionBandColor property. When the user does draw a selection band, the SpriteSurface triggers the RangeSelected event.

The Wraparound property determines what happens if sprite objects move outside the virtual size of the SpriteSurface. If Wraparound is false, sprites are allowed to move outside of the actual dimensions, they are just not rendered. If true, then a sprite moving beyond one edge of the surface will appear at the other edge, effectively wrapping around the surface.

Sprite

The Sprite class defines an object that can be moved around and rendered onto a SpriteSurface. Sprite is an abstract class that defines a Position property, a Width and Height, and some other properties that are common to all sprites. TurboSprite contains a number of concrete derived sprite classes:

  • BitmapSprite uses a Bitmap to render itself.
  • AnimatedBitmapSprite descends from BitmapSprite, and allows you to specify a set of Bitmaps that define the frames of animation that are used to render the sprite.
  • PolygonSprite renders itself based on a set of points that you define, and supports rotation around its axis.
  • ParticleExplosionSprite generates a particle based explosion, allowing you to control the size, speed, and color range.
  • ShockwaveSprite generates an explosive effect composed of outwardly radiating circles.
  • StarFieldSprite reproduces the classic arcade game star-field hyperspace effect.

You are encouraged to use these as examples and create your own Sprite-descendant classes.

The Sprite class contains two important methods that warrant further discussion here:

  • The Process method is virtual, and you can choose to override it if you wish in a concrete descendant class. TurboSprite calls a sprite's Process method prior to each frame of animation, whether the sprite is within the visible portion of the virtual area or not. Here, you can perform any processing that you wish to apply to the sprite during each frame of animation. This can include things like particle dispersal, decrementing a life counter, etc.
  • The Render method is abstract, so it must be implemented in derived classes. It contains the code that actually draws the sprite onto the SpriteSurface. Render provides a Graphics object that you should use to render the sprite.

When rendering a sprite, use the sprite's Location, minus the current OffsetX and OffsetY of the SpriteSurface to determine where to draw it on the Graphics object. The example below is the Render method of BitmapSprite, which uses a Bitmap to render itself. Note that the code also subtracts half the size of the Bitmap when rendering, ensuring that the Bitmap is centered on the sprite's actual position.

C#
//Render it on the sprite surface
protected internal override void Render(Graphics g)
{
   g.DrawImage(Bitmap, X - WidthHalf - Surface.OffsetX, 
               Y - HeightHalf - Surface.OffsetY);
}

X and Y are properties of the Sprite class, giving you access to its location. As you can see above, the Sprite class provides access to its SpriteSurface via the Surface property.

In Sprite-derived classes, it is important to tell TurboSprite how large the sprite is. TurboSprite uses this information, in combination with the sprite's Position, to determine if it needs to be rendered during each animation frame. The sprite's Shape property (a RectangleF) should be assigned in a Sprite-derived class to define its size. The Shape property should be set irrespective of the sprite's location. For example, if a sprite is 20x20 pixels in size, its Shape property should be set to a new RectangleF (-10, -10, 10, 10); this centers the sprite's shape around its location. You should set the Shape property so that it roughly corresponds to the image that you draw during the Render method.

Below, we see that the BitmapSprite sets the Shape property when the Bitmap is assigned:

C#
//The bitmap used to render the sprite
public Bitmap Bitmap
{
   get
   {
      return _bitmap;
   }
   set
   {
      _bitmap = value;
      if (_bitmap != null)
        Shape = new RectangleF(-_bitmap.Width / 2, -_bitmap.Height / 2, 
                               _bitmap.Width, _bitmap.Height);
   }
}

TurboSprite supports the clicking of sprites and notifying the client through a SpriteClicked event. You don't always want a sprite's visible drawn region to determine the area of the sprite that defines when it is clicked. Because of this, the Sprite class also has a ClickShape property that you can set if you wish to make the clickable region different than the Shape. By default, ClickShape assumes the value of whatever the Shape property was set to.

The Sprite class also defines some properties that describe rotation, including FacingAngle, Spin, and SpinSpeed. If Spin is set to SpinType.ClockWise or SpinType.CounterClockwise, then TurboSprite will automatically adjust the sprite's FacingAngle during each animation cycle. It is up to the derived Sprite classes to utilize the FacingAngle when they render the sprite. To see an example of one that does, examine the code for the PolygonSprite class. PolygonSprite maintains an internal list of points that define the polygon shape of the sprite. During the Process method, the points are rotated based on the sprite's FacingAngle:

C#
//Process the sprite on each animation cycle - handle rotation
protected internal override void Process()
{
   //Process rotation of shape
   if (FacingAngle != _lastAngle)
   {
      float sin = Sprite.Sin(FacingAngle);
      float cos = Sprite.Cos(FacingAngle);
      _lastAngle = FacingAngle;
      for (int p = 0; p < _points.Length; p++)
      {
         _points[p].X = _unrotated[p].X * cos - _unrotated[p].Y * sin;
         _points[p].Y = _unrotated[p].Y * cos + _unrotated[p].X * sin;
      }

      //This causes Shape to be correctly recalculated
      Points = _points;
   }
}

As a side note, the Sprite class creates static lookup tables of sin and cos values that reduce calculation overhead during the animation cycle, and PolygonSprite makes use of these lookup tables in the code above.

SpriteEngine

To have your sprites appear on the SpriteSurface, they must be added to a SpriteEngine. SpriteEngine is a non-visual component that manages sprite movement and collision detection. SpriteEngine has a Surface property which should be set to the SpriteSurface that its sprites will be drawn on. You can use several SpriteEngines connected to the same SpriteSurface to provide different behaviors to different groups of sprites on the same surface.

The SpriteEngine's Priority property determines the order in which sprites are rendered when multiple SpriteEngines are connected to a SpriteSurface.

Collision detection among sprites is handled by setting the SpriteEngine's DetectCollisionSelf and DetectCollisionFlag properties. If DetectCollisionSelf is true, the sprites within the SpriteEngine will detect collisions among themselves. When multiple SpriteEngines are used, the ones that have the same values for DetectCollisionFlag will detect collisions amongst themselves. When a sprite collision is detected, the SpriteSurface triggers a SpriteCollision event, passing you the two Sprite objects that collided.

Moving Sprites

Sprites are hardly worth anything unless there is some way of putting them into motion! In TurboSprite, this is handled by components that descend from SpriteEngine. The SpriteEngine provides a design pattern for defining a sprite's movement. Derived components can utilize this pattern to create different ways to move sprites. TurboSprite contains one such descendant, the SpriteEngineDestination component, which moves sprites at a specific speed towards a specific destination.

The design here utilizes a special tag property in the Sprite class called MovementData. A SpriteEngine-derived component can assign whatever object it needs to this property to track and/or control the movement of the sprite, as it sees fit. The place to set this value is in the InitializeSprite method of SpriteEngine, which should be overridden in derived classes. The SpriteEngineDestination overrides this method to create an instance of a DestinationMover object, and assigns it to the Sprite's MovementData property:

C#
//Create a DestinationMove object and attach it to the sprite
protected override void InitializeSprite(SCG.TurboSprite.Sprite sprite)
{
   sprite.MovementData = new DestinationMover(sprite);
}

SpriteEngineDestination provides a public method called GetMover which returns the instance of the DestinationMover to the client for a particular Sprite object. The DestinationMover class itself contains properties that describe a sprite's speed and destination and whether or not the sprite should stop moving once it reaches its destination. The client code can thus cause a sprite to move by setting the DestinationMover object's Speed and Destination properties.

TurboSprite executes the movement logic when it calls the SpriteEngine's MoveSprite method. This happens during each animation cycle. SpriteEngineDestination overrides the MoveSprite method and employs its custom movement logic. See the code for the SpriteEngineDestination and the comments contained, for a more thorough understanding of the sprite movement design pattern. Although it may seem cumbersome, it provides a level of flexibility for different movement behaviors that can be added in the future.

The TurboSprite Demo App

Included in the source code is a full demo application that illustrates creating sprites, adding them to SpriteEngines, and moving them. It also touches on some of the other goodies that come with TurboSprite, like the GamePieceBitmapFactory component that lets you easily access individual sprite graphics that can be contained in a single larger Bitmap, and even colorize them to any specific color.

Here is the code that executes when the "Bitmap Sprite" button is clicked. This creates a BitmapSprite instance, and puts it into action on the SpriteSurface.

C#
private void btnAddSprite_Click(object sender, EventArgs e)
{
   //Create a BitmapSprite
   BitmapSprite s = new BitmapSprite((Bitmap)picGlyph.Image);
   s.Bitmap.MakeTransparent(Color.Black);

   //Center it on the SpriteSurface
   s.Position = new Point(surface.Width / 2, surface.Height / 2);

   //Add it to the SpriteEngine
   engineDest.AddSprite(s);

   //Set its speed and destination
   DestinationMover dm = engineDest.GetMover(s);
   dm.Speed = rnd.Next(10) + 1;
   dm.Destination = new Point(rnd.Next(surface.Width), rnd.Next(surface.Height));
   dm.StopAtDestination = false; 
}

TurboSprite in Action

TurboSprite is the animation engine used in my freeware real-time strategy game, Solar Vengeance. Solar Vengeance uses many of the TurboSprite features described here, and some that are included in the package but not touched on in this article:

  • Uses a number of custom Sprite-derived classes to render StarShips and StarSystems.
  • Uses the AnimatedBitmapSprite class to render Wormholes.
  • Uses the ParticleExplosionSprite and ShockwaveSprites to render explosions.
  • Uses the SquareGridSpriteSurface as the main surface. This is a component that derives from the basic SpriteSurface, but provides additional functionality that allows you to work with square grids.
  • Uses the SpriteEngineDestination to move sprites on the playing field.

Visit the Silicon Commander Games website to learn more about TurboSprite, Solar Vengeance, and our open-source .NET multiplayer gaming and chat component package, PrismServer.

Image 2

License

This article, along with any associated source code and files, is licensed under The BSD License