Series60游戏设计参考(三)

 

每个位图都会基于如上的定义而生成一个enumerated ID。The IDs are generated into the mbg file in system's include path(epoc32/include). 这里位图可以通过给定的ID从mbm中进行访问。每个ID数值都四自动创建的,并拥有如下格式:
EMbm<MBM file name><bitmap file name>,如EMbmMygameImage1.

我们可以通过如下的代码来访问MBM中的bitmaps.

#include <MyGame.mbg> // generated on compilation
#include <aknutils.h> // for CompleteWithAppPath()
CFbsBitmap* CMyGameView::LoadMyBitmapL()

  // set the name of the multi-bitmap file containing the bitmaps
  _LIT(KMBMFileName,"MyGame.mbm");
  TFileName mbmFileName(KMBMFileName);
  CompleteWithAppPath(mbmFileName);
  // load the bitmap from the mbm file
  CFbsBitmap* bitmap = new (ELeave) CFbsBitmap();
  CleanupStack::PushL(bitmap);
  // EMbmMygameImage1 is enumerated value from MyGame.mbg file
  User::LeaveIfError(bitmap->Load(mbmFileName, EMbmMygameImage1));
  CleanupStack::Pop(); // bitmap
  return bitmap;


当位图装入时,可以将它显示出来,如BitBlt
void CMyGameView::Draw(const TRect& /*aRect*/) const

   //Get the system graphics context
   CWindowGc& gc = SystemGc();

   //Draw the bitmap
   gc.BitBlt(TPoint(10, 10), iMyShipBitmap);
}   

我们有时候要需要使用mask,mask是两色的位图(因此1bit位图即可解决),我们可以使用BitBltMasked:
void CMyGameView::Draw( const TRect& /*aRect*/ ) const

  // Get the system graphics context
  CWindowGc& gc = SystemGc();
  // Draw masked bitmap
  gc.BitBltMasked( TPoint(10, 10), iMyShipBitmap, iMyShipRect, iMyShipMask, EFalse);


如果应用程序要画一个位图到窗口中,那它应该转换和该窗口一致的显示模式,而这将花费操作的时间而导致绘制变得缓慢,因此images应该在装入时就转换为正确的颜色深度。比如在游戏的初始化时或某个关卡的开始处,通常都生成一个包容器来处理所有需要使用的位图的装入、转换和存储。

转换应该使用一个临时的位图,代码如下:
CFbsBitmap* CMyGameView::LoadAndConvertBitmapL(Const TDesC& aFileName, TInt aBitmapId )

  // Load the original 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( newBitmap );
  CleanupStack::PushL( graphicsDevice );
  CFbsBitGc* graphicsContext;
  User::LeaveIfError( graphicsDevice->CreateContext( graphicsContext ) );
  // Blit the loaded bitmap to the new bitmap (the actual
  // conversion)
  bitmapContext->BitBlt( TPoint(0,0), originalBitmap );
  CleanupStack::Pop(3);
  delete bitmapContext;
  delete bitmapDevice;
  delete originalBitmap;
  return newBitmap;

上面的代码表示装入一个适当的位图(从MBM文件中),然后转化这个位图到window 显示模式,如果做的那,就是生成一个新的位图而将装入的那个传输到它上面。

位图还可以在运行时旋转和缩放处理,it is usually more practical to scale and rotate a single bitmap at runtime than to use multiple bitmaps with different rotation and scaling (for example, a ship sprite, which rotates 360 degrees):

CMdaBitmapRotator:给定角度来旋转位图
CMdaBitmapScaler:缩放位图

Series60 2.0平台向后,应该使用CbitmapRotator和CbitmapScaler来替代。

The methods are asynchronous by nature and neeed an observer class inherited from MMdaImageUtilObserver in order to be notified when the operation is completed.

如果提供的位图管理功能还不够,你可以使用Graphics API直接修改位图(see the previous LoadAndConvertBitmapL method for creating graphics context for a bitmap),如果速度是个很重要的因素,那可以通过指针直接访问位图数据,从而修改它。

如果要直接访问位图的数据,你可以使用CFbsBitmap::DataAddress方法,它可以返回指定位图的指针(指向位图左上角的指针),在操作时我们要有一定的预防措施,因为在运行时由于内存的防碎片处理而可能导致位图的地址发生变化,这就意味着堆中的位图数据区必须锁定起来,在访问前我们要使用TBitmapUtil。或者在S60平台2.0后使用CFbsBitmap的LockHeap和UnlockHeap方法来锁定。

在直接访问位图数据前,必须注意数据的格式,举例,16-bit的位图中,每个pixel是5-6-5格式(red bits - green bits - blue bits),而12-bit位图中则为4-4-4格式。

下面的代码演示了一个简单的效果,它增加了每个象素的red color部分(这是在16-bit位图里)

void CMyGameView::DoMyBitmapEffect(CFbsBitmap* aBitmap)

  // Lock heap
  // For series 60 2.0 use:
  // aBitmap->LockHeap();
  // For series 60 1.0 use:
  TBitmapUtil bitmapUtil(aBitmap);
  bitmapUtil.Begin( TPoint(0,0) );
  // Edit bitmap
  TSize bitmapSize = aBitmap->SizeInPixels();
  // NOTE: TUint16* applies to 16bit bitmaps only; the pointer must
  // correspond the bit depth of the bitmap.
  TUint16* bitmapData = (TUint16*)aBitmap->DataAddress();
  for ( TInt y = 0; y < bitmapSize.iHeight; y++ );
 {
   for ( TInt x = 0; x < bitmapSize.iWidth; x++ )
  {
    // Increase colour value of each pixel by one
    *bitmapData = ( *bitmapData & 31 ) | // blue
           ( ( *bitmapData >> 5 ) & 63 ) | // green
           ( ( *bitmapData >> 11 ) & 31 + 1 ); // red这样才能单独为red进行操作:)
    bitmapData++;
   }
  }
  // Unlock heap
  // For series 60 1.0 use:
  bitmapUtil.End();
  // For series 60 2.0 use:
  // aBitmap->UnlockHeap();


从2.0后,2D硬件加速apI就带来了更多的位图管理办法,如透明传输,这里不需要再弄什么mask了,只要定义透明色值即可:)

6.5 Sprites
一个sprite是一个masked bitmap,或多个位图,可以在背景或其他sprites上移动, preferably without applictions having to redraw completely the underlying window.

你可以通过使用位图,以及时间器来构造你的sprite classes。此外SymbianOS还包括一个ready-made RWsSprite类,它可以提供sprite的自动重绘和动画。

构造一个新的sprite的对象步骤如下:
1、Create a new RwsSprite.
2、Create as many TSpriteMembers as needed and append them to sprite.
3、Active the sprite.

每个sprite都有一个或多个成员,TSpriteMember,它提供了依附于sprite的image的属性,每个成员包括:
(1)The bitmap for the sprite member (iBitmap).
(2)The mask for the bitmap (iMaskBitmap). Set to NULL if no mask is used.
(3)Whether or not the mask is inverted (iInvertMask). Set to EFalse, if transparent color is black.
(4)Offset to define the sprite’s center (iOffset).
(5)How long the member is visible before displaying the next member (iInterval).
(6)Drawing mode (iDrawMode; used only when mask is not used).

下面的代码演示了如何从MBM装入一个位图,并由此生成一个sprite,以及动画的处理。
// .h
RWsSprite iMySprite;
TSpriteMember iMySpriteMembers[7];
// .cpp
void CMyGameAppView::ConstructL(const TRect& aRect)

  CreateWindowL();
  SetRect(aRect);
  // The mbm contains:
  // SpriteImage1.bmp
  // * Every second image is a mask.
  // SpriteImage1Mask.bmp
  // . . .
  _LIT(KMySpriteMBM, "MySprite.mbm");
  TFileName mbmFileName(KMySpriteMBM);
  CompleteWithAppPath(mbmFileName);
  // Construct my sprite
  iMySprite = RWsSprite( CEikonEnv::Static()->WsSession() );
  User::LeaveIfError( iMySprite.Construct( Window(), TPoint(0,0), 0 ) );
  // Load bitmaps (image+mask) and set properties for each member;
  // 7 members in total (7 images + 7 masks)
  for ( TInt i = 0; i < 7; i ++ )
 {
   // Load image
   iMySpriteMembers.iBitmap = new ( ELeave ) CFbsBitmap();
   User::LeaveIfError( iMySpriteMembers.iBitmap->Load( mbmFileName, i * 2, EFalse ) );
   // Load mask for the image
   iMySpriteMembers.iMaskBitmap = new ( ELeave ) CFbsBitmap();
   User::LeaveIfError( iMySpriteMembers.iMaskBitmap->Load( mbmFileName, i * 2 + 1, EFalse ) );
   // Set properties for the member
   iMySpriteMembers.iInvertMask = EFalse;
   iMySpriteMembers.iOffset = TPoint(0,0);
   // Change image every 1/10 seconds
   iMySpriteMembers.iInterval = TimeIntervalMicroSeconds32(100000);
   // Append created member to sprite
   User::LeaveIfError( iMySprite.AppendMember( iMySpriteMembers ) );
  }
  // All members added. Activate the sprite; the sprite will be
  // drawn/animated continuously to view until sprite is destroyed
  // with iMySprite.Close()
  User::LeaveIfError(iMySprite.Activate());
  ActivateL();


当sprite构造成功并激活后,它会自动的重绘和运动。唯一所要做的事情就是改变sprite的位置:
void CMyGameAppView::MoveMySpriteTo(const TPoint& aPos)

  iMySprite.SetPosition(aPos);


有时候需要改变位图的内容,since the images in each member are just plain bitmaps(CFbsBitmap) and only the handles of the bitmaps are sent to the window server(the ownership of the bitmaps does not change when adding members), the contents of the bitmaps can be changed andtime-just modify the iBitmap and iMaskBitmap of the member. After modifying the bitmaps you can call iMySprite.UpdateMember(index) to immediately apply the changes (the method redraws the sprite), otherwise the new outlook of the sprite is not visible until it is redrawn later.

注意你不能使用上述的方法改变一个位图的大小,如果需要的话,你必须生成一个新的TSpriteMember并且用UpdateMember(TInt aIndex, const TSpriteMember& aMemberData)来换掉那个现存的。

6.6 Animation and Video Clips
S60 platform2.0提供了一个简单的API来播放video clips, 它可以被用来显示短的视频,如一个游戏的intro。重放是个很繁重的任务(这得看视频的格式了),因此在游戏中使用视频也许并不十分可行。同样,有限的存储空间也不容许我们这样做起,因此尽可能的避免使用视频。

6.7 Double Buffering
在游戏的图形中包括了多个可移动的对象,它们需要频繁的更新。window server的客户端buffer可能会被填满,因此在所有对象都被更新前就可能发生刷新。对用户来说,就会产生闪烁的现象等。解决的方法就是用双缓冲,先将图形放到off-screen位图中,然后再将他们输出到屏幕。特别是游戏,一秒内要重绘屏幕数遍,更是需要这样一个off-screen位图。

使用的步骤如下所述:
1、生成一个新的位图,如果有很对象要放到back buffer上时,它们的color depth最好是一致,这样可以避免花费时间在相互之间转换。
2、为这个back buffer bitmap生成一个位图device和graphics context。
3、每次要更新屏幕时,要将graphics画到back buffer中去,当绘制完成后,就要调用view的DrawNow or DrawDeferred,这里后者是比较安全的方法。
4、在view的Draw中,要处理的就是把back buffer bimap传输到view(font buffer)中。

参见例子:
void CMyGameView::ConstructL(const TRect& aRect)

  .
  .
  .
  // Create a new bitmap with size of view’s rect and color depth of
  // screen
  TDisplayMode displayMode = CEikonEnv::Static()->ScreenDevice()->DisplayMode();
  iBackBufferBitmap = new(ELeave) CFbsBitmap();
  User::LeaveIfError(iBackBufferBitmap->Create(Rect().Size(), displayMode));
  // Create bitmap device for the bitmap
  iBackBufferDevice = CFbsBitmapDevice::NewL(iBackBufferBitmap);
  // Create graphics context for the bitmap
  User::LeaveIfError(iBackBufferDevice.CreateContext(iBackBufferGc));
 }


CMyGameView::~CMyGameView()

  delete iBackBufferGc;
  delete iBackBufferDevice;
  delete iBackBufferBitmap;
 }

// Called by e.g. timer to update the screen periodically.
// Here all the necessary drawing is done to backbuffer.
void CMyGameView::UpdateDisplay()

  // Draw some background
  iBackBufferGc->BitBlt(TPoint(0, 0), iMyBackgroundBitmap);
  // Draw something else here onto backbuffer
  .
  .
  .
  // When drawing to backbuffer is done, update the view
  DrawDeferred();
 }

void CMyGameView::Draw(const TRect& /*aRect*/) const

  CWindowGc& gc = SystemGc();
  // Just draw the backbuffer to view
  gc.BitBlt(Rect().iTl, iBackBufferBitmap);
 }


6.8 Direct Draw
Drawing onto the screen using the window server requires a context switch, which slows down drawing speed. 要绕开window server,就要摆脱掉context switch,应用程序可以直接访问屏幕,这被称为direct drawing:
SymbianOS中,有3个方法可以直接来绘制屏幕:
1、Creating and using CFbsScreenDevice.
2、直接访问屏幕内存区.
3、使用CDirectScreenAccess.

CFbsScreenDevice是一个graphics device,可以直接寻址到一个screen driver,SCDV.DLL,在为其生成一个CFbsBitGc后,就可以象其他的graphics device一样使用了。不管怎么说这里可以避开window server直接操作到屏幕上。

更快点的方法是访问屏幕内存地址,并通过一个指针来直接操作:

void CMyGameView::FillScreenDirectly() const

  TPckgBuf<TScreenInfoV01> infoPckg;
  TScreenInfoV01& screenInfo = infoPckg();
  UserSvr::ScreenInfo(infoPckg);
  TUint16* screenMemory = (TUint16*) screenInfo.iScreenAddress + 16;
  for(TInt y = 0; y < screenInfo.iScreenSize.iHeight; y++)
 {
   for(TInt x = 0; x < screenInfo.iScreenSize.iWidth; x++)
  {
     *screenMemory++ = 0;
   }
  }


屏幕内存有一个32byte的header,操作时要注意。

尽管直接写屏幕内存比操作CFbsScreenDevice更快点,但具体的功能还因硬件和屏幕设备的驱动而不同,在有些symbian设备中,当屏幕内存发生变化时就自动更新,有些则需要明确的指出变化。

上面将的屏幕内存地址只有在目标机器才是有效的,因此绘制代码在硬件和模拟器上都是不同的,You can solve this problem by using a temporary bitmap and its data address when running the application on the emulator, and directly accessing the screen when running the application on the device.

void CMyGameView::MyDrawing()

  #ifdef __WINS__
  // Draw to bitmap
  TUint16* myScreenPointer = iMyBitmap.DataAddress();
  #else // Hardware environment
  // Draw directly to the screen memory
  TUint16* myScreenPointer = GetMyScreenAddress();
  #endif
  DoMyDrawing(myScreenPointer);


使用direct draw的一个常见问题上一,window server不参与绘制后,导致它不能在其他窗口或窗口组到达前台时通知该应用程序,尽管应用程序会在失去焦点后得到一个时间,但并不能立即停止direct drawing,屏幕因此会变得混乱。如当一个电话接进来时。

SymbianOS提供了CDirectScreenAccess,它提供了一个安全但仍很快速的方法来直接访问屏幕。当使用CDirectScreenAccess时,它处理了和window server的通讯,通过callback interface我们可以接到两个notifications:
MDirectScreenAccess::AbortNow:在直接屏幕访问必须停止时被调用,如屏幕上的一个弹出窗口
MDirectScreenAccess::Restart:在已经安全的重续direct screen drawing时被调用。

下列代码演示了一个CDirectScreenAccess的操作,and how the direct draw support is activated:
// Inherited from MDirectScreenAccess
void CMyGameView::Restart(RDirectScreenAccess::TTerminationReasons aReason)

  // Usually just restart direct screen accessing
  TRAPD(err, iMyDrawer->StartL());
  if(err != KErrNone)
 {
   // Error; cannot restart
  }


// Inherited from MDirectScreenAccess; called when it’s needed to
// abort direct screen access immediately
void CMyGameView::AbortNow(RDirectScreenAccess::TTerminationReasons aReason)

   // Stop direct screen access immediately
   // e.g. dialog has become visible on screen


// Construct CDirectScreenAccess
void CHelloWorldBasicAppView::CreateMyDrawerL()

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


// Draw backbuffer bitmap to screen using CDirectScreenAccess
void CMyGameView::DisplayBackBuffer() const

  iMyDrawer->Gc()->BitBlt( TPoint(0,0), iMyBackBuffer );


在我们调用CDirectScreenAccess::StartL来激活direct draw support时,the client side window server buffer should be flushed. 要让屏幕自动的更新,我们需要调用screen device的SetAutoUpdate方法(给予ETrure参数),当direct draw激活后CDirectScreenAccess就生成了CFbsBitGc,应用程序可以使用它来绘制到屏幕上。

当另一个窗口被带到前台时,CDirectScreenAccess就会从window server得到一个事件来终止绘制,CDirectScreenAccess会调用从MDirectScreenAccess继承的AbortNow方法,应用程序可以重载它以终止绘制。为了防止屏幕变得紊乱,window server在abort drawing事件得到处理前不会去绘制重叠的窗口。
 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值