一、遊戲架構結構 遊戲的運作流程和拍電影差不多,拍電影前演員事先準備好各種服裝和化妝等等準備工作(遊戲初始化),導演喊Action開始錄製電影(遊戲主迴圈開始),錄製的過程中導演會不時地指導著演員(遊戲輸入),而演員根據台詞指令碼(遊戲邏輯)進行表演,表現出各種不同的畫面(遊戲渲染),直到導演喊收工(遊戲結束),拍戲進入了尾聲,這時,送便當的應該也到了……
- 遊戲初始化
此模組對遊戲資料進行初始化操作,為遊戲分配相應的記憶體空間。
- 遊戲主迴圈
此模組開始運行各種操作,直到遊戲結束或者使用者離開遊戲為止。
- 遊戲輸入
此模組負責監聽使用者的輸入,根據輸入改變相應的遊戲邏輯。
- 遊戲邏輯
此模組是遊戲的主體部分,包括遊戲中的碰撞檢測,人工智慧,物理系統等,其結果影響下一個畫面的形成。
- 遊戲聲音
此模組負責播放遊戲聲音,聲音將調用擴音器播放簡單的音效。
- 遊戲渲染
此模組根據遊戲邏輯渲染畫面,控制台遊戲畫面是由字元構成的。
- 畫面播放速率鎖定
此模組用於同步顯示,遊戲的複雜度,電腦的不同時刻或者不同電腦運行遊戲時快時慢,這取決於CPU當時的負載情況和處理能力,從而使遊戲畫面重新整理率時高時低,這影響了遊戲的體驗效果。畫面播放速率鎖定按照某個最大畫面播放速率進行同步,從而改善遊戲畫面的呈現。
- 遊戲結束
此模組在遊戲退出時運行,用於清理遊戲資源並釋放記憶體空間。
以上粗略地描述了遊戲架構的基本模組,然而並沒有完整地實現各種功能,下面將屏蔽其他模組,把焦點放到主遊戲架構上,實現一個通用的遊戲架構類。二、遊戲架構實現 在項目開始階段,需要定義一些基本的編碼規範:
- 類的命名:以C(Console)字母開頭,如CGame,CDraw,CMatrix等。
- 欄位命名:以m_字母加底線開頭,如m_draw,m_dc_mouse等。
- 方法命名:以小寫字母開頭,如run(),draw()等。
以上只是我個人喜好的編寫方式,寫出來方便大家閱讀My Code,在項目中並不出現C#的屬性欄位,我將所有的屬性寫成了更清晰的方法形式(它們本身就是方法嘛),至於其他的,也沒有什麼特別的了。
架構靜態結構
- ICGame介面定義一個run方法,主程式與遊戲主體的訊息通訊通過這個介面來進行,隱藏了具體遊戲架構的細節。
- CGame類為主要的遊戲架構類,提供遊戲需要的方法和事件等,並提供一系列抽象和虛擬方法,提供給遊戲衍生類別具體實現,為遊戲的實現提供一個基本的統一的代碼架構。
- CGameEngine類封裝了ICGame介面,以方便主程式的調用。
- CRun類為程式的進入點,與CGameEngine類發生通訊,啟動並運行具體的遊戲執行個體。
///ICGame介面的實現
using System;namespace CEngine{ /// <summary> /// 遊戲運行介面 /// </summary> public interface ICGame { void run(); }}
///CGame類實現
using System;using System.Threading;namespace CEngine{ /// <summary> /// 通用遊戲類 /// </summary> public abstract class CGame : ICGame { #region 欄位 /// <summary> /// 畫面更新速率 /// </summary> private Int32 m_updateRate; /// <summary> /// 當前幀數 /// </summary> private Int32 m_fps; /// <summary> /// 記錄幀數 /// </summary> private Int32 m_tickcount; /// <summary> /// 記錄上次已耗用時間 /// </summary> private Int32 m_lastTime; /// <summary> /// 遊戲是否結束 /// </summary> private Boolean m_bGameOver; #endregion #region 建構函式 /// <summary> /// 建構函式 /// </summary> public CGame() { m_bGameOver = false; } #endregion #region 遊戲運行函數 /// <summary> /// 遊戲初始化 /// </summary> protected abstract void gameInit(); /// <summary> /// 遊戲邏輯 /// </summary> protected abstract void gameLoop(); /// <summary> /// 遊戲結束 /// </summary> protected abstract void gameExit(); #endregion #region 遊戲設定函數 /// <summary> /// 設定畫面更新速率 /// </summary> /// <param name="rate"></param> protected void setUpdateRate(Int32 rate) { this.m_updateRate = rate; } /// <summary> /// 擷取畫面更新率 /// </summary> /// <returns></returns> protected Int32 getUpdateRate() { return 1000 / this.m_updateRate; } /// <summary> /// 擷取FPS /// </summary> /// <returns></returns> protected Int32 getFPS() { return this.m_fps; } /// <summary> /// 計算FPS /// </summary> private void setFPS() { Int32 ticks = Environment.TickCount; m_tickcount += 1; if (ticks - m_lastTime >= 1000) { m_fps = m_tickcount; m_tickcount = 0; m_lastTime = ticks; } } /// <summary> /// 延遲 /// </summary> private void delay() { this.delay(1); } protected void delay(Int32 time) { Thread.Sleep(time); } /// <summary> /// 遊戲結束 /// </summary> /// <param name="gameOver"></param> protected void setGameOver(Boolean gameOver) { this.m_bGameOver = gameOver; } /// <summary> /// 遊戲是否結束 /// </summary> /// <returns></returns> protected Boolean isGameOver() { return this.m_bGameOver; } /// <summary> /// 設定游標是否可見 /// </summary> /// <param name="visible"></param> protected void setCursorVisible(Boolean visible) { Console.CursorVisible = visible; } /// <summary> /// 設定控制台標題 /// </summary> /// <param name="title"></param> protected void setTitle(String title) { Console.Title = title; } /// <summary> /// 擷取控制台標題 /// </summary> /// <returns></returns> protected String getTitle() { return Console.Title; } /// <summary> /// 關閉遊戲並釋放資源 /// </summary> private void close() { } #endregion #region 遊戲啟動介面 /// <summary> /// 遊戲運行 /// </summary> public void run() { //遊戲初始化 this.gameInit(); Int32 startTime = 0; while (!this.isGameOver()) { //啟動計時 startTime = Environment.TickCount; //計算fps this.setFPS(); //遊戲邏輯 this.gameLoop(); //保持一定的FPS while (Environment.TickCount - startTime < this.m_updateRate) { this.delay(); } } //遊戲退出 this.gameExit(); //釋放遊戲資源 this.close(); } #endregion }}
CGame類封裝了幾個遊戲函數gameInit、gameLoop、gameExit,分別對應了遊戲的初始化、遊戲邏輯和遊戲結束,設定為抽象方法,提供給衍生類別具體實現;其他的函數用於配置運行參數;然而最重要的還是run函數,是這個遊戲架構的核心,封裝了遊戲啟動並執行邏輯層次,這樣就把實際驅動遊戲的運行細節封裝起來,使開發人員更注重具體的遊戲設計而不用關心如何驅動遊戲的細節,一定程度上提高了開發效率。 遊戲運行架構虛擬碼:
遊戲初始化 while(!遊戲結束) { … 遊戲邏輯 … 保持畫面播放速率 } 遊戲結束 清理資源釋放記憶體空間
可以看到遊戲的運行是靠一個迴圈來驅動的,在這裡並沒有完整的實現一個運行架構,沒有遊戲輸入、遊戲聲音和遊戲渲染部分,隨著章節講解的進行,這些內容將逐步出現,並一步步完善這個遊戲架構。
///CGameEngine類實現
using System;namespace CEngine{ /// <summary> /// 遊戲啟動類 /// </summary> public sealed class CGameEngine { public static void Run(ICGame game) { if (game == null) { Console.Write("引擎未初始化!"); Console.ReadLine(); } else { game.run(); } } }}
///CRun類實現
using System;using CEngine;namespace CRun{ class CRun { static void Main(string[] args) { //CGameEngine.Run(new DemoGame()); } } //class DemoGame:CGame { }}
由於還沒有具體遊戲執行個體,以上採取虛擬碼形式描述程式進入點如何啟動一個遊戲執行個體,實際情況按照以上格式修改即可。
三、遊戲架構測試 儘管遊戲架構沒有完整的實現,但是也可以測試它的運行情況,這裡用一個每秒重新整理一幀的小例子來說明遊戲架構的使用方法和測試它的運行情況。 ///TestGame類實現
using System;using CEngine;namespace Game{ public class TestGame : CGame { private Int32 m_ticks; private Int32 m_lasttime; /// <summary> /// 遊戲初始化 /// </summary> protected override void gameInit() { //設定遊戲標題 setTitle("遊戲架構測試"); //設定遊戲畫面重新整理率 每毫秒一次 setUpdateRate(1000); //設定游標隱藏 setCursorVisible(false); Console.WriteLine("遊戲初始化成功!"); m_lasttime = Environment.TickCount; } /// <summary> /// 遊戲邏輯 /// </summary> protected override void gameLoop() { if (m_ticks++ < 15) { Console.WriteLine(string.Format(" 遊戲運行中,第{0}幀,耗時{1}ms", m_ticks, Environment.TickCount - m_lasttime)); m_lasttime = Environment.TickCount; } else { setGameOver(true); } } /// <summary> /// 遊戲結束 /// </summary> protected override void gameExit() { Console.WriteLine("遊戲結束!"); Console.ReadLine(); } }}
相應地,CRun類修改為:
using System;using CEngine;using Game;namespace CRun{ class CRun { static void Main(string[] args) { CGameEngine.Run(new TestGame()); } }}
測試結果為: 可以看出TestGame類主要關注的是具體遊戲的邏輯,而無須考慮遊戲如何裝載和如何啟動並執行細節,從而把更多時間花在遊戲的設計和使用者體驗上,而這,就是這個遊戲架構的設計目的。
四、結語 儘管架構還是如此簡陋,但基本闡述了一個遊戲運行架構的基本模型,而隨著講解的深入,架構將會逐步完善。