Windows游戏编程快速入门方法

                        Windows游戏编程快速入门方法

 

                        Easideao(简单思路)

 

 

序言:

 

    2001年到2005年,在不知不觉中我已经渡过了4年的职业游戏开发生涯。在这4年里经常会有些网友向我询问编程的入门有没有捷径。每一次我都不知怎么回答才算合适,我也一直想表达一下我的思路和想法,但一直都没有能力把自己的见解在书面上表达出来,其实我认为编写程序并不是很难的事情。最关键的是你对他是否有兴趣,最难的是坚持学习。如果没有兴趣,即使你刚刚入了一点们如果不坚持下去,也是一事无成。

    虽然毅力在学习的过程中有着不可置疑的位置,但是有个合适的方法和适合自己的方法还是很重要的。假如你的兴趣和毅力都过了关,我接下来将以一个游戏的代码编写过程写下来,我坚持写下来,你坚持读完,按照我讲述的步骤去做。我这里不会把所有细节都讲述出来,因为那是太庞大的任务,我的力量无法实现,我们下面的方法就是:我说怎么做,你就怎么做,先知道怎样做一些事情,当你能够按照我说的做出正确的结果说明你已经会了,如果有不懂得再去查看相关资料。

    上面说的有些繁琐,我自己也不太愿意写下去了,我的文笔水平有限,请大家谅解。接下来最重要的就是跟着我做。如果你有什么意见或问题可以给我发E-mail : chinagdh@163.com

 

         

 

 

 

                                         第一章  Windows程序

  1. 打开Visual Studio 2003.net ,选择File -> Blank Solution

  1. Name栏里输入 BattleCity 并按 ok 按钮, browse选择解决方案存放位置

3.Solution Explorer 里在 Solution ‘BattleCity’上按右键。在下拉菜单中选择 Add -> New Project

4.Add New Project 对话框里选择 Visual C++ Projects -> Win32 -> Win32 Project,在Name栏里打入 Tank 并按回车

5.选择Application Settings 并在 Empty project 前面打钩,创建一个空的Win32 项目。

6.Tank项目上按右键 选择Add -> New Folder 增加文件夹,并命名WinApp

7.WinApp文件夹上按右键选择Add -> Add New Item

8.选择Visual C++ -> C++ File(.cpp) Name栏里输入 WinApp.cpp

9.反复7.8步 增加 WinApp.h AppEntry.cpp AppEntry.h

10.双击 WinApp.h 打开文件我们在WinApp.h头文件中加入以下代码

 

 

 

 

 

 

 

 

11.以同样的方法处理AppEntry.h , 这种方法保证头文件只被include一次,这是我喜欢用的一种方法也可以在第一行写#pragma once

12.打开AppEntry.h 加入代码 #include <windows.h>

13.打开WinApp.h 加入代码 #include "AppEntry.h"

14.打开WinApp.cpp 加入代码 #include "WinApp.h"

15.定义主程序句柄和主窗口句柄

16.增加获得主程序句柄和主窗口句柄的全局函数

17. 为方便以后获得主程序句柄和主窗口句柄 在 WinApp.h 中声明

HINSTANCE   GetAppHandle();

HWND        GetMainWnd();

18.定义Windows程序主函数,这是一个Windows程序的入口函数,我们认为程序从此函数开始执行。

19.Solution Explorer 中选择Tank项目,按右键选择Build 编译一下,看程序是否可以编译。

编译成功会在Output窗口中出现提示信息 Build1 succeeded, 0 failed, 0 skipped 表示 成功一个 0 失败 0 跳过

20.设置Tank生成的路径,在项目Tank上按右键选择Properties

21.选择程序生成路径(Output File) ../RunTime/Tank.exe

22.选择运行路径(Working Directory) ../RunTime 在编译一下

23.增加必要的几个函数 程序的初始化 结束 主循环 消息处理函数,代码如下

#include "WinApp.h"

 

// 定义主程序句柄

HINSTANCE   g_hTheApp   = NULL;

// 定义主窗口句柄

HWND        g_hMainWnd  = NULL;

 

 

//

 

// 获得主程序句柄

HINSTANCE   GetAppHandle()

{

    return g_hTheApp;

}

 

// 获得主窗口句柄

HWND        GetMainWnd()

{

    return g_hMainWnd;

}

 

//

 

// Windows程序初始化

HWND AppInit( HINSTANCE hInstance, int nShowCmd );

 

// Windows程序结束

void AppTerm();

 

// Windows消息循环和主循环

int AppMsgLoop();

 

// Windows消息处理函数

LRESULT CALLBACK AppWndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam );

 

//

 

 

// Windows程序主函数

int WINAPI WinMain(

                   IN HINSTANCE hInstance,

                   IN HINSTANCE hPrevInstance,

                   IN LPSTR lpCmdLine,

                   IN int nShowCmd

                   )

{

   

    // 保存主程序句柄

    g_hTheApp = hInstance;

 

    // 初始化Window窗口和程序

    g_hMainWnd = AppInit( g_hTheApp, nShowCmd );

    if( g_hMainWnd == NULL )

    {

        MessageBox( NULL, "Can't Create Main Window", "Error", MB_OK );

        return 0;

    }

 

    // 进入主消息循环

    return AppMsgLoop();

}

 

 

 

// Windows程序初始化

HWND AppInit( HINSTANCE hInstance, int nShowCmd )

{

    return NULL;

}

 

 

 

// Windows程序结束

void AppTerm()

{

}

 

 

 

// Windows消息循环和主循环

int AppMsgLoop()

{

    return 0;

}

 

 

 

// Windows消息处理函数

LRESULT CALLBACK AppWndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )

{

    return DefWindowProc( hWnd, msg, wParam, lParam );

}

 

 

24.AppInit()函数里注册程序和创建主窗口

 

// Windows程序初始化

HWND AppInit( HINSTANCE hInstance, int nShowCmd )

{

    // 程序实例类名

    static char szWindowsClassName[] = "TankBattleCity";

 

    static int iWindowWidth     = 800;  // 窗口宽度

    static int iWindowHeight    = 600;  // 窗口高度

 

 

    // 注册窗口类

    WNDCLASS wc;

 

    wc.style            = CS_HREDRAW | CS_VREDRAW;

 

    // 设置主窗口消息处理函数

    wc.lpfnWndProc      = AppWndProc;

 

    wc.cbClsExtra       = 0;

    wc.cbWndExtra       = 0;

    wc.hInstance        = hInstance;

    wc.hIcon            = LoadIcon( 0, IDI_APPLICATION );

    wc.hCursor          = LoadCursor( 0, IDC_ARROW );

    wc.hbrBackground    = static_cast<HBRUSH>(GetStockObject(WHITE_BRUSH));

    wc.lpszMenuName     = 0;

    wc.lpszClassName    = szWindowsClassName;

 

    if( !RegisterClass( &wc ) )

    {

        MessageBox( NULL, "RegisterClass - Failed", "Error", MB_OK );

        return false;

    }

 

    // 创建主窗口

    HWND hWnd = CreateWindow( szWindowsClassName

        , szWindowsClassName

        , WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX

        , CW_USEDEFAULT

        , CW_USEDEFAULT

        , iWindowWidth

        , iWindowHeight

        , 0

        , 0

        , hInstance

        , 0 );

 

    if( NULL == hWnd )

    {

        MessageBox( NULL, "Create Main Window - Failed", "Error", MB_OK );

        return NULL;

    }

 

    // 显示主窗口

    ShowWindow( hWnd, SW_SHOW );

    // 更新主窗口显示内容

    UpdateWindow( hWnd );

 

    return hWnd;

}

 

25.编写AppMsgLoop() 的程序主循环

 

// Windows消息循环和主循环

int AppMsgLoop()

{

    MSG msg;

    ZeroMemory( &msg, sizeof(MSG) );

 

   

_AppLoop:

 

    // 检测是否有消息要处理

    if( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) )

    {

        // 如果接收不到消息表示要退出程序

        if( !GetMessage( &msg, NULL, 0, 0 ) )

        {

            // 转向结束程序

            goto _ExitApp;

        }

 

        // 执行消息处理

        TranslateMessage( &msg );

        DispatchMessage( &msg );

    }

    else

    {

        // 如果没有任何消息要处理就调用主循环

 

        Sleep(1);

    }

 

goto _AppLoop;

 

_ExitApp:

 

    AppTerm();

 

    return (int)msg.wParam;

}

 

26.AppWndProc()函数里加入推出程序消息处理

 

// Windows消息处理函数

LRESULT CALLBACK AppWndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )

{

    switch( msg )

    {

    case WM_DESTROY:

        PostQuitMessage( 0 );

        break;

    }

 

    return DefWindowProc( hWnd, msg, wParam, lParam );

}

 

      到现在我们已经可以编译并执行一个没有做任何事情的Windows程序,这里我不想和任何人讨论关于编码好坏的问题,例如在AppMsgLoop()函数中用了goto和标号。我个人觉得在这里不适宜讨论这个问题。我们重要的是知道如何让程序工作,有关于Windows编程的细节你可以查阅《Windows程序设计》一书。

 

 

第二章  简化的程序接口

 

      前一章中我们介绍了如何编写Windows程序,已经完成了一个简单的有一个主窗口的Windows程序,接下来我们钻心写我们的游戏程序了。为了以后的编写方便使各部分程序内容彼此独立,我们现在开始定义一个程序简单的程序接口,我们以后就不需要再顾虑太多的Windows程序相关的内容。

      程序接口的实现我们用到了C++中最常用的一种功能虚拟函数(virtual function) 来实现接口功能。在这里你只需要知道在基类中定义的函数前面加了一个virtual关键字,以后派生类中都可以重写该函数内容。

     

1.   打开AppEntry.h 输入下面程序

 

#ifndef __AppEntry_H__

#define __AppEntry_H__

 

#include <windows.h>

 

class IAppEntry

{

public:

   

    // 初始化程序

    virtual bool Initialize( HINSTANCE hInstance, HWND hWnd ) = 0;

 

    // 结束程序

    virtual void Terminal() = 0;

 

    // 主处理函数

    virtual void Process() = 0;

   

    // 主渲染函数

    virtual void Render() = 0;

   

    // 主窗口消息处理函数

    virtual LRESULT WndProc( UINT message, WPARAM wParam, LPARAM lParam ) = 0;

 

    // 定义Window程序类名函数

    virtual const char *WindowClassName() = 0;

 

    // 定义Window窗口宽度

    virtual const int WindowWidth() = 0;

   

    // 定义Window窗口高度

    virtual const int WindowHeight() = 0;

 

    // 获得 主程序句柄

    virtual HINSTANCE GetInstance() = 0;

 

    // 获得 主窗口句柄

    virtual HWND GetWnd() = 0;

 

};

 

#endif // __AppEntry_H__

 

这里每一个函数 后面都写了一个 = 0 表示该函数是纯虚函数, 再派生类中必须重写。这种方法是定义接口的常用方法。

 

 

2.   打开WinApp.cpp 加入下面代码

 

// Windows消息循环和主循环

int AppMsgLoop();

 

// Windows消息处理函数

LRESULT CALLBACK AppWndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam );

 

// 获得简单程序接口实例,我们将在GameApp.cpp中定义

IAppEntry *AppEntryClass();

 

//

 

 

// Windows程序主函数

int WINAPI WinMain(

                   IN HINSTANCE hInstance,

                   IN HINSTANCE hPrevInstance,

                   IN LPSTR lpCmdLine,

                   IN int nShowCmd

                   )

 

3.   WinMain函数中加入下面代码

// Windows程序主函数

int WINAPI WinMain(

                   IN HINSTANCE hInstance,

                   IN HINSTANCE hPrevInstance,

                   IN LPSTR lpCmdLine,

                   IN int nShowCmd

                   )

{

   

    // 保存主程序句柄

    g_hTheApp = hInstance;

 

    // 初始化Window窗口和程序

    g_hMainWnd = AppInit( g_hTheApp, nShowCmd );

    if( g_hMainWnd == NULL )

    {

        MessageBox( NULL, "Can't Create Main Window", "Error", MB_OK );

        return 0;

    }

 

    // 初始化主程序

    if( !AppEntryClass()->Initialize( g_hTheApp, g_hMainWnd ) )

    {

        MessageBox( NULL, "Initialize AppEntry Failed", "Error", MB_OK );

        return 0;

    }

 

    // 进入主消息循环

    return AppMsgLoop();

}

 

 

4.   AppTerm() 函数里加入下面代码

// Windows程序结束

void AppTerm()

{

    // 结束程序

    AppEntryClass()->Terminal();

}

5.   AppMsgLoop()函数里加入下面代码

// Windows消息循环和主循环

int AppMsgLoop()

{

    MSG msg;

    ZeroMemory( &msg, sizeof(MSG) );

 

   

_AppLoop:

 

    // 检测是否有消息要处理

    if( PeekMessage( &msg, NULL, 0, 0, PM_NOREMOVE ) )

    {

        // 如果接收不到消息表示要退出程序

        if( !GetMessage( &msg, NULL, 0, 0 ) )

        {

            // 转向结束程序

            goto _ExitApp;

        }

 

        // 执行消息处理

        TranslateMessage( &msg );

        DispatchMessage( &msg );

    }

    else

    {

        // 如果没有任何消息要处理就调用主循环

        AppEntryClass()->Process();

        Sleep(1);

    }

 

goto _AppLoop;

 

_ExitApp:

 

    AppTerm();

 

    return (int)msg.wParam;

}

 

6.   AppWndProc()函数里加入下面代码

// Windows消息处理函数

LRESULT CALLBACK AppWndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )

{

    switch( msg )

    {

    case WM_DESTROY:

        PostQuitMessage( 0 );

        break;

    }

   

    // 调用简单程序接口的消息处理函数,如果返回不是FALSE 就直接完成消息处理

    // 否则 继续调用Windows程序默认消息处理函数.

    LRESULT lr = AppEntryClass()->WndProc( msg, wParam, lParam );

    if( lr != FALSE )

        return lr;

 

    return DefWindowProc( hWnd, msg, wParam, lParam );

}

7.   Tank项目上建一个新目录GameApp并创建两个文件 AppGame.cpp AppGame.h

8.   AppGame.h里 加入下面代码

#ifndef __AppGame_H__

#define __AppGame_H__

 

#include "WinApp.h"

 

class CAppGame : public IAppEntry

{

public:

 

    CAppGame();

    virtual ~CAppGame();

 

    // 初始化程序

    virtual bool Initialize( HINSTANCE hInstance, HWND hWnd );

 

    // 结束程序

    virtual void Terminal();

 

    // 主处理函数

    virtual void Process();

 

    // 主渲染函数

    virtual void Render();

 

    // 主窗口消息处理函数

    virtual LRESULT WndProc( UINT message, WPARAM wParam, LPARAM lParam );

 

    // 定义Window程序类名函数

    virtual const char *WindowClassName();

 

    // 定义Window窗口宽度

    virtual const int WindowWidth();

 

    // 定义Window窗口高度

    virtual const int WindowHeight();

 

    // 获得 主程序句柄

    virtual HINSTANCE GetInstance();

 

    // 获得 主窗口句柄

    virtual HWND GetWnd();

 

protected:

 

    // 保存主程序句柄

    HINSTANCE m_hInstance;

    // 保存主窗口句柄

    HWND m_hWnd;

 

};

 

#endif // __AppGame_H__

 

 

9.   打开AppGame.cpp 加入简单接口程序实例,和获得程序实例的函数

#include "AppGame.h"

 

// 定义主游戏程序实例

CAppGame theAppGame;

 

// 定义获得简单程序接口实例函数,这个在WinApp.cpp中用到,而且必须定义

// 这是Windows程序调用游戏程序的接口

IAppEntry *AppEntryClass()

{

    return &theAppGame;

}

 

 

10.加入下面代码从写简单程序接口的纯虚函数内容

//

 

CAppGame::CAppGame()

{

    m_hInstance = NULL;

    m_hWnd = NULL;

}

 

CAppGame::~CAppGame()

{

 

}

 

// 初始化程序

bool CAppGame::Initialize( HINSTANCE hInstance, HWND hWnd )

{

    m_hInstance = hInstance;

    m_hWnd = hWnd;

 

    return true;

}

 

// 结束程序

void CAppGame::Terminal()

{

}

 

// 主处理函数

void CAppGame::Process()

{

}

 

// 主渲染函数

void CAppGame::Render()

{

}

 

// 主窗口消息处理函数

LRESULT CAppGame::WndProc( UINT message, WPARAM wParam, LPARAM lParam )

{

    return FALSE;

}

 

// 定义Window程序类名函数

const char *CAppGame::WindowClassName()

{

    return "TankBattleCity";

}

 

// 定义Window窗口宽度

const int CAppGame::WindowWidth()

{

    return 800;

}

 

// 定义Window窗口高度

const int CAppGame::WindowHeight()

{

    return 600;

}

 

// 获得 主程序句柄

HINSTANCE CAppGame::GetInstance()

{

    return m_hInstance;

}

 

// 获得 主窗口句柄

HWND CAppGame::GetWnd()

{

    return m_hWnd;

}

 

     11.打开WinApp.cpp, 并在 AppInit()函数里做如下修改

// Windows程序初始化

HWND AppInit( HINSTANCE hInstance, int nShowCmd )

{

    // 程序实例类名

    const char *szWindowsClassName = AppEntryClass()->WindowClassName();

 

    static int iWindowWidth     = AppEntryClass()->WindowWidth();   // 窗口宽度

    static int iWindowHeight    = AppEntryClass()->WindowHeight();  // 窗口高度

 

 

    // 注册窗口类

    WNDCLASS wc;

 

    wc.style            = CS_HREDRAW | CS_VREDRAW;

     

     12.介绍一点小工具。在Tank项目上加入一个Utility目录 加入FormatString.h FormatString.cpp 并加入下面代码

 

#ifndef __FormatString_H__

#define __FromatString_H__

 

#include <assert.h>

 

// 格式串垫片函数模版

template <const int iBufLen> inline const char *FormatString( const char *szFormat, ... )

{

    static char szOutStr[iBufLen];

    szOutStr[0] = 0;

    va_list vl;

 

    va_start( vl, szFormat );

    vsprintf(szOutStr, szFormat, vl);

    va_end(vl);

 

    assert( strlen(szOutStr) < iBufLen && "Critical Damage" );

 

    return szOutStr;

}

 

// 我给出一个常用的缓冲大小为1024字节的宏

#define FSTR FormatString<1024>

 

// 用法:

//      OutputDebugString( FSTR( "Format Out %s", "ok" ) );

 

#endif // __FormatString_H__

 

 

我们可以在任何需要输出字符串格式化时使用它 ,这种方法很方便。这个不能用来嵌套调用,也不能用于复杂线程调用,static char szOutStr[iBufLen]; 这是个唯一的内存地址,其他函数调用改变了他的内容就会出错。也就是说用这个工具组合出来的字符串要立即调用

 

 

现在我们项目文件如下图:

 

 

第三章  显示图片和基本绘图

 

      这一章我将介绍如何用WindowsGDI显示图片到程序主窗口上,用GDI显示只是为了简单方便,来完成我们的任务,如果要更高效的方法可以使用 DirectX 3D DirectDraw接口。

 

1.   首先我们准备好下面这张图片我把它放在运行目录RunTime

取名为ImageC.bmp,接下来就是如何把这张图片显示出来。

     

 

 

 

 

 

 

 

 

      2.在Tank项目里加入Render目录,并增加文件 GDIGraphicsDevice.cpp GDIGraphicsDevice.h GDISurface.cpp GDISurface.h GDITextRender.cpp GDITextRender.h

 

      3.打开GDIGraphicsDevice.h 加入下面代码

#ifndef __GDIGraphicsDevice_H__

#define __GDIGraphicsDevice_H__

 

#include <Windows.h>

 

// 绘图设备,这里指最后能够显示在屏幕上的设备,这是虚拟的,

// 我们把它叫做图形设备。

class CGDIGraphicsDevice

{

public:

 

    CGDIGraphicsDevice();

    virtual ~CGDIGraphicsDevice();

   

    // 创建设备, 让设备操作对象指向 某个窗口

    bool Create( HWND hWnd, int iWidth, int iHeight );

    // 释放设备

    void Release();

    // 更新显示,使主表面内容显示到屏幕上

    void UpdateFrame( HDC hdc );

 

    // 获得操作指向的窗口

    HWND GetWnd();

 

protected:

 

    // 调整窗口大小

    void AdjuestWindowSize( int iWidth, int iHeight );

 

protected:

 

    // 操作指向的窗口句柄

    HWND m_hWnd;

 

};

 

#endif // __GDIGraphicsDevice_H__

 

4.打开GDIGraphicsDevice.cpp 加入下面代码

#include "GDIGraphicsDevice.h"

 

 

CGDIGraphicsDevice::CGDIGraphicsDevice()

{

    m_hWnd = NULL;

}

 

CGDIGraphicsDevice::~CGDIGraphicsDevice()

{

 

}

 

 

bool CGDIGraphicsDevice::Create( HWND hWnd, int iWidth, int iHeight )

{

    return true;

}

 

void CGDIGraphicsDevice::Release()

{

 

}

 

void CGDIGraphicsDevice::UpdateFrame( HDC hdc )

{

 

}

 

HWND CGDIGraphicsDevice::GetWnd()

{

    return m_hWnd;

}

 

//

 

// 调整窗口大小 使绘图区域等于 iWidth iHeight 指示的大小

void CGDIGraphicsDevice::AdjuestWindowSize( int iWidth, int iHeight )

{

 

}

 

      5.实现调整窗口大小函数

// 调整窗口大小 使绘图区域等于 iWidth iHeight 指示的大小

void CGDIGraphicsDevice::AdjuestWindowSize( int iWidth, int iHeight )

{

    // 定义窗口在屏幕上的位置 和 窗口显示区位置

    RECT rcScreen,rcClient;

 

    // 获得窗口在屏幕上的位置 和 窗口显示区位置

    GetWindowRect( m_hWnd, &rcScreen );

    GetClientRect( m_hWnd, &rcClient );

 

    // 计算两个区域的宽和高

    int rScreenWidth    = rcScreen.right - rcScreen.left + 1;

    int rScreenHeight   = rcScreen.bottom - rcScreen.top + 1;

    int rClientWidth    = rcClient.right - rcClient.left + 1;

    int rClientHeight   = rcClient.bottom - rcClient.top + 1;

 

    // 计算两个区域的大小差

    int rW = rScreenWidth   - rClientWidth;

    int rH = rScreenHeight  - rClientHeight;

 

    // 计算出新窗口大小

    rcScreen.right  =   iWidth  + rW;

    rcScreen.bottom =   iHeight + rH;

 

    // 设置窗口大小

    SetWindowPos( m_hWnd,NULL,rcScreen.left, rcScreen.top, rcScreen.right,rcScreen.bottom,SWP_FRAMECHANGED );

 

    // 显示出Windows

    ShowWindow( m_hWnd, SW_SHOW );

    // 更新窗口

    UpdateWindow( m_hWnd );

}

 

 

      6.实现创建设备函数

// 创建设备, 让设备操作对象指向 某个窗口

bool CGDIGraphicsDevice::Create( HWND hWnd, int iWidth, int iHeight )

{

    // 保存窗口句柄

    m_hWnd = hWnd;

    // 调整显示区大小

    AdjuestWindowSize( iWidth, iHeight );

   

    return true;

}

 

 

      7.打开GDISurface.h 输入下面代码

#ifndef __GDISurface_H__

#define __GDISurface_H__

 

#include <windows.h>

 

class CGDIGraphicsDevice;

 

// 表面类 用来存放一个图片,并用来显示图片

class CGDISurface

{

public:

   

    // 位块传送方式

    enum SurfaceBltMode

    {

        BLT_BLOCK,          // 复制方式, 完全显示在目标上

        BLT_ALPHATEST,      // 透明色检测方式, 如果遇到 RGB( 0, 0, 255 ) 图片里的纯蓝色就跳过

        BLT_ALPHABLEND,     // 未支持

    };

 

    CGDISurface();

    virtual ~CGDISurface();

   

    // 创建一个位图

    bool Create( CGDIGraphicsDevice *pDevice, int iWidth, int iHeight );

    // 从文件读取一个位图

    bool LoadBmp( CGDIGraphicsDevice *pDevice, const char *szFileName );

 

    // 释放位图

    void Release();

 

    // 获得宽

    int GetWidth();

    // 获得高

    int GetHeight();

   

    // 获得位图设备句柄

    HDC GetDC();

    // 获得透明通道位图设备句柄

    HDC GetMaskDC();

 

    // 清除表面为 某种颜色

    void Clear( COLORREF c = RGB( 0, 0, 0 ) );

    // 画店

    void SetPixel( int x, int y, COLORREF c );

    // 画线

    void Line( int x1, int y1, int x2, int y2, COLORREF c );

    // 画矩形

    void Rect( int x1, int y1, int x2, int y2, COLORREF c );

   

    // 位图传送

    void Blt( CGDISurface *pSurface, int x, int y, int rx, int ry, int w, int h, SurfaceBltMode sbmMode );

 

protected:

   

    // 表面宽度

    int m_iWidth;

    // 表面高度

    int m_iHeight;

 

    // 位图句柄

    HBITMAP m_hBitmap;

    // 位图透明通道图句柄

    HBITMAP m_hMaskBitmap;

 

    // 保存两种位图句柄

    HBITMAP m_hOldBitmap;

    HBITMAP m_hOldMaskBitmap;

 

    // 两个用来显示的设备句柄

    // 位图设备句柄

    HDC m_hDC;                 

    // 透明通道位图设备句柄

    HDC m_hMaskDC;

 

};

 

#endif // __GDISurface_H__

 

     8.打开GDISurface.cpp 输入下面代码

#include "GDISurface.h"

#include "GDIGraphicsDevice.h"

 

CGDISurface::CGDISurface()

{

    m_iWidth            = 0;

    m_iHeight           = 0;

    m_hBitmap           = NULL;

    m_hMaskBitmap       = NULL;

    m_hOldBitmap        = NULL;

    m_hOldMaskBitmap    = NULL;

    m_hDC               = NULL;                 

    m_hMaskDC           = NULL;

}

 

CGDISurface::~CGDISurface()

{

 

}

 

// 创建一个位图

bool CGDISurface::Create( CGDIGraphicsDevice *pDevice, int iWidth, int iHeight )

{

    return true;

}

 

// 从文件读取一个位图

bool CGDISurface::LoadBmp( CGDIGraphicsDevice *pDevice, const char *szFileName )

{

    return true;

}

 

// 释放位图

void CGDISurface::Release()

{

 

}

 

// 获得宽

int CGDISurface::GetWidth()

{

    return m_iWidth;

}

 

// 获得高

int CGDISurface::GetHeight()

{

    return m_iHeight;

}

 

// 获得位图设备句柄

HDC CGDISurface::GetDC()

{

    return m_hDC;

}

 

// 获得透明通道位图设备句柄

HDC CGDISurface::GetMaskDC()

{

    return m_hMaskDC;

}

 

// 清除表面为 某种颜色

void CGDISurface::Clear( COLORREF c )

{

 

}

 

// 画店

void CGDISurface::SetPixel( int x, int y, COLORREF c )

{

 

}

 

// 画线

void CGDISurface::Line( int x1, int y1, int x2, int y2, COLORREF c )

{

 

}

 

// 画矩形

void CGDISurface::Rect( int x1, int y1, int x2, int y2, COLORREF c )

{

 

}

 

// 位图传送

void CGDISurface::Blt( CGDISurface *pSurface, int x, int y, int rx, int ry, int w, int h, BltMode iMode )

{

 

}

 

10.           实现创建位图

// 创建一个位图

bool CGDISurface::Create( CGDIGraphicsDevice *pDevice, int iWidth, int iHeight )

{

    // 获得主窗口绘图设备句柄

    HWND hWnd = pDevice->GetWnd();

    HDC hdcWindow = ::GetDC( hWnd );

   

    // 创建图形设备句柄

    m_hDC       = ::CreateCompatibleDC( hdcWindow );

    m_hMaskDC   = ::CreateCompatibleDC( hdcWindow );

 

    // 创建位图和透明位图

    m_hBitmap       = ::CreateCompatibleBitmap( hdcWindow, iWidth, iHeight );

    m_hMaskBitmap   = ::CreateBitmap( iWidth, iHeight, 1, 1, NULL );

   

    // 关联设备和位图句柄

    m_hOldBitmap        = (HBITMAP)::SelectObject( m_hDC, m_hBitmap );

    m_hOldMaskBitmap    = (HBITMAP)::SelectObject( m_hMaskDC, m_hMaskBitmap );

 

    // 制作透明通道位图

    ::SetBkColor( m_hDC, RGB(0,0,255) );

    ::BitBlt( m_hMaskDC, 0, 0, iWidth, iHeight, m_hDC, 0, 0, SRCCOPY );

   

    ::SetBkColor( m_hDC, RGB(0,0,0) );

    ::SetTextColor( m_hDC, RGB(255,255,255) );

    ::BitBlt( m_hDC, 0, 0, iWidth, iHeight, m_hMaskDC, 0, 0, SRCAND );

 

    // 释放主窗口绘图句柄

    ::ReleaseDC( hWnd, hdcWindow );

 

    // 保存表面大小

    m_iWidth    = iWidth;

    m_iHeight   = iHeight;

 

    return true;

}

 

11.           实现从文件读取位图, 方法和创建位图类似,只是原位图从文件用LoadImage读入。

// 从文件读取一个位图

bool CGDISurface::LoadBmp( CGDIGraphicsDevice *pDevice, const char *szFileName )

{

    // 读取位图文件信息,确定位图大小

    FILE *fp = fopen( szFileName, "rb" );

    if( NULL == fp )

    {

        OutputDebugString( FSTR( "Open bmp file [%s] failed(%s:%d)", szFileName, __FILE__, __LINE__ ) );

        return false;

    }

 

    BITMAPFILEHEADER bmfh;

    BITMAPINFOHEADER bmih;

 

    fread( &bmfh, sizeof(BITMAPFILEHEADER), 1, fp );

    fread( &bmih, sizeof(BITMAPINFOHEADER), 1, fp );

 

    fclose(fp);

   

    // 如果不是位图则返回失败

    if( bmfh.bfType != 0x4D42 )

    {

        OutputDebugString( FSTR( "the bmp file [%s] type is failed(%s:%d)", szFileName, __FILE__, __LINE__ ) );

        return false;

    }

 

    HBITMAP hBmp = (HBITMAP)::LoadImage(NULL, szFileName, IMAGE_BITMAP, bmih.biWidth, bmih.biHeight, LR_LOADFROMFILE | LR_CREATEDIBSECTION );

 

    m_iWidth    = bmih.biWidth;

    m_iHeight   = bmih.biHeight;

 

    HWND hWnd = pDevice->GetWnd();

    HDC hdcWindow = ::GetDC( hWnd );

 

    m_hBitmap       = ::CreateCompatibleBitmap( hdcWindow, m_iWidth, m_iHeight );

    m_hMaskBitmap   = ::CreateBitmap( m_iWidth, m_iHeight, 1, 1, NULL );

 

    HDC hTempDC = ::CreateCompatibleDC( hdcWindow );

    m_hDC       = ::CreateCompatibleDC( hdcWindow );

    m_hMaskDC   = ::CreateCompatibleDC( hdcWindow );

 

    HBITMAP hOldBitmap  = (HBITMAP)::SelectObject( hTempDC, hBmp );

    m_hOldBitmap        = (HBITMAP)::SelectObject( m_hDC, m_hBitmap );

    m_hOldMaskBitmap    = (HBITMAP)::SelectObject( m_hMaskDC, m_hMaskBitmap );

 

    ::BitBlt( m_hDC, 0, 0, m_iWidth, m_iHeight, hTempDC, 0, 0, SRCCOPY );

 

    ::SetBkColor( m_hDC, RGB(0,0,255) );

    ::BitBlt( m_hMaskDC, 0, 0, m_iWidth, m_iHeight, m_hDC, 0, 0, SRCCOPY );

 

    ::SetBkColor( m_hDC, RGB(0,0,0) );

    ::SetTextColor( m_hDC, RGB(255,255,255) );

    ::BitBlt( m_hDC, 0, 0, m_iWidth, m_iHeight, m_hMaskDC, 0, 0, SRCAND );

 

    ::SelectObject( hTempDC, hOldBitmap );

    DeleteDC( hTempDC );

    DeleteObject( hBmp );

 

    ::ReleaseDC( hWnd, hdcWindow );

 

    return true;

}

 

12.           实现释放位图

// 释放位图

void CGDISurface::Release()

{

    ::SelectObject( m_hDC, m_hOldBitmap );

    ::SelectObject( m_hMaskDC, m_hOldMaskBitmap );

    ::DeleteDC( m_hMaskDC );

    ::DeleteDC( m_hDC );

    ::DeleteObject( m_hBitmap );

    ::DeleteObject( m_hMaskBitmap );

}

 

13.           实现4个基本绘图操作

// 清除表面为 某种颜色

void CGDISurface::Clear( COLORREF c )

{

    // 创建填充画笔

    HBRUSH hBrush = CreateSolidBrush( c );

 

    // 填充整个表面

    RECT rect = { 0, 0, m_iWidth, m_iHeight };

    FillRect( m_hDC, &rect, hBrush );

 

    // 释放画笔

    DeleteObject( hBrush );

}

 

// 画店

void CGDISurface::SetPixel( int x, int y, COLORREF c )

{

    ::SetPixel( m_hDC, x, y, c );

}

 

// 画线

void CGDISurface::Line( int x1, int y1, int x2, int y2, COLORREF c )

{

    HPEN hPen = CreatePen(PS_SOLID, 1, c );

    HPEN hOldPen = SelectPen( m_hDC, hPen );

 

    MoveToEx( m_hDC, x1, y1, (LPPOINT) NULL );

    LineTo( m_hDC, x2, y2 );

 

    SelectPen( m_hDC, hOldPen );

    DeletePen( hPen );

}

 

// 画矩形

void CGDISurface::Rect( int x1, int y1, int x2, int y2, COLORREF c )

{

    // 4条线 矩形边框

    Line( x1, y1, x2, y1, c );

    Line( x1, y2, x2, y2, c );

    Line( x1, y1, x1, y2, c );

    Line( x2, y1, x2, y2, c );

}

 

14.           实现位图传送功能

// 位图传送

void CGDISurface::Blt( CGDISurface *pSurface, int x, int y, int rx, int ry, int w, int h, BltMode iMode )

{

    HDC hdcDst = pSurface->GetDC();

    switch( iMode )

    {

    case BLT_BLOCK:

        // 基本复制方式传送位图

        BitBlt( hdcDst, x, y, w, h, m_hDC, rx, ry, SRCCOPY );

        break;

    case BLT_ALPHATEST:

        // 带透明色方式传送位图

        SetBkColor( hdcDst, RGB(255,255,255) );

        SetTextColor( hdcDst, RGB(0,0,0) );

        BitBlt( hdcDst, x, y, w, h, m_hMaskDC, rx, ry, SRCAND );

        BitBlt( hdcDst, x, y, w, h, m_hDC, rx, ry, SRCPAINT );

        break;

    case BLT_ALPHABLEND:

        break;

    }

}

15.           打开GDIGraphicsDevice.h 加入下面代码

#ifndef __GDIGraphicsDevice_H__

#define __GDIGraphicsDevice_H__

 

#include <Windows.h>

 

class CGDISurface;

 

// 绘图设备,这里指最后能够显示在屏幕上的设备,这是虚拟的,

// 我们把它叫做图形设备。

class CGDIGraphicsDevice

{

public:

 

    CGDIGraphicsDevice();

    virtual ~CGDIGraphicsDevice();

   

    // 创建设备, 让设备操作对象指向 某个窗口

    bool Create( HWND hWnd, int iWidth, int iHeight );

    // 释放设备

    void Release();

    // 更新显示,使主表面内容显示到屏幕上

    void UpdateFrame( HDC hdc );

 

    // 获得操作指向的窗口

    HWND GetWnd();

 

    // 获得主表面

    CGDISurface *GetMainSurface();

 

protected:

 

    // 调整窗口大小

    void AdjuestWindowSize( int iWidth, int iHeight );

 

protected:

 

    // 操作指向的窗口句柄

    HWND m_hWnd;

 

    // 住缓冲表面

    CGDISurface *m_pMainSurface;

 

};

 

#endif // __GDIGraphicsDevice_H__

 

16.           打开GDIGraphicsDevice.cpp 修改和增加下面代码

#include "GDIGraphicsDevice.h"

#include "GDISurface.h"

 

CGDIGraphicsDevice::CGDIGraphicsDevice()

{

    m_hWnd = NULL;

    m_pMainSurface = NULL;

}

 

CGDIGraphicsDevice::~CGDIGraphicsDevice()

{

 

}

 

// 创建设备, 让设备操作对象指向 某个窗口

bool CGDIGraphicsDevice::Create( HWND hWnd, int iWidth, int iHeight )

{

    // 保存窗口句柄

    m_hWnd = hWnd;

    // 调整显示区大小

    AdjuestWindowSize( iWidth, iHeight );

   

    m_pMainSurface = new CGDISurface();

 

    return true;

}

 

// 释放设备

void CGDIGraphicsDevice::Release()

{

    delete m_pMainSurface;

    m_pMainSurface = NULL;

}

 

17.           增加获取主表面函数

// 获得主表面

CGDISurface *CGDIGraphicsDevice::GetMainSurface()

{

    return m_pMainSurface;

}

 

18.           增加创建主表面

// 创建设备, 让设备操作对象指向 某个窗口

bool CGDIGraphicsDevice::Create( HWND hWnd, int iWidth, int iHeight )

{

    // 保存窗口句柄

    m_hWnd = hWnd;

    // 调整显示区大小

    AdjuestWindowSize( iWidth, iHeight );

   

    m_pMainSurface = new CGDISurface();

 

    if( !m_pMainSurface->Create( this, iWidth, iHeight ) )

    {

        OutputDebugString( FSTR( "GraphicsDevice Create Main Surface Failed" ) );

        return false;

    }

 

    return true;

}

 

// 释放设备

void CGDIGraphicsDevice::Release()

{

    m_pMainSurface->Release();

    delete m_pMainSurface;

    m_pMainSurface = NULL;

}

 

19.           增加更新显示函数功能

// 更新显示,使主表面内容显示到屏幕上

void CGDIGraphicsDevice::UpdateFrame( HDC hdc )

{

    // 如果主表面没有创建成功就返回

    if( NULL == m_pMainSurface )

        return;

 

    HDC hdcSrc = m_pMainSurface->GetDC();

    int iWidth  = m_pMainSurface->GetWidth();

    int iHeight = m_pMainSurface->GetHeight();

   

    // 更新显示到屏幕,将由 WM_PAINT 调用

    BitBlt( hdc, 0, 0, iWidth, iHeight, hdcSrc, 0, 0, SRCCOPY );

}

 

 

 

 

 

 

 

 

 

20.           为了方便我们在项目Tank上加入一个全局数据定义文件,大家可以在此定义非常公用或重要的全局变量。并加入两个文件 ShareData.h ShareData.cpp

21.           打开ShareData.h 加入下面代码

#ifndef __ShareData_H__

#define __ShareData_H__

 

#include "GDIGraphicsDevice.h"

#include "GDISurface.h"

#include "GDITextRender.h"

 

extern CGDIGraphicsDevice *theGraphicsDevice;

 

#endif // __ShareData_H__

22.           打开ShareData.cpp 加入下面代码

#include "ShareData.h"

 

CGDIGraphicsDevice *theGraphicsDevice = NULL;

 

23.           打开AppGame.cpp里加入下面代码

#include "AppGame.h"

#include "ShareData.h"

 

CGDISurface theSurface;

 

// 定义主游戏程序实例

CAppGame theAppGame;

 

24.           加入下面代码,创建

// 初始化程序

bool CAppGame::Initialize( HINSTANCE hInstance, HWND hWnd )

{

    m_hInstance = hInstance;

    m_hWnd = hWnd;

   

    // 创建图形设备

    theGraphicsDevice = new CGDIGraphicsDevice();

    if( !theGraphicsDevice->Create( m_hWnd, WindowWidth(), WindowHeight() ) )

    {

        OutputDebugString( "Create GraphicsDevice Failed" );

        return false;

    }

   

    // 读取位图

    if( !theSurface.LoadBmp( theGraphicsDevice, "ImageC.bmp" ) )

    {

        OutputDebugString( "Load bitmap Failed" );

        return false;

    }

 

    return true;

}

 

// 结束程序

void CAppGame::Terminal()

{

    // 释放位图

    theSurface.Release();

 

    // 释放图形设备

    theGraphicsDevice->Release();

    delete theGraphicsDevice;

}

25.  修改下面代码来显示读入的图片

// 主处理函数

void CAppGame::Process()

{

    Render();

}

 

// 主渲染函数

void CAppGame::Render()

{

    // 显示位图

    theSurface.Blt( theGraphicsDevice->GetMainSurface(), 0, 0, 0, 0, WindowWidth(), WindowHeight(), CGDISurface::BLT_BLOCK );

}

26.           在消息处理函数里加入更新显示代码

// 主窗口消息处理函数

LRESULT CAppGame::WndProc( UINT message, WPARAM wParam, LPARAM lParam )

{

    HDC hdc;

    PAINTSTRUCT ps;

 

    switch( message )

    {

    case WM_PAINT:

        {

            hdc = BeginPaint( m_hWnd, &ps );

            if( theGraphicsDevice )

            {

                // 调用图形设备更新显示

                theGraphicsDevice->UpdateFrame( hdc );

            }

            EndPaint( m_hWnd, &ps );

        }

        return TRUE;

    case WM_ERASEBKGND:

        return TRUE;

    }

 

    return FALSE;

}

 

27.           CAppGame::Render()里加入更新显示通知

// 主渲染函数

void CAppGame::Render()

{

    // 显示位图

    theSurface.Blt( theGraphicsDevice->GetMainSurface(), 0, 0, 0, 0, WindowWidth(), WindowHeight(), CGDISurface::BLT_BLOCK );

 

    // 通知Window程序更新显示窗口

    RECT rcWindow;

    GetClientRect( m_hWnd, &rcWindow );

    RedrawWindow( m_hWnd, &rcWindow, NULL, RDW_INVALIDATE );

}

28.           运行程序可以看到如下效果

29.在CAppGame::Render() 里加入下面代码

// 主渲染函数

void CAppGame::Render()

{

    // 清除主表面

    theGraphicsDevice->GetMainSurface()->Clear( RGB( 125,0, 23 ) );

 

    // 显示位图

    theSurface.Blt( theGraphicsDevice->GetMainSurface(), 0, 0, 0, 0, WindowWidth(), WindowHeight(), CGDISurface::BLT_BLOCK );

 

    // 通知Window程序更新显示窗口

    RECT rcWindow;

    GetClientRect( m_hWnd, &rcWindow );

    RedrawWindow( m_hWnd, &rcWindow, NULL, RDW_INVALIDATE );

}

大家可以看到下面效果

 

30.将CAppGame::Render() 修改为

void CAppGame::Render()

{

    // 清除主表面

    theGraphicsDevice->GetMainSurface()->Clear( RGB( 125,0, 23 ) );

 

    // 显示位图

    theSurface.Blt( theGraphicsDevice->GetMainSurface(), 0, 0, 0, 0, WindowWidth(), WindowHeight(), CGDISurface::BLT_ALPHATEST );

 

    // 通知Window程序更新显示窗口

    RECT rcWindow;

    GetClientRect( m_hWnd, &rcWindow );

    RedrawWindow( m_hWnd, &rcWindow, NULL, RDW_INVALIDATE );

}

可以看到如下效果

蓝色已经没有了,绘制方式改为透明方式。

31.我们将theSurface 改为 theImageC 并且放到ShareData.cpp 中定义 以便以后使用方便

// 初始化程序

bool CAppGame::Initialize( HINSTANCE hInstance, HWND hWnd )

{

    m_hInstance = hInstance;

    m_hWnd = hWnd;

   

    // 创建图形设备

    theGraphicsDevice = new CGDIGraphicsDevice();

    if( !theGraphicsDevice->Create( m_hWnd, WindowWidth(), WindowHeight() ) )

    {

        OutputDebugString( "Create GraphicsDevice Failed" );

        return false;

    }

   

 

    // 读取位图

    theImageC = new CGDISurface();

    if( !theImageC->LoadBmp( theGraphicsDevice, "ImageC.bmp" ) )

    {

        OutputDebugString( "Load bitmap Failed" );

        return false;

    }

 

    return true;

}

 

// 结束程序

void CAppGame::Terminal()

{

    // 释放位图

    theImageC->Release();

    delete theImageC;

    theImageC = NULL;

 

    // 释放图形设备

    theGraphicsDevice->Release();

    delete theGraphicsDevice;

    theGraphicsDevice = NULL;

}

 

// 主处理函数

void CAppGame::Process()

{

    Render();

}

 

// 主渲染函数

void CAppGame::Render()

{

    // 清除主表面

    theGraphicsDevice->GetMainSurface()->Clear( RGB( 125,0, 23 ) );

 

    // 显示位图

    theImageC->Blt( theGraphicsDevice->GetMainSurface(), 0, 0, 0, 0, WindowWidth(), WindowHeight(), CGDISurface::BLT_ALPHATEST );

 

    // 通知Window程序更新显示窗口

    RECT rcWindow;

    GetClientRect( m_hWnd, &rcWindow );

    RedrawWindow( m_hWnd, &rcWindow, NULL, RDW_INVALIDATE );

}

 

 

第四章  文字显示 计时器 刷新率

 

            在编写程序时有些很重要的信息要显示出来,帮助调试程序,其中显示文字处于非常重要的位置。计时器我们将用到系统函数timeGetTime(),它是用来获得系统从开始到现在所经历的毫秒数,记录一个开始时间,然后不断判断现在的时间和开始时间的间隔,以达到控制某一段代码以一定的频率执行。刷新率是表示在一秒钟之内更新显示了多少次,最后我们把它显示在屏幕上。

           

1.            打开GDITextRender.h 输入下面代码

#ifndef __GDITextRender_H__

#define __GDITextRender_H__

 

#include <windows.h>

 

class CGDISurface;

 

// 文字显示类

class CGDITextRender

{

public:

 

    CGDITextRender();

    virtual ~CGDITextRender();

   

    // 创建字体

    bool Create( PLOGFONT pFont );

    // 释放

    void Release();

 

    // 向表面输出文字

    void TextOut( CGDISurface *pSurface, int x, int y, const char *szString, COLORREF c, int len = 0 );

 

protected:

 

    // 字体句柄

    HFONT m_hFont;

 

};

 

#endif // __GDITextRender_H__

 

2.            打开GDITextRender.cpp 输入下面代码

#include "GDITextRender.h"

 

CGDITextRender::CGDITextRender()

{

    m_hFont = NULL;

}

 

CGDITextRender::~CGDITextRender()

{

 

}

 

// 创建字体

bool CGDITextRender::Create( PLOGFONT pFont )

{

    return true;

}

 

// 释放

void CGDITextRender::Release()

{

}

 

// 向表面输出文字

void CGDITextRender::TextOut( CGDISurface *pSurface, int x, int y, const char *szString, COLORREF c, int len )

{

}

 

3.            实现创建字体功能

// 创建字体

bool CGDITextRender::Create( PLOGFONT pFont )

{

    // 创建 pFont 指定的字体

    m_hFont = CreateFont(

        pFont->lfHeight,

        pFont->lfWidth,

        pFont->lfEscapement,

        pFont->lfOrientation,

        pFont->lfWeight,

        pFont->lfItalic,

        pFont->lfUnderline,

        pFont->lfStrikeOut,

        pFont->lfCharSet,

        pFont->lfOutPrecision,

        pFont->lfClipPrecision,

        pFont->lfQuality,

        pFont->lfPitchAndFamily,

        pFont->lfFaceName );

 

    if( m_hFont == NULL )

    {

        OutputDebugString( FSTR( "Create Font Failed: %s %d/n", pFont->lfFaceName, pFont->lfHeight ) );

        return false;

    }

 

    return true;

}

 

4.实现释放字体

// 释放

void CGDITextRender::Release()

{

    // 如果字体已经创建

    if( m_hFont )

    {

        // 删除字体句柄

        DeleteObject( m_hFont );

        m_hFont = NULL;

    }

}

 

5.修改文字输出函数,加入输出文字功能

 

#include "GDITextRender.h"

#include "GDISurface.h"

#include "FormatString.h"

#include <windowsx.h>

 

 

// 向表面输出文字

void CGDITextRender::TextOut( CGDISurface *pSurface, int x, int y, const char *szString, COLORREF c, int len )

{

    // 如果指定的字符串长度是0

    if( len == 0 )

    {

        // 自动获取字符串长度

        len = (int)strlen( szString );

    }

 

    // 获得表面设备句柄

    HDC hdc = pSurface->GetDC();

 

    // 选择字体

    HFONT hOldFont = SelectFont( hdc, m_hFont );

 

    // 显示文字

    SetBkMode( hdc, TRANSPARENT );

    SetBkColor( hdc, RGB(0,0,0) );

    SetTextColor( hdc, c );

    ::TextOut( hdc, x, y, szString, len );

 

    // 恢复字体

    SelectFont( hdc, hOldFont );

}

 

6.打开ShareData.h 加入下面代码

#ifndef __ShareData_H__

#define __ShareData_H__

 

#include "GDIGraphicsDevice.h"

#include "GDISurface.h"

#include "GDITextRender.h"

 

extern CGDIGraphicsDevice *theGraphicsDevice;

 

extern CGDISurface *theImageC;

 

extern CGDITextRender *theTextRender;

 

#endif // __ShareData_H__

 

 

7.打开ShareData.cpp 加入下面代码

 

#include "ShareData.h"

 

CGDIGraphicsDevice *theGraphicsDevice = NULL;

 

CGDISurface *theImageC = NULL;

 

CGDITextRender *theTextRender = NULL;

 

 

8.打开AppGame.cpp 加入 初始化文字输出功能 和释放

// 初始化程序

bool CAppGame::Initialize( HINSTANCE hInstance, HWND hWnd )

{

    m_hInstance = hInstance;

    m_hWnd = hWnd;

   

    // 创建图形设备

    theGraphicsDevice = new CGDIGraphicsDevice();

    if( !theGraphicsDevice->Create( m_hWnd, WindowWidth(), WindowHeight() ) )

    {

        OutputDebugString( "Create GraphicsDevice Failed" );

        return false;

    }

   

 

    // 读取位图

    theImageC = new CGDISurface();

    if( !theImageC->LoadBmp( theGraphicsDevice, "ImageC.bmp" ) )

    {

        OutputDebugString( "Load bitmap Failed" );

        return false;

    }

 

 

    // 初始化文字输出功能

    LOGFONT FontInfo = { 16, 0, 0, 0,

        FW_NORMAL, FALSE, FALSE, FALSE,

        DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,

        CLIP_DEFAULT_PRECIS,

        DEFAULT_QUALITY,

        VARIABLE_PITCH,

        "宋体" };

 

    theTextRender = new CGDITextRender();

    if( !theTextRender->Create( &FontInfo ) )

    {

        OutputDebugString( "Create theTextRender Failed/n" );

        return false;

    }

 

    return true;

}

 

// 结束程序

void CAppGame::Terminal()

{

    // 释放文字输出功能

    theTextRender->Release();

    delete theTextRender;

    theTextRender = NULL;

 

    // 释放位图

    theImageC->Release();

    delete theImageC;

    theImageC = NULL;

 

    // 释放图形设备

    theGraphicsDevice->Release();

    delete theGraphicsDevice;

    theGraphicsDevice = NULL;

}

 

9.在CAppGame::Render()函数里做如下修改来测试文字输出功能

// 主渲染函数

void CAppGame::Render()

{

    CGDISurface *pScreen = theGraphicsDevice->GetMainSurface();

 

    // 清除主表面

    pScreen->Clear( RGB( 125,0, 23 ) );

 

    // 显示位图

    theImageC->Blt( pScreen, 0, 0, 0, 0, WindowWidth(), WindowHeight(), CGDISurface::BLT_ALPHATEST );

 

    // 显示文字

    theTextRender->TextOut( pScreen, 0, 0, "测试文字ABC", RGB( 255, 255, 255 ) );

 

    // 通知Window程序更新显示窗口

    RECT rcWindow;

    GetClientRect( m_hWnd, &rcWindow );

    RedrawWindow( m_hWnd, &rcWindow, NULL, RDW_INVALIDATE );

}

看到如下结果

 

 

 

 

 

 

            10.在Tank项目Utility目录里加入Fps.cpp Fps.h

           

            11.打开Fps.h 输入下面代码

#ifndef __Fps_H__

#define __Fps_H__

 

#include <WTypes.h>

 

// fps

class CFps

{

public:

 

    CFps();

    virtual ~CFps();

 

    // 统计, 没掉一次 计数器加1 并判断 是否该计算FPS

    void Count();

   

    // 获得FPS

    int GetFps();

 

protected:

   

    // 记录刷新率

    int m_iFps;

    // 统计次数

    int m_iCount;

    // 上一次记录FPS的时间

    DWORD m_dwPrevRecordFpsTime;

    // 计算FPS时间间隔

    DWORD m_dwRecordFpsInterval;

 

};

 

#endif // __Fps_H__

            12.打开Fps.cpp 输入下面代码

#include "Fps.h"

#include <mmsystem.h>

#pragma comment( lib, "winmm.lib" )

 

CFps::CFps()

{

    m_iCount = 0;

    m_iFps = 0;

    m_dwPrevRecordFpsTime = 0;

    m_dwRecordFpsInterval = 1000; // 1秒钟记录一次

}

 

CFps::~CFps()

{

 

}

 

void CFps::Count()

{

    // 没调用一次就加1

    m_iCount++;

 

    // 如果时间已经过去1000毫秒

    if( timeGetTime() - m_dwPrevRecordFpsTime >= m_dwRecordFpsInterval )

    {

        // 记录1秒钟调用次数

        m_iFps = m_iCount;

        // 复位计数器

        m_iCount = 0;

        // 复位开始计时时间

        m_dwPrevRecordFpsTime = timeGetTime();

    }

}

 

int CFps::GetFps()

{

    return m_iFps;

}

            13.在ShareData.h ShareData.cpp 中分别加入下面代码

ShareData.h :

#ifndef __ShareData_H__

#define __ShareData_H__

 

#include "GDIGraphicsDevice.h"

#include "GDISurface.h"

#include "GDITextRender.h"

#include "Fps.h"

 

extern CGDIGraphicsDevice *theGraphicsDevice;

 

extern CGDISurface *theImageC;

 

extern CGDITextRender *theTextRender;

 

extern CFps *theRenderFps;

 

#endif // __ShareData_H__

 

ShareData.cpp :

#include "ShareData.h"

 

CGDIGraphicsDevice *theGraphicsDevice = NULL;

 

CGDISurface *theImageC = NULL;

 

CGDITextRender *theTextRender = NULL;

 

CFps *theRenderFps = NULL;

 

            14.在AppGame.cpp CAppGame::Initialize() CAppGame::Terminal()中加入下面代码

 

    theTextRender = new CGDITextRender();

    if( !theTextRender->Create( &FontInfo ) )

    {

        OutputDebugString( "Create theTextRender Failed/n" );

        return false;

    }

 

    // 创建渲染刷新率统计器

    theRenderFps = new CFps();

 

    return true;

}

 

// 结束程序

void CAppGame::Terminal()

{

    // 释放渲染刷新率统计器

    delete theRenderFps;

 

    // 释放文字输出功能

    theTextRender->Release();

                       

 

            15.在 CAppGame::Render() 函数里加入 刷新率统计,并显示在屏幕上,并在

AppGame.cpp 最上加入 #include “FormatString.h”

// 主渲染函数

void CAppGame::Render()

{

    CGDISurface *pScreen = theGraphicsDevice->GetMainSurface();

 

    // 清除主表面

    pScreen->Clear( RGB( 127,127, 127 ) );

 

    // 显示位图

    //theImageC->Blt( pScreen, 0, 0, 0, 0, WindowWidth(), WindowHeight(), CGDISurface::BLT_ALPHATEST );

 

    // 显示文字

    theTextRender->TextOut( pScreen, 0, 0, FSTR( "FPS = %2d", theRenderFps->GetFps() ), RGB( 255, 255, 255 ) );

 

    // 通知Window程序更新显示窗口

    RECT rcWindow;

    GetClientRect( m_hWnd, &rcWindow );

    RedrawWindow( m_hWnd, &rcWindow, NULL, RDW_INVALIDATE );

 

    theRenderFps->Count();

}

运行程序看到如下效果

           

            16.为了给CPU更多的时间,我们不需要程序运行很快,我们如何限制渲染速度,打开AppGame.h 加入下面代码, 再运行可以看到 Fps已经是30

 

protected:

 

    // 保存主程序句柄

    HINSTANCE m_hInstance;

    // 保存主窗口句柄

    HWND m_hWnd;

 

    // 最后一次渲染的时间

    DWORD m_dwRenderLastTime;

};

      17.在主渲染函数中加入

// 主渲染函数

void CAppGame::Render()

{

    if( timeGetTime() - m_dwRenderLastTime < 33 )

    {

        return;

    }

    m_dwRenderLastTime = timeGetTime();

第五章  精灵动画

                       

            在游戏中跑来跑去的小人,小狗,Tank 等叫做精灵,每个精灵都会有一张图画,如果会做动作的会有几张图画交替显示,从而看起来像是在跑。如图 28x28大小的一个图块是精灵动画的一张图片,后面的那个是第二张,我们把这每一张图片叫做帧, 我们说Tank的上方向行走是2帧动画。

在我们的项目中,我们定义帧的方法是 记录一个图快的位置和大小

如图 X,Y,W,H 来表示一个帧。

 

1.在TANK项目中加入一个目录Animation,并增加6个文件 Frame.h

Frame.cpp Animation.h Animation.cpp SpriteManager.h SpriteManager.cpp

            2.在Frame.h Frame.cpp 增加帧类代码

 

Frame.h :

#ifndef __Frame_H__

#define __Frame_H__

 

#include "GDISurface.h"

 

// 动画帧类

class CFrame

{

public:

 

    // ImageC上的位置和大小

    int m_iX;

    int m_iY;

    int m_iW;

    int m_iH;

 

    // 渲染到表面

    void Render( CGDISurface *pSurface, int iX, int iY, CGDISurface::BltMode iMode );

 

};

 

#endif // __Frame_H__

 

Frame.cpp :

#include "Frame.h"

 

#include "ShareData.h"

 

// 渲染到表面

void CFrame::Render( CGDISurface *pSurface, int iX, int iY, CGDISurface::BltMode iMode )

{

    theImageC->Blt( pSurface, iX, iY, m_iX, m_iY, m_iW, m_iH, iMode );

}

 

3.打开Animation.h 输入下面代码

#ifndef __Animation_H__

#define __Animation_H__

 

#include "Frame.h"

#include <vector>

 

// 动画类

class CAnimation

{

public:

 

    CAnimation();

    virtual ~CAnimation();

 

    // 加入帧

    void Push( CFrame& f );

 

    // 获得总帧数

    int GetMaxFrame();

    // 获得某帧数据

    CFrame *GetFrame( int iFrame );

 

protected:

 

    // 帧数据

    std::vector<CFrame> m_vectorFrame;

 

};

 

#endif // __Animation_H__

 

4.            打开Animation.cpp 输入下面代码

#include "Animation.h"

 

 

CAnimation::CAnimation()

{

    m_vectorFrame.clear();

}

 

CAnimation::~CAnimation()

{

    m_vectorFrame.clear();

}

 

// 加入帧

void CAnimation::Push( CFrame& f )

{

    m_vectorFrame.push_back( f );

}

 

// 获得总帧数

int CAnimation::GetMaxFrame()

{

    return (int)m_vectorFrame.size();

}

 

// 获得某帧数据

CFrame *CAnimation::GetFrame( int iFrame )

{

    if( iFrame < 0 && iFrame >= (int)m_vectorFrame.size() )

        return NULL;

 

    return &m_vectorFrame[iFrame];

}

 

5.            打开SpriteManager.h 输入下面代码

#ifndef __SpriteManager_H__

#define __SpriteManager_H__

 

#include "Animation.h"

 

class CSpriteManager

{

public:

   

    CSpriteManager();

    virtual ~CSpriteManager();

 

    // 创建动画数据集

    bool Create();

    // 释放动画数据集

    void Release();

 

    // 获得动画数据

    CAnimation *GetAnimation( int index );

 

    // 获得动画总数

    int GetAnimationNumber();

 

protected:

 

    // 存储动画数据

    std::vector< CAnimation > m_vectorAnimation;

 

};

 

#endif // __SpriteManager_H__

 

6.打开SpriteManager.cpp 输入下面代码

#include "SpriteManager.h"

 

CSpriteManager::CSpriteManager()

{

 

}

 

CSpriteManager::~CSpriteManager()

{

 

}

 

// 创建动画数据集

bool CSpriteManager::Create()

{

    return true;

}

 

 

// 释放动画数据集

void CSpriteManager::Release()

{

    m_vectorAnimation.clear();

}

 

// 获得动画数据

CAnimation *CSpriteManager::GetAnimation( int index )

{

    if( index < 0 || index >= (int)m_vectorAnimation.size() )

        return NULL;

 

    // 返回index所指的动画

    return &m_vectorAnimation[index];

}

 

// 获得动画总数

int CSpriteManager::GetAnimationNumber()

{

    return (int)m_vectorAnimation.size();

}

 

 

            7.在CSpriteManager::Create()里加入第一个动画,动画也可能只有1帧。下面是加入前两个帧给0号动画。同样的方法,可以在此函数里加入所有动画。

 

// 创建动画数据集

bool CSpriteManager::Create()

{

    CAnimation *pAni;

    CFrame f;

    // 第一个动画

    pAni = new CAnimation();

   

    // 第一帧 (0,0,28,28)

    f.m_iX = 0;

    f.m_iY = 0;

    f.m_iW = 28;

    f.m_iH = 28;

    pAni->Push( f );

 

    // 第二帧 (28,0,28,28)

    f.m_iX = 28;

    f.m_iY = 0;

    f.m_iW = 28;

    f.m_iH = 28;

    pAni->Push( f );

   

    // Ani ID = 0 两帧动画加入动画库

    m_vectorAnimation.push_back( *pAni );

 

    delete pAni;

 

    return true;

}

 

            8.在ShareData.h ShareData.cpp 里分别加入下面代码

 

ShareData.h:

#ifndef __ShareData_H__

#define __ShareData_H__

 

#include "GDIGraphicsDevice.h"

#include "GDISurface.h"

#include "GDITextRender.h"

#include "Fps.h"

#include "SpriteManager.h"

 

extern CGDIGraphicsDevice *theGraphicsDevice;

 

extern CGDISurface *theImageC;

 

extern CGDITextRender *theTextRender;

 

extern CFps *theRenderFps;

 

extern CSpriteManager *theSpriteManager;

 

#endif // __ShareData_H__

 

 

ShareData.cpp:

 

#include "ShareData.h"

 

CGDIGraphicsDevice *theGraphicsDevice = NULL;

 

CGDISurface *theImageC = NULL;

 

CGDITextRender *theTextRender = NULL;

 

CFps *theRenderFps = NULL;

 

CSpriteManager *theSpriteManager = NULL;

     9.在AppGame.cpp里加入下面初始化和释放动画管理器代码

 

    theTextRender = new CGDITextRender();

    if( !theTextRender->Create( &FontInfo ) )

    {

        OutputDebugString( "Create theTextRender Failed/n" );

        return false;

    }

 

    // 创建渲染刷新率统计器

    theRenderFps = new CFps();

 

    // 创建动画管理器

    theSpriteManager = new CSpriteManager();

    if( !theSpriteManager->Create() )

    {

        OutputDebugString( "Create Sprite Manager Failed/n" );

        return false;

    }

 

    return true;

}

 

// 结束程序

void CAppGame::Terminal()

{

    // 释放动画管理器

    theSpriteManager->Release();

    delete theSpriteManager;

    theSpriteManager = NULL;

 

 

            10.在 CAppGame::Render()里作如下修改,显示一个动画的一帧。

    // 清除主表面

    pScreen->Clear( RGB( 127,127, 127 ) );

 

    // 显示位图

    //theImageC->Blt( pScreen, 30, 30, 0, 0, WindowWidth(), WindowHeight(), CGDISurface::BLT_ALPHATEST );

 

    // 显示一个动画的一帧

    CAnimation *pAni = theSpriteManager->GetAnimation( 0 );

    CFrame *pF = pAni->GetFrame( 0 );

    pF->Render( pScreen, 100, 100, CGDISurface::BLT_ALPHATEST );

 

    // 显示文字

    theTextRender->TextOut( pScreen, 0, 0, FSTR( "FPS = %2d", theRenderFps->GetFps() ), RGB( 255, 255, 255 ) );

 

            大家可以看到显示效果是,一个Tank显示在屏幕上。

 

 

10.接下来的任务就是就是把这些动画编排好,以便以后使用。根据下面的图我们可以看出,这组图是很有规律,这是每一个TANK4个方向上的动画,我写了一个循环按照规则创建动画在CSpriteManager.cpp Create()函数里加入初始化代码

// 创建动画数据集

bool CSpriteManager::Create()

{

    CAnimation *pAni;

    CFrame f;

 

    // 所有TANK图片都是28x28

    f.m_iW = 28;

    f.m_iH = 28;

 

    // 两组动画,上面是玩家TANK,下面是地方TANK

    for( int k=0; k<2; k++ )

    {

        // 每一大组内有8TANK的动画

        for( int l=0; l<8; l++ )

        {

            // 每种Tank4个方向上的动画

            for( int j=0; j<4; j++ )

            {

                pAni = new CAnimation();

 

                // 每个方向上的动画有2

                for( int i=0; i<2; i++ )

                {

                    f.m_iX = l*28*2 + i*28;

                    f.m_iY = k*28*4 + j*28;

                    pAni->Push( f );

                }

 

                m_vectorAnimation.push_back( *pAni );

                delete pAni;

            }

        }

    }

   

    // 动画定位公式是: 动画ID = 坦克型号 * 4 + 方向

 

    return true;

}

11.在CAppGame::Render()里加入并修改代码,运行程序可以看到TANK行走动画,每2秒钟随机变化一个TANK

    // 显示位图

    //theImageC->Blt( pScreen, 30, 30, 0, 0, WindowWidth(), WindowHeight(), CGDISurface::BLT_ALPHATEST );

 

   

    // 当前帧

    static int iCurFrame = 0;

    // 动画计时开始时间

    static DWORD dwAniStartTime = timeGetTime();

 

    // 当前动画ID

    static int iCurAni = 0;

    // 最后一次改变动画的时间

    static DWORD dwAniChangeLastTime = timeGetTime();

 

    if( timeGetTime() - dwAniChangeLastTime > 2000 )

    {

        iCurAni = rand()%64;

        dwAniChangeLastTime = timeGetTime();

    }

    if( timeGetTime() - dwAniStartTime > 33 )

    {

        iCurFrame++;

        if( iCurFrame >= 2 )

            iCurFrame = 0;

        dwAniStartTime = timeGetTime();

    }

 

    // 显示一个动画的一帧

    CAnimation *pAni = theSpriteManager->GetAnimation( iCurAni );

    CFrame *pF = pAni->GetFrame( iCurFrame );

    pF->Render( pScreen, 100, 100, CGDISurface::BLT_ALPHATEST );

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

第六章  场景

 

 

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值