Symbian中的游戏编程(二)

类别:软件工程 点击:0 评论:0 推荐:

翻译:huwell

Window Server
window
server是给所有拥有GUI的应用程序所使用的,它提供给应用程序一个接口,从而使它们与其他应用程序交互时更方便。这个server的主要任务就是管理系统资源,象对屏幕和键盘的访问,它使用Symbian系统中client-server体系架构来执行,因此对共享资源的控制更加有力。客户程序和server运行在不同的进程中,不可以直接访问各自的内存地址区域,因此它们之间的通信就要通过一个消息来传递,这个在client和server之间的通道就成为一个session,当session打开后,一个client可以使用session发送一个消息到server端,从而完成一次对server的请求。这个消息包括一个32bit请求类型操作码,和最多4个32bit的参数。在请求完成后,server返回一个32bit完成码到client中,server可以使用进程间通信服务来发送和接收额外的数据。

每个client应用程序与window
server的通信都要使用一个window server
session类:RWsSession,这个类的主要任务是协调发送到应用程序的异步事件,比如重绘事件,priority
key events和标准事件包括用户输入事件等,window
server会判断哪个应用程序和窗口会接受这个事件。如,一个键盘事件只能发送到拥有焦点的窗口,而一个重绘时间只能发送到应用程序中当前可见的窗口。Due
to the higher process priority of the window server, the events are
handled with higher priority than applications’ other
requests.

(一)、Client Side Buffer
应用程序对window
server的请求通常通过如下的步骤来处理:
1). The window server’s
client side processes the request.
2). A context switch from the
client process to the server process occurs.
3). The window server
processes the request,
4). A context switch back to the client
process
occurs.

这个方法确保了请求在正确的步骤中被处理,而当最后控制重新又回到客户端手中时,请求已经被处理过了。不管怎么说,在两个进程间的环境切换是十分繁杂的过程,会产生比较大的开销。

Even
though, the window server has been implemented as a fixed process, which
has a fixed virtual address area and hence does not need its address
pointers to be updated, the context switch originates unfounded speed
losses. The clients, raising a request, also has to wait for a
synchronous response. For most of the requests, including drawing
methods, this is unnecessary. Because of these drawbacks, the
asynchronous function calls are buffered in a client side window server
buffer.

In the GT versions of the Symbian OS the buffer size has
been fixed to 640 bytes. Series 60 has grown the buffer to 6400 bytes,
and added support for applications to alter the buffer size.
(注意)Larger buffer size is especially valuable in applications where
drawing consists of a number of drawing functions or large amount of
text. This can be seen as decreased flickering. The buffer size can be
changed in the ConstructL of the application’s UI class:

void
CMyAppUi::ConstructL()

  BaseConstructL();
  // WS
buffer size can be set after the BaseContructL is
called
  RWsSession &ws =
iEikonEnv->WsSession();
  TInt bufsize =
10000;
  ws.SetBufferSizeL(bufsize);
  // Continue normal app
UI contruction
  iMyView = new ( ELeave )
CMyMainView;
  iMyView->ConstructL( ClientRect()
);
  AddToStackL( iMyView
);


如此一来,当缓冲被发送时,所有的methods都会在一次message中发送到window
server中,这样就只有两次环境切换发生,从而减少了负荷。通常缓冲在如下情况下发生:

1、缓冲满了
2、一个同步method被调用
3、EventReady(),
RedrawReady()或PriorityKeyReady()被调用
4、Flush()被调用,或
5、一个会导致缓冲溢出的method被调用

If
a drawing is initiated in response to another event than a window server
event, an explicit Flush should be called. This is the case, for
example, in games, where drawing is usually initiated in response to a
timer
event.

(二)Windows
应用程序在屏幕上绘图要使用windows,它们是由window
server来管理的,下图就描绘了各不同window类之间的关系:

border=0>
(windowsgroup.gif)

所有的窗口都是从基类:RWindowTreeNode派生的,这个基类指定了他们的z-order。可显示的窗口也同样都是从一个抽象基类中派生的,这就是RWindowBase。RWindow是一个标准窗口,它可以根据程序的要求来绘制或重绘。RBackedUpWindow表现了这样的一种窗口,它将自己的内容都存放在一个backup
bitmap中。当RBackedUpWindow的某区域有效时,the window server redraws it
automatically from the backup bitmap, without requiring an application
redraw. 所有从RWindowBase中派生的类,都有一个显示模式(display
mode)用于指定其颜色深度。显示模式是由TDisplayMode这个枚举类型来定义的,而具体模式一般都和屏幕的一致。RWindowGroup是一个non-drawable
window,它在
屏幕上是不可见的。The main function of RWindowGroup is
to handle the keyboard focus and to form a group for the applications’
other windows. 一般说来应用程序都有个window
group,用来联系所有的按键事件。

应用程序的窗口们都是一个树状层次,最端点是windonw
group,因此每个drawable窗口都有一个父窗口以及一个或多兄弟以及子窗口。窗口的z-order就是由他们的原始位置所指定的,这个位置和他们的父窗口相关,并使之在兄弟窗口中保持唯一性。当然,我们可以通过改变这个位置从而改变其窗口的drawing
order。

最前面的窗口和window group其位置为0。window
group的原始位置也同它们的优先级联系在一起。有个API可以访问窗口的原始位置,定义在RWindowTreeNode。

(三)Control
Environment
The window server provides a relatively low-level
interface, which should be encapsulated in active objects due to the
asynchronous services。应用程序可以去实现它,但系统在window
server和我们的程序之间提供了一个中间层,这就是control
environment(CONE),它封装了window
server提供的服务。CONE运行在每个程序的进程中,并被所有拥有GUI的应用程序所使用。CONE的基类是CCoeEnv,这是一个活动对象,当从window
server接受到事件后它的RunL函数就会被执行。CONE的主要任务就是评估这些事件,并把它们转发到各个相宜的组件中去处理。CONE提供了一个清除栈,一个active
scheduler,以及一些实用函数。CONE使用这个active scheduler来管理从window
server来的异步服务。键盘事件的优先级要超过重绘事件,以便程序能更快的响应用户的输入。

CONE提供了应用程序端的控件(control),它是symbian系统中最基本的交互单元了,每个drawable窗口都包含一个或多个控件,这是从抽象基类CCoeControl中继承的。一个拥有整个窗口的control被称为window-owning
control,而只覆盖了部分的窗口则称为non-window-owning控件,如寄生的控件。为什么要介绍控件那,因为他们提供了比窗口多的优点。首先,他们比窗口更节约内存的使用,他们也减少了在一个应用程序和window
server之间进行client-server通信的必要,因为重绘一个component只需要一个事件,而不管有它拥有多少control。The
redraw events are generated for windows and thus a component which
consists of more than one window, would need more than one redraw
requests. Also the logic to detect intersections, is simpler with
controls.

control可以象窗口一样嵌套,一个compound
control应该重载两个函数:CountComponentControls和ComponentControl。Compound
control也需要处理按键事件的分派问题。

在CONE中维护着一个控件堆栈,用来管理the
channeling for the
application,控件堆栈包括了那些需要按键事件的控件的列表。事件会根据他们在堆栈中的位置传递到他们那。

CONE
can be accessed from the CCoeControl and CCoeAppUi derived classes using
the iCoeEnv class
member.如果它们都不可用,则可以通过静态函数CCoeEnv::Static()来获得使用,它会返回一个指向CCoeEnv的指针。注意,这方法只能是在没有办法访问到它的情况下才可以使用,因为这里使用了thread-local
static(TLS),它是很慢的。

(四)UI
Library
CONE自己并不提供任何具体的UI组件,它们是由UI库来提供的。s60提供了一个UI库,Avkon,这是从标准的symbian系统UI库——Uikon——中扩展和修改来的。Avkon提供了各种组件,这些都是专为s60屏幕量身定做的。

Avkon
UI组件根据它们在屏幕上的布局可以机分为3个大类:状态pane,主pane以及control
pane。可以参见下图来看:


border=0>
(threepane.gif)

status pane包括几个子pane:signal
pane, context pane, title pane, battery pane, uni indicator pane和navi
pane。每个程序都有一个状态pane的实例,它是在应用程序开始启动时自动生成的。这里signal
pane, battery pane和uni indicator pane是属于一个system side
server的,它们不能被应用程序所改变,其他的pane是可以的。而main则是应用程序数据正常显示的地方。Avkon
UI库提供了各种组件,应用程序也可以为main pane生成它们自己的控件。Avkon
UI库所提供的组件包括:list boxes, grids,
form和不同类型的弹出窗口,如询问和选项菜单。而control
pane包括softkeys和一个滚动指示器。

尽管main
pane的区域已经能适应大部分程序的需要了,但对游戏来说,一般需要更大的空间:),更大的游戏视图空间提了游戏的可玩性。

如果要将游戏的视图覆盖整个屏幕,可以针对,偶控件调用CCoeControl::SetExtentToWholeScreen函数。这样status和control
pane就被隐藏不见了。它们可以通过调用CCoeControl::MakeVisible函数(为EFalse)来完成。

Bitmaps
———-
symbian操作系统可以被认为是一个bitmap-oriented
OS。

symbian系统提供了它自己的位图格式,MBM,这是个多位图的文件。MBM利用一个位图转换工具bmconv从windows位图中生成的。作为一个MBM,它可以包括多个位图,bmconv会生成洋位图header文件,MBG文件,它列举出了所有位图的ID,以便访问它们。当要从一个MBM文件中装入一个位图时,就应该include相应的头文件,并将正确的ID做为给定的参数。

bmconv可以从命令行运行,也可以在一个工程文件中进行如下定义:
START
BITMAP [target-file]
HEADER
TARGETPATH [targetpath]
SOURCEPATH
[sourcepath]
SOURCE [colour-depth]
[source-bitmap]
END

bmconv可能生成两个不同类型的symbian位图:ROM和non-ROM位图。non-ROM的位图就是一般的文件存储位图,使用run-length
encoding(RLE)进行了压缩,可以在它们要被使用时装入RAM,而为了加快绘制速度,ROM位图是不被压缩的,因此它们可以直接从ROM中使用,缺省时bmconve生成的是文件存储位图,即non-ROM的。

注意,制作sprite图时我们会用到mask位图,因为只有黑白色,因此它最好被存储为1bit的图,s60提供了一个命令行工具叫makemask,它可以从8bit
bitmaps中生成1bit的mask。Makemask uses the last palette index in the
original bitmap as a transparent
colour.

尽管symbian系统提供了一些API来设置位图的调色板,但没有被实现。The
APIs were supplemented before any support for colour displays was
implemented.

位图通过CFbsBitmap类来管理,它提供了生成个装入位图的方法,并可以定义它们自己的颜色深度和大小。它使用了RFbsSession类来访问FBS,因此对用户隐藏了session类。CFbsBitmap也提供了直接访问位图数据的方法。可以通过DataAddress成员来获得指向数据地址的指针,也可以通过访问GetScanLine成员来访问到一个specific
scan
line。

位图也根据他们的大小而在FBS分为两个不同的堆,以4kb为个界限。大尺寸的图形可以进行自动的磁盘碎片整理。而因为要整理,因此这个堆应该可以locked,这里TBitmapUtil可以提供堆的锁定和解锁,而有当位图的image
data被编辑时,我们也需要锁定堆。

drawing和拷贝都提供了自动锁定。

//
Lock the heap if a large bitmap
if ( bitmap->IsLargeBitmap()
)

  TBitmapUtil bitmapUtil( bitmap
);
  bitmapUtil.Begin( TPoint(0,0) );

// Edit
bitmap
TSize bitmapSize = bitmap->SizeInPixels();
TUint16*
bitmapData = (TUint16*)bitmap->DataAddress();
TUint16 colour =
0;
for ( TInt y = 0; y < bitmapSize.iHeight; y++
);

  for ( TInt x = 0; x < bitmapSize.iWidth; x++
)
  {
    *bitmapData++ = colour++;
  }

//
Release the heap
if ( bitmap->IsLargeBitmap()
)

  BitmapUtil.End();


这可以使位图的绘制比CFbsBitmaps更快,window
server提供了它自己的位图类,CWsBitmap。它可以避免在window
server和FBS之间额外的环境切换,通过自主掌握bitmap
handle。这CWsBitmap是从CFbsBitmap中派生的。注意,当绘制速度要求很高时,我们应该使用CWsBitmap。

Drawing
———
应用程序使用windows在屏幕上绘制,可以使用CWsScreenDevice来实现,它与CWindowGc相联系,这样graphics的context和device都有了。

The
drawing methods of CWindowGc are buffered on the client side window
server
buffer.

Drawing可以在系统或应用程序初始化时引发,系统初始化的绘制是当窗口生成时,或窗口的内容无效时引发的。当一个窗口要被重绘时,window
server就会发送一个redraw
event到应用程序中。CONE就可以处理控件的重绘处理,这就是为什么每个控件都应该处理Draw方法,从而能正确完成其redraw事件。

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)
);


上面的TRect参数就表明了具体的无效区域,大部分控件是忽略这个参数的,因为它们的重绘很简单,即使整个区域也不会太慢。

应用程序的drawing则需要应用程序数据或状态的改变。CCoeControl提供了非虚函数DrawNow()可以让window
server进行控件的重绘。

CCoeCOntrol也提供了DrawDeferred方法,它可以使窗口无效,并重新从window
server发出一个redraw
event。这两种方法的不同之处在于,DrawNow强制控件立即重绘自己,但DrawDeffered则是低优先级的重绘,因为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();


(一)Sprites
Sprites是一个masked
bitmap,它用不着重绘后面的窗口就可以移动。Symbian系统中提供了两种不同类型的sprites:pointer和animated
bitmaps。
如图(spritesclass.gif)


RWsSpriteBase是一个抽象的sprites基类,它拥有一个或多个TSpriteMembers,它包含了sprite的位图数据,TSpriteMember也定义mask位图,以及位图的定位,还有轮显的时间间隔。RWsSprite是sprites的一个具体类,除了构造,它只提供了一个方法,SetPostion,我们可以使用它来移动这个sprite,下面的代码就演示了以上的内容:

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] ) );


注意在sprite member被更新和添加后,sprite应该调用RWsSpriteBase::Activate来激活。这样sprite就可以在屏幕上显示了,并可以移动。

而sprite的内容可以通过调用RWsSpriteBase::UpdateMember来改变。CFbsBitmaps are also accessible to the window server, only the bitmap handles of a sprite are sent to the window server. This makes updating of a sprite’s bitmaps considerably fast.

当一个sprite不再需要时,我们应该调用RWsSpriteBase::Close来释放占用的window server资源。这样并不会释放客户端的成员数据,它们应该被删除掉。RWsPointerCursor则可以生成一个cursors,和RWsSprite一样使用,只有点不同,那就是当pointer已经activated后,不会显示在屏幕上,直到RWindowTreeNode::SetPointerCursor被调用。

(二)Double buffering
当游戏中包含多个需移动的图片而更新又比较频繁时,我们应该尽量填满客户端的缓冲,以便所有对象可以一次更新完毕。对用户来说,可能会发生屏幕闪烁的现象,我们可以通过双缓冲的方法来解决这一问题,图片首先可以先绘制到off-screen位图中,最后再做为一次window server操作绘制到屏幕。特别是在一秒要多次更新屏幕的,更应该使用这种方法。

一个off-screen位图可以通过使用位图graphics context和graphics device类:CFbsBitGc和CFbsBitmapDevice来生成它。

当程序要在窗口中绘制位图时,it gets converted into the same display mode than the window.这样一来,操作就会放慢。对游戏来说,如果要使用位图表现动画,则应该在动画开始时完成这种转换。可以参考下面的列子:
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;


上面的代码从MBM加载位图后,就转换为window的显示模式,并产生一个位图用来做缓冲。如果游戏有多个位图需要转化,我们应该在初始化场景或关卡时一次做完。从而对用户隐藏这些操作。

Direct draw
—————
在屏幕上绘制,要使用window server,这就需要场景的切换了,这样一来就减低了绘制的速度。我们能不能饶开window server,从而摆脱掉场景切换(context switch)那?可以的,程序可以直接访问屏幕,symbian中可以有两种方法在屏幕上直接绘制。

CFbsScreenDevice就是这样的一个graphics device,它可以直接定址到屏幕driver, SCDV.DLL。在生成CFbsBitGc后,它可以同其他grahics device一样使用。这样就可以直接绘制到屏幕上,而无须通过window server了。而另一个方法,就是向屏幕寻址。我们可以通过使用UserSrv类来完成:
TPckgBuf<TScreenInfoV01> infoPckg;
TScreenInfoV01& screenInfo = infoPckg();
UserSvr::ScreenInfo(infoPckg);
TUint16* screenMemory = screenInfo.iScreenAddress + 16;

屏幕内存有一个32byte头,我们写内存的时候需要把它考虑在内。

直接写屏幕内存的方式要比使用CFbsScreenDevice来得更加快速。
In some Symbian OS based terminals the screen is automatically updated from the screen memory when the memory is changed, whereas in other terminals the drawing needs to be explicitly activated.

注意屏幕内存寻址只能在目标机器上有效,因此代码需要按实际机器还是模拟器来分别组织,参见下面代码:
#ifdef __WINS__ // Emulator environment
// Draw to an off-screen bitmap
#else // Hardware environment
// Draw directly to the screen memory
#endif

以上两种方法带来一个弊端,这就是因为屏弃了window server,所以程序不会得到通知——如其他窗口或窗口组到了前台。尽管程序会在失去焦点时收到一个事件,但这不能及时的停止绘画,屏幕可能会变乱。

这里有个未公开的API(在6.x系列前)可以很好的解决这个问题,这个API包括两个类:MDirectScreenAccess,它提供给应用程序一个回调函数,一个是CDirectScreenAccess,它提供了与window server之间的通信。下面的代码演示其是如何构造的:
iDrawer = CDirectScreenAccess::NewL(iEikonEnv->WsSession(), *iEikonEnv->ScreenDevice(), Window(), *this);
iEikonEnv->WsSession().Flush();
iDrawer->StartL();
iDrawer->ScreenDevice()->SetAutoUpdate(ETrue);

这里CDirectScreenAccess的NewL方法会需要一个window server session,CONE的graphics device,应用程序的窗口,以及一个指向从MDirectedScreenAccess派生的类做参数。在CDirectScreenAccess::StartL被调用来activate the direct draw support之前,the client side window server buffer should be flsushed.为了让屏幕自动更新,screen device的SetAutoUpdate方法应该调用(使用ETrue做为参数),这些工作完成以后直接绘制就可以开始工作了,CDirectScreenAccess生成了CFbsBitGc这个graphics context,我们可以使用下面的语句来绘制:
iDrawer->Gc()->BitBlt( TPoint(0, 0), iBitmap);

当另一个窗口前成前台时,CDirectScreenAccess会从window server得到一个事件从而终止绘制,这时CDirectScreenAccess会调用MDirectScreenAccess派生类的AbortNow方法,我们程序需要重载它以便处理终止,为了防止屏幕变乱,window server不会在重叠的窗口上绘图,知道abort drawing事件被处理完毕。

文章来源:http://symbian.org.cn/bbs/viewtopic.php?t=907

本文地址:http://com.8s8s.com/it/it31899.htm