基于Nokia S60的遊戲開發之四

應用程序在屏幕上的描畫一般是使用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說明子圖形類的層次。

基于Nokia S60的遊戲開發之四

圖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方法,這個方法必須被應用程序重載以便中斷描畫。 爲了防止屏幕被弄亂,窗口服務器直到中斷描畫事件被處理的時候才畫重疊窗口

基于Nokia S60的遊戲開發之一
本系列文章是基于Nokia Series 60和Symbian OS技術,指導開發者在現在的Series 60移動電話終端上開發高級的移動遊戲。   Symbian操作系統簡介   Symbian操作系統是所有Symbian OS電話共享的應用編程接口( API)技...查看完整版>>基于Nokia S60的遊戲開發之一
 
基于Nokia S60的遊戲開發之二
把基于Series 60的智能電話作爲一種遊戲設備   本章將深入研究Series 60和Symbian OS,描述它們作爲一個遊戲平台的特性。此外,智能電話的需求和限制也將被討論。 需求   與許多其它用于遊戲的設備不同,智能電話...查看完整版>>基于Nokia S60的遊戲開發之二
 
基于Nokia S40的猜數字遊戲之二
現在我們已經有一能夠接收用戶輸入事件的Button類了,下面我們應該考慮如何實現遊戲中相關的邏輯,猜數字中的遊戲邏輯都比較簡單,主要是産生一個4位隨機數字且不能重複,其次是根據輸入返回給用戶結果。我們提供一個...查看完整版>>基于Nokia S40的猜數字遊戲之二
 
基于Nokia S40的猜數字遊戲之一
基于Nokia S40的猜數字遊戲之一
筆者剛剛開始學習寫遊戲,並沒有什麽經驗,因此選擇了門檻比較低的猜數字遊戲。花了一天的時間,基本能夠在Nokia6108上運行了,界面比較簡單,爲學習之用。 下面介紹一下如何實現猜數字遊戲,其實這是一個比...查看完整版>>基于Nokia S40的猜數字遊戲之一
 
基于Nokia S40的猜數字遊戲之二
作者:mingjava 文章來源: 現在我們已經有一能夠接收用戶輸入事件的Button類了,下面我們應該考慮如何實現遊戲中相關的邏輯,猜數字中的遊戲邏輯都比較簡單,主要是産生一個4位隨機數字且不能重複,其次是根據輸入...查看完整版>>基于Nokia S40的猜數字遊戲之二
 
基于Nokia S40的猜數字遊戲之一
基于Nokia S40的猜數字遊戲之一
筆者剛剛開始學習寫遊戲,並沒有什麽經驗,因此選擇了門檻比較低的猜數字遊戲。花了一天的時間,基本能夠在Nokia6108上運行了,界面比較簡單,爲學習之用。 下面介紹一下如何實現猜數字遊戲,其實這是一個比...查看完整版>>基于Nokia S40的猜數字遊戲之一
 
基于Java的移動遊戲開發入門
假如讀者有Java編程經驗,尤其是AWT和SWING的Java GUI開發經驗將十分有助于理解本文,盡管不是必需的。  一、 引言...查看完整版>>基于Java的移動遊戲開發入門
 
基于NokiaS60的遊戲開發
本文是基于Nokia Series 60和Symbian OS技術,指導開發者在現在的Series 60移動電話終端上開發高級的移動遊戲。   Symbian操作系統簡介  Symbian操作系統是所有Symbian OS電話共享的應用編程接口( API)技術的公共...查看完整版>>基于NokiaS60的遊戲開發
 
基于Nokia手機的移動遊戲開發步步通(五)
5 高分屏幕  當用戶從主菜單中選擇"High scores"選項的時候,高分就會顯示出來。高分是顯示在全屏畫布(FullCanvas)實例上的。分數應該在一個屏幕上就顯示完,而不要卷動頁面,因爲這樣會給用戶帶來麻煩。當然了,...查看完整版>>基于Nokia手機的移動遊戲開發步步通(五)
 
 
回到王朝網路移動版首頁