在我们结束本章对用户接口的讨论之前,得考虑怎样把这些类整合进GameEngine类。游戏引擎会包含列表2-37里的属性。
列表2.37:GameEngine属性
public class CGameEngine { #region attributes // A local reference to the DirectX device private static Microsoft.DirectX.Direct3D.Device m_pd3dDevice; private System.Windows.Forms.Form m_WinForm; public static GameInput m_GameInput = null; private SplashScreen m_SplashScreen = null; private OptionScreen m_OptionScreen = null; public float fTimeLeft = 0.0f ; Thread m_threadTask = null; #endregion |
虽然在游戏开始时我们需要显示两个不同的splash screen,但是我们只需要splash screen类的一个引用。注意到我们还有GameInput类实例和OptionScreen类实例。你也会看到有一个Thread类实例。记得我提到过,我们在显示splash screen时执行一些资源加载。当游戏应用程序请求显示splash screen时,也会给我们一个函数并行的执行这个函数。这个后台任务的委托见下面这行代码。我们会在单独的一个线程执行给定的函数,以便从硬盘加载相关的资源文件时不影响我们的渲染循环。创建这个线程的真正机制稍后将在讨论ShowSplash方法时讨论。
public delegate void BackgroundTask();
选项屏幕是由游戏应用程序创建和配置,并且传递下去给游戏引擎。这有方法SetOptionScreen(见列表2-38)完成,它只是复制类的引用到游戏引擎成员变量。
列表2.38:SetOptionScreen方法
public void SetOptionScreen ( OptionScreen Screen ) { m_OptionScreen = Screen; } |
游戏引擎在游戏应用程序启动时,被游戏应用程序初始化。应用程序提供应用程序窗口的句柄和用于渲染的Direct3D设备。初始化中,在此时,我们只是保存窗口的句柄和设备引用,以备将来使用,并且使用窗体创建一个GameInput的实例,见列表2-39。
列表2.39:Initialize方法
public void Initialize ( System.Windows.Forms.Form form, Microsoft.DirectX.Direct3D.Device pd3dDevice) { // Capture a reference to the window handle. m_WinForm = form; // For now just capture a reference to the DirectX device for future use. m_pd3dDevice = pd3dDevice;
m_GameInput = new GameInput ( m_WinForm ); } |
ShowSplash方法(见列表2-40)管理着splash screen从创建到销毁的整个生命周期。这个方法的参数有splash screen图片的文件名,显示splash screen的秒数,以及splash screen显示时需要在后台执行的函数。如果游戏引擎splash screen成员变量是空,这是我们第一次要求显示splash screen。Splash screen被创建,后台任务如果被指定了则会开始。线程代码创建一个新的线程,赋给它一个名字以便于后面去检查它,并且启动这个线程。虽然游戏开发者能够使用这个方法产生一个工作线程,它在本教学游戏中持续运行,但是这是很拙劣的编程习惯。这样的线程应该由游戏应用程序本身来启动和管理。
列表2.40:ShowSplash方法
public bool ShowSplash ( string sFileName, int nSeconds, BackgroundTask task ) { bool bDone = false;
if ( m_SplashScreen == null ) { m_SplashScreen = new SplashScreen( sFileName, nSeconds);
if ( task != null ) { m_threadTask = new Thread(new ThreadStart(task) ); m_threadTask.Name = "Game_backgroundTask"; m_threadTask.Start (); } }
bDone = m_SplashScreen.Render();
fTimeLeft = m_SplashScreen.fTimeLeft;
if ( bDone ) { m_SplashScreen.Dispose(); m_SplashScreen = null; } return bDone; } |
一个splash screen被创建,我们让它自己渲染自己,并且让我们知道显示时间是否已经到了。如果时间到了,我们清除这个splash screen,释放它的资源,并且设置它的引用为空,以便稍后显示另外一个splash screen。完成的状态传回给游戏应用程序。游戏应用程序使用这个信息来切换游戏状态。
DoOptions方法(见列表2-41)控制着选项屏幕的渲染。因为游戏应用程序负责选项屏幕的创建和配置,我们仅仅需要渲染它。在调用渲染方法前(Prior to calling the Render method though),我们需要更新屏幕的最新鼠标移动和按钮状态。我们使用最近的X和Y的位移以及鼠标主键的状态来调用SetMousePosition方法。Render方法完成剩余的。
列表2.41:DoOptions方法
public void DoOptions ( ) { if ( m_OptionScreen != null ) { m_OptionScreen.SetMousePosition(m_GameInput.GetMousePoint().X, m_GameInput.GetMousePoint().Y, m_GameInput.IsMouseButtonDown(0) ); m_OptionScreen.Render(); } } |
GetPlayerInputs方法(列表2-42)是我们为本游戏引擎引进的最后一个方法。它只是GameInput类Poll方法的一个包装。This way the game has control over when the inputs are polled without giving it access to the GameInput class。
列表2.42:GetPlayerInputs方法
public void GetPlayerInputs ( ) { m_GameInput.Poll(); } |
App.cs文件里的游戏应用的更改支持了出现在第二章列表2-43里的代码(The changes to the game application in file App.cs to support the code presented in Chapter 2 appear in Listing 2-43)。最重要的更改位于OneTimeSceneInit方法。此方法在开始处理游戏循环之前调用,是初始化游戏引擎最好的地方。在初始化字体和游戏引擎之后,它映射键盘上的Esc键到应用程序的Terminate方法。这是退出游戏几种方法中的一种。控制台创建后,QUIT命令被加入到控制台的命令列表里。最后,选项屏幕被创建和初始化,拥有两个按钮。Play按钮除了改变状态不做其他任何事。Quit按钮提供退出游戏的第三种方法。
列表2.43:OneTimeSceneInit方法
protected override void OneTimeSceneInit() { // Initialize the font's internal textures. m_pFont.InitializeDeviceObjects( device );
m_Engine.Initialize( this, device );
CGameEngine.Inputs.MapKeyboardAction(Key.Escape, new ButtonAction(Terminate), true); CGameEngine.Inputs.MapKeyboardAction(Key.A, new ButtonAction(MoveCameraXM), false); CGameEngine.Inputs.MapKeyboardAction(Key.W, new ButtonAction(MoveCameraZP), false); CGameEngine.Inputs.MapKeyboardAction(Key.S, new ButtonAction(MoveCameraXP), false); CGameEngine.Inputs.MapKeyboardAction(Key.Z, new ButtonAction(MoveCameraZM), false); CGameEngine.Inputs.MapKeyboardAction(Key.P, new ButtonAction(ScreenCapture), true); CGameEngine.Inputs.MapMouseAxisAction(0, new AxisAction(PointCamera)); CGameEngine.Inputs.MapMouseAxisAction(1, new AxisAction(PitchCamera));
m_Console = new GameEngine.Console( m_pFont, "console.jpg" );
GameEngine.Console.AddCommand("QUIT", "Terminate the game", new CommandFunction(TerminateCommand)); GameEngine.Console.AddCommand("STATISTICS", "Toggle statistics display", new CommandFunction(ToggleStatistics));
m_OptionScreen = new OptionScreen( "Options1.jpg" ); m_OptionScreen.AddButton( 328, 150, "PlayOff.jpg", "PlayOn.jpg", "PlayHover.jpg", new ButtonFunction(Play) ); m_OptionScreen.AddButton( 328, 300, "OuitOff.jpg", "QuitOn.jpg", "QuitHover.jpg", new ButtonFunction(Terminate) ); m_Engine.SetOptionScreen( m_OptionScreen );
music = new Jukebox(); music.AddSong("nadine.mp3"); music.AddSong("Rock.mp3"); music.Volume = 0.75f ; music.Play(); } |
最后需要检查的是对D3Dapp.cs文件(见列表2-44)OnKeyDown的更改,这在描述控制台那一节提起过。键盘的每一次按键,KeyDown消息会发送给得到焦点的应用程序。字母,数字和空格直接被发给AddCharacterToEntryLine方法,回车键触发这一行被处理。退格键触发Backspace方法。句号和负号键(The period and minus keys)需要特别处理,因为他们不会自动被ToString方法转换成正确的字符串(since they do not automatically get translated to they proper string by the ToString method)。这也是F12键映射到打开和关闭控制台的地方。此处理器最后的一点代码被应用程序基类用来支持在窗口和全屏之间切换,通过敲击Alt-Enter键的组合。
列表2.44:OnKeyDown消息处理器
protected override void OnKeyDown(System.Windows.Forms.KeyEventArgs e) { char tstr = (char)(e.KeyValue); if ( GameEngine.Console.IsVisible && e.KeyData == System.Windows.Forms.Keys.Return ) { GameEngine.Console.ProcessEntry(); } if ( e.KeyData == System.Windows.Forms.Keys.F12 ) { GameEngine.Console.ToggleState(); } else if ( GameEngine.Console.IsVisible && (e.KeyData == System.Windows.Forms.Keys.Space | | ( e.KeyData >= System.Windows.Forms.Keys.A && e.KeyData <= System.Windows.Forms.Keys.Z ) | | ( e.KeyData >= System.Windows.Forms.Keys.DO && e.KeyData <= System.Windows.Forms.Keys.D9 ) ) ) { GameEngine.Console.AddCharacterToEntryLine( tstr ); } else if ( GameEngine.Console.IsVisible && e.KeyData == System.Windows.Forms.Keys.OemPeriod ) GameEngine.Console.AddCharacterToEntryLine( "." ); } else if ( GameEngine.Console.IsVisible && e.KeyData == System.Windows.Forms.Keys.OemMinus ) { GameEngine.Console.AddCharacterToEntryLine( '-' ); } else if ( GameEngine.Console.IsVisible && e.KeyData == System.Windows.Forms.Keys.Back ) { GameEngine.Console.Backspace(); } if ( (e.Alt) && (e.KeyCode == System.Windows.Forms.Keys.Return)) { // Toggle the full-screen/window mode, if( active && ready ) { Pause( true );
try { ToggleFullscreen(); Pause( false ); return; } catch { DisplayErrorMsg( new GraphicsException(GraphicsException.ErrorCode.ResizeFailed), AppMsgType.ErrorAppMustExit); } finally { e.Handled = true; } } } } |