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

Managed DirectX Tutorial Part 2 - Samus Running

Rate me:
Please Sign up or sign in to vote.
4.73/5 (31 votes)
31 Jan 20034 min read 329.4K   2K   82   48
Step-by-step tutorial on Managed DirectX, Part 2 - Sprite animation and Audio playback.

Sample Image - mdxtutorial2.jpg

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) :

C#
private void PlayAudio(string audiofile)
{
    // Create the audio class with specified audio file
    Audio audio = new Audio(audiofile)
    // Play Audio
    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)

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

C#
// Direct Draw variables
//DirectDraw Device used in all the application
private Device display;
//Front Surface
private Surface front = null;
//Back Buffer Surface
private Surface back = null;
//Surface that hold the Samus Sprites
private Surface samus = null;
//Constant of number of frames
private const int nFrames = 8;
//Array of Rectangle to hold the position of the frames
private Rectangle[] frames = new Rectangle[nFrames];
//Sprite Height
private int samus_height = 49;
//Sprite Height
private int samus_width = 35;
//Current frame displayed
private int currentframe = 0;
//Path to the samus sprite
private string samus_sprite = Application.StartupPath + "\\samussprite.bmp";

// Audio Playback variables
//Audio Playback class
private Audio music;
//String of the musicpath
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.

C#
private void InitAudioPlayback()
{
    // Create the Audio class with the first element of the playlist
    music = new Audio(m_playlist[0]);
    // Event to handle the ending of the music
    music.Ending += new EventHandler(this.OnMusicEnding);
    // Play the music
    music.Play();
}

FillPlaylist method

The FillPlaylist method lists the directory music and fills the m_playlist variable.

C#
private string[] FillPlaylist()
{
    // Get the number of song
    maxindex = Directory.GetFiles(musicpath).Length;
    // Create the playlist
    string[] playlist = new string[maxindex];
    // Fill the playlist
    playlist = Directory.GetFiles(musicpath);
    // Return
    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.

C#
private void OnMusicEnding(object sender, System.EventArgs e)
{
    // Check if the Audio class is created
    if(music != null)
    {
        // Increment the music idnex
        musicindex++;
        // Stop the current song played
        music.Stop();
        // If this is the song, return to the first song
        if(musicindex >= maxindex - 1)
        {
            musicindex = 0;
        }

        try
        {
            // Open the next song
            music.Open(m_playlist[musicindex]);
            // Reset the Ending event
            music.Ending -= new EventHandler(this.OnMusicEnding);
            music.Ending += new EventHandler(this.OnMusicEnding);
            // Play it
            music.Play();    
        }
        catch(Exception)
        {
            // If they are any problems, exit the application
            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.

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

C#
// Draw the title to the back buffer using source copy blit
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.

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

C#
private void InitDirectDraw()
{
    // Used to describe a Surface
    SurfaceDescription description = new SurfaceDescription();
    // Init the Device
    display = new Device();

    // Set the Cooperative Level and parent, 
    // Setted to Full Screen Exclusive to the form
    display.SetCooperativeLevel(this, 
        CooperativeLevelFlags.FullscreenExclusive);
    // Set the resolution and color depth used in full screen 
    //(640x480, 16 bit color)
    display.SetDisplayMode(640, 480, 16, 0, false);

    // Define the attributes for the front Surface
    description.SurfaceCaps.PrimarySurface = true;

    description.SurfaceCaps.Flip = true;
    description.SurfaceCaps.Complex = true;

    // Set the Back Buffer count
    description.BackBufferCount = 1;

    // Create the Surface with specifed description and device)
    front = new Surface(description, display);

    description.Clear();

    // A Caps is a set of attributes used by most of DirectX components
    SurfaceCaps caps = new SurfaceCaps();
    // Yes, we are using a back buffer
    caps.BackBuffer = true;

    // Associate the front buffer to back buffer with specified caps
    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));
    }

    // Set the fore color of the text
    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.

C#
private void DrawNextFrame()
{
    // If the front isn't create, ignore this function
    if (front == null)
    {
        return;
    }

    // If the form is minimized, ignore this function
    if(this.WindowState == FormWindowState.Minimized)
    {
        return;
    }
    try
    {
        back.ColorFill(Color.Black);
        // Draw the title to the back buffer using source copy blit
        back.DrawFast(320, 240, samus, 
                frames[currentframe], DrawFastFlags.DoNotWait);

        // Draw the text also to the back buffer using source copy blit
        back.DrawText(10, 30, helptext, false);
        back.DrawText(10, 50, "Playing : "+m_playlist[musicindex], false);

        // Doing a flip to transfer back buffer to the front, faster
        front.Flip(back, FlipFlags.DoNotWait);
    }
    catch(WasStillDrawingException)
    {
        return;
    }
    catch(SurfaceLostException)
    {
        // If we lost the surfaces, restore the surfaces
        RestoreSurfaces();
    }
}

RestoreSurfaces method

No changes for this method

C#
private void RestoreSurfaces()
{
    // Used to describe a Surface
    SurfaceDescription description = new SurfaceDescription();

    // Restore al the surface associed with the device
    display.RestoreAllSurfaces();

    // For the samus sprite, we need to dispose it first 
    // and then re-create it
    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.

C#
public Tutorial2()
{
    //
    // Required for Windows Form Designer support
    //
    InitializeComponent();

    m_playlist = FillPlaylist();
    InitDirectDraw();
    InitAudioPlayback();
    // Remove the cursor
    this.Cursor.Dispose();
    // Show the form if isn't already do
    this.Show();
    TimerSprite.Start();
    TimerSprite.Enabled = true;
    // The main loop
    while(Created)
    {
        DrawNextFrame();
        // Make sure that the application process the messages
        Application.DoEvents();
    }
}

The Closing event

The Closing event disposes the DirectX components used in the application.

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

C#
private void Tutorial2_KeyUp(object sender, 
                System.Windows.Forms.KeyEventArgs e)
{
    // If the user press Escape or Enter, the tutorial exits
    if(e.KeyCode == Keys.Escape || e.KeyCode == Keys.Enter)
        this.Close();
}

Main method

You need to make the same change as the first part.

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

  • First release of Part 2

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
Canada Canada
I'm currently in College(Cegep) in Computer Science.

I'm developping since 2 years starting with C and now i'm an active C# developper.



1998 - Got my PC

1999 - Make my first web page

2000 - Started ROM Translation

2001 - Learned programming with C, first program(Final Fantasy 1 Trainer)

2002 - Learned C#, got VS.NET, started my first big programming project(Aeru IRC)

2003 - Continue Aeru IRC, Learn Managed DirectX, go to CEGEP.

2004 - Dumped Windows as my main desktop(using Gentoo Linux), remake Contra on GBA in C, learn x86 and ARM7 assembler.
2005 - Gamefu (http://sf.net/projects/gamefu/)


My current knowlegde in computer language:
ARM7 assembler, C, C++, C#, PHP and Delphi.


My current active projects is a coding group called Pixel Coders found at http://www.pixel-coders.tk

Comments and Discussions

 
AnswerRe: Playing an Embedded Resource? Pin
sumo_guy16-May-03 1:44
sumo_guy16-May-03 1:44 
GeneralRe: Playing an Embedded Resource? Pin
Jeroen Landheer23-Aug-04 13:33
Jeroen Landheer23-Aug-04 13:33 
GeneralDirect3D additions Pin
Larsenal20-Mar-03 9:10
Larsenal20-Mar-03 9:10 
GeneralRe: Direct3D additions Pin
Shock The Dark Mage23-Mar-03 3:42
Shock The Dark Mage23-Mar-03 3:42 
GeneralRe: Direct3D additions Pin
Jared Bienz25-Apr-03 17:44
Jared Bienz25-Apr-03 17:44 
GeneralPlease Note: Pin
Brian Olej8-Mar-03 16:08
Brian Olej8-Mar-03 16:08 
Questiontransparent background? Pin
Brian Olej4-Mar-03 9:09
Brian Olej4-Mar-03 9:09 
AnswerRe: transparent background? Pin
Pyt Troll4-Mar-03 9:38
sussPyt Troll4-Mar-03 9:38 
Hi,

You need to create a ColorKey object and attach it to your Surface.
Then when you draw that surface, use the DrawFlags.KeySource flag.
Here is a code snippet.

C#
Surface m_surfSamus;
Surface m_surfBuffer;
.
.
.
ColorKey ck = new ColorKey();
// Set the transparent color to black
ck.ColorSpaceHighValue = ck.ColorSpaceLowValue = Color.Black.ToArgb ();
// Attach the ColorKey to the surface
m_SurfSamus.SetColorKey ( ColorKeyFlags.SourceDraw, ck );
.
.
.
// Draw the surface on the buffer using the ColorKey for
// transparency
m_buffer.Draw ( 
	destinationRect,
        m_SurfSamus,
	sourceRect,
	DrawFlags.Wait | DrawFlags.KeySource );


Pyt
GeneralRe: transparent background? Pin
Brian Olej5-Mar-03 8:56
Brian Olej5-Mar-03 8:56 
GeneralRe: transparent background? Pin
Anonymous17-Mar-03 6:40
Anonymous17-Mar-03 6:40 
GeneralRe: transparent background? Pin
Pyt17-Mar-03 12:44
Pyt17-Mar-03 12:44 
GeneralRe: transparent background? Pin
Anonymous22-Mar-03 0:39
Anonymous22-Mar-03 0:39 
GeneralRe: transparent background? Pin
jaybradley13-May-03 20:31
jaybradley13-May-03 20:31 
GeneralRe: transparent background? Pin
Anonymous13-Oct-03 8:39
Anonymous13-Oct-03 8:39 
GeneralRe: transparent background? Pin
yopet21-Jan-04 3:09
yopet21-Jan-04 3:09 
GeneralRe: transparent background? Pin
Mishan Pog3-May-05 6:36
sussMishan Pog3-May-05 6:36 
GeneralMetroid Sprites Pin
sumo_guy18-Feb-03 14:51
sumo_guy18-Feb-03 14:51 
GeneralRe: Metroid Sprites Pin
Shock The Dark Mage20-Feb-03 15:33
Shock The Dark Mage20-Feb-03 15:33 
GeneralGood Job! Pin
Rocky Moore2-Feb-03 0:06
Rocky Moore2-Feb-03 0:06 
GeneralRe: Good Job! Pin
Shock The Dark Mage3-Feb-03 16:13
Shock The Dark Mage3-Feb-03 16:13 
GeneralRe: Good Job! Pin
sumo_guy18-Feb-03 5:59
sumo_guy18-Feb-03 5:59 
GeneralRe: Good Job! Pin
Shock The Dark Mage20-Feb-03 15:37
Shock The Dark Mage20-Feb-03 15:37 

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.