荔园在线

荔园之美,在春之萌芽,在夏之绽放,在秋之收获,在冬之沉淀

[回到开始] [上一篇][下一篇]


发信人: Minatl (Minatl), 信区: Program
标  题: DirectDraw 游戏编程基础(2)
发信站: BBS 荔园晨风站 (Wed Jan 20 19:25:36 1999), 转信

DirectDraw 游戏编程基础(2)

游戏使计算机的发展超越了晶体管时代

      微软公司供稿

例程1(DDEX1):DirectDraw 的基本知识
在使用 DirextDraw时,需要首先创建一个对象DirectDraw 的实体,该对象实体代表了微机
显示适配器。然后,使用接口所提供的方法来操作该对象实体,使之完成有关命令和任务。
接着,你还需要创建一个或多个
DirectDraw-surface对象的实体,以便能在图形表面(Surface)上展示你的游戏画面。
下面,在例程 DDEX1 中展示如何使用Directx 3 SDK来 DirectDraw对象实体,如何创建一
个带有后台缓冲区的基本表面(Surface),以及如何弹出表面(Surface)。

注意:所有的例程都是用C++写成的,如果你的编辑器是C,你需要在文件中作出某些改动(至
少,你要加入 Vtable 和指向各种接口方法的 this 指针)。
DirectDraw 初始化:
     DirectDraw 初始化代码写在例程 DDEX1 的 doInit 函数中。

        /*
         * Create the main DirectDraw object.
         */
     ddrval = DirectDrawCreate(NULL,&lpDD,NULL);
     if(ddrval==DD_OK)
       {
         //Get exclusive mode.
         Ddrval=lpdd->SetCooperativeLevel(hwnd,
                              DDSCL_EXCLUSIVE|DDSCL_FULLSCREEN);
         if(ddrval==DD_OK)
           {
             //Create the primary surface with 1 back buffer.
             Ddsd.dwSize = sizeof(ddsd);
             ddsd.dwFlags = DDSD_CAPS \ DSD_BACKBUFFERCOUNT;
             ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |
                                       DDSCAPS_FLIP |
                                       DDSCAPS_COMPLEX;
             ddsd.dwBackBufferCount = 1;
             ddrval = lpDD->CreateSurfae(&ddsd, &lpDDSPrimary,
           NULL);
             if(ddrval == DD_OK)
              {
               //Cet a pointer to the back buffer.
               Ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
               ddrval = lpDDSPrimary->GetAttachedSurface(&ddscaps,
                                                        &lpDDSBack);
               if( ddrval == DD_OK)
               { // Draw some text.
               If(lpDDSPrimary->GetDC(&hdc) == DD_OK)
               {
                 SetBkColor(hdc, RGB(0,0,255));
                 SetTextColor( hdc,RGB(255,255,0 ) );
                 TextOut( hdc, 0, 0, sxFrontMsg, lstrlen(szFrontMsg ));
                 lpDDSPrimary->ReleaseDC(hdc);
                 }
                 if(lpDDSBack->GetDC(&hdc) == DD_OK)
                 {
                   SetBkColor( hdc, RGB(0, 0, 255 ) );
                   SetTextColor( hdc, RGB( 255,255, 0 ) ):
                   TexOut( hdc, 0, 0, szBackMsg, lstrlen( szBackMsg ) );
                   lpDDSBack->ReleaseDC(hdc);
                   }
                 // Create a timer to flop the pages.
                 If(SetTimer( hwnd, TIMER_ID, TIMER_RATE, NULL))
                   {
                    return TRUE;
                    }
                 }
              }
            }
          }
        }
        wsprintf(buf,"Direct Draw Init Failed (%08lx)\n",ddrval);
          .
          .
          .

以下针对初始化 DirectDraw 对象和准备表面(Surface)集的各个步骤分别进行讨论:

创建一个 DirectDraw 对象
    为了创建一个 DirecDraw 对象实体,你应该在程序中使用DirectDrawCreate API 函数
(注意:这里我所说的是应该,而不是必须), 这是因为使用 OLE 中的 CoCreatelnstance
函数也能创建一个 DirectDraw 对象实体,但这不在我们的讨论范围之中)。
DirectDrawCreate
采用全球统一的标准,它代表显示设备,这些显示设备在大多数情况下被定为 NULL (即:系
统使用缺省的显示设备)。 当DirectDraw对象实体创建好后,就会有一个指针指向该对象实
体。而且,在调色板中有三分之一的指针指向 NULL (这样做的目的是为了今后的扩展)。
接下来的例子说明如何创建一个DirectDraw 对象,并判别该对象是否创建成功:
    ddrval = directDrawCreat( NULL, &lpDD, NULL );
    if( ddrval == DD_OK )
    {
      //lpDD is a valid DirectDraw object.
      }
      else
      {
        //DirectDraw object could not be created.
        }

使用 IDirectDraw2 和 IDirectDrawSurface2 接口

在你读本文的其他部分时,你会注意到所有的例程都使用的是 IDirectDraw和
IDirectDrawSurface 的老版本接口。这是因为 DirectX 3 SDK 所给出的例程还没有来及使
用 IDirectDraw 和 IDirectDrawSurface 更新后的接口。你可以通过调用
IDirectDraw::QueryInterface
方法来得到 IDirectDraw2 和IDirectDrawSurface2 接口。
    下面的代码给出如何得到 IDirectDraw2 接口:

       // Create an IDirectDraw2 interface.
       LPDIRECTDRAW lpDD;

       ddrval = DirectDrawCreate( NULL, &lpDD, NULL);
       if(ddrval != DD_OK)
           return;
       ddrval = lpDD->SetCooperativeLevel(hwnd, DDSCL_NORMAL);
       if(ddrval != DD_OK)
           return;
       ddrval = lpDD->QueryInterfave(IID_IDirectDraw2, (lPVOID *)&lpDD2);
       if(ddrval !=DD_OK)
           return;

下面的代码给出如何得到 IDirectDrawSurface2 接口:
       LPDIRECTDRAWSURFACE lpSurf;
       LPDIRECTDRAWSURFACE lpSurf2;

       // Create surfaces.
       Memset( &ddsd, 0, sizeof(ddsd ));
       ddsd.dwSize = sizeof(ddsd);
       ddsd.dwFlags = DDSD_CAPS |DDSD_WIDTH |DDSD_HEIGHT;
       ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN |
                          DDSCAPS_SYSTEMMEMORY;
       ddsd.dwWidth = 10;
       ddsd.dwHeight = 10;

       ddrval = lpDD2->CreateSurface( &ddsd, &lpSurf, NULL);
       if(ddrval != DD_OK)
              return;

       ddrval = lpSurf->QueryInterface(
              IID_IDirectDrawSurface2, ( LPVOID *(&lpSurf2);

       if(ddrval !=DD_OK)
              return;
设置显示模式
安装DirectDraw 的下一步是设置显示模式。在DirectDraw应用程序中设置显示模式关键有
两个步骤:第一,调用Idirectdraw::SetCooperativelevel方法设置底层参数。设置好底层
参数后,再调用 IDirectDraw::SetdisplayMode方法来设置显示方式。

确定应用程序的运行特征:
如果你想改变你的显示方式 ,  你必须先在调色板的 dwFlags 中设置    DDSCL_
EXCLUSIVE  和 DDSCL_FULLSCREEN 标志。其中,调色板的dwFlags包含在
IDirectDraw::SetCooperativelLevel
方法中。这样一来,你的应用程序就可以单独占用显示设备,而使其它进程不能共享显示设
备。 另外 , 标志DDSCL_FULLSCREEN
把显示设备置为全屏幕方式。正如在运行DDWX1时见到的那样,当同时按下ALT键和TAB键后
,最初的表面(Surface)(尽管仍然有效)会被你的表面(Surface)所覆盖,且只有你的表面
(Surface)能够进行写屏操作。
下面的例子说明如何使用 IDirectDraw::SetCooperativeLevel:
         HRESULT    ddrval;
         LPDIRECTDRAW  lpDD;  // already created by DirectDrawCreate

         ddrval = lpDD->SetCooperativeLevel(hwnd, DDSCL_EXCLUSIVE |
                         DDSCL_FULLSCREEN);
         if(ddrval == DD_OK)
         {
           // exclusive mode was successful
         }
         else
         {
           // not successful
           // however, the application can still run
           }

如果 IDirectDraw::SetCooperativeLevel 不返回
DD_OK,你的应用程序仍能继续执行,但是我不主张这样做。因为这样会使你的程序无法工
作在全屏幕模式下,而且可能不按照你预先的要求工作。如果你确实想使你的应用程序继续
运行下去,你应该显示一个错误信息,以便使终端用户知道 IDirectDraw
::SetCooperativeLevel
没有返回DD_OK,然后由用户自己去决定是否继续执行该程序。
使用IDirectDraw::SetCooperativeLevel 时,有一个要求:给每一个窗口赋一个句柄。这
样当你的应用程序被异常中止时,Windows 能够知道。例如:
    当发生GP错误时,GDI被推入缓冲区,终端用户就再也不能返回到Windows界面。
为防止这种情况的发生,DirectDraw
能够在关键时刻执行一个后台过程向前台弹出一定的信息,利用这些信息可以确定应用程序
是何时被中止的。这就给应用程序强加了一个限制。首先,你必须给每个窗口赋一个特殊的
句柄。通过这些句柄,可以知道应用程序的运行信息。也就是说,要创建一个窗口,你必须
设置一个处于活
动状态的句柄。否则,无法很好执行许多功能。诸如:
    当你的程序被终止时,可能会导致GDI的混乱,也可能会使组合键ALT+TAB不
能正常工作。

改变显示模式
确定好应用程序的运行特征后,你就可以使用IDiredctDraw::SetDisplay方法来改变显示模
式了。下面的例子展示出如何把显示模式设置为 640’480’8 bpp:
         HRESULT ddrval;
         LPDIRECTDRAW  lpDD; // already created

         ddrval = lpDD->SetDisplayMode(640, 480, 8);
         if( ddrval == DD_OK)
         {
           // mode changed
           }
          else
          {
            // mode cannot be changed
            //mode is either not supported
           //or someone else has exclusive mode
          }

在设置显示模式时,你应该判别一下终端用户的硬件设备是否支持高级显示模式。如果不支
持,则你应把显示模式设为标准模式,以便能支持更多的适配器。例如,如果你想设计能在
所有系统下运行的应用程序,则应把显示模式设为:640 480
8。如果适配器不支持你要设置的显示模式,则IDirectDraw::SetDisplayMode  会返回一个
DDERR_INVALIDMODE错误值。因此,在设置显示模式之前,你应使用
IDirectDraw::EnumDisplayMode方法来判别一下终端用户适配器所支持的显示类型。
    创建一个可弹出式表面(Surface)集
当你已经设置好上面的显示模式后,你就可以创建一个表面(Surface)系统,且可以在它上
面开发各种应用。尽管我们在DDEX1例程中把显示模式设为全屏幕模式,你仍能够创建一系
列的表面(Surface),并在这些表面(Surface)之间进行自由切换。
如果,我们调用方法DirectDraw::SetCooperativeLevel把显示模式设置为 DDSCL_NORMAL的
话, 则只能创建一个位拷贝表面(Surface)集。
设置表面(Surface)的各项参数
创建可切换式表面(Surface)集的第一步是:在DDSURFACEDESC结构中设定表面(Surface)的
各     项参数。下面的例子显示了要创建一个可切换式表面(Surface)集需用到的各项定义
和标志:
         // Create the primary surface with 1 back buffer.
         Ddsd.dwSize = sizeof( ddsd );
         ddsd.dwFlags = DDSD_CAPS \ DDSD_BACKBUFFERCOUNT;
         ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE |
                 DDSCAPS_FLIP | DDSCAPS_COMPLEX;
         ddsd,dwBackBufferCount = 1;
在上面的例子中,DDSURFACEDESC结构的大小被赋给dwSize成员。这样做的      目的是:
防止你在调用DirectDraw的方法时返回一个无效值(且更关键的是:这样做便于今后
DDSURFACEDESC结构的扩展)。
成员dwFlags用于标明DDSURFACEDESC结构中哪些区域填入的信息有效,哪      些区域的信
息无效。正如例程DDEX1,我们用dwFlags表明了你要使用结构DDSCAPS      (DDSC_CAPS),
以及你要创建一个后台缓冲区(DDSD_BACKBUFFERCOUNT)。
例程中成员dwCaps包含了一些用在DDSCAPS结构中的标志。这样一来,成员
dwCaps就定义了一个主表面(Surface)(DDSCAPS_PRIMARYSURFACE),一个弹出式表面
(Surface)(DDS-CPAS_PRIMARYSURFACE),和一个复表面(Surface)(DDSCAPS_COMPLEX)。所谓
复表面(Surface)是指,该表面(Surface)是由若干子表面(Surface)组成的。最后,上面的
例程定义了一个后台缓冲
区。这个后台缓冲区是背景和前景真正被写入的内存区。写好背景和前景后,再把它们从后
台缓冲区弹到主表面(Surface)上。在例程DDEX1中,后台缓冲区的个数被设为1。
你可以在显示存储器空间允许的前提下,设置任意多的后台缓冲区。有关创建多个后台缓冲
区的详细内容参见后面的“三重缓冲技术”(Triple
Buffering)一节。表面(Surface)所占用的存储单元可以是显示存储器,也可以是系统内存
。如果应用程序所需的存储空间超出了系统内存,则DirectDraw会自动使用显示存储器(例
如,你的适配器只有 1 兆
RAM,而你同时定义了多个后台缓冲区)。当然,你也可以指定只使用系统内存或只使用显示
存储器。如果把DDSCAPS结构中的dwCaps设为DDSCAPS_SYSTEMMEMORY,DirectDraw就只使用
系统内存。如果把结构DDSCAPS中的dwCaps设为DDSCAPS_VIDEOMEMORY,则DirectDraw就只使
用显示存储器。(
如果,你设定只使用显存,但显存的大小又不够用来创建一个表面(Surface),这时,
IDirectDraw::CreateDurface就会返回一个DDERR_OUTOFVIDEOMEMORY的错误信息。)

创建表面(Surface)集
定义完DDSURFACEDESC结构中的各项参数后,你就可以使用这个结构和指针       IpDD  来
调用IDirectDraw::CreatSurface方法了。其中,指针IpDD指向由函数
DirectDrawCreate所生成的对象DirectDraw。上述的具体过程见下例:
           ddrval = lpDD->CreateSurface( &ddsd, &lpDDSPrimary,NULL);
           if( ddrval == DD_OK )
           {
             // lppDDSPrimary points to new surface
             }
             else
             {
              // surface was not created
              return FALSE;
              }
调用IDirectDraw::CreateSurface成功后,调色板IpDDSPrimary将指向主表面(Surface)。
  完成上述过程后,你就可以通过调用IDirectDrawSurface::GetAttached-Surface方法得
到一个指向后台缓冲区的指针。如下例所示:
           ddscaps.dwCaps = DDSCAPS_BACKBUFFER;
           ddrval = lpDDSPrimary->GetAttachedSurface( &ddcaps, &lpDDSBack );
           if( ddrval == DD_OK)
           {
             // lpDDSBack points to the back buffer
             }
             else
             {
               return FALSE;
               }
通过提供主表面(Surface)的地址和设定DDSCAPS_BACKBUFFER标志,调用方法:
IdirectDrawSurface::GetAttackedSrface成功后,调色板lpDDSBack就指向后台缓冲区。
对表面(Surface)进行写操作
    创建好主表面(Surface)和后台缓冲区后,例程DDEX1调用Windows GDI标准函数,
   对主表面(Surface)和后台缓冲区进行写文本操作。如下例所示:
if ( lpDDSPrimary->GetDC( &hdc) == DD_OK)
             {
              SetBkColor( hdc, RGB(0, 0, 255 ) );
              SetTextColor( hdc, RGB(255, 255, 0) );
              TextOut(hdc, 0, 0, szFrontMsg, lstrlen(szFrontMsg) );
              lpDDSPrimary->ReleaseDC(hdc);
              }
              if (lpDDSBack->GetDc(&hdc) == DD_OK)
              {
               SetBkColor(hdc,RGB(0,0,255));
               SetTextColor(hdc, RGB(255,255,0));
               TextOut(hdc,0,0,szBackMsg,lstrlen(szBackMsg));
               lpDDSBack->ReleaseDC(hdc);
               }
上例中调用了方法IDirectDrawSrface::GetDC来用一个句柄给表面(Surface)加
锁。使用Windows标准函数需要一个指向deviceContext的句柄。如果你不习惯这样做,你可
以不调用Windows标准函数,而调用IDirectDrawSurface::Lock方法和
IDirectDrawSurface::UnLock方法来给后台缓冲区加锁和解锁。
给表面(Surface)加锁(可以是整个表面(Surface),也可以是表面(Surface)的一部分),
能确保应用程序和位拷贝进程不能覆盖表面(Surface)所占存储空间。这样可以避免应用程
序对表面(Surface)进行写操作时发生错误。另外,只有当表面(Surface)存储单元处于开锁
状态时,你的应用
程序才能把表面(Surface)一页一页地由后台弹至前台。
在表面(Surface)加锁状态下,上例调用Windows GDI标准函数SetBkColor来设定背景颜色,
用SetTextColor来设定前景文本的颜色,还调用了TextOut函数把背景和前景显示到表面
(Surface)上。
当把文本写到缓冲区后,例程调用了IDirectDrawSurface::ReleaseDC方法解锁表面
(Surface),并释放句柄。无论何时,只要你停止对缓冲区进行写操作,你就必须调用
IDirectDrawSurface::ReleaseDC
或者IDirectDrawSurface::Unlock来解锁表面(Surface),释放句柄。至于具体调用上述两
种方法的哪一个,要视具体情况而定。重申一遍,只有表面(Surface)处于开锁状态,应用
程序才能把它们由后台弹至前台。
你可能还有点疑惑,为什么这里只对主表面(Surface)进行写操作?通常,在你写一个表面
(Surface)时,只有那些写到主表面(Surface)后台缓冲区的内容才能显示出来。在本例
DDEX1中,程序完成第一次弹出表面(Surface)操作时,会有一个明显的延时,所以DDEX1的
初始化函数中只对主表
面(Surface)缓冲区进行写操作是为了防止程序刚开始时显示不连贯。正如后面所看到的,
例程DDEX1在WM_TIMER期间只对后台缓冲区进行写操作。初始化函数和标题页只能放在主表
面(Surface)缓冲区中。
注意:调用IDirectDrawSurface::Unlock对表面(Surface)解锁后,指向表面(Surface)存储
单元的指针就失效。要想重新获得该指针,就必须调用IDirectDrawSurface::Lock方法。
对表面(Surface)集进行写和弹出操作
初始化结束后,DDEX1应用程序进入消息环。就是在这个循环中,后台缓冲区被锁定,新的内
容被写入,当后台缓冲区未被锁定时,表面(Surface)就被弹出。WM TIMER包括了用以写入
和弹出表面(Surface)大部分代码。
写入表面(Surface)
WM TIMER信息的前半部分是用来写入到后台缓冲区的。在这里使用到的大部分     技术,
在"对表面(Surface)进行布置操作"部分中,就已经被讨论过了。但是我将再次简要地讨论
一下。以下就是在DDEX1中WM TIMERde的内容:
    case WM TIMER:
         // Flip surface.
         If(bActive)
         {
            if (LpDDSBack->GetDC(&hdc)_== DD_OK)
            {
                SetBKColor( hdc, RGB(0, 0, 255 ) );
                SetTextColor( hdc, RGB( 255,255, 0 ) );
                if( phase )
                {
                  TextOut( hdc, 0, 0, szFrontMsg, Lstrlen(szFrontMsg));
                  phase = 0;
                }
                else
                {
                  TextOut(hdc, 0, 0, szBackMsg, Lstrlen(szBackMsg) );
                  phase = 1;
                }
                LpDDSBack_>ReleaseDC(hdc);
             }

在准备写入之前,GetDC行将锁定后台缓冲区。SetBKColor和SetTextColor函数设     置背
景和文本的颜色。
    下一步,变量"phase"决定应该写入主缓冲区信息还是写入后台缓冲区信息。如果
"phase"等于1, 主表面(Surface)信息便被写入, 并且将"phase"置为0。 如果"phase" 等
于0,后台缓冲区信息便被写入, 并且将"phase"置为1。 但是, 你应当注意,在这两种情
况下,
这些信息都要被写入到后台缓冲区中。一旦信息被写入到后台缓冲区中,那末通过使用
IDrectDrawSurface::ReleaseDC方法, 后台缓冲区就被解锁。
弹出表面(Surface)
一旦表面(Surface)内存被打开, 你就可以使用IDirectDrawSurface::Flip方法将后台缓冲
区弹出到主表面(Surface)中了。 下面的例程表示了在DDEX1中如何做到这一点:
   while( 1 )
     {
        HRESULT ddrval;
        ddral = LpDDSPriprimary->Flip( NULL, 0 );
        if( ddral == DD_OK )
        {
            break;
        }
        if( ddral == DDERR_SURFACELOST )
        {
            ddral = LpDDSPrimary->Restore();
            if( ddral != DD_OK )
            {
                break;
            }
         }
         if( ddral != DDERR_WASSTILLDRAWING )
         {
             break;
         }
     }
在这个例程中,IPDDSPrimary指针指明了主表面(Surface)和与之相关联的后台缓冲区。当
IDirectDrawSurface:Flip被调用时, 前表面(Surface)和后表面(Surface)被交换(注意:
只是表面(Surface)的指针被交换, 并无数据移动)。 如果弹出是成功的,
并且返回到DD_ok,那末,应用程序就从当前循环中断。
在弹出的同时,返回一个DDERR_SURFACELOST值,则调用IDirectdrawSurface::Restore即可
恢复该表面(Surface)。 如果恢复成功,应用程序就循环返回到
IDirectDrawSurface::Flip的调用,并且再运行一次。 如果表面(Surface)恢复不成功,
那末,应用程序便从当前循环中断,并且返回一个错误信息。一件很重要的事情就是:即使
在你已经调用IDirectDrawSurface::Flip之后,交换也不会立即完成。
此时,系统中的原表面(Surface)将缩小为一个条形图标,这样一来,就为下一次点击该图
标弹出原表面(Surface)作好了准备。例如,如果以前的弹出操作还没有发生,那末方法
IDirectDrawSurface::Flip就会返回参数DDERR_WASSTILLDRAWING的值。在这个例程中,方
法IDirectDrawSurfac
e::Flip调用将会继续循环,直到调用返回DD_OK值为止。
Deallocating the DirectDraw Objects
     当你按下F12时,在退出应用程序之前,DDEX1应用程序处理WM DESTROY信息。该信息
调用finiObjects函数,该函数包括了所有的Iunknown Release调用,如下所示:
     static void finiObjects( void )
     {
        if( LpDD != NULL )
        {
            if( LpDDSPrimary !+Null )
            {
                LpDDSPrimary->Release();
                LpDDSprimary = NULL;
            }
            LpDD->Release();
            LpDD = NULL;
         }
       }/* finiObjects */
该程序是相当直观的。该应用程序检查DirectDraw和DirectDrawSurface对象的指针是否为
空,当然,这些指针非空。然后DDEX1调用IDirectDrawSurface::Release方法,将
IdirectDrawSurface对象的参考值减1。如果当参考值等于0时,IDirectDrawSurface对象所
占的内存就将被释放。然?
笸ü柚肐DirectDrawSurface的值为空,DirectDrawSurface的指针就被释放了。应用程序
然后调用IDirectDraw::relese,并将DirectDraw对象的关联值减少到0,释放 DirectDraw
对象的操作是通过设置DirectDraw对象的值为空完成的,此时DirectDraw对象的指针也就被
释放了。

--
※ 来源:.BBS 荔园晨风站 bbs.szu.edu.cn.[FROM: 192.168.0.240]


[回到开始] [上一篇][下一篇]

荔园在线首页 友情链接:深圳大学 深大招生 荔园晨风BBS S-Term软件 网络书店