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
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
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
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ą-
cą 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
();
}
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
();
}
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
;
}
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
();
}
}
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.
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
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