Sprites
From SDL.NET
Using Sprites in SDL.NET
A tutorial by David Hudson (jendave at yahoo dot com)
| Table of contents |
Introduction
At the simplest level, sprite is a 2D object that can be shown on and moved around the screen. Each sprite has a Surface and a Rectangle to define what the sprite looks like and where it is located.
This tutorial will provide some insight into creating sprites, displaying them and having them respond to input events such as the mouse or keyboard.
Sprite Class
The sprite class consists of a Surface and a Rectangle. The Surface object is displayed when the Sprite is Blitted to the screen. The Surface can consist of a graphic image, a rendered text string or a primitive for example.
The rectangle is used to show what part of the Surface should be displayed. Typically, the rectangle should encompass the entire surface, but there may be reasons that the rectangle differs from the size of the surface. For example if the Surface actually consists of multiple images to be used for animation, the rectangle can be used to mark which part of the image is to be currently displayed.
The Sprite class has virtual Update() methods which are to be overridden by inheriting classes. These methods, used in conjunction with SpriteCollections will update the Sprite’s position and/or Surface when input events such as mouse button presses, mouse motion, keyboard presses occur.
The Render() method is provided to perform an action on a Surface before it is Blit to the Screen. In the Sprite class, the Render() method simply returns the Surface. In an inherited class, however, this method is often overridden to perform a task on the surface before it is Blit. For example, when a text string may need to be converted to a Surface before it is Blit to the screen. The Render() method can be overridden to do that.
The are some other properties of a Sprite:
|
AllowDrag (bool) |
Can the sprite be dragged (usually with the mouse)? |
|
Visible (bool) |
Is the Sprite supposed to visible on the screen? |
|
BeingDragged (bool) |
Is the Sprite currently being dragged? |
The Sprite class itself does not do much; its Update() methods are empty. The Sprite class is really meant to be extended with its Update() methods overridden by new logic. The BounceSprite class has overridden Update methods that determine what happens when a mouse button, mouse motion or tick event happens.
Some classes extended from Sprite are included in SDL.NET. The TextSprite displays text rendered as a sprite. The AnimatedSprite provides a basis for displaying multiple surfaces over time to create the animation.
SpriteCollection class
The SpriteCollection class is a generic collection for sprites that helps aggregate sprites and execute events on them.
Besides having methods for adding, removing and accessing the Sprites in the collection, SpriteCollection has methods for enabling events on the Sprites it contains. For example, the SpriteCollection.EnableMouseButtonEvent() allows all the sprites in the collection to respond to mouse button clicks. Once the event is fired, the SpriteCollection passes the event and EventArgs to each sprite.
private void Update(object sender, MouseButtonEventArgs e)
{
for (int i = 0; i < this.Count; i++)
{
this[i].Update(e);
}
}
The sprite can then decide how to respond. Usually, the sprite will have logic to check if the mouse click happened on which sprite. For example:
public override void Update(MouseButtonEventArgs args)
{
if (this.IntersectsWith(new Point(args.X, args.Y)))
{
// If we are being held down, pick up the marble
if (args.ButtonPressed)
{
if (args.Button == MouseButton.PrimaryButton)
{
this.BeingDragged = true;
}
else
{
this.Kill();
}
}
else
{
this.BeingDragged = false;
}
}
}
If you click on the spirte with the primary mouse button, you can drag the sprite. If you click on it with the secondary button, it will remove the sprite from the screen and the SpriteCollection.
SpriteCollections can be used to categorize sprites into logical groups. A program may have SpriteCollection for the player’s spaceship and another collection for the enemies. Another SpriteCollection could contain only those sprites that are close to the player’s ship to do faster collision detection.
BounceSprites Demo
Let’s take a look at the Sprites in action. This demo is included with the SDL.NET source. BounceSprite.cs is the sprite class. BounceSprites.cs is the demo app itself.
BounceSprite.cs
using System;
using System.Drawing;
using SdlDotNet.Core;
using SdlDotNet.Input;
using SdlDotNet.Graphics;
using SdlDotNet.Graphics.Sprites;
namespace SdlDotNetExamples.SmallDemos
{
/// <summary>
///
/// </summary>
public class BounceSprite : AnimatedSprite
{
#region Fields
Random rand = new Random();
//Move sprites 5 pixels per tick
private int dx = 5;
private int dy = 5;
private int dz;
//Sprites will be bounded by the screen edges minus
//their size so they will not go off the screen
private Rectangle bounds = new Rectangle();
#endregion Fields
#region Constructor
/// <summary>
///
/// </summary>
/// <param name="surfaces"></param>
/// <param name="coordinates"></param>
public BounceSprite(SurfaceCollection surfaces, Point coordinates)
: base(surfaces, coordinates)
{
if (surfaces == null)
{
throw new ArgumentNullException("surfaces");
}
//Sprites will be bounded by the screen edges minus
//their size so they will not go off the screen
this.bounds =
new Rectangle(0, 0, Video.Screen.Rectangle.Width -
(int)surfaces.Size.Width, Video.Screen.Rectangle.Height -
(int)surfaces.Size.Height);
//The sprite can be dragged
this.Animate = true;
this.AllowDrag = true;
}
#endregion Constructor
#region Event Update Methods
/// <summary>
/// Every tick will update the animation frame
/// </summary>
/// <param name="args"></param>
public override void Update(TickEventArgs args)
{
if (args == null)
{
throw new ArgumentNullException("args");
}
//Call the base method
base.Update(args);
//Change the sprite coordinates if the sprite is not being dragged
if (!this.BeingDragged)
{
this.X += dx;
this.Y += dy;
//this.Z += dz;
// Bounce off the left
if (this.X < bounds.Left)
{
this.X = bounds.Left;
this.Z = rand.Next(0, 41);
}
// Bounce off the top
if (this.Y < bounds.Top)
{
this.Y = bounds.Top;
this.Z = rand.Next(0, 41);
}
// Bounce off the bottom
if (this.Y > bounds.Bottom)
{
this.Y = bounds.Bottom;
this.Z = rand.Next(0, 41);
}
// Bounce off the right
if (this.X > bounds.Right)
{
this.X = bounds.Right;
this.Z = rand.Next(0, 41);
}
// Bounce off the left
if (this.Z < 0)
{
this.Z = 0;
}
// Bounce off the bottom
if (this.Z > 40)
{
this.Z = 40;
}
if (this.Z == 0)
{
dz = (Math.Abs(this.dz));
}
if (this.Z == 40)
{
dz = -1 * (Math.Abs(this.dz));
}
// Reverse the directions when the sprite hits an edge
if (this.X == bounds.Left)
{
dx = (Math.Abs(this.dx));
}
if (this.X == bounds.Right)
{
dx = -1 * (Math.Abs(this.dx));
}
if (this.Y == bounds.Top)
{
dy = (Math.Abs(this.dy));
}
if (this.Y == bounds.Bottom)
{
dy = -1 * (Math.Abs(this.dy));
}
//Console.WriteLine("Z: " + this.Z);
}
}
/// <summary>
/// If the mouse click hits a sprite,
/// then the sprite will be marked as 'being dragged'
/// </summary>
/// <param name="args"></param>
public override void Update(MouseButtonEventArgs args)
{
if (args == null)
{
throw new ArgumentNullException("args");
}
if (this.IntersectsWith(new Point(args.X, args.Y)))
{
// If we are being held down, pick up the marble
if (args.ButtonPressed)
{
if (args.Button == MouseButton.PrimaryButton)
{
this.BeingDragged = true;
this.Animate = false;
}
else
{
this.Kill();
}
}
else
{
this.BeingDragged = false;
this.Animate = true;
}
}
}
/// <summary>
/// If the sprite is picked up, this moved the sprite to follow
/// the mouse.
/// </summary>
public override void Update(MouseMotionEventArgs args)
{
if (args == null)
{
throw new ArgumentNullException("args");
}
if (!AllowDrag)
{
return;
}
// Move the window as appropriate
if (this.BeingDragged)
{
this.X += args.RelativeX;
this.Y += args.RelativeY;
}
}
#endregion Event Update Methods
#region IDisposable
private bool disposed;
/// <summary>
/// Destroys the surface object and frees its memory
/// </summary>
/// <param name="disposing">If ture, dispose unmanaged resources</param>
protected override void Dispose(bool disposing)
{
try
{
if (!this.disposed)
{
if (disposing)
{
}
this.disposed = true;
}
}
finally
{
base.Dispose(disposing);
}
}
#endregion IDisposable
}
}
BounceSprites.cs
using System;
using System.IO;
using System.Drawing;
using System.Collections.ObjectModel;
using System.Collections.Generic;
using SdlDotNet.Core;
using SdlDotNet.Graphics;
using SdlDotNet.Graphics.Sprites;
using SdlDotNet.Input;
namespace SdlDotNetExamples.SmallDemos
{
/// <summary>
/// Demo of Bouncing Balls using Sprites.
/// The Bouncesprites will respond to Tick Events by spinning.
/// You can click on each sprite and move them around the
/// screen as well (MouseButton and MouseMotion events).
/// </summary>
public class BounceSprites : IDisposable
{
#region Fields
private Surface screen; //video screen
private SpriteCollection master = new SpriteCollection(); //holds all sprites
private int width = 640; //screen width
private int height = 480; //screen height
private int maxBalls = 10; //number of balls to display
private Random rand = new Random(); //randomizer
string dataDirectory = "Data";
string filePath = Path.Combine("..", "..");
private Surface background;
#endregion Fields
#region EventHandler Methods
//Handles keyboard events.
// The 'Escape' and 'Q'keys will cause the app to exit
private void KeyboardDown(object sender, KeyboardEventArgs e)
{
if (e.Key == Key.Escape || e.Key == Key.Q)
{
Events.QuitApplication();
}
}
Collection<Rectangle> rects = new Collection<Rectangle>();
//A ticker is running to update the sprites constantly.
//This method will fill the screen with black to clear it of the sprites.
//Then it will Blit all of the sprites to the screen.
//Then it will refresh the screen and display it.
private void Tick(object sender, TickEventArgs args)
{
rects = screen.Blit(master);
screen.Update(rects);
screen.Erase(master, background);
}
private void Quit(object sender, QuitEventArgs e)
{
Events.QuitApplication();
}
#endregion EventHandler Methods
#region Methods
//Main program loop
private void Go()
{
//Set up screen
if (File.Exists(Path.Combine(dataDirectory, "background.png")))
{
filePath = "";
}
background = new Surface(Path.Combine(filePath, Path.Combine(dataDirectory, "background.png")));
Video.WindowIcon();
Video.WindowCaption = "SDL.NET - Bounce Sprites";
screen = Video.SetVideoMode(width, height);
screen.Blit(background);
screen.Update();
//This loads the various images (provided by Moonfire)
// into a SurfaceCollection for animation
SurfaceCollection marbleSurfaces = new SurfaceCollection();
marbleSurfaces.Add(new Surface(Path.Combine(filePath, Path.Combine(dataDirectory, "marble1.png"))), new Size(50, 50));
for (int i = 0; i < this.maxBalls; i++)
{
//Create a new Sprite at a random location on the screen
master.Add(new BounceSprite(marbleSurfaces,
new Point(rand.Next(screen.Rectangle.Left, screen.Rectangle.Right),
rand.Next(screen.Rectangle.Top, screen.Rectangle.Bottom))));
}
//The collection will respond to mouse button clicks, mouse movement and the ticker.
master.EnableMouseButtonEvent();
master.EnableMouseMotionEvent();
master.EnableTickEvent();
//These bind the events to the above methods.
Events.KeyboardDown +=
new EventHandler<KeyboardEventArgs>(this.KeyboardDown);
Events.Tick += new EventHandler<TickEventArgs>(this.Tick);
Events.Quit += new EventHandler<QuitEventArgs>(this.Quit);
//Start the event ticker
Events.Run();
}
/// <summary>
/// Entry point for App.
/// </summary>
[STAThread]
public static void Main()
{
BounceSprites bounce = new BounceSprites();
bounce.Go();
}
/// <summary>
/// Lesson Title
/// </summary>
public static string Title
{
get
{
return "BounceSprites: Bouncing balls";
}
}
#endregion Methods
#region IDisposable Members
private bool disposed;
/// <summary>
/// Destroy object
/// </summary>
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Destroy object
/// </summary>
public void Close()
{
Dispose();
}
/// <summary>
/// Destroy object
/// </summary>
~BounceSprites()
{
Dispose(false);
}
/// <summary>
///
/// </summary>
/// <param name="disposing"></param>
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
if (this.background != null)
{
this.background.Dispose();
this.background = null;
}
}
this.disposed = true;
}
}
#endregion
}
}
The demo app will create a 800x600 screen that features 10 rotating gray sprites. They will respond to Tick events by spinning. If a user clicks on one with a mouse, the marble will stop moving and be dragged along with the mouse (MouseButton and MouseMotion events).
Sprite surfaces were created by D.R.E. Moonfire (d.moonfire AT mfgames DOT com)

