C#游戏编程:《控制台小游戏系列》之《三、游戏输入模块》

一、输入模块结构

   
  游戏输入模块包括对鼠标和键盘两种输入设备的处理,此模块主要为游戏框架提供鼠标操作和键盘操作能力,这些功能均以事件形式提供从而达到模块间消息传递的目的。
  模块间的消息传递结构图:

  ①、CGame(派生)对象订阅CKeyboard事件(如keydown、keyup等)。
  ②、CGame(派生)对象订阅CMouse事件(如mousemove、mousedown、mouseaway等)。
  ③、新的键盘消息抵达CKeyboard对象。
  ④、新的鼠标消息抵达CMouse对象。
  ⑤、CKeyboard或CMouse对象把相应的通知送到已订阅对象,这些对象按期望的方式处理键盘或鼠标的消息。

二、输入模块实现

   为了 在控制台实现鼠标和键盘的操作,首先引入几个Win32 API,供游戏输入类所用。
//////////////////////////////////////////////////////////////////////////////////////////////////////////

    函数原型:HWND FindWindow(LPCSTR lpClassName,LPCSTR lpWindowName)
    函数描述:寻找窗口列表中第一个符合指定条件的顶级窗口;
    参数信息:lpClassName指向包含了窗口类名的空中止(C语言)字串的指针;或设为零,表示接收任何类;
              lpWindowName指向包含了窗口文本(或标签)的空中止(C语言)字串的指针;或设为零,表示接收任何窗口标题;
    返回值:  找到窗口的句柄。如未找到相符窗口,则返回零。

//////////////////////////////////////////////////////////////////////////////////////////////////////////

    函数原型:SHORT GetAsyncKeyState(int vKey);
    函数描述:判断函数调用时指定虚拟键的状态;
    参数信息:vKey欲测试的虚拟键的键码;
    返回值:  自对GetAsyncKeyState函数的上一次调用以来,如键已被按过,则位0设为1;否则设为0。如键目前处于按下状态, 
              则位15设为1;如抬起,则为0。

//////////////////////////////////////////////////////////////////////////////////////////////////////////

    函数原型:BOOL GetCursorPos(LPPOINT lpPoint);
    函数描述:该函数检取光标的位置,以屏幕坐标表示;
    参数信息:POINT结构指针,该结构接收光标的屏幕坐标;
    返回值:  非零表示成功,零表示失败。

//////////////////////////////////////////////////////////////////////////////////////////////////////////

    函数原型:BOOL ScreenToClient(HWND hWnd, LPPOINT lpPoint);
    函数描述:该函数把屏幕上指定点的屏幕坐标转换成用户坐标;
    参数信息:hwnd一个窗口的句柄,该窗口定义了要使用的客户区坐标系统;
              lpPoint屏幕坐标系统中包含了屏幕点的结构;
    返回值:  非零表示成功,零表示失败。

//////////////////////////////////////////////////////////////////////////////////////////////////////////
  ///CInput类实现
[csharp]  view plain copy print ?
  1. using System;  
  2. using System.Runtime.InteropServices;  
  3. using CGraphics;  
  4.   
  5. namespace CEngine  
  6. {  
  7.     internal abstract class CInput  
  8.     {  
  9.         internal const Int32 KEY_STATE = 0x8000;  
  10.         ///   
  11.         /// 判断函数调用时指定虚拟键的状态  
  12.         ///   
  13.         ///   
  14.         ///   
  15.         [DllImport("User32.dll")]  
  16.         protected static extern Int16 GetAsyncKeyState(System.Int32 vKey);  
  17.         ///   
  18.         /// 获取光标位置  
  19.         ///   
  20.         ///   
  21.         ///   
  22.         [DllImport("User32.dll")]  
  23.         protected static extern Boolean GetCursorPos(out CPoint lpPoint);  
  24.         ///   
  25.         /// 屏幕坐标转换成工作区坐标  
  26.         ///   
  27.         ///   
  28.         ///   
  29.         ///   
  30.         [DllImport("User32.dll")]  
  31.         protected static extern Int16 ScreenToClient(IntPtr hwnd, out CPoint lpPoint);  
  32.     }  
  33. }  
  以上引用了以后讲解到的游戏渲染模块的相关结构CPoint,是一个表示位置的结构,既然这里要用到,就提前把它也写到这里。
  ///CPoint结构实现
[csharp]  view plain copy print ?
  1. using System;  
  2.   
  3. namespace CGraphics  
  4. {  
  5.     ///   
  6.     /// 位置结构  
  7.     ///   
  8.     public struct CPoint  
  9.     {  
  10.         private Int32 m_x;  
  11.         private Int32 m_y;  
  12.   
  13.         public CPoint(Int32 x, Int32 y)  
  14.         {  
  15.             this.m_x = x;  
  16.             this.m_y = y;  
  17.         }  
  18.   
  19.         public Int32 getX()  
  20.         {  
  21.             return this.m_x;  
  22.         }  
  23.   
  24.         public Int32 getY()  
  25.         {  
  26.             return this.m_y;  
  27.         }  
  28.   
  29.         public void setX(Int32 x)  
  30.         {  
  31.             this.m_x = x;  
  32.         }  
  33.   
  34.         public void setY(Int32 y)  
  35.         {  
  36.             this.m_y = y;  
  37.         }  
  38.   
  39.         public static Boolean operator ==(CPoint p1, CPoint p2)  
  40.         {  
  41.             return (p1.m_x == p2.m_x) && (p1.m_y == p2.m_y);  
  42.         }  
  43.   
  44.         public static Boolean operator !=(CPoint p1, CPoint p2)  
  45.         {  
  46.             return (p1.m_x != p2.m_x) || (p1.m_y != p2.m_y);  
  47.         }  
  48.   
  49.         public override bool Equals(object obj)  
  50.         {  
  51.             return this == (CPoint)obj;  
  52.         }  
  53.   
  54.         public override int GetHashCode()  
  55.         {  
  56.             return base.GetHashCode();  
  57.         }  
  58.   
  59.         public static CPoint operator -(CPoint p1, CPoint p2)  
  60.         {  
  61.             return new CPoint(p1.getX() - p2.getX(), p1.getY() - p2.getY());  
  62.         }  
  63.   
  64.         public static CPoint operator +(CPoint p1, CPoint p2)  
  65.         {  
  66.             return new CPoint(p1.getX() + p2.getX(), p1.getY() + p2.getY());  
  67.         }  
  68.   
  69.         public override string ToString()  
  70.         {  
  71.             return string.Format("[{0},{1}]", m_x, m_y);  
  72.         }  
  73.     }  
  74. }  
  ///CKeyboard类实现
[csharp]  view plain copy print ?
  1. using System;  
  2.   
  3. namespace CEngine  
  4. {  
  5.     ///   
  6.     /// 键盘键值  
  7.     ///   
  8.     public enum CKeys  
  9.     {  
  10.         A = 0x41,  
  11.         Add = 0x6b,  
  12.         Alt = 0x40000,  
  13.         Apps = 0x5d,  
  14.         Attn = 0xf6,  
  15.         B = 0x42,  
  16.         Back = 8,  
  17.         BrowserBack = 0xa6,  
  18.         BrowserFavorites = 0xab,  
  19.         BrowserForward = 0xa7,  
  20.         BrowserHome = 0xac,  
  21.         BrowserRefresh = 0xa8,  
  22.         BrowserSearch = 170,  
  23.         BrowserStop = 0xa9,  
  24.         C = 0x43,  
  25.         Cancel = 3,  
  26.         Capital = 20,  
  27.         CapsLock = 20,  
  28.         Clear = 12,  
  29.         Control = 0x20000,  
  30.         ControlKey = 0x11,  
  31.         Crsel = 0xf7,  
  32.         D = 0x44,  
  33.         D0 = 0x30,  
  34.         D1 = 0x31,  
  35.         D2 = 50,  
  36.         D3 = 0x33,  
  37.         D4 = 0x34,  
  38.         D5 = 0x35,  
  39.         D6 = 0x36,  
  40.         D7 = 0x37,  
  41.         D8 = 0x38,  
  42.         D9 = 0x39,  
  43.         Decimal = 110,  
  44.         Delete = 0x2e,  
  45.         Divide = 0x6f,  
  46.         Down = 40,  
  47.         E = 0x45,  
  48.         End = 0x23,  
  49.         Enter = 13,  
  50.         EraseEof = 0xf9,  
  51.         Escape = 0x1b,  
  52.         Execute = 0x2b,  
  53.         Exsel = 0xf8,  
  54.         F = 70,  
  55.         F1 = 0x70,  
  56.         F10 = 0x79,  
  57.         F11 = 0x7a,  
  58.         F12 = 0x7b,  
  59.         F13 = 0x7c,  
  60.         F14 = 0x7d,  
  61.         F15 = 0x7e,  
  62.         F16 = 0x7f,  
  63.         F17 = 0x80,  
  64.         F18 = 0x81,  
  65.         F19 = 130,  
  66.         F2 = 0x71,  
  67.         F20 = 0x83,  
  68.         F21 = 0x84,  
  69.         F22 = 0x85,  
  70.         F23 = 0x86,  
  71.         F24 = 0x87,  
  72.         F3 = 0x72,  
  73.         F4 = 0x73,  
  74.         F5 = 0x74,  
  75.         F6 = 0x75,  
  76.         F7 = 0x76,  
  77.         F8 = 0x77,  
  78.         F9 = 120,  
  79.         FinalMode = 0x18,  
  80.         G = 0x47,  
  81.         H = 0x48,  
  82.         HanguelMode = 0x15,  
  83.         HangulMode = 0x15,  
  84.         HanjaMode = 0x19,  
  85.         Help = 0x2f,  
  86.         Home = 0x24,  
  87.         I = 0x49,  
  88.         IMEAccept = 30,  
  89.         IMEAceept = 30,  
  90.         IMEConvert = 0x1c,  
  91.         IMEModeChange = 0x1f,  
  92.         IMENonconvert = 0x1d,  
  93.         Insert = 0x2d,  
  94.         J = 0x4a,  
  95.         JunjaMode = 0x17,  
  96.         K = 0x4b,  
  97.         KanaMode = 0x15,  
  98.         KanjiMode = 0x19,  
  99.         KeyCode = 0xffff,  
  100.         L = 0x4c,  
  101.         LaunchApplication1 = 0xb6,  
  102.         LaunchApplication2 = 0xb7,  
  103.         LaunchMail = 180,  
  104.         LControlKey = 0xa2,  
  105.         Left = 0x25,  
  106.         LineFeed = 10,  
  107.         LMenu = 0xa4,  
  108.         LShiftKey = 160,  
  109.         LWin = 0x5b,  
  110.         M = 0x4d,  
  111.         MediaNextTrack = 0xb0,  
  112.         MediaPlayPause = 0xb3,  
  113.         MediaPreviousTrack = 0xb1,  
  114.         MediaStop = 0xb2,  
  115.         Menu = 0x12,  
  116.         Modifiers = -65536,  
  117.         Multiply = 0x6a,  
  118.         N = 0x4e,  
  119.         Next = 0x22,  
  120.         NoName = 0xfc,  
  121.         None = 0,  
  122.         NumLock = 0x90,  
  123.         NumPad0 = 0x60,  
  124.         NumPad1 = 0x61,  
  125.         NumPad2 = 0x62,  
  126.         NumPad3 = 0x63,  
  127.         NumPad4 = 100,  
  128.         NumPad5 = 0x65,  
  129.         NumPad6 = 0x66,  
  130.         NumPad7 = 0x67,  
  131.         NumPad8 = 0x68,  
  132.         NumPad9 = 0x69,  
  133.         O = 0x4f,  
  134.         Oem1 = 0xba,  
  135.         Oem102 = 0xe2,  
  136.         Oem2 = 0xbf,  
  137.         Oem3 = 0xc0,  
  138.         Oem4 = 0xdb,  
  139.         Oem5 = 220,  
  140.         Oem6 = 0xdd,  
  141.         Oem7 = 0xde,  
  142.         Oem8 = 0xdf,  
  143.         OemBackslash = 0xe2,  
  144.         OemClear = 0xfe,  
  145.         OemCloseBrackets = 0xdd,  
  146.         Oemcomma = 0xbc,  
  147.         OemMinus = 0xbd,  
  148.         OemOpenBrackets = 0xdb,  
  149.         OemPeriod = 190,  
  150.         OemPipe = 220,  
  151.         Oemplus = 0xbb,  
  152.         OemQuestion = 0xbf,  
  153.         OemQuotes = 0xde,  
  154.         OemSemicolon = 0xba,  
  155.         Oemtilde = 0xc0,  
  156.         P = 80,  
  157.         Pa1 = 0xfd,  
  158.         Packet = 0xe7,  
  159.         PageDown = 0x22,  
  160.         PageUp = 0x21,  
  161.         Pause = 0x13,  
  162.         Play = 250,  
  163.         Print = 0x2a,  
  164.         PrintScreen = 0x2c,  
  165.         Prior = 0x21,  
  166.         ProcessKey = 0xe5,  
  167.         Q = 0x51,  
  168.         R = 0x52,  
  169.         RControlKey = 0xa3,  
  170.         Return = 13,  
  171.         Right = 0x27,  
  172.         RMenu = 0xa5,  
  173.         RShiftKey = 0xa1,  
  174.         RWin = 0x5c,  
  175.         S = 0x53,  
  176.         Scroll = 0x91,  
  177.         Select = 0x29,  
  178.         SelectMedia = 0xb5,  
  179.         Separator = 0x6c,  
  180.         Shift = 0x10000,  
  181.         ShiftKey = 0x10,  
  182.         Sleep = 0x5f,  
  183.         Snapshot = 0x2c,  
  184.         Space = 0x20,  
  185.         Subtract = 0x6d,  
  186.         T = 0x54,  
  187.         Tab = 9,  
  188.         U = 0x55,  
  189.         Up = 0x26,  
  190.         V = 0x56,  
  191.         VolumeDown = 0xae,  
  192.         VolumeMute = 0xad,  
  193.         VolumeUp = 0xaf,  
  194.         W = 0x57,  
  195.         X = 0x58,  
  196.         XButton1 = 5,  
  197.         XButton2 = 6,  
  198.         Y = 0x59,  
  199.         Z = 90,  
  200.         Zoom = 0xfb  
  201.     }  
  202.   
  203.     ///   
  204.     /// 键盘类  
  205.     ///   
  206.     internal sealed class CKeyboard : CInput  
  207.     {  
  208.         ///   
  209.         /// 键盘事件委托  
  210.         ///   
  211.         ///   
  212.         ///   
  213.         internal delegate void CKeyboardHandler(TEventArgs e);  
  214.         ///   
  215.         /// 键盘按下事件  
  216.         ///   
  217.         private event CKeyboardHandler m_keyDown;  
  218.         ///   
  219.         /// 键盘释放事件  
  220.         ///   
  221.         private event CKeyboardHandler m_keyUp;  
  222.         ///   
  223.         /// 上次按下的键值  
  224.         ///   
  225.         private CKeys m_oldKey=CKeys.None;  
  226.   
  227.         ///   
  228.         /// 构造函数  
  229.         ///   
  230.         public CKeyboard()  
  231.         {  
  232.   
  233.         }  
  234.   
  235.         ///   
  236.         /// 是否按下键  
  237.         ///   
  238.         ///   
  239.         ///   
  240.         private Boolean isKeyDown(CKeys vKey)  
  241.         {  
  242.             return 0 != (GetAsyncKeyState((Int32)vKey) & KEY_STATE);  
  243.         }  
  244.   
  245.         ///   
  246.         /// 获取键盘按下的键值  
  247.         ///   
  248.         ///   
  249.         private CKeys getCurKeyboardDownKey()  
  250.         {  
  251.             CKeys vKye = CKeys.None;  
  252.             foreach (Int32 key in Enum.GetValues(typeof(CKeys)))  
  253.             {  
  254.                 if (isKeyDown((CKeys)key))  
  255.                 {  
  256.                     vKye = (CKeys)key;  
  257.                     break;  
  258.                 }  
  259.             }  
  260.             return vKye;  
  261.         }  
  262.   
  263.         ///   
  264.         /// 响应键盘按下事件  
  265.         ///   
  266.         ///   
  267.         private void onKeyDown(CKeyboardEventArgs e)  
  268.         {  
  269.             CKeyboardHandler temp = m_keyDown;  
  270.             if (temp != null)  
  271.             {  
  272.                 temp.Invoke(e);  
  273.             }  
  274.         }  
  275.   
  276.         ///   
  277.         /// 响应键盘释放事件  
  278.         ///   
  279.         ///   
  280.         private void onKeyUp(CKeyboardEventArgs e)  
  281.         {  
  282.             CKeyboardHandler temp = m_keyUp;  
  283.             if (temp != null)  
  284.             {  
  285.                 temp.Invoke(e);  
  286.             }  
  287.         }  
  288.   
  289.         ///   
  290.         /// 添加键盘按下事件  
  291.         ///   
  292.         ///   
  293.         public void addKeyDownEvent(CKeyboardHandler func)  
  294.         {  
  295.             m_keyDown += func;  
  296.         }  
  297.   
  298.         ///   
  299.         /// 添加键盘释放事件  
  300.         ///   
  301.         ///   
  302.         public void addKeyUpEvent(CKeyboardHandler func)  
  303.         {  
  304.             this.m_keyUp += func;  
  305.         }  
  306.   
  307.         ///   
  308.         /// 键盘事件处理  
  309.         ///   
  310.         public void keyboardEventsHandler()  
  311.         {  
  312.             CKeyboardEventArgs e;  
  313.             CKeys vKeyDown = getCurKeyboardDownKey();    
  314.      
  315.             if (vKeyDown != CKeys.None)  
  316.             {  
  317.                 this.m_oldKey = vKeyDown;  
  318.                 e = new CKeyboardEventArgs(vKeyDown);  
  319.                 this.onKeyDown(e);  
  320.             }  
  321.             else if (m_oldKey != CKeys.None && !isKeyDown(this.m_oldKey))  
  322.             {  
  323.                 e = new CKeyboardEventArgs(this.m_oldKey);  
  324.                 this.onKeyUp(e);  
  325.                 this.m_oldKey = CKeys.None;  
  326.             }  
  327.         }  
  328.     }  
  329. }  
  很明显,键盘类事件委托用到一个CKeyboardEventArgs类型事件参数,CKeyboardEventArgs类定义为:
  ///CKeyboardEventArgs类实现
[csharp]  view plain copy print ?
  1. using System;  
  2. namespace CEngine  
  3. {  
  4.     public sealed class CKeyboardEventArgs : EventArgs  
  5.     {  
  6.         private CKeys m_keys;  
  7.   
  8.         public CKeyboardEventArgs(CKeys keys)  
  9.         {  
  10.             this.m_keys = keys;  
  11.         }  
  12.   
  13.         public CKeys getKey()  
  14.         {  
  15.             return m_keys;  
  16.         }  
  17.     }  
  18. }  
  CKeyboard提供了addKeyDownEvent和addKeyUpEvent函数,用于其他对象订阅键盘事件,keyboardEventsHandler捕获键盘消息并响应相应的事件,把通知发送给订阅事件的对象,这样订阅者就可以按照它的期望处理相应的事件行为了。
  了解了键盘类的实现,下面再看看鼠标类,和键盘类雷同,鼠标类也为其他对象提供几个鼠标事件,以供其他对象订阅,并自行处理鼠标事件。
  ///CMouse类实现
[csharp]  view plain copy print ?
  1. using System;  
  2. using CGraphics;  
  3.   
  4. namespace CEngine  
  5. {  
  6.     ///   
  7.     /// 鼠标键值  
  8.     ///   
  9.     [Flags]  
  10.     public enum CMouseButtons  
  11.     {  
  12.         Left = 0x01,  
  13.         Middle = 0x04,  
  14.         None = 0,  
  15.         Right = 0x02  
  16.     }  
  17.   
  18.     ///   
  19.     /// 鼠标类  
  20.     ///   
  21.     internal sealed class CMouse : CInput  
  22.     {  
  23.         ///   
  24.         /// 鼠标事件委托  
  25.         ///   
  26.         ///   
  27.         ///   
  28.         internal delegate void CMouseHandler(TEventArgs e);  
  29.         ///   
  30.         /// 鼠标移动事件  
  31.         ///   
  32.         private event CMouseHandler m_mouseMove;  
  33.         ///   
  34.         /// 鼠标离开事件  
  35.         ///   
  36.         private event CMouseHandler m_mouseAway;  
  37.         ///   
  38.         /// 鼠标按下事件  
  39.         ///   
  40.         private event CMouseHandler m_mouseDwon;  
  41.   
  42.         ///   
  43.         /// 最大X值  
  44.         ///   
  45.         private readonly Int32 MAX_X = 639;  
  46.         ///   
  47.         /// 最大Y值  
  48.         ///   
  49.         private readonly Int32 MAX_Y = 400;  
  50.   
  51.         ///   
  52.         /// 控制台句柄  
  53.         ///   
  54.         private IntPtr m_hwnd = IntPtr.Zero;  
  55.         ///   
  56.         /// 鼠标离开工作区范围前的位置  
  57.         ///   
  58.         private CPoint m_oldPoint;  
  59.         ///   
  60.         /// 鼠标是否离开工作区范围  
  61.         ///   
  62.         private Boolean m_leave;  
  63.   
  64.         ///   
  65.         /// 构造函数  
  66.         ///   
  67.         public CMouse(IntPtr hwnd)  
  68.         {  
  69.             this.m_hwnd = hwnd;  
  70.             this.m_oldPoint = new CPoint(0, 0);  
  71.             this.m_leave = false;  
  72.   
  73.             this.MAX_X = (Console.WindowWidth <<3) - 1;  
  74.             this.MAX_Y = Console.WindowHeight <<4;  
  75.         }  
  76.  
  77.         #region 鼠标函数  
  78.   
  79.         ///   
  80.         /// 是否按下鼠标  
  81.         ///   
  82.         ///   
  83.         ///   
  84.         private Boolean isMouseDown(CMouseButtons vKey)  
  85.         {  
  86.             return 0 != (GetAsyncKeyState((Int32)vKey) & KEY_STATE);  
  87.         }  
  88.   
  89.         ///   
  90.         /// 获取鼠标当前按下的键值  
  91.         ///   
  92.         ///   
  93.         private CMouseButtons getCurMouseDownKeys()  
  94.         {  
  95.             CMouseButtons vKey = CMouseButtons.None;  
  96.             foreach (Int32 key in Enum.GetValues(typeof(CMouseButtons)))  
  97.             {  
  98.                 if (isMouseDown((CMouseButtons)key))  
  99.                 {  
  100.                     //可以同时按下多个键  
  101.                     vKey |= (CMouseButtons)key;  
  102.                 }  
  103.             }  
  104.             return vKey;  
  105.         }  
  106.   
  107.         ///   
  108.         /// 获取鼠标列坐标  
  109.         ///   
  110.         ///   
  111.         private Int32 getMouseX()  
  112.         {  
  113.             return getMousePoint().getX();  
  114.         }  
  115.   
  116.         ///   
  117.         /// 获取鼠标行坐标  
  118.         ///   
  119.         ///   
  120.         private Int32 getMouseY()  
  121.         {  
  122.             return getMousePoint().getY();  
  123.         }  
  124.   
  125.         ///   
  126.         /// 是否离开工作区  
  127.         ///   
  128.         ///   
  129.         private Boolean isLeave()  
  130.         {  
  131.             return m_leave;  
  132.         }  
  133.   
  134.         ///   
  135.         /// 获取鼠标坐标  
  136.         ///   
  137.         ///   
  138.         private CPoint getMousePoint()  
  139.         {  
  140.             CPoint point;  
  141.             //获取鼠标在屏幕的位置  
  142.             if (GetCursorPos(out point))  
  143.             {  
  144.                 if (m_hwnd != IntPtr.Zero)  
  145.                 {  
  146.                     //把屏幕位置转换成控制台工作区位置   
  147.                     ScreenToClient(m_hwnd, out point);  
  148.   
  149.                     if ((point.getX() >= 0 && point.getX() <= MAX_X)  
  150.                         && point.getY() >= 0 && point.getY() <= MAX_Y)  
  151.                     {  
  152.                         this.m_oldPoint = point;  
  153.                         this.m_leave = false;  
  154.                     }  
  155.                     else  
  156.                     {  
  157.                         m_leave = true;  
  158.                     }  
  159.                 }  
  160.             }  
  161.             return m_oldPoint;  
  162.         }  
  163.  
  164.         #endregion  
  165.  
  166.         #region 鼠标事件  
  167.   
  168.         ///   
  169.         /// 响应鼠标移动事件  
  170.         ///   
  171.         ///   
  172.         private void onMouseMove(CMouseEventArgs e)  
  173.         {  
  174.             CMouseHandler temp = m_mouseMove;  
  175.             if (temp != null)  
  176.             {  
  177.                 temp.Invoke(e);  
  178.             }  
  179.         }  
  180.   
  181.         ///   
  182.         /// 响应鼠标离开事件  
  183.         ///   
  184.         ///   
  185.         private void onMouseAway(CMouseEventArgs e)  
  186.         {  
  187.             CMouseHandler temp = m_mouseAway;  
  188.             if (temp != null)  
  189.             {  
  190.                 temp.Invoke(e);  
  191.             }  
  192.         }  
  193.   
  194.         ///   
  195.         /// 响应鼠标按下事件  
  196.         ///   
  197.         ///   
  198.         private void onMouseDown(CMouseEventArgs e)  
  199.         {  
  200.             CMouseHandler temp = m_mouseDwon;  
  201.             if (temp != null)  
  202.             {  
  203.                 temp.Invoke(e);  
  204.             }  
  205.         }  
  206.   
  207.         ///   
  208.         /// 添加鼠标移动事件  
  209.         ///   
  210.         ///   
  211.         public void addMouseMoveEvent(CMouseHandler func)  
  212.         {  
  213.             m_mouseMove += func;  
  214.         }  
  215.   
  216.         ///   
  217.         /// 添加鼠标离开事件  
  218.         ///   
  219.         ///   
  220.         public void addMouseAwayEvent(CMouseHandler func)  
  221.         {  
  222.             m_mouseAway += func;  
  223.         }  
  224.   
  225.         ///   
  226.         /// 添加鼠标按下事件  
  227.         ///   
  228.         ///   
  229.         public void addMouseDownEvent(CMouseHandler func)  
  230.         {  
  231.             m_mouseDwon += func;  
  232.         }  
  233.   
  234.         ///   
  235.         ///鼠标事件处理  
  236.         ///   
  237.         public void mouseEventsHandler()  
  238.         {  
  239.             CMouseEventArgs e;  
  240.   
  241.             CPoint point = getMousePoint();  
  242.   
  243.             CMouseButtons vKey = getCurMouseDownKeys();  
  244.             if (!isLeave())  
  245.             {  
  246.                 if (vKey != CMouseButtons.None)  
  247.                 {  
  248.                     e = new CMouseEventArgs(point.getX(), point.getY(), vKey);  
  249.                     this.onMouseDown(e);  
  250.                 }  
  251.   
  252.                 e = new CMouseEventArgs(point.getX(), point.getY(), false);  
  253.                 this.onMouseMove(e);  
  254.             }  
  255.             else  
  256.             {  
  257.                 e = new CMouseEventArgs(-1, -1, true);  
  258.                 this.onMouseAway(e);  
  259.             }  
  260.         }  
  261.  
  262.         #endregion  
  263.     }  
  264. }  
  鼠标类事件委托用到一个CMouseEventArgs类型事件参数,用来传递鼠标当前的位置等信息,CMouseEventArgs类定义为:
///CMouseEventArgs类实现
[csharp]  view plain copy print ?
  1. using System;  
  2.   
  3. namespace CEngine  
  4. {  
  5.     ///   
  6.     /// 鼠标事件参数  
  7.     ///   
  8.     public sealed class CMouseEventArgs : EventArgs  
  9.     {  
  10.         private Int32 m_x;  
  11.         private Int32 m_y;  
  12.         private Boolean m_leave;  
  13.         private CMouseButtons m_vKey;  
  14.   
  15.         public CMouseEventArgs(Int32 x, Int32 y, Boolean leave)  
  16.         {  
  17.             this.m_x = x;  
  18.             this.m_y = y;  
  19.             this.m_leave = leave;  
  20.         }  
  21.   
  22.         public CMouseEventArgs(Int32 x, Int32 y, CMouseButtons key)  
  23.         {  
  24.             this.m_x = x;  
  25.             this.m_y = y;  
  26.             this.m_vKey = key;  
  27.         }  
  28.   
  29.         public Int32 getX()  
  30.         {  
  31.             return m_x;  
  32.         }  
  33.   
  34.         public Int32 getY()  
  35.         {  
  36.             return m_y;  
  37.         }  
  38.   
  39.         public Boolean isLeave()  
  40.         {  
  41.             return m_leave;  
  42.         }  
  43.   
  44.         public CMouseButtons getKey()  
  45.         {  
  46.             return m_vKey;  
  47.         }  
  48.   
  49.         public bool containKey(CMouseButtons key)  
  50.         {  
  51.             return (getKey() & key) == key;  
  52.         }  
  53.   
  54.         public override string ToString()  
  55.         {  
  56.             return string.Format("{0},{1}", getX(), getY());  
  57.         }  
  58.     }  
  59. }  

三、完善游戏框架

   至此,游戏输入模块基本完成了,应该能满足多数游戏的控制需求,我们把这些功能封装到游戏框架中去,以方便我们控制游戏中的逻辑。回到游戏框架类,进一步完善之:
///CGame类实现
[csharp]  view plain copy print ?
  1. using System;  
  2. using System.Threading;  
  3. using System.Runtime.InteropServices;  
  4.   
  5. namespace CEngine  
  6. {  
  7.     ///   
  8.     /// 通用游戏类  
  9.     ///   
  10.     public abstract class CGame : ICGame  
  11.     {  
  12.         #region Api函数  
  13.   
  14.         [DllImport("User32.dll")]  
  15.         private static extern IntPtr FindWindow(String lpClassName, String lpWindowName);  
  16.  
  17.         #endregion  
  18.  
  19.         #region 字段  
  20.         ///   
  21.         /// 控制台句柄  
  22.         ///   
  23.         private IntPtr m_hwnd = IntPtr.Zero;  
  24.   
  25.         //……略  
  26.   
  27.         ///   
  28.         /// 鼠标输入设备  
  29.         ///   
  30.         private CMouse m_dc_mouse;  
  31.         ///   
  32.         /// 键盘输入设备  
  33.         ///   
  34.         private CKeyboard m_dc_keyboard;  
  35.  
  36.         #endregion  
  37.  
  38.         #region 构造函数  
  39.   
  40.         ///   
  41.         /// 构造函数  
  42.         ///   
  43.         public CGame()  
  44.         {  
  45.             //……略  
  46.   
  47.             m_hwnd = FindWindow(null, getTitle());  
  48.             m_dc_mouse = new CMouse(m_hwnd);  
  49.             m_dc_keyboard = new CKeyboard();  
  50.   
  51.             //订阅鼠标事件  
  52.             m_dc_mouse.addMouseMoveEvent(gameMouseMove);  
  53.             m_dc_mouse.addMouseAwayEvent(gameMouseAway);  
  54.             m_dc_mouse.addMouseDownEvent(gameMouseDown);  
  55.   
  56.             //订阅键盘事件  
  57.             m_dc_keyboard.addKeyDownEvent(gameKeyDown);  
  58.             m_dc_keyboard.addKeyUpEvent(gameKeyUp);  
  59.         }  
  60.  
  61.         #endregion  
  62.  
  63.         #region 游戏运行函数  
  64.   
  65.         //……略  
  66.   
  67.         ///   
  68.         /// 游戏输入  
  69.         ///   
  70.         private void gameInput()  
  71.         {  
  72.             //处理鼠标事件  
  73.             this.getMouseDevice().mouseEventsHandler();  
  74.             //处理键盘事件  
  75.             this.getKeyboardDevice().keyboardEventsHandler();  
  76.         }  
  77.   
  78.         //……略  
  79.  
  80.         #endregion  
  81.   
  82.         //……略  
  83.  
  84.         #region 游戏输入设备  
  85.   
  86.         ///   
  87.         /// 获取鼠标设备  
  88.         ///   
  89.         ///   
  90.         internal CMouse getMouseDevice()  
  91.         {  
  92.             return m_dc_mouse;  
  93.         }  
  94.   
  95.         ///   
  96.         /// 获取键盘设备  
  97.         ///   
  98.         ///   
  99.         internal CKeyboard getKeyboardDevice()  
  100.         {  
  101.             return m_dc_keyboard;  
  102.         }  
  103.  
  104.         #endregion  
  105.  
  106.         #region 游戏输入事件  
  107.   
  108.         ///   
  109.         /// 鼠标移动虚拟函数  
  110.         ///   
  111.         ///   
  112.         protected virtual void gameMouseMove(CMouseEventArgs e)  
  113.         {  
  114.             //此处处理鼠标移动事件  
  115.         }  
  116.   
  117.         ///   
  118.         /// 鼠标离开虚拟函数  
  119.         ///   
  120.         ///   
  121.         protected virtual void gameMouseAway(CMouseEventArgs e)  
  122.         {  
  123.             //此处处理鼠标离开事件  
  124.         }  
  125.   
  126.         ///   
  127.         /// 鼠标按下虚拟函数  
  128.         ///   
  129.         ///   
  130.         protected virtual void gameMouseDown(CMouseEventArgs e)  
  131.         {  
  132.             //此处处理鼠标按下事件  
  133.         }  
  134.   
  135.         ///   
  136.         /// 键盘按下虚拟函数  
  137.         ///   
  138.         ///   
  139.         protected virtual void gameKeyDown(CKeyboardEventArgs e)  
  140.         {  
  141.             //此处处理键盘按下事件  
  142.         }  
  143.   
  144.         ///   
  145.         /// 键盘释放虚拟函数  
  146.         ///   
  147.         ///   
  148.         protected virtual void gameKeyUp(CKeyboardEventArgs e)  
  149.         {  
  150.             //此处处理键盘释放事件  
  151.         }  
  152.  
  153.         #endregion  
  154.  
  155.         #region 游戏启动接口  
  156.   
  157.         ///   
  158.         /// 游戏运行  
  159.         ///   
  160.         public void run()  
  161.         {  
  162.             //游戏初始化  
  163.             this.gameInit();  
  164.   
  165.             Int32 startTime = 0;  
  166.             while (!this.isGameOver())  
  167.             {  
  168.                 //启动计时  
  169.                 startTime = Environment.TickCount;  
  170.                 //计算fps  
  171.                 this.setFPS();  
  172.                 //游戏输入  
  173.                 this.gameInput();  
  174.                 //游戏逻辑  
  175.                 this.gameLoop();  
  176.                 //保持一定的FPS  
  177.                 while (Environment.TickCount - startTime < this.m_updateRate)  
  178.                 {  
  179.                     this.delay();  
  180.                 }  
  181.             }  
  182.   
  183.             //游戏退出  
  184.             this.gameExit();  
  185.             //释放游戏资源  
  186.             this.close();  
  187.         }  
  188.  
  189.         #endregion  
  190.     }  
  191. }  
  由于篇幅有限,与上章相同部分代码就以“略”来替代,此处增加了游戏输入类的函数gameInput,在游戏循环中监听键盘和鼠标的输入;订阅了鼠标和键盘的相应事件,以虚拟函数形式提供给派生类处理将要发生的事件行为。

四、测试输入模块

   修改上章的TestGame类,以测试游戏的输入控制功能。
  ///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 Boolean m_bkeydown = false;  
  9.   
  10.         ///   
  11.         /// 游戏初始化  
  12.         ///   
  13.         protected override void gameInit()  
  14.         {  
  15.             //设置游戏标题  
  16.             setTitle("游戏框架测试");  
  17.             //设置游戏画面刷新率 每毫秒一次  
  18.             setUpdateRate(30);  
  19.             //设置光标隐藏  
  20.             setCursorVisible(false);  
  21.   
  22.             Console.WriteLine("游戏初始化成功!");  
  23.         }  
  24.   
  25.         ///   
  26.         /// 游戏逻辑  
  27.         ///   
  28.         protected override void gameLoop()  
  29.         {  
  30.   
  31.         }  
  32.   
  33.         ///   
  34.         /// 游戏结束  
  35.         ///   
  36.         protected override void gameExit()  
  37.         {  
  38.             Console.WriteLine("游戏结束!");  
  39.             Console.ReadLine();  
  40.         }  
  41.   
  42.         protected override void gameKeyDown(CKeyboardEventArgs e)  
  43.         {  
  44.             if (!m_bkeydown)  
  45.             {  
  46.                 Console.WriteLine("按下键:" + e.getKey());  
  47.   
  48.                 m_bkeydown = true;  
  49.             }  
  50.   
  51.             if (e.getKey() == CKeys.Escape)  
  52.             {  
  53.                 setGameOver(true);  
  54.             }  
  55.         }  
  56.   
  57.         protected override void gameKeyUp(CKeyboardEventArgs e)  
  58.         {  
  59.             Console.WriteLine("释放键:" + e.getKey());  
  60.             m_bkeydown = false;  
  61.         }  
  62.   
  63.         protected override void gameMouseAway(CMouseEventArgs e)  
  64.         {  
  65.             setTitle("鼠标离开了工作区!");  
  66.         }  
  67.   
  68.         protected override void gameMouseDown(CMouseEventArgs e)  
  69.         {  
  70.             if (e.getKey() == CMouseButtons.Left)  
  71.             {  
  72.                 Console.SetCursorPosition(15, 2);  
  73.                 Console.WriteLine("鼠标工作区坐标:" + e.ToString() + "  " + e.getKey().ToString());  
  74.             }  
  75.             else if (e.getKey() == CMouseButtons.Right)  
  76.             {  
  77.                 Console.SetCursorPosition(15, 3);  
  78.                 Console.WriteLine("鼠标工作区坐标:" + e.ToString() + "  " + e.getKey().ToString());  
  79.             }  
  80.             else if (e.getKey() == CMouseButtons.Middle)  
  81.             {  
  82.                 Console.SetCursorPosition(15, 4);  
  83.                 Console.WriteLine("鼠标工作区坐标:" + e.ToString() + "  " + e.getKey().ToString());  
  84.             }  
  85.         }  
  86.   
  87.         protected override void gameMouseMove(CMouseEventArgs e)  
  88.         {  
  89.             setTitle("鼠标回到了工作区!");  
  90.         }  
  91.     }  
  92. }  

  测试结果截图:

  以上结果为依次按下方向键、ESC键和在工作区内单击鼠标左键、右键和中间键引发相应事件所得到的数据,当鼠标离开工作区范围时,控制台标题将会变成“鼠标离开了工作区”。

五、结语

   本章实现了游戏的输入,包括了键盘的输入和鼠标的输入,完善了游戏框架的功能,在下一章,我们将要实现游戏的渲染模块,从而告别这毫无色彩的世界,并将进一步完善我们的游戏框架。

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