默然说话

一个异想天开的人做着异想天开的梦

用户操作
[即时聊天] [发私信] [加为好友]
默然ID:mouyong
66137次访问,排名1571(-1)好友5人,关注者9
我快乐,我存在
mouyong的文章
原创 108 篇
翻译 4 篇
转载 31 篇
评论 13 篇
默然的公告
如果要联系我,希望能说明来意,谢谢.

点击这里给我发消息

Google

最近评论
sap99:www.sap99.com/,SAP99资料多多

SAP免费资料下载
http://www.sap99.com

有很多的学习资料,推荐一下,
peigen:又~~~~为什么是又呢???
dcopperfield:顶下
gaoyunpeng:无意中进入到这个博客,很快就被里面的内容所吸引,感觉很有意思,不知道为什么会有这样的感觉,或许只是一种直觉上的吸引吧,一直在看博客里的文章,觉得很不错,天天等更新,哈哈,终于看到新的文章啦~
我会一直关注的~
mouyong:谢谢你的鼓励,我会更加努力。
了祝愿你实现自己的理想,达成自己的目标
文章分类
收藏
    相册
    存档
    软件项目交易
    订阅我的博客
    XML聚合  FeedSky
    订阅到鲜果
    订阅到Google
    订阅到抓虾
    订阅到BlogLines
    订阅到Yahoo
    订阅到GouGou
    订阅到飞鸽
    订阅到Rojo
    订阅到newsgator
    订阅到netvibes

    原创 游戏编程中的人工智能(2-1)收藏

    新一篇: 游戏编程中的人工智能(2-2) | 旧一篇: Java文件同步软件(源代码)

    nbsp;
    第2章        Windows编程进阶
    “我总忍不住要表达我不喜欢那些认为理解会破坏他们的经验的人们……他们怎么会知道呢?”
    Marvin Minsky
    在前一章里已经学过了如何创建窗口——现在开始介绍如何利用Windows的绘图及文字工具。当熟悉了这些内容之后,将确切地说明什么是资源,怎样利用它们来创建自己的菜单,图标,鼠标光标等内容。学完本章后,将具备足够的Windows编程基本知识来理解本书其余章节所有源代码范例,然后就可以进入遗传算法和神经网络的学习了。其中偶尔会涉及一些必须让读者了解的额外知识,但一定是很容易解释清楚的。
     
    2.1       Windows图形设备接口
    Windows负责屏幕上绘制图形的部分称为图形设备接口(Graphics Device Interface,GDI),可在窗口中显示的图形大体上可以被归为4类:
    文本(text)。GDI中关于文本的部分显然非常重要。GDI中提供了大量的格式化文本和输出工具,用户几乎可以得到想得到的任何一种方式在屏幕上创建和输出文字。
    直线(line),形状(shape),曲线(curve)。GDI对绘制直线,曲线(Bezier曲线),基本形(如矩形和椭圆)和多边形提供了充分的支持。多边形就是由一系列相互连接的顶点组成的shape,最后一点和第一点相连,形成闭合图形。绘图时,首先需要创建一支用来绘图的画笔(Pen),然后用该画笔绘制需要的shape。
    位图(bitmap)。GDI提供了许多处理位图的函数。可以加载位图,缩放位图,保存位图或从一处复制位图到另一处。位图复制通常被游戏程序员称为blitting(图像位块传送)。
    填充区域(filled area)。绘图时除了画笔,还可以创建用户自己的画刷(Brush)。画刷可用来填充屏幕上的区域(region)和shape。
    注意:在游戏界里,GDI是以慢出名的。所谓慢,是与其他API,如OpenGL或微软的DirectX相比较而言的。在例子中选用GDI,是因为它简单好用,也好理解,使用它也足够快,而且更重要的是,在代码中不会充斥混淆视听的复杂API调用。
    设备描述表——又称DC——在使用GDI的图形文字绘制中扮演很重要的角色。在朝任何图形输出设备——屏幕,打印机,甚至是内存中的一块位图——开始绘图之前,都必须获得该设备的设备描述表的句柄。画刷,画笔,光标,桌面,窗口实例(hInstance),图标,位图……都有相应的句柄。句柄就象执照。要开车需要执照,要经营酒店也需要执照。类似地,也需要取得对特定类型的对象或设备的句柄,才能进行想要的操作。如果向Window发出请求“是否可以在这个窗口上绘图?”,则Windows就会赋予用户一个执照——窗口句柄——这样就可以绘图了。
    2.1.1.1   如何得到句柄(Handle)?
    可以有几种方法用来取得设备描述表句柄,为了简单,将用缩写HDC来称呼设备描述表句柄。
    在第一章中处理WM_PAINT消息的内容时,首先创建的是PAINTSTRUCT结构,Windows将其填充,以描述关于窗口的详细信息。PAINTSTRUCT结构定义如下:
    typedef struct tagPAINTSTRUCT{
        HDC hdc;
        BOOL fErase;
        RECT rcPaint;
        BOOL fRestore;
        BOOL fIncUpdate;
        BYTE rgbReseerved[
    32];
    }

    现在可以发现,第一个域hdc,就是HDC。如果要在WindowProc的WM_PAINT部分进行绘图,便可以将hdc作为窗口句柄。
    注意:从PAINTSTRUCT中获得的句柄只对在RECT结构rcPaint定义的区域中绘图有效。如果想要在此区域外绘图,就需要从其他途径获得HDC。
    当调用BeginPaint时,它不但返回一个HDC,同时还填充了PAINTSTRUCT结构。因此,一个获得HDC的方法如下:
    case WM_PAINT:{
        PAINTSTRUCT ps;
        HDC hdc;
        hdc
    =BeginPaint(hwnd,&ps);
        
    //**在这里使用hdc作图
        EndPaint(hwnd,&ps);
        
    return 0;
    }

    但你不需要将所有的绘图工作都集中在WindowProc的WM_PAINT部分中进行,只要从Windows那里得到了HDC,你就可以在任何时候绘图。为了确保在任何时候从Windows获得一个HDC,可以调用GetDC函数。
    HDC hdc=GetDC(hWnd);
    hWnd为你要获取HDC的窗口的句柄。无论何时用这个方法创建HDC,一定不要忘记在使用完毕之后释放它。可以使用ReleaseDC函数来释放它:
    ReleaseDC(hWnd,hdc);
    没有在WM_PAINT中调用ReleaseDC是因为EndPaint函数自动释放了DC。但如果创建了HDC而事后没有释放,就会出现资源泄漏问题,慢慢地程序也可能出现许多不曾听说的故障。甚至可能导致系统崩溃。记住这一点很重要。
    如果需要的话,也可以获取一个应用于整个窗口(包括系统菜单和标题栏域)的HDC,而不仅仅是客户区。为此可以用GetWindowDC(hWnd)来实现。
    HDC hdc=GetDC(NULL);
    也可以获得对整个屏幕的HDC,只要在调用GetDC的时候用NULL作为参数即可。
    HDC hdc=GetDC(NULL);
    提示:许多初学者会忘记的事情是Windows并不监视窗口的重画。因此,如果在WindowProc函数的WM_PAINT部分以外进行绘图,一定要确保你画出来的东西会在屏幕需要更新的时候(例如,用户拖动一个其他窗口经过了窗口的上空,或用户把窗口最小化,或最大化)被重画。否则,很快地屏幕看起来就会一团糟了。
    这里有一个绘制线条的程序(默然:只需要更换第1章的main.cpp就可以了,define.h文件不变):
    //-----------------------------------------------------------------------
    // 
    // Name: GDI_Lines1
    // 
    // Author: Mat Buckland 2002
    //
    // Desc: 基本线程绘制示范
     
    //------------------------------------------------------------------------
     
    #include 
    <windows.h>
     
    #include 
    "defines.h"
     
     
     
    //--------------------------------- 公共声明 ------------------------------
    //
    //------------------------------------------------------------------------
     
    char* g_szApplicationName = "The Groovy GDI - Lines";
    char*   g_szWindowClassName = "MyWindowClass";
     
    //---------------------------- WindowProc ---------------------------------
    // 
    // 这是处理所有Windows消息的回调函数
    //-------------------------------------------------------------------------
    LRESULT CALLBACK WindowProc (HWND   hwnd,
                                 UINT   msg,
                                 WPARAM wParam,
                                 LPARAM lParam)
    {
        
    //记录客户window区域的坐标
         static int cxClient, cyClient;
        
        
    switch (msg)
        
    {
        
            
    //当你的应用程序窗口第一次显示时,
            
    //一个WM_CREATE消息被送出
            case WM_CREATE:
             
    {
             
    //为了获得客户端窗口的尺寸,首先我们需要创建一个RECT结构
             
    //然后请求Windows用客户端窗口的尺寸填充这个结构
             
    //从而使用RECT对cxClient 与cyClient赋值
                    RECT rect;
     
                    GetClientRect(hwnd, 
    &rect);
     
                    cxClient 
    = rect.right;
                    cyClient 
    = rect.bottom;       
             }

        
        
    break;
     
            
    case WM_PAINT:
                
    {
                     PAINTSTRUCT ps;
              
              BeginPaint (hwnd, 
    &ps);
     
              
    //决定我们在每一边画多少条线。
              const int NumLinesPerSide = 10;
     
              
    //根据窗口的X与Y来计算窗口每一条线的间隔
              int yStep = cyClient/NumLinesPerSide;
              
    int xStep = cxClient/NumLinesPerSide;
              
              
    //现在,开画!
              for (int mult=1; mult<NumLinesPerSide; ++mult)
              
    {
                
    //移到线开始的位置
                MoveToEx(ps.hdc, xStep*mult, 00);
                
    //画线
                LineTo(ps.hdc, 0, cyClient-yStep*mult);
     
                
    //移到线开始的位置
                MoveToEx(ps.hdc, xStep*mult, cyClient, 0);
                 
    //画线
                LineTo(ps.hdc, cxClient, cyClient-yStep*mult);
     
               
    //移到线开始的位置
                MoveToEx(ps.hdc, xStep*mult, 00);
                 
    //画线
                LineTo(ps.hdc, cxClient, yStep*mult);
     
               
    //移到线开始的位置
                MoveToEx(ps.hdc, xStep*mult, cyClient, 0);
                 
    //画线
                LineTo(ps.hdc, 0, yStep*mult);
              }

     
              EndPaint (hwnd, 
    &ps);
                 }

     
           
    break;
     
        
    //用户是否改变了窗口的尺寸?
            case WM_SIZE:
             
    {
            
    //如果改变了窗口的尺寸,我们必须更新我们的变量以便重绘
            
    //我们更改cxClient和cyClient,以便缩放我们的线条
                 cxClient = LOWORD(lParam);
                 cyClient 
    = HIWORD(lParam); 
             }

     
          
    break;
              
             
    case WM_DESTROY:
                 
    {        
             
    //销毁应用程序,这需要一个WM_QUIT消息
                     PostQuitMessage (0);
     
                 }

     
           
    break;
     
        }
    //switch结尾
     
         
    //那些我们没处理的消息
             
    //使用Windows的默认处理函数去处理它们
             return DefWindowProc (hwnd, msg, wParam, lParam);
    }

     
    //-------------------------------- WinMain -------------------------------
    //
    // Windows程序的入口
    //------------------------------------------------------------------------
    int WINAPI WinMain (HINSTANCE hInstance,
                        HINSTANCE hPrevInstance,
                        LPSTR     szCmdLine, 
                        
    int       iCmdShow)
    {
         
    //我们窗口的句柄
             HWND                       hWnd;
        
             
    //我们的窗口类结构
             WNDCLASSEX     winclass;
             
         
    //第一次填充窗口类结构
           winclass.cbSize        = sizeof(WNDCLASSEX);
           winclass.style         
    = CS_HREDRAW | CS_VREDRAW;
         winclass.lpfnWndProc   
    = WindowProc;
         winclass.cbClsExtra    
    = 0;
         winclass.cbWndExtra    
    = 0;
         winclass.hInstance     
    = hInstance;
         winclass.hIcon         
    = LoadIcon(NULL, IDI_APPLICATION);
         winclass.hCursor       
    = LoadCursor(NULL, IDC_ARROW);
         winclass.hbrBackground 
    = (HBRUSH)GetStockObject (WHITE_BRUSH);
         winclass.lpszMenuName 
    = NULL;
         winclass.lpszClassName 
    = g_szWindowClassName;
           winclass.hIconSm       
    = LoadIcon(NULL, IDI_APPLICATION);
     
             
    //注册窗口类
            if (!RegisterClassEx(&winclass))
            
    {
                MessageBox(NULL, 
    "注册失败!""错误"0);
     
                
    //退出应用程序
                return 0;
            }

     
             
    //创建窗口并将ID赋值给hwnd    
         hWnd = CreateWindowEx (NULL,                 //扩长样式(extended style)
                               g_szWindowClassName, //窗口类名(window class name)
                                g_szApplicationName, //窗口标题(window caption)
                                WS_OVERLAPPEDWINDOW, //窗口样式(window style)
                                0,                    //初始x坐标(initial x position)
                                0,                    //初始y坐标(initial y position)
                                WINDOW_WIDTH,         //初始x尺寸(initial x size)
                                WINDOW_HEIGHT,        //初始y尺寸(initial y size)
                                NULL,                 //初窗口句柄(parent window handle)
                                NULL,                 //窗口菜单句柄(window menu handle)
                                hInstance,            //程序对象句柄(program instance handle)
                                NULL);                //创造参数(creation parameters)
     
         
    //确定窗口创建成功
         if(!hWnd)
         
    {
           MessageBox(NULL, 
    "创建窗口失败!""错误!"0);
         }

             
         
    //让窗口可见
             ShowWindow (hWnd, iCmdShow);
         UpdateWindow (hWnd);
     
         
    //这个将用来保存所有的窗口消息
             MSG msg;
         
             
    //我们的消息处理器的入口
             while (GetMessage (&msg, NULL, 00))
         
    {
              TranslateMessage (
    &msg);
              DispatchMessage (
    &msg);
         }

     
         UnregisterClass( g_szWindowClassName, winclass.hInstance );
     
         
    return msg.wParam;
    }
    为了创建这些美妙的线条,只需要对第一章中你看到的窗口过程做一些修改即可。下面是修改过后的WindowProc的片断:
    LRESULT CALLBACK WindowProc (HWND   hwnd,
                                 UINT   msg,
                                 WPARAM wParam,
                                 LPARAM lParam)
    {
        
    //记录客户窗口区域的尺寸
         static int cxClient, cyClient;
    这里定义了两个静态整型变量来保存窗口客户区的尺寸。客户区也就是窗口中读者将进行绘图的区域,但不包括标题栏,滚动条,边框。任何绘图所用到的运算都基于这两个值进行,这样,显示图形就能够在用户改变窗口尺寸的时候进行相应的缩放。
    switch (msg)
        
    {
        
            
    //当你的应用程序窗口第一次显示时,
            
    //一个WM_CREATE消息被送出
            case WM_CREATE:
             
    {
             
    //为了获得客户端窗口的尺寸,首先我们需要创建一个RECT结构
             
    //然后请求Windows用客户端窗口的尺寸填充这个结构
             
    //从而使用RECT对cxClient 与cyClient赋值
                    RECT rect;
     
                    GetClientRect(hwnd, 
    &rect);
     
                    cxClient 
    = rect.right;
                    cyClient 
    = rect.bottom;       
             }

        
        
    break;
    当第一次创建好窗口后,需要确定客户区的宽和高。为此,创建一个RECT结构,将其和一个窗口句柄一起传给GetClientRect函数。然后只需读取RECT结构中的信息,并用它们来设置cxClient和cyClient。这样就可从xcClient和cyClient来知道“画布”究竟有多大了。
    下面进行绘图。先请读者浏览一下WM_PAINT处理段,接着将逐个讲解相关的部分。
    case WM_PAINT:
                
    {
                     PAINTSTRUCT ps;
              
              BeginPaint (hwnd, 
    &ps);
     
              
    //决定我们在每一边画多少条线。
              const int NumLinesPerSide = 10;
     
              
    //根据窗口的X与Y来计算窗口每一条线的间隔
              int yStep = cyClient/NumLinesPerSide;
              
    int xStep = cxClient/NumLinesPerSide;
              
              
    //现在,开画!
              for (int mult=1; mult<NumLinesPerSide; ++mult)
              
    {
                
    //移到线开始的位置
                MoveToEx(ps.hdc, xStep*mult, 00);
                
    //画线
                LineTo(ps.hdc, 0, cyClient-yStep*mult);
     
                
    //移到线开始的位置
                MoveToEx(ps.hdc, xStep*mult, cyClient, 0);
                 
    //画线
                LineTo(ps.hdc, cxClient, cyClient-yStep*mult);
     
                
    //移到线开始的位置
                MoveToEx(ps.hdc, xStep*mult, 00);
                 
    //画线
                LineTo(ps.hdc, cxClient, yStep*mult);
     
               
    //移到线开始的位置
                MoveToEx(ps.hdc, xStep*mult, cyClient, 0);
                 
    //画线
                LineTo(ps.hdc, 0, yStep*mult);
              }

     
              EndPaint (hwnd, 
    &ps);
                 }

     
           
    break;
    可以看到,绘图代码还是相当易懂的。注意这里绘画所用到的点都是根据cxClient和cyClient计算出来的。这是适应窗口缩放所必须的。有关这一点,后面马上就会具体谈到。现在,解释一下MoveToEx函数和LineTo函数。
    如果有一条从点A到点B的线段,MoveToEx可以用来先将画笔所在位置移动到点A,然后再利用LineTo以当前画笔绘制一条线段到B点。MoveToEx的函数原型如下 :
     
    BOOL MoveToEx(HDC hdc,                  //DC句柄
                  int   X,                    //当前点位置的X坐标
                  int   Y,                    //当前点位置的Y坐标
                 LPPOINT    lpPoint);         //指向前一个点的位置的指针
    给这个函数传入一个设备描述表句柄和一对X,Y坐标,它就会将画笔移动到目标点,移动过程中不会画出线来。如果因为某种原因而不得不把画笔移回原始位置,可以从lpPoint取到开始绘图之前画笔的位置。这在某些情况下是有用的,但一般情况下很少用它,将它设为NULL即可。
    一旦把画笔定位于线段的起点后,就可用LineTo来绘制线段:
    BOOL LineTo(HDC hdc,                //DC句柄
               int nXEnd,               //线段终点的X坐标
               int nYEnd);              //线段终点的Y坐标
    下面解释WindowProc中的一段新内容。
    //用户是否改变了窗口的尺寸?
            case WM_SIZE:
             
    {
            
    //如果改变了窗口的尺寸,我们必须更新我们的变量以便重绘
            
    //我们更改cxClient和cyClient,以便缩放我们的线条
                 cxClient = LOWORD(lParam);
                 cyClient 
    = HIWORD(lParam); 
             }

     
          
    break;
    当用户改变窗口大小时,系统会发出一条WM_SIZE消息。窗口的新尺寸存放在32位整数lParam的高,低字里面传给WindowProc。HIWORD和LOWORD是Windows用以取得32位整数的高,低字的宏。接着用新的尺寸数据更新cxClient和cyClient,由于这两个值是所有绘图计算的基础,故新绘制的图形就能得到相应的缩放。
    技巧:有几种方法可以用来禁止用户改变窗口的大不,但最简单的方法就是在调用CreateWindowEx的时候不使用WS_THICKFRAME标志。如果在文档里查找WS_OVERLAPPEDWINDOW,会发现使用它能节省一些时间,它是一些标志的组合,其中就WS_THICKFRAME。因此,要达到和前面同样的结果,但除了WS_THICKFRAME,应使用如下这些标志:
    WS_OVERLAPPED|WS_VISIBLE|WS_CAPTION|WS_SYSMENU
    试一试这个方法,看这样做以后在试图改变窗口大小的时候会发生什么。
    2.1.2.1   创建自定义画笔
    前面的例子用默认的黑色来画线,但如果希望用其他颜色和不同的线宽绘图应该怎么做?为了达到这些目的,必须用CreatePen函数创建你自定义的画笔。下面看一下这个函数: