background image

09/2008

66

Programowanie urządzeń mobilnych

Programowanie gier dla Symbian OS – szkielet aplikacji

www.sdjournal.org

67

M

aszyny,  które  jeszcze  kilkadziesiąt 
lat  temu  stanowiły  jedynie  mrzon-
kę wąskiej garstki specjalistów, dziś 

mieszczą się  w  naszych  kieszeniach.  Mowa  tu 
oczywiście o nowoczesnych telefonach komór-
kowych. Na dzień dzisiejszy, za całkiem rozsąd-
ną cenę otrzymujemy urządzenie wyposażone 
w kamerę o wysokiej rozdzielczości, kolorowy 
wyświetlacz, dostęp do szerokopasmowego in-
ternetu, zintegrowany moduł GPS, wysokiej ja-
kości odtwarzacz dźwięku stereo, akcelerometr 
i układ wspomagający renderowanie grafiki 3D 
w czasie rzeczywistym. No i przy okazji – apa-
rat telefoniczny. Ten szalony pęd technologicz-
ny tworzy ogromną, otwartą przestrzeń dla no-
wych aplikacji, zaś lwią ich cześć stanowią gry. 
Niniejszy  tekst  rozpoczyna  cykl  artykułów 
traktujących  o  programowaniu  gier  dla  Sym-
bian OS – jednego z czołowych graczy na ryn-
ku mobilnych systemów operacyjnych.

Zanim zaczniemy...

...kilka słów wstępu. Tak jak wspomniano wy-
żej, niniejszy artykuł traktuje o programowa-

niu gier na telefony komórkowe działające pod 
kontrolą  Symbian  OS.  Cykl  poświęcony  jest 
przede  wszystkim  dla  programistów  języka 
C++, którzy nie znają, lub znają słabo system 
operacyjny Symbian, a chcieliby na poważnie 
zainteresować  się  programowaniem  gier  na 
urządzenia  mobilne  pracujące  pod  jego  kon-
trolą. Symbian jest tworem potężnym i z pro-
gramistycznego  punktu  widzenia  dość  kon-
trowersyjnym (ciekawą dyskusję na ten temat 
przedstawia  artykuł  pt.  Unix  –  piszemy  pro-
gram na komórkę
 autorstwa Bartosza Taudula, 
przedstawiony w numerze 06/2008 Software 
Developer's  Journal).  Cykl  moich  artykułów 
ma  na  celu  przedstawienie  tych  udogodnień 
oferowanych  przez  Symbiana,  które  są  klu-
czowe  przy  programowaniu  gier.  Aby  nieco 
uprościć  to  niełatwe  zadanie,  przedstawione 
rozważania ograniczać się będą do dziewiątej 
wersji wspomnianego systemu oraz platformy 
Series60. W niniejszym tekście przedstawiony 
zostanie szkielet aplikacji dostosowany do po-
trzeb mobilnych gier, stanowiący bazę dla ko-
lejnych,  bardziej  zaawansowanych  rozważań. 
W  ramach  omówienia  wspomnianego  szkie-
letu  przeanalizowane  zostaną  takie  zagadnie-
nia jak implementacja głównej pętli gry, ryso-
wanie, obsługa interakcji aplikacji z użytkow-
nikiem poprzez klawiaturę, oraz obsługa pod-
stawowych zdarzeń systemowych.

Środowisko pracy

Przygodę  z  programowaniem  gier  dla  Sym-
bian  OS  rozpoczniemy  od  przygotowania  
środowiska  pracy.  Zakładam,  że  Czytelnik 
posiada system operacyjny Windows XP. Na 
początek  musimy  zainstalować  dystrybu-
cję  Perl'a  oraz  środowisko  uruchomieniowe 
języka  Java.  W  dalszej  kolejności  będziemy 
potrzebowali  SDK  (Software  Development 
Kit),  zawierającego  podstawowy  zestaw  na-
rzędzi  potrzebnych  do  tworzenia  aplikacji 
dla  Symbian  OS.  Nasz  wybór  pada  w  tym 
przypadku  na  pakiet  S60  3rd  Edition  SDK 
for  Symbian  OS,  Feature  Pack  2.  Pakiet  ten 
dostępny jest za darmo na portalu Forum No-
kia – przed jego ściągnięciem wymagana jest 
jednak  rejestracja  (ramka  W  Sieci).  Aby  roz-
począć  instalację  wystarczy  rozpakować  po-
brane archiwum z SDK i uruchomić plik se-
tup.exe
.  Proces  powinien  odbyć  się  bezpro-
blemowo.  Na  sam  koniec  instalator  zapyta 
się  o  zgodę  na  doinstalowanie  zestawu  na-
rzędzi  kompilacyjnych  dla  procesora  ARM. 
Na pytanie to należy odpowiedzieć twierdzą-
co  –  bez  tego  zestawu  nie  będziemy  w  sta-
nie zbudować naszej aplikacji w wersji dzia-
łającej na urządzeniu. Jeśli chodzi o wymaga-
nia dla SDK, to przed jego instalacją powin-
niśmy uzbroić się w około 1GB wolnej prze-
strzeni dyskowej oraz odrobinę wolnego cza-
su  (ze  względu  na  dużą  liczbę  małych  pli-
ków  w  archiwum  instalacja  trwa  dość  dłu-
go). Pracę ze środowiskiem warto rozpocząć 
od  uruchomienia  emulatora.  Można  to  zro-
bić  wybierając  odpowiednią  opcję  z  Menu 
Start  lub  –  prościej  –  wpisując  z  linii  pole-
ceń komendę epoc. Jeśli wszystko poszło do-
brze,  to  nasze  dotychczasowe  wysiłki  nagro-
dzone zostaną uruchomieniem się emulatora 
(Rysunek 1).

Po uruchomieniu emulatora należy zareje-

strować  SDK  wybierając  z  menu  opcję  Help 

Programowanie 
gier dla Symbian OS

Niniejszy  tekst  rozpoczyna  cykl  artykułów  traktujących  o  programowaniu 

gier  na  telefony  komórkowe  działające  pod  kontrolą  Symbian  OS.  W 

pierwszym odcinku cyklu przedstawiona jest implementacja szkieletu aplikacji 

zawierającego takie podstawowe udogodnienia jak pętla gry, rysowanie, a także 

przechwytywanie i obsługa zdarzeń klawiatury oraz interakcja z systemem. 

Dowiesz się:

•   Jak rozpocząć pracę z narzędziami do progra-

mowania aplikacji dla Symbian OS;

•   Jak zaimplementować pętlę gry w aplikacji dla 

Symbian OS;

•  Jak  w  grach  pisanych  pod  Symbian  OS  efek-

tywnie  renderować  grafikę,  obsługiwać  zda-
rzenia klawiatury i przechwytywać podstawo-
we zdarzenia systemowe.

Powinieneś wiedzieć:

•   Średnio  zaawansowana  wiedza  ogólna  z  za-

kresu programowania w języku C++;

•   Podstawowa  wiedza  z  zakresu  programowa-

nia orientowanego obiektowo w języku C++.

Poziom trudności

Szkielet aplikacji

background image

09/2008

66

Programowanie urządzeń mobilnych

Programowanie gier dla Symbian OS – szkielet aplikacji

www.sdjournal.org

67

–>

Register.  Drugim  narzędziem  składającym 

się na nasze środowisko pracy – w odniesie-
niu do tego artykułu – będzie Carbide.C++ 
v1.3. Carbide to IDE stworzone w oparciu o 
platformę  Eclipse,  dedykowane  jako  wspar-
cie  dla  programistów  tworzących  aplikacje 
pod Symbian OS. Narzędzie to w darmowej 
wersji  Express  można  pobrać  z  portalu  Fo-
rum Nokia (ramka W Sieci). 

Przy  pierwszym  uruchomieniu  Carbide 

prosi o ustalenie folderu roboczego – najwy-
godniej jest w tym celu stworzyć sobie kata-
log  przeznaczony  na  symbianowe  projekty 
(najlepiej  na  tym  samym  dysku,  na  którym 
zainstalowane  było  SDK).  Po  uruchomieniu 
IDE  automatycznie  załaduje  wtyczki  z  SDK 
i  poprosi  o  restart.  Po  ponownym  urucho-
mieniu  IDE  nasze  środowisko  będzie  goto-
we do pracy.

Szkielet aplikacji – przegląd

Rozważania  przedstawione  w  dalszej  czę-
ści artykułu opierać się będą na kodach źró-
dłowych  przykładowej  aplikacji  –  GameSke-
leton
. Kody te można pobrać z witryny SDJ. 
Kluczowe  fragmenty  wspomnianych  ko-
dów przedstawione będą na listingach w ni-
niejszym artykule, aczkolwiek gorąco nama-
wiam do pobrania całej paczki, chociażby w 
celu  uruchomienia  przykładowego  progra-
mu  i  poeksperymentowania  z  kodem  źró-
dłowym.

Symbian uważany jest za system trudny do 

nauki. Wynika to chociażby z faktu, że apli-
kacja typu Hello World składa się z około 50 
kilobajtów  kodu  i  konfiguracji  rozmieszczo-
nych w kilkunastu plikach. Dodatkowo Sym-
bian  narzuca  pewne  specyficzne  idiomy  ko-

Rysunek 1. Okno emulatora przy pierwszym 
uruchomieniu po instalacji SDK

Listing 1. Definicja klasy CGameSkeletonApplication

class

 

CGameSkeletonApplication

    

:

 

public

 

CAknApplication

    

{

public

:

    

TUid

 

AppDllUid

()

 

const

;

protected

:

    

CApaDocument

*

 

CreateDocumentL

();

    

}

;

 // 

class

 

CGameSkeletonApplication

Listing 2. Implementacja metod klasy CGameSkeletonApplication

const

 

TUid

 

KUidGameSkeletonApp

 

=

 

{

 

0xA000958F

 

}

;

CApaDocument

*

 

CGameSkeletonApplication

::

CreateDocumentL

()

    

{

    

return

 

(

 

static_cast

<

 

CApaDocument

*

 

>(

        

CGameSkeletonDocument

::

NewL

(

 

*

this

 

)

 

)

 

);

    

}

TUid

 

CGameSkeletonApplication

::

AppDllUid

()

 

const

    

{

    

return

 

KUidGameSkeletonApp

;

    

}

Listing 3. Interfejs klasy CGameSkeletonDocument

class

 

CGameSkeletonDocument

    

:

 

public

 

CAknDocument

    

{

public

:

    

static

 

CGameSkeletonDocument

*

 

NewL

(

        

CEikApplication

&

 

aApp

 

);

    

static

 

CGameSkeletonDocument

*

 

NewLC

(

        

CEikApplication

&

 

aApp

 

);

    

virtual

 ~

CGameSkeletonDocument

();

    

CEikAppUi

*

 

CreateAppUiL

();

private

:

    

CGameSkeletonDocument

(

 

CEikApplication

&

 

aApp

 

);

    

void

 

ConstructL

();

    

}

;

 // 

class

 

CGameSkeletonDocument

Listing 4. Interfejs klasy CGameSkeletonAppUi

class

 

CGameSkeletonAppUi

   

:

 

public

 

CAknAppUi

    

{

public

:

    

CGameSkeletonAppUi

();

    

virtual

 ~

CGameSkeletonAppUi

();

    

void

 

ConstructL

();

private

:

    

void

 

HandleCommandL

(

 

TInt

 

aCommand

 

);

    

TKeyResponse

 

HandleKeyEventL

(

        

const

 

TKeyEvent

&

 

aKeyEvent

,

        

TEventCode

 

aType

 

);

private

:

    

CGameSkeletonContainer

*

 

iAppContainer

;

    

}

;

 // 

class

 

CGameSkeletonAppUi

background image

09/2008

68

Programowanie urządzeń mobilnych

Programowanie gier dla Symbian OS – szkielet aplikacji

www.sdjournal.org

69

dowania, które mocno odbiegają od standar-
du języka C++. W przypadku pisania progra-
mów dla Symbian OS stosuje się często kre-
atory  generujące  szkielety  aplikacji.  W  dal-
szej części artykułu opiszę właśnie taki szkie-
let,  stanowiący  punkt  wyjścia  przy  progra-
mowaniu  gier.  Symbian  oferuje  cały  szereg 
architektur  aplikacji  z  graficznym  interfej-
sem  (np.  architektura  jednowidokowa  lub 
wielowidokowa).  Z  punktu  widzenia  pro-
gramisty  gier,  dużą  cześć  wiedzy  na  temat 
wspomnianych architektur można pominąć. 
W  naszej  szkieletowej  aplikacji  zastosujemy 
bardzo  prostą  architekturę  bazującą  na  jed-
nej, pełnoekranowej kontrolce. Osoby zainte-
resowane  szczegółami  technicznymi  związa-
nymi z architekturą aplikacji w Symbian OS 
zapraszam do ramki Bibliografia gdzie przed-
stawiłem tytuły nawiązujące do tego tematu. 
W niniejszym podpunkcie przedstawię jedy-
nie to, co konieczne jest do zrozumienia pre-
zentowanego przykładu. Na początek – szyb-
ki przegląd klas wchodzących w skład naszej 
przykładowej aplikacji. Zacznijmy od klasy 

C

GameSkeletonApplication

.  definicja  tej  kla-

sy przedstawiona jest na Listingu 1.

Jak  widać  na  wspomnianym  Listingu,  de-

finicja  ta  nie  jest  specjalnie  skomplikowana. 
Klasa 

CXXXApplication 

(gdzie  wedle  kon-

wencji  Symbianowej  XXX  to  nazwa  projek-
tu)  odgrywa  rolę  punktu  wejścia  aplikacji 
(ang.  entry  point).  Kluczowa  jest  tu  metoda 
fabrykująca 

CreateDocumentL()

,  która  od-

powiada  za  tworzenie  dokumentu  aplikacji 
(

CApaDocument

).  Osoby  zaciekawione  zna-

czeniem  magicznych  prefiksów  i  postfiksów 
(np. 

C

 lub 

L

) w nazwach prezentowanych klas 

oraz  ich  metod  zapraszam  do  przeczytania 

Listing 5. Definicja konstruktora klasy CGameSkeletonAppUi

void

 

CGameSkeletonAppUi

::

ConstructL

()

    

{

    

CAknAppUiBase

::

SetFullScreenApp

(

 

ETrue

 

);

    

BaseConstructL

();

    

CAknAppUi

::

SetKeyBlockMode

();

    

iAppContainer

        

=

 

new

 

(

 

ELeave

 

)

 

CGameSkeletonContainer

;

    

iAppContainer

->

ConstructL

(

 

ApplicationRect

()

 

);

    

AddToStackL

(

 

iAppContainer

 

);

    

}

Listing 6. Definicja destruktora klasy CGameSkeletonAppUi

CGameSkeletonAppUi

::

~

CGameSkeletonAppUi

()

    

{

    

if

 

(

 

iAppContainer

 

)

        

{

        

RemoveFromStack

(

 

iAppContainer

 

);

        

delete

 

iAppContainer

;

        

}

    

}

Listing 7. Definicja metody HandleCommandL

void

 

CGameSkeletonAppUi

::

HandleCommandL

(

 

TInt

 

aCommand

 

)

    

{

    

switch

 

(

 

aCommand

 

)

        

{

        

case

 

EEikCmdExit

:

            

{

            

Exit

();

            

break

;

            

}

        

default

:

 

break

;

        

}

    

}

Listing 8. Funkcje zdefiniowane w pliku GameSkeleton.cpp

LOCAL_C

 

CApaApplication

*

 

NewApplication

()

    

{

    

return

 

new

 

CGameSkeletonApplication

;

    

}

GLDEF_C

 

TInt

 

E32Main

()

    

{

    

return

 

EikStart

::

RunApplication

(

 

NewApplication

 

);

    

}

Listing 9. Schemat pętli gry

while

 

(

 

Gra

 

trwa

 

)

{

    

1

Oblicz

 

czas

 

kt

ó

ry

 

min

ął 

od

 

poprzedniego

        

przebiegu

 

p

ę

tli

.

    

2

Uaktualnij

 

model

 

gry

 

na

 

podstawie

 

r

óż

nicy

 

czasu

,

        

kt

ó

ry

 

up

ł

yn

ął 

od

 

ostatniego

 

przebiegu

 

p

ę

tli

 

czasu

.

    

3

Narysuj

 

na

 

podstawie

 

modelu

 

kolejn

ą 

ramk

ę 

gry

        

i

 

wy

ś

wietl

 

j

ą 

na

 

ekranie

.

    

4

Zsynchronizuj

 

d

ź

wi

ę

k

 

z

 

modelem

 

gry

.

}

Rysunek 2. Efekt działania aplikacji 
GameSkeleton

background image

09/2008

68

Programowanie urządzeń mobilnych

Programowanie gier dla Symbian OS – szkielet aplikacji

www.sdjournal.org

69

ramki  zatytułowanej  Idiomy  kodowania  przy 
programowaniu  aplikacji  dla  Symbian  OS

Wracając  do  metody 

CreateDocumentL()

w  jej  ciele  (Listing  2)  jest  dynamicznie  two-
rzona i jednocześnie zwracana instancja kla-
sy 

CGameSkeletonDocument

.  Druga  meto-

da  klasy 

CGameSkeletonApplication

,  czyli 

AppDllUid()

  zwraca  tzw.  UID  aplikacji.  Pi-

sząc  w  uproszczeniu,  UID  to  niepowtarzal-
ny identyfikator aplikacji. Skąd i w jaki spo-
sób  taki  UID  można  pobrać,  to  już  odręb-
na kwestia – będę o tym pisał w dalszej czę-
ści artykułu. Dociekliwi Czytelnicy zaintere-
sują  się  zapewne  dlaczego  instancja  obiektu 

CGameSkeletonDocument

  tworzona  jest  przy 

pomocy tajemniczej metody statycznej 

NewL

Tychże  Czytelników  odsyłam  tym  razem  do 
ramki  zatytułowanej  Idiomy  kodowania  przy 
programowaniu aplikacji dla Symbian OS
.

W  dalszej  kolejności  warto  zainteresować 

się  klasą 

CGameSkeletonDocument

.  Interfejs 

tej klasy przedstawiony jest na Listingu 3.

Według nomenklatury symbianowej klasa 

CXXXDocument

 reprezentuje część M (model) 

z  wzorca  MVC  (ang.  Model-View-Controller
na  którym  bazuje  architektura  aplikacji  UI. 
Innymi słowy –  klasa ta odpowiada za zarzą-
dzanie danymi tej aplikacji (np. wczytywanie 
i  zapisywanie  danych  na  dysk  w  przypadku 
tzw. aplikacji opartych na plikach). W przy-
padku naszego szkieletu gry nie będziemy ko-
rzystać z klasycznego MVC i w tym kontek-
ście  klasa 

CGameSkeletonDocument

  ma  cha-

rakter pomocniczy. To co nas w tej klasie inte-
resuje  to  metoda  fabrykująca 

CreateAppUiL

która  istnieje  jedynie  po  to,  aby  zwrócić  in-
stancję  obiektu  klasy 

CGameSkeletonAppUi

Klasa  ta  (jej  interfejs  pokazany  jest  na  Li-
stingu  4)  odpowiada  za  zarządzanie  graficz-
nym interfejsem użytkownika – reprezentu-
je część V (ang. view, czyli widok) we wspo-
mnianym wcześniej wzorcu MVC.

Klasa 

CGameSkeletonAppUi

  nawiązuje  do 

interesujących  nas  tematów,  więc  przeanali-
zujemy ją bardziej szczegółowo. Gdyby nasz 
szkielet  reprezentował  aplikację  z  systemo-
wym
  interfejsem  graficznym  dla  Symbia-
na,  to  właśnie  ta  klasa  byłaby  odpowiedzial-
na za przechowywanie i kontrolę jej widoku 
(lub widoków). Widoki, z kolei odgrywają ro-
lę  kontenerów  kontrolek.  W  przypadku  pi-
sania gry niepotrzebny nam jest cały ten na-
rzut,  dlatego  skorzystamy  z  prostego  konte-
nera  (

CGameSkeletonContainer

),  który  re-

prezentuje  prostą  kontrolkę  przykrywają-
  cały  wyświetlacz  urządzenia.  Do  szcze-
gółów  samej  kontrolki  przejdziemy  w  dal-
szej  części  artykułu  –  teraz  skupmy  się  na 
implementacji  poszczególnych  metod  klasy 

CGameSkeletonAppUi

. Na początek konstruk-

tor 

ConstructL()

  (Czytelników  zdziwio-

nych  dlaczego  nazywam  zwyczajną  metodę 
konstruktorem,  zapraszam  do  lektury  ram-
ki Idiomy kodowania przy programowaniu na-

Listing 10. Interfejs klasy CGameSkeletonContainer

class

 

CGameSkeletonContainer

    

:

 

public

 

CCoeControl

    

{

public

:

    

static

 

TInt

 

Tick

(

 

TAny

*

 

aCallback

 

);

    ~

CGameSkeletonContainer

();

    

void

 

ConstructL

(

 

const

 

TRect

&

 

aRect

 

);

    

void

 

FocusChanged

(

 

TDrawNow

 

aDrawNow

 

);

    

void

 

SizeChanged

();

    

TInt

 

CountComponentControls

()

 

const

;

    

CCoeControl

*

 

ComponentControl

(

 

TInt

 

aIndex

 

)

 

const

;

    

TKeyResponse

 

OfferKeyEventL

(

        

const

 

TKeyEvent

&

 

aKeyEvent

,

        

TEventCode

 

aType

 

);

private

:

    

void

 

Draw

(

 

const

 

TRect

&

 

aRect

 

)

 

const

;

    

void

 

StartTickGenerator

();

    

void

 

StopTickGenerator

();

    

void

 

OnTick

();

    

void

 

Draw

(

 

CGraphicsContext

&

 

aGc

 

)

 

const

;

    

void

 

Update

(

 

TInt64

 

aDt

 

);

    

CWsBitmap

*

 

iBackupBitmap

;

    

CFbsBitmapDevice

*

 

iBitmapDevice

;

    

CFbsBitGc

*

 

iBitmapGc

;

    

CFpsCounter

*

 

iFpsCounter

;

    

CPeriodic

*

 

iTickGenerator

;

    

TTime

 

iTickStart

;

    

TTime

 

iTickStop

;

    

TKeyState

 

iKeyState

;

    

}

;

 // 

class

 

CGameSkeletonContainer

Listing 11. Definicja metody CGameSkeletonContainer::ConstructL

void

 

CGameSkeletonContainer

::

ConstructL

(

 

const

 

TRect

&

 

aRect

 

)

    

{

    

CreateWindowL

();

    

SetBlank

();

    

SetRect

(

 

aRect

 

);

    

iBackupBitmap

        

=

 

new

 

(

 

ELeave

 

)

 

CWsBitmap

(

 

iCoeEnv

->

WsSession

()

 

);

    

iBackupBitmap

->

Create

(

        

Rect

()

.

Size

()

,

        

iEikonEnv

->

DefaultDisplayMode

()

 

);

    

iBitmapDevice

        

=

 

CFbsBitmapDevice

::

NewL

(

 

iBackupBitmap

 

);

    

User

::

LeaveIfError

(

        

iBitmapDevice

->

CreateContext

(

 

iBitmapGc

 

)

 

);

    

iTickGenerator

        

=

 

CPeriodic

::

NewL

(

 

CActive

::

EPriorityStandard

 

);

    

iFpsCounter

 

=

 

CFpsCounter

::

NewL

(

        

KFpsCounterIntervalInSeconds

 

);

    

ActivateL

();

    

}

background image

09/2008

70

Programowanie urządzeń mobilnych

www.sdjournal.org

71

tywnych aplikacji dla Symbian OS). Ciało tej 
funkcji przedstawione jest na Listingu 5.

Pierwsza  instrukcja  tej  metody,  czy-

li  wywołanie  funkcji 

SetFullScreenApp()

 

z  klasy  bazowej 

CAknAppUiBase

  odpowia-

da  za  przełączenie  aplikacji  w  tryb  pełno-

ekranowy.  Kolejna  instrukcja  (wywołanie 

BaseConstructL()

)  odpowiada  za  inicjali-

zację składowych kontrolki odziedziczonych 
z  klasy  bazowej.  Cel  wywoływania  funkcji 

SetKeyBlockMode()

 opisany będzie w dalszej 

części  artykułu.  W  następnym  kroku  mo-

żemy  zaobserwować  dwufazową  konstruk-
cję  naszej  kontrolki  (patrz:  Idiomy  kodowa-
nia  przy  programowaniu  natywnych  aplikacji 
dla Symbian OS
). Na końcu metody umiesz-
czono kod odpowiedzialny za bardzo istotną 
operację  –  dodanie  wspomnianej  kontrolki 
do stosu kontrolek. Gdybyśmy pominęli ten 
krok,  to  nasza  kontrolka  nie  otrzymywałaby 
systemowych powiadomień związanych z ob-
sługą klawiatury i nie bylibyśmy w stanie ob-
służyć interakcji użytkownika z aplikacją.

W destruktorze klasy 

CGameSkeletonAppUi

 

(Listing 6) nie dzieje się nic ciekawego oprócz 
zdjęcia naszego kontenera ze stosu kontrolek 
oraz  zniszczenia  go  przy  pomocy  operato-
ra 

delete

.

Definicja metody 

HandleCommandL

 (Listing 

7) może wydawać się mało ciekawa, ale warto 
przeanalizować jej treść.

W systemie Symbian, w kontekście interak-

cji z użytkownikiem mamy do czynienie z dwo-
ma typami komunikatów: są to komendy oraz 
zdarzenia  klawiatury.  Komendy  reprezentu-
ją  pewne  zdarzenie  logiczne,  związane  z  sys-
temowymi  komponentami  graficznego  inter-
fejsu użytkownika (np. wybór opcji w menu). 
W  przypadku  programowania  gier,  z  kompo-
nentów  tych  rzadko  się  korzysta.  Jeśli  potrze-
bujemy  menu  w  grze,  to  raczej  narysujemy  je 
ręcznie i obsłużymy przy pomocy niskopozio-
mowych zdarzeń klawiatury. W tej sytuacji po-
wstaje pytanie: po co implementujemy metodę 

HandleCommandL()

 w naszym szkielecie? Otóż 

istnieje  ku  temu  konkretny  powód.  Chodzi  o 
to, że każda aplikacja symbianowa musi w po-
prawny  sposób  obsłużyć  systemową  komendę 

EEikCmdExit

. Komenda ta przychodzi do apli-

kacji od systemu w takich sytuacjach jak zabi-
cie aplikacji
:

•   z poziomu listy aktywnych zadań syste-

mowych;

•   przy pomocy czerwonego klawisza;
•   z powodu jej deinstalacji;
•   z  powodu  niskiego  poziomu  pamięci  w 

systemie.

Zignorowanie  tej  komendy  nie  przynosi  ra-
czej nic dobrego. Teoretycznie moglibyśmy nie 
przeładowywać definicji 

HandleCommandL()

 z 

klasy bazowej, która poprawnie obsługuje ten 
komunikat, aczkolwiek w takiej sytuacji traci-
my  możliwość  reakcji  na  to  istotne  zdarzenie 
(można  przy  tej  okazji  spróbować  np.  zapisać 
stan gry na dysk, aby następnym razem nie za-
czynać rozgrywki od początku).

Na końcu pozostaje nam rozważyć definicję 

metody 

HandleKeyEventL()

.  Metoda  ta  służy 

do obsługi niskopoziomowych zdarzeń genero-
wanych przez klawiaturę. W naszym przypad-
ku  implementacja  tej  metody  jest  trywialna  i 
mieści się w jednej linii:

return EKeyWasNotConsumed;

Listing 12. Definicja metody StartTickGenerator odpowiedzialnej za uruchomienie timera

void

 

CGameSkeletonContainer

::

StartTickGenerator

()

    

{

    

if

 

(

 

!

iTickGenerator

->

IsActive

()

 

)

        

{

        

iTickGenerator

->

Start

(

            

KDesiredFramesPerSecond

,

            

KDesiredFramesPerSecond

,

            

TCallBack

(

 

CGameSkeletonContainer

::

Tick

,

                       

this

 

)

 

);

        

iTickStart

.

HomeTime

();

        

}

    

}

Listing 13. Definicja metody CGameSkeletonContainer::Tick()

TInt

 

CGameSkeletonContainer

::

Tick

(

 

TAny

*

 

aCallback

 

)

    

{

    

ASSERT

(

 

aCallback

 

);

    

CGameSkeletonContainer

*

 

gameSkeletonContainer

 

=

        

static_cast

<

 

CGameSkeletonContainer

*

 

>(

 

aCallback

 

);

    

gameSkeletonContainer

->

OnTick

();

    

return

 

ETrue

;

    

}

Listing 14. Implementacja metody CGameSkeletonContainer::Draw()

void

 

CGameSkeletonContainer

::

Draw

(

 

const

 

TRect

&

 

aRect

 

)

    

const

    

{

    

CWindowGc

&

 

gc

 

=

 

SystemGc

();

    

gc

.

BitBlt

(

 

aRect

.

iTl

iBackupBitmap

aRect

 

);

    

}

Listing 15. Implementacja metody CGameSkeletonContainer::OnTick()

void

 

CGameSkeletonContainer

::

OnTick

()

    

{

    

iTickStop

.

HomeTime

();

    

TInt64

 

dt

 

=

 

iTickStart

.

Int64

()

 

-

 

iTickStop

.

Int64

();

    

iTickStart

 

=

 

iTickStop

;

    

Update

(

 

dt

 

);

    

Draw

(

 

*

iBitmapGc

 

);

    

DrawNow

();

    

iFpsCounter

->

Update

();

    

}

background image

09/2008

70

Programowanie urządzeń mobilnych

www.sdjournal.org

71

Listing 16. Implementacja metody uaktualniającej zawartość tylnego bufora

void

 

CGameSkeletonContainer

::

Draw

(

    

CGraphicsContext

&

 

aGc

 

)

 

const

    

{

    

aGc

.

Reset

();

    

aGc

.

SetBrushColor

(

 

KRgbBlack

 

);

    

aGc

.

SetBrushStyle

(

 

CGraphicsContext

::

ESolidBrush

 

);

    

aGc

.

DrawRect

(

 

Rect

()

 

);

    

TBuf

<

 

KFpsMessageMaxLength

 

>

 

fpsMessage

;

    

fpsMessage

.

Format

(

 

KFpsMessageFormatString

,

                       

iFpsCounter

->

Fps

()

 

);

    

aGc

.

UseFont

(

 

iEikonEnv

->

NormalFont

()

 

);

    

aGc

.

SetPenColor

(

 

KRgbWhite

 

);

    

aGc

.

DrawText

(

 

fpsMessage

,

                  

TPoint

(

 

KFpsMessagePosX

,

                          

KFpsMessagePosY

 

)

 

);

    

aGc

.

DiscardFont

();

    

}

Listing 17. Obsługa zdarzeń klawiatury

TKeyResponse

 

CGameSkeletonContainer

::

OfferKeyEventL

(

    

const

 

TKeyEvent

&

 

aKeyEvent

,

    

TEventCode

 

aType

 

)

    

{

    

TKeyResponse

 

response

 

=

 

EKeyWasConsumed

;

    

TKeyState

 

input

 

=

 

KKeyNull

;

    

switch

 

(

 

aKeyEvent

.

iScanCode

 

)

        

{

        

case

 

EStdKeyDevice0

:

 

input

 

=

 

KKeySoftLeft

;

 

break

;

        

case

 

EStdKeyDevice1

:

 

input

 

=

 

KKeySoftRight

;

 

break

;

        

case

 

EStdKeyUpArrow

:

 

input

 

=

 

KKeyUp

;

 

break

;

        

case

 

EStdKeyDownArrow

:

 

input

 

=

 

KKeyDown

;

 

break

;

        

case

 

EStdKeyLeftArrow

:

 

input

 

=

 

KKeyLeft

;

 

break

;

        

case

 

EStdKeyRightArrow

:

 

input

 

=

 

KKeyRight

;

 

break

;

        

case

 

EStdKeyDevice3

:

 

input

 

=

 

KKeyFire

;

 

break

;

        

case

 '0'

:

 

input

 

=

 

KKey0

;

 

break

;

        

case

 '1'

:

 

input

 

=

 

KKey1

;

 

break

;

        

case

 '2'

:

 

input

 

=

 

KKey2

;

 

break

;

        

case

 '3'

:

 

input

 

=

 

KKey3

;

 

break

;

        

case

 '4'

:

 

input

 

=

 

KKey4

;

 

break

;

        

case

 '5'

:

 

input

 

=

 

KKey5

;

 

break

;

        

case

 '6'

:

 

input

 

=

 

KKey6

;

 

break

;

        

case

 '7'

:

 

input

 

=

 

KKey7

;

 

break

;

        

case

 '8'

:

 

input

 

=

 

KKey8

;

 

break

;

        

case

 '9'

:

 

input

 

=

 

KKey9

;

 

break

;

        

default

:

 

response

 

=

 

EKeyWasNotConsumed

;

 

break

;

        

}

    

if

 

(

 

EEventKeyDown

 

==

 

aType

 

)

        

{

        

iKeyState

 

|=

 

input

;

        

}

    

else

 

if

 

(

 

EEventKeyUp

 

==

 

aType

 

)

        

{

        

iKeyState

 

&=

 ~

input

;

        

}

    

return

 

response

;

    

}

background image

09/2008

72

Programowanie urządzeń mobilnych

Programowanie gier dla Symbian OS – szkielet aplikacji

www.sdjournal.org

73

Zwrócenie  takiej  wartości  przez  tę  funkcję 
jest  sygnałem  dla  silnika  aplikacji,  że  zda-
rzenia  klawiatury  będą  obsługiwane  gdzie 
indziej  –  w  naszym  przypadku  będą  one 
przekierowywane  bezpośrednio  do  naszej 
kontrolki.

Tak oto przebrnęliśmy przez klasy stanowią-

ce fundament szkieletu gry. Ostatnia klasa z 
tego zestawu, czyli 

CGameSkeletonContainer

 

zawiera  to,  co  stanowi  istotę  niniejszego  ar-
tykułu – czyli wszystkie podstawowe mecha-
nizmy  obsługi  gry.  Wspomniane  mechani-
zmy  będą  opisywane  w  kolejnych  podpunk-
tach artykułu.

Aby  zakończyć  przegląd  prezentowanego 

szkieletu  aplikacji,  warto  wyjaśnić  znacznie 
funkcji  zdefiniowanych  w  pliku  GameSkele-
ton.cpp
 (Listing 8).

Funkcje  te  definiują  główny  punkt  wejścia 

całego  projektu  –  taki  odpowiednik  funk-
cji 

main()

  w  konsolowych  programach  pisa-

nych  w  języku  C  lub  C++.  Prawdziwa  funk-
cja 

main()

  ukryta  jest  w  systemowym  silni-

ku aplikacji (ang. application framework), któ-
ry wykorzystuje funkcje przedstawione na Li-
stingu  8  w  celu  stworzenia  instancji  naszej 
aplikacji.

Duża  część  przedstawionego  wyżej  kodu 

ma  charakter  powtarzalny  –  przy  tworze-
niu nowego projektu trzeba go napisać od no-
wa, podmieniając jedynie pewne jego części. 
Aby uprościć ten proces, przy tworzeniu no-
wych projektów aplikacji symbianowych, wy-
korzystuje się różnego rodzaju generatory ko-
du. W drugiej części cyklu artykułów o pro-
gramowaniu gier dla Symbian OS, w którym 
będę między innymi pokazywał, jak na przy-
kładzie zaprezentowanego tu szkieletu stwo-
rzyć prostą grę, zaprezentuję narzędzie auto-
matyzujące ten proces.

Pętla gry

Większość aplikacji przeznaczonych dla Sym-
bian  OS  działa  w  oparciu  o  zdarzenia  gene-
rowane na bazie interakcji z użytkownikiem. 
Typowa  aplikacja  symbianowa  (na  przykład 
program  do  pisania  SMS-ów)  czeka  na  reak-
cję  użytkownika  (np.  wciśnięcie  klawisza). 
Jeśli  wspomniana  reakcja  nie  następuje,  to 
aplikacja po prostu nie robi nic. Podejście ta-
kie  jest  jak  najbardziej  uzasadnione  w  przy-
padku programów dedykowanych systemom 
mobilnym.  Przy  jego  stosowaniu,  w  sytuacji 
kiedy użytkownik nie generuje żadnych zda-
rzeń,  wątek  aplikacji  może  być  zawieszony, 
dzięki czemu urządzenie zużywa mniej cen-
nej  energii.  W  przypadku  gier  sytuacja  wy-
gląda  nieco  inaczej:  wnika  to  z  faktu,  że  w 
przypadku  tego  rodzaju  aplikacji  tematem 
przewodnim jest zazwyczaj akcja. Jeśli użyt-
kownik  nie  reaguje,  to  (zazwyczaj)  przegry-
wa,  gdyż  akcja  w  międzyczasie  toczy  się  da-
lej.  Innymi  słowy  –  brak  reakcji  użytkowni-
ka w grze nie oznacza, że będzie ona stać bez-
czynnie. W przypadku tego rodzaju aplikacji 
– wymagających ciągłej aktualizacji zarówno 
w warstwie graficznej (widok) jak i logicznej 
(model) – jako mechanizm sterujący stosuje 
się  tzw.  pętlę  gry.  Pętla  taka  przedstawiona 
jest w postaci pseudokodu na Listingu 9.

W dalszej części tego podpunktu przedsta-

wię, w jaki sposób można zrealizować wspo-
mnianą  pętlę  w  aplikacji  dedykowanej  dla 
Symbian OS. Już na samym początku trzeba 
mocno podkreślić, że blokująca pętla 

while

podobna do tej, którą pokazałem na Listingu 
9,  absolutnie  nie  wchodzi  w  rachubę.  Gdy-
by  ktoś  pokusił  się  o  jej  zastosowanie,  to  po 
kilkunastu  sekundach  miałby  okazję  zaob-
serwować  skutki  wystąpienia  niechlubnego, 
systemowego panika 

EVwsViewEventTimeOut

 

(panik to w nomenklaturze Symbian OS pe-
wien  rodzaj  assercji),  stanowiącego  zmorę 
wielu programistów symbianowych. W prak-
tyce  fakt  ten  objawia  się  wystąpieniem  sys-
temowej  notki  z  napisem  ViewSrv  11  i  za-
trzymaniem  aplikacji.  Aby  w  pełni  zrozu-
mieć  skąd  bierze  się  ten  komunikat,  należa-
ło by wyjaśnić ideę tzw.  aktywnych obiektów 
(ang.  active  objects),  czyli  mechanizmu  lek-
kich  wątków  zaimplementowanych  w  syste-
mie  Symbian.  Temat  ten  wykracza  jednak 
poza ramy niniejszego artykułu, zaś jego zro-
zumienie  nie  jest  konieczne  do  pisania  gier 
bazujących na przedstawionym tu szkielecie. 
Osoby  zainteresowane  pogłębieniem  swojej 
wiedzy w zakresie aktywnych obiektów odsy-
łam do ramki Bibliografia, w której przedsta-
wiłem listę tytułów zgłębiających między in-
nymi i tę tematykę.

Pisząc  w  dużym  uproszczeniu,  opisany 

wyżej  problem  wynika  z  faktu,  że  aktywne 
obiekty,  na  których  opiera  się  wielowątko-
wość w Symbian OS, nie podlegają wywłasz-
czaniu i kręcąca się bez końca pętla 

while()

 

sprawiłaby,  iż  inne  wątki  działające  w  sys-
temu  nie  mogłyby  dojść  do  głosu.  Kluczem 
do  rozwiązania  tego  problemu  jest  wyko-
rzystanie  tzw.  timera  (będącego  w  rzeczywi-
stości  aktywnym  obiektem),  czyli  mechani-
zmu,  który  powoduje  cykliczne  wywoływa-
nie  funkcji  zwrotnej  (ang.  callback).  W  na-
szym przypadku, każde takie wywołanie od-
powiadać będzie jednemu przebiegowi opisa-
nej wyżej pętli gry. W tym momencie, w gło-
wach niektórych Czytelników może pojawić 
się myśl o tym, aby wykorzystać wielokrotne 
timery  do  kontroli  logiki  aplikacji.  Pomysły 
takie należy raczej odrzucić – ich stosowanie 
prowadzi  do  powstawania  trudnych  proble-
mów synchronizacyjnych, zaś wynikowy kod 
trudno  się  rozwija  i  pielęgnuje.  Dodatkowo, 
używanie  wielu  aktywnych  obiektów  pro-
wadzi  do  zużywania  zasobów  jądra  systemu 
i znacznie obniża wydajność aplikacji. Jeden 
timer  stanowiący  serce  naszej  gry  będzie  jak 
najbardziej wystarczający. W tym momencie 
proponuję spojrzeć na Listing 10 zawierający 
interfejs klasy 

CGameSkeletonContainer

.

Do  zawartości  wspomnianego  Listingu 

będę  odnosił  się  wielokrotnie.  W  tym  miej-
scu  proponuję  zwrócić  uwagę  na  wskaź-
nik  do  obiektu 

CPeriodic

.  Obiekt  ten,  two-

rzony  dynamicznie  w  konstruktorze  klasy 

CGameSkeletonContainer

 (Listing 11) repre-

zentuje wspomniany timer.

Dostęp  do  obiektu  otrzymujemy  przez 

wskaźnik 

iTickGenerator

. Działanie timera 

jest bardzo proste. Tuż po stworzeniu jest on 
nieaktywny. Sposób jego uruchomienia przed-
stawiony jest na Listingu 12.

Na  początek  musimy  sprawdzić  czy  ti-

mer  nie  jest  przypadkiem  już  uruchomio-
ny.  Jeśli  ten  warunek  jest  spełniony  to  mo-
żemy  rozpoczynać  pracę.  Uruchomienie  ti-

Listing 18. Obsługa zdarzenia utraty aktywności

void

 

CGameSkeletonContainer

::

FocusChanged

(

    

TDrawNow

 

aDrawNow

 

)

    

{

    

if

 

(

 

IsFocused

()

 

)

        

{

        

StartTickGenerator

();

        

}

    

else

        

{

        

if

 

(

 

iTickGenerator

->

IsActive

()

 

)

            

{

            

StopTickGenerator

();

            

}

        

}

    

if

 

(

 

aDrawNow

 

)

        

{

        

DrawNow

();

        

}

    

}

background image

09/2008

72

Programowanie urządzeń mobilnych

Programowanie gier dla Symbian OS – szkielet aplikacji

www.sdjournal.org

73

mera  wykonujemy  za  pośrednictwem  funk-
cji 

Start()

.  Funkcja  ta  przyjmuje  trzy  argu-

menty  –  pierwszy  z  nich  określa  po  jakim 
czasie  timer  ma  ruszyć,  druga  definiuje  in-
terwał  pomiędzy  kolejnymi  tyknięciami  zaś 
ostatni  opakowuje  funkcję  zwrotną.  Obiekt 
opakowujący przyjmuje adres funkcji (w na-
szym  przykładzie  jest  to  statyczna  metoda 

CGameSkeletonContainer::Tick

)  oraz  adres 

obiektu, który do tej metody będzie przeka-
zywany  przy  każdym  jej  uruchomieniu.  W 
naszym przypadku jest to wskaźnik na siebie 
samego (

this

). Ten prosty trik pozwala nam 

przekazać  do  funkcji  zwrotnej  obiekt,  który 
kontroluje  timer.  Spójrzmy  teraz  na  defini-
cję  funkcji 

CGameSkeletonContainer::Tick

 

(Listing 13).

Ta  statyczna  metoda  rzutuje  otrzymany 

wskaźnik  typu 

TAny*

  (symbianowy  odpo-

wiednik  typu 

void*

  –  ramka  zatytułowana 

Symbian  a  definicje  typów)  na  wskaźnik  do 
kontenera (

CGameSkeletonContainer*

) i wy-

wołuje na nim metodę 

OnTick()

. Ta ostatnia 

jest  już  zwyczajną  metodą  niestatyczną  i  ja-
ko taka posiada dostęp do wszystkich skład-
ników klasy. W metodzie tej możemy umie-
ścić ciało pętli gry.

Uważni  czytelnicy  analizujący  implemen-

tację  metod  klasy 

CGameSkeletonContainer

 

zauważyli  być  może  pewien  potencjalny  de-
fekt.  Chodzi  o  to,  że  w  konstruktorze  kla-
sy  nie  występuje  inicjacja  wszystkich  skła-
dowych  (np. 

iTickStart

iTickStop

  czy 

iKeyState

).  Wbrew  pozorom,  efekt  ten  jest 

zamierzony i wiąże się z tym, że składowe w 
klasach oznaczonych prefiksem 

C

, czyli – we-

dług  symbianowych  konwencji  kodowania 
–  dziedziczących  po  klasie  bazowej 

CBase

są  automatycznie  zerowane  przez  konstruk-
tor 

CBase

.

Zanim  przejdziemy  do  omawiania  kolej-

nych  zagadnień,  warto  jeszcze  ustosunkować 
się  do  wykorzystania  stałej   

KDesiredFrames

PerSecond.

 Stała ta określa częstotliwość tyka-

nia naszego timera i co za tym idzie – maksy-
malną liczbę ramek rysowanych w trakcie jed-
nej sekundy, czyli tzw. współczynnik FPS (ang. 
Frames Per Second). W przypadku prezentowa-
nego  przykładu  wartość  wspomnianej  stałej 
wynosi  1000000/30.  1000000  określa  licz-
bę  mikrosekund  w  jednej  sekundzie  (timer 
przyjmuje  interwały  zapisane  w  mikrosekun-
dach). W tej sytuacji zakładamy, że timer uru-
chomi funkcję 

OnTick()

 około trzydziestu razy 

w trakcie jednej sekundy. Dociekliwi czytelni-
cy mogą zastanawiać się dlaczego dobrałem tę 
wartość w taki, a nie inny sposób. Oto wyjaśnie-
nie. W systemie Symbian w wersji 9 standardo-
wa  rozdzielczość  timera  wynosi  1/64  sekundy 
(dokładnie  15.625  milisekund)  –  ogranicze-
nie to dotyczy zarówno emulatora jak i sprzę-
tu. Inaczej mówiąc – timer nie jest w stanie ty-
kać
 szybciej niż 64 razy na sekundę. W tej sytu-
acji, jeśli ustawimy interwał timera na 20 mili-

sekund, to tyknięcia i tak będą występować co 
około 30 milisekund (2 x 15.625 milisekund). 
Stąd wniosek, że w celu uzyskania równomier-
nego  tykania  warto  przydzielać  interwały  o 
wielokrotności  15  milisekund.  Dobrana  prze-
ze mnie wartość daje stosunkowo równomier-
ną częstotliwość odświeżania ekranu na pozio-
mie 30/31 FPS. Częstotliwość ta jest stosunko-
wo optymalna dla większości gier uruchamia-
nych na telefonach komórkowych.  Pętlę gry, w 
zależności  od  potrzeb,  można  zaimplemento-
wać na bazie innych timerów, np. 

CHeartBeat

CTimer

 czy 

CIdle

. Niektóre, alternatywne roz-

wiązania  w  tym  zakresie  przedstawię  w  kolej-
nych  artykułach  z  cyklu  Programowanie  Gier 
Dla Symbian OS
.

Rysowanie 

System Symbian oferuje cały szereg API prze-
znaczonych  do  rysowania.  Najprostszy  spo-
sób  renderowania  grafiki  wiąże  się  z  wyko-
rzystaniem  standardowego  kontekstu  gra-
ficznego  (

CWindowGc

).  Dostęp  do  tego  kon-

tekstu można uzyskać następująco:

CWindowGc& gc = SystemGc();

Za  odrysowanie  kontrolki  na  ekranie  odpo-
wiada  funkcja 

Draw()

  w  klasie  ją  reprezen-

tującej.

Do  funkcji  tej  przekazywany  jest  prostokąt 

określający  fragment  ekranu,  na  którym  kon-
trolka  może  rysować  (w  naszym  przypadku 
jest to pełny obszar wyświetlacza). Na Listingu 
14  przedstawiona  jest  implementacja  metody 

CGameSkeletonContainer::Draw()

. Do zawar-

tości tego Listingu powrócimy za moment.

Kolejna  alternatywna  metoda  rysowania 

polega  na  bezpośrednim  odwoływaniu  się 
do bufora ekranu przy pomocy dedykowane-

go API (DSA, czyli Direct Screen Access), bądź 
wykorzystaniu biblioteki OpenGL ES. W na-
szym  przykładzie    wykorzystane  jest  jeszcze 
inne  podejście,  powszechnie  znane  jako  po-
dwójne  buforowanie  (ang.  double  buffering). 
Podejście to w kontekście Symbian OS może 
być z powodzeniem wykorzystywane zarów-
no  przy  implementacji  stosunkowo  statycz-
nych  gier  logicznych,  jak  i  szybkich,  dyna-
micznych  gier  akcji.  W  przypadku  stosowa-
nia  tej  techniki  aplikacja  wykorzystuje  tzw. 
tylny bufor (ang. back buffer), na którym ry-
suje się scenę. Później bufor ten jest przeno-
szony do bufora ramki (czytaj: na ekran) przy 
pomocy  jednej,  szybkiej  operacji  binarnego 
kopiowania (ang. blitting). Z racji tego, że po-
jedyncze operacje rysowania w buforze ram-
ki są zazwyczaj bardzo czasochłonne, to ryso-
wanie  poszczególnych  elementów  sceny  gry 
bezpośrednio  na  ekran  jest  zazwyczaj  wą-
skim  gardłem  wydajnościowym  i  na  doda-
tek  powoduje  nieprzyjemne  dla  oka  migota-
nie.  Korzystając  z  techniki  podwójnego  bu-
forowania  unikamy  obydwu  problemów.  W 
przypadku aplikacja GameSkeleton, do imple-
mentacji  opisanej  wyżej  techniki  wykorzy-
stamy klasę 

CWsBitmap

, która posłuży nam ja-

ko tylny bufor. Kod odpowiedzialny za two-
rzenie  i  inicjację  obiektu  tej  klasy  znajduje 
się  w  metodzie 

CGameSkeletonContainer::

ConstructL

  (Listing  11).  Oprócz  samej  bit-

mapy  (

iBackupBitmap

)  potrzebne  są  nam 

dwie  dodatkowe  składowe: 

iBitmapDevice

 

oraz 

iBitmapGc

.  Obiekty  reprezentowane 

przez  te  składowe  niezbędne  są  do  stworze-
nia  kontekstu  graficznego  związanego  z  bit-
mapą reprezentującą tylny bufor. W rezulta-
cie  możemy  używać 

iBitmapGc

  jak  zwyczaj-

nego kontekstu graficznego, zaś wyniki prze-
prowadzanych na nim operacji zostaną zapi-

Konwencje oraz idiomy kodowania
przy programowaniu aplikacji dla Symbian OS

Symbian  narzuca  na  swoich  programistów  cały  szereg  konwencji  oraz  idiomów  kodowa-
nia,  które  dość  znacząco  wpływają  na  postać  kodu  źródłowego  aplikacji  tworzonych  pod 
ten  system.  Jeśli  chodzi  o  konwencje  kodowania,  to  najbardziej  w  oczy  rzucają  się  defini-
cje typów podstawowych (TInt, TReal itd.); prefiksy w nazwach klas, atrybutów i składowych 
oraz postfiksy w nazwach funkcji. Co do nazw klas – wyróżniane są cztery podstawowe ich 
rodzaje  i  w  tym  kontekście  stosuje  się  różne  nazewnictwo.  Klasy  T  reprezentują  typu  pod-
stawowe  oraz  ich  złożenia,  klasy  C  alokują  zasoby,  klasy  M  reprezentują  interfejsy  zaś  kla-
sy R – uchwyty do zasobów. Najczęściej spotykany postfiks w nazwach funkcji to L – ozna-
cza  on  tzw.  funkcje  leave'ujace.  Mechanizm  ucieczki  (ang.  leave)  to  symbianowa  podróbka 
wyjątków.  Z  mechanizmem  tym  wiąże  się  idiom  dwufazowej  konstrukcji  obiektów  (metody 

NewL()

/

NewLC()

/

ConstructL()

) oraz tzw. stos czyszczenia (ang. cleanup stack). Wszystkie te 

konstrukcje  stosuje  się  o  to,  aby  uniknąć  gubienia  zasobów  systemowych  (głównie  pamię-
ci). Co do symbianowych konwencji kodowania, to trzeba przyznać, że posiadają one swo-
je  plusy  (np.  narzucają  spójność  i  porządek  w  kodach  źródłowych).  Idiomy  kodowania  nie-
stety wypadają nieco gorzej – stanowią one zazwyczaj źródło nadmiarowej pracy i utrudnia-
ją pielęgnację programów. Pozytywne jest to, że Symbian dostrzega powoli potrzebę stoso-
wania alternatyw w odniesieniu do idiomatycznego sposobu kodowania i wspiera nowe roz-
wiązania (np. wtyczki Open C/C++). W kolejnych odsłonach cyklu przedstawię bliżej te spo-
śród  symbianowych  idiomów  kodowania,  które  są  niezbędne  dla  programistów  gier.  Opo-
wiem  także  o  wspomnianych  wtyczkach  (Open  C/C++),  które  dla  tychże  programistów  są 
szczególnie ciekawe.

background image

09/2008

74

Programowanie urządzeń mobilnych

www.sdjournal.org

75

sane (czytaj: narysowane) w 

iBackupBitmap

W  tym  miejscu  warto  wrócić  do  przedsta-
wionej  wcześniej  funkcji  odrysowującej  za-
wartość  kontrolki  na  ekranie  (Listing  14). 
Jak  widać  na  wspomnianym  listingu,  funk-
cja  ta  odpowiada  jedynie  za  skopiowanie  za-
wartości tylnego bufora na ekran. Operację tę 
realizujemy przy pomocy funkcji 

BitBlit()

W tym momencie warto przeanalizować ko-
lejność operacji związanych z rysowaniem w 
naszej przykładowej aplikacji. Kluczową rolę 
w tym procesie odgrywa kod zawarty w me-
todzie 

CGameSkeletonContainer::OnTick()

 

(Listing 15).

Na  początku  tej  metody  wyliczamy  różnicę 

czasu, na podstawie której uaktualniać będzie-
my logikę gry – zakładamy, że odpowiadać bę-
dzie za to funkcja 

Update()

, która jest wywoły-

wana w dalszej kolejności. Następnie mamy wy-
wołanie funkcji 

Draw()

. Warto w tym miejscu 

zauważyć, że wywołana funkcja ma sygnaturę:

void  CGameSkeletonContainer::Draw(  CGra-
phicsContext& aGc ) const;

Innymi słowy – nie jest to systemowa funkcja 
rysująca  zawartość  kontrolki  na  ekranie,  tyl-
ko zdefiniowana przez użytkownika metoda 
służąca do renderowania tylnego bufora. Ge-
neralnie,  przy  programowaniu  gier  w  Sym-
bian OS dobrze jest definiować w klasach re-
prezentujących  wizualne  elementy  aplikacji 
metody  rysujące,  które  przyjmują  argument 
o  typie 

CGraphicsContext

,  stanowiący  klasę 

bazową  dla  wszystkich  kontekstów  graficz-
nych.  Jeśli  skorzystamy  z  tego  interfejsu,  to 
wynikowy  kod  źródłowy  będzie  niezależny 
od zastosowanej metody renderowania.

Analizując  zawartość  klasy 

CGameSkele

tonContainer

, warto zwrócić uwagę na skła-

dową 

iFpsCounter

. Składowa ta jest wskaźni-

kiem  do  obiektu  klasy 

CFpsCounter

.  W  ra-

mach  tej  klasy  zaimplementowano  prosty 
mechanizm  zliczania  ilości  narysowanych 
klatek  na  sekundę  (ang.  frames  per  second
FPS).  Z  punktu  widzenia  klientów  tej  klasy 
najistotniejsze jest wywoływanie na jej obiek-
cie metody 

Update()

 po narysowaniu każdej, 

kolejnej  klatki  (co  też  czynimy  w  prezento-
wanym przykładzie – Listing 15). Na Listin-
gu  16  pokazana  jest  implementacja  metody 

Draw()

, odpowiedzialnej za uaktualnianie za-

wartości tylnego bufora. 

Pierwsza  część  metody 

Draw()

  odpowia-

da  za  zamalowanie  całego  prostokąta  ekra-
nu czarnym kolorem. Część druga odpowia-
da za rysowanie licznika FPS (wartość liczni-
ka pobierana jest z obiektu reprezentowane-
go przez składową 

iFpsCounter

). Rysowanie 

za pomocą kontekstu graficznego (

CGraphic-

sContext

)  jest  stosunkowo  nieskomplikowa-

ne i nie odbiega specjalnie od konwencji spo-
tykanych na innych platformach. Ciekawość 
Czytelników  może  pobudzić  za  to  zmienna 
lokalna zdefiniowana jako:

TBuf< KFpsMessageMaxLength > fpsMessage;

Zmienna ta jest przykładem tzw. deskrypto-
ra – czyli obiektu reprezentującego napis w 
programach  pisanych  pod  Symbian  OS.  W 
porównaniu do standardowego szablonu re-
prezentującego  napisy  w  języku  C++  (

std:

:string

),  deskryptory  to  dość  skompliko-

wany  twór  i  na  tym  etapie  pominiemy  ich 
szczegółowy opis. Do tematu wrócimy w ko-
lejnych odcinkach cyklu.

Podsumowując  zawartość  tego  podpunk-

tu  warto  zauważyć,  że  przedstawiony  tu  me-
chanizm  rysowania  nie  jest  najbardziej  efek-
tywnym  z  możliwych,  aczkolwiek  reprezen-
tuje  złoty  środek  pomiędzy  prostotą  imple-

mentacji,  a  wydajnością.  Bardziej  zaawanso-
wane  techniki  renderowania  w  Symbian  OS 
planuję zaprezentować w jednym z kolejnych 
odcinków cyklu.

Interakcja z użytkownikiem

Interakcja  z  użytkownikiem  w  systemie 
Symbian  OS  odbywa  się  przede  wszystkim 
przy  pomocy  klawiatury.  Jak  już  wcześniej 
wspominałem,  w  naszym  szkielecie  gry  za 
obsługę  tejże  interakcji  odpowiada  klasa 

CGameSkeletonContainer

Implementacja 

tego  mechanizmu  umieszczona  jest  w  funk-
cji 

OfferKeyEventL

 (Listing 17).

Funkcja  ta  jest  asynchronicznie  wywo-

ływana  przez  silnik  aplikacji  w  przypad-
ku  występowania  kolejnych  zdarzeń  klawia-
tury.  W  naszym  przypadku  system  obsłu-
gi wspomnianych zdarzeń opiera się na uak-
tualnianiu odpowiednich bitów w składowej 

iKeyState

.  W  przypadku  gdy  dany  klawisz 

jest  wciśnięty,  to  odpowiedni  bit  w  tej  skła-
dowej zostaje zapalony. W sytuacji odwrotnej 
– bit jest gaszony. Maski dla poszczególnych 
klawiszy zdefiniowane są w oddzielnym pli-
ku nagłówkowym (KeyState.h), tak aby moż-
na było je wykorzystywać wielu plikach źró-
dłowych.  Testowanie  wciśnięcia  danego  kla-
wisza  można  łatwo  zrealizować  przy  pomo-
cy  operatorów  bitowych.  Dla  przykładu,  na-
stępujący fragment kodu testuje czy wciśnię-
ty został klawisz 7:

if ( iKeyState & KKey7 ) { /* ... */ }

Prezentowana  aplikacja  obsługuje  klawisze 
kierunkowe  oraz  enter,  a  także  klawisze  ste-
rujące  (tzw.  softkeys  –  duże  klawisze  znajdu-
jące  się  zazwyczaj  bezpośrednio  pod  wyświe-
tlaczem  telefonu  komórkowego)  oraz  klawi-
sze numeryczne. Warto w tym miejscu dodać, 
że w aplikacjach Symbian OS równoczesna ob-
sługa  wielu  klawiszy  jest  domyślnie  wyłączo-
na.  Aby  włączyć  tę  obsługę,  należy  dodać  w 
konstruktorze klasy 

CGameSkeletonAppUi

 (Li-

sting 4) wywołanie:

CAknAppUi::SetKeyBlockMode( ENoKeyBlock );

Niestety,  metoda  ta  dostępna  jest  tylko  na 
platformie Series60.

Interakcja z systemem

Dość  specyficzną  cechą  dotyczącą  progra-
mowania  gier  na  urządzenia  mobilne,  jest 
obsługa  tzw.  przerwań  (bądź  interrupcji). 
Wyobraźmy  sobie,  że  podczas  trwania  akcji 
gry  dzwoni  telefon.  W  tej  sytuacji  warto  by 
wstrzymać pętlę gry – przynajmniej do cza-
su zakończenia rozmowy. Powodów ku temu 
jest wiele. Najważniejsze z nich to:

•   automatyczna  ochrona  użytkownika  przed 

skutkami nagłego wyrwania się z akcji gry – 

W Sieci

•   ftp://ftp.activestate.com/ActivePerl/Windows/5.6/ActivePerl-5.6.1.635-MSWin32-x86.msi 

– 

dystrybucja Perla potrzebna do tworzenia aplikacji dla Symbian OS

•   http://java.sun.com/ – stąd można pobrać środowisko uruchomieniowe języka Java
•   http://www.forum.nokia.com/  –  portal  dla  programistów  piszących  oprogramowania  dla 

telefonów  firmy  Nokia;  można  stąd  pobrać  bezpłatne  narzędzia  programistyczne  wyko-
rzystywane w niniejszym artykule

•   http://www.newlc.com/  –  portal  programistów  aplikacji  dla  Symbain  OS;  można  tu  zna-

leźć cały szereg interesujących informacji – zarówno dla początkujących jak i zaawanso-
wanych

Bibliografia

•   Developing Software for Symbian OS: An Introduction to Creating Smartphone Applications 

in C++; Steve Babin; John Wiley & Sons, 2005. Ta pozycja dobra jest dla Czytelników, któ-
rzy  chcieliby  stosunkowo  szybko  poznać  podstawy  programowania  aplikacji  dla  Sym-
bian OS

•   Symbian OS Explained: Effective C++ Programming for Smartphones; Jo Stichbury; John 

Wiley & Sons, 2004. Ta pozycja dobra jest dla czytelników, którzy chcieli by poznać bar-
dziej  specjalistyczne  tematy  związane  z  programowaniem  Symbian  OS,  np.  szczegóły 
dotyczące implementacji i korzystania z aktywnych obiektów

background image

09/2008

74

Programowanie urządzeń mobilnych

www.sdjournal.org

75

można  w  tym  przypadku  założyć,  że  mało 
kto będzie w stanie kontynuować grę w trak-
cie rozmowy;

•   oszczędzanie  energii  telefonu  –  wszak 

kręcąca się pętla gry tę cenną energię zu-
żywa.

Na  dodatek,  Symbian  jest  systemem  wielo-
zadaniowym  i  użytkownik  może  w  każdej 
chwili  przeskoczyć  do  innej  aplikacji –  w  tej 
sytuacji  spauzowanie  pętli  gry  byłoby  rów-
nież  jak  najbardziej  na  miejscu.  Wszystkie 
omówione  powyżej  sytuacje  można  uogól-
nić  do  jednego  systemowego  zdarzenia  ja-
kim jest utrata aktywności (ang. focus). Zda-
rzenie  to  występuje  w  aplikacji  wtedy,  gdy 
inna  aplikacja  przejmuje  kontrolę  (mogliby-
śmy  użyć  analogii  ze  świata  Windows  i  po-
wiedzieć:  kiedy  inne  okienko  wyskakuje  na 
wierzch
). Tak właśnie dzieje się, kiedy w tele-
fonie  symbianowym  rozpoczyna  się  rozmo-
wa  (za  obsługę  połączenia  głosowego  odpo-
wiada  dedykowana  aplikacja),  tak  samo  jest 
gdy  użytkownik  wybierze  z  listy  zadań  in-
ny program. 

Zdarzenie  utraty  aktywności  obsługuje-

my  z  poziomu  klasy  kontrolki  (

CGameSke-

letonContainer

).  W  przypadku  wystąpie-

nia  tego  zdarzenia,  silnik  aplikacji  asyn-
chronicznie  wywoła  na  kontrolce  funkcję 

FocusChanged()

. Przyjrzyjmy się, jak funkcja 

ta  jest  zaimplementowana  w  naszej  kontrol-
ce (Listing 18).

Implementacja  jest  prosta:  na  począt-

ku  sprawdzamy  czy  aplikacja  utraciła  bądź 
odzyskała  kontrolę  (służy  do  tego  funk-
cja 

IsFocused()

).  W  pierwszym  z  tych 

dwóch  przypadków  wywołujemy  pomocni-
czą  funkcję 

StopTickGenerator()

,  spraw-

dziwszy  uprzednio  czy  aby  nasz  timer 
wcześniej  był  uruchomiony  (

iTickGenera-

tor->IsActive()

).  Implementacja  funkcji 

StopTickGenerator()

  jest  trywialna  i  spro-

wadza  się  do  wywołania  funkcji 

Cancel()

 

na obiekcie timera, co powoduje jego zatrzy-
manie:

void  CGameSkeletonContainer::StopTickGe-
nerator()
    { iTickGenerator->Cancel(); }

W  przypadku  odwrotnym  (tj.  kiedy  apli-
kacja 

odzyskuje 

kontrolę) 

wywołuje-

my  omówioną  już  wcześniej  funkcję 

StartTickGenerator()

,  odpowiedzialną  za 

uruchomienie  timera  (Listing  12).  Dzięki 
temu  prostemu  zabiegowi,  jesteśmy  w  sta-
nie  obsłużyć  cały  szereg  wydarzeń  zakłóca-
jących naszą grę.

Oczywiście,  interakcja  z  systemem  nie 

kończy  się  na  obsłudze  zdarzenia  utraty 
kontroli  przez  aplikację.  Kolejne  przydatne 
mechanizmy  związane  z  tą  tematyką  (mię-
dzy  innymi  obsługę  zmiany  orientacji  ekra-

nu oraz wykrywanie braku aktywności użyt-
kownika) będę omawiał w kolejnych artyku-
łach z cyklu.

Testujemy!

Nadszedł  wreszcie  upragniony  moment 
przetestowania  naszej  przykładowej  aplika-
cji. Aby tego dokonać, należy wykonać nastę-
pujący szereg czynności. Na początek aplika-
cję trzeba zbudować. W tym celu posłużymy 
się środowiskiem Carbide.C++. Archiwum z 
aplikacją należy rozpakować, a następnie za-
importować  do  Carbide  korzystając  z  opcji 
File –> Import –> Symbian OS –> Symbian OS 
Bld.inf
  file.  Wspomniany  plik  Bld.inf  znaj-
duje  się  w  podkatalogu  group  projektu.  Ro-
lę  tego,  oraz  pozostałych  plików  konfigura-
cyjnych  w  projektach  symbianowych  omó-
wię  w  kolejnym  odcinku  z  serii  moich  ar-
tykułów.  Teraz  skupimy  się  na  uruchomie-
niu  aplikacji.  Po  zaimportowaniu  projek-
tu należy go zbudować (Project –> Build Pro-
ject
).  Jako,  że  domyślnie  ustawioną  konfigu-
racją  w  tym  projekcie  jest  Emulator  Debug
to  efektem  naszego  ostatniego  działania  bę-
dzie stworzenie binarnej wersji naszej aplika-
cji działającej pod emulatorem. Przy pomocy 
komendy  Run  –>  Run  (Ctrl  +  F11)  możemy 
obejrzeć efekt działania aplikacji GameSkele-
ton
 (Rysunek 2).

Wspomniany  efekt  –  czyli  czarny  ekran  z 

narysowanym licznikiem FPS – nie jest mo-
że  porażający,  aczkolwiek  mając  na  uwadze 
przedstawioną  wyżej  analizę,  wiemy,  że  stoi 
za nim dość obszerna mechanika.

W  dalszej  kolejności  warto  by  zająć  się 

uruchomieniem  naszego  przykładu  na  fi-
zycznym  urządzeniu.  Niestety,  ze  wzglę-
du  na  dość  skomplikowany  proces  certyfi-
kacji (Symbiana wymaga, by każda aplikacja 
działająca na telefonie została podpisana od-
powiednim  certyfikatem  bezpieczeństwa), 
kwestię  tę  chwilowo  pominiemy  (wrócimy 
do niej w kolejnym artykule z cyklu).

Zadanie domowe

Przedstawiony  wyżej  szkielet  aplikacji  sta-
nowi  solidny  wstęp  do  bardziej  zaawanso-
wanych i ciekawszych tematów związanych z 
programowaniem gier. Czytelników, którym 
przypadł  do  gustu  niniejszy  tekst  i  planują 
kontynuować  lekturę  mojego  cyklu  w  kolej-
nym numerze SDJ, zachęcam do wykonania 
kilku  ćwiczeń  związanych  z  przedstawiony-
mi tu materiałami. Po pierwsze, warto zapo-
znać się bliżej z narzędziem Carbide, a szcze-
gólnie z debuggerem. Jako pierwszą część za-
dania  domowego  zalecam  powstawianie  w 
różnych  miejscach  naszej  przykładowej  apli-
kacji  punktów  wstrzymania  (ang.  breakpo-
ints
), uruchomienie jej w trybie debug (F11
i  przeprowadzenie  praktycznych  obserwacji 
mechaniki  działania  szkieletu  (pętla  głów-
na, zdarzenia itd.). Jako drugie zadanie, pro-

ponuję  zapoznać  się  z  metodami  dostępny-
mi  w  ramach  klasy 

CGraphicsContext

.  Ko-

rzystając  ze  wspomnianych  metod  warto  w 
ramach  ćwiczenia  zaimplementować  me-
chanizm  wizualizacji  zdarzeń  klawiatury. 
W tym celu można narysować zbudowaną z 
prymitywów graficznych oraz tekstów imita-
cję
 klawiatury telefonu i w zależności od sta-
nu zmiennej 

iKeyState 

podświetlać (np. ry-

sować  innym  kolorem)  te  jej  fragmenty,  dla 
których  wystąpiło  naciśnięcie  klawisza.  Me-
chanizm  ten  można  wykorzystać  do  przete-
stowania  systemu  obsługi  zdarzeń  klawiatu-
ry – warto zaimplementować go tak, aby we-
ryfikował  obsługę  wciśnięć  wielu  klawiszy 
jednocześnie.

Podsumowanie

W niniejszym artykule przedstawiłem szkie-
let aplikacji dla Symbian OS, stanowiący so-
lidny punkt wyjścia przy pisaniu gier. W ko-
lejnych podpunktach pokazałem jak oprogra-
mować  pętlę  gry,  renderowanie  zawartości 
ekranu,  prosty  system  obsługi  zdarzeń  kla-
wiatury oraz podstawowe mechanizmy inte-
rakcji z systemem. W kolejnym artykule z cy-
klu pokażę jak:

•   zaimplementować prostą grę bazującą na 

przedstawionym szkielecie;

•   zaprogramować operacje odczytu i zapi-

su danych z/do plików;

•   dołączyć  do  przedstawionego  szkieletu 

obsługę dźwięku;

•   wykorzystać  idiomy  programistyczne 

Symbian OS przy tworzeniu gier;

•   uruchomić grę na telefonie.

Na  koniec  chciałbym  wymienić  osoby,  któ-
re dzięki swoim wnikliwym recenzjom przy-
czyniły  się  do  znacznej  poprawy  jakości  ni-
niejszego  artykułu.  Wspomniane  osoby  to: 
David de Rosier, Jacek Noe Cybularczyk oraz 
Mateusz Kula. Chciałbym również podzięko-
wać  mojej  ukochanej  żonie  –  Oli,  za  cierpli-
wość,  którą  okazała  mi  w  trakcie  pisania  te-
go artykułu.

RAFAŁ KOCISZ

Pracuje  na  stanowisku  Dyrektora  Techniczne-
go  w  firmie  Gamelion,  wchodzącej  w  skład  Gru-
py  BLStream.  Rafał  specjalizuje  się  w  technolo-
giach  związanych  z  produkcją  oprogramowa-
nia  na  platformy  mobilne,  ze  szczególnym  na-
ciskiem  na  tworzenie  gier.  Grupa  BLStream  po-
wstała  by  efektywniej  wykorzystywać  potencjał 
dwóch,  szybko  rozwijających  się  producentów 
oprogramowania  –  BLStream  i  Gamelion.  Firmy 
wchodzące  w  skład  grupy  specjalizują  się  w  wy-
twarzaniu oprogramowania dla klientów korpo-
racyjnych, w rozwiązaniach mobilnych oraz pro-
dukcji i testowaniu gier. 
Kontakt z autorem: rafal.kocisz@gmail.com

Programowanie gier dla Symbian OS – szkielet aplikacji