Drawing Option Screens


The next user interface component to address is the option screen. Depending on the complexity of a game, it is possible to have multiple option screens that are accessed from a primary option screen. This is where players determine what kind of game they wish to play or if they want to exit from the game. The requirements for the option screens will drive the complexity of the controls used on the screens anywhere from simple buttons all the way up to controls reminiscent of MFC dialog boxes.

Building Blocks for the Option Screen

For our initial option screen, we will use a button-style interface. To support this, we will define a small class called ImageButton that will hold our button data.

To add a little flash to our buttons, we will define them as having three possible states: Off , Hover , and On . The Off state will have a basic image of the button. The Hover state will show the button highlighted and will be displayed whenever the mouse cursor is over the button. This helps reinforce for players that they are pointing at a button. The On state can be used for items that they can toggle on and off (e.g., an image with a checked check box). We also need to indicate where on the screen the button should be displayed and potentially a method that should be called when the button is selected.

The declaration of the ImageButton class shown in Listing 2 “9 takes these requirements into account.

Listing 2.9: ImageButton Declaration
start example
 public delegate void ButtonFunction( );  public class ImageButton : IDisposable  {     private int                  m_X          = 0     private int                  m_Y          = 0;     private Image                m_OffImage   = null;     private Image                m_OnImage    = null;     private Image                m_HoverImage = null;     private bool                 m_bOn        = false;     private bool                 m_bHoverOn   = false;     private ButtonFunction       m_Function   = null;     private Rectangle            m_Rect;     private VertexBuffer         m_vb =       null;     private CustomVertex.TransformedTextured[] data; 
end example
 

The Rectangle value within the class holds the size of the button. One assumption that is made about the three images of the button is that all three are the same size. If we mistakenly initialize the class with different- size images, we will have problems that range from only portions of the image being drawn to assertions generated because we are trying to draw a rectangle that is bigger than the image.

Note

I recommend that you develop the image for the Off button and then use it as the basis for creating the artwork for the other two.

The constructor for the ImageButton class is a little more involved than the other classes we have dealt with so far. This constructor needs six arguments: the X and Y values for positioning the button on the screen, the filenames for the three images, and the delegate function to be called when the button is selected. In code that should look familiar from the SplashScreen class, we also create a vertex buffer with data to draw the button on the screen as a textured rectangle (see Listing 2 “10).

Listing 2.10: ImageButton Constructor
start example
 public ImageButton(int nX, int nY, string sOffFilename, string sOnFilename ,        string sHoverFilename, ButtonFunction pFunc )  {     m_X = nX;     m_Y = nY;     m_OffImage    = new Image( sOffFilename );     m_OnImage     = new Image( sOnFilename );     m_HoverImage  = new Image( sHoverFilename );     m_Rect        = m_OffImage.GetRect();     m_Rect.X += m_X;     m_Rect.Y += m_Y;     m_Function = pFunc;     m_vb = new VertexBuffer( typeof(CustomVertex.TransformedTextured), 4,        CGameEngine.Device3D, Usage.WriteOnly,        CustomVertex.TransformedTextured.Format, Pool.Default );  } 
end example
 

The Render and Dispose methods are shown in Listing 2 “11. The Render method works very much like the method used in the splash and option screens. The difference is in the coordinates used in setting up the vertex buffer. Instead of filling the screen, the two triangles that form the button fill the rectangle position assigned to the button. The Dispose method simply calls the Dispose method for each of the button images.

Listing 2.11: ImageButton Render and Dispose Methods
start example
 public void Render()  {     try     {        data = new CustomVertex.TransformedTextured[4];        data[0].X =    (float)m_Rect.X;        data[0].Y =    (float)m_Rect.Y;        data[0].Z =    1.0f;        data[0].Tu =    0.0f;        data[0].Tv =    0.0f;        data[1].X =   (float)(m_Rect.X+m_Rect.Width);        data[1].Y =     (float)m_Rect.Y;        data[1].Z =     1.0f;        data[1].Tu =    1.0f;        data[1].Tv =    0.0f;        data[2].X =     (float)m_Rect.X;        data[2].Y =   (float)(m_Rect.Y+m_Rect.Height);        data[2].Z =     1.0f;        data[2].Tu =    0.0f;        data[2].Tv =    1.0f;        data[3].X =   (float)(m_Rect.X+m_Rect.Width);        data[3].Y =   (float)(m_Rect.Y+m_Rect.Height);        data[3].Z =     1.0f;        data[3].Tu =    1.0f;        data[3].Tv =    1.0f;        m_vb.SetData(data, 0, 0);        CGameEngine.Device3D.SetStreamSource( 0, m_vb, 0 );        CGameEngine.Device3D.VertexFormat =           CustomVertex.TransformedTextured.Format;        // Set the texture.        CGameEngine.Device3D.SetTexture(0, GetTexture() );        // Render the face.        CGameEngine.Device3D.DrawPrimitives(           PrimitiveType.TriangleStrip, 0, 2 );     }     catch (DirectXException d3de)     {        Console.AddLine( "Unable to display imagebutton " );        Console.AddLine( d3de.ErrorString );     }     catch ( Exception e )     {        Console.AddLine( "Unable to display imagebutton " );        Console.AddLine( e.Message );     }  }  public void Dispose()  {     m_OffImage.Dispose();     m_OnImage.Dispose();     m_HoverImage.Dispose();  } 
end example
 

Other classes that are rendering the buttons use the remaining methods in the class. The GetTexture method, shown in Listing 2 “12, returns the correct image surface depending on the current hover and activation state of the button.

Listing 2.12: ImageButton GetTexture Method
start example
 public Surface GetTexture()  {     if ( m_bHoverOn )     {        return m_HoverImage. GetTexture ();     }     else if ( m_bOn )     {        return m_OnImage. GetTexture ();     }     else {        return m_OffImage. GetTexture ();     }  } 
end example
 

The GetDestRect and GetSrcRect methods get the destination and source rectangles for the button. The two rectangles are the same size with the destination rectangle offset by the position of the button on the option screen. We also need a GetPoint method so that the rendering class knows where to put the button on the screen (see Listing 2 “13).

Listing 2.13: ImageButton Size and Position Properties
start example
 public Rect GetDestRect() { return m_Rect; }  public Rect GetSrcRect() { return m_OffImage.GetRect(); }  public Point GetPoint() { return new Point(m_X, m_Y); } 
end example
 

The last three methods in the class are used in the interaction of the button with the mouse. The first is a private method that checks to see if a supplied position is within the button rectangle. The next method sets the hover state for the button based on whether the supplied position is over the button. The last of the ImageButton methods process mouse button clicks on the button. The calling class calls the ClickTest method if the primary mouse button has been clicked. The current mouse position is checked to see if it is within the area of the button. If so, the state of the button is toggled. Finally, if the button is currently on and a function has been supplied for the button, the function is invoked. The code for these functions is shown in Listing 2 “14.

Listing 2.14: ImageButton Hit Test Methods
start example
 private bool InRect( Point p )  {     return m_Rect. Contains ( p );  }  public void HoverTest( Point p )  {     m_bHoverOn = InRect( p );  }  public void ClickTest( Point p )  {     if ( InRect( p ) ) m_bOn = !m_bOn;     if ( m_bOn && m_Function != null ) m_Function();  } 
end example
 

Now that we have our ImageButton class for rendering our buttons, it is time to build the option screen itself. The option screen will consist of a background image, an array of buttons that will appear on the screen, and a mouse cursor that will be controlled by mouse movement. The declaration of the OptionScreen class appears in Listing 2 “15.

Listing 2.15: OptionScreen Declaration
start example
 public class OptionScreen  {  #region Attributes     private Image          m_Background    = null;     private Image          m_Cursor        = null;     public bool            m bInitialized  = false;     private ArrayList      m_buttons       = new ArrayList ();     private Point          m_MousePoint    = new Point(0,0);     private bool           m_bMouseIsDown  = false;     private bool           m_bMouseWasDown = false;     private VertexBuffer m_vb;  #endregion 
end example
 

Designing the OptionScreen Class

The constructor for the option screen will require a single argument: the name of the file containing the background image for the screen. The constructor will load the background image and the image for the mouse cursor. The mouse cursor will use the DDS format since that format can include transparency information. The final step is to create the vertex buffer to be used in rendering the screen. Listing 2 “16 shows the constructor for the option screen.

Listing 2.16: OptionScreen Constructor
start example
 public OptionScreen( string filename)  {     try     {        m_Background = new Image( filename );        m_Cursor = new Image ("Cursor.dds");        m_vb = new VertexBuffer( typeof(CustomVertex.TransformedTextured), 4,              CGameEngine.Device3D, Usage.WriteOnly ,              CustomVertex.TransformedTextured.Format, Pool.Default );     }     catch (DirectXException d3de)     {        Console.AddLine("Error creating Option Screen" + filename );        Console.AddLine( d3de.ErrorString );     }     catch ( Exception e )     {        Console.AddLine("Error creating Option Screen" + filename );        Console.AddLine( e.Message );     }  } 
end example
 

Now we have the basic option screen, but it doesn t do much for us without any controls on it. We need a method that allows the game application to add buttons to the option page. This will be our AddButton method, which will take all of the same arguments as the ImageButton constructor. It will create a new ImageButton and add it to the list of buttons for the option screen as shown in Listing 2 “17.

Listing 2.17: OptionScreen AddButton Method
start example
 public void AddButton(int nX, int nY, string sOffFilename, string sOnFilename ,     string sHoverFilename, ImageButton.ButtonFunction pFunc )  {     ImageButton button = new ImageButton ( nX, nY, sOffFilename, sOnFilename,     sHoverFilename, pFunc );     m_buttons.Add(button);  } 
end example
 

We will need another helper method that will be called each frame prior to rendering the option screen. The SetMousePosition method will be used to update the position of the screen s mouse cursor and the primary button status. This is illustrated in Listing 2 “18.

Listing 2.18: OptionScreen SetMousePosition Method
start example
 public void SetMousePosition ( int x, int y, bool bDown )  {     m_MousePoint.X = x;     m_MousePoint.Y = y;     m_bMouseIsDown = bDown;  } 
end example
 

After creating the option screen and adding some buttons, we are ready to start rendering the screen. Just as in the splash screen, we need to capture the current fog state and disable fogging so that it does not affect the option screen or its buttons. This routine gets a bit involved, so we re going to step through this one a bit at a time. The routine begins by creating and filling an array of vertices for the rectangle used to render the background. The data is then used to render the background image using code similar to that used for the splash screens, as shown in Listing 2 “19.

Listing 2.19: OptionScreen Render Method (Conclusion)
start example
 public void Render()  {     try     {        bool fog_state = CgameEngine.Device3D.RenderState.FogEnable;        CgameEngine.Device3D.RenderState.FogEnable = false;        CustomVertex.TransformedTextured[] data =                   new CustomVertex.TransformedTextured[4];        data[0].X =    0.0f;        data[0].Y =    0.0f;        data[0].Z =    0.0f;        data[0].Tu =   0.0f;        data[0].Tv =   0.0f;        data[1].X = CGameEngine.Device3D.Viewport.Width;        data[1].Y =    0.0f;        data[1].Z =    0.0f;        data[1].Tu =   1.0f;        data[1].Tv =   0.0f;        data[2].X =    0.0f;        data[2].Y = CGameEngine.Device3D.Viewport.Height;        data[2].Z =    0.0f;        data[2].Tu =   0.0f;        data[2].Tv =   1.0f;        data[3].X = CGameEngine.Device3D.Viewport.Width;        data[3].Y = CGameEngine.Device3D.Viewport.Height;        data[3].Z =    0.0f;        data[3].Tu =   1.0f;        data[3].Tv =   1.0f;        m_vb.SetData(data, 0, 0);        CGameEngine.Device3D.SetStreamSource( 0, m_vb, 0 );        CGameEngine.Device3D.VertexFormat =                            CustomVertex.TransformedTextured.Format;        // Set the texture.        CGameEngine.Device3D.SetTexture(0, m_Background.GetTexture() );        // Render the screen background.        CGameEngine.Device3D.DrawPrimitive( PrimitiveType.TriangleStrip, 0, 2 ); 
end example
 

Now that the background has been rendered, it is time to add the buttons. We will loop through the array of buttons. Before we render the button image onto the screen, we will call the button s HoverTest method with the current mouse position so that the button can determine if it needs to display the hover state image. Then if the primary mouse button is down, and the mouse button was not down last time, we will call the button s ClickTest method so that it may determine if the player has clicked the button and react accordingly . Finally, we have the button render itself to the screen. This code is shown in Listing 2 “20a.

Listing 2.20a: OptionScreen Render Method
start example
 // Copy on the buttons.     foreach ( ImageButton button in m_buttons )     {        button.HoverTest ( m_MousePoint );        if ( m_bMouseIsDown && !m_bMouseWasDown )        {           button.ClickTest( m_MousePoint );        }        button.Render();  } 
end example
 

After we have repeated this procedure for each button in the array, we save the status of the mouse button down flag for use on the next pass.

 m_bMouseWasDown = m_bMouseIsDown; 

Now that all of the buttons have been rendered, it is time to render the image of our mouse cursor. We do this last, of course, so that the mouse cursor appears to move on top of the buttons. The procedure for rendering the mouse cursor follows the same pattern we just saw with the buttons and is shown in Listing 2 “20b. The biggest difference between this code and that used for the buttons is the requirement for portions of the cursor to be transparent. We want the cursor to look like an arrow. To accomplish this, I created the texture image using Microsoft Paint and the Microsoft Texture Tool to include an alpha channel defining the opaque and transparent portions of the image. As you will see in the listing, there are several device settings that control alpha transparency. Once we have finished rendering, we restore the fog state to its previous value.

Listing 2.20b: OptionScreen Render Method
start example
 // Draw cursor.  Rectangle mouserect = new Rectangle(m_MousePoint,m_Cursor.GetSize());  try  {     data[0].X =    (float)mouserect.X;     data[0].Y =    (float)mouserect.Y;     data[0].Z =    0.0f;     data[0].Tu =   0.0f;     data[0].Tv =   0.0f;     data[1].X =  (float)(mouserect.X+mouserect.Width);     data[1].Y =    (float)mouserect.Y;     data[1].Z =    0.0f;     data[1].Tu =   1.0f;     data[1].Tv =   0.0f;     data[2].X =    (float)mouserect.X;     data[2].Y =  (float)(mouserect.Y+mouserect.Height);     data[2].Z =    0.0f;     data[2].Tu = 0  .0f;     data[2].Tv =   1.0f;     data[3].X =  (float)(mouserect.X+mouserect.Width);     data[3].Y =  (float)(mouserect.Y+mouserect.Height);     data[3].Z =    0.0f;     data[3].Tu =   1.0f;     data[3].Tv =   1.0f;     m_vb.SetData(data, 0, 0);     CGameEngine.Device3D.SetStreamSource( 0, m_vb, 0 );     CGameEngine.Device3D.VertexFormat = CustomVertex.TransformedTextured.Format;     // Set the texture.     CGameEngine.Device3D.SetTexture(0, m_Cursor.GetTexture() );     // Set diffuse blending for alpha set in vertices.     CGameEngine.Device3D.RenderState.AlphaBlendEnable = true;     CGameEngine.Device3D.RenderState.SourceBlend = Blend.SourceAlpha;     CGameEngine.Device3D.RenderState.DestinationBlend = Blend.InvSourceAlpha;     // Enable alpha testing (skip pixels with less than a certain alpha).     if( CGameEngine.Device3D.DeviceCaps.AlphaCompareCaps.GreaterEqual )     {        CGameEngine.Device3D.RenderState.AlphaTestEnable = true;        CGameEngine.Device3D.RenderState.ReferenceAlpha = 008;        CGameEngine.Device3D.RenderState.AlphaFunction = Compare.GreaterEqual;     }     // Render the face.     CGameEngine.Device3D.DrawPrimitive( PrimitiveType.TriangleStrip, 0, 2 );  }  catch (DirectXException d3de)  {     Console.AddLine( "Unable to display cursor " );     Console.AddLine( d3de.ErrorString );  }  catch ( Exception e )  {     Console.AddLine( "Unable to display cursor " );     Console.AddLine( e.Message );  }  CgameEngine.Device3D.RenderState.FogEnable = fog_state;  } 
end example
 

Like any of our code that has a possibility of failure, we have wrapped the rendering code within a Try block. The corresponding Catch block posts a message to the console and continues on. This allows our code to soft fail by noting the problem and continuing on (see Listing 2 “20c).

Listing 2.20c: OptionScreen Render Method
start example
 catch (DirectXException d3de)  {     Console.AddLine( "Error rendering Option Screen" );     Console.AddLine( d3de.ErrorString );  }  catch ( Exception e )  {     Console.AddLine( "Error rendering Option Screen" );     Console.AddLine( e.Message );  } 
end example
 

That concludes our definition of the OptionScreen class. The last section of this chapter will show how our game engine controls the SplashScreen and OptionScreen classes.




Introduction to 3D Game Engine Design Using DirectX 9 and C#
Introduction to 3D Game Engine Design Using DirectX 9 and C#
ISBN: 1590590813
EAN: 2147483647
Year: 2005
Pages: 98

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net