游戏编程入门(2):创建游戏引擎和使用该引擎制作小游戏

本文提供一个完整的游戏引擎代码,并有详细代码解析。后续将根据这个游戏引擎开发小游戏,逐渐完善该引擎并介绍游戏编程相关的知识。

假设你起码拥有C++的知识。

以《游戏编程入门》 Micheal Morrison为基本教材,PDF书籍下载,光盘内容下载。

开发环境为Win7,VC 6++ 企业版

接上文 游戏编程入门(1):游戏专业概论


搭建开发环境

VC 6++ 企业版下载

解压后,进入文件夹内,点击AUTORUN.EXE进行安装。

无毒,建议把360关了,老提醒些有的没的,一般就是下一步下一步,没什么难的。

安装过程不会的话,点击这里

测试VC 6++

安装好了后,我们进行一个简单的测试,测试VC 6++是否正常运作

点击文件,新建,选择工程中的Win32 Console Application(Win32控制台程序)

游戏编程入门(2):创建游戏引擎和使用该引擎制作小游戏_第1张图片

选择 一个“Hello World”程序

游戏编程入门(2):创建游戏引擎和使用该引擎制作小游戏_第2张图片

初始文件结构如下:

游戏编程入门(2):创建游戏引擎和使用该引擎制作小游戏_第3张图片

进入Test.cpp。编译,出现错误。

C1083: Cannot open precompiled header file: ‘Debug/Test.pch’: No such file or directory 报错

错误原因

预编译头文件还没有编译

解决方法

右键点击项目工程中的该cpp文件,选择setting(设置),在c/c++栏,,选择PreCompiled headers(预编译的头文件),然后设置第一项(选择不使用预编译头选项)。

游戏编程入门(2):创建游戏引擎和使用该引擎制作小游戏_第4张图片

再次编译,问题解决

VC6++打开,添加文件,会崩溃 的问题

点击打开文件,或者添加文件,会导致崩溃。

错误原因

VC6与新版本windows不兼容

解决方法

(1)下载 FileTool.dll 文件,将下载的FileTool.rar文件解压得到FileToll.dll,然后将FileToll.dll放到VC目录下的 \Common\MSDev98\AddIns 文件夹中,如图:

游戏编程入门(2):创建游戏引擎和使用该引擎制作小游戏_第5张图片

(2)打开VC6,点击“工具”菜单,选择“定制”,打开定制对话框,如图:

游戏编程入门(2):创建游戏引擎和使用该引擎制作小游戏_第6张图片

(3)在“定制”对话框中选择“附加项和宏文件”,勾选“File Tool……”项(如果没有该项的话,点击“浏览”,定位到刚才的FileTool.dll文件并打开,就会出现该项),完成后点击关闭,如图:

游戏编程入门(2):创建游戏引擎和使用该引擎制作小游戏_第7张图片

(4)关闭“定制”对话框后,就会出现下图所示的两个按钮,分别是对应“打开”和“向工程中添加文件”的功能。

游戏编程入门(2):创建游戏引擎和使用该引擎制作小游戏_第8张图片

现在就可以正常使用了,但是点击原来的“打开”或者Ctrl+O依旧会崩溃(没啥影响)

强迫症患者可以继续去该Blog解决问题->
http://www.cnblogs.com/leftshine/p/5211605.html

游戏引擎 源代码

先给出游戏引擎源代码。后面开始进行分析。(不习惯的朋友可以先看后面的分析,再来看这部分源代码)

GameEngine 分为 GameEngine.h 和 GameEngine.cpp。

在头文件GameEngine.h中我们存储功能函数、数据接口的声明,而在GameEngine.cpp中我们具体实现一部分。

GameEngine.h 源代码

#pragma once /*该头文件仅编译一次(因为同一头文件会在许多源文件中多次引用。如
果没有指定编译一次,则编译时出现重定义错误。*/

//-----------------------------------------------------------------
//包含的头文件 
//-----------------------------------------------------------------
#include 

//-----------------------------------------------------------------
//Windows函数声明 
//-----------------------------------------------------------------
/*WinMain函数应初始化应用程序,显示主窗口,进入一个消息接收一发送循环,
这个循环是应用程序执行的其余部分的顶级控制结构。*/ 
int WINAPI        WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow);
//窗口过程,指向一个应用程序定义的窗口过程的指针。                   
LRESULT CALLBACK  WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);

//-----------------------------------------------------------------
//游戏事件函数声明 
//-----------------------------------------------------------------
BOOL GameInitialize(HINSTANCE hInstance);//初始化游戏 
void GameStart(HWND hWindow);            //启动游戏 
void GameEnd();                          //结束游戏 
void GameActivate(HWND hWindow);         //激活游戏 
void GameDeactivate(HWND hWindow);       //停用游戏 
void GamePaint(HDC hDC);                 //绘制游戏 
void GameCycle();                        //循环游戏 

//-----------------------------------------------------------------
// GameEngine类定义 
//-----------------------------------------------------------------
class GameEngine
{
protected:
  //成员变量
  static GameEngine*  m_pGameEngine; //指向自身的静态指针,用于游戏程序的外部访问
  HINSTANCE           m_hInstance;           //应用程序实例句柄
  HWND                m_hWindow;             //主窗口句柄
  TCHAR               m_szWindowClass[32];   //窗口类的名称
  TCHAR               m_szTitle[32];         //主游戏窗口的名称
  WORD                m_wIcon, m_wSmallIcon; //游戏的两个程序图标的数字ID
  int                 m_iWidth, m_iHeight;   //游戏屏幕的宽度和高度
  int                 m_iFrameDelay;         //游戏周期之间的间隔,单位是ms
  BOOL                m_bSleep;              //表示游戏是否在休眠

public:
  //构造函数和析构函数

  //游戏引擎构造函数使用默认的屏幕大小(640*480)创建游戏,这是实际的游戏区
          GameEngine(HINSTANCE hInstance, LPTSTR szWindowClass, LPTSTR szTitle,
            WORD wIcon, WORD wSmallIcon, int iWidth = 640, int iHeight = 480);
  virtual ~GameEngine();

  //常规方法

  //在引擎外部使用这个静态方法访问指向引擎的静态指针 
  static GameEngine*  GetEngine() 
  { 
         return m_pGameEngine; 
  };
  //创建引擎后,初始化游戏程序 
  BOOL                Initialize(int iCmdShow);
  //处理引擎内的标准Windows事件 
  LRESULT             HandleEvent(HWND hWindow, UINT msg, WPARAM wParam,LPARAM lParam);

  //访问方法
  HINSTANCE GetInstance() 
  { 
            return m_hInstance; 
  };
  HWND      GetWindow() 
  { 
            return m_hWindow; 
  };
  void      SetWindow(HWND hWindow) 
  { 
            m_hWindow = hWindow; 
  };
  LPTSTR    GetTitle() 
  { 
            return m_szTitle; 
  };
  WORD      GetIcon() 
  { 
            return m_wIcon; 
  };
  WORD      GetSmallIcon() 
  {
             return m_wSmallIcon; 
  };
  int       GetWidth() 
  {
             return m_iWidth; 
  };
  int       GetHeight() 
  { 
            return m_iHeight; 
  };
  int       GetFrameDelay() 
  { 
            return m_iFrameDelay; 
  };
  //指定帧速率,当值为30时会使游戏以30帧/秒的速率运行
  void      SetFrameRate(int iFrameRate) 
  { 
            m_iFrameDelay = 1000 /iFrameRate; 
  };
  BOOL      GetSleep() 
  { 
            return m_bSleep; 
  };
  void      SetSleep(BOOL bSleep) 
  { 
            m_bSleep = bSleep; 
  };
};

GameEngine.cpp 源代码


//-----------------------------------------------------------------
//包含的头文件
//-----------------------------------------------------------------
#include "GameEngine.h"

//-----------------------------------------------------------------
// 初始化静态的游戏指针 
//-----------------------------------------------------------------
GameEngine *GameEngine::m_pGameEngine = NULL;

//-----------------------------------------------------------------
// Windows函数  
//-----------------------------------------------------------------
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
  PSTR szCmdLine, int iCmdShow)
{
  MSG         msg;
  static int  iTickTrigger = 0;
  int         iTickCount;

  if (GameInitialize(hInstance)) //通过调用GameInitialize()初始化游戏 
  {
    // 初始化游戏引擎 
    if (!GameEngine::GetEngine()->Initialize(iCmdShow))
      return FALSE;

    // 进入主消息循环 
    while (TRUE)
    {
      if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
      {
        //处理消息 
        if (msg.message == WM_QUIT)
          break;
        TranslateMessage(&msg);
        DispatchMessage(&msg);
      }
      else  //这段代码创建游戏的计时机制 
      {
        // 确保游戏引擎没有休眠 
        if (!GameEngine::GetEngine()->GetSleep())
        {
          // 检查滴答计数,查看是否过了一个游戏周期 
          iTickCount = GetTickCount();
          if (iTickCount > iTickTrigger)
          {
            iTickTrigger = iTickCount +
              GameEngine::GetEngine()->GetFrameDelay();
            GameCycle();
          }
        }
      }
    }
    return (int)msg.wParam;
  }

  // 结束游戏 
  GameEnd();

  return TRUE;
}

LRESULT CALLBACK WndProc(HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam)
{
  // 将所有Windows消息传递给游戏引擎 
  return GameEngine::GetEngine()->HandleEvent(hWindow, msg, wParam, lParam);
}

//-----------------------------------------------------------------
// GameEngine的构造函数和析构函数 
//-----------------------------------------------------------------
GameEngine::GameEngine(HINSTANCE hInstance, LPTSTR szWindowClass,
  LPTSTR szTitle, WORD wIcon, WORD wSmallIcon, int iWidth, int iHeight)
{
  // 设置游戏引擎的成员变量 
  m_pGameEngine = this;
  m_hInstance = hInstance;
  m_hWindow = NULL;
  if (lstrlen(szWindowClass) > 0)
    lstrcpy(m_szWindowClass, szWindowClass);
  if (lstrlen(szTitle) > 0)
    lstrcpy(m_szTitle, szTitle);
  m_wIcon = wIcon;
  m_wSmallIcon = wSmallIcon;
  m_iWidth = iWidth;
  m_iHeight = iHeight;
  m_iFrameDelay = 50;   // 默认为20帧每秒(50ms/1000=20帧/秒) 
  m_bSleep = TRUE;
}

GameEngine::~GameEngine()
{
}

//-----------------------------------------------------------------
// Game Engine 常规方法 
//-----------------------------------------------------------------
//Initialize方法处理一些通常在WinMain()中执行的杂乱方法 
BOOL GameEngine::Initialize(int iCmdShow)
{
  WNDCLASSEX    wndclass;

  // 创建主窗口的窗口类 
  wndclass.cbSize         = sizeof(wndclass);
  wndclass.style          = CS_HREDRAW | CS_VREDRAW;
  wndclass.lpfnWndProc    = WndProc;
  wndclass.cbClsExtra     = 0;
  wndclass.cbWndExtra     = 0;
  wndclass.hInstance      = m_hInstance;
  wndclass.hIcon          = LoadIcon(m_hInstance,
    MAKEINTRESOURCE(GetIcon()));
  wndclass.hIconSm        = LoadIcon(m_hInstance,
    MAKEINTRESOURCE(GetSmallIcon()));
  wndclass.hCursor        = LoadCursor(NULL, IDC_ARROW);
  wndclass.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
  wndclass.lpszMenuName   = NULL;
  wndclass.lpszClassName  = m_szWindowClass;

  // 注册窗口类 
  if (!RegisterClassEx(&wndclass))
    return FALSE;

  /* 根据游戏大小计算窗口大小和位置 (这段代码允许确定实际游戏屏幕的确切大小,
  这个大小与整个应用程序窗口的大小不同) */
  int iWindowWidth = m_iWidth + GetSystemMetrics(SM_CXFIXEDFRAME) * 2,
      iWindowHeight = m_iHeight + GetSystemMetrics(SM_CYFIXEDFRAME) * 2 +
        GetSystemMetrics(SM_CYCAPTION);
  if (wndclass.lpszMenuName != NULL)
    iWindowHeight += GetSystemMetrics(SM_CYMENU);
  int iXWindowPos = (GetSystemMetrics(SM_CXSCREEN) - iWindowWidth) / 2,
      iYWindowPos = (GetSystemMetrics(SM_CYSCREEN) - iWindowHeight) / 2;

  // 创建窗口 
  m_hWindow = CreateWindow(m_szWindowClass, m_szTitle, WS_POPUPWINDOW |
    WS_CAPTION | WS_MINIMIZEBOX, iXWindowPos, iYWindowPos, iWindowWidth,
    iWindowHeight, NULL, NULL, m_hInstance, NULL);
  if (!m_hWindow)
    return FALSE;

  // 显示和更新窗口 
  ShowWindow(m_hWindow, iCmdShow);
  UpdateWindow(m_hWindow);

  return TRUE;
}

//接受并处理通常在WinProc()中处理的消息 
LRESULT GameEngine::HandleEvent(HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam)
{
  // 将Windows消息传递给游戏引擎成员函数 
  switch (msg)
  {
    case WM_CREATE:
      // 设置游戏窗口并开始游戏 
      SetWindow(hWindow);
      GameStart(hWindow);  //响应WM_CREATE消息,开始游戏 
      return 0;

    case WM_SETFOCUS:
      // 激活游戏并更新休眠状态 
      GameActivate(hWindow);
      SetSleep(FALSE);
      return 0;

    case WM_KILLFOCUS:
      // 停用游戏并更新休眠状态 
      GameDeactivate(hWindow);
      SetSleep(TRUE);
      return 0;

    case WM_PAINT:
      HDC         hDC;
      PAINTSTRUCT ps;
      hDC = BeginPaint(hWindow, &ps);

      // 绘制游戏 
      GamePaint(hDC);      //响应WM_PAINT消息,绘制游戏 

      EndPaint(hWindow, &ps);
      return 0;

    case WM_DESTROY:
      // 结束游戏并退出应用程序 
      GameEnd();
      PostQuitMessage(0);  //响应WM_DESTROY消息,结束游戏 
      return 0;
  }
  return DefWindowProc(hWindow, msg, wParam, lParam);
}

游戏引擎 解析

游戏引擎是什么

游戏引擎是指一些已编写好的可编辑电脑游戏系统或者一些交互式实时图像应用程序的核心组件。这些系统为游戏设计者提供各种编写游戏所需的各种工具,其目的在于让游戏设计者能容易和快速地做出游戏程序而不用由零开始。

此处自行编写的游戏引擎包含两部分:GameEngine.h 和 GameEngine.cpp。

下面给出两部分的结构图,方便理解。

GameEngine.h 结构图

游戏编程入门(2):创建游戏引擎和使用该引擎制作小游戏_第9张图片

GameEngine.cpp 结构图

游戏编程入门(2):创建游戏引擎和使用该引擎制作小游戏_第10张图片

游戏事件函数

将游戏分解为事件

每一个Windows程序都可以分解为事件,即在程序运行时发生的事情,例如单击鼠标和更改窗口大小。正如Windows程序包含必须处理的事件一样,游戏也有其在开发过程中必须考虑的一组特有事件。可以将游戏的初始化过程视为一个事件,它负责加载游戏的图形和声音、清空游戏区域、将得分归零等。

类似的,游戏中也有用户输入,意味着单击鼠标和按键操作是游戏必须自己处理的事件。此外还要牢记一点,在Windows中,可以最小化某些游戏或者将其置于后台,意味着可能希望暂停游戏。可以使用两个事件来表示这个激活和停用过程。

许多事件当然都可以包括在游戏引擎中,但是下面这些事件适用于任何游戏的部分核心事件。

  • 初始化
  • 启动
  • 结束
  • 激活
  • 停用
  • 绘制
  • 循环

初始化事件在一开始运行游戏时发生,这时游戏将执行重要的初始设置任务,包括创建游戏引擎本身。启动和结束事件对应于游戏的开始和结束,这时很适合执行与特定的游戏会话相关联的初始化和清理任务。

在最小化游戏或者将其发送到后台,然后再恢复时,就将发生激活和停用事件。当游戏需要绘制自身时,将发送绘制事件,类似于Windows WM_PAINT消息,最后,循环事件使游戏执行一个单独的游戏周期。

当游戏中发生一个事件时,就会调用相应的事件处理程序,使游戏有机会做相应的响应。下面列出了这些函数,它们直接对应于游戏事件。

Bool GameInitialize(HINSTANCE hInstance); //初始化游戏
void GameStart(HWND hWindow);             //启动游戏
void GameEnd();                           //结束游戏
void GameActivate(HWND hWindow);          //激活游戏
void GameDeactivate(HWND hWindow);        //停用游戏
void GamePaint(HDC hDC);                  //绘制游戏
void GameCycle();                         //循环游戏

HINSTANCE 数据类型

第一个函数GameInitialize(),属于HINSTANCE类型的hInstance参数。

H表示句柄,HINSTANCE对应的资源是instance。

HINSTANCE数据类型应用程序实例的句柄,相当于装入到了内存的资源ID。它允许程序访问其资源(因为资源与应用程序一起存储在内存中)。句柄实际上是一个无符号长整数。

应用程序实例实际上是已经加载到内存中并在Windows中运行的一个程序。如果使用过Alt+Tab组合键来切换Windows中正在运行的应用程序,那么就会很熟悉不同的应用程序实例。

HWND 数据类型

h 是类型描述,表示句柄(handle), Wnd 是变量对象描述,表示窗口,所以hWnd 表示窗口句柄

Windows 运行环境,通过给应用程序中的每个窗体和控件分配一个句柄(或 hWnd)来标识它们。hWnd 属性用于Windows API调用。许多 Windows 运行环境函数需要活动窗口的 hWnd 作为参数。

HDC 数据类型

MFC中的设备上下文句柄,封装了用于绘制线条等的 Windows API。

GameEngine类

虽然在游戏事件处理程序与游戏引擎之间存在紧密的联系,但是实际上是独立的。因为就结构来说,将游戏引擎放在它自己的C++类中会更好。这个类名为GameEngine。

class GameEngine
{
protected:
  //成员变量
  static GameEngine*  m_pGameEngine; //指向自身的静态指针,用于游戏程序的外部访问
  HINSTANCE           m_hInstance;           //应用程序实例句柄
  HWND                m_hWindow;             //主窗口句柄
  TCHAR               m_szWindowClass[32];   //窗口类的名称
  TCHAR               m_szTitle[32];         //主游戏窗口的名称
  WORD                m_wIcon, m_wSmallIcon; //游戏的两个程序图标的数字ID
  int                 m_iWidth, m_iHeight;   //游戏屏幕的宽度和高度
  int                 m_iFrameDelay;         //游戏周期之间的间隔,单位是ms
  BOOL                m_bSleep;              //表示游戏是否在休眠

public:
  //构造函数和析构函数

  //游戏引擎构造函数使用默认的屏幕大小(640*480)创建游戏,这是实际的游戏区
          GameEngine(HINSTANCE hInstance, LPTSTR szWindowClass, LPTSTR szTitle,
            WORD wIcon, WORD wSmallIcon, int iWidth = 640, int iHeight = 480);
  virtual ~GameEngine();

  //常规方法

  //在引擎外部使用这个静态方法访问指向引擎的静态指针 
  static GameEngine*  GetEngine() 
  { 
         return m_pGameEngine; 
  };
  //创建引擎后,初始化游戏程序 
  BOOL                Initialize(int iCmdShow);
  //处理引擎内的标准Windows事件 
  LRESULT             HandleEvent(HWND hWindow, UINT msg, WPARAM wParam,
  LPARAM lParam);

  //访问方法
  HINSTANCE GetInstance() 
  { 
            return m_hInstance; 
  };
  HWND      GetWindow() 
  { 
            return m_hWindow; 
  };
  void      SetWindow(HWND hWindow) 
  { 
            m_hWindow = hWindow; 
  };
  LPTSTR    GetTitle() 
  { 
            return m_szTitle; 
  };
  WORD      GetIcon() 
  { 
            return m_wIcon; 
  };
  WORD      GetSmallIcon() 
  {
             return m_wSmallIcon; 
  };
  int       GetWidth() 
  {
             return m_iWidth; 
  };
  int       GetHeight() 
  { 
            return m_iHeight; 
  };
  int       GetFrameDelay() 
  { 
            return m_iFrameDelay; 
  };
  //指定帧速率,当值为30时会使游戏以30帧/秒的速率运行
  void      SetFrameRate(int iFrameRate) 
  { 
            m_iFrameDelay = 1000 /iFrameRate; 
  };
  BOOL      GetSleep() 
  { 
            return m_bSleep; 
  };
  void      SetSleep(BOOL bSleep) 
  { 
            m_bSleep = bSleep; 
  };
};

命名类的成员变量时使用m_开始,表示他们是类成员。此外,在命名全局变量时以g_开始,表示变量时全局的。

以下对出现在代码中几个数据类型,做解释。具体的函数,将会在后面介绍。

TCHAR 数据类型

TCHAR是windows中定义的一个类型不是标准C中的,TCHAR是通过define定义的字符串宏。

因为C++支持两种字符串,即常规的ANSI编码(使用”“包裹)和Unicode编码(使用L”“包裹),这样对应的就有了两套字符串处理函数,比如:strlen和wcslen,分别用于处理两种字符串。

为了存储这样的通用字符,就有了TCHAR:
当没有定义_UNICODE宏时,TCHAR = char,_tcslen =strlen
当定义了_UNICODE宏时,TCHAR = wchar_t , _tcslen = wcslen

WORD 数据类型

不是C++标准的类型,是微软SDK中的类型,WORD的意思为字,是2byte的无符号整数,表示范围0~65535。

LPTSTR 数据类型

表示指向字符/字符串的指针

LP:长指针(long pointer)。
T:win32环境中有一个_T宏,用来标识字符是否采用Unicode编码(两字节表示一个字符),若程序中定义了Unicode,该字符/字符串被作为Unicode字符串,否则就是标准的ANSI(单字节表示一个字符)字符串。
STR:表示这个变量是一个字符串。

LRESULT 数据类型

LRESULT就是long result,也就是长整型结果值,指的是从窗口程序或者回调函数返回的32位值

UINT 数据类型

UINT类型在WINDOWS API中有定义,它对应于32位无符号整数,代表消息名称

WPARAM 数据类型

WPARAM的实质是消息响应机制中的一个参数。例如对鼠标消息而言,WPARAM参数中包含了发生该消息时,SHIFT、CTRL等键的状态信息

LPARAM 数据类型

LPARAM的实质是消息响应机制中的一个参数。例如对鼠标消息而言,一般包含鼠标的位置信息。

消息机制

Windows中发生的一切都可以用消息来表示,消息用于告诉操作系统发生了什么,所有的Windows应用程序都是消息驱动的。

一个消息是由消息的名称(UINT)和两个参数(WPARAM, LPARAM)组成。消息的参数中包含有重要的信息。例如对鼠标消息而言,LPARAM中一般包含鼠标的位置信息,而WPARAM参数中包含了发生该消息时,SHIFT、CTRL等键的状态信息,对于不同的消息类型来说,两个参数也都相应地具有明确意义。

1、消息的组成:一个消息由一个消息名称(UINT),和两个参数(WPARAM,LPARAM)构成。当用户进行了输入或是窗口的状态发生改变时系统都会发送消息到某一个窗口。例如当菜单选中之后会有WM_COMMAND消息发送,WPARAM的高字中(HIWORD(wParam))是命令的ID号,对菜单来讲就是菜单ID。当然用户也可以定义自己的消息名称,也可以利用自定义消息来发送通知和传送数据。

2、谁将收到消息一个消息必须由一个窗口接收。在窗口的过程(WNDPROC)中可以对消息进行分析,对自己感兴趣的消息进行处理。例如你希望对菜单选择进行处理那么你可以定义对WM_COMMAND进行处理的代码,如果希望在窗口中进行图形输出就必须对WM_PAINT进行处理。

3、未处理的消息到那里去了:M$为窗口编写了默认的窗口过程,这个窗口过程将负责处理那些你不处理消息。正因为有了这个默认窗口过程我们才可以利用Windows的窗口进行开发而不必过多关注窗口各种消息的处理。例如窗口在被拖动时会有很多消息发送,而我们都可以不予理睬让系统自己去处理。

4、窗口句柄:说到消息就不能不说窗口句柄,系统通过窗口句柄来在整个系统中唯一标识一个窗口,发送一个消息时必须指定一个窗口句柄表明该消息由那个窗口接收。而每个窗口都会有自己的窗口过程,所以用户的输入就会被正确的处理。例如有两个窗口共用一个窗口过程代码,你在窗口一上按下鼠标时消息就会通过窗口一的句柄被发送到窗口一而不是窗口二。

5、示例:下面有一段伪代码演示如何在窗口过程中处理消息

LONG yourWndProc(HWND hWnd,UINT uMessageType,WPARAM wP,LPARAM)
{
switch(uMessageType)
{
//使用SWITCH语句将各种消息分开
    case(WM_PAINT):
        doYourWindow(...);//在窗口需要重新绘制时进行输出
        break;
    case(WM_LBUTTONDOWN):
        doYourWork(...);//在鼠标左键被按下时进行处理
        break;
    default:
        callDefaultWndProc(...);//对于其它情况就让系统自己处理
        break;
}
}

接下来谈谈什么是消息循环:系统将会维护一个或多个消息队列,所有产生的消息都会被放入或是插入队列中。系统会在队列中取出每一条消息,根据消息的接收句柄而将该消息发送给拥有该窗口的程序的消息循环。每一个运行的程序都有自己的消息循环,在循环中得到属于自己的消息并根据接收窗口的句柄调用相应的窗口过程。而在没有消息时消息循环就将控制权交给系统所以Windows可以同时进行多个任务。

下面的伪代码演示了消息循环的用法

while(1)
{
    id=getMessage(...);
    if(id == quit)
        break;
    translateMessage(...);
}

当该程序没有消息通知时getMessage就不会返回,也就不会占用系统的CPU时间。

WinMain( )

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
  PSTR szCmdLine, int iCmdShow)
{
  MSG         msg;
  static int  iTickTrigger = 0;
  int         iTickCount;

  if (GameInitialize(hInstance)) //通过调用GameInitialize()初始化游戏 
  {
    // 初始化游戏引擎 
    if (!GameEngine::GetEngine()->Initialize(iCmdShow))
      return FALSE;

    // 进入主消息循环 
    while (TRUE)
    {
      if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
      {
        //处理消息 
        if (msg.message == WM_QUIT)
          break;
        TranslateMessage(&msg);
        DispatchMessage(&msg);
      }
      else  //这段代码创建游戏的计时机制 
      {
        // 确保游戏引擎没有休眠 
        if (!GameEngine::GetEngine()->GetSleep())
        {
          // 检查滴答计数,查看是否过了一个游戏周期 
          iTickCount = GetTickCount();
          if (iTickCount > iTickTrigger)
          {
            iTickTrigger = iTickCount +
              GameEngine::GetEngine()->GetFrameDelay();
            GameCycle();
          }
        }
      }
    }
    return (int)msg.wParam;
  }

  // 结束游戏 
  GameEnd();

  return TRUE;
}

WinMain是一个函数,该函数的功能是被系统调用,作为一个32位应用程序的入口点

WinMain函数应初始化应用程序,显示主窗口,进入一个消息接收一发送循环,这个循环是应用程序执行的其余部分的顶级控制结构。

虽然这个WinMain函数与所有Windows应用程序中的相似,但是存在一个重要的区别。这个区别就是,这个WinMain函数创建了一个游戏循环,处理以指定的时间间隔生成游戏周期事件的任务。

Windows程序中最小事件单元是滴答(Tick),它相当于1ms,对执行精确的计时任务很有用。在这里,WinMain以滴答计数,以便确定应该何时通知游戏开始一个新的周期。iTickTrigger和iTickCount变量用来创建WinMain中的游戏循环计时机制。

在WinMain中调用的第一个函数是GameInitialize( ),它初始化游戏。GameInitialize( )是一个游戏事件函数,属于这个游戏的专用代码,因此它不是游戏引擎的直接组成部分。属于游戏引擎部分的方法是Initialize( ),调用它来初始化游戏引擎本身。

WINAPI 数据类型

该WinMain函数,返回的是WINAPI,即视窗操作系统应用程序接口,是微软对于Windows操作系统中可用的内核应用程序编程接口的称法。它设计为由C/C++程序调用,而且它也是应用软件与Windows系统最直接的交互方式。

PSTR 数据类型

PSTR的字面意思是指向字符串的指针,但是由于32位的普及,从Visual C++ 6.0开始它们完全相同,没有任何区别,只是由于习惯大家还分别在不同的地方使用它们。
即:LPSTR=PSTR=char *(完全等同)

对WinMain函数参数的分析

HINSTANCE hInstance:应用程序当前实例的句柄

HINSTANCE hPrevInstance:应用程序的先前实例的句柄。对于同一个程序打开两次,出现两个窗口第一次打开的窗口就是先前实例的窗口。对于一个32位程序,该参数总为NULL。

PSTR szCmdLine:指向应用程序命令行的字符串的指针,不包括执行文件名。

例如:在D盘下有一个sunxin.txt文件,当我们用鼠标双击这个文件时将启动记事本程序(notepad.exe),此时系统会将D:\sunxin.txt作为命令行参数传递给记事本程序的WinMain函数,记事本程序在得到这个文件的全路径名后,就在窗口中显示该文件的内容。

int iCmdShow:指明窗口如何显示。

该参数可以是下列值之一:

SW_HIDE:隐藏窗口并且激活另外一个窗口。
SW_MINIMIZE:最小化指定的窗口,并且激活在系统表中的顶层窗口。
SW_RESTORE:激活并显示窗口。如果窗口已经最小化或最大化,系统将以恢复到原来的尺寸和位置显示窗口(与SW_SHOWNORMAL相同)。
SW_SHOW:激活一个窗口并以原来的尺寸和位置显示窗口。
SW_SHOWMAXIMIZED:激活窗口并且将其最大化。
SW_SHOWMINIMIZED:激活窗口并将其最小化(以图标显示)。
SW_SHOWMINNOACTIVE:将一个窗口显示为图标。激活窗口维持活动状态。
SW_SHOWNA:以窗口的当前状态显示窗口。激活窗口保持活动状态。
SW_SHOWNOACTIVATE:以窗口的最近一次的尺寸和位置显示窗口。不激活窗口。
SW_SHOWNORMAL:激活并显示窗口。如果窗口最大化或最小化,系统将其恢复到原来的尺寸和位置(与SW_RESTORE相同)。

如果函数成功,当它接收到一个WM_QUIT消息时就中止,函数应该返回在该消息的wParam参数的退出值(指定消息的附加值)。如果函数在进入消息循环前退出,应该返回零。

MSG 数据类型

MSG是Windows程序中的结构体

在Windows程序中,消息是由MSG结构体来表示的。MSG结构体的定义如下

typedef struct tagMSG {
    HWND hwnd;
    UINT message;
    WPARAM wParam;
    LPARAM lParam;
    DWORD time;
    POINT pt;
} MSG;

该结构体中各成员变量的含义如下:

第一个成员变量hwnd是窗口句柄,表示消息所属的窗口。我们通常开发的程序都是窗口应用程序,一个消息一般都是与某个窗口相关联的。例如,在某个活动窗口中按下鼠标左键,产生的按键消息就是发给该窗口的。在Windows程序中,用HWND类型的变量来标识窗口。

第二个成员变量message指定了消息的标识符(消息名称),UINT型为32位无符号整数类型在Windows中,消息是由一个数值来表示的,不同的消息对应不同的数值。但是由于数值不便于记忆,所以Windows将消息对应的数值定义为WM_XXX宏(WM是Window Message的缩写)的形式,XXX对应某种消息的英文拼写的大写形式。例如,鼠标左键按下消息是WM_LBUTTONDOWN,键盘按下消息是WM_KEYDOWN,字符消息是WM_CHAR,等等。在程序中我们通常都是以WM_XXX宏的形式来使用消息的。

提示:如果想知道WM_XXX消息对应的具体数值,可以在Visual C++开发环境中选中WM_XXX,然后单击鼠标右键,在弹出菜单中选择goto definition,即可看到该宏的具体定义。跟踪或查看某个变量的定义,都可以使用这个方法。

第三、第四个成员变量wParam和lParam,用于指定消息的附加信息。例如,当我们收到一个字符消息的时候,message成员变量的值就是WM_CHAR,但用户到底输入的是什么字符,那么就由wParam和lParam来说明。wParam、lParam表示的信息随消息的不同而不同。如果想知道这两个成员变量具体表示的信息,可以在MSDN中关于某个具体消息的说明文档查看到。读者可以在VC++的开发环境中通过goto definition查看一下WPARAM和LPARAM这两种类型的定义,可以发现这两种类型实际上就是unsigned int和long。

最后两个变量分别表示消息投递到消息队列中的时间和鼠标的当前位置。

PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)

PeekMessage是一个Windows API函数。该函数为一个消息检查 线程消息队列,并将该消息(如果存在)放于指定的结构。

函数原型为:

BOOL PeekMessage(
    LPMSG IpMsg,
    HWND hWnd,
    UINT wMSGfilterMin,
    UINT wMsgFilterMax,
    UINT wRemoveMsg
);

lpMsg:接收消息信息的MSG结构指针。

hWnd:其消息被检查的窗口句柄。

wMsgFilterMin:指定被检查的消息范围里的第一个消息。

wMsgFilterMax:指定被检查的消息范围里的最后一个消息。

wRemoveMsg:确定消息如何被处理。

此参数可取下列值之一:

PM_NOREMOVE PeekMessage处理后,消息不从队列里除掉。

PM_REMOVE PeekMessage处理后,消息从队列里除掉。

PM_NOYIELD 此标志使系统不释放等待调用程序空闲的线程。可将PM_NOYIELD随意组合到PM_NOREMOVE或PM_REMOVE。

TranslateMessage(&msg);

TranslateMessage函数用于将虚拟键消息转换为字符消息。

函数原型

BOOL TranslateMessage( CONST MSG*lpMsg );

IpMsg:指向含有消息的MSG结构的指针。

返回值:如果消息被转换(即,字符消息被寄送到调用线程的消息队列里),返回非零值。

如果消息是WM_KEYDOWN,WM_KEYUP WM_SYSKEYDOWN或WM_SYSKEYUP,返回非零值,不考虑转换。

如果消息没被转换(即,字符消息没被寄送到调用线程的消息队列里),返回值是零。

DispatchMessage(&msg);

该函数分发一个消息给窗口程序。

函数原型

LONG DispatchMessage(CONST MSG*lpmsg);

参数
lpmsg:指向含有消息的MSG结构的指针。

返回值:返回值是窗口程序返回的值。尽管返回值的含义依赖于被调度的消息,但返回值通常被忽略。

GetTickCount( )

GetTickCount返回从操作系统启动所经过的毫秒数,它的返回值是DWORD。

WndProc( )

LRESULT CALLBACK WndProc(HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam)
{
  // 将所有Windows消息传递给游戏引擎 
  return GameEngine::GetEngine()->HandleEvent(hWindow, msg, wParam, lParam);
}

Windows是消息驱动的。所以消息将发往窗口中,而在窗口的过程(WNDPROC)中可以对消息进行分析,对自己感兴趣的消息进行处理。

WndProc所做的事情实际上就是将所有消息都传递给HandleEvent。这乍看起来是浪费时间。不过,思路是允许GameEngine类的方法处理消息。这样就可以使用一种与游戏引擎一致的方法处理它们。

GameEngine::GameEngine( )

GameEngine::GameEngine(HINSTANCE hInstance, LPTSTR szWindowClass,
  LPTSTR szTitle, WORD wIcon, WORD wSmallIcon, int iWidth, int iHeight)
{
  // 设置游戏引擎的成员变量 
  m_pGameEngine = this;
  m_hInstance = hInstance;
  m_hWindow = NULL;
  if (lstrlen(szWindowClass) > 0)
    lstrcpy(m_szWindowClass, szWindowClass);
  if (lstrlen(szTitle) > 0)
    lstrcpy(m_szTitle, szTitle);
  m_wIcon = wIcon;
  m_wSmallIcon = wSmallIcon;
  m_iWidth = iWidth;
  m_iHeight = iHeight;
  m_iFrameDelay = 50;   // 默认为20帧每秒(50ms/1000=20帧/秒) 
  m_bSleep = TRUE;
}

GameEngine::GameEngine( )构造函数负责初始化游戏引擎成员变量。

GameEngine::~GameEngine( )

GameEngine::~GameEngine()
{
}

析构函数保持为空,以备将来使用。

GameEngine::Initialize( )

BOOL GameEngine::Initialize(int iCmdShow)
{
  WNDCLASSEX    wndclass;

  // 创建主窗口的窗口类 
  wndclass.cbSize         = sizeof(wndclass);
  wndclass.style          = CS_HREDRAW | CS_VREDRAW;
  wndclass.lpfnWndProc    = WndProc;
  wndclass.cbClsExtra     = 0;
  wndclass.cbWndExtra     = 0;
  wndclass.hInstance      = m_hInstance;
  wndclass.hIcon          = LoadIcon(m_hInstance,
    MAKEINTRESOURCE(GetIcon()));
  wndclass.hIconSm        = LoadIcon(m_hInstance,
    MAKEINTRESOURCE(GetSmallIcon()));
  wndclass.hCursor        = LoadCursor(NULL, IDC_ARROW);
  wndclass.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
  wndclass.lpszMenuName   = NULL;
  wndclass.lpszClassName  = m_szWindowClass;

  // 注册窗口类 
  if (!RegisterClassEx(&wndclass))
    return FALSE;

  /* 根据游戏大小计算窗口大小和位置 (这段代码允许确定实际游戏屏幕的确切大小,
  这个大小与整个应用程序窗口的大小不同) */
  int iWindowWidth = m_iWidth + GetSystemMetrics(SM_CXFIXEDFRAME) * 2,
      iWindowHeight = m_iHeight + GetSystemMetrics(SM_CYFIXEDFRAME) * 2 +
        GetSystemMetrics(SM_CYCAPTION);
  if (wndclass.lpszMenuName != NULL)
    iWindowHeight += GetSystemMetrics(SM_CYMENU);
  int iXWindowPos = (GetSystemMetrics(SM_CXSCREEN) - iWindowWidth) / 2,
      iYWindowPos = (GetSystemMetrics(SM_CYSCREEN) - iWindowHeight) / 2;

  // 创建窗口 
  m_hWindow = CreateWindow(m_szWindowClass, m_szTitle, WS_POPUPWINDOW |
    WS_CAPTION | WS_MINIMIZEBOX, iXWindowPos, iYWindowPos, iWindowWidth,
    iWindowHeight, NULL, NULL, m_hInstance, NULL);
  if (!m_hWindow)
    return FALSE;

  // 显示和更新窗口 
  ShowWindow(m_hWindow, iCmdShow);
  UpdateWindow(m_hWindow);

  return TRUE;
}

GameEngine类中的Initialize( )方法用来初始化游戏引擎。更具体的说,Initialize( )方法现在执行大量杂乱的Windows设置任务,例如为主游戏窗口创建一个窗口类,然后从这个类中创建一个窗口。

用来描述主窗口的窗口样式是WS_POPUPWINDOW、WS_CAPTION和WS_MINIMIZEBOX,这将生成一个大小不可调整、无法最大化的窗口。不过,它包含一个菜单,并且可以最小化。

传入的参数是:int iCmdShow:指明窗口如何显示。

该参数可以是下列值之一:
SW_HIDE:隐藏窗口并且激活另外一个窗口。
SW_MINIMIZE:最小化指定的窗口,并且激活在系统表中的顶层窗口。
SW_RESTORE:激活并显示窗口。如果窗口已经最小化或最大化,系统将以恢复到原来的尺寸和位置显示窗口(与SW_SHOWNORMAL相同)。
SW_SHOW:激活一个窗口并以原来的尺寸和位置显示窗口。
SW_SHOWMAXIMIZED:激活窗口并且将其最大化。
SW_SHOWMINIMIZED:激活窗口并将其最小化(以图标显示)。
SW_SHOWMINNOACTIVE:将一个窗口显示为图标。激活窗口维持活动状态。
SW_SHOWNA:以窗口的当前状态显示窗口。激活窗口保持活动状态。
SW_SHOWNOACTIVATE:以窗口的最近一次的尺寸和位置显示窗口。不激活窗口。
SW_SHOWNORMAL:激活并显示窗口。如果窗口最大化或最小化,系统将其恢复到原来的尺寸和位置(与SW_RESTORE相同)。

WNDCLASSEX 数据类型

WNDCLASSEX属于一个窗台类,WNDCLASSEX 结构用于注册窗口类。

结构体原型

typedef struct WNDCLASSEX {
    UINT cbSize;
    UINT style;
    WNDPROC lpfnWndProc;
    int cbClsExtra;
    int cbWndExtra;
    HINSTANCE hInstance;
    HICON hIcon;
    HCURSOR hCursor;
    HBRUSH hbrBackground;
    LPCTSTR lpszMenuName;
    LPCTSTR lpszClassName;
    HICON hIconSm;
} WNDCLASSEX, *PWNDCLASSEX;

参数解析

cbSize
WNDCLASSEX 的大小。我们可以用sizeof(WNDCLASSEX)来获得准确的值。

style
从这个窗口类派生的窗口具有的风格。您可以用“or”操作符来把几个风格或到一起。

lpfnWndProc
窗口处理函数的指针。

cbClsExtra
指定紧跟在窗口类结构后的附加字节数。

cbWndExtra
指定紧跟在窗口实例的附加字节数。如果一个应用程序在资源中用CLASS伪指令注册一个对话框类时,则必须把这个成员设成DLGWINDOWEXTRA。

hInstance
本模块的实例句柄。

hIcon
图标的句柄。

hCursor
光标的句柄。

hbrBackground
背景画刷的句柄。

lpszMenuName
指向菜单的指针。

lpszClassName
指向类名称的指针。

hIconSm
和窗口类关联的小图标。如果该值为NULL。则把hIcon中的图标转换成大小合适的小图标。

GetSystemMetrics( )

GetSystemMetrics( )用于得到被定义的系统数据或者系统配置信息。

函数原型

int WINAPI GetSystemMetrics( __in intnIndex);

函数参数nIndex的定义

SM_ARRANGE 标志用于说明系统如何安排最小化窗口。

SM_CXFIXEDFRAME,SM_CYFIXEDFRAME:围绕具有标题但无法改变尺寸的窗口(通常是一些对话框)的边框的厚度

SM_CYCAPTION:以像素计算的普通窗口标题的高度

SM_CYMENU:以像素计算的单个菜单条的高度

SM_CXSCREEN,SM_CYSCREEN: 以像素为单位计算的屏幕尺寸。

CreateWindow( )

Windows API函数,创建窗口。

函数原型

HWND CreateWindow(
    LPCTSTR lpClassName,
    LPCTSTR lpWindowName,
    DWORD dwStyle,
    int x,
    int y,
    int nWidth,
    int nHeight,
    HWND hWndParent,
    HMENU hMenu,
    HANDLE hlnstance,
    LPVOID lpParam);

参数详情

lpClassName
指向一个空结束的字符串或整型数atom。

如果该参数是一个整型量,它是由此前调用theGlobalAddAtom函数产生的全局量。这个小于0xC000的16位数必须是lpClassName参数字的低16位,该参数的高位必须是0。

如果lpClassName是一个字符串,它指定了窗口的类名。这个类名可以是任何用函数RegisterClass注册的类名,或是任何预定义的控制类名。请看说明部分的列表。

LPWindowName
指向一个指定窗口名的空结束的字符串指针。

如果窗口风格指定了标题条,由lpWindowName指向的窗口标题将显示在标题条上。当使用Createwindow函数来创建控制例如按钮,选择框和静态控制时,可使用lpWindowName来指定控制文本。

dwStyle
指定创建窗口的风格。该参数可以是下列窗口风格的组合再加上说明部分的控制风格。

风格意义
WS_BORDER:创建一个带边框的窗口。

WS_CAPTION:创建一个有标题框的窗口(包括WS_BORDER风格)。

WS_CHILD:创建一个子窗口。这个风格不能与WS_POPUP风格合用。

WS_CHILDWINDOW:与WS_CHILD相同。

WS_CLIPCHILDREN:当在父窗口内绘图时,排除子窗口区域。在创建父窗口时使用这个风格。

WS_CLIPSIBLINGS:排除子窗口之间的相对区域,也就是,当一个特定的窗口接收到WM_PAINT消息时,WS_CLIPSIBLINGS 风格将所有层叠窗口排除在绘图之外,只重绘指定的子窗口。如果未指定

WS_CLIPSIBLINGS风格,并且子窗口是层叠的,则在重绘子窗口的客户区时,就会重绘邻近的子窗口。

WS_DISABLED:创建一个初始状态为禁止的子窗口。一个禁止状态的窗口不能接受来自用户的输入信息。

WS_DLGFRAME:创建一个带对话框边框风格的窗口。这种风格的窗口不能带标题条。

WS_GROUP:指定一组控制的第一个控制。这个控制组由第一个控制和随后定义的控制组成,自第二个控制开始每个控制,具有WS_GROUP风格,每个组的第一个控制带有WS_TABSTOP风格,从而使用户可以在组间移动。用户随后可以使用光标在组内的控制间改变键盘焦点。

WS_HSCROLL:创建一个有水平滚动条的窗口。

WS_ICONIC:创建一个初始状态为最小化状态的窗口。与

WS_MINIMIZE风格相同。

WS_MAXIMIZE:创建一个初始状态为最大化状态的窗口。

WS_MAXIMIZEBOX:创建一个具有最大化按钮的窗口。该风格不能与

WS_EX_CONTEXTHELP风格同时出现,同时必须指定WS_SYSMENU风格。

WS_OVERLAPPED:产生一个层叠的窗口。一个层叠的窗口有一个标题条和一个边框。与WS_TILED风格相同。

WS_OVERLAPPEDWINDOW:创建一个具有WS_OVERLAPPED,

WS_CAPTION,WS_SYSMENU WS_THICKFRAME,

WS_MINIMIZEBOX,WS_MAXIMIZEBOX风格的层叠窗口,与

WS_TILEDWINDOW风格相同。

WS_POPUP:创建一个弹出式窗口。该风格不能与WS_CHILD风格同时使用。

WS_POPUPWINDOW:创建一个具有WS_BORDER,

WS_POPUP,WS_SYSMENU风格的窗口,WS_CAPTION和

WS_POPUPWINDOW必须同时设定才能使窗口某单可见。

WS_SIZEBOX:创建一个可调边框的窗口,与WS_THICKFRAME风格相同。

WS_SYSMENU:创建一个在标题条上带有窗口菜单的窗口,必须同时设定WS_CAPTION风格。

WS_TABSTOP:创建一个控制,这个控制在用户按下Tab键时可以获得键盘焦点。按下Tab键后使键盘焦点转移到下一具有WS_TABSTOP风格的控制。

WS_THICKFRAME:创建一个具有可调边框的窗口,与WS_SIZEBOX风格相同。

WS_TILED:产生一个层叠的窗口。一个层叠的窗口有一个标题和一个边框。与WS_OVERLAPPED风格相同。

WS_TILEDWINDOW:创建一个具有WS_OVERLAPPED,

WS_CAPTION,WS_SYSMENU, WS_THICKFRAME,

WS_MINIMIZEBOX,WS_MAXIMIZEBOX风格的层叠窗口。与

WS_OVERLAPPEDWINDOW风格相同。

WS_VISIBLE:创建一个初始状态为可见的窗口。

WS_VSCROLL:创建一个有垂直滚动条的窗口。

X
指定窗口的初始水平位置。对一个层叠或弹出式窗口,X参数是屏幕坐标系的窗口的左上角的初始X坐标。

对于子窗口,x是子窗口左上角相对父窗口客户区左上角的初始X坐标。如果该参数被设为CW_USEDEFAULT则系统为窗口选择缺省的左上角坐标并忽略Y参数。CW_USEDEFAULT只对层叠窗口有效,如果为弹出式窗口或子窗口设定,则X和y参数被设为零。

Y
指定窗口的初始垂直位置。对一个层叠或弹出式窗口,y参数是屏幕坐标系的窗口的左上角的初始y坐标。

对于子窗口,y是子窗口左上角相对父窗口客户区左上角的初始y坐标。对于列表框,y是列表框客户区左上角相对父窗口客户区左上角的初始y坐标。如果层叠窗口是使用WS_VISIBLE风格位创建的并且X参数被设为CW_USEDEFAULT,则系统将忽略y参数。

nWidth
以设备单元指明窗口的宽度。对于层叠窗口,nWidth或是屏幕坐标的窗口宽度或是CW_USEDEFAULT。若nWidth是CW_USEDEFAULT,则系统为窗口选择一个缺省的高度和宽度:缺省宽度为从初始X坐标开始到屏幕的右边界,缺省高度为从初始Y坐标开始到目标区域的顶部。

CW_USEDEFAULT只对层叠窗口有效;如果为弹出式窗口和子窗口设定CW_USEDEFAULT标志则nWidth和nHeight被设为零。

nHeight
以设备单元指明窗口的高度。对于层叠窗口,nHeight是屏幕坐标的窗口宽度。若nWidth被设为CW_USEDEFAULT,则系统忽略nHeight参数。

hWndParent
指向被创建窗口的父窗口或所有者窗口的句柄。若要创建一个子窗口或一个被属窗口,需提供一个有效的窗口句柄。这个参数对弹出式窗口是可选的。Windows NT 5.0;创建一个消息窗口,可以提供HWND_MESSAGE或提供一个己存在的消息窗口的句柄。

hMenu
菜单句柄,或依据窗口风格指明一个子窗口标识。对于层叠或弹出式窗口,hMenu指定窗口使用的菜单:如果使用了菜单类,则hMenu可以为NULL。对于子窗口,hMenu指定了该子窗口标识(一个整型量),一个对话框使用这个整型值将事件通知父类。应用程序确定子窗口标识,这个值对于相同父窗口的所有子窗口必须是唯一的。

hlnstance:与窗口相关联的模块实例的句柄。

lpParam
指向一个值的指针,该值传递给窗口WM_CREATE消息。该值通过在IParam参数中的CREATESTRUCT结构传递。如果应用程序调用CreateWindow创建一个MDI客户窗口,则lpParam必须指向一个CLIENTCREATESTRUCT结构。

返回值:如果函数成功,返回值为新窗口的句柄:如果函数失败,返回值为NULL。若想获得更多错误信息,请调用GetLastError函数。[1]

ShowWindow( )

该函数设置指定窗口的显示状态。

函数原型

BOOL ShowWindow(HWND hWnd, int nCmdShow);

参数详解

hWnd:指窗口句柄。

nCmdShow:指定窗口如何显示。
如果发送应用程序的程序提供了STARTUPINFO结构,则应用程序第一次调用ShowWindow时该参数被忽略。否则,在第一次调用ShowWindow函数时,该值应为在函数WinMain中nCmdShow参数。

在随后的调用中,该参数可以为下列值之一:
SW_FORCEMINIMIZE:在WindowNT5.0中最小化窗口,即使拥有窗口的线程被挂起也会最小化。在从其他线程最小化窗口时才使用这个参数。nCmdShow=11。

SW_HIDE:隐藏窗口并激活其他窗口。nCmdShow=0。

SW_MAXIMIZE:最大化指定的窗口。nCmdShow=3。

SW_MINIMIZE:最小化指定的窗口并且激活在Z序中的下一个顶层窗口。nCmdShow=6。

SW_RESTORE:激活并显示窗口。如果窗口最小化或最大化,则系统将窗口恢复到原来的尺寸和位置。在恢复最小化窗口时,应用程序应该指定这个标志。nCmdShow=9。

SW_SHOW:在窗口原来的位置以原来的尺寸激活和显示窗口。nCmdShow=5。

SW_SHOWDEFAULT:依据在STARTUPINFO结构中指定的SW_FLAG标志设定显示状态,STARTUPINFO 结构是由启动应用程序的程序传递给CreateProcess函数的。nCmdShow=10。

SW_SHOWMAXIMIZED:激活窗口并将其最大化。nCmdShow=3。

SW_SHOWMINIMIZED:激活窗口并将其最小化。nCmdShow=2。

SW_SHOWMINNOACTIVE:窗口最小化,激活窗口仍然维持激活状态。nCmdShow=7。

SW_SHOWNA:以窗口原来的状态显示窗口。激活窗口仍然维持激活状态。nCmdShow=8。

SW_SHOWNOACTIVATE:以窗口最近一次的大小和状态显示窗口。激活窗口仍然维持激活状态。nCmdShow=4。

SW_SHOWNORMAL:激活并显示一个窗口。如果窗口被最小化或最大化,系统将其恢复到原来的尺寸和大小。应用程序在第一次显示窗口的时候应该指定此标志。nCmdShow=1。

UpdateWindow( )

更新指定窗口的客户区。

功能:如果窗口更新的区域不为空,UpdateWindow函数就发送一个WM_PAINT消息来更新指定窗口的客户区。函数绕过应用程序的消息队列,直接发送WM_PAINT消息给指定窗口的窗口过程,如果更新区域为空,则不发送消息。

函数原型

BOOL UpdateWindow(
    HWND hWnd // 窗口的句柄
);

参数详解:
hWnd:要更新的窗口的句柄.

GameEngine::HandleEvent( )

LRESULT GameEngine::HandleEvent(HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam)
{
  // 将Windows消息传递给游戏引擎成员函数 
  switch (msg)
  {
    case WM_CREATE:
      // 设置游戏窗口并开始游戏 
      SetWindow(hWindow);
      GameStart(hWindow);  //响应WM_CREATE消息,开始游戏 
      return 0;

    case WM_SETFOCUS:
      // 激活游戏并更新休眠状态 
      GameActivate(hWindow);
      SetSleep(FALSE);
      return 0;

    case WM_KILLFOCUS:
      // 停用游戏并更新休眠状态 
      GameDeactivate(hWindow);
      SetSleep(TRUE);
      return 0;

    case WM_PAINT:
      HDC         hDC;
      PAINTSTRUCT ps;
      hDC = BeginPaint(hWindow, &ps);

      // 绘制游戏 
      GamePaint(hDC);      //响应WM_PAINT消息,绘制游戏 

      EndPaint(hWindow, &ps);
      return 0;

    case WM_DESTROY:
      // 结束游戏并退出应用程序 
      GameEnd();
      PostQuitMessage(0);  //响应WM_DESTROY消息,结束游戏 
      return 0;
  }
  return DefWindowProc(hWindow, msg, wParam, lParam);
}

HandleEvent( )方法包含了一个switch语句,它获得Windows消息并单独响应他们。HandleEvent( )方法也调用游戏引擎函数。

首先处理每次初次创建主游戏窗口时发送的WM_CREATE消息。这个消息的处理程序代码设置游戏引擎中的窗口句柄,然后调用GameStart( )游戏事件函数来初始化游戏。

WM_SETFOCUSWM_KILLFOCUS消息分别通知游戏,它的游戏窗口接受(激活)或失去(停用)了输入焦点。如果游戏被激活(获得焦点),则调用GameActivate( )函数,唤醒游戏,如果游戏窗口被停用(失去焦点),则调用GameDeactivate( )函数,使游戏休眠。

HandleEvent( )方法中其余的消息相当简单,它们主要调用游戏函数。WM_PAINT消息处理程序调用标准的Win32 BeginPaint( )函数,然后调用GamePaint( )函数,最后调用EndPaint( )函数来结束绘制过程。最后,WM_DESTROY处理程序调用GameEnd( )函数,然后终止整个程序。

PAINTSTRUCT 数据类型

用于绘制窗口客户区的信息的结构体。

结构体定义:

typedef struct tagPAINTSTRUCT {
HDC hdc;
BOOL fErase;
RECT rcPaint;
BOOL fRestore;
BOOL fIncUpdate;
BYTE rgbReserved[32];
} PAINTSTRUCT, *PPAINTSTRUCT;

参数解释

BeginPaint可以得到客户区设备描述表的句柄,GetDC也可以得到,MFC里的CClientDC与之对应。

hdc是用于绘制的句柄,

fErase如果为非零值则擦除背景,否则不擦除背景,

rcPaint 通过制定左上角和右下角的坐标确定一个要绘制的矩形范围,该矩形单位相对于客户区左上角,

后面三个参数都是系统预留的,编程一般用不到。

BeginPaint(hWindow, &ps)

BeginPaint函数为指定窗口进行绘图工作的准备,并用将和绘图有关的信息填充到一个PAINTSTRUCT结构中。

函数原型

HDC BeginPaint(
    HWND hwnd, // 窗口的句柄
    LPPAINTSTRUCT lpPaint // 绘制信息
);

参数解释

hwnd:[输入]被重绘的窗口句柄

lpPaint:[输出]指向一个用来接收绘画信息的PAINTSTRUCT结构

EndPaint(hWindow, &ps);

EndPaint函数标记指定窗口的绘画过程结束;这个函数在每次调用BeginPaint函数之后被请求,但仅仅在绘画完成以后。

函数原型

BOOL EndPaint(
HWND hWnd, // 窗口句柄
CONST PAINTSTRUCT *lpPaint // 绘制窗口的数据
);

参数解释

hWnd:[输入]已经被重画的窗口的HANDLE

lpPaint:[输入]指向一个PAINTSTRUCT结构,该结构包含了绘画信息,是BeginPaint函数返回的返回值:

PostQuitMessage(0);

该函数向系统表明有个线程有终止请求。通常用来响应WM_DESTROY消息,指定应用程序退出代码。

void PostQuitMessage(int nExitCode)

参数:nExitCode:指定应用程序退出代码。此值被用作消息WM_QUIT的
wParam参数。

返回值:无。

DefWindowProc(hWindow, msg, wParam, lParam);

DefWindowProc函数调用缺省的窗口过程来为应用程序没有处理的任何窗口消息提供缺省的处理。该函数确保每一个消息得到处理。

函数功能:该调用DefWindowProc函数时使用窗口过程接收的相同参数。

函数原型

LRESULT DefWindowProc(HWND hWnd,UINT Msg,WPARAM wParam,LPARAM IParam);

用引擎开发Blizzard雪花小游戏

说一下VC 6++中新建文件的问题。其他的IDE,一般右键文件夹,即可选择添加不同的文件至文件中。

而VC 6++要点击菜单栏的文件->新建,然后选添加至工程就行了。

说下资源脚本的问题。

新建 资源脚本Resource script .rs文件

资源脚本,它是一个简单的文本文件,可以手工编辑,也可以让Visual C++自动编辑,或者你用其它的自动编辑器编辑。无论如何,资源脚本文件要有一个.rc的扩展名。

点击菜单栏的文件->新建->选中资源脚本

游戏编程入门(2):创建游戏引擎和使用该引擎制作小游戏_第11张图片

成功创建,出现如下界面

游戏编程入门(2):创建游戏引擎和使用该引擎制作小游戏_第12张图片

右击Blizzard.rc文件夹。

游戏编程入门(2):创建游戏引擎和使用该引擎制作小游戏_第13张图片

点击插入即可新建新的位图文件进资源脚本中。因为该游戏中,我们已有自己的位图文件,所以我们点击引入。找到自己的位图文件后,选中,点击引入按钮。

游戏编程入门(2):创建游戏引擎和使用该引擎制作小游戏_第14张图片

引入成功后,即可看见位图文件被导入至资源脚本中。

游戏编程入门(2):创建游戏引擎和使用该引擎制作小游戏_第15张图片

接下来,我们要对位图文件,进行改名。右击位图文件,选择属性。

游戏编程入门(2):创建游戏引擎和使用该引擎制作小游戏_第16张图片

在ID处改名,改完右击回车即可。另一个位图文件也按照相同操作,ID名则改成IDI_BLIZZARD_SM。

游戏编程入门(2):创建游戏引擎和使用该引擎制作小游戏_第17张图片

至此,资源脚本设置好了。当创建资源脚本时,VC会自动帮我们创建一个resource.h头文件,并且Blizzard.rc资源脚本与resource.h头文件相关联,即Blizzard.rc中include

Blizzard 目录结构和效果图

给一张项目最后的目录。

游戏编程入门(2):创建游戏引擎和使用该引擎制作小游戏_第18张图片

效果图:(其实就是雪花不停的从窗口各个地方出现)

游戏编程入门(2):创建游戏引擎和使用该引擎制作小游戏_第19张图片

Blizzard 源代码

该Blizzard雪花游戏除了使用游戏引擎文件GameEngine.cpp 和 GameEngine.h以外。

还有它独有的Blizzard.cpp 和 Blizzard.h 和 资源脚本Blizzard.rc以及自动创建的resource.h。

资源脚本Blizzard.rc的创建,上面已经介绍过了。接下来将贴其他几个代码,而代码解析则放在后面。

resource.h 源代码

resource.h 头文件可以算是一个帮助器文件,它定义了在程序中使用的所有资源的标识符。在这个例子中,唯一的资源是两个不同大小(16*16,32*32)的图标。

resource.h头文件声明了Blizzard示例中使用的图标的标识符。

资源通常使用某个特定范围内的数字,这样就更容易相互区分并有助于确保它们具有唯一的值。

//-----------------------------------------------------------------
// Icons                    Range : 1000 - 1999
//-----------------------------------------------------------------
#define IDI_BLIZZARD        1000
#define IDI_BLIZZARD_SM     1001

Blizzard.h 源代码

Blizzard.h头文件只是导入几个头文件并声明重要的全局游戏引擎指针。

#pragma once

#include
#include "Resource.h"
#include "GameEngine.h"

//全局变量
GameEngine* g_pGame;   //每一个基于游戏引擎的游戏都需要一个全局游戏引擎指针

Blizzard.cpp 源代码

主要的Blizzard程序位于Blizzard.cpp源代码中,在Blizzard.cpp中我们定义了游戏事件函数。

Blizzard.cpp源代码文件揭示了在使用游戏引擎时,一个小型Windows程序(游戏)的程序代码变得多么简单,只需要定义属于该游戏的游戏事件函数,而不必管与Windows有关的API。

//-----------------------------------------------------------------
// Blizzard 应用程序
//-----------------------------------------------------------------

//-----------------------------------------------------------------
// 包含文件
//-----------------------------------------------------------------
#include "Blizzard.h"

//-----------------------------------------------------------------
// GameEngine.h 中的游戏事件函数
//-----------------------------------------------------------------

//初始化游戏
BOOL GameInitialize(HINSTANCE hInstance)
{
  // 创建游戏引擎
  g_pGame = new GameEngine(hInstance, TEXT("Blizzard"),TEXT("Blizzard"), IDI_BLIZZARD, IDI_BLIZZARD_SM);
  if (g_pGame == NULL)
  {
    return FALSE;
  }

  // 设置帧频
  g_pGame->SetFrameRate(15);  //15帧/秒

  return TRUE;
}

//开始游戏
void GameStart(HWND hWindow)
{
  // 生成随机数生成器种子
  srand(GetTickCount());
}

//结束游戏
void GameEnd()
{
  // 清理游戏引擎
  delete g_pGame;
}

//激活游戏
void GameActivate(HWND hWindow)
{
  HDC   hDC;
  RECT  rect;

  // 在游戏屏幕上绘制文本
  GetClientRect(hWindow, &rect);
  hDC = GetDC(hWindow);
  DrawText(hDC, TEXT("Here comes the blizzard!"), -1, &rect,
    DT_SINGLELINE | DT_CENTER | DT_VCENTER);
  ReleaseDC(hWindow, hDC);
}

//停用游戏
void GameDeactivate(HWND hWindow)
{
  HDC   hDC;
  RECT  rect;

  // 在游戏屏幕上绘制停用文本
  GetClientRect(hWindow, &rect);
  hDC = GetDC(hWindow);
  DrawText(hDC, TEXT("The blizzard has passed."), -1, &rect,
    DT_SINGLELINE | DT_CENTER | DT_VCENTER);
  ReleaseDC(hWindow, hDC);
}

//这个例子中的所有绘制工作都发生在GameCycle()函数中,因此不需要做任何事情,不过这种情况很少见
void GamePaint(HDC hDC)
{
}

//循环游戏
void GameCycle()
{
  HDC   hDC;
  HWND  hWindow = g_pGame->GetWindow();

  // 在游戏屏幕上的随机位置绘制雪花图标
    hDC = GetDC(hWindow);
    DrawIcon(hDC, rand() % g_pGame->GetWidth(), rand() % g_pGame->GetHeight(),
      (HICON)(WORD)GetClassLong(hWindow, GCL_HICON));
    ReleaseDC(hWindow, hDC);
}

GameCycle( )函数中的第一步是获得主游戏窗口的窗口句柄,它允许在游戏屏幕上绘制图形。

绘制工作实际上发生在调用Win32 DrawIcon( )函数绘制Blizzard图标时。调用标准的rand( )函数来确定游戏屏幕上的一个随机位置,然后使用Win32 GetClassLong( )函数从游戏窗口类中提取图标。

编译中可能出现的问题

error LNK2001: unresolved external symbol _main 错误

错误原因

Windows项目要使用Windows子系统, 而不是Console

解决方法

选择菜单中的[Project工程] –> [Settings设置] –> 选择”Link连接”属性页,在Project Options 工程选项中将 /subsystem:console 改成 /subsystem:windows 。

游戏编程入门(2):创建游戏引擎和使用该引擎制作小游戏_第20张图片

A corresponding workspace 点击dsp文件,无法进入工程

VC 6++的工程文件为dsp,当我们保存工程文件,再进入时,可能会报这个错误。

游戏编程入门(2):创建游戏引擎和使用该引擎制作小游戏_第21张图片

解决方法

自己打开VC6++ ,用菜单栏里的打开文件(报错的话,用前面的工具,选O打开),选中该工程的dsp文件,即可打开这个工程。

源代码 下载

http://pan.baidu.com/s/1ge2Vzr1

你可能感兴趣的:(?.游戏编程入门,游戏编程入门)