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 SpriteEngine
s and Sprite
s (see below). When you connect SpriteEngine
s to a SpriteSurface
, the sprites contained in the SpriteEngine
s 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 Bitmap
s 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.
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:
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
:
protected internal override void Process()
{
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;
}
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 SpriteEngine
s 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 SpriteEngine
s 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 SpriteEngine
s 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:
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 SpriteEngine
s, 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
.
private void btnAddSprite_Click(object sender, EventArgs e)
{
BitmapSprite s = new BitmapSprite((Bitmap)picGlyph.Image);
s.Bitmap.MakeTransparent(Color.Black);
s.Position = new Point(surface.Width / 2, surface.Height / 2);
engineDest.AddSprite(s);
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 ShockwaveSprite
s 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.