C#游戏编程:《控制台小游戏系列》之《二、游戏框架设计》

一、游戏框架结构

   
  游戏的运作流程和拍电影差不多,拍电影前演员事先准备好各种服装和化妆等等准备工作(游戏初始化),导演喊Action开始录制电影(游戏主循环开始),录制的过程中导演会不时地指导着演员(游戏输入),而演员根据台词脚本(游戏逻辑)进行表演,表现出各种不同的画面(游戏渲染),直到导演喊收工(游戏结束),拍戏进入了尾声,这时,送盒饭的应该也到了……
  • 游戏初始化
    此模块对游戏数据进行初始化操作,为游戏分配相应的内存空间。
  • 游戏主循环
    此模块开始运行各种操作,直到游戏结束或者用户退出游戏为止。
  • 游戏输入
    此模块负责监听用户的输入,根据输入改变相应的游戏逻辑。
  • 游戏逻辑
    此模块是游戏的主体部分,包括游戏中的碰撞检测,人工智能,物理系统等,其结果影响下一个画面的形成。
  • 游戏声音
    此模块负责播放游戏声音,声音将调用扬声器播放简单的音效。
  • 游戏渲染
    此模块根据游戏逻辑渲染画面,控制台游戏画面是由字符构成的。
  • 帧速率锁定
    此模块用于同步显示,游戏的复杂度,计算机的不同时刻或者不同计算机运行游戏时快时慢,这取决于CPU当时的负载情况和处理能力,从而使游戏画面刷新率时高时低,这影响了游戏的体验效果。帧速率锁定按照某个最大帧速率进行同步,从而改善游戏画面的呈现。
  • 游戏结束
    此模块在游戏退出时运行,用于清理游戏资源并释放内存空间。
   以上粗略地描述了游戏框架的基本模块,然而并没有完整地实现各种功能,下面将屏蔽其他模块,把焦点放到主游戏框架上,实现一个通用的游戏框架类。

二、游戏框架实现

   在项目开始阶段,需要定义一些基本的编码规范:
  • 类的命名:以C(Console)字母开头,如CGame,CDraw,CMatrix等。
  • 字段命名:以m_字母加下划线开头,如m_draw,m_dc_mouse等。
  • 方法命名:以小写字母开头,如run(),draw()等。
  以上只是我个人喜好的编写方式,写出来方便大家阅读我的代码,在项目中并不出现C#的属性字段,我将所有的属性写成了更清晰的方法形式(
它们本身就是方法嘛),至于其他的,也没有什么特别的了。

  框架静态结构


  • ICGame接口定义一个run方法,主程序与游戏主体的消息通讯通过这个接口来进行,隐藏了具体游戏框架的细节。
  • CGame类为主要的游戏框架类,提供游戏需要的方法和事件等,并提供一系列抽象和虚拟的方法,提供给游戏派生类具体实现,为游戏的实现提供一个基本的统一的代码架构。
  • CGameEngine类封装了ICGame接口,以方便主程序的调用。
  • CRun类为程序的入口点,与CGameEngine类发生通讯,启动并运行具体的游戏实例。
  ///ICGame接口的实现
[csharp]  view plain copy print ?
  1. using System;  
  2.   
  3. namespace CEngine  
  4. {  
  5.     ///   
  6.     /// 游戏运行接口  
  7.     ///   
  8.     public interface ICGame  
  9.     {  
  10.         void run();  
  11.     }  
  12. }  
  ///CGame类实现
[csharp]  view plain copy print ?
  1. using System;  
  2. using System.Threading;  
  3.   
  4. namespace CEngine  
  5. {  
  6.     ///   
  7.     /// 通用游戏类  
  8.     ///   
  9.     public abstract class CGame : ICGame  
  10.     {  
  11.         #region 字段  
  12.   
  13.         ///   
  14.         /// 画面更新速率  
  15.         ///   
  16.         private Int32 m_updateRate;  
  17.         ///   
  18.         /// 当前帧数  
  19.         ///   
  20.         private Int32 m_fps;  
  21.         ///   
  22.         /// 记录帧数  
  23.         ///   
  24.         private Int32 m_tickcount;  
  25.         ///   
  26.         /// 记录上次运行时间  
  27.         ///   
  28.         private Int32 m_lastTime;  
  29.         ///   
  30.         /// 游戏是否结束  
  31.         ///   
  32.         private Boolean m_bGameOver;  
  33.  
  34.         #endregion  
  35.  
  36.         #region 构造函数  
  37.   
  38.         ///   
  39.         /// 构造函数  
  40.         ///   
  41.         public CGame()  
  42.         {  
  43.             m_bGameOver = false;  
  44.         }  
  45.  
  46.         #endregion  
  47.  
  48.         #region 游戏运行函数  
  49.   
  50.         ///   
  51.         /// 游戏初始化  
  52.         ///   
  53.         protected abstract void gameInit();  
  54.         ///   
  55.         /// 游戏逻辑  
  56.         ///   
  57.         protected abstract void gameLoop();  
  58.         ///   
  59.         /// 游戏结束  
  60.         ///   
  61.         protected abstract void gameExit();  
  62.  
  63.         #endregion  
  64.  
  65.         #region 游戏设置函数  
  66.   
  67.         ///   
  68.         /// 设置画面更新速率  
  69.         ///   
  70.         ///   
  71.         protected void setUpdateRate(Int32 rate)  
  72.         {  
  73.             this.m_updateRate = rate;  
  74.         }  
  75.   
  76.         ///   
  77.         /// 获取画面更新率  
  78.         ///   
  79.         ///   
  80.         protected Int32 getUpdateRate()  
  81.         {  
  82.             return 1000 / this.m_updateRate;  
  83.         }  
  84.   
  85.         ///   
  86.         /// 获取FPS  
  87.         ///   
  88.         ///   
  89.         protected Int32 getFPS()  
  90.         {  
  91.             return this.m_fps;  
  92.         }  
  93.   
  94.         ///   
  95.         /// 计算FPS  
  96.         ///   
  97.         private void setFPS()  
  98.         {  
  99.             Int32 ticks = Environment.TickCount;  
  100.             m_tickcount += 1;  
  101.             if (ticks - m_lastTime >= 1000)  
  102.             {  
  103.                 m_fps = m_tickcount;  
  104.                 m_tickcount = 0;  
  105.                 m_lastTime = ticks;  
  106.             }  
  107.         }  
  108.   
  109.         ///   
  110.         /// 延迟  
  111.         ///   
  112.         private void delay()  
  113.         {  
  114.             this.delay(1);  
  115.         }  
  116.   
  117.         protected void delay(Int32 time)  
  118.         {  
  119.             Thread.Sleep(time);  
  120.         }  
  121.   
  122.         ///   
  123.         /// 游戏结束  
  124.         ///   
  125.         ///   
  126.         protected void setGameOver(Boolean gameOver)  
  127.         {  
  128.             this.m_bGameOver = gameOver;  
  129.         }  
  130.   
  131.         ///   
  132.         /// 游戏是否结束  
  133.         ///   
  134.         ///   
  135.         protected Boolean isGameOver()  
  136.         {  
  137.             return this.m_bGameOver;  
  138.         }  
  139.   
  140.         ///   
  141.         /// 设置光标是否可见  
  142.         ///   
  143.         ///   
  144.         protected void setCursorVisible(Boolean visible)  
  145.         {  
  146.             Console.CursorVisible = visible;  
  147.         }  
  148.   
  149.         ///   
  150.         /// 设置控制台标题  
  151.         ///   
  152.         ///   
  153.         protected void setTitle(String title)  
  154.         {  
  155.             Console.Title = title;  
  156.         }  
  157.   
  158.         ///   
  159.         /// 获取控制台标题  
  160.         ///   
  161.         ///   
  162.         protected String getTitle()  
  163.         {  
  164.             return Console.Title;  
  165.         }  
  166.   
  167.   
  168.         ///   
  169.         /// 关闭游戏并释放资源  
  170.         ///   
  171.         private void close()  
  172.         {  
  173.   
  174.         }  
  175.  
  176.         #endregion  
  177.  
  178.         #region 游戏启动接口  
  179.   
  180.         ///   
  181.         /// 游戏运行  
  182.         ///   
  183.         public void run()  
  184.         {  
  185.             //游戏初始化  
  186.             this.gameInit();  
  187.   
  188.             Int32 startTime = 0;  
  189.             while (!this.isGameOver())  
  190.             {  
  191.                 //启动计时  
  192.                 startTime = Environment.TickCount;  
  193.                 //计算fps  
  194.                 this.setFPS();  
  195.                 //游戏逻辑  
  196.                 this.gameLoop();  
  197.                 //保持一定的FPS  
  198.                 while (Environment.TickCount - startTime < this.m_updateRate)  
  199.                 {  
  200.                     this.delay();  
  201.                 }  
  202.             }  
  203.   
  204.             //游戏退出  
  205.             this.gameExit();  
  206.             //释放游戏资源  
  207.             this.close();  
  208.         }  
  209.  
  210.         #endregion  
  211.     }  
  212. }  
  CGame类封装了几个游戏函数gameInit、gameLoop、gameExit,分别对应了游戏的初始化、游戏逻辑和游戏结束,设置为抽象方法,提供给派生类具体实现;其他的函数用于配置运行参数;然而最重要的还是run函数,是这个游戏框架的核心,封装了游戏运行的逻辑层次,这样就把实际驱动游戏的运行细节封装起来,使开发人员更注重具体的游戏设计而不用关心如何驱动游戏的细节,一定程度上提高了开发效率。
  游戏运行框架伪代码:
[csharp]  view plain copy print ?
  1. 游戏初始化  
  2. while(!游戏结束)  
  3. {    
  4.    …  
  5.    游戏逻辑  
  6.    …  
  7.    保持帧速率  
  8. }  
  9. 游戏结束  
  10. 清理资源释放内存空间  
   可以看到游戏的运行是靠一个循环来驱动的,在这里并没有完整的实现一个运行框架,没有游戏输入、游戏声音和游戏渲染部分,随着章节讲解的进行,这些内容将逐步出现,并一步步完善这个游戏框架。

  ///CGameEngine类实现
[csharp]  view plain copy print ?
  1. using System;  
  2.   
  3. namespace CEngine  
  4. {  
  5.     ///   
  6.     /// 游戏启动类  
  7.     ///   
  8.     public sealed class CGameEngine  
  9.     {  
  10.         public static void Run(ICGame game)  
  11.         {  
  12.             if (game == null)  
  13.             {  
  14.                 Console.Write("引擎未初始化!");  
  15.                 Console.ReadLine();  
  16.             }  
  17.             else  
  18.             {  
  19.                 game.run();  
  20.             }  
  21.         }  
  22.     }  
  23. }  
    ///CRun类实现
[csharp]  view plain copy print ?
  1. using System;  
  2. using CEngine;  
  3.   
  4. namespace CRun  
  5. {  
  6.     class CRun  
  7.     {  
  8.         static void Main(string[] args)  
  9.         {  
  10.             //CGameEngine.Run(new DemoGame());  
  11.         }  
  12.     }  
  13.   
  14.     //class DemoGame:CGame { }  
  15. }  
   由于还没有具体游戏实例,以上采取伪代码形式描述程序入口点如何启动一个游戏实例,实际情况按照以上格式修改即可。

三、游戏框架测试

   尽管游戏框架没有完整的实现,但是也可以测试它的运行情况,这里用一个每秒刷新一帧的小例子来说明游戏框架的使用方法和测试它的运行情况。
  ///TestGame类实现
[csharp]  view plain copy print ?
  1. using System;  
  2. using CEngine;  
  3.   
  4. namespace Game  
  5. {  
  6.     public class TestGame : CGame  
  7.     {  
  8.         private Int32 m_ticks;  
  9.         private Int32 m_lasttime;  
  10.   
  11.         ///   
  12.         /// 游戏初始化  
  13.         ///   
  14.         protected override void gameInit()  
  15.         {  
  16.             //设置游戏标题  
  17.             setTitle("游戏框架测试");  
  18.             //设置游戏画面刷新率 每毫秒一次  
  19.             setUpdateRate(1000);  
  20.             //设置光标隐藏  
  21.             setCursorVisible(false);  
  22.   
  23.             Console.WriteLine("游戏初始化成功!");  
  24.   
  25.             m_lasttime = Environment.TickCount;  
  26.         }  
  27.   
  28.         ///   
  29.         /// 游戏逻辑  
  30.         ///   
  31.         protected override void gameLoop()  
  32.         {  
  33.             if (m_ticks++ < 15)  
  34.             {  
  35.                 Console.WriteLine(string.Format("  游戏运行中,第{0}帧,耗时{1}ms", m_ticks, Environment.TickCount - m_lasttime));  
  36.                 m_lasttime = Environment.TickCount;  
  37.             }  
  38.             else  
  39.             {  
  40.                 setGameOver(true);  
  41.             }  
  42.         }  
  43.   
  44.         ///   
  45.         /// 游戏结束  
  46.         ///   
  47.         protected override void gameExit()  
  48.         {  
  49.             Console.WriteLine("游戏结束!");  
  50.             Console.ReadLine();  
  51.         }  
  52.     }  
  53. }  
  相应地,CRun类修改为:
[csharp]  view plain copy print ?
  1. using System;  
  2. using CEngine;  
  3. using Game;  
  4.   
  5. namespace CRun  
  6. {  
  7.     class CRun  
  8.     {  
  9.         static void Main(string[] args)  
  10.         {  
  11.             CGameEngine.Run(new TestGame());  
  12.         }  
  13.     }  
  14. }  
   测试结果为:

   可以看出TestGame类主要关注的是具体游戏的逻辑,而无须考虑游戏如何装载和如何运行的细节,从而把更多时间花在游戏的设计和用户体验上,而这,就是这个游戏框架的设计目的。

四、结语

   尽管框架还是如此简陋,但基本阐述了一个游戏运行框架的基本模型,而随着讲解的深入,框架将会逐步完善。

你可能感兴趣的:(C#游戏编程)