
This chapter describes a test application for DirectX .NET in C#. It uses many of the DirectX .NET classes to implement an application very similar to that built in the DirectX tutorial.
The application was initially built using the Visual Studio form editor. A new form, called Form1, was created, with its border style set to 'fixed single' and its title set to "DXTest".
Now we add the namespaces which we will use in this program at the top:
using System; using System.Drawing; using System.Collections; using System.ComponentModel; using System.Windows.Forms; using Sunlight.DirectX.Graphics; using Sunlight.DirectX.Input; using Sunlight.DirectX.SoundMusic;
The form base class was then changed to Sunlight.IdleForm, to allow us to use the idle-time processing.
We add a number of fields, to support the graphics, audio and input systems, which you can see in the code.
The first thing that occurs in a C# program is that a static Main method is called. This is simply a case of creating the form and letting it run:
static void Main() { Form1 form = new Form1(); form.Run(); }
DirectX initialisation should be performed in the Init event fired by IdleForm. We also would like to implement idle-time processing, so we define two event handlers for these events:
public Form1() { // // Required for Windows Form Designer support // InitializeComponent(); // // TODO: Add any constructor code after InitializeComponent call // Init += new EventHandler(OnInit); Idle += new EventHandler(OnIdle); }
The bulk of the initialisation takes place in the OnInit method. First, the Device object is built (but not created; this will happen at the end of OnInit). This object is built first to allow the DirectInput object to reference it.
protected void OnInit(object sender, EventArgs e) { // Initialise the DirectX Graphics device. device = new Device(d3d); device.ParentWindow = this; device.Windowed = true;
Now, we build the DirectInput object, together with its associated action map.
// Initialise the DirectInput object, including the action map. di.ActionMapName = "DXTest"; di.AppID = new Guid("{CA761232-ED42-11CE-BACD-00AA0057B223}"); di.Genre = Genres.ARCADE_SIDE2SIDE; di.Actions.Add(new ActionMap.Entry(0, Actions.AXIS_ARCADES_LATERAL, "Left/Right")); di.Actions.Add(new ActionMap.Entry(1, Actions.KEYBOARD_LEFT, "Left")); di.Actions.Add(new ActionMap.Entry(2, Actions.KEYBOARD_RIGHT, "Right")); di.Actions.Add(new ActionMap.Entry(3, Actions.KEYBOARD_ESCAPE, "Quit")); di.Actions.Add(new ActionMap.Entry(4, Actions.KEYBOARD_P, "Pause")); di.Actions.Add(new ActionMap.Entry(5, Actions.KEYBOARD_F1, "Help")); di.Actions.Add(new ActionMap.Entry(6, Actions.KEYBOARD_S, "Full Screen On/Off")); di.Action += new DirectInput.ActionEventHandler(OnInputAction); di.ParentWindow = this; di.Direct3DDevice = device;
The GUID was generated using GUIDGEN, part of the Platform SDK. You should use a different one for your applications. We have defined the genre as a side-to-side arcade game, which accurately describes the set of actions available for this application. Next, the action map is built up. Finally, we add an event handler for DirectInput action events.
The next step is to initialise the music system. First, we must initialise a DirectMusic object:
// Initialise the DirectMusic object. dm.ParentWindow = this; dm.Create();
Now, we load a MIDI file in as the background music. We trap the Finished event so that we can make the music repeat.
// Load the background music. This has an event handler to allow it to repeat. soundMusic.DirectMusicObject = dm; soundMusic.Filename = "passport.mid"; soundMusic.Finished += new EventHandler(OnMusicFinished);
We now want to build the sprite objects. First, we need a SpriteManager:
// Initialise the sprite manager.
manager.DeviceObject = device;
Next, we need to initialise the textures the sprites will reference. The textures will be automatically loaded when the device is created.
// Initialise the textures. texBackground.DeviceObject = device; texBackground.Filename = "a.png"; texSprites.DeviceObject = device; texSprites.Filename = "sprites.png";
Now we can build the sprites.
// Build the sprites. spriteBackground = new Sprite(manager, new Rectangle(0, 0, 640, 480), texBackground, new Point(0, 0)); spriteFixed = new Sprite(manager, new Rectangle(288, 208, 64, 64), texSprites, new Point(128, 0)); spriteMoving = new Sprite(manager, new Rectangle(288, 100, 64, 64), texSprites, new Point(0, 0)); spritePlayer = new Sprite(manager, new Rectangle(288, 150, 64, 64), texSprites, new Point(64, 0));
Finally, we can create the Device object.
device.Create();
We will now test the DirectInput configuration UI, just as before.
di.Configure();
Finally, before the application begins, we will start the music playing:
soundMusic.Play();
When the music finishes, we simply want to repeat the same track over again:
private void OnMusicFinished(object sender, System.EventArgs e) { // Continuous repeat soundMusic.Play(); }
Input handling is almost identical to that in the DirectX tutorial, and is very simple.
void OnInputAction(DirectInput sender, DirectInputEventArgs e) { switch (e.ID) { case 0: if (e.AxisPosition < -20) { bLeft = true; bRight = false; } else if (e.AxisPosition > 20) { bRight = true; bLeft = false; } else { bLeft = bRight = false; } break; case 1: bLeft = e.ButtonPressed; break; case 2: bRight = e.ButtonPressed; break; case 3: if (e.ButtonPressed) bQuit = true; break; case 4: if (e.ButtonPressed) bPaused = !bPaused; break; case 5: if (e.ButtonPressed) bShowHelp = true; break; case 6: if (e.ButtonPressed) bModeSwitch = true; break; default: break; } }
The real meat of DXTest is in OnIdle, where all drawing occurs, and the program reacts to inputs.
protected void OnIdle(object sender, EventArgs e) { if (!device.IsCreated || device.Paused) return;
The first thing to do is to check the input for any changes.
di.Check();
We can now handle the various inputs. Closing the application is simply a case of closing the form:
if (bQuit) { Close(); return; }
Changing modes is equally simple: changing the Windowed flag indicates that a mode switch should occur, and Device.Reset performs the switch.
if (bModeSwitch) { device.Windowed = !device.Windowed; device.Reset(); bModeSwitch = false; }
Help is performed by displaying the DirectInput configuration UI in display-only mode.
if (bShowHelp) { bShowHelp = false; di.Display(); }
The sprites are moved around if the device is not paused: one by the input, one automatically.
if (!bPaused) { // Move a sprite around with the input. if (bLeft) xPlayer--; if (bRight) xPlayer++; spritePlayer.Left = 288 + xPlayer; // Move a sprite around automatically. xAutomaticSprite++; if (xAutomaticSprite > 100) xAutomaticSprite = -100; spriteMoving.Left = 288 + xAutomaticSprite; }
Now the sprites are drawn in the appropriate order (back to front):
spriteBackground.Draw();
spriteFixed.Draw();
spriteMoving.Draw();
spritePlayer.Draw();
Finally, the actual drawing is performed by SpriteManager.FinishDraw. A scene is first begun, the drawing occurs, then the scene is ended and a flip is performed to transfer the contents of the back buffer to the display.
device.BeginScene();
manager.FinishDraw();
device.EndScene();
device.Flip();
}
If you are using Visual Studio, you should be able to load the project provided and build it. You may have to add a reference to your compiled Sunlight.DirectX.dll class library.
If you are building from the command line, enter the following command line:
csc /out:DXTest.exe /r:Sunlight.DirectX.dll *.cs
Make sure the media files are in the directory from which you run the resulting assembly.
That's it. You should be able to run this against the assembly formed from the C++ .NET source code presented in the first few chapters, and see a working DirectX application - with graphics, sound and input - in under 200 lines of written code.