Series60游戏设计参考

来源:互联网 发布:java 类型擦除 编辑:程序博客网 时间:2024/05/18 01:06

[其他] 【转帖】在游戏设计中,要注意的一个首要问题便于能够处理系统的各种事件,如一个电话或短信的到来,当这些系统时间发生时,前台的程序会失去焦点,这时我们可以通过重载CAknAppUI::HandleForegroundEventL来处理中断游戏时所要处理的事情。

为了考虑电池的节能使用,我们的游戏在长时间没有用户输入时(这时很可能是用户忘记关闭游戏了),就不应该再处理任何timer,以便机器进入休眠状态,事实上,如果程序处于这样的状态,系统会发送一个事件来通知我们,我们可以调用RTimer::Inactivity来处理。

设计游戏时我们所要考虑的另一个重要方面便是可移植性,比如到其他设备上,可能会产生屏幕大小不容的问题,因此我们在游戏中涉及屏幕大小的代码,不应该使用直接的数字数值,因该调用类似CEikAppUi::ApplicationRect()和CWsScreenDevice::GetDefaultScreenSizeAndRotation()这样的函数来获得屏幕大小等。

手机上设计游戏有很多的制约,内存,CPU,图形处理器以及运算处理器都无法和PC相比,甚至屏幕,键盘,等都对上面的开发产生了一定的制约,譬如说键盘,只有有限的数字键,不同的设备间,键的布局还可能不同,怎么办,呵呵,我们在设计游戏时就一定要考虑到游戏中操作时使用键的重定义。如上的这些制约实际上都限制了我们,因此在开发中要考虑好手机上适合开发什么样的游戏并不失其可玩性,不过目前N-Gage的横空出世使得我们在手机上玩古墓丽影都变为可能。呵呵。


内存
内存管理在一个内存受制的设备中显得尤为重要,这包括运行时的内存使用和最后生成的代码大小。大多数symbian设备中只有8MB或更少的RAM,in addition to RAM, the devices have ROM for preinstalled software and a user data area for installed applications and the systems#39; writeable and persistent data files。此外,设备中可以安装各种扩展卡,如CF卡或MMC卡。

使用RAM中最重要的原则就是对分配的内存要及时释放,在模拟器上开始时,它提供一个内存检查的宏(如果程序中使用一个GUI时就会缺省提供)这个宏可以用来在程序没有释放内存时示警,由此在早期设计过程中就杜绝了内存的泄漏。而在目标机器上,由核心来保证跟踪每条线程的内存使用,并在他们退出时自动的加以释放,以保证程序退出时所有的内存都得到正确的释放。

在设计一个应用程序时,堆栈的使用是值得重视的。在SymbianOS中,每个线程都有它自己的内存堆栈,而它在线程运行后是不能增加的。

Series60中一个应用程序却省的堆栈大小只有8KB,正是因为如此,我们应该更为小心的使用它。在模拟器和实际机器上,这方面是存在分歧的,在模拟器上堆栈是没有限制的,而在机器上却有。这是因为模拟器上使用了PC的堆栈。因此每个程序都应该尽早的在机器上进行调式。堆栈溢出的大部分原因是因为stack descriptors的使用,同样递归的使用也是个吃内存的大户。如果递归编程是必要的,那传递的参数大小和局部变量都要很好的控制到最小化,小糊涂曾经编写过一个程序,从windows移植过来一个核心运算部分,就是递归,非常复杂以至于不能拆解开来,结果程序在3650上经常报内存不足-_-0

由于游戏中对图形的要求,因此位图成了程序内存消耗的大方面。在这方面节约内存的有效措施并不是减少位图的使用,而是降低他们的颜色深度。Symbian OS支持24bit位图,这样可提供16777216种颜色,但实际上目标机器所能表达的颜色不是很多。因此位图的颜色不应该超过机器所能表达的范围。一般来说8bit(256色)就足够应付大多数需求了,而所有的mask都应该转为1bit位图。

As with bitmaps, any data used in game – sound, music, and video – should not consume a lot of space.

3、Structure of the Game and Game Loop
游戏的循环和游戏的结构是游戏中非常重要的部分。一个好的结构设计可以使得游戏开发更为容易些。这里给出了一个结构的示例和游戏循环的实现部分,它们是在Series60 Avkon下完成的。

Series60 Avkon应用程序的文档-视图结构是非常适合游戏使用的,因此一个游戏的典型结构如下:
AppUI:
游戏的controller,它控制了游戏的视图和菜单命令,以及其他用户输入(可传递袄active view)。

View:
拥有整个游戏的引擎(或部分引擎),displays the game state from the engine, and handles the UI controls on the view and other user input.

Document:
用来存储引擎中游戏的状态(实际中是很少使用的)。

Engine:
游戏引擎,它实际上在一个单独的类——而非UI(AppUI或view)中完成了整个游戏的逻辑部分。这表明了该类的结构和职能。游戏引擎有时会和view混合在一起,这是不能避免的,因为view处理了用户的输入和引擎的绘制部分。

实际上有2种方式可以用来实现游戏的多个场景(states)
1、使用多个views(如,intro, main menu, option screen, game playing).
2、使用单独一个view.

如果只使用一个view,那引擎比较容易实现——它只被appUI所拥有,因为appUI和view需要处理用户的输入,而view还要现实游戏的状态,因此我们通常会把一个游戏引擎的reference传递到view中(在它生成时)。这使的游戏的结构变得稍有晦涩。

使用单独的view会使用户输入和绘制部分变得凌乱,因为需要这个view根据游戏的状态做出判断,特别在一个大游戏里更是容易弄乱。

而当游戏使用多个逻辑views时,用户的输入和绘制部分可以很清楚的放入各个相关类中,如:
View for the introduction of the game
View for the main menu
View for the options screen
View for game playing

Since the Avkon architecture provides a ready-made framework for displaying multiple view, 所有上述成立可以很容易的实现,从而把结构变得更为清晰。AppUI的职能就是管理多个视图。

但每件事都有他们的正反面,开发者可以自己判断是否使用多视图,如特别在处理image containers和sound classes的实现,以及如果从各个view中访问他们而产生的问题。

一般来说游戏的循环是由一个timer来构建的,由此来更新游戏的状态,而用户输入的处理并不受timer的制约,通常timer在view中实现,它发送timer events到引擎,并周期性的更新view。
1. void MyGameView::MyTimer()
2. {
3.   iMyEngine-gt;NextFrame(frameTime);
4.   UpdateDisplay();
5. }
复制代码
另外有种情况是,游戏的引擎有个内部timer,这样会似的程序看起来比较混乱,因为引擎即要更新view,而view又要发送用户输入到引擎。

如果游戏引擎包含有Active Objects(多任务),may be necessary to use the method where the engine updates the screen’s back buffer when the active object is ready, and view always draws the last previous full frame – the variety of possibilities is almost limitless.

4、Timers
各种Timer被使用在游戏中,他们组成了游戏的循环。他们被用来周期性的重绘屏幕,更新sprites的位置,以及处理游戏中的各种事件。

symbianOS中的时间发生器是比较低的(和其他设备相比),不过仍可应付大多典型需求,只有设备中kernel端timer可以提供1/64秒的精度,而模拟器上则只能提供1/10秒了(可以使用UserHal::TickPeriod得到该实际值)。

SymbianOS中可以提供3个timer:
(1)、Simple timers,由RTimer提供,可以在每隔一段时间或在某一时间点上产生触发事件。RTimer提供了对timing services的最底层访问,完成时通常需要Active Objects的配合。如果游戏引擎是使用Active Object完成的,那使用这个timer最好。

(2)、Periodic timer(CPeriodic),可在给定周期提供触发事件,event is notified to the application through a callback (TCallBack) given when creating the timer. 这个timer因为其简单性而用得较多,通常需要一个timer来产生周期性的事件时就使用它。

(3)Heartbeat timer(CHeartBeat),它和periodic timer类似,但当一个时间事件丢失时(也就是说没有被处理)heartbeat timer可以执行一个callback以通知程序。CPeriondic timer并不是十分精确的,因为它要等待程序准备好处理一个新事件后才会发送这个事件(这个时候程序不能太忙)。

这里heartbeat timer的附属功能——即通知丢失事件(MBeating::Synchronize),很少需要。因为毕竟游戏中并不需要如此精确的时间统计。

如sprites的移动为(position = speed * time),因此轻微的时间丢失是不会产生什么危害的。

正如前面所说,CPeriodic timer是足够游戏使用的,下面的代码演示了这方面的内容:
1. // Starts the timer
2. void CMyGameView::StartTimerL()
3. {
4.   // tickInterval in microseconds; 100000 equals to 1/10 seconds const TInt KTickInterval = 100000;
5.   iPeriodic = CPeriodic::NewL(CActive::EPriorityLow);
6.   iPeriodic-gt;Start(KTickInterval, KTickInterval, TCallBack(Tick, this));
7. }
8. // Stops the timer
9. void CMyGameView::StopTimer()
10. {
11.  iPeriodic-gt;Cancel();
12.  delete iPeriodic;
13.  iPeriodic = NULL;
14. }
15. // Called by the timer when given interval elapsed
16. 
17. // This must be static method
18. TInt CMyGameView::Tick(TAny* aObject)
19. {
20.    // call non-static method
21.   ((CMyGameEngine*)aObject)-gt;NextTick();
22.   // Allow the timer to continue it’s work return 1;
23. }
24. 
25. // non-static timer event; called by static Tick()
26. void CMyGameView::NextTick()
27. {
28.   TTime currentTime;
29.   currentTime.HomeTime();
30.   TInt64 currentTick = currentTime.Int64();
31.   // Calculate elapsed time from last timer event in microseconds
32.   // iLastTick is the current time from previous timer event
33.   TInt64 frameTime = currentTick - iLastTick;
34.   iLastTick = currentTick;
35.   // Update activity on the screen according to frameTime
36.   switch(iGameState)
37.  {
38.    case EPlayingTheGame:
39.    // Calculate e.g. sprite positions in game screen and
40.    // redraw the screen
41.    HandleGameTick(frameTime);
42.    break;
43.    case EWatchingTheIntro:
44.    // Intro of the game can be handled with the same timer
45.    HandleIntroTick(frameTime);
46.    break;
47.    // Other possible game states could be handled too
48.   }
49. }
复制代码
我们并不需要使用多个timer,这样会造成程序结构紊乱和代码的不清晰,每个timer同时也都是一个Active Object(除了RTimer,但事实上它也需要ActiveObject的配合使用),这样每timer都会产生Active Scheduler的装载和资源的耗费。
(小糊涂的五子棋就使用了一个timer,主要负责intro的loading以及一个棋盘动画的显示:)

timer的优先级和Active Scheduler的装入直接影响到timer的精确性。此外,should not swamp
the active scheduler with long lasting calculations between timer events, the priority of the timer should be kept low and the timer’s interval should be realistic (not just, for example, one microsecond). These precautions should be taken into account to prevent the known ViewSrv 11 panic issue, which occurs when the active scheduler is swamped and the application’s ViewSrv Active Object cannot respond in time.
不要让active scheduler太繁忙以至于来不及调度active object:)

5、Keyboard
由于目前series 60设备的多样性,使得我们在键盘操作上要认真考虑,最理想的状态便是可以选择控制游戏的各个键。

键盘事件的处理在程序中是很简单的:
AppUI在HandleKeyEventL中接收到key event;
AppUI通过调用view的OfferKeyEventL,将key event发送到当前活动的view中。

如果使用了AddToStackL(通常在AppUI的ContructL()函数中,如AddToStackL(iAppView);),那key event就会自动的从AppUI传递此view中,key event不会被AppUI成立,除非这个key event需要的功能是全局的(也就是说不是和任何制定的view联系在一起的)

TKeyEvent和TEventCode,做为参数传递到OfferKeyEventL中,包括3个重要的数值:
Key event type
Scan code of the key
Character code of the key.

每次按键都会按顺序产生3个事件:
EEventKeyDown:当键被按下时产生

EEventKey:在键被按下后产生,如果键被保持按下的状态,那该事件则周期性的发生(发生频率可以使用RWsSession::GetKeyboardRepeatRate来查询)

EEventKeyUp:当键被释放后产生

实际设计中,通常使用EEventKeyDown来控制一次行动(如发射导弹)。如果要控制诸如飞船飞行等,那就要使用一个flag来指示该键仍处于按下状态,直到EEventKeyUp发生时才重置这个flag。

如果按键超过很长时间,那EEventkey事件就会多次传递到应用程序,重复的频率可以通过RWsSession::SetKeyboardRepeatRate来修改。这个是很有用的,因为你可以使用EEventKey每隔段时间就产生一次东西,如可以如果按键被长时间按下后,可以每隔0.5秒就发射一次导弹。

TKeyEvent为一个键准备了两个不同的代码,Scan code (TKeyEvent::iScanCode) is a practical choice for processing the key events in games, since it bypasses possible keyboard mappings and FEP. The character code in TKeyEvent::iCode may change, when, for example, a single key is pressed multiple times, depending on how the mappings and FEP are set up. When controlling the activity in the game, it is more important to know which physical key the user has pressed than the resulting character.

缺省时,同时按下多个键是不允许的,只能接受第一个被按下的键的事件。这称为key blocking,缺省时只有电源键和编辑键才是non-blocked keys,而大多数游戏都是需要处理多个键盘同时被按下的情况的(如果一边移动,一边发射武器),我们可以通过如下步骤来实现:
1、CAknAppUi提供了SetKeyBlockMode方法来取消key blocking。
2、Key blocking也可以通过系统设置来改变,但不建议这样做,因为它将对其他程序也发生影响。

下面是个切换key blocking的示例:
1. void CMyGameAppUi::ConstructL()
2. {
3.    //Disable key blocking
4.    SetKeyBlockMode(ENoKeyBlock);
5. }
复制代码
而游戏中view的OfferKeyEventL的处理应该看起来如下:
1. TKeyResponse CMyGameView::OfferKeyEventL(const TKeyEvent& aKeyEvent, TEventCode aType)
2. {
3.    switch(aType)
4.   {
5.     case EEventKey:
6.       if(aKeyEvent.iScanCode == EStdKeyNkp5 || aKeyEvent.iScanCode == EStdKeyDevice3)
7.      {
8.        // EEventKey is called multiple times periodically as
9.        // long as the key is pressed down, so the ship will be
10.        // firing periodically too.
11.        iMyGameEngine->Fire();
12.        return EKeyWasConsumed;
13.       }
14.       break;
15.     case EEventKeyDown:
16.       switch(aKeyEvent.iScanCode)
17.      {
18.        // Key pressed down; start moving the ship to left or
19.        // right, and keep it moving as long as the key is
20.        // pressed.
21.        case EStdKeyNkp4:
22.        case EStdKeyLeftArrow:
23.           iMyGameEngine->SetShipMovingTo( ELeft );
24.           return EKeyWasConsumed;
25.        case EStdKeyNkp6:
26.        case EStdKeyRightArrow:
27.           iMyGameEngine->SetShipMovingTo( ERight );
28.           return EKeyWasConsumed;
29.        }
30.        break;
31.     case EEventKeyUp:
32.       switch(aKeyEvent.iScanCode)
33.      {
34.        // Key released; stop moving the ship
35.        case EStdKeyNkp4:
36.        case EStdKeyLeftArrow:
37.        case EStdKeyNkp6:
38.        case EStdKeyRightArrow:
39.        iMyGameEngine->SetShipMovingTo( ENowhere );
40.        return EKeyWasConsumed;
41.       }
42.       break;
43.     }
44.     return EKeyWasNotConsumed;
45. }
复制代码
缺省时是不可以同时处理两个键的,但是我们现在做了ENoKeyBlock处理,那可以同时处理两个键,一个在EEventKey中的周期性响应的键事件,一个是EEventKeyDown后放置flag不停处理的事件。

 


1
评分次数
 
xingkongyu
收藏 评分
江湖在心里,心有多大,江湖就有多大!

 回复 引用
   订阅 TOP
分享到:               

 
高原梦
 
渐入佳境
 
UID
43430
积分
291
学分
24 
来自
北京-北京市  2#
 发表于 2010-4-16 11:27 | 只看该作者
6、Graphics
6.1 Graphics Architecture Overview
SymbianOS的图形支持在系统的图形设备接口(GDI)指定。GDI定义了最低层的绘制功能并提供了绘制文本、分形和位图的函数。系统中所有的图形组件都依靠GDI,这点我们可以从下图(Figure 1)中看出。


在SymbianOS中,绘制是通过graphics contexts和graphics devices来完成的,这里GDI提供了一个抽象的graphics context类,CGraphicsContext,它是所有graphics contexts的基类。它定义了drawing settings,象pen和brush style,并提供了方法给应用程序以使用GDI的图形功能。而实际的绘制是在graphics device中完成的,其基类为CGraphicsDevice,which specifies the attributes of a device the drawing is assigned to.如下图所示(Figure 2),它演示了从graphics contexts和graphics devices中继承的类。


这些具体的context和device类都是在BITGDI中完成的,which is a screen and bitmap-speicific graphics component.它在汇编级进行过高度优化,可以保证很快的图形绘制。The BITGDI implements rasterizing and rendering of images and it supports drawing in on- and off-screen bitmaps.

6.2 Drawing Basics
我们可以使用CWindowGc提供的方法来绘制控件,控件的基类为CCoeControl,而应用程序的view也是一个控件。CWindowGc可以通过访问CCoeControl::SystemGc来得到。它也可以使用外部控件的CWindowGc——通过调用CEikkonEnv::Static()->SystemGc(),but usually drawing should be done within the controls context.

为了得到屏幕的属性,如显示模式,可以通过调用CWsScreenDevice的功能函数来获得,它可以通过访问CWindowGc:evice来获得。

每个从CCoeControl派生的控件都有一个Draw方法。它可以完成控件的绘制,下面的代码做了演示:
1. void CMyGameView:raw(const TRect& /*aRect*/ ) const
2. {
3.    //Get the system graphics context
4.    CWindowGc& gc = SystemGc();
5.    //Set drawing settings
6.    gc.SetBrushStyle(CGraphicsContext::ESolidBrush);
7.    gc.SetBrushColor(KRgbRed);
8.    //Draw
9.    gc.DrawLine(TPoint(10, 10), TPoint(30, 10));
10. }
复制代码
这里的TRect参数定义了可以进行绘制的有效区域,the given rectangle is ignored due to the fact that it is much simpler and in most cases, especially in games, faster to redraw the whole control.

Draw方法是被framework所调用的,你不能直接调用它,那在什么情况下会调用这个Draw那:
(1)创建一个窗口时
(2)当其他事件发生时窗口的内容变的无效(如有了重载的窗口)
(3)CCoeControl:rawNow或CCoeControl: rawDeferred可以直接调用它。DrawNow直接进行绘制,而DrawDeferred则仅仅标记control区域为无效,最后由window server来完成重绘。

在游戏中,屏幕通常在短时间内就会得到更新,因此最好使用DrawDeferred方法来重绘屏幕,它允许,如,用户的输入得到适当的处理,因为redraw event在一个较低的优先级。

你要知道的是,在每次调用DrawDeferred后并不能立即调用Draw方法,因为多个DrawDeferred的执行过程是这样的,首个DrawDeferred调用后如果系统还没有重绘的话,那后继的DrawDeferred是无效的。这就要注意了。

我们也可以不经过draw event而绘制一个控件,但这需要额外的步骤(而这些步骤本来是由框架调用Draw方法时自动完成的):
1. Activate the graphics context with CWindowGc::Activate.
2. Notify the window server about the drawing with RWindow::BeginRedraw.
3. Draw on the control.
4. Notify the window server when drawing is finished with RWindow::EndRedraw.
Deactivate graphics context with CWindowGc: eactivate.

下面的代码指示了上面的步骤
1. void CMyGameView::MyDrawMethod()
2. {
3.   // Get the system graphics context
4.   CWindowGc& gc = SystemGc();
5.   // Begin drawing
6.   gc.Activate(Window());
7.   // Window().Invalidate(); // for backed-up windows
8.   Window().BeginRedraw();
9.   // Set drawing settings
10.   gc.SetBrushStyle( CGraphicsContext::ESolidBrush );
11.   gc.SetBrushColor( KRgbRed );
12.   // Draw
13.   gc.DrawLine( TPoint(10,10), TPoint(30,10) );
14.   // End drawing
15.   Window().EndRedraw();
16.   gc.Deactivate();
17. }
复制代码
6.3 Text and Fonts
实际上每个游戏中设计者都需要在屏幕上显示文字,在游戏中,字体通常要被设定和游戏风格想匹配,SymbianOS中提供了一组标准的字体,也可以生成并使用你自己的位图字体。在系统中每种字体只能有一个实例存在,并且在系统所有的线程中共享。这样可以节约内存。

要使用一个特定的字体绘制文本,你必须:
1. Construct a CFont object by querying a font with the given parameters from the screen device (CGraphicsDevice::GetNearestFontInTwips).

2. Before drawing text, set the graphics context’s font to a constructed one. (CWindowGc::UseFont)

3. When the font is no longer needed, it can be released with CGraphicsDevice::ReleaseFont. Before releasing the font, it must be released from the graphics context (CWindowGc: iscardFont).

下面的代码生成了一个新的字体,绘制了文本,并释放了该字体:
1. void CMyGameView::Draw( const TRect& /*aRect*/ ) const
2. {
3.   CWindowGc& gc = SystemGc();
4.   // Get smallest possible arial font
5.   _LIT(KMyFontName,"Arial");
6.   CFont* myFont;
7.   TFontSpec myFontSpec(KMyFontName,1);
8.   CGraphicsDevice* screenDevice = iCoeEnv->ScreenDevice();
9.   screenDevice->GetNearestFontInTwips(myFont,myFontSpec);
10.   // Use new font
11.   gc.UseFont(myFont);
12.   // Draw some text
13.   _LIT(KMyText,"My text");
14.   gc.DrawText(KMyText, TPoint(40, 40) );
15.   // Discard and release the font
16.   gc.DiscardFont();
17.   screenDevice->ReleaseFont(myFont);
18. }
复制代码
实际上一般是在ConstructL中生成font,而在destructor中释放。

TFontSpec指定了font的属性:
(1)Height of the font
(2)Typeface(TTypeFace); for example, “Roman”, “Courier”. Each typeface has symbol, serif and proportional attributes.
Font style (TFontStyle); posture (italic), weight (bold) and print position (subscript, superscript.)
(3)Font style (TFontStyle); posture (italic), weight (bold) and print position (subscript, superscript.)

当询问关于一个字体的属性时,将返回和给定属性最为接近的那个字体。

CEikonEnv也有一组函数可以获得该环境中具体字体的信息,如CEikonEnv::TitleFont就返回了title font。要找到字体库中可用的字体,那可以使用CGraphicsDevice::NumTypeFaces获得字体的数目,然后再用CGraphicsDevice::TypeFaceSupport获得具体字体的信息。
1. void CMyGameView:istMyFontsL()
2. {
3.   // Find out the number of typefaces
4.   TInt iNumTypefaces = iCoeEnv->ScreenDevice()->NumTypefaces();
5.   // Show name of each available font
6.   TBuf<64> myFontName;
7.   TBuf<128> text;
8.   for(TInt t = 0; t < iNumTypefaces; t++)
9.  {
10.    // Get font name
11.    TTypefaceSupport myTypefaceSupport;
12.    iCoeEnv->ScreenDevice()->TypefaceSupport(myTypefaceSupport, t);
13.    myFontName = myTypefaceSupport.iTypeface.iName.Des();
14.    _LIT(KMyText, "Font %d/%d:");
15.    text.Format(KMyText, t, iNumTypefaces);
16.    iEikonEnv->InfoWinL(text, myFontName);
17.   }
18. }
复制代码
在游戏中有时候是需要生成一个特定字体的,特定字体的绘制程序为,将位图中代表各个字的image传输到屏幕上,这是个有效而快速的方法,这样你可以现实各种字体:)不过在完成时要花费时间。

你也可以生成一个新的SymbianOS字体,这样就可以使用现存的字体和图形APIs了,要生成这样的字体,你应该:
(1)生成一个font definition file;如MyFont.gd
(2)生成Adobe BDF(Bitmap Distribution Format) font bitmaps,如,MyFont.bdf
(3)Add required, Symbian OS specific properties to the BDF file if they are missing.
(4)使用fnttran工具来生成symbian系统字体存储文件,如,MyFont.gdr.(如fnttran MyFont.bdf MyFont.gd MyFont.gdr)
(5)拷贝生成的字体文件,MyFont.gdr到/system/fonts文件中(我们可以把文件放在pkg中来生成安装文件),而对模拟器来说只要拷贝该文件到fonts目录即可。

字体定义文件(MyFont.gd)可能看起来如下:
1. Typeface tf_MyFont
2.   Name "My Font" Proportional
3.   FontBitmaps
4.    MyFont // Name of the font in BDF file
5.   EndFontBitmaps
6. EndTypeface
7. 
8. FontStoreFile
9.   CollectionUid 123456789 // Uid of the font; must be unique
10.   KPixelAspectRatio 1000
11.   CopyrightInfo
12.    "MyCopyrightInfo"
13.   EndCopyrightInfo
14.   Typefaces
15.    tf_MyFont
16.   EndTypefaces
17. EndFontStoreFile
复制代码
当使用BDF格式时,各种实用函数都可以用来生成该字体。BDF文件通过Windows bitmaps或特定字体编辑工具来生成,或者自己手动来生成这个文件。因为基本说来它是一个文本文件,不过要手动来做的话很费事。

When the BDF file is created, it usually requires a few modifications. These properties must exist in the BDF file (between STARTPROPERTIES and ENDPROPERTIES statements):
Uid uid (not the same as CollectionUid.)
MaxNormalWidth width.
MaxConsecutiveFillChars max.
Bold [1/0].
Italic [1/0].
FONTASCENT ascent.
FONTDESCENT descent.

Typically Uid and MaxNormalWidth properties are missing from the BDF file. fnttran will notify you if some of the values are missing.

一切就绪后,我们可以这样来使用新字体:
1. CFont* CMyGameView::GetMyOwnFont()
2. {
3.   _LIT(KMyFontName,"MyFont");
4.   CFont* myFont;
5.   TFontSpec myFontSpec(KMyFontName,1);
6.   CGraphicsDevice* screenDevice = iCoeEnv->ScreenDevice();
7.   screenDevice->GetNearestFontInTwips(myFont,myFontSpec);
8.   return myFont;
9. }
复制代码
字体文件可以拷贝到任何盘下(C:/system/fonts, E:/system/fonts),而设备则需要一个soft reboot才能使用它们。也可以动态的来安装字体,使用CWsScreenDevice::AddFile,这样就可以防止重启的需要。

6.4 Bitmaps
在SymbianOS中,使用位图的首选方法是生成MBM(multi-Bitmap文件),然后可以在运行时来访问位图。MBM可以在一个单独的文件中包含多个给定的位图。An MBM can either be a file store or a ROM image type. The ROM image MBM is not compressed and thus accessing the MBM does not consume RAM. File store MBMs are compressed (by default), and accessing the file store MBMs consumes memory due to the needed decompression. The default type of MBMs is file store.

MBMs可以使用位图转换工具bmconv来生成。可以直接从命令行运行,也可以放在工程文件mmp中。在工程文件中,格式应如下:
// MyGame.mmp
START BITMAP MyGame.mbm
HEADER
TARGETPATH ../../../../wins/c/system/apps/MyGame
SOURCEPATH ../MyBitmaps
// color-depth source-bitmap
SOURCE c12 image1.bmp
SOURCE c12 image2.bmp
SOURCE c12 image3.bmp
END

 

每个位图都会基于如上的定义而生成一个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.
1. #include <MyGame.mbg> // generated on compilation
2. #include <aknutils.h> // for CompleteWithAppPath()
3. CFbsBitmap* CMyGameView:oadMyBitmapL()
4. {
5.   // set the name of the multi-bitmap file containing the bitmaps
6.   _LIT(KMBMFileName,"MyGame.mbm");
7.   TFileName mbmFileName(KMBMFileName);
8.   CompleteWithAppPath(mbmFileName);
9.   // load the bitmap from the mbm file
10.   CFbsBitmap* bitmap = new (ELeave) CFbsBitmap();
11.   CleanupStack:ushL(bitmap);
12.   // EMbmMygameImage1 is enumerated value from MyGame.mbg file
13.   User:eaveIfError(bitmap->Load(mbmFileName, EMbmMygameImage1));
14.   CleanupStack:op(); // bitmap
15.   return bitmap;
16. }
复制代码
当位图装入时,可以将它显示出来,如BitBlt
1. void CMyGameView:raw(const TRect& /*aRect*/) const
2. {
3.    //Get the system graphics context
4.    CWindowGc& gc = SystemGc();
5. 
6.    //Draw the bitmap
7.    gc.BitBlt(TPoint(10, 10), iMyShipBitmap);
8. }   
复制代码
我们有时候要需要使用mask,mask是两色的位图(因此1bit位图即可解决),我们可以使用BitBltMasked:
1. void CMyGameView:raw( const TRect& /*aRect*/ ) const
2. {
3.   // Get the system graphics context
4.   CWindowGc& gc = SystemGc();
5.   // Draw masked bitmap
6.   gc.BitBltMasked( TPoint(10, 10), iMyShipBitmap, iMyShipRect, iMyShipMask, EFalse);
7. }
复制代码
如果应用程序要画一个位图到窗口中,那它应该转换和该窗口一致的显示模式,而这将花费操作的时间而导致绘制变得缓慢,因此images应该在装入时就转换为正确的颜色深度。比如在游戏的初始化时或某个关卡的开始处,通常都生成一个包容器来处理所有需要使用的位图的装入、转换和存储。

转换应该使用一个临时的位图,代码如下:
1. CFbsBitmap* CMyGameView:oadAndConvertBitmapL(Const TDesC& aFileName, TInt aBitmapId )
2. {
3.   // Load the original bitmap
4.   CFbsBitmap* originalBitmap = new ( ELeave ) CFbsBitmap();
5.   CleanupStack:ushL( originalBitmap );
6.   User::LeaveIfError( originalBitmap->Load( aFileName, aBitmapId, EFalse ) );
7.   // Create a new bitmap, graphics device and context
8.   CFbsBitmap* newBitmap = new ( ELeave ) CFbsBitmap();
9.   CleanupStack::PushL( newBitmap );
10.   newBitmap->Create( originalBitmap->SizeInPixels(), Window()->DisplayMode() );
11.   CFbsBitmapDevice* graphicsDevice = CFbsBitmapDevice::NewL( newBitmap );
12.   CleanupStack::PushL( graphicsDevice );
13.   CFbsBitGc* graphicsContext;
14.   User::LeaveIfError( graphicsDevice->CreateContext( graphicsContext ) );
15.   // Blit the loaded bitmap to the new bitmap (the actual
16.   // conversion)
17.   bitmapContext->BitBlt( TPoint(0,0), originalBitmap );
18.   CleanupStack::Pop(3);
19.   delete bitmapContext;
20.   delete bitmapDevice;
21.   delete originalBitmap;
22.   return newBitmap;
23. }
复制代码
上面的代码表示装入一个适当的位图(从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:ataAddress方法,它可以返回指定位图的指针(指向位图左上角的指针),在操作时我们要有一定的预防措施,因为在运行时由于内存的防碎片处理而可能导致位图的地址发生变化,这就意味着堆中的位图数据区必须锁定起来,在访问前我们要使用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位图里)
1. void CMyGameView::DoMyBitmapEffect(CFbsBitmap* aBitmap)
2. {
3.   // Lock heap
4.   // For series 60 2.0 use:
5.   // aBitmap->LockHeap();
6.   // For series 60 1.0 use:
7.   TBitmapUtil bitmapUtil(aBitmap);
8.   bitmapUtil.Begin( TPoint(0,0) );
9.   // Edit bitmap
10.   TSize bitmapSize = aBitmap->SizeInPixels();
11.   // NOTE: TUint16* applies to 16bit bitmaps only; the pointer must
12.   // correspond the bit depth of the bitmap.
13.   TUint16* bitmapData = (TUint16*)aBitmap->DataAddress();
14.   for ( TInt y = 0; y < bitmapSize.iHeight; y++ );
15.  {
16.    for ( TInt x = 0; x < bitmapSize.iWidth; x++ )
17.   {
18.     // Increase colour value of each pixel by one
19.     *bitmapData = ( *bitmapData & 31 ) | // blue
20.            ( ( *bitmapData >> 5 ) & 63 ) | // green
21.            ( ( *bitmapData >> 11 ) & 31 + 1 ); // red这样才能单独为red进行操作:)
22.     bitmapData++;
23.    }
24.   }
25.   // Unlock heap
26.   // For series 60 1.0 use:
27.   bitmapUtil.End();
28.   // For series 60 2.0 use:
29.   // aBitmap->UnlockHeap();
30. }
复制代码
从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).
江湖在心里,心有多大,江湖就有多大!

 回复 引用
   TOP

 
高原梦
 
渐入佳境
 
UID
43430
积分
291
学分
24 
来自
北京-北京市  3#
 发表于 2010-4-16 11:28 | 只看该作者
下面的代码演示了如何从MBM装入一个位图,并由此生成一个sprite,以及动画的处理。
1. // .h
2. RWsSprite iMySprite;
3. TSpriteMember iMySpriteMembers[7];
4. // .cpp
5. void CMyGameAppView::ConstructL(const TRect& aRect)
6. {
7.   CreateWindowL();
8.   SetRect(aRect);
9.   // The mbm contains:
10.   // SpriteImage1.bmp
11.   // * Every second image is a mask.
12.   // SpriteImage1Mask.bmp
13.   // . . .
14.   _LIT(KMySpriteMBM, "MySprite.mbm");
15.   TFileName mbmFileName(KMySpriteMBM);
16.   CompleteWithAppPath(mbmFileName);
17.   // Construct my sprite
18.   iMySprite = RWsSprite( CEikonEnv::Static()->WsSession() );
19.   User::LeaveIfError( iMySprite.Construct( Window(), TPoint(0,0), 0 ) );
20.   // Load bitmaps (image+mask) and set properties for each member;
21.   // 7 members in total (7 images + 7 masks)
22.   for ( TInt i = 0; i < 7; i ++ )
23.  {
24.    // Load image
25.    iMySpriteMembers.iBitmap = new ( ELeave ) CFbsBitmap();
26.    User::LeaveIfError( iMySpriteMembers.iBitmap->Load( mbmFileName, i * 2, EFalse ) );
27.    // Load mask for the image
28.    iMySpriteMembers.iMaskBitmap = new ( ELeave ) CFbsBitmap();
29.    User::LeaveIfError( iMySpriteMembers.iMaskBitmap->Load( mbmFileName, i * 2 + 1, EFalse ) );
30.    // Set properties for the member
31.    iMySpriteMembers.iInvertMask = EFalse;
32.    iMySpriteMembers.iOffset = TPoint(0,0);
33.    // Change image every 1/10 seconds
34.    iMySpriteMembers.iInterval = TimeIntervalMicroSeconds32(100000);
35.    // Append created member to sprite
36.    User::LeaveIfError( iMySprite.AppendMember( iMySpriteMembers ) );
37.   }
38.   // All members added. Activate the sprite; the sprite will be
39.   // drawn/animated continuously to view until sprite is destroyed
40.   // with iMySprite.Close()
41.   User::LeaveIfError(iMySprite.Activate());
42.   ActivateL();
43. }
44. 
45. 当sprite构造成功并激活后,它会自动的重绘和运动。唯一所要做的事情就是改变sprite的位置:
46. void CMyGameAppView::MoveMySpriteTo(const TPoint& aPos)
47. {
48.   iMySprite.SetPosition(aPos);
49. }
复制代码
有时候需要改变位图的内容,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)中。

参见例子:
1. void CMyGameView::ConstructL(const TRect& aRect)
2. {
3.   .
4.   .
5.   .
6.   // Create a new bitmap with size of view’s rect and color depth of
7.   // screen
8.   TDisplayMode displayMode = CEikonEnv::Static()->ScreenDevice()->DisplayMode();
9.   iBackBufferBitmap = new(ELeave) CFbsBitmap();
10.   User::LeaveIfError(iBackBufferBitmap->Create(Rect().Size(), displayMode));
11.   // Create bitmap device for the bitmap
12.   iBackBufferDevice = CFbsBitmapDevice::NewL(iBackBufferBitmap);
13.   // Create graphics context for the bitmap
14.   User::LeaveIfError(iBackBufferDevice.CreateContext(iBackBufferGc));
15.  }
16. 
17. 
18. CMyGameView::~CMyGameView()
19. {
20.   delete iBackBufferGc;
21.   delete iBackBufferDevice;
22.   delete iBackBufferBitmap;
23.  }
24. 
25. // Called by e.g. timer to update the screen periodically.
26. // Here all the necessary drawing is done to backbuffer.
27. void CMyGameView::UpdateDisplay()
28. {
29.   // Draw some background
30.   iBackBufferGc->BitBlt(TPoint(0, 0), iMyBackgroundBitmap);
31.   // Draw something else here onto backbuffer
32.   .
33.   .
34.   .
35.   // When drawing to backbuffer is done, update the view
36.   DrawDeferred();
37.  }
38. 
39. void CMyGameView::Draw(const TRect& /*aRect*/) const
40. {
41.   CWindowGc& gc = SystemGc();
42.   // Just draw the backbuffer to view
43.   gc.BitBlt(Rect().iTl, iBackBufferBitmap);
44.  }
复制代码
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直接操作到屏幕上。

更快点的方法是访问屏幕内存地址,并通过一个指针来直接操作:
1. void CMyGameView::FillScreenDirectly() const
2. {
3.   TPckgBuf<TScreenInfoV01> infoPckg;
4.   TScreenInfoV01& screenInfo = infoPckg();
5.   UserSvr::ScreenInfo(infoPckg);
6.   TUint16* screenMemory = (TUint16*) screenInfo.iScreenAddress + 16;
7.   for(TInt y = 0; y < screenInfo.iScreenSize.iHeight; y++)
8.  {
9.    for(TInt x = 0; x < screenInfo.iScreenSize.iWidth; x++)
10.   {
11.      *screenMemory++ = 0;
12.    }
13.   }
14. }
复制代码
屏幕内存有一个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.
1. void CMyGameView::MyDrawing()
2. {
3.   #ifdef __WINS__
4.   // Draw to bitmap
5.   TUint16* myScreenPointer = iMyBitmap.DataAddress();
6.   #else // Hardware environment
7.   // Draw directly to the screen memory
8.   TUint16* myScreenPointer = GetMyScreenAddress();
9.   #endif
10.   DoMyDrawing(myScreenPointer);
11. }
复制代码
使用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:
1. // Inherited from MDirectScreenAccess
2. [code]void CMyGameView::Restart(RDirectScreenAccess::TTerminationReasons aReason)
3. {
4.   // Usually just restart direct screen accessing
5.   TRAPD(err, iMyDrawer->StartL());
6.   if(err != KErrNone)
7.  {
8.    // Error; cannot restart
9.   }
10. }
11. 
12. // Inherited from MDirectScreenAccess; called when it’s needed to
13. // abort direct screen access immediately
14. void CMyGameView::AbortNow(RDirectScreenAccess::TTerminationReasons aReason)
15. {
16.    // Stop direct screen access immediately
17.    // e.g. dialog has become visible on screen
18. }
19. 
20. // Construct CDirectScreenAccess
21. void CHelloWorldBasicAppView::CreateMyDrawerL()
22. {
23.   delete iMyDrawer;
24.   iMyDrawer = NULL;
25.   iMyDrawer = CDirectScreenAccess::NewL( iEikonEnv->WsSession(), *iEikonEnv->ScreenDevice(), Window(), *this);
26.   iEikonEnv->WsSession().Flush();
27.   iMyDrawer->StartL();
28.   iMyDrawer->ScreenDevice()->SetAutoUpdate(ETrue);
29. }
30. 
31. // Draw backbuffer bitmap to screen using CDirectScreenAccess
32. void CMyGameView::DisplayBackBuffer() const
33. {
34.   iMyDrawer->Gc()->BitBlt( TPoint(0,0), iMyBackBuffer );
35. }
复制代码
[/code]
在我们调用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事件得到处理前不会去绘制重叠的窗口。

 

6.9 Hardware Acceleration
S60平台2.0后(i.e Symbian OS 7.0)提供了基于硬件的2D图形硬件加速API,图形加速已经集成到已存的Graphics API,程序直接用即可。

硬件加速API是一组操作,包括硬件的加速(如一个独立的图形处理器)和软件的加速(对绘制算法的优化)。

除了现存的Graphics APIs,2D硬件加速API还有如下新的内容:
1)Hardware bitmaps(RHardwareBitmap), CFbsBitmap has been extended with CreateHardwareBitmap and HardwareBitmapHandle methods. RHardwareBitmap provides an interface to created hardware bitmap for the framework.

2)Masked, transparent, alpha blended and copy blits. (TGopBitBltMasked, TGopBitBltTransparent, TGopBitBltAlphaBitmap, TGopBitBltAlphaChannel, TgopBitBlt.)

3)Fade effects for a given area. (TGopFadeRect, TgopFadeParams.)

4)Filled polygons with solid color or pattern. (TGopFilledPolygon, TgopFilledPolygonWithPattern.)

5)Filled rectangles with solid color, pattern or bitwise operations. (TGopFilledRect, TGopFilledRectWithPattern, TgopFilledRectUsingDrawMode.)
江湖在心里,心有多大,江湖就有多大!

 回复 引用
   TOP

 
高原梦
 
渐入佳境
 
UID
43430
积分
291
学分
24 
来自
北京-北京市  4#
 发表于 2010-4-16 11:29 | 只看该作者
有两个从抽象类CGraphicsAccelerator中继承的类,提供了如下的操作:
1、CHardwareGraphicsAccelerator: Provides graphics operations with a given RHarwareBitmap. The operations are implemented in hardware, software or a mixture of both – depending on the device’s capabilities.
2、CSoftwareGraphicsAccelerator: Provides purely software implementations of graphics operations. This might be useful to implement in existing code since it allows operations to a plain non-hardware CFbsBitmap.

下面上个简单操作的例子:
1. void CMyGameView::MyHwBlitL(CFbsBitmap* iMySourceBitmap)
2. {
3.   CFbsBitmap* iMyBitmap = new (ELeave) CFbsBitmap();
4.   CleanupStack:ushL(iMyBitmap);
5.   iMyBitmap->CreateHardwareBitmap(Rect().Size(), EColor64K, KUidMyGameApp); // 16-bit color depth assumed
6.   // Construct hardware bitmap from the fbsbitmap
7.   RHardwareBitmap iMyHwBitmap(iMyBitmap->HardwareBitmapHandle());
8.   // Create accelerator for the hardware bitmap.
9.   CHardwareGraphicsAccelerator* iMyHwAccel = CHardwareGraphicsAccelerator::NewL(iMyHwBitmap);
10.   // Create bit blit operation.
11.   TGopBitBlt myBlitOperation(TPoint(0, 0), // Destination coords
12.   TAcceleratedBitmapSpec(iMySourceBitmap), // Source
13.   // Rectangle within the source to be blitted
14.   TRect(0, 0, 40, 40) );
15.   // Execute the operation; the defined area from
16.   // iMySourceBitmap is copied into iMyBitmap
17.   iMyHwAccel->Operation(myBlitOperation);
18.   CleanupStack:opAndDestroy();
19. }
复制代码
7 Sounds
在symbianOS中使用media server来播放和管理声音,这个media server支持各种各样的audio文件格式,如wav, au和wve,还提供给应用程序以API来为该server开发其他的文件格式插件。The media server的客户端API(在2.0中i.e Multi Media Framework)提供给游戏两个重要的特性:
1)、CMdaAudioPalyerUtility:提供了装入和播放的功能,以及设置音量,均衡等。这个类只能和一个单独的示例数据联系在一起,因此需要生成CMdaAudioPlayerUtility的实例,Playback is possible from memory via a descriptor or directly from file.要播放的audio data必须是wav, au 或RAW格式的。在s60 2.0中你可以重复使用那个实例,因为你可以在构建完后再装入一个新的文件。

2)、Audio转换,CMdaAudioConvertUtility:允许audio clips从一个格式转到另一个。

在播放上四,你需要有一个类完成MMdaAudioPlayerCallback的方法以接收回放事件:
1)MMdaAudioPlayerCallback::MapcInitComplete:当sample data已经初始化后由该framework调用,如,装入文件。
2)MMdaAudioPlayerCallback::MapcPalyComplete:当playback已经完成时。

下面的代码演示了一个使用CMdaAudioPlayerUtilty类的事例:
1. // Called when initialization done; e.g. sample loaded
2. void CmySamplePlayer::MapcInitComplete(TInt aError, const TtimeIntervalMicroSeconds& aDuration)
3. {
4. }
5. 
6. // Called when playback completed
7. void CmySamplePlayer::MapcPlayComplete(TInt aError)
8. {
9. }
10. 
11. void CmySamplePlayer:layMySampleL()
12. {
13.   // Create a sample player and load a sample from a file
14.   _LIT(KmySampleName,”MyClip.wav”);
15.   TFileName sampleFileName(KmySampleName);
16.   CompleteWithAppPath(sampleFileName);
17.   delete iMySamplePlayer;
18.   iMySamplePlayer = NULL;
19.   iMySamplePlayer = CmdaAudioPlayerUtility::NewFilePlayerL( sampleFileName, *this );
20.   // Play the sample
21.   iMySamplePlayer-&gtlay();
22. }
复制代码
在series60中,为应用程序的每个键都准备了个缺省的声音。声音会因为按键的长短或重复而不同。series60里CAknAppUi,提供了可以更改他们键缺省音的方法,在资源文件里更改:
1. RESOURCE AVKON_SKEY_LIST r_example_skey_list
2. {
3.   list =
4.   {
5.     AVKON_SKEY_INFO
6.    {
7.      key=EstdKeyLeftArrow;
8.      sid=EAvkonSIDNoSound;
9.     },
10.     AVKON_SKEY_INFO
11.    {
12.      key=EstdKeyLeftArrow;
13.      sid=EAvkonSIDNoSound;
14.      type=ESKeyTypeLong;
15.     },
16.     AVKON_SKEY_INFO
17.    {
18.      key=EstdKeyLeftArrow;
19.      sid=EAvkonSIDNoSound;
20.      type=ESKeyTypeRepeat;
21.     }
22.    };
23. }
复制代码
这里sound ids,SIDs,要在avkon.hrh头文件中指定,在游戏里,如果一个键可能会长时间按下,那重复的声音可以通过上面的方法来取消,就是指定这个key event's sound ID为EAvkonSIDNoSound.这是因为每次一个key repeat event被接收到后播放重复的声音都会花费一段处理时间。如果游戏中需要一个连贯的声音,那应该使用audio sample player。

8 Networking and Communication
S60为多人game提供了各种连接通讯方法,下面就是建议使用的各种方法:
1)Bluetooth - 当前最具实用性的方法,因为大部分新机器都支持蓝牙
2)TCP/IP sockets - 通过互联网,如GPRS
3)IrDA - 用的比较少,现在一般被蓝牙所替代了,有的新设备根本不提供红外的支持。

连接的速度在game中一般不是那么要求严格的,因为要传输的数据量很小(通常game的状态不会耗费多少空间,如对象的位置),上面所说的几种方法都能提供很好的连接方式。

8.1 Designing Communication Framework
要开完成多人game,你必须设计一个框架,它选择了某种网络方式。For all networking methods – Bluetooth, TCP/IP and IrDA – a common, single design pattern can be used by utilizing Active Objects.

联网需要多个active object来提供异步的操作,通常游戏中一个比较简单的、同步的操作是不可能的,or not desired due to blocking of execution.典型的联网结构需要使用3个活动对象:
1)Reader
处理传进来的数据并将它传递到Controller,这时不需要分解数据(only low level protocol parsing, if any)。

2)Writer
处理输出的数据,由Controller将数据传递到网络(only low lever parsing)

3)Controller
在设备之间建立连接,拥有并使用Reader和Writer来传递数据-提供high level protocol implementation,那就是数据怎么样被传输,以及按什么样的格式被传输。

这三个活动对象通常都包括它们自己的状态机制,而Controller可以包括多个状态机制,以用来提供各种不同的连接状态(connecting, connection established, sending data, receiving data)以及传输状态(data block 1 send, ack received, data block 2 sent, expecting reply, response received)。Usually the communication between the devices in games is simple, and does not require a transaction state:
1)、Device 1 sends data to Device 2 in a single block
2)、Device 2 sends data to Device 1 in a single block. (这个可能为前一个消息的回应,Device 1实际上并不是在等待回应,只是在一个消息被收到时的反应。)

game engine using the networking owns the Controller, and communication between the engine and the Controller is done typiclly via callback mechanism. Controller应该提供一个简单的接口,而用户对Controller的内部机制功能并不需要知道太多。

如果是使用Bluetooth/IrDA的话,那么完成一次最简单的联网只要四个方法:
1)CmyController::SeekDevice
寻找Bluetooth/IrDA的设备并通过callback得到可用设备的列表。
2)CmyController::ConnectToDevice
连接到给应的设备上,通过callback来通知连接成功或失败。
3)CmyController::SendMessage
通过连接远程设备来发送message(data)
4)CmyController:isconnect
和远程设备断开

对TCP/IP来说SeekDevices并不需要,但是其他的都是一样的。

所有上面的方法,除了Disconnect,都是异步的,而Controller会通过callback将结果传递会game engine.
1)MmyController::Event
a general notification of an error(connection failed), success(connected ok) or just informaional(for displaying the progress of the current operation, such as connection attempt). The controller应该从错误中尝试恢复。而这个事件只是用来通知用户和game engine,尽量的要简单直接。game engine对连接的controller应该尽量的所知甚少。

2)MmyController::MessageReceived
一个信息从远程设备中获取(如游戏状态),The message is parsed by the Controller and then passed to game engine with this callback.

Bluetooth, IrDA, TCP/IP都可以通过一般的socket client API(RSocket)来使用:
1)Bluetooth sockets API provides communication over L2CAP and RFCOMM layers, and provides discovery of Bluetooth devices.
2)IrDA sockets API, with IrMUX and IrTinyTP protocol sets. By using IAS, you can create service announcements/queries and even communicate with Windows OS based devices.
3)TCP/IP sockets provide domain name resolutions, Ipv4, and in Series 60 Developer Platform 2.0, Ipv6.

8.2 Game Data Receiving
有时候需要,如在游戏安装后更新和添加新的关卡。这时需要的文件可以通过email,或IrDA甚至MMS来获得。在S60中,我们可以将该文件的类型注册,这样获得的文件就可以自动的进入正确的目录,一边游戏能找到它们。

为了使game支持额外的game data,S60开发平台允许第三方游戏使用Multipurpose Internet Mail Extensions, MIME类型将他们的数据文件格式到系统中,MIME类型被一些连接程序所使用,象WML浏览器和消息应用程序,为了找出一个文件的路径,必须要存储特定的类型,这样的文件可以存放新的关卡,武器或游戏图片。

在S60中游戏的MIMI类型格式为:application/x-NokiaGameData-<APPLICATION-ID>,这里<APPLICATION-ID>是game的symbian OS application UID的最后8位。这个MIME类型可以放在symbian OS应用程序信息文件,aif文件中描述:
1. RESOURCE AIF_DATA
2. {
3.   app_uid=0x12345678; // Application UID
4.   datatype_list =
5.  {
6.    DATATYPE
7.    {
8.      priority = EdataTypePriorityHigh;
9.      type = “application/x-NokiaGame-Data-12345678”;
10.     }
11.   };
12. }
复制代码
这里DATATYPE中的优先级指明了当前应用程序是如何处理数据格式的。EdataTypePriorityHigh should be used for data formats which can not be handled by other applications.

接受到的数据文件的目标路径应该在<APPLICATION-ID>.ini中找到。这个文件需要以unicode格式来存储。它包括SDDataDir=<GAME-PATH>,这里<GAME-PATH>是文件的接收路径。The path is relative to the default game data directory, c:/nokia/games,当游戏安装后,ini文件需要拷贝到/System/ShareData目录下,这个可以通过在pkg文件中指定路径来处理。

这里Series60开发平台指定了一个standard header structure for game data files,它需要加入到MIME类型中才能工作。The Data type field can be used to specify the type of a file that is internal for the game. Name String是一个unicode string,可以使用户在菜单中选择数据的项目。数据的ID和数据的版本都是唯一的数字,可以指定文件数据的类型和版本号。The NGDX字段必须包括ASCII字符

串"NGDX"
(见Figure 8图)


9 安装
9.1 应用程序信息文件
应用程序的图标是任何应用程序在安装后第一个可见的部分,这个icon定义在AIF中,应用程序信息文件为应用程序定义了如下的特性:
1)Application icon
2)程序在系统shell中是否可见
3)程序是否和指定的MIME类型联系在一起
4)应用程序是否可以在system shell中生成新的文档
5)应用程序是内嵌的吗

AIF文件可以定义在工程的mmp文件:
aif MyGame.aif /MyGame/aif MyGameAif.rss c8 Icon1.bmp Icon1Mask.bmp
语法为:
aif <applications aif file> <aif file location> <aif resources> <icon bit depth> <icon 1> <iconmask 1> <icon2> <iconmaks2>

...

一个应用程序可以有多个图标以适应不同的场合,system shell会自动选择合适尺寸的icon进行必要的显示。一般application的icon尺寸为44x44,8-bit color,此外你可以提供其他的icon.

为aif准备的资源文件rss看起来如下:
1. #include <aiftool.rh>
2. RESOURCE AIF_DATA
3. {
4.   // application uid
5.   app_uid=0x1000ABCD;
6.   // Application caption for each language
7.   caption_list=
8.   {
9.     CAPTION
10.     {
11.        code = ElangEnglish;
12.        caption = “MyGame”;
13.      }
14.    };
15. // Datatypes associated with the application
16.  datatype_list =
17.  {
18.     DATATYPE
19.     {
20.       priority = EdataTypePriorityHigh;
21.       type = “application/x-NokiaGame-Data-12345678”;
22.     }
23.    };
24.   // Number of icons in AIF (does not include masks; each icon must
25.   // have a mask)
26.   num_icons=1;
27.   // Capabilities
28.   embeddability = KappNotEmbeddable;
29.   hidden = KappNotHidden;
30.   newfile = KappDoesNotSupportNewFile;
31. }
复制代码
如果你的程序包括一个aif,那你记得把那个生成的文件放入到sis package中,注意如果AIF的UID和你的应用程序的UID不匹配,那不会显示任何错误,但是安装后生成的应用程序的图标将是不可见的。

9.2 Installation File
pkg file定义了安装文件(sis)的内容,它包括应用程序的UID,一个支持的语言列表,目标产品的UID和打包在sis的一组文件:
; MyGame.pkg
; Specifies an installation file for MyGame
;Languages
&EN
;Header
#,(0x1000ABCD),1,0,0
; Required line for Series 60 devices. Defines the target product
; UID.
(0x101F6F88), 0, 0, 0,
“/epoc32/release/thumb/urel/MyGame.app”-“!:/system/apps/MyGame/MyGame.app”
“/epoc32/release/thumb/urel/MyGame.rsc”-“!:/system/apps/MyGame/MyGame.rsc”
“/epoc32/release/thumb/urel/MyGame.mbm”-“!:/system/apps/MyGame/MyGame.mbm”
“/epoc32/release/thumb/urel/MyGame.aif”-“!:/system/apps/MyGame/MyGame.aif”
“../MyGame/MyGameSample.wav”-“!:/system/apps/MyGame/MyGameSample.wav”

Product UID定义了应用程序的目标环境,大部分的s60版本是向下兼容的。
参见下表:
(Figure 9.1)


如果程序需要依据各不同的平台来进行安装,那就可以使用条件语句块来处理,这时pkg里的语句如下:
;
; Files to install
;
IF MachineUID=0x101fb3dd
; Nokia 6600 specific files
“../MyFiles/FileFor6600.dat”-“!:/system/apps/MyGame/MyData.dat”
ELSEIF MachineUID=0x101f466a
; Nokia 3650 specific files
“../MyFiles/FileFor3650.dat”-“!:/system/apps/MyGame/MyData.dat”
ELSE
; Files for other devices
“../MyFiles/FileForOthers.dat”-“!:/system/apps/MyGame/MyData.dat”
ENDIF

如上的使用你就可以生成一个支持多平台的安装文件,除了机器UID外,还有很多属性,如内存和CPU的标识:

注意,机器UID和Product UID是不同的,见下:
(Figure 9.2)


可以使用如下的代码来找出该设备的机器UID:
#include <hal.h> //and link with hal.lib
TInt machineUid = 0;
HAL::Get(HALData::EmachineUid, machineUid);
江湖在心里,心有多大,江湖就有多大!

 

原创粉丝点击