绘制Splash Screens
第一章里,我提到了使用splash screens向玩家呈现游戏开发者的荣誉,以及游戏的序幕。对于更加高级的游戏或者商业游戏,它们可能是一张静态的图片或者一个视频短片。视频短片由DirectX的DirectShow组件来处理,不管是DirectX以前的版本还是当前的C++版本。这个有Managed DirectX里的AudioVideo类提供支持。微软提供的视频纹理示例举例说明了怎么做。
微软使用内存中的一个结构保存一个图片,是表面(surface)还是纹理,依赖于怎么使用此图片。如果图片用来在屏幕上显示二维方式(If the image is being presented in a two-dimensional fashion on the screen),这叫做表面。如果图片的一部分或者全部应用在三维的形体上,这叫做纹理。我们的splash screens 使用纹理来保存图片。为了使纹理更易于使用,我们使用一个新类Image包装了纹理。这个类,就像之前讨论过的GameInput类,从IDisposable接口继承而来,以便我们可以控制何时和怎样清楚自己。这个类有三个属性:一个字符串记录了图片是从这个文件名加载,一个ImageImformation结构提供了图片的信息,和纹理本身。列表2-5表示了属性的定义。
列表2.5:Image类的成员
private Texture m_image = null; private ImageInformation m_info = new ImageInformation(); private string m_sFilename; |
此类的构造函数只有一个参数,即图片文件名。图片文件可以有几种格式,包括bitmap(.bmp),压缩的JPEG(.jpg),以及DirectDraw Surface(.dds)。第三种格式设定复杂一些,但是包含透明需要的alpha通道信息。现在我推荐使用JPEG,因为这种文件方便大多数图形软件操作,并且文件尺寸更小。构造函数保存了文件名的拷贝,并且调用另一个方法Load,真正从文件加载此图片到表面(surface)。我们把加载方法分割为单独的方法,那是因为之后可能需要重新加载此图片。方法Load完成了此类的大多数工作,它的代码如列表2-6所示。
列表2.6:Image Load方法
public void Load() { try { m_info = new ImageInformation(); m_info = TextureLoader.ImageInformationFromFile(m_sFilename); m_image = TextureLoader.FromFile(CGameEngine.Device3D ,m_sFilename); } catch (DirectXException d3de) { Console.AddLine("Unable to load image" + m_sFilename); Console.AddLine(d3de.ErrorString); } catch ( Exception e ) { Console.AddLine("Unable to load image" + m_sFilename); Console.AddLine(e.Message); } } |
你第一个注意到的将是所有代码都包装在Try/Catch区块里。因为有几个原因可能会导致此函数失败,所以获取这些异常就很重要了。我们想要加载的文件可能不存在或者因为内存不够导致创建纹理失败。这里处理这些错误仅仅是在控制台上输出错误信息。我将在本章的“开发控制台”小节详细阐述控制台。
Load方法做的第一件事情是创建和从文件加载ImageInformation信息。它将告诉我们图片的大小,以便我们创建合适的纹理尺寸来保存图片。下一步是创建图片的表面。类TextLoader有一个方法,FromFile,正是用来做这个事情。
如果所有事都如你所愿,我们就在纹理存有图片,准备渲染到屏幕上了。此类并不渲染本身,相反,他提供两个方法暴露纹理和它的尺寸,通过Size和Rectangle结构(it has two methods that expose the texture and its size as both a Size and a Rectangle structure)。这样就使任何类只要有Image类实例,能够渲染图片。
现在我们以及有保存图片的类了,准备好建立splash screen了。类SplashScreen将提供所需的功能。想我们创建的每一个类一样,它也是从IDisposable继承而来。此类中的Dispose方法简单的为图片调用Dispose。Splash screen的构造函数需要两个参数:需要显示的图片文件名和splash screen需要显示的时间秒数。文件名传递给图片的构造函数来创建splash screen图片。时间间隔数加上系统当前的时间,然后设置为splash screen的结束。一个顶点缓冲区也被创建,保存图片绘制到屏幕上的位置的顶点。类SplashScreen如列表2-7所示。
列表2.7:SplashScreen声明及构造函数
public class SplashScreen : IDisposable { private Image image = null; private float m_StartTime; private float m_EndTime = 0.0f ; private VertexBuffer m_vb; public bool m_bInitialized = false; public float fTimeLeft;
public SplashScreen( string filename, int nDuration) { image = new Image( filename ); m_StartTime = DXUtil.Timer( TIMER.GETABSOLUTETIME ); m_EndTime = m_StartTime + nDuration; m_ vb = new VertexBuffer( typeof(CustomVertex.TransformedTextured), 4, CGameEngine.Device3D, Usage.WriteOnly, CustomVertex.TransformedTextured.Format, Pool.Default ); } |
此类中另一个主要的方法是Render方法。这是splash screen真正绘制到系统屏幕上的地方。此方法返回false,如果splash screen时间还没有结束,返回ture,如果已经到结束时间了。渲染splash screen的第一步是创建屏幕上四个角上变换顶点的数组。每一个顶点都包含了纹理坐标信息,以便在屏幕上放置纹理。一旦数据结构准备好了(Once the data structure has been populated),数据便被复制到顶点缓冲区里。
注释:为了提高渲染速度,顶点缓冲区通常位于显卡内存中。
一旦顶点数据到了缓冲区,我们需要设置渲染设备以便把图片绘制到屏幕上。第一步是获取当前的雾效状态,然后关闭雾效。我们不想让雾效影响splash screen。然后设置我们的顶点缓冲区为当前的流源(stream source),并且让设备知道流里的数据格式。我们也需要设置当前的纹理为splash screen image里的图片。真正的渲染发生在当我们对设备下命令使用顶点缓冲区里的数据来绘制两个三角形的带(a strip of two triangles)时。
绘制后,剩下的唯一事情是检查时间,看splash screen是否已经到时间。所有的代码如列表2-8所示。
列表2.8:SplashScreen Render方法
public bool 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 ;
CGameEngine.Device3D.SetStreamSource( 0, m_vb, 0 ); CGameEngine.Device3D.VertexFormat = CustomVertex.TransformedTextured.Format;
// Set the texture. CGameEngine.Device3D.SetTexture(0, image.GetTexture() );
// Render the face. CGameEngine.Device3D.DrawPrimitive( PrimitiveType.TriangleStrip, 0, 2 ); CgameEngine.Device3D.RenderState.FogEnable = fog_state; } catch (DirectXException d3de) { Console.AddLine( "Unable to display SplashScreen" ); Console.AddLine( d3de.ErrorString ); } catch ( Exception e ) { Console.AddLine( "Unable to display SplashScreen" ); Console.AddLine( e.Message ); }
// Check for timeout. float fCurrentTime = DXUtil.Timer( TIMER.GETABSOLUTETIME );
return ( fCurrentTime > m_EndTime ); } |