
Introduction
Welcome to the second part of my tutorial on Managed DirectX, included with DirectX 9 SDK. Most C# developers were waiting for this release. Before Managed DirectX, C# developers were using DirectX via COM Interop of DirectX 7 or 8 VB component. The new Managed DirectX components offer best performance and easier programming over DirectX COM Interop. This tutorial is for newcomers in DirectX development, like me, or for people who were using COM Interop for DirectX development. In this tutorial, we will make a clone of Super Metroid game called Managed Metroid. We will try to use all the components of Managed DirectX (DirectDraw
, DirectSound
, DirectInput
, Direct3D
, DirectPlay
and AudioVideoPlayback
).
In part 2, we will continue with sprite animation and audio playback with AudioVideoPlayback
namespace.
Requirements
These articles require some knowledge of C#. Also a basic game development background would be useful.
In this article, I assume that you have read the first part of the tutorial found at http://www.codeproject.com/cs/media/mdx_tutorial1.asp[^].
Software requirements:
- Microsoft Windows NT4 SP6, Windows 2000, Windows XP Pro (for compiling only)
- Visual C# .NET or Visual Studio .NET
- DirectX 9 SDK Full or only C# Part (available from Microsoft)
- Image editing tool (optional but useful)
Music playback
With Managed DirectX, if you want a quick way to playback music in your application then you're served. The AudioVideoPlayback
namespace is a wrapper of the DirectShow API found in C++. So, you can play any type of audio and video unless that you have the codec on your computer.
We implant Audio Playback in a code like this (It's just a sample one, this is not in the demo project) :
private void PlayAudio(string audiofile)
{
Audio audio = new Audio(audiofile)
audio.Play();
}
Adding namespace
Here are the references you need to add to the project for this tutorial (in the code and in References folder in solution)
using System.IO;
using Microsoft.DirectX;
using Microsoft.DirectX.AudioVideoPlayback;
using Microsoft.DirectX.DirectDraw;
Adding DirectX variables
First of all, we declare the DirectDraw device. This time, we also add a constant that hold the number of frames, an array of Rectangle
that holds each frame of the animation and finally we add the sprite height and width in int
.
After that, we declare the Audio playback variables. First of all, the Audio
class needed to play the music. The m_playlist
variable is an array of strings holding the music listed in directory music.
The Timer
is used to animate the sprite by incrementing the currentframe
variable. Finally, the helptext
string stores the help text drawn on the screen.
private Device display;
private Surface front = null;
private Surface back = null;
private Surface samus = null;
private const int nFrames = 8;
private Rectangle[] frames = new Rectangle[nFrames];
private int samus_height = 49;
private int samus_width = 35;
private int currentframe = 0;
private string samus_sprite = Application.StartupPath + "\\samussprite.bmp";
private Audio music;
private string musicpath = Application.StartupPath + "\\music\\";
//The playlist initialized later
private string[] m_playlist = null;
//The maxindex, store the upper bounds of the playlist
private int maxindex = 0;
//Current index of the music played
private int musicindex = 0;
// Misc variables
//Timer used to cadence the sprite animation
private System.Windows.Forms.Timer TimerSprite;
//Help Text
private string helptext =
"Managed DirectX Tutorial 2 - Press Enter or Escape to exit";
Initialize audio playback
Initializing audio playback is very simple. Create the Audio
class with an initial music file, then add the OnMusicEnding
event. Finally, just play the music.
private void InitAudioPlayback()
{
music = new Audio(m_playlist[0]);
music.Ending += new EventHandler(this.OnMusicEnding);
music.Play();
}
FillPlaylist method
The FillPlaylist
method lists the directory music and fills the m_playlist
variable.
private string[] FillPlaylist()
{
maxindex = Directory.GetFiles(musicpath).Length;
string[] playlist = new string[maxindex];
playlist = Directory.GetFiles(musicpath);
return playlist;
}
OnMusicEnding event
This function opens the next song in the play list. Instead of creating a new instance of the Audio
class, we use the method Open
to play the next song.
private void OnMusicEnding(object sender, System.EventArgs e)
{
if(music != null)
{
musicindex++;
music.Stop();
if(musicindex >= maxindex - 1)
{
musicindex = 0;
}
try
{
music.Open(m_playlist[musicindex]);
music.Ending -= new EventHandler(this.OnMusicEnding);
music.Ending += new EventHandler(this.OnMusicEnding);
music.Play();
}
catch(Exception)
{
this.Close();
}
}
}
Sprite animation
My method to animate a sprite is very simple. Here is the image I will animate:

With Jasc Animation Shop and Paint Shop Pro, I pasted all the frames of the animation into a single image:

So how do I select each frame in my application? We pre-compute the area of each frame and we store it in an array of Rectangle
.
Each Rectangle
has the size of each frame of the animation shown below. The Point
locates the top-left point of the current frame. Imagine this like a selection in your favorite image editing program.
for(int i=0; i< nFrames; i++)
{
Point pt = new Point(samus_width * i, 0);
frames[i] = new Rectangle(pt, new Size(samus_width, samus_height));
}
But now, how do I draw each frame in my Draw
method? The Draw
and DrawFast
methods of DirectDrawDevice
can specify a region of the surface to draw with a Rectangle
.
back.DrawFast(320, 240, samus, frames[currentframe],
DrawFastFlags.DoNotWait);
Finally, we add a Windows Timer
to our application. The Timer
is used to set the tick of the animation.
private void TimerSprite_Tick(object sender, System.EventArgs e)
{
if(currentframe >= nFrames - 1)
{
currentframe = 0;
}
else
{
currentframe++;
}
}
DirectDraw initialization
Now too much code has changed here. We added the pre-compute of the animation frames.
private void InitDirectDraw()
{
SurfaceDescription description = new SurfaceDescription();
display = new Device();
display.SetCooperativeLevel(this,
CooperativeLevelFlags.FullscreenExclusive);
display.SetDisplayMode(640, 480, 16, 0, false);
description.SurfaceCaps.PrimarySurface = true;
description.SurfaceCaps.Flip = true;
description.SurfaceCaps.Complex = true;
description.BackBufferCount = 1;
front = new Surface(description, display);
description.Clear();
SurfaceCaps caps = new SurfaceCaps();
caps.BackBuffer = true;
back = front.GetAttachedSurface(caps);
description.Clear();
samus = new Surface(samus_sprite, description, display);
for(int i=0; i< nFrames; i++)
{
Point pt = new Point(samus_width * i, 0);
frames[i] = new Rectangle(pt, new Size(samus_width, samus_height));
}
back.ForeColor = Color.White;
}
DrawNextFrame method
The Draw
method has changed name. It's now DrawNextFrame
. We added the draw of the current frame. The current song played is also written on the screen.
private void DrawNextFrame()
{
if (front == null)
{
return;
}
if(this.WindowState == FormWindowState.Minimized)
{
return;
}
try
{
back.ColorFill(Color.Black);
back.DrawFast(320, 240, samus,
frames[currentframe], DrawFastFlags.DoNotWait);
back.DrawText(10, 30, helptext, false);
back.DrawText(10, 50, "Playing : "+m_playlist[musicindex], false);
front.Flip(back, FlipFlags.DoNotWait);
}
catch(WasStillDrawingException)
{
return;
}
catch(SurfaceLostException)
{
RestoreSurfaces();
}
}
RestoreSurfaces method
No changes for this method
private void RestoreSurfaces()
{
SurfaceDescription description = new SurfaceDescription();
display.RestoreAllSurfaces();
samus.Dispose();
samus = null;
samus = new Surface(samus_sprite, description, display);
return;
}
Constructor
In the constructor, we add the fill of the play list and we add the start of the timer.
public Tutorial2()
{
InitializeComponent();
m_playlist = FillPlaylist();
InitDirectDraw();
InitAudioPlayback();
this.Cursor.Dispose();
this.Show();
TimerSprite.Start();
TimerSprite.Enabled = true;
while(Created)
{
DrawNextFrame();
Application.DoEvents();
}
}
The Closing event
The Closing
event disposes the DirectX components used in the application.
private void Tutorial2_Closing(object sender,
System.ComponentModel.CancelEventArgs e)
{
if(display != null)
{
display.RestoreDisplayMode();
display.SetCooperativeLevel(this, CooperativeLevelFlags.Normal);
display.Dispose();
}
if(music != null)
{
if(music.Playing)
{
music.Stop();
}
music.Dispose();
}
}
The KeyUp event
The KeyUp
event is the same as in Part 1 of this tutorial.
private void Tutorial2_KeyUp(object sender,
System.Windows.Forms.KeyEventArgs e)
{
if(e.KeyCode == Keys.Escape || e.KeyCode == Keys.Enter)
this.Close();
}
Main method
You need to make the same change as the first part.
[STAThread]
static void Main()
{
Tutorial2 app = new Tutorial2();
Application.Exit();
}
Conclusion
In this tutorial, we learned how to animate a sprite using DirectDraw
and play background music with Managed DirectX. The next tutorial will be an enhancement of Part 2 with DirectInput
. If you have any comments, suggestions, code corrections, please make me a remark at the bottom of the article.
Known issues
- The demo will exit by itself because the music failed to load.
- When the demo exits, an error message box appears with a .NET Broadcast Event.
History
Update 1.0