Symbian(俄罗斯方块)

谢选
2023-12-01

===========================================================

作者: jcszjswkzhou(http://jcszjswkzhou.itpub.net)

发表于: 2007.08.14 20:48

分类: 技术

出处: http://jcszjswkzhou.itpub.net/post/32804/373440

---------------------------------------------------------------

第一个程序是series60 SDK自带的hello world程序,在group目录下将有下面的工程文件,bld.inf、s60test.mmp、及由bldmake生成的abld.bat文件,有关编译的命令可以查看相关资料。

第一个程序是series60 SDK自带的hello world程序,在group目录下将有下面的工程文件,bld.inf、s60test.mmp、及由bldmake生成的abld.bat文件,有关编译的命令可以查看相关资料。

Groupstep1.rss是资源文件,在我们的例子中包含软键盘的定义(R_AVKON_SOFTKEYS_OPTIONS_EXIT-右边的选择键及左边的退出键)和选择菜单,还可以在后面加更多的资源.

Groupstep1.pkg描述如何去创建*.sis文件,*.sis是可以安装在手机中的文件。

inc和src包含程序的源代码,hello world在SDK中有详细的描述,我在这里主要讲一些主要的。

不像windows和UNIX程序,symbian程序没有带main()函数,可以像动态链接被系统装载,像其他每个动态链接它有e32dll函数,但是必须被迅速归还。

GLDEF_C TInt E32Dll(TDllReason /*aReason*/)

{

return KErrNone;

}

系统调用newapplication()函数得到新的CApaapplication对象,

EXPORT_C CApaApplication* NewApplication()

{

return (new CS60TestApplication);

}

在avkon(for series60)中返回一个CAknapplication子类的对象,在这个例子中是在s60testapplication.cpp实现的CS60testapplication,在每次执行中下面两个函数必须重载,第一个,在AppDllUid将返回该程序唯一的UID,我们例子中的UID不会出现在实际发布的程序中.

第二个函数是创建CApaDocument类对象的CreateDocumentL函数,

CApaDocument* CS60TestApplication::CreateDocumentL()

{

CApaDocument* document = CS60TestDocument::NewL(*this);

return document;

}

TUid CS60TestApplication::AppDllUid() const

{

return KUidS60TestApp;

}

在我们的例子中是CAknDocument的继承类CS60TestDocument

class CS60TestDocument : public CAknDocument

{

public:

static CS60TestDocument* NewL(CEikApplication& aApp);

static CS60TestDocument* NewLC(CEikApplication& aApp);

~CS60TestDocument();

CS60TestAppUi *iAppUi;

public: // from CAknDocument

CEikAppUi* CreateAppUiL();

private:

void ConstructL();

CS60TestDocument(CEikApplication& aApp);

};要重载CreateAppUil,此函数是用来建立用户接口响应对象的,

CEikAppUi *CS60TestDocument::CreateAppUiL()

{

iAppUi=new(ELeave) CS60TestAppUi(this);

return iAppUi;

}

在我们的例子中这是由类CS60TestAppUi实现的,在该类中ConstructL函数首先调用BaseConstructL函数进行初始化,从资源中装载软键盘和菜单定义,

void CS60TestAppUi::ConstructL()

{

BaseConstructL();

iAppView=CS60TestAppView::NewL(ClientRect(), iDoc);

AddToStackL(iAppView);

}

接下来我们将创建类CS60TestAppView的对象,这个类是CCoeControl的继承类,

class CS60TestAppView : public CCoeControl

{

..........

}

CCoeControl对象将控制哪个在屏幕中描绘,我们的ClientRect()控制将填充状态栏与软键盘间的空间,把它改成ApplicationRect()将控制全屏,AddToStackL接受来自键盘的反应,AppUi对象同样接受来自菜单的反应,当用户选择了菜单命令HandleCommandL将调用相应的命令代码,将完成结束命令和"Hello"命令显示一段文本。

void CS60TestAppUi::HandleCommandL(TInt aCommand)

{

switch(aCommand)

{

case EEikCmdExit:

case EAknSoftkeyExit:

Exit();

break;

case ES60TestHell

{

_LIT(message, "Hello!");

CAknInformationNote *informationNote=new(ELeave) CAknInformationNote;

informationNote->ExecuteLD(message);

}

break;

default:

Panic(ES60TestBasicUi);

break;

}

}

Draw函数是我们在CS60TestAppView唯一重载的函数,它将当我们的视图需重画时被调用,在本例中我们将显示在(176*144)的区域中,定义字符串将用到TBuf类。

在这一步中我将加入游戏需要的数据结构。

这里我们又加入了两个类TBlock和TGrid,这两个类没有特定的数据类型,它们是T型类,TBlock指向一个单个的俄罗斯方块(由四个小方块组成),TGrid指向已由(20*10)小方块填充的格子。在document类中,iGrid包含当前面板,iCurrBlock包含正在下落的方块,iBlockPos是正在下落方块的位置。

本例加的主要是比特的位操作,symbian OS特殊之处是用了TFixedArray类,它象普通的类被利用,但是内部会有下标检查,当在TFixedArray中下标是20时不会有内存溢出而是抛出错误。

第三步:加入用户接口

这一步我们将加入用户接口来测试第二步中加入的数据结构,我们可以用方向键移动方块到想要的位置然后用OK来固定方块(因为刚开始方块在顶部,先按向下的方向键,才能看到方块),玩家可以旋转方块。

首先用CS60TestAppView::Draw函数画背板,

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

{

CWindowGc &gc=SystemGc();

TRect rect=Rect();

gc.Clear(rect);

int i, j;

TFixedArray<TInt8, KGridX> arr;

gc.SetPenColor(TRgb(0));

gc.SetBrushStyle(CWindowGc::ESolidBrush);

for (i=0; i<=KGridY; i++)

gc.DrawLine(TPoint(KBoardOffset, KBoardOffset+KCellSize*i),

TPoint(KBoardOffset+KGridX*KCellSize, KBoardOffset+KCellSize*i));

for (i=0; i<=KGridX; i++)

gc.DrawLine(TPoint(KBoardOffset+KCellSize*i, KBoardOffset),

TPoint(KBoardOffset+KCellSize*i, KBoardOffset+KGridY*KCellSize));

for (i=0; i<KGridY; i++)

{

iDoc->GetRowContent(i, arr);

for (j=0; j<KGridX; j++)

{

gc.SetBrushColor(KColors[arr[j]]);

if (arr[j])

gc.DrawRect(TRect(KBoardOffset+KCellSize*j, KBoardOffset+KCellSize*i,

KBoardOffset+KCellSize*(j+1)+1, KBoardOffset+KCellSize*(i+1)+1));

}

}

}

我们用TGrid获得方块类型,用DrawLine和DrawRect来画背板,用SetPenColor来控制边框和线条的颜色,用SetBrushStyle/SetBrushColor来控制背板小方块的颜色,所以CWindowGc的方法可以查看SDK帮助文件。

我们需要对每个按键起作用,每次按键产生一个事件,该事件首先送给CS60TestAppView处理,将压到AddStackL栈顶部,缺省执行是返回EKeyWasNoConsumed,接下来此事件将送给CS60TestAppUi处理,在这个类中将用HandlKeyEventL来处理对应的按键.

KeyResponse CS60TestAppUi::HandleKeyEventL(const TKeyEvent &aKeyEvent,

TEventCode aType)

{

if (aType==EEventKey)

{

if (aKeyEvent.iCode==EKeyUpArrow)

if (iDoc->iBlockPos.iY>0)

iDoc->MoveBlock(iDoc->iBlockPos-TPoint(0, 1));

if (aKeyEvent.iCode==EKeyDownArrow)

iDoc->MoveBlock(iDoc->iBlockPos+TPoint(0, 1));

if (aKeyEvent.iCode==EKeyLeftArrow)

iDoc->MoveBlock(iDoc->iBlockPos-TPoint(1, 0));

if (aKeyEvent.iCode==EKeyRightArrow)

iDoc->MoveBlock(iDoc->iBlockPos+TPoint(1, 0));

if (aKeyEvent.iCode==EKeyDevice3)

{

if (iDoc->FixBlock())

iDoc->NewBlock();

}

if (aKeyEvent.iCode=='1')

iDoc->RotateBlock(-1);

if (aKeyEvent.iCode=='0' || aKeyEvent.iCode=='3')

iDoc->RotateBlock(1);

}

return EKeyWasNotConsumed;

}

但方格有变化时,我们将重画屏幕,CS60TestAppUi::UpdateBroad里的DrawDeferred来刷新整个屏幕。

好的设计是对每个按键都用EKeyWasConsumed来响应,我们会用EKeyWasNotConsumed来处理一些无用的按键。

最后在菜单里假如"new game"选项.

MENU_ITEM {command = ES60TestNewGame; txt = "New Game";}

当前游戏还不是一个有趣的游戏,用户可以移动方块到他想要的地方,这样就很无聊,这一步我们将加入游戏引擎,它将使方块自由下落。

这个引擎类是CTimer类的继承类CS60TestEngine,我将用After(iInterval)将引擎挂起一段时间,至少要隔iInterval微秒后,再运行CS60TestEngine::RunL,如果用一个循环延迟时间来取代CTimer,这样将要中断主线程,不能接收按键事件和显示菜单。

CTimer是一个需激活对象,我们用CActiveScheduler::Add(this)将它加入时间表队列。

void CS60TestEngine::ConstructL()

{

CTimer::ConstructL();

CActiveScheduler::Add(this);

After(iInterval);

iState=ERunning;

}

但用户重新玩游戏,将先用Cancel()来结束,在隔一定时间后重新开始.

void CS60TestEngine::Reset()

{

if (iState==ERunning)

Cancel();

iState=ERunning;

After(iInterval);

}

在RunL里,方块延一条线下坠,但它不能再下坠时我们将固定此方块,再产生新的方块,

void CS60TestEngine::RunL()

{

if (!iDoc->MoveBlock(iDoc->iBlockPos+TPoint(0, 1)))

{

if (!iDoc->FixBlock())

{

// Game over

TBuf<64> message;

CEikonEnv::Static()->ReadResource(message, R_NOTE_GAME_OVER);

CAknInformationNote *informationNote=new(ELeave) CAknInformationNote;

informationNote->ExecuteLD(message);

iState=EGameOver;

return;

}

iDoc->CheckRows();

if (iDoc->iLevel<=(iDoc->iLines/10))

{

iInterval*=3;

iInterval/=4;

iDoc->iLevel++;

}

iDoc->NewBlock();

}

iBeginTime.HomeTime();

After(iInterval);

}

但不能再放方块时,我们将结束游戏,并显示一段文字

CEikonEnv::Static()->ReadResource(message, R_NOTE_GAME_OVER)

结束游戏引擎

iState=EGameOver。

我们将在资源文件中加载"game over",这样我们只要翻译资源文件就可将游戏翻译成不同的语言,s60test.rss在加入TBUF型字符串

RESOURCE TBUF32 r_note_game_over

{

buf = "Game Over";

}

Build 将其建成s60test..rsg文件,在这个文件中R_NOTE_GAME_OVER定义成ID,通过

CEikonEnv::Static()->ReadResource(message, R_NOTE_GAME_OVER)

来加载"game over"

第五步

我们已经基本完成游戏,但是在几个方面还要改进。

第一个是用户打开其它程序或打开菜单,游戏仍在继续,当他回来继续玩的时候,游戏可能已经结束了,为避免这样因此我们应加入暂停的功能。

暂停/停止暂停的功能用户将会在暂停的时候用到,这时将要修改选择的菜单,TechPause/TechUnPause将会被用户切换到其他程序或菜单(不改变菜单选项)时调用,

void TechPause() { iTechPauseRef++; DoPause(); }

void TechUnpause() { iTechPauseRef--; DoPause(); }

这两个都参考了计数器,调用两次TechPause,将调用两次TechUnPause来停止暂停游戏,这是以前老版本游戏的用法,本游戏不是这样的

void CS60TestAppView::FocusChanged(TDrawNow aDrawNow)

{

if (IsFocused())

{

if (!iFocus)

{

iFocus=true;

iEngine->TechUnpause();

}

} else

{

if (iFocus)

{

iFocus=false;

iEngine->TechPause();

}

}

}

如果在DoPause里进行暂停和停止暂停,在暂停是我们要计算暂停多长时间,并结束计数器,

void CS60TestEngine::DoPause()

{

__ASSERT_ALWAYS(iPauseRef>=0 && iTechPauseRef>=0, Panic(ES60TestAssert));

if (iPauseRef==0 && iTechPauseRef==0)

{

if (iState==EPaused)

{

int ms=iPauseTime.MicroSecondsFrom(iBeginTime).Int64().GetTInt();

if (ms<0 || ms>iInterval)

ms=0;

iState=ERunning;

After(iInterval-ms);

}

} else

{

if (iState==ERunning)

{

iState=EPaused;

iPauseTime.HomeTime();

Cancel();

}

}

}

在停止暂停时我们同样要一个计数器来计算剩余的时间。

我们调用TechPause/TechUnPause CS60TestAppView::FocusChanged时是我们打开其他程序或菜单也就是我们的焦点不在此游戏上是,而Pause/UnPause CS60TestAppUi::HandleCommandL在菜单选项里选择的。

当用户从菜单里选择"pause"后,我们应将菜单改成"unpause"转态,这是通过CS60TestAppUi::DynInitMenuPaneL来实现的,每次显示菜单是都会执行它。因此我们在资源文件中设定相对应的字符串。

RESOURCE TBUF16 r_menu_pause_title

{

buf = "Pause";

}

RESOURCE TBUF16 r_menu_unpause_title

{

buf = "Unpause";

}

另外我们还在背景里加了一副图片,

图片在symbian OS中被存为*.mbm文件,是从*.bmp文件在build过程时制作过来的,在mmp文件加入

START BITMAP S60Test.mbm

HEADER

TARGETPATH systemappsstep5

SOURCEPATH .. itmaps

SOURCE c12 tlo.bmp

END

就可以了,一个*.mmp文件可以包含几个*.bmp文件

在本例中s60test.mmp将包含一个bmp文件,图片前面的c12表示是12bit(4096色)来节省空间,你也可以用C16(65536色),build也可以创建另外一个文件s60test.mbg,它将包含所有*.mbm文件的ID,在我们的文件就一个,所以它的ID是EMbmS60testTLO,

iBackground=CEikonEnv::Static()->CreateBitmapL(iPathName, EMbmS60testTlo);

来装载背景图片,

我们的游戏需要一个很好的图标和名字,这些都可以通过AIF文件实现,主要就是在资源文件S60testaif.rss中定义AIF_DATA数据,

#include <aiftool.rh>

RESOURCE AIF_DATA

{

app_uid=0x04545FF6;

caption_list=

{

CAPTION

{

code = ELangEnglish;

caption = "Tetris";

}

};

num_icons=2;

embeddability=KAppNotEmbeddable;

newfile=KAppDoesNotSupportNewFile;

}

// End of File

除了这个文件我们还需要四张图片,44*44bitmap,44*44bitmask,44*23bitmap,44*23bitmask,图片用不同的颜色,可以辨别什么时候用的什么图片,AIF文件需要在*.mmp文件里通过AIF命令引用

AIF Step6.aif ..Aif S60TestAif.rss

c12 Icon.bmp IconMask.bmp IconSmall.bmp IconSmallMask.bmp

 

 

 

CTIC.川科创新 3G嵌入式技术教育专家(3G送手机)

3G手机软件工程师培训 现热招中 报名即 送3G手机 一部

www.ctic.cc

转载于:https://www.cnblogs.com/learnSky/archive/2010/02/25/1673138.html

 类似资料: