基于Nokia S60的游戏开发之四
发布时间:2006-10-14 3:13:39   收集提供:gaoqian

  应用程序在屏幕上的描画一般是使用CWsScreenDevice图形设备来完成,与CWindowGc图形上下文相关联。CONE提供了一个CWindowGc实例作为描画控件的标准图形上下文。它被CCoeEnv创建并且可以使用CCoeControls::SystemGc方法访问。CWindowGc的描画方法在客户端窗口服务器缓冲区上进行缓冲。

  描画要么是一个系统初始事务要么是一个应用程序初始事务。系统初始描画在窗口创建的时候被触发,或者当窗口内容因为窗口重叠而失效的时候被触发。对于后一种情况,窗口服务器为每个窗口保持一个无效的区域。如果一个窗口需要重画,窗口服务器发送一个重画事件到拥有无效窗口的应用程序中。CONE然后使用无效区域来建立需要被重画的控件,并且调用它们的Draw方法。这就是为什么每个控件都应该实现Draw方法来重画它们自己。CCoeControl中的Draw的默认为控件为空。下面的代码说明了Draw方法的示例:

void CExampleControl::Draw( const TRect& /*aRect*/ ) const
{
 // Get the system graphics context
 CWindowGc& gc = SystemGc();
 // Set drawing settings
 gc.SetBrushStyle( CGraphicsContext::ESolidBrush );
 gc.SetBrushColor( KRgbRed );
 // Draw
 gc.DrawLine( TPoint(10,10), TPoint(30,10) );

  Draw方法的TRect参数指明了需要重画的无效区域。然而大多数控件忽略矩形,由于它非常简单并且重新描画整个控件也不是非常慢。

  当一个应用程序的数据或者状态改变的时候,需要应用程序初始化描画,并且屏幕需要更新。CCoeControl提供非虚拟DrawNow方法,指明控件将要描画的窗口服务器,调用控件的Draw方法,最后指明完成描画的窗口服务器。CCoeControl还提供了DrawDeferred方法,使窗口无效并且在窗口服务器中触发一个新的重画事件。这两个方法之间的差异是DrawNow强制控件立即重画自己,而DrawDeferred导致一个重画事件将使用低优先级操作。由于CONE使用比重画事件更优先的级别处理用户输入事件,所以任何未定的用户输入事件都将首先处理。但由于需要重画整个控件,故都是很繁重的操作,通常只有改变的部分需要重画,这可以使用下面的代码做到:

void CExampleControl::DrawBitmap( const TPoint& aPoint,
const CFbsBitmap* aBitmap )
{
 // Get the system graphics context and control rectangle
 CWindowGc& gc = SystemGc();
 // Establish drawing rectangle
 TRect rect = TRect( aPoint,
 TSize( aBitmap.iWidth, aBitmap.iHeight ) );
 // Activate graphics context
 ActivateGc();
 // Invalidate window
 Window().Invalidate( rect );
 Window().BeginRedraw( rect );
 // Draw a bitmap
 gc.DrawBitmap( aPoint, aBitmap );
 Window().EndRedraw();
 // Deactivate graphics context
 DeactivateGc();

  上面的示例代码在aPoint参数定义的位置画一个CFbsBitmap。示例中值得注意的是图形上下文在使用之前需要激活,在描画完成之后失活。还有窗口服务器需要取得客户端即将启动重画的信息,这使用BeginRedraw方法来完成。由于窗口服务器只允许一个应用程序在无效区域中描画,所以需要Invalidate方法。在一个系统初始重画中,CONE激活图形上下文并且调用用于应用程序的BeginRedraw方法。如果窗口已经无效了,那么Invalidate方法就不必被调用了--这就是为什么系统初始需要被首先描画。
  子图形(精灵)

  子图形是一个经过蒙板化(Mask)的位图,可以在应用程序不重画底层窗口的情况下移动。如果游戏不需要经常更新背景,那么使用子图形就再好不过了。例如类似于PacMan这样的游戏,在这种游戏中动画在一个不能卷轴并且固定的背景上移动。重画是靠窗口服务器来执行的,替代一个较高优先性的任务。这种游戏要考虑的是平滑的动画和子图形的运动。Symbian OS提供两种不同的子图形:指针和动画位图。图1说明子图形类的层次。

图1说明子图形类的层次

  RWsSpriteBase是一个用于子图形的抽象基本类。它拥有一个或多个包含子图形的位图数据的TSpriteMembers。通过指定带有不同的位图的多个成员,子图形就可以活动起来了。TSpriteMember还定义了位图的蒙板,子图形中位图的位置和位图显示的时间间隔。RWsSprite是一个用于子图形的具体的类。除了构造器之外,它只提供一个方法SetPosition,可用于移动子图形。下面的代码说明了使用从MBM文件中装载的位图创建子图形的示例。

RWsSprite sprite = RWsSprite( iEikonEnv->WsSession() );
User::LeaveIfError( sprite.Construct( Window(), TPoint(0,0), 0 );
for ( TInt i=0; i < 8; i += 2 )
{
 iMember[i/2].iBitmap = new ( ELeave ) CFbsBitmap();
 User::LeaveIfError( member.iBitmap->Load( KBitmapFile, i, EFalse ) );
 iMember[i/2].iBitmap = new ( ELeave ) CFbsBitmap();
 User::LeaveIfError( member.iMaskBitmap->Load( KBitmapFile, i+1, EFalse ) );
 iMember[i/2].iInvertMask = EFalse;
 iMember[i/2].iOffset = TPoint(0,0);
 iMember[i/2].iInterval = TTimeIntervalMicrosecond32(100000);
 User::LeaveIfError( sprite.AppendMember( iMember[i/2] ) );

  在子图形成员已经更新并且附加到RWsSprite类之后,子图形可以通过调用RWsSpriteBase::Activate来激活。在此之后,这个子图形显示在屏幕上,并且准备移动。子图形的内容可以使用RWsSpriteBase:UpdateMember方法来变化。因为CFbsBitmaps还可访问窗口服务器,所以只有子图形的位图句柄被发送到窗口服务器。这使子图形的位图的更新相当迅速。当子图形不再需要的时候,窗口服务器需要调用RWsSpriteBase::Close来释放资源。但不释放需要被删除的客户端成员数据。RWsPointerCursor是一个用于应用程序创建光标的类。

  双缓冲

  如果一个游戏的图形由多个需要被经常更新的运动对象组成,窗口服务器的客户端缓冲可能被充满并且可能会在所有对象都更新的时候溢出。用户可能会发现屏幕出现闪烁。如果一个视图仍然在更新的时候,可能会出现闪烁或者其他不希望的效果。这些问题的解决方案是双缓冲,图形先被画在一个屏外位图上,然后被画到屏幕上作为一个单一窗口服务器操作。尤其是对于那种在一秒钟内重画几次屏幕的游戏,使用屏外位图可以改善它们的性能。

  一个屏外位图可以使用位图化的图形上下文和图形设备类来创建:CFbsBitGc和CFbsBitmapDevice。它们使用其他的上下文和设备类来创建和使用。为了获得额外的性能,位图自己就应该是一个CWsBitmap位图。在屏外位图更新之后,它可以使用正常的窗口服务器的描画方法画在窗口中。

  当一个应用程序在一个窗口画位图时,它转化为和窗口相同的显示模式。这是一个很消耗时间的操作,实质上可能降低描画的速度。因此把位图用于动画的游戏应该在动画开始之前就完成转化。转化可以通过使用一个屏外位图来执行,如下面的示例方法演示:

CFbsBitmap* CExampleControl::LoadAndConvertBitmapL(
Const TDesC& aFileName, TInt aBitmapId )
{
 // Load the bitmap
 CFbsBitmap* originalBitmap = new ( ELeave ) CFbsBitmap();
 CleanupStack::PushL( originalBitmap );
 User::LeaveIfError( originalBitmap->Load( aFileName, aBitmapId, EFalse ) );
 // Create a new bitmap, graphics device and context
 CFbsBitmap* newBitmap = new ( ELeave ) CFbsBitmap();
 CleanupStack::PushL( newBitmap );
 newBitmap->Create( originalBitmap->SizeInPixels(), Window()->DisplayMode() );
 CFbsBitmapDevice* graphicsDevice = CFbsBitmapDevice::NewL(bitmapConverted );
 CleanupStack::PushL( graphicsDevice );
 CFbsBitGc* graphicsContext;
 User::LeaveIfError( graphicsDevice->CreateContext( graphicsContext ) );
 TPoint zero(0,0);

 // Blit the loaded bitmap to the new bitmap
 bitmapContext->BitBlt( zero, originalBitmap );
 CleanupStack::Pop(3);
 delete bitmapContext;
 delete bitmapDevice;
 delete originalBitmap;
 return newBitmap;

  示例方法使用一个文件名和位图ID作为参数,并且从一个MBM文件中装载相应的位图。如果一个游戏有许多位图应该转化,那么应该在游戏或者等级的初始化阶段转化。因此用户就不会看到这个操作了。
  直接描画

  使用窗口服务器在屏幕上描画需要一个上下文转换,这会减慢描画速度。为了绕过窗口服务器省去繁琐的上下文转换,可以直接访问屏幕。这被称作直接描画。在Symbian OS中有两种方法来直接在屏幕上描画。

  CFbsScreenDevice是一个可以被发送到屏幕驱动程序SCDV.DLL的图形设备。在创建一个CFbsBitGc图形上下文之后,它能像任何其他的图形设备一样使用。然而,可以直接在屏幕上描画,而不需要使用窗口服务器。直接在屏幕上描画的另一种方法是从系统中查询屏幕内存地址。这可以使用UserSrv类来实现:

TPckgBuf<TScreenInfoV01> infoPckg;
TScreenInfoV01& screenInfo = infoPckg();
UserSvr::ScreenInfo(infoPckg);
TUint16* screenMemory = screenInfo.iScreenAddress + 16;

  屏幕内存有一个32字节的头。

  即使在屏幕内存内写数据比CFbsScreenDevice稍微快一点,但是功能可能根据硬件和屏幕的设备驱动程序的不同而有差异。在一些基于Symbian OS的终端中,屏幕在内存变化的时候自动从屏幕内存中更新,而在其他的终端中描画需要明确的激活。屏幕内存地址只对目标硬件有效,因此描画代码需要分为硬件和模拟器两部分。在模拟器环境中,可以描画到一个屏外位图中,而不是屏幕内存中,然后使用正常的窗口服务器描画方法位块传送到屏幕上。环境可以通过使用__WINS__定义来检测出来。

#ifdef __WINS__ // Emulator environment
// Draw to an off-screen bitmap
#else // Hardware environment
// Draw directly to the screen memory
#endif 

  这两种直接描画方法的一个共同的问题是窗口服务器不了解描画,因此它不能通知应用程序是否出现另一个窗口或者窗口组。 即使当应用程序失去焦点的时候得到一个事件,它们也不能停止直接描画,因为直接描画实在太快了,并且屏幕内容有可能被弄乱。 这可能发生在玩游戏的时候,突然有电话打进来的情况下。
新近的GT 6.1版本提供了一个应用编程接口用于直接描画,将能解决前面提到的问题。 这个应用编程接口由两个类组成:一个MDirectScreenAccess类,提供用于应用程序的回调方法,还有一个CDirectScreenAccess类处理与窗口服务器的通讯。 下面的代码说明CDirectScreenAccess实例是如何构造的,以及直接描画支持是如何激活的。

iDrawer = CDirectScreenAccess::NewL(iEikonEnv->WsSession(), *iEikonEnv->ScreenDevice(), Window(), *this);
iEikonEnv->WsSession().Flush();
iDrawer->StartL();
iDrawer->ScreenDevice()->SetAutoUpdate(ETrue); 

  CDirectScreenAccess的NewL方法获得一个窗口服务器会话、CONE的图形设备、应用程序窗口和一个到MDirectedScreenAccess导出类的指针作为参数。 在CDirectScreenAccess::StartL被调用来激活直接描画支持之前,客户端窗口服务器缓冲应该溢出。 为了能自动更新屏幕,屏幕设备的SetAutoUpdate方法需要使用ETrue参数。 当直接描画支持激活的时候,CDirectScreenAccess产生一个CFbsBitGc图形上下文,可以被应用程序用来在屏幕上绘画。

iDrawer->Gc()->BitBlt( TPoint(0,0), iBitmap ); 

  当另一个窗口出现在应用程序窗口上时,CDirectScreenAccess从窗口服务器取得一个事件来中断描画。 CDirectScreenAccess然后调用MDirectScreenAccess派生类的AbortNow方法,这个方法必须被应用程序重载以便中断描画。 为了防止屏幕被弄乱,窗口服务器直到中断描画事件被处理的时候才画重叠窗口

 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50