绘制选项屏幕
下一个要讲述的用户接口组件是选项屏幕。依赖于游戏的复杂性,它可能由多个选项屏幕组成,通过一个主选项屏幕来访问。在这里玩家可以设定他偏爱的游戏方式或者退出游戏。选项屏幕的需求决定了在屏幕上应用的控件(controls)的复杂性,从从头到尾应用简单的按钮,到使用类似于MFC的对话框。
为选项屏幕建立砖块
为我们的最初的选项屏幕,我们使用一个按钮风格的接口。为了支持这个,我们定义一个小类ImageButton,它保存我们的按钮数据。
为了按钮增加一点炫的效果,我们定义他有三个可能的状态:Off,Hover和On。Off状态有一个基本的图片。Hover状态显示高亮,一旦鼠标指针在按钮上就触发这个状态。这就提示了玩家鼠标点在了按钮上。On状态可以用于能触发开和关的条目(例如一个用于被选择的选择框)(The On state can be used for items that they can toggle on and off(e.g., an image with a checked check box))。我们还需要指定按钮要显示在屏幕的什么地方和按钮被选择时需要调用的方法。
列表2-9中类ImageButton的声明考虑了这些需求。
列表2.9:ImageButton的声明
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; |
类中的矩行值保存了按钮的尺寸。我们假设了按钮的三个图片的尺寸相同。如果我们错误的以不同尺寸的图片初始了类,就会出现只绘制部分图片的错误,因为我们试图绘制一个比图片大的矩形。
注意:我建议你先制作Off状态按钮的图片,然后以这个图片为基础来创建其他两个状态的图片。
ImageButton类的构造函数比目前我们处理过的其他类都要棘手一点。构造函数需要六个参数:在屏幕上定位的X和Y值,三个图片的文件名,以及当按钮被选择需要调用的函数。代码应该和类SplashScreen类似,我们也创建一个顶点缓冲,其中的数据用来绘制有纹理的矩形按钮(参见列表2-10)。
列表2.10:ImageButton的构造函数
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 ); } |
Render和Dispose方法见列表2-11。Splash和option屏幕的Render方法非常相似。区别之处在设置顶点缓冲的坐标。不是填充屏幕,组成按钮的两个三角形是填充按钮的矩形。Dispose方法简单的为按钮的每个图片调用Dispose方法。
列表2.11:ImageButton的Render和Dispose方法
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].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(); } |
其他的类使用按钮类其他的方法来渲染按钮。列表2-12中的GetTexture方法,返回正确的图片表面,依赖于当前的盘旋(hover)和按钮被激活的状态。
表2.12:ImageButton GetTexture方法
public Surface GetTexture() { if ( m_bHoverOn ) { return m_HoverImage. GetTexture (); } else if ( m_bOn ) { return m_OnImage. GetTexture (); } else { return m_OffImage. GetTexture (); } } |
GetDestRect和GetSrcRect方法取得按钮的目的和源矩形。这两个矩形尺寸是相同的。我们还需要方法GetPoint,以便渲染类知道把按钮放在屏幕哪里(参看列表2-13)。
列表2.13:ImageButton Size和Position属性
public Rect GetDestRect() { return m_Rect; } public Rect GetSrcRect() { return m_OffImage.GetRect(); } public Point GetPoint() { return new Point(m_X, m_Y); } |
类最后的三个方法用在按钮和鼠标的交互上。第一个是私有方法,检查一个位置是否在按钮的矩形里。下一个方法设定按钮的悬停状态,基于给定的位置位于按钮上。最后一个方法处理按钮的鼠标点击。一旦鼠标的主键发生点击,调用类则调用方法ClickTest。检查鼠标当前位置,看是否在按钮的范围内。如果是,按钮的状态即被触发。最后,如果按钮是开的状态,并且按钮有提供回调函数,此回调函数被调用。这些函数的代码如列表2-14所示。
列表2.14:ImageButton Hit Test 方法
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(); } |
现在,我们有ImageButton类用来渲染我们的按钮了,是时候创建选项屏幕本身了。选项屏幕由一个背景图片,一组出现在屏幕上的按钮,以及由鼠标移动控制的鼠标指针组成。类OptionScreen的声明如列表2-15所示。
列表2.15:OptionScreen 声明
设计OptionScreen类
Option screen的构造函数需要一个参数:屏幕背景图片的文件名。构造函数将加载背景图片和鼠标指针图片。鼠标指针将使用DDS格式,因为此格式可以包含透明信息。最后一步是创建顶点缓存,用来渲染屏幕。列表2-16显示了option screen的构造函数。
列表2.16:OptionScreen Constructor
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 ); } } |
现在,我们有一个基本的选项屏幕了,但是没有任何控制在它上面,则没什么用。我们需要一个允许游戏应用程序添加按钮到选项页的方法。这就是我们的AddButton方法,它需要的参数和ImageButton构造函数需要的参数一摸一样。它将创建一个新的ImageButton,并且添加到optionScreen的按钮列表中,代码列表2-17。
列表2.17:OptionScreen AddButton方法
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); } |
我们还需要另外一个辅助方法,它在每一帧需要选项屏幕之前调用。SetMousePosition方法用来更新屏幕鼠标指针的位置,以及主按钮的状态。代码如列表2-18所示。
列表2.18:OptionScreen SetMousePosition方法
public void SetMousePosition ( int x, int y, bool bDown ) { m_MousePoint.X = x; m_MousePoint.Y = y; m_bMouseIsDown = bDown; } |
在创建选项屏幕和添加一些按钮之后,我们准备开始渲染屏幕了。正如在splash screen中,我们需要捕获当前雾效状态并且禁用雾效,以便它影响选项屏幕或者按钮。这个例程有一点复杂,所以我们一次看一段代码。此例程开始时,创建和填充用来渲染背景的矩形顶点数据。这些数据随后用来渲染背景图片,所使用的代码类似splash screens,如列表2-19所示。
列表2.19:OptionScreen Render方法(结论)
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 ); |
现在,背景已经被渲染,是添加按钮的时候了。我们在按钮组中循环。在我们渲染按钮图片到屏幕之前,我们将使用鼠标当前位置调用按钮的HoverTest方法,以便鼠标能确定是否有需要显示悬停状态的图片。然后,如果鼠标主按钮被按下,并且鼠标按钮最近一次没有被按下,我们将调用按钮的ClickTest方法,以便决定玩家是否点击了按钮,并作出相应的反应。最后,我们让按钮渲染自己到屏幕。代码如列表2 -20a 所示。
列表 2.20a :OptionScreen Render方法
// 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(); } |
在我们对按钮组里的每一个按钮重复了这一过程(procedure)后,我们保存鼠标按钮按下的状态标志,以备下一次运行时使用。
m_bMouseWasDown = m_bMouseIsDown;
现在,所有的按钮被渲染了,是时候渲染鼠标指针图片了。我们最后做这个,以便鼠标指针在按钮的最上面移动。渲染鼠标指针的过程在列表2-20b中,和按钮的差不多。与按钮的最大的区别是,鼠标指针需要部分透明。我们想要鼠标看起来像一个箭头。为了达到这个,我使用微软Paint和微软Texture Tool来创建包含定义了不透明和透明的alpha通道的纹理图片。就如你将要在列表里看到的,有好几种设备设置,它们控制着alpha透明。一旦我们完成了渲染,我们需要恢复雾效状态到它之前的值。
列表2.20b:OptionScreen Render方法
像我们的任何代码一样,都有失败的可能,我们把渲染代码包装在Try区块里。相应的Catch区块发送消息到控制台,并且继续。这允许我们的代码有“软失败”,通过记录问题并且继续运行(见列表2 -20c )。
列表 2.20c :OptionScreen Render方法
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 ); } |
结束了我们OptionScreen类的定义。本章的最后一节将展示我们的游戏引擎怎样控制SplashScreen和OptionScreen类。