The C-Unit Framework

  CU_MDX_Framework.zip (63.0 KiB, 4,314 hits)


  CUnitFramework.zip (101.1 KiB, 20,443 hits)

Extract the C-Unit Framework
inside the project (not solution) directory.

When working with your own projects, it is extremely helpful to put together a framework that you can reuse again and again in future projects. This saves both time and energy since you don’t have to write the same code that you have already written half a dozen times. Using the knowledge from the previous tutorials, I will introduce you to my C-Unit Framework, which we can use to easily create_ new applications.

The idea behind any framework is to hide the code that is common to every application using the framework. This includes window creation, DirectX initialization, fullscreen toggles, etc. Since all that code remains the same in every application, we shouldn’t have to fuss with it let alone see it. Instead, we will modify an application-specific class whose methods will be called by the framework whenever necessary.

To begin with, our framework will need to support 2 basic types of resource methods: resource creation methods and resource disposal methods. The resource creation and disposal methods will need to account for which memory pool the resource is created in. For our purposes, we will categorize a resource as being either a Pool.Managed resource or a Pool.Default resource. Managed resources are backed up by system memory and do not need to be recreated unless the device is destroyed. Default resources, on the other hand, are not backed up so default resources need to be recreated whenever the device is reset. Keeping all this in mind, we will write Managed resource creation and disposal methods (OnCreateDevice and OnDestroyDevice) and Default resource creation and disposal methods (OnResetDevice and OnLostDevice). We will also add functions to handle application initialization, frame updates and frame renders, key presses, etc. (I recommend reading MSDN’s page on managing resources before continuing).

The C-Unit Framework supports the use of stack-based states. This allows for the implementation of multiple game-states and/or screens. Each node of the stack will inherit from the abstract GameState class:

/// A game state. 
/// Contains a method for each stage of execution.

public abstract class GameState
{
public abstract void OnCreateDevice( Device device );
public abstract void OnResetDevice( Device device );
public abstract void OnLostDevice();
public abstract void OnDestroyDevice();
public abstract void OnUpdateFrame( Device device, float elapsedTime );
public abstract void OnRenderFrame( Device device, float elapsedTime );
public abstract void OnKeyboard( List keyState, char pressedChar, int pressedKey,
float elapsedTime );
public abstract void OnMouse( Point position, int xDelta, int yDelta, int zDelta,
bool[] buttons, float elapsedTime );
public abstract void OnControl( int controlID, object data );
public abstract bool DoneWithState
{
get;
}
}

The GameState class contains the resource methods described above as well as a method for each stage of execution. The parameters of each method will be supplied by the Framework, so all we need to do to implement our own state is create_ a new class that inherits from GameState and then implement all the methods. Each GameState instance will be stored on a stack maintained by the StateManager class:

using System;
using System.Windows.Forms;

using System.Drawing;
using System.Collections.Generic;
using Microsoft.DirectX.Direct3D;

namespace CUnit
{

public class StateManager
{

Stack m_stack;

///

Creates a new StateManager
public StateManager() Toggle

{

m_stack = new Stack();

}

///

Pushes a State onto the stack.
/// New State.
public void Push( GameState state ) Toggle

{

m_stack.Push( state );

}

///

Pops a State off the stack.
/// The State popped or null if the stack is empty.
public GameState Pop() Toggle

{

if ( m_stack.Count > 0 )
{

// Release resources before pop
m_stack.Peek().OnLostDevice();
m_stack.Peek().OnDestroyDevice();
return m_stack.Pop();

}
return null;

}

///

Peeks the top of the stack.
/// The State at the top of the Stack or null if the stack is empty.
public GameState Peek() Toggle

{

if ( m_stack.Count > 0 )
{

return m_stack.Peek();

}
return null;

}

///

Call when the Device is created.
/// D3D Device
public void OnCreateDevice( Device device ) Toggle

{

foreach ( GameState s in m_stack )
{

s.OnCreateDevice( device );

}

}

///

Call when the Device is reset.
/// D3D Device
public void OnResetDevice( Device device ) Toggle

{

foreach ( GameState s in m_stack )
{

s.OnResetDevice( device );

}

}

///

Call when the Device is lost.
public void OnLostDevice() Toggle

{

foreach ( GameState s in m_stack )
{

s.OnLostDevice();

}

}

///

Call when the Device is destroyed.
public void OnDestroyDevice() Toggle

{

foreach ( GameState s in m_stack )
{

s.OnDestroyDevice();

}

}

///

Updates the frame prior to rendering.
/// D3D Device
/// Time since last frame
public void OnUpdateFrame( Device device, float elapsedTime ) Toggle

{

if ( m_stack.Count > 0 )
{

m_stack.Peek().OnUpdateFrame( device, elapsedTime );
// Check if the State is finished
if ( m_stack.Peek().DoneWithState )
{

Pop();

}

}

}

///

Renders the current frame of the current state.
/// D3D Device
/// Time since last frame
public void OnRenderFrame( Device device, float elapsedTime ) Toggle

{

if ( m_stack.Count > 0 )
{

m_stack.Peek().OnRenderFrame( device, elapsedTime );

}

}

///

Keyboard handler.
/// List of pressed keys.
/// Character read from keyboard.
/// Keycode read from keyboard.
/// Time since last frame
public void OnKeyboard( List pressedKeys, char pressedChar, int pressedKey, float elapsedTime ) Toggle

{

if ( m_stack.Count > 0 )
{

m_stack.Peek().OnKeyboard( pressedKeys, pressedChar, pressedKey, elapsedTime );

}

}

///

Mouse handler.
/// Mouse position in client coordinates
/// X-axis delta.
/// Y-axis delta.
/// Wheel delta.
/// Mouse button state.
/// Time since last frame
public void OnMouse( Point position, int xDelta, int yDelta, int zDelta, bool[] buttons, float elapsedTime ) Toggle

{

if ( m_stack.Count > 0 )
{

m_stack.Peek().OnMouse( position, xDelta, yDelta, zDelta, buttons, elapsedTime );

}

}

///

GUI handler.
/// Control ID
/// Control data
public void OnControl( int controlID, object data ) Toggle

{

if ( m_stack.Count > 0 )
{

m_stack.Peek().OnControl( controlID, data );

}

}

}

}

The StateManager class manipulates a stack of GameStates and processes whatever GameState is on the top of the stack. For the resource manipulation methods, StateManager iterates through all the GameStates on the stack in order to keep all the resources on the stack valid.

Notice the GameState class has a DoneWithState property. This property is used to tell StateManager that it can pop the state off the stack (in StateManager.OnUpdateFrame). For example, if you have a menu system, when the user is down selecting items, you would set DoneWithState to true, and the menu would be popped off the stack. Of course, if you want to manually pop a state off the stack, you are free to do so. Just override the DoneWithState property and return false to prevent StateManager from popping the state automatically.

The main class of execution, GameApp, implements GameState and is the first GameState to be placed on the Framework’s stack:

namespace CUnit
{

/// Main application
public class GameApp : GameState
{

private Framework m_framework = null;
private const string Instructions = “Esc: Quit”;
private string m_instructionDisplay = “F1: View instructions”;
public enum ControlID { Fullscreen, Options };
private float m_fps = 0f;
private Gui.GuiManager m_gui = null;
private BitmapFont m_bFont = null;

///

Default constructor.
public GameApp( Framework framework ) Toggle

{

m_framework = framework;
m_framework.PushState( this );

}

///

Initialize application-specific resources, states here.
/// True on success, false on failure
private bool Initialize() Toggle

{

m_bFont = new BitmapFont( “Arial38.fnt”, “Arial38_00.tga” );
return true;

}

///


/// This event will be fired immediately after the Direct3D device has been
/// created, which will happen during application initialization. This is the best
/// location to create_ Pool.Managed resources since these resources need to be
/// reloaded whenever the device is destroyed. Resources created
/// here should be released in the OnDetroyDevice callback.
///

/// The Direct3D device
public override void OnCreateDevice( Device device ) Toggle

{

if ( m_gui != null )
{

m_gui.Clear();

}

m_gui = new Gui.GuiManager( “CUnit.xml”, new Gui.GuiManager.ControlDelegate( OnControl ) );
m_gui.CreateButton( (int)ControlID.Fullscreen, 0, new PointF( (float)BackBufferWidth – 120f, 10f ), new SizeF( 110f, 30f ), “Toggle Fullscreen”, 15f, new ColorValue( 0, 0, 0 ) );
m_gui.CreateButton( (int)ControlID.Options, 0, new PointF( (float)BackBufferWidth – 120f, 50f ), new SizeF( 110f, 30f ), “Options”, 15f, new ColorValue( 0, 0, 0 ) );
m_gui.OnCreateDevice( device );

if ( m_bFont != null )
{

m_bFont.OnCreateDevice( device );

}

}

///


/// This event will be fired immediately after the Direct3D device has been
/// reset, which will happen after a lost device scenario, a window resize, and a
/// fullscreen toggle. This is the best location to create_ Pool.Default resources
/// since these resources need to be reloaded whenever the device is reset. Resources
/// created here should be released in the OnLostDevice callback.
///

/// The Direct3D device
public override void OnResetDevice( Device device ) Toggle

{

if ( m_gui != null )
{

m_gui.OnResetDevice( device );

}
if ( m_bFont != null )
{

m_bFont.OnResetDevice( device );

}

// Set render states
device.RenderState.Lighting = false;
device.RenderState.FillMode = m_framework.FillMode;

// Keep buttons at upper right corner
m_gui.SetPosition( (int)ControlID.Fullscreen, new PointF( (float)BackBufferWidth – 120f, 10f ) );
m_gui.SetPosition( (int)ControlID.Options, new PointF( (float)BackBufferWidth – 120f, 50f ) );

}

///


/// This function will be called after the Direct3D device has
/// entered a lost state and before Device.Reset() is called. Resources created
/// in the OnResetDevice callback should be released here, which generally includes all
/// Pool.Default resources.
///

public override void OnLostDevice() Toggle

{

if ( m_gui != null )
{

m_gui.OnLostDevice();

}
if ( m_bFont != null )
{

m_bFont.OnLostDevice();

}

}

///


/// This callback function will be called immediately after the Direct3D device has
/// been destroyed, which generally happens as a result of application termination.
/// Resources created in the OnCreateDevice callback should be released here, which
/// generally includes all Pool.Managed resources.
///

public override void OnDestroyDevice() Toggle

{

if ( m_gui != null )
{

m_gui.OnDestroyDevice();

}
if ( m_bFont != null )
{

m_bFont.OnDestroyDevice();

}

}

///

Updates a frame prior to rendering.
/// The Direct3D device
/// Time elapsed since last frame
public override void OnUpdateFrame( Device device, float elapsedTime ) Toggle

{

}

///

Renders the current frame.
/// The Direct3D device
/// Time elapsed since last frame
public override void OnRenderFrame( Device device, float elapsedTime ) Toggle

{

device.Clear( ClearFlags.Target | ClearFlags.ZBuffer, Color.Black, 1.0f, 0 );
device.BeginScene();

m_gui.Render( device );

// Only need to rebuild the text when the FPS updates
if ( m_fps != m_framework.FPS )
{

m_fps = m_framework.FPS;
BuildText();

}
m_bFont.Render( device );

device.EndScene();
device.Present();

}

///

Builds all the BitmapFont strings.
private void BuildText() Toggle

{

m_bFont.ClearStrings();
m_bFont.AddString( “FPS: “ + m_fps.ToString( “f2” ), new RectangleF( 5f, 5f, (float)BackBufferWidth, 100f ), BitmapFont.Align.Left, 16f, ColorValue.FromColor( Color.Red ), true );
m_bFont.AddString( m_instructionDisplay, new RectangleF( 5f, 20f, 500f, 500f ), BitmapFont.Align.Left, 16f, ColorValue.FromColor( Color.White ), true );

}

///

Keyboard event handler
/// List of pressed keys
/// Unicode character read from Windows Form
/// Pressed key from Form used for repeatable keys
/// Time since last frame
public override void OnKeyboard( List pressedKeys, char pressedChar, int pressedKey, float elapsedTime ) Toggle

{

if ( m_gui.KeyBoardHandler( pressedKeys, pressedChar, pressedKey ) )
{

return;

}

foreach ( Keys k in pressedKeys )
{

switch ( k )
{

case Keys.Escape:

m_framework.Close();
break;

case Keys.F1:

m_framework.LockKey( Keys.F1 );
m_instructionDisplay = ( m_instructionDisplay == Instructions ) ? “F1: View instructions” : Instructions;
BuildText();
break;

}

}

}

///

Mouse event handler
/// Mouse position in client coordinates
/// X-axis delta
/// Y-axis delta
/// Mouse wheel delta
/// Mouse buttons
public override void OnMouse( Point position, int xDelta, int yDelta, int zDelta, bool[] buttons, float elapsedTime ) Toggle

{

if ( m_gui.MouseHandler( position, buttons, zDelta ) )
{

return;

}

}

///

Gui Control handler
/// Control ID
/// Control data
public override void OnControl( int controlID, object data ) Toggle

{

switch ( controlID )
{

case (int)ControlID.Fullscreen:

m_framework.ToggleFullscreen();
break;

case (int)ControlID.Options:

m_framework.PushState( new DeviceOptionsDialog( m_framework ) );
break;

}

}

///


/// Inherited method from GameState. Determines whether
/// the StateManager should pop this state.
///

public override bool DoneWithState Toggle

{

get { return false; }

}

///

The main entry point for the application.
[STAThread]
static void Main() Toggle

{

using ( Framework framework = new Framework() )
{

GameApp app = new GameApp( framework );

try
{

app.Initialize();
framework.Initialize( true, 640, 480, “Creating a Framework” );
framework.Run();

}
catch ( Exception e )
{

framework.Close();
System.Windows.Forms.MessageBox.Show( e.ToString(), “Error” );
return;

}

}

}

///

Gets the Device’s current back buffer height
public int BackBufferHeight Toggle

{

get { return m_framework.CurrentSettings.PresentParameters.BackBufferHeight; }

}

///

Gets the Device’s current back buffer width
public int BackBufferWidth Toggle

{

get { return m_framework.CurrentSettings.PresentParameters.BackBufferWidth; }

}

}

}

The GameApp class implements all the methods of GameState. For now, you can ignore the code that is already in the methods (the gui code and bitmap font code) since we will go over it in a later tutorial. I’m just showing you how to implement a GameState. Every time we create_ a new program, we just need to edit this file. Some code that should look familiar is in the OnRenderFrame method. It has the Device rendering methods that we learned about in the earlier tutorials. Remember, all of these methods will be called automatically by the Framework (with the exception of Main, or course) and the provided parameters should provide us with an easy means of creating new applications.

So what class maintains StateManager and supplies all the parameters? That would be the Framework class:

namespace CUnit
{

public partial class Framework : Form
{

private Graphics m_graphics = null;
private Timer m_timer = null;
private StateManager m_gameStates = null;
private FrameworkState m_state;
private DeviceSettings m_newSettings = null;

public Framework() Toggle

{

InitializeComponent();

// Set default values
m_state = new FrameworkState();
m_graphics = new Graphics();
m_timer = new Timer();
m_state.WindowLocation = new Point( 0, 0 );
m_state.WindowSize = new Size( 640, 480 );
m_state.FillMode = FillMode.Solid;
m_mousePosition = new System.Drawing.Point(
MousePosition.X – this.ClientRectangle.X, MousePosition.Y – this.ClientRectangle.Y );
this.Closed += new EventHandler( Framework_Closed );
m_gameStates = new StateManager();

}

///

Initialize the Framework.
/// True for window mode, false for fullscreen mode.
/// Window width
/// Window height
/// Text to display in the title bar.
/// True on success, false on failure
public void Initialize( bool windowed, int width, int height, string title ) Toggle

{

m_state.WindowSize = new Size( width, height );
this.ClientSize = m_state.WindowSize;
this.Text = title;
this.Show();

// Initialize Direct3D
m_graphics.Initialize( windowed, this, width, height );

// Set device events
m_graphics.Device.DeviceLost += new System.EventHandler( this.OnLostDevice );
m_graphics.Device.DeviceReset += new System.EventHandler( this.OnResetDevice );
m_graphics.Device.Disposing += new System.EventHandler( this.OnDestroyDevice );

// Create resources
OnCreateDevice();
OnResetDevice( this, null );

Pause( false, false );
m_state.Initialized = true;

}

///

Runs the main loop.
public void Run() Toggle

{

Application.Idle += new EventHandler( this.OnApplicationIdle );
Application.Run();

}

///

Called after the device is created. Create Pool.Managed resources here.
private void OnCreateDevice() Toggle

{

m_gameStates.OnCreateDevice( m_graphics.Device );

}

///

Called after the device is reset. Create Pool.Default resources here.
/// Sending object
/// Event arguments
private void OnResetDevice( object sender, EventArgs args ) Toggle

{

m_gameStates.OnResetDevice( m_graphics.Device );

}

///

Called when the device is lost. Release Pool.Default resources here.
/// Sending object
/// Event arguments
private void OnLostDevice( object sender, EventArgs args ) Toggle

{

m_gameStates.OnLostDevice();

}

///

Called after the device is disposed. Release Pool.Managed resources here.
/// Sending object
/// Event arguments
private void OnDestroyDevice( object sender, EventArgs args ) Toggle

{

m_gameStates.OnDestroyDevice();

}

///

Update the current frame.
private void OnUpdateFrame() Toggle

{

if ( m_graphics.Device == null || m_graphics.Device.IsDisposed )
{

return;

}

m_timer.Update();

// Process mouse event
if ( m_numPressedButtons > 0 || m_xDelta != 0 || m_yDelta != 0 || m_zDelta != 0 )
{

m_gameStates.OnMouse( m_mousePosition, m_xDelta, m_yDelta, m_zDelta, m_mouseButtons, m_timer.ElapsedTime );
m_xDelta = 0;
m_yDelta = 0;
m_zDelta = 0;
// Need this or else app won’t be able to process mouse releases
if ( NoButtonsDown )
{

m_numPressedButtons = 0;

}

}

// Process keyboard event
if ( m_pressedKeys.Count > 0 )
{

int oldLockCount = m_keyLock.Count;
m_gameStates.OnKeyboard( m_pressedKeys, m_pressedChar, m_pressedKey, m_timer.ElapsedTime );

// Application may be enumerating through pressedKeys, so we should
// remove newly locked keys after the application enumerates.
if ( oldLockCount != m_keyLock.Count )
{

foreach ( Keys k in m_keyLock )
{

m_pressedKeys.Remove( k );

}

}
m_pressedChar = char.MinValue;
m_pressedKey = 0;

}

m_gameStates.OnUpdateFrame( m_graphics.Device, m_timer.ElapsedTime );

// New settings from DeviceOptionsDisplay
if ( m_newSettings != null )
{

ChangeDevice( m_newSettings );
m_newSettings = null;

}

}

///

Render the current frame.
private void OnRenderFrame() Toggle

{

if ( ( this.WindowState == FormWindowState.Minimized ) ||
m_graphics.Device == null || m_graphics.Device.IsDisposed || m_state.FormClosing ||
m_state.DeviceLost )
{

return;

}

try
{

m_gameStates.OnRenderFrame( m_graphics.Device, m_timer.ElapsedTime );

}
catch ( DeviceLostException )
{

// The device is lost
System.Threading.Thread.Sleep( 50 );
if ( !m_state.DeviceLost )
{

Pause( true, true );
m_state.DeviceLost = true;

}

}

}

///

Resets the device with new settings or creates a new device.
/// New Device settings
public void ChangeDevice( DeviceSettings newSettings ) Toggle

{

Pause( true, true );
m_state.DisableResize = true;
DeviceSettings oldSettings = m_graphics.CurrentSettings;

if ( newSettings.PresentParameters.IsWindowed )
{

m_graphics.WindowedSettings = (DeviceSettings)newSettings.Clone();

}
else
{

m_graphics.FullscreenSettings = (DeviceSettings)newSettings.Clone();

}
m_graphics.Windowed = newSettings.PresentParameters.IsWindowed;

// Set new window style
if ( m_graphics.Windowed )
{

// Going to window mode
this.FormBorderStyle = FormBorderStyle.Sizable;
this.TopMost = false;

}
else
{

// Going to fullscreen mode
this.FormBorderStyle = FormBorderStyle.None;
// Save the current location/client size
m_state.WindowLocation = this.Location;
m_state.WindowSize = this.ClientSize;

}

// If AdapterOrdinal, DeviceType, and BehaviorFlags are the same, we can just do a Reset().
// If they’ve changed, we need to do a complete device tear down/rebuild.
if ( ( oldSettings.AdapterOrdinal == newSettings.AdapterOrdinal ) &&
( oldSettings.DeviceType == newSettings.DeviceType ) &&
( oldSettings.BehaviorFlags == newSettings.BehaviorFlags ) )
{

// We can just Reset the device
m_graphics.Reset();

}
else
{

// Have to dispose and recreate the device
m_graphics.ChangeDevice( newSettings );

// Set device events
m_graphics.Device.DeviceLost += new System.EventHandler( this.OnLostDevice );
m_graphics.Device.DeviceReset += new System.EventHandler( this.OnResetDevice );
m_graphics.Device.Disposing += new System.EventHandler( this.OnDestroyDevice );

// Create resources
OnCreateDevice();
OnResetDevice( this, null );

}

if ( m_graphics.Windowed )
{

// Going to window mode
// Restore the window size/location
this.Location = m_state.WindowLocation;
this.ClientSize = m_state.WindowSize;

}

m_state.DisableResize = false;
Pause( false, false );

}

///

Toggles between window and fullscreen mode
public void ToggleFullscreen() Toggle

{

// Flip windowed state
m_graphics.Windowed = !m_graphics.Windowed;

if ( m_graphics.Windowed )
{

ChangeDevice( m_graphics.WindowedSettings );

}
else
{

ChangeDevice( m_graphics.FullscreenSettings );

}

}

///

Pushes a new state onto the frameworks State stack.
/// New state
public void PushState( GameState state ) Toggle

{

m_gameStates.Push( state );

}

///

Pauses or unpauses rendering and the timer.
/// True to pause rendering, false to unpause rendering.
/// True to pause the timer, false to unpause the timer.
private void Pause( bool pauseRendering, bool pauseTimer ) Toggle

{

if ( pauseRendering )
{

m_state.RenderingPausedCount++;

}
else
{

m_state.RenderingPausedCount = Math.Max( 0, m_state.RenderingPausedCount – 1 );

}
if ( pauseTimer )
{

m_state.TimerPausedCount++;

}
else
{

m_state.TimerPausedCount = Math.Max( 0, m_state.TimerPausedCount – 1 );

}

m_state.RenderingPaused = ( m_state.RenderingPausedCount > 0 );
m_state.TimerPaused = ( m_state.TimerPausedCount > 0 );
if ( m_state.TimerPaused && m_timer != null )
{

m_timer.Stop();

}
else if ( !m_state.TimerPaused && m_timer != null )
{

m_timer.Start();

}

}

///

Window resize event. Reset the device, update_ and render the frame.
/// Event arguments
protected override void OnResize( EventArgs e ) Toggle

{

if ( m_state.DisableResize ||
( this.WindowState == FormWindowState.Minimized )
|| m_graphics == null || m_state.DeviceLost || !m_state.Initialized )
{

return;

}
base.OnResize( e );
try
{

m_graphics.Reset();

}
catch ( DeviceLostException )
{

if ( !m_state.DeviceLost )
{

m_state.DeviceLost = true;
Pause( true, true );

}
return;

}
OnUpdateFrame();
OnRenderFrame();

}

///

Application idle event. Updates and renders frames.
/// Sending object
/// Event arguments
private void OnApplicationIdle( object sender, EventArgs e ) Toggle

{

if ( m_graphics.Device == null )
{

return;

}
while ( AppStillIdle )
{

if ( !m_graphics.Device.IsDisposed && !m_state.FormClosing && !m_state.DeviceLost &&
this.WindowState != FormWindowState.Minimized && m_state.Initialized )
{

OnUpdateFrame();
OnRenderFrame();

}
else if ( m_state.DeviceLost )
{

RegainLostDevice();

}

}

}

///

Tries to regain a lost device.
private void RegainLostDevice() Toggle

{

// Check for lost device
if ( !m_graphics.Device.CheckCooperativeLevel() )
{

ResultCode code = m_graphics.Device.CheckCooperativeLevelResult();
if ( code == ResultCode.DeviceLost )
{

// The device has been lost but cannot be reset at this time.
// So wait until it can be reset.
System.Threading.Thread.Sleep( 50 );
return;

}
try
{

m_state.DisableResize = true;
m_graphics.Reset();
m_state.DeviceLost = false;
m_state.DisableResize = false;
Pause( false, false );

}
catch ( DeviceLostException )
{

// The device was lost again, so continue waiting until it can be reset.
System.Threading.Thread.Sleep( 50 );

}

}

}

///

Called when the Form is closed
/// Sending object
/// Event arguments
private void Framework_Closed( object sender, EventArgs e ) Toggle

{

m_state.FormClosing = true;
Application.Exit();

}

///

Returns whether the application is currently idle.
private bool AppStillIdle Toggle

{

get
{

NativeMethods.Message msg;
return !NativeMethods.PeekMessage( out msg, IntPtr.Zero, 0, 0, 0 );

}

}

///

Gets the Framework’s Graphics object
public Graphics Graphics Toggle

{

get { return m_graphics; }

}

///

Gets and Sets new settings for the Device
public DeviceSettings NewSettings Toggle

{

get { return m_newSettings; }
set { m_newSettings = value; }

}

///

Returns the size of the current backbuffer.
public Size DisplaySize Toggle

{

get
{

if ( m_graphics != null )
{

if ( m_graphics.Windowed )
{

return this.ClientSize;

}
else
{

return m_graphics.FullscreenSize;

}

}
return this.ClientSize;

}

}

///

Gets and sets the fillmode.
public FillMode FillMode Toggle

{

get
{

return m_state.FillMode;

}
set
{

m_state.FillMode = value;
if ( m_graphics.Device != null )
{

m_graphics.Device.RenderState.FillMode = value;

}

}

}

///

Gets the framerate.
public float FPS Toggle

{

get { return m_timer.FPS; }

}

///

Gets the current settings of the device.
public DeviceSettings CurrentSettings Toggle

{

get
{

if ( m_graphics != null )
{

return m_graphics.CurrentSettings;

}
return null;

}

}

}

}

The Framework class is the main workhorse of the C-Unit Framework. It maintains a StateManager instance and supplies it with the parameters used to call all the methods of GameState. In the Framework class, you will see methods that look familiar (OnCreateDevice, OnLostDevice, etc.), methods that look foreign, and methods that are self-explanatory. For all the code that looks foreign, such as the timer code, lost device code, graphics code, and device settings code, you can ignore for now since we’ll be going over it in later tutorials.

The Framework class maintains the execution flow of the application and contains Tom Miller’s render loop that we created in the first tutorial. You can probably understand the code by reading through it so here is a rough diagram of the Framework’s execution:

Flow

The C-Unit Framework also provides all the mouse and keyboard input data you might need to use through a partial class of Framework:

namespace CUnit
{

partial class Framework
{

// Keyboard
private List m_pressedKeys = new List();
private List m_keyLock = new List();
private char m_pressedChar = char.MinValue;
private int m_pressedKey = 0;

// Mouse
private System.Drawing.Point m_mousePosition;
private int m_xDelta = 0;
private int m_yDelta = 0;
private int m_zDelta = 0;
private bool[] m_mouseButtons = new bool[3];
private int m_numPressedButtons = 0;

///

Mouse move event.
/// Event arguments
protected override void OnMouseMove( MouseEventArgs e ) Toggle

{

m_xDelta = m_mousePosition.X – e.Location.X;
m_yDelta = m_mousePosition.Y – e.Location.Y;
m_mousePosition = e.Location;

}

///

Mouse wheel event.
/// Event arguments
protected override void OnMouseWheel( MouseEventArgs e ) Toggle

{

m_zDelta = e.Delta;

}

///

Mouse down event.
/// Event arguments
protected override void OnMouseDown( MouseEventArgs e ) Toggle

{

m_numPressedButtons++;
switch ( e.Button )
{

case MouseButtons.Left:

m_mouseButtons[0] = true;
break;

case MouseButtons.Middle:

m_mouseButtons[1] = true;
break;

case MouseButtons.Right:

m_mouseButtons[2] = true;
break;

}

}

///

Mouse up event.
/// Event arguments
protected override void OnMouseUp( MouseEventArgs e ) Toggle

{

switch ( e.Button )
{

case MouseButtons.Left:

m_mouseButtons[0] = false;
break;

case MouseButtons.Middle:

m_mouseButtons[1] = false;
break;

case MouseButtons.Right:

m_mouseButtons[2] = false;
break;

}

}

///

Returns true if the main mouse buttons are all down.
private bool NoButtonsDown Toggle

{

get { return m_mouseButtons[0] == m_mouseButtons[1] == m_mouseButtons[2] == false; }

}

///

Key down event.
/// Event arguments
protected override void OnKeyDown( KeyEventArgs e ) Toggle

{

if ( !m_pressedKeys.Contains( e.KeyCode ) && !m_keyLock.Contains( e.KeyCode ) )
{

m_pressedKeys.Add( e.KeyCode );

}
// Store key value for repeatable non-character keys in GUI EditBox
// such as backspace, arrows, and delete
m_pressedKey = e.KeyValue;

}

///

Key press event.
/// Event arguments
protected override void OnKeyPress( KeyPressEventArgs e ) Toggle

{

// OnKeyPress lets us grab character keys
m_pressedChar = e.KeyChar;

}

///

Key up event.
/// Event arguments
protected override void OnKeyUp( KeyEventArgs e ) Toggle

{

m_pressedKeys.Remove( e.KeyCode );
m_keyLock.Remove( e.KeyCode );

}

///

Locks a key so it is only read once per key down.
/// Key to lock
public void LockKey( Keys key ) Toggle

{

m_keyLock.Add( key );

}

}

}

Using the Windows Forms input events, I simply store all the input data in some simple packages to be used by the GameStates. Most of this code is pretty straight forward so I shouldn’t need to explain much. The only extra feature is the LockKey method. The LockKey method allows you to lock a key so it is only processed once per key down. This is useful because in many games, when we hit a key, we only want an action to execute once.

In the Framework class, there is an instance of FrameworkState. FrameworkState just holds some simple state information about the Framework:

namespace CUnit
{

public struct FrameworkState
{

public bool Initialized;
public bool DeviceLost;
public bool FormClosing;
public bool DisableResize;
public bool DeviceOptionsShowing;
public bool RenderingPaused;
public int RenderingPausedCount;
public int TimerPausedCount;
public bool TimerPaused;
public Point WindowLocation;
public Size WindowSize;
public FillMode FillMode;

}

}

The FrameworkState struct is basically a container for a bunch of state variables. Putting them in a struct just makes the Framework class less cluttered and give me some nice use for Intellisense 🙂

There are two different ways we can use the stack maintained by StateManager. The first way is to call Framework.PushState, which will add a GameState onto the Framework’s stack as shown in this code snippet from GameApp:

/// Default constructor.
public GameApp( Framework framework )
{

m_framework = framework;
m_framework.PushState( this );

}

///

Gui Control handler
/// Control ID
/// Control data
public override void OnControl( int controlID, object data )
{

switch ( controlID )
{

case (int)ControlID.Options:

m_framework.PushState( new DeviceOptionsDialog( m_framework ) );
break;

}

}

In the above code, we’re pushing the main state, GameApp, onto the Framework’s stack in the GameApp constructor. Actually, this initial push is required in order for the Framework to have anything on its stack. You can also see that in OnControl, which is called when we interact with the C-Unit Framework’s GUI, we push the DeviceOptionsDialog onto the stack. The DeviceOptionsDialog is used to configure the Direct3D Device, but we’ll learn how to do that later. When the DeviceOptionsDialog is pushed on the stack, all processing goes to the DeviceOptionsDialog class, which inherits from GameState. When we’re done with the DeviceOptionsDialog, it is popped from the stack and execution returns to GameApp, which is the next node on the stack.

The other way to use the stack in StateManager is to give the GameApp class its own instance of StateManager. This allows you to manually maintain your own application’s game state stack. You would just need to put StateManager’s method calls in the corresponding methods of GameApp.

Now that we have a Framework to build applications with, we can continue on with the Managed DirectX learning madness.