Sie sind auf Seite 1von 90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

MAGAZIN

bersicht

Forenregeln

Mods + Admins

LOGINHARDWARE
APPS

FORUM

Wiki

Community
Seite

Diskussion

Quelltextbetrachten

Versionen/Autoren

Spieleentwicklung101

FOLG

Kateg
Spielentwicklung101vonMarioZechnerstehtuntereinerCreativeCommonsNamensnennungNicht
kommerziellWeitergabeuntergleichenBedingungen3.0UnportedLizenz .
Inhaltsverzeichnis[Verstecken]
1Danksagung
2Einleitung
3Wasmussichvorabwissen?
4Wofangichan?
4.1DasApplikationsgerst
4.2DasEingabeModul
4.3DasDateiI/OModul
4.4DasGrafikModul
4.5DasSoundModul
4.6DasNetzwerkModul
4.7DasSimulationsModul
5UndaufAndroid?
5.1AndroidActivity
5.2TouchScreen&Accelerometer
5.3Ressourcen,AssetsunddieSDKarte
5.3.1Ressource
5.3.2Assets
5.3.3SDKarte
5.4OpenGLES
5.4.1GrundlegendeszurGrafikprogrammierung
5.4.2EinwenigMathematik
5.4.3DasersteDreieck
5.4.4Farbspiele
5.4.5Texturen
5.4.6Mesh&TexturKlasse
5.4.7Projektionen
5.4.8Kamera,ZBufferundwielscheichdenSchirm
5.4.9LichtundSchatten
5.4.10Transformationen
5.4.11Textzeichnen
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

1/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

5.4.12Meshesladen
5.5SoundPoolundMediaPlayer
6SpaceInvaders
6.1AnalysedesOriginals

Spezi

6.2DasSpielfeld
6.3DieSimulation
6.3.1BlockKlasse
6.3.2ExplosionKlasse
6.3.3ShotKlasse
6.3.4ShipKlasse
6.3.5InvaderKlasse
6.3.6SimulationKlasse
6.4DasRendering
6.4.1RendererKlasse
6.5SoundundMusik
6.5.1SoundManagerKlasse
6.6SpaceInvadersActivity&Screens
6.6.1StartScreenKlasse
6.6.2GameOverScreenKlasse
6.6.3GameLoopKlasse
6.6.4SpaceInvadersActivity
6.7PerformanceTipps
6.8AbschlieendeWorte
6.8.1AllgemeineSpieleentwicklung
6.8.2OpenGLES

Danksagung
GroerDankgebhrtAntoniaWagner.SiehatinminutiserKleinstarbeitalldiegroenundkleinen
orthographischenundgrammatischenDrachenausdiesemArtikelentfernt.DankeAntonia!

Einleitung
DieserArtikelsollinmehrerenEtappenandasThemaSpieleentwicklungheranfhren.Eineallgemeine
EinfhrungindieThematikbildetdabeidenGrundstock.DiesesollGrundbegriffeundMechanismenimberblick
erklren,anschlieendwerdendieeinzelnenTeilaufgabenbeiderEntwicklungeinesSpielserklrt.Abschlieend
werdendiesoerarbeitetenThemenineinekleineSpaceInvadersVariantegegossen.
AlskleineVorwarnung:Ichschreibeseitca.zehnJahrenkleinereundgrereSpiele.MeineHerangehensweise
istsichernichtdieOptimalste.AuchknnenmirimRahmendiesesArtikelsfaktischeFehlerunterlaufen,ichwerde
aberversuchen,diesezuvermeiden.Auchwerdeich,woangebracht,Anglizismenverwenden,dadiesedas
GooglennachweiterfhrendemMaterialerleichtern.Auerdemwerdeich,frBegriffe,diemaneventuell
Nachschlagenmchte,einigeWikipediaLinkseinbauen.Also:Losgeht's.

Wasmussichvorabwissen?
IndiesemArtikelsetzeicheinpaarDingealsMinimumvoraus.KeineAngst,hhereMathematikgehrtnichtdazu
)
Java
HandhabungvonEclipse
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

2/90

2/26/2015

InstalliertesAndroidSDK

Spieleentwicklung101AndroidWikiAndroidPIT

sowieEclipsePlugin

ActivityLifeCycle
ErstelleneinesneuenAndroidProjektsinEclipse
DenCodezudiesemTutorialknntihreuchperSVNvonderAdressehttp://android
gamedev.googlecode.com/svn/trunk/ holen.ImProjektgibteseineMainActivitydieeuchgleichwieindenSDK
DemoseineListeanApplikationenzeigt.EinfachdasProjektausdemSVNherunterladen,inEclipseimportieren
undeineRunConfigurationanlegenunddieDefaultLaunchActivitystarten.

Wofangichan?
AmAnfangjedesSpielsstehteineIdee.WirdeseinPuzzler?EinRundenstrategiespiel?EinFirstPerson
Shooter?AuchwenndieseGenresunterschiedlichernichtseinknnten,sounterscheidensiesichimGrunde
ihresDaseinsoftwenig.EinSpielkanninmehrereModulezurErledigungdiverserAufgabeneingeteiltwerden:
Applikationsgerst
EingabeModul
DateiI/OModul
GrafikModul
SoundModul
NetzwerkModul
SimulationsModul
ImFolgendenwollenwirunsmitdiesensechsModulenetwasgenauerbeschftigen.

DasApplikationsgerst
DasApplikationsgerststelltdieBasisfrdasSpieldar.InderRegelhneltdiesesherkmmlichen
Applikationsgerstennicht.SpielesindindenmeistenFllennichtEventbasiert,d.h.sielaufenstndig,zeichnen
dabeidieSpielweltpermanentneu,holensichdauerndneueBenutzereingabenundsimulierendieWelt(fr
Kartenspieleundhnlichesmussdiesnatrlichnichtgelten).WennmannichtgeradefreineKonsole
programmiert,stelltsicheinemjedochdasProblem,dassdiemeistenBetriebssystemeeventbasierte
ProgrammierungalsParadigmagewhlthaben.SoauchaufAndroid.ApplikationenwerdendabeinurbeiBedarf
neugezeichnet,zumBeispielwennderAnwenderTexteingibt,einenButtondrcktundsoweiter.Eswirdalsonur
dannCodeausgefhrt,wenneseineBenutzereingabegibt.ImAllgemeinenhebeltmandiesaus,indemman
einenseparatenThreadstartet,derdasBetriebssystemveranlasst,dieApplikationpermanentneuzuzeichnen.In
diesemThreadbefindetsichsogutwieimmereineSchleife,auchMainLoopgenannt,innerhalbderersichimmer
dasselbeabspielt.Dassiehtstarkvereinfachtsoaus:
while(!done)
{
processInput()
simulateWorld()
renderWorld()
}
WiedieserMainLoopgenauaussieht,hngtvonvielerleiFaktorenab,zumBeispieldemverwendeten
Betriebssystem,demSpielselbstundsoweiter.
ImRahmendiesesArtikelswerdenwirsehen,wiemandiesesKonzeptuersteinfachaufAndroid
implementierenkann.

DasEingabeModul
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

3/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

UmdemSpielerdieMglichkeitzugeben,indasSpielsystemeingreifenzuknnen,mssendessenEingaben
irgendwiegelesenwerden.DieseAufgabebernimmtdasEingabeModul.Tastaturen,Muse,TouchScreens,
Joysticks,GamepadsundeinigeandereexotischeMglichkeitenstehenhierzurVerfgung.Wiemanandie
Eingabekommt,istdabeiwiederbetriebssystemabhngig.
AndroidverfgtbereinigeEingabeMglichkeiten,wirwerdenunsmitdenbeidenwichtigstenbeschftigen
1. demTouchScreen
2. demAccelerometer

DasDateiI/OModul
AlleRessourceneinesSpielesmsseninirgendeinerFormderApplikationzugnglichgemachtwerden.In
speziellenFllenkanndasSpieldieseOntheflyzurLaufzeitprozeduralselbsterstellen,meistensliegendiese
aberinFormvonDateienaufeinemDatentrgervor.AuchhiergibtesverschiedeneMglichkeitenderAblage:
DateienknnenschngeordnetinOrdnernabgelegtwerden,wildineinemeinzigenOrdnergespeichertsein
odergarineinerZipDateifeinsuberlichgepacktvorliegen.DasI/OModulfrDateiensolldiesabstrahieren,
damitimProgrammcodederZugriffaufdieRessourcenerleichtertwird.
AndroidbietethiermitseinemRessourcenundAssetSystemschoneinennettenAnsatz,aufdaswirspternoch
zusprechenkommenwerden.

DasGrafikModul
InunserenmodernenZeitenistfrvieleSpieleentwicklerdieserTeileinesSpielswohlderwichtigste(teilszu
LastendesSpielspaes).DiesesModulbernimmtdieDarstellungsmtlichergrafischenInhaltedesSpieles,sei
diesdasUserInterface,welchesinderRegelzweidimensionalist,oderdieSpieleweltselbst,meistinderdritten
Dimension.DaLetzteresoftrechenintensivist,wirdspezielleHardwareverwendet,umdasGanzezu
beschleunigen.DieKommunikationmitdieserHardware,ihrklarzumachen,wie,wo,wasgezeichnetwerdensoll,
sowiedieVerwaltungvongrafischenRessourcen,wieBitmaps

undGeometry(auchMeshes

genannt)istdie

HauptaufgabediesesSystems.HierunterfallenauchDinge,wiedasZeichnenvonPartikelEffekten oderder
EinsatzvonsogenanntenShadern (aufAndroidnochnichtmglich).Ganzallgemeinkannfestgehalten
werden,dassdiemeistenObjekte,diesimuliertwerdenaucheinegrafischeEntsprechunghaben.Meiner
ErfahrungnachistesuersthilfreichdiesimulierteWeltkomplettunabhngigvondergrafischenDarstellungzu
machen.DasGrafikmodulholtsichlediglichInformationvonderWeltSimulation,hataberaufdiesekeinen
Einfluss.Wemdasetwaszuvageist:keineAngst,derZusammenhangsolltesptestensbeimEntwickelndes
SpaceInvaderKlonserkenntlichwerden.Esseijedochgesagt,dassdieserAnsatzeserlaubt,dasGrafikModul
beliebigauszutauschen,zumBeispielstatteiner2DDarstellungdasganzeauf3Dzuportieren,ohnedassdabei
derSimulationsteilgendertwerdenmuss.
AufmerksamenLesernistvielleichtderZusammenhangzwischendemGrafikModulunddemMainLoopbereits
aufgefallen.NeuenGrafikkartenwirdmeistmitBenchmarkszuLeibegerckt,diediesogenanntenFramesper
Second(kurzFPS)oderFrameRate

messen.Diesegebenan,wieoftderMainLoopineinerSekunde

durchlaufenwurde.Eswirdalsogezhlt,wieoftderMainLoop(Inputverarbeiten,WeltsimulierenunddasGanze
dannzeichnen)ineinerSekundedurchlaufenwird.ImZugeunsererUnternehmungwerdenwirimmereinAuge
aufdieFrameRatewerfen,umetwaigeEngpsseinunseremSpielidentifizierenzuknnen.
Spterwerdenwirsehen,wiewirAndroid2Dund3DGrafikenberOpenGLES

entlockenknnen.

DasSoundModul
SoundeffekteundMusikgehrenzujedemgutenSpiel.DementsprechendkmmertsichdasSoundModulum
dasAbspielensolcherRessourcen.DabeigibteszwischenSoundeffektenundMusikeinenwichtigen
Unterschied.SoundeffektesindinderRegelsehrklein(imKilobyteBereich)undwerdendirektimHauptspeicher
gehalten,dasieoftverwendetwerden.BeispielsweisedasFeuergeruscheinerKanone.Musikwiederumliegtoft
inkomprimierterFormvor(mp3,ogg)undbrauchtunkomprimiert(unddamitabspielbar)sehrvielSpeicherplatz.
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

4/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

Siewirddahermeistgestreamed,dasheitbeiBedarfstckweisevonderFestplatteodereinemanderenMedium
(DVD,Internet)nachgeladen.AufImplementationssbenemachtdiesofteinenUnterschied,dadieser
Nachlademechanismusmeistselbstrealisiertwerdenmuss.
InZeitenvonSurroundSoundHeimsystemenlegenSpieleentwicklerauchWertaufdreidimensionalenKlang.
Meistens,sowiebeiderGrafik,hardwarebeschleunigt.AndroidbietetdieseMglichkeitnochnicht,erlaubtaber
relativschmerzlosdasAbspielenvonSoundeffektenunddasStreamenvonMusik,wiewirspternochsehen
werden.

DasNetzwerkModul
SeitWorldofWarcraftundCounterStrikeistklar:anMultiplayerMglichkeitenkommtkeinmodernesSpielvorbei!
DasNetzwerkModulhatdabeigleichmehrereAufgabenzustemmen.AufdereinenSeitehandhabtesdie
KommunikationmitetwaigenServern,sendetMitteilungenvonSpielernherum,ldtvonSpielerngebastelte
LevelsinsNetzundsoweiter.DiessindquasiadministrativeAufgabenundhabennurindirektmitdem
Spielgeschehenselbstzutun.AufderanderenSeitegiltesSpieledaten,wieaktuellePositionen,dasAbfeuern
vonKugeln,dasentsendenvonTruppenundvielesmehr,dasdenanderenRechnernimNetzmitgeteiltwerden
muss,dieanderPartieteilnehmen.AbhngigvomGenredesSpielskommenhierverschiedeneMethodenzum
Einsatz,umdasSpielgeschehenzusynchronisieren.DieserThemenbereichistsogroundkomplex,dassihm
ambesteneineigenerArtikelgewidmetwerdensollte.ImRahmendiesesTexteswerdeichnichtweiteraufdiese
Komponenteeingehen.

DasSimulationsModul
DamitsichdieDingeimSpielbewegen,mussmansieauchirgendwieantreiben.DasistdieAufgabedes
Simulationsmoduls.EsbeinhaltetsmtlicheInformationenzumSpielgeschehenselbst,wiediePositionvon
Spielfiguren,derenaktuelleAktion,wievielMunitionnochbrigistundsoweiter.AufBasisder
Benutzereingaben,sowiederEntscheidungeneinermglicherweiseimplementiertenKnstlichenIntelligenz,wird
dasVerhaltenderSpielobjektesimuliert.DieSimulationluftdabeiimmerSchrittweiseab.InjedemDurchgang
desMainLoopswirddieSimulationumeinenSchrittvorangetrieben.Diespassiertzumeistzeitbasiert,dasheit,
mansimulierteinebestimmteZeitspanne.FreinweichesAblaufenwirdalsZeitspannemeistdieZeit,dieseit
demZeichnendesletztenFramesvergangenist,herangezogen.EinkleinesBeispiel:gegebeneine
Kanonenkugel,diemit10m/snachrechtsfliegt,schreitenwireinenSchrittinderSimulationweiter.DieZeitspanne
seitdemletztenFramebetrgt0.016s(16Millisekunden,entsprichteinerFrameRatevon60fps).10m/s*0.016s=
0.16m,dasheitdieKanonenkugelistnachAbschlussdiesesSimulationsschrittesum16Zentimeterweiterlinks,
imVergleichzumletztenFrame.DieseArtdesSimulationsschrittesnenntmanFrameIndependentMovement
undsollteBestandteiljedesSimulationsModulssein.WiederNameschonsagt,istesegal,wievielFPSdas
Spielschafft,dieKanonenkugelwirdsichaufallenSystemengleichverhalten(wennauchdieZwischenschritte
andereseinmgen).Esseiangemerkt,dassmanbeiVerwendungvonPhysikSystemenmeistfixeZeitschritte
verwendet,dadiekleinenSchwankungenbeimMessenderZeitspannezwischendemaktuellenunddemletzten
FramevielePhysikSystemeinstabilmachenknnen.WirwerdeninunseremSpaceInvadersKlonkeine
grandiosenPhysikspielereienimplementieren,daherbleibenwirbeiderherkmmlichenzeitbasiertenMethode,
diedieFrameZeitspanneheranzieht.
AbhngigvomSpieletypgehrtauchdieknstlicheIntelligenzzumSimulationsModul.Diesekannsehrsimpel
Ausfallen,zumBeispieldasVerhaltenderGoombasinSuperMario,dienurdummnachrechtsundlinkslaufen.In
Echtzeitstrategiespielenkanndieseschonumeinigeskomplexerwerden.DerTerminusknstlicheIntelligenzist
hierstrenggesehenauchnichtganzkorrekt,inErmangelungeinesbesserenBegriffsbleibenwirabereinfach
dabei.

UndaufAndroid?
WirwollennurfralldieobengenanntenModuleeineEntsprechungaufAndroidentwickeln.Wirbeginnenmitder
ActivityselbstundversuchendasMainLoopMusterdortzuimplementieren.DasVerwaltenvonDateienber
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

5/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

RessourcenundAssetswerdenwirunsalsnchstesansehen.AnschlieendwerdenwirunsnhermitOpenGL
ESbeschftigenundAndroiddazubringen,frunsinteressanteDingezuzeichnen.DieAusgabevon
SoundeffektenundMusikbildetdenAbschlussdiesesKapitels,womitwirdannfrunserenSpaceInvadersKlon
gerstetsind.
ImZugediesesKapitelswerdenwirwiederverwendbareKomponentenentwickeln,schlielichwreesnicht
sinnvoll,jedesMaldasRadneuzuerfinden.AlleCodesknntihrunter[http://code.google.com/p/android
gamedev/http://code.google.com/p/androidgamedev/

]findenundperSVNauschecken.DasProjektbeinhaltet

einpaarBeispielprogrammezudeneinzelnenAbschnitten,sowiedenSpaceInvadersKlonselbst.

AndroidActivity
DasGrundgerstunseresSpielsbildeteinesimpleActivity.DabeiergibtsicheinklassischesHenne/EiProblem:
wirmssenhierschonmitOpenGLESbeginnen,ohneunsdamitauszukennen.AberkeineAngst,dasGanze
erweistsichalsrelativeinfach.
ZieldiesesKapitelswirdessein,einelauffhigeOpenGLESActivityzuerstellen,diedengrundlegendenActivity
LifeCyclerespektiert.SeitSDK1.5gibtesdiesogenanntenGLSurfaceView .SieisteinGUIBausteinund
hnelteinerListView,diemaneinfachindieActivityeinhngt.DieInitialisierungvonOpenGLmithalbwegsguten
Parameternwirddabeifrunsbernommen.DesWeiterenstartetsieeinenzweitenThreadnebendemGUI
ThreadderActivity,derdasNeuzeichnendesGeschehenspermanentanstt.Hiersiehtmanschonerste
ParallelenzumMainLoopKonzept.WirwerdendavonGebrauchmachen.
DamitwireineMglichkeithaben,dasNeuzeichnenselbstzubernehmen,bietetdieGLSurfaceViewein
ListenerKonzept(auchObserverDesignPattern genannt).EineApplikation,diesichindenRenderingThread
derGLSurfaceVieweinhngenmchte,registriertbeidiesereineImplementierungdesInterfaceRenderer
DiesesInterfacehatdreiMethoden,dieabhngigvomStatusderGLSurfaceViewaufgerufenwerden.

publicabstractvoidonDrawFrame(GL10gl)
publicabstractvoidonSurfaceCreated(GL10gl,EGLConfigconfig)
publicabstractvoidonSurfaceChanged(GL10gl,intwidth,intheight)

DieMethodeonDrawFrame

istjene,diedieGLSurfaceViewjedesMalbeimNeuzeichnenaufruft.Den

Parametergl,denwirdabeierhalten,werdenwirsptergenauerbesprechen.
DieMethodeonSurfaceCreated wirdaufgerufen,sobalddieGLSurfaceViewfertiginitialisiertist.Hierkannman
verschiedeneSetupAufgabenerledigen,wiezumBeispieldasLadenvonRessourcen.
DieMethodeonSurfaceChanged wirdaufgerufen,wennsichdieAbmessungenderGLSurfaceViewndern.
Diespassiert,wennderBenutzerdasAndroidGertkipptundsoindenPortraitoderLandscapeModusschaltet.
DieParameterwidthundheightgebenunsdabeidieBreiteundHhedesBereichesan,aufdenwirzeichnenund
zwarinPixeln.DieseInformationwerdenwirspternochbentigen.
UnsereersteActivityhatalsoeinpaarAufgaben:
ErstelleneinerGLSurfaceViewundEinhngenindieActivity
SetzeneinerRendererImplementierungfrdieGLSurfaceView
ImplementierungderRenderererImplementierung
FreinesaubereImplementierungwerdenwireinfachdieKlasseActivityableitenunddieseGameActivity
nennen.DieserverpassenwireinAttributvomTypGLSurfaceView,denwirinderonCreateMethodederKlasse
instanzierenundindieActivityeinhngen.WeitersimplementiertunsereabgeleiteteActivitydasInterface
Renderer.InzweiweiterenAttributVariablenspeichernwirdieaktuelleGredeszubemalendenBereichs,die
wirbeimAufrufderMethodeonSurfaceChangedinErfahrungbringen.DiesenBereichnenntmanimbrigen
auchViewport .WirwerdendieseTerminologiefortanbernehmen.AuerdemfgenwirnochGetterMethoden
indieKlasseein,damitwirspteraufdieAbmessungenzugreifenknnen.
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

6/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

UmdenActivityLifeCycleauchsauberzuimplementieren,mssenwirdieMethodenonPauseundonResume
nochberschreiben.IndiesenRufenwirdieselbenMethodenauchfrunsereGLSurfaceViewauf.Diesistntig,
damitverschiedeneRessourcensauberverwaltenwerdenknnen.
InunsererActivitywerdenwirauchgleichdieFrameRateunddieZeitspannezwischendemaktuellenunddem
letztenFramemessen.DieZeitspannenenntmanauchDeltaTime,wiedereinBegriff,denwirunsabjetztmerken
werden.UmeinegenaueZeitmessungimMillisekundenbereichzugewhrleisten,verwendenwirdie
System.nanoTime Methode.DieseliefertunsdieaktuelleZeitinNanosekundenalslongTypzurck.Frdie
DeltaTimemerkenwirunsdenZeitpunktdesletztenFramesalseigenesAttributinderKlasse.DieDeltaTime
selbsterrechnenwirdanninderonDrawFrameMethode,indemwireinfachdieaktuelleZeitabzglichderzuvor
gespeichertenZeitnehmen.DieseDeltaTimespeichernwirineinemweiterenAttribut,umspterimSpieleinfach
daraufzugreifenzuknnen.Diesistnotwendig,dawirsiefrdasFrameIndependentMovementbentigen.Zum
AbschlussschreibenwirdieaktuelleZeitwiederindasAttribut,dasfrdieSpeicherungderDeltaTime
BerechnungimnchstenFramevorgesehenist.
AlsletztenPuzzleSteinwerdenwirnochanunseremDesignetwasfeilen.WirwollenunsereActivityjanichtjedes
Malneuschreiben,darumfhrenwireineigenesListenerKonzeptein.Diesmachenwirbereinkleines
Interface,daszweiMethodenhat.
publicinterfaceGameListener
{
publicvoidsetup(GameActivityactivity,GL10gl);
publicvoidmainLoopIteration(GameActivityactivity,GL10gl);
}
DerGameActivityspendierenwireineMethodesetGameListener,derwireineGameListenerImplementierung
bergebenknnen.DieActivitymerktsichdiesenListenerundruftseineMethodenentsprechendauf.Die
MethodeSetupwirddabeinachdemStartdesSpielsaufgerufenundermglichtesuns,Ressourcenzuladen,die
wirdannspterimMainLoopbrauchen.InderGameActivityrufenwirdieseMethodeinonSurfaceCreatedauf,
fallseinGameListenergesetztwurde.DieMethodemainLoopIterationimplementiertdenKrperdesMainLoop.
HierwerdenwirspterdannallesfrunserSpielntigeerledigen,wiedieWeltzusimulierenoderdiesezu
zeichnen.DieseMethodewirdinderActivityinonDrawFrameaufgerufen.FangenwirmitderProgrammierung
einesneuenSpielsan:AlserstesimplementierenwirlediglicheineActivity.DieseleitetdirektvonGameActivityab
undwirsetzenihreinenGameListenerinderonCreateMethode.DerGameListeneristalsodaseigentlichSpiel.
DamithabenwirvorerstdenGrundstockfrunsererstesSpielgelegt,einevollFunktionsfhigeActivity,dieuns
dieVerwendungvonOpenGLerlaubt.WirwerdendieGameActivityKlassegleichnocheinwenigausbauen,um
dortauchEingabenentgegennehmenzuknnen.DenCodefrdieKlasseknntihreuchunter
[http://code.google.com/p/android
gamedev/source/browse/trunk/src/com/badlogic/gamedev/tools/GameActivity.java
http://code.google.com/p/android
gamedev/source/browse/trunk/src/com/badlogic/gamedev/tools/GameActivity.java

]ansehen.

TouchScreen&Accelerometer
DasLesenvonBenutzereingabenaufAndroidist,wievielesanderes,wiederbereinListenerKonzept
implementiert.WirvernachlssigenhierEingabenberdenTrackball,dieTastaturoderdasDPad,dadiesden
RahmendiesesArtikelswohlsprengenwrde.WirkonzentrierenunszuerstaufTouchEingabenundgehen
spterzumAccelerometerber.
FrdieEingabeperTouchbrauchenwireinGUIElement,dasdieseauchentgegennimmt.MitderGLSurfaceView
inunsererGameActivityhabenwirbereitseinengeeignetenKandidaten.Esgiltsomitnureinenentsprechenden
ListenerbeiderGLSurfaceViewzuregistrieren.DasInterface,daswirimplementierenwollen,nenntsich
OnTouchListener undhatnureineeinzigeMethode

http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

7/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

publicabstractbooleanonTouch(Viewv,MotionEventevent)
FrunsinteressantistderParametereventvomTypMotionEvent .DieserbeinhaltetdieKoordinatendesTouch
Events,sowiedieAktion.AlsoobderFingergeradeaufgesetztwurde,obergezogenwirdoderoberwiedervom
Displaygenommenwurde.DieKoordinatensinddabeizweidimensionalundrelativzumView,frdenwirden
Listenerregistrierthaben.berdieMethodenMotionEvent.getX() undMotionEvent.getY() erhaltenwirddie
Werte.Abhngigdavon,obwirimLandscapeoderPortraitModussind,sinddiexundyAchseausgerichtet.Die
positivexAchsezeigtdabeiimmernachrechts,diepositiveyAchsenachunten.DerNullpunktbefindetsichalso
inderoberenlinkenEcke.DiesisteinwichtigesFaktum,dasvielenNeulingenamAnfangProblememacht,daes
nichtmitdeminderSchulegelernten,klassischenkartesischenKoordinatensystembereinstimmt.Die
KoordinatenwerdendabeiwiederinPixelangegeben.
WelcheAktiongeradeaktuellist,liefertunsdieMethodeMotionEvent.getAction().WirwerdenaufdieAktionen
MotionEvent.ACTION_DOWN,MotionEvent.ACTION_UPundMotionEvent.ACTION_MOVEreagieren.Die
GameActivitylassenwirdasInterfaceOnMotionListenerimplementieren.Wirspendierenihrauchdreineue
AttributetouchX,touchYundisTouched,indenenwirdenaktuellenStatusdesTouchScreenspeichern.Kommt
einMotionEvent.ACTION_DOWNEventdaher,speichernwirdiexunddieyKoordinateintouchXbzw.touchY
undsetzenisTouchedauftrue,beieinemMotionEvent.ACTION_MOVEmachenwirdasselbeundimFallevon
MotionEvent.ACTION_UPsetzenwirisTouchedauffalse.DamitwirimGameListeneraufdenaktuellenStatus
zugreifenknnen,gebenwirderGameActivityauchnochGetterMethoden,umdieWerteauslesenzuknnen.
DasAuslesendesaktuellenStatusnenntmanallgemeinauchPolling

Esseiangemerkt,dassdieonTouchMethodeimGUIThreadundnichtimRenderThreadderGLSurfaceView
vomBetriebssystemaufgerufenwird.NormalerweisemsstemansichhierSorgenumetwaigeThread
Synchronisierungmachen.DaessichbeidenAttributen,diedenStatushaltenaberumPlainOldDatatypes
handeltunddasSchreibenaufdieseatomarist,knnenwirdashiereinfachbersehen.
DieGameActivityregistriertsichselbstalsOnTouchListenerbeiderGLSurfaceViewinderonCreateMethode,die
wirdementsprechenderweitern.
DerAccelerometeristebenfallswiederbereinListenerKonzeptansprechbar(ja,dasziehtsichsoziemlich
durchallesdurch).DasentsprechendeInterfacenenntsichSensorEventListener .Diesenregistriertmanaber
nichtbeieinemView,sondernbeimSensorManager .Zugrifferhaltenwiraufdiesenwiefolgt:
SensorManagermanager=(SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
Bevorwirunsdortregistrierenknnen,mssenwiraberzuersteinmalprfen,obderAccelerometerberhaupt
verfgbarist.Diesfunktioniertso:
booleanaccelerometerAvailable=manager.getSensorList(Sensor.TYPE_ACCELEROMETER).size()>0;

IsteinAccelerometervorhanden,knnenwirunsohnegroeProblemebeidiesemregistrieren:
Sensoraccelerometer=manager.getSensorList(Sensor.TYPE_ACCELEROMETER).get(0);
if(!manager.registerListener(this,accelerometer,SensorManager.SENSOR_DELAY_GAME))
accelerometerAvailable=false;
VomManagerholenwirunsdenerstenAccelerometerSensor,denwirfinden(inderRegelgibtesdavonnur
einen).DanachregistrierenwirunsberdieSensorManager.registerListener() Methode.DieserVorgangkann
fehlschlagen,deshalbprfenwirauch,obesgeklappthat.DerParameter
SensorManager.SENSOR_DELAY_GAMEgibtdabeian,wieoftdasBetriebssystemdenAccelerometerabtasten
soll,indiesemFalloftgenug,umfreinSpielzugengen.
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

8/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

Wasnochbleibt,istdasVerarbeitenderSensorEvents.Dasmachenwirinder
SensorEventListener.onSensorChanged()

Methode,diewirimplementieren.

publicabstractvoidonSensorChanged(SensorEventevent)
hnlichwiebeimVerarbeitenvonTouchbekommenwirhierwiedereinEvent,indiesemFallvomTyp
SensorEvent .DieseKlassebesitzteinffentlichesAttributnamensvalues,dasdiefrunsrelevantenWerte
enthlt.VondiesengibtesdreiStck,gespeichertandenIndizes0bis2.DiesedreiWertegebendabeidie
BeschleunigunginMeterproSekundeentlangderx,yundzAchsedesAndroidGertsan.DermaximalWert
betrgtdabeijeweils+9.81m/s,wasderErdbeschleunigungentspricht.HltmandasAndroidGertimPortrait
Mode,sogehendiepositivexAchsenachrechts,diepositiveyAchsenachobenunddiepositiveZAchse
geradeausdurchdasGert.

AccelerometerAchsen

Diesbleibtauchso,wennmandasGertimLandscapeModushlt.WirwerdendannbeiderSpaceInvaders
Umsetzungsehen,wiewirdieseWerteeinsetzenknnen.
GleichwiefrTouchEventsspendierenwirderGameActivityeinigeneueDinge.ZuBeginnwredaeinneues
AttributvomTypfloatArray.DiesehltunseredreiAccelerometerWerte.AuerdemlassenwirdieActivitydas
SensorEventListenerInterfaceimplementieren.ZumAbschlussbrauchenwirnochdreiMethoden,dieunsjeweils
denAccelerometerWertfreineAchseliefernundwirsindfertig.GleichwiefrTouchEventsknnenwirdamit
denAccelerometerStatusauslesen.
EineBeispielApplikation,diedenaktuellenTouchundAccelerometerStatusperLogCatausgibt,findetihrunter
[http://code.google.com/p/android
gamedev/source/browse/trunk/src/com/badlogic/gamedev/samples/InputSample.java
http://code.google.com/p/android
gamedev/source/browse/trunk/src/com/badlogic/gamedev/samples/InputSample.java

].Diesezeigtauchgleich,

wiewirabjetztneueSamplesunddaseigentlichSpielaufbauenwerden.WirleitenzuerstvonGameActivityab,
registrierenunsinderonCreateMethodealsGameListenerbeiunsselbstundbefllendanndiesetupund
renderMethodemitunseremApplikationsCode.Einfachundelegant.

Ressourcen,AssetsunddieSDKarte
DateiEinundAusgabeaufAndroidisteinweitesLand.MehrereMglichkeitenstehenunszurVerfgung,wir
werdenkurzaufalleeingehen,kleineCodeStckesollenillustrieren,wiemandieeinzelnenMglichkeiten
anwendenkann.

Ressource
RessourcenstellendenvonGooglegewnschtenWegzurVerwaltungvonDateiendar.SiewerdenimAndroid
ProjektinspezielldafrvorgeseheneOrdnergespeichertundsinddannimApplikationsCodeberIdentifier
direktansprechbar.FrdieSpieleentwicklungsindsiemeinerAnsichtnachnurbedingtvonNutzen,dadie
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

9/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

vorgegebeneVerzeichnisstrukturetwaseinschrnkt.AufRessourcengibtesnurLesezugriff,dasiedirektinder
APKDateiderApplikationabgelegtwerden.hnlichwieRessourceninnormalenJavaJarDateien.Einen
schnenberblickbietetfolgenderLink
nchstenKandidatenzu.

,wirlassenRessourceneinmalauenvorundwendenunsdem

Assets
AssetswerdenebenfallswieRessourcendirektinderAPKDateieingepackt.DerVorteilliegthierinderfreien
WahlderVerzeichnisstruktur.SiehnelndamitvielmehrdemherkmmlichenJavaRessourcenMechanismus
(undsindmirdaherauchsympathischer).ImAndroidProjektkannmanunterdemAssetsVerzeichnisseine
eigeneStrukturbeliebiganlegen.DerZugriffaufeinAssetluftdabei,wiegewohnt,berInputStreams:
InputStreamin=activity.getAssets().open("path/to/resource");
WieRessourcensindauchAssetsnurlesbar.

SDKarte
WennderBesitzerdesAndroidGertseineSDKarteeingelegthat,kannmaninderRegelaufdieserschreiben
undlesen.DazubedarfesinderDateiAndroidManifest.xmldesProjekteseinesZusatzes:
<usespermissionandroid:name="Android.permission.WRITE_EXTERNAL_STORAGE"/>
EinundAusgabefunktioniertdannberdieherkmmlichenJavaKlassen.
FileInputStreamin=newFileInputStream("/sdcard/path/to/file");
FileOutputStreamout=newFileOutputStream("/sdcard/path/to/file");

OpenGLES
Jetztgeht'sansEingemachte.OpenGLES

isteineSchnittstelle,dieesunserlaubtdirektmitderGrafikkarte

einesmobilenGertszusprechen.DerStandardwurdevonmehrerenHerstellerngemeinsamentworfenund
lehntsichstarkandieVariantean,dieinherkmmlichenPCs,aberauchinWorkstationszumEinsatzkommt
(genauerandieVersion1.3).ImRahmendiesesArtikelswerdenwirunsdiewichtigstenDingezuGemtefhren,
verstndlicherweisekannichhierabernichtaufallesundjedeseingehen.BevorwirunsindieUntiefenvon
OpenGLESstrzen,mssenwirunsabernocheinpaargrundlegendeDingeanschauen,dieallgemeininder
Computergrafikgelten.

GrundlegendeszurGrafikprogrammierung
DieEntwicklungimGrafikbereichwarindenletztenJahrzehntenextrem.VieleDingehabensichgendert,beider
Programmierungbliebaberaucheinigesgleich.GrundlagefrsoziemlichjedeArtvonGrafikprogrammierungist
dersogenannteFrameBuffer .DieseristeinTeildesVideoRAMundentsprichtinJavaTermeneinemgroen
eindimensionalenArray,indemdieFarbwertejedesPixelsfrdasaktuellamBildschirmangezeigteBild
gespeichertwerden.WiedieFarbwertecodiertwerden,hngtvomBildschirmmodusab.HierkommtderBegriff
derFarbtiefe insSpiel.Diesespezifiziert,wievieleBitsproPixelverwendetwerden.Herkmmlicherweisesind
dasbeiDesktopSystemen24bzw.32Bit.AufmobilenGertensind16BitFarbtiefenweitverbreitet.DieFarbe
selbstwirdalsRotGrnBlauTriplebzw.RotGrnBlauAlphaQuadrupleindiesen16,24oder32Bitabgelegt.
JenachFarbtiefekannfrjedederKomponentennatrlicheinegrereoderkleinereReichweiteentstehen.Wir
mssenunsaberSpaghettimonsterseidankbeiOpenGLESnichtoftundvorallemnichtsointensiv,wiezuDOS
ZeitenmitderThematikauseinandersetzen.FarbenwerdeninOpenGLESnormalerweisenormiert,d.h.im
Bereichzwischen0und1frjedederKomponentenderFarbe(rot,grn,blau,alpha=Transparenz)angegeben.

http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

10/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

WollenwiralsodieAusgabeamBildschirmndern,somssenwirdenFramebuffer,genauerdiePixelim
Framebuffer,manipulieren.FrhergeschahdaswirklichquasinochperHand,heutzutagewirdunsdiesedirekte
ManipulationdesFramebuffervonBibliotheken,wieOpenGLabgenommen.GrobgesagtzeichnetOpenGLfr
unsgefrbte,texturierteDreieckeindenFramebufferunddasganzehardwarebeschleunigt.WirhabenEinfluss
darauf,woimFramebufferdieseDreieckewiegezeichnetwerden,wiewirspternochsehenwerden.
Pixelmssennatrlichadressiertwerdenknnen.ManverwendetdazueinzweidimensionalesKoordinaten
System.KoordinatenindiesemSystemwerdendabeiineinelineareAdresseimFramebufferumgerechnet.Und
zwarmitdereinfachenFormel:
Adresse=x+y*Breite
WobeimitBreitedieBreitedesBildschirmsinPixelgemeintist.Hiererklrtsichauch,wiesodieyAchseindiesem
KoordinatenSystemnachuntenzeigt(wiejenes,daswirfrTouchEventsverwenden).DieAdresse0imFrame
BufferentsprichtdemPixelinderoberenlinkenEckedesBildschirmsundhatdieKoordinaten0,0.BeiCRT
MonitorenfngtderKathodenstrahlindieserEckean,dieDatenfrdieIntensitt,dieerhabensollbekommter
vereinfachtgesagtausdemFrameBuffer,wobeinatrlichamAnfangdiesesFrameBufferszulesenbegonnen
wird(Position0,Koordinate0,0).
OpenGLarbeitetinternauchindiesemKoordinatenSystem,nachauenabermiteinemanderen.Unkonfiguriert
liegtderUrsprunginderMittedesBildschirms,diepositivexAchsegehtnachrechtsunddiepositiveyAchse
gehtnachoben,dabeibewegensichdiexundyKoordinatenimBereich1,1,hnlichzudenBereichenbei
Farben.WollenwirPixelperfektarbeiten,mssenwirdasOpenGLerstbeibringen.Wirwerdenspternoch
sehen,wiewirdasbewerkstelligen.
BewegungamBildschirmentstehthnlichwiebeieinemZeichentrickfilm.Eswerdenverschiedene
Animationsphasen,oderFramesnacheinanderindenFramebuffergeschrieben.IstdieFrequenzmitderwirdie
Framesschreibenhochgenug,entstehtbeimBetrachterdieIllusionvonBewegung.24BilderproSekunde
werdeninderRegelbeiFilmengezeigt.

EinwenigMathematik
Ja,ohneMathematikkommenwirleidernichtaus.KonkretbrauchenwireinweniglineareAlgebra.Klingt
grauslich,istesabereigentlichgarnicht.DenStoff,denwirunshierzuGemtefhren,solltenvieleschoneinmal
inderSchulegehrthaben.WirwerdenunskurzmitVektoreninderdrittenDimensionbeschftigen.
DefinierenwirzuerstdasKoordinatenSystemvonOpenGL,indemwirunsdannbewegenwerden.Diepositivex
Achsezeigtnachrechts,diepositiveyAchsezeigtnachobenunddiepositivezAchsezeigtausderEbene
heraus.SiehedazudienchsteGrafik:

EinenPunktindiesemSystemgibtmanberdieVerschiebungaufdendreiAchsenan,d.h.einPunkthat3
Koordinaten,x,yundz.EinVektorgibteineRichtungimKoordinatenSystemanundistnichtmiteinemPunkt
gleichzusetzen.VektorenknnenimSystembeliebigverschobenwerden.TrotzdemwerdenwirdieTerminiVektor
undPunkteinwenigdurchmischen,damanimAlltaginderRegelmitVektorenarbeitet.WirwerdenimArtikel
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

11/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

Vektorenwiefolgtanschreiben:
v=[vx,vy,vz]
Vektorenwerdenfettgedruckt,Skalare(alsoeinfachZahlen)werdenwirnormaldrucken.
MitVektorenkannmanauchwunderbarrechnen.AlsersteswollenwirdieLngeeinesVektorsbestimmen:
|v|=Math.sqrt(vx*vx+vy*vy+vz*vz);
Dassollteeuchbekanntvorkommen:dieLngeeinesVektorsleitetsichvonPythagoras'Satzab.DieNotationauf
derrechtenSeitebedeutet"LngedesVektorsv".
Vektorenkannmanauchaddierenundsubtrahieren:
a+b=[ax+bx,ay+by,az+bz]
ab=[axbx,ayby,azbz]
BeiderMultiplikationsiehtdasganzhnlichaus:
a*b=ax*bx+ay*by+az*bz
DasnenntmanauchdasSkalarprodukt zweierVektoren.MiteinemkleinenKniffkannmanmitdiesem
SkalarproduktdenWinkelzwischenzweiVektorenmessen:
winkel=Math.acos(a*b/(|a|*|b|));
IchmischehierJavaundmathematischeNotationetwas,damirdieFormatierungsmglichkeitenfehlenundesso
etwasverstndlicherwird.DerWinkelistdabeiimmer<=180Grad.Math.acos()liefertdiesenWinkeljedochnicht
inGradsonderninBogenmawelchesmanrechteinfachmitMath.toDegrees()inGradumrechnenkann.Alle
trigonometrischenFunktionenderKlasseMatharbeitenbrigensmitBogenma,sowohlwasParameteralsauch
wasRckgabewertebetrifft.DassorgtoftfrschwerzufindendeBugs,alsoimmerdarandenken.
ZumSchlusswollenwirnochaufEinheitsvektoreneingehen.DiessindVektoren,diedieLngeeinshaben.Um
einenbeliebigenVektorzueinemEinheitsvektorzumachen,mssenwirdessenKomponenteneinfachdurch
seineLngedividieren.
a'=[ax/|a|,ay/|a|,az/|a|]
DerApostrophnachdemazeigtandassessichumeinenEinheitsvektorhandelt.WirwerdenEinheitsvektoren
spterfreinpaarKleinigkeitenbentigen.
UnddamitsindwirmitdemMathematikKapitelauchschonfertig.Ichhoffeeswarnichtgarzuschlimm.Bei
UnsicherheitenempfehleicheuchimNetzeinweniginMaterialzumThemazustbern.

DasersteDreieck
WieEingangsschonerwhnt,istOpenGLimGrundeseinesHerzenseineDreieckZeichenmaschine.Indiesem
Abschnittwollenwirunsdaranmachen,dasersteDreieckaufdenBildschirmzuzaubern.Dazuerstellenwireine
neueActivity,dievonGameActivityableitetunddiesichselbstalsGameListenerinderonCreate()Methode
registriert.
WiewirschonEingangserwhnthabennennensichdieDinge,diewirzeichnenMeshes.EinsolchesMeshwird
durchsogenannteVerticesdefiniert.EinVertexentsprichtdabeieinemPunktdesMeshesmitverschiedenen
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

12/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

Attributen.AnfangswollenwirunsnurumdiewichtigsteKomponentekmmern,derPosition.DiePositioneines
Vertexwird,ihrhabteserraten,alsdreidimensionalerVektorangegeben.EinPunktalleinemachtnochkeinMesh,
darumbrauchenwirmindestensdrei.MehrereDreieckesindnatrlichauchkeinProblem,wirwollenaberklein
anfangen.
OpenGLESerwartetinseinerBasisversion1.1dieVertexPositionenineinemdirectByteBuffer

.Definierenwir

zurbungeinmaleinDreieckinderxyEbenemitHilfesoeinesByteBuffers:
ByteBufferbuffer=ByteBuffer.allocateDirect(3*3*4);
buffer.order(ByteOrder.nativeOrder());
FloatBuffervertices=buffer.asFloatBuffer();
vertices.put(0.5f);
vertices.put(0.5f);
vertices.put(0);

vertices.put(0.5f);
vertices.put(0.5f);
vertices.put(0);
vertices.put(0);
vertices.put(0.5f);
vertices.put(0);
GanzschnvielCodefrsoeinkleinesDreieck.AlsersteserstellenwireinendirectByteBufferder3*3*4Bytes
groist.DieZahlergibtsichdawir3Verticeshabenzuje3Komponenten(x,y,z)diejeweils4ByteSpeicher
brauchen(float>32bit).AnschlieendsagenwirdemByteBuffer,dasserallesinnativerOrdnungspeichernsoll,
d.h.inBigoderLittleEndian.DenfertiginitialisiertenByteBufferwandelnwirdannineinenFloatBufferumdenwir
mitderMethodeFloatBuffer.put()befllenknnen.Jeweils3AufrufedefinierendieKoordinateneinesVertex'
unseresDreiecks.DerersteVertexliegtlinksunterdemUrsprung,derzweiteVertexrechtsunterdemUrsprung
undderdritteVertexdirektberdemUrsprung.DazufolgendesBild:

DamithabenwirOpenGLabernochimmernichtwirklichetwasverraten.Dasmachenwirdochgleichund
veranlassendasZeichnenunseresDreiecks:
gl.glViewport(0,0,activity.getViewportWidth(),activity.getViewportHeight());
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glVertexPointer(3,GL10.GL_FLOAT,0,vertices);
gl.glDrawArrays(GL10.GL_TRIANGLES,0,3);
ImerstenMethodenaufrufteilenwirOpenGLmit,welcherBereichdesBildschirmsbezeichnetwerdensoll.Die
erstenbeidenParametergebendabeidieStartkoordinatendeszubezeichnendenBereichsan,dienchsten
beidenseineGre.BeideAngabenwerdeninPixelngemacht,hiersagenwirkonkret,dassderganzeBildschirm
ausgenutztwerdensoll.
Achtung:derEmulatorbentigtunbedingtdenAufrufvonglViewport.AufGertenistderViewportbereitsaufden
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

13/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

gesamtenBildschirmgesetzt,imEmulatorhatderViewportamAnfangdieGre0,0.Nichtvergessen,dasonst
nichtsamBildschirmgezeichnetwirdundmanstundenlangsucht(ja,istmirauchschonpassiert...)!
ImnchstenAufrufsagenwirOpenGL,dasswirihmjetztgleichVertexPositionenbergebenwerdenunderfortan
beimZeichnenimmerdenbergebenenFloatBufferverwendensoll.ImdrittenAufrufteilenwirOpenGLmit,woer
diePositionsdatenfindet.DerersteParametergibtdabeidieAnzahlderVerticesan,derzweitegibtdenTypender
KomponentenjederPositionan,indiesemFallfloats.DerdritteParameternenntsichstrideundistfrunsohne
Belang,wirsetzenihneinfachauf0.DerletzteParameteristderFloatBuffer,denwirzuvormitdenPositionender
3Verticesbefllthaben.AlsLetztesbefehlenwirOpenGLdieebendefiniertenVerticeszuzeichnen.Dererste
Parametergibtdabeian,waswirzeichnenwollen.IndiesemFall:Dreiecke.DerzweiteParametergibtan,ab
welcherPositionimFloatBufferOpenGLbeginnensoll,diePositionsdatenzuholen.DerletzteParametersagt
OpenGLnoch,wievieleVerticeswirgezeichnethabenwollen.ZeichnenwirDreiecke,mussdieserParameter
immereinVielfachesvon3sein.UndschonhabenwirdasersteDreieckmitOpenGLgezeichnet!Zur
EntspannunghierdergesamteCodediesesBeispiels:
publicclassTriangleSampleextendsGameActivityimplementsGameListener
{
privateFloatBuffervertices;
publicvoidonCreate(BundlesavedInstance)
{
super.onCreate(savedInstance);
setGameListener(this);
}
@Override
publicvoidsetup(GameActivityactivity,GL10gl)
{
ByteBufferbuffer=ByteBuffer.allocateDirect(3*4*3);
buffer.order(ByteOrder.nativeOrder());
vertices=buffer.asFloatBuffer();
vertices.put(0.5f);
vertices.put(0.5f);
vertices.put(0);
vertices.put(0.5f);
vertices.put(0.5f);
vertices.put(0);
vertices.put(0);
vertices.put(0.5f);
vertices.put(0);
vertices.rewind();
}
@Override
publicvoidmainLoopIteration(GameActivityactivity,GL10gl)
{
gl.glViewport(0,0,activity.getViewportWidth(),activity.getViewportHeight());
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glVertexPointer(3,GL10.GL_FLOAT,0,vertices);
gl.glDrawArrays(GL10.GL_TRIANGLES,0,3);
}
}
AlternativkannmansichdenCodeetwasschnerformatiertunter[http://code.google.com/p/android
gamedev/source/browse/trunk/src/com/badlogic/gamedev/samples/TriangleSample.java
http://code.google.com/p/android
gamedev/source/browse/trunk/src/com/badlogic/gamedev/samples/TriangleSample.java

]ansehen.

UndhiereinScreenshotunseresDreiecks
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

14/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

Farbspiele
EinweiesDreieckistnatrlichetwaslangweilig.Umdaszundern,verwendenwir,bevorwirglDrawArrays
aufrufen,denBefehl
glColor4f(floatr,floatg,floatb,floata);
R,g,bstehenfrdiedreiFarbkomponenten,astehtfrdieTransparenz.AlleWertesindimBereich0bis1
anzugeben.Setzenwirrundbauf1bekommenwireinschnespink:

http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

15/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

Ichhabevorherschonerwhnt,dasseinVertexnichtnureinePositionhat.Solangewirdiesenichtexplizit
definieren,hatjederVertexineinemMeshdieFarbe,diewirmitglColor4fangeben.UmjedemVertexeineeigene
Farbezugeben,verwendenwirdenselbenMechanismus,wiefrdieVertexPositionen.Zuerstbauenwirwieder
einendirectFloatBuffer,indenwirfrjedenVertexdirr,g,bundaWertespeichern:
buffer=ByteBuffer.allocateDirect(3*4*4);
buffer.order(ByteOrder.nativeOrder());
colors=buffer.asFloatBuffer();
colors.put(1);
colors.put(0);
colors.put(0);
colors.put(1);
colors.put(0);
colors.put(1);
colors.put(0);
colors.put(1);
colors.put(0);
colors.put(0);
colors.put(1);
colors.put(1);
colors.rewind();
Dawir3Verticeshaben,brauchenwireinenByteBuffer,der3Farbenhlt,zuje4Komponenten(r,g,b,a)mitje4
byte(floats).DenByteBufferschaltenwirwiederaufnativeorderundwandelnihnineinenFloatBufferum.Nun
knnenwirihnmitdendreiFarbenfrunseredreiVerticesbefllen,hierrot(1,0,0,1),grn(0,1,0,1)undblau
(0,0,1,1).BeimZeichnensagenwirOpenGL,dassesunserenFloatBufferfrdieFarbenderVerticesverwenden
soll:

http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

16/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
gl.glColorPointer(4,GL10.GL_FLOAT,0,colors);
ZuerstsagenwirOpenGL,dasswirfrdieFarbendereinzelnenVerticeseinenFloatBufferhaben
(glEnableClientState).Danngebenwir,gleich,wiebeidenVertexPositionen,an,wodieserFloatBufferzufinden
ist(glColorPointer).DerersteParametersagt,wievieleKomponenteneineFarbehat(4>r,g,b,a),derzweite
Parametergibtan,welchenTypdieKomponentenhaben,derdritteParameteristwiederderstrideunddervierte
istunserzuvorbefllterFloatBuffer.Unddaswaresauchschonwieder.Achtung:hatmandenclientstate
GL10.GL_COLOR_ARRAYaktiviert,wirdjederAufrufvonglColor4fignoriert.Esmussdannunbedingtein
FloatBuffermitglColorPointerangegebenwerden,derzumindestsovieleFarbenbesitzt,wiedasMeshVertices
hat,bzw.soviele,wiemanVerticesbeiglDrawArraysangibt!
DergesamteCodezumZeichnenunseresnunschneingefrbtenDreieckssiehtsoaus:
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glVertexPointer(3,GL10.GL_FLOAT,0,vertices);
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
gl.glColorPointer(4,GL10.GL_FLOAT,0,colors);
gl.glDrawArrays(GL10.GL_TRIANGLES,0,3);
HierzeichnetsichlangsameinMusterab:frjedeKomponenteeinesVertex,alsoz.B.diePositionoderdieFarbe,
gebenwireinenArray(GL10.GL_VERTEX_ARRAY,GL10.GL_COLOR_ARRAY)mitglEnableClientStatefreiund
gebendannmiteinerderglXXXPointerMethodenanwo,dasentsprechende"Array"(inunseremFallinForm
einesFloatBuffer)zufindenist.DieseMethodedesZeichnensnenntmaninOpenGLVertexArrays undistdie
einzigeMethode,mitdermaninOpenGLES1.0berhauptetwaszeichnenkann.Wirwerdenvielleichtineinem
anderenArtikeldiesogenanntenVertexBufferObjects nherbetrachten,dieabOpenGLES1.1zurVerfgung
stehen.EinstweilenbleibenwiraberbeidenVertexArrays,dasieaufallenAndroidGertenfunktionieren.
HiernocheinScreenshotunseresfarbigenDreiecks:

http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

17/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

DenCodefrdiesesBeispielknntihreuchunter[http://code.google.com/p/android
gamedev/source/browse/trunk/src/com/badlogic/gamedev/samples/ColorSample2.java
http://code.google.com/p/android
gamedev/source/browse/trunk/src/com/badlogic/gamedev/samples/ColorSample2.java

]ansehen.

Texturen
Sorichtigpeppigwirdes,wennmanseinenDreieckenTexturen verpasst.DabeitapeziertmanaufdieDreiecke
eineBitmap,diemanzuvorgeladenhat.BevorwirunsandieTexturierungselbstmachen,schauenwiruns
schnellan,wiemaneineBitmapberhauptldt.ImBeispielProjekthabeichimassetsVerzeichniseinePNG
Dateinamens"droid.png"abgelegt.DiesesladenwirinunserersetupMethodewiefolgt:
try
{
Bitmapbitmap=null;
bitmap=BitmapFactory.decodeStream(getAssets().open("droid.png"));
}
catch(Exceptionex)
{
//Ohno!
}
Sehreinfach:wirrufendiestatischeMethodedecodeStreamderKlasseBitmapFactoryaufundbergebenihr
einenInputStreamaufunserBitmapAssetnamens"droid.png".DadieMethodeeineIOExceptionwirft,machen
wirnocheinentrycatchBlockdarum.Dawirkluggenugwaren,dasAssetauchwirklichindasentsprechende
Verzeichniszupacken,sollteesaberkeineExceptiongeben.NormalerweisebehandeleichExceptionsbeim
LadenvonRessourcenmiteinemLogOutputundeinemSystem.exit(1).Wieihrdaslst,bleibtabereuch
berlassen.
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

18/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

DasTexturierenselbstistwiederrelativeinfach.DieBitmap,diemanldtwirdineinnormiertesKoordinaten
Systemgelegt:

DenAchsengebenwirzurVermeidungvonVerwechslungenmitdemVertexPositionenKoordinatenSystem
neueNamen,snachRechtsundtnachunten.Egal,welcheAbmessungendasBildhat,Pixelwerdenimmerim
Bereich[0,0][1,1]angesprochen.Mankannsoz.B.leichteinehochauflsendeTexturmiteinerniedrig
auflsendenTexturaustauschen,ohnedieTexturKoordinatendesMesheszundern.WasdieBildabmessungen
betrifft,sogibteseineLimitationaufAndroid.EsmssenZweierPotenzensein,also1,2,4,8,32,64,128,256
usw.Maximalsolltemannichtmehrals512x512Pixelverwenden,dieHardwareknntedasnichtmehr
untersttzen.DieBildermssendabeinichtquadratischsein,sondernknnenz.B.auchdieAbmessungen32x64
oder128x32haben.
DamitunserDreiecktexturiertwird,mssenwirfrjedenVertexzuersteineTexturKoordinateangeben.Die
AngabeerfolgtdabeiimKoordinatenSystemderTextur,alsozweidimensionalundjeweilszwischen0und1(man
kannauchkleinereundgrereWerteangeben,dasschauenwirunsaberspteran):

http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

19/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

HierhabenwirunserDreieckgemapped.AufmerksameLeserwissenschon,wasjetztkommt:DieKoordinaten
speichernwirwiederineinenFloatBuffer:
buffer=ByteBuffer.allocateDirect(3*2*4);
buffer.order(ByteOrder.nativeOrder());
texCoords=buffer.asFloatBuffer();
texCoords.put(0);
texCoords.put(1);

texCoords.put(1);
texCoords.put(1);

texCoords.put(0.5f);
texCoords.put(0);
texCoords.rewind();
DieGreergibtsichwiederausdendreiVertices,frdiewirjeweilsTexturKoordinatenmitzweiKomponenten
haben,diewiederumjeweils4Bytegrosind(float).DerRestsollteselbsterklrendsein.
BevorwirdieTexturKoordinatenalsVertexKomponenteOpenGLmitteilen,mssenwirunsnochumeine
Kleinigkeitkmmern.UndzwarumdaseigentlicheladenderTextur.WirhabenzwarschondieBitmapausdem
Assetgeladen,eineTexturhabenwirabernochnichterstellt.Dasmachenwirjetzt:
int[]TextureIDs=newint[1];
gl.glGenTextures(1,TextureIDs,0);
TextureID=TextureIDs[0];

MitglGenTextursweisenwirOpenGLan,unseineneueTexturzuerstellen.DerersteParametergibtdabeian,
wievieleTexturenwirerstellenwollen(eine),indenzweitenParameterspeichertOpenGLdanndieID(s)der
neuenTextur(en).DerletzteParameteristnureinOffset,abdemOpenGLindembergebenenArrayschreiben
soll.DieerhalteneTexturIDmssenwirunsmerken,mitdieseraktivierenwirdannspterdieTextur.
AlsnchstesmssenwirdieBitmapindieTexturladen.HierhatunsdasAndroidTeameinengroenBrocken
ArbeitabgenommenundstelltunsdieKlasseGLUtilszurVerfgung:

http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

20/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

gl.glBindTexture(GL10.GL_TEXTURE_2D,TexturID);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D,0,bitmap,0);
ZuerstmssenwirdieTextur"binden",damitsiezuraktuellaktivenTexturwird.Dazubergebenwiralsersten
ParameterGL10.TEXTURE_2D(dessenErklrungichmirspare,dasisteinfachimmerso:))undalszweiten
ParameterdiezuvorgenerierteTexturID.ErstdannknnenwirDatenindieTexturladen,ihreKonfiguration
ndernodersiealsTexturfreinesunsererMeshesverwenden.AlsnchsterRufenwirdiestatischeMethode
texImage2DderKlasseGLUtilsauf,dieunserezuvorgeladeneBitmapindieTexturldt.DenerstenParameter
ignorierenwirwieder,denzweitenauch(gibtdenMipMapLevel an),alsdrittenParameterbergebenwirdie
BitmapunddenletztenParameterignorierenwirauchwieder.DamithatunsereTexturjetztdieBilddaten,diesie
habensoll.
AlsletztenSchrittmssenwirdieTexturjetztnochkonfigurieren:
gl.glTexParameterf(GL10.GL_TEXTURE_2D,GL10.GL_TEXTURE_MIN_FILTER,GL10.GL_LINEAR);
gl.glTexParameterf(GL10.GL_TEXTURE_2D,GL10.GL_TEXTURE_MAG_FILTER,GL10.GL_LINEAR);
gl.glTexParameterf(GL10.GL_TEXTURE_2D,GL10.GL_TEXTURE_WRAP_S,GL10.GL_CLAMP_TO_EDGE);
gl.glTexParameterf(GL10.GL_TEXTURE_2D,GL10.GL_TEXTURE_WRAP_T,GL10.GL_CLAMP_TO_EDGE);
DieerstenbeidenMethodensetzendieFilterderaktuellgebundenenTextur,diezumEinsatzkommt,wenndie
TexturamBildschirmgreralsimOriginalist,bzw.kleineralsimOriginalist.DerletzteParametergibtdabeiden
Filteran.HierhatmandieWahlzwischenGL10.GL_NEAREST,GL10.GL_LINEAR,
GL10.GL_LINEAR_MIPMAP_NEARESTundGL10.GL_LINEAR_MIPMAP_LINEAR.GL10.GL_NEARESTistder
hsslichste,GL10.GL_LINEARisteinbilinearerFilter,derganzguteErgebnissebringtunddiebeidenMipMap
Filterignorierenwireinstweilenwieder.
DiebeidenanderenMethodengebenan,wasgeschehensoll,wennderUserTexturKoordinatenangibt,die
kleinerals0odergrerals1sind.WirwhlenhierGL10.GL_CLAMP_TO_EDGEwaszurFolgehat,dasssolche
TextureneinfachaufdenBereichgeschnittenwerden(kleiner0wird0,grer1wird1).Alternativkannmanhier
GL10.GL_WRAPangeben.DieshatzurFolge,dassdieKoordinatenmodulo1genommenwerden.Eine4.5wird
sozur0.5undsoweiter.DamitkannmandieTexturbereinDreieckmehrereMalewiederholen.DieAngabedes
WrapModuserfolgtdabeifrdiesundtKomponenteeinzeln.
DamithabenwirdieTexturfertiggeladenundkonfiguriert.UnsbleibtnochdasberzeichnenmitderTextur.Hier
dergesamteCodeimberblick:
gl.glEnable(GL10.GL_TEXTURE_2D);
gl.glBindTexture(GL10.GL_TEXTURE_2D,TextureID);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glTexCoordPointer(2,GL10.GL_FLOAT,0,texCoords);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glVertexPointer(3,GL10.GL_FLOAT,0,vertices);
gl.glDrawArrays(GL10.GL_TRIANGLES,0,3);

ZuerstmssenwirOpenGLsagen,dassesabjetztalleMeshesmitderaktuellgebundenenTexturtexturierensoll,
alsnchstesbindenwirunsereTextur.Danngebenwiran,dassunsereVerticesTexturKoordinatenhabenund
wirdiesebergebenwerden,wasimnchstenAufrufmitglTexCoordPointergeschieht.Hierunsertexturiertes
Dreieck:

http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

21/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

DenCodezumBeispielfindetihrunter[[http://code.google.com/p/android
gamedev/source/browse/trunk/src/com/badlogic/gamedev/samples/TextureSample.java
http://code.google.com/p/android
gamedev/source/browse/trunk/src/com/badlogic/gamedev/samples/TextureSample.java

EsseinochangemerktdassmandieTexturnureinmalerstellt(z.B.indersetupMethode).Ichhattedaschon
CodevoneinigenLeutengesehen,diedieSelbeTexturimmerundimmerwiederladen.WasfrMeshesgilt:
einmalmachen,solangverwenden,wientig,danndieRessourcenwiederfreigeben.ImFallvonVertexArrays
gibtesnichtszutun.ImFallvonTexturenmssenwirdieselschen,wassehreinfachgeht:
int[]TextureIDs={TextureID};
gl.glDeleteTextures(1,TextureIDs,0);
WirgebeneinfachdieTexturIDanundschonistdieTexturGeschichte.MansollteeinegelschteTexturnatrlich
nachdemLschennichtmehrbinden.
Unddaswar'swieder.EigentlichkeineZauberei,einwenigCodeistesaberschon.Wirwerdendarumzwei
Klassenbauen,dieunsfrMeshesundTextureneinwenigArbeitabnehmenunddenCodeschlankermachen.

Mesh&TexturKlasse
FreuerSeelenheilhabichzweiKlassenentwickelt,dieihrsehreinfachverwendenknnt.Zumeinenhabenwir
dadieMeshKlasse:
publicfinalclassMesh
{
publicenumPrimitiveType
{
Points,
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

22/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

Lines,
Triangles,
LineStrip,
TriangleStrip,
TriangleFan
}
publicMesh(GL10gl,intnumVertices,booleanhasColors,booleanhasTextureCoordinates,booleanhasNo
publicvoidrender(PrimitiveTypetype)
publicvoidvertex(floatx,floaty,floatz)
publicvoidcolor(floatr,floatg,floatb,floata)
publicvoidnormal(floatx,floaty,floatz)
publicvoidtexCoord(floats,floatt)
publicvoiddispose();

}
IhrknntsieberdenKonstruktoreinfachinstanzieren.DenerstenParametererhaltetihrinder
GameListener.setup()bzw.GameListener.render()Methode.DerzweiteParametergibtan,wievieleVerticesdas
Meshinsgesamthabensoll.DerdritteParameterbesagt,obdasMeshauchFarbendefiniert,dervierte,obTextur
Koordinatendabeiseinsollenundderletzte,obNormalen vorhandensind.Moment,Normalen?Dieerklren
wirandieserStellenicht.SiewerdenfrdieBeleuchtungvonMeshesdurchLichtquellenbentigt.Damitwirin
spterenTeileneinmaldaraufeingehenknnen,habeichsiegleichmitindenQuellcodeeingebaut.
NachdemihrdasMeshinstanzierthabt,knntihressehreinfachbefllen.UnserColorSamplevonobenwrde
z.B.soausschauen:
mesh=newMesh(gl,3,true,false,false);
mesh.color(1,0,0,1);
mesh.vertex(0.5f,0.5f,0);
mesh.color(0,1,0,1);
mesh.vertex(0.5f,0.5f,0);
mesh.color(0,0,1,1);
mesh.vertex(0,0.5f,0);
AlsRichtliniegilthier:zuerstimmeralleKomponentenungleichderPositionfreinenVertexangeben(Color,
TexturKoordinaten,Normale)undzumfixierendesVertexvertexmitderPositiondesVertexaufrufen.Natrlich
solltetihrnichtmehrVerticesdefinieren,alsihrimKonstruktorangegebenhabt.ZumRenderndesMeshreicht
folgenderAufruf
mesh.render(PrimitiveType.Triangles);

PrimitiveTypeist,wieobenzusehen,einEnum,welchesmehrereArtenvonPrimitivendefiniert.Wirhabenbis
jetztnurdieDreieckebesprochen,essindaberauchanderePrimitivemglich.IhrknntdieseimNetz
nachschlagen(z.B.TriangleStrip),umeinenEinblickzuerlangen.
EinschnesFeaturederKlasseist,dassihrdasMesh,nachdemihreseinmalgerenderthabt,wiederneu
definierenknnt.IhrverwendetdazueinfachwiederdieMethodencolor,texCoordusw.,wievorhergezeigt.
Wichtigdabeiistaber,dassdasMeshmindestenseinmalzuvorgerendertwurde,daansonsteneininternerZeiger
nichtzurckgesetztwird.IhrknnteuchdenCodezurMeshKlasseunter[http://code.google.com/p/android
gamedev/source/browse/trunk/src/com/badlogic/gamedev/tools/Mesh.javahttp://code.google.com/p/android
gamedev/source/browse/trunk/src/com/badlogic/gamedev/tools/Mesh.java ]ansehen.WirklichetwasNeues
macheichdortnicht.DieGrundlagendafrhabtihrbereitsobengesehen.DenQuellcodezueinemBeispiel
Programm,dasdieMeshKlasseverwendet,findetihrunter[http://code.google.com/p/android
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

23/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

gamedev/source/browse/trunk/src/com/badlogic/gamedev/samples/MeshSample.java
http://code.google.com/p/android
gamedev/source/browse/trunk/src/com/badlogic/gamedev/samples/MeshSample.java
derKlassewirdderCodeumeinigesschlankerundverstndlicher.

].DurchdieVerwendung

AlskleinenBonushabichdieKlasseauchnochetwasaufgedrseltundVertexBufferObjectsimplementiert.
Diesewerdenverwendet,wenndasGertdieseuntersttzt.SiegebeneinwenigmehrPerformance.BeiGerten
mitAndroidVersion<=1.5sindsieauchdieeinzigeLsungdaspermanenteGarbageCollecten,welchesmit
VertexArraysauftrittzubeenden.LeideristdadenEntwicklernvonAndroideinkleinerBugunterlaufen,derbei
directBuffernauftritt.Dasganzeerfolgttransparent,ihrmssteuchalsonichtdarumkmmern.Seidihrmitder
VerwendungeinesMeshfertig,msstihrdiesesperAufrufderMethodeMesh.dispose()freigeben.
DieTexturKlasseistnocheinfacherundimFolgendenzusehen.

publicclassTexture
{
publicenumTextureFilter
{
Nearest,
Linear,
MipMap
}
publicenumTextureWrap
{
ClampToEdge,
Wrap
}
publicTexture(GL10gl,Bitmapimage,TextureFilterminFilter,TextureFiltermaxFilter,TextureWraps
publicvoidbind()
publicvoiddispose()
publicvoiddraw(Bitmapbmp,intx,inty)
publicintgetHeight()
publicintgetWidth()

}
BeimInstanzierengebenwirwiederdieGL10Instanzan,ebensowiebeimMesh.Auerdembergebenwirdie
Bitmap,diegewnschtenVergrerungsundVerkleinerungsFiltersowiedieWrapModifrdiesundtTextur
Koordinaten.Diesehabenwirjaobenganzkurzangerissen.AuchMipMappingisthierschonimplementiert,
einfachdenminFilteraufTextureFilter.MipMapsetzen.DesWeiterengibteseineMethodebind()diedieTextur
bindet,wiebeiglBindTexture.DieMethodedisposelschtdieTexturundgibtalleRessourcenfrei.DieMethode
draw()isteinsehrnettesFeature.SieerlaubtesimNachhineineineandereBitmapaneinebestimmtexy
PositioninderTexturzuzeichnen.DieKoordinatenwerdendabeiinPixelnangegeben,derUrsprungistdas
oberelinkeEck,diepositiveyAchsegehtnachunten.InternbindetdieMethodedieTexturvordemzeichnen,
manmusshieralsoaufdenSeiteneffektachten.
WasdieKlassenichtmacht,istdasEinschaltenvonTexturierungberglEnable.Dasalsonichtvergessen.Ein
BeispielfrdieVerwendungderTexturKlasseinZusammenhangmitderMeshKlassefindetihrunter
[http://code.google.com/p/android
gamedev/source/browse/trunk/src/com/badlogic/gamedev/samples/TextureMeshSample.java
http://code.google.com/p/android
gamedev/source/browse/trunk/src/com/badlogic/gamedev/samples/TextureMeshSample.java ].DasMeshdort
verwendetFarbundTexturKoordinaten,waseinenhbschenEffekthat:)
DamithabenwirjetztzweisehrkleineundfeineKlassen,dieunsvielArbeitundCodeabnehmen.

http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

24/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

Projektionen
Ichbineinwenigfies.NachdemKapitelberVektorenhabichversprochen,dassdasallesanMathematikwar,
waswirhiersehenwerden.Ichhabgelogen.WirwerdenunsjetztmitProjektionen beschftigen.Dabeigibtes
zweifrunsrelevanteArten:
Parallelprojektion
Zentralprojektion
DieParallelprojektionwirdauchorthographischeProjektiongenannt,dieZentralprojektionkenntmanauchals
perspektivischeProjektion.WasgenaumachteineProjektion?SienimmtunseredreidimensionalenVertex
Positionenundtransformiertdiesein2DKoordinatenamBildschirm(vereinfachtausgedrckt,biszuden
BildschirmkoordinatengibtesnocheinpaarZwischenschritte,diesparenwirunsaber).Dieorthographische
ProjektionverwendetmanimAllgemeinen,wennmanin2Darbeitenmchte,wiez.B.inaltenNESSpielen.Die
perspektivischeProjektionverwendetmanfralleSpiele,dieeinen3DEindruckverwendenwollen.InderSchule
solltendiemeistenvoneuchschonmalFluchtpunktZeichnungengemachthaben,genaudasselbePrinzip
verwendetauchdieperspektivischeProjektion,nurmathematischausformuliert.
BeginnenwirmitderorthographischenProjektion.FrjedeArtvonProjektionbrauchenwireineProjektionsflche,
inunseremFallistdasderBildschirm.DieorthographischeProjektionnimmteinfachjedenVertexherund
ignoriertdessenzKoordinate.DiexundyKoordinatenwerdenmehrodermindersobernommen,wiesiesind.
Geometrischkannmansichdassovorstellen,dassvonjedemVertexeineLinieausgeht,diedieProjektionsflche
schneidet.DieseLiniensindalleparallelzueinanderundnormal,d.h.ineinem90GradWinkelzur
Projektionsflche.DieSchnittpunktederLinienmitderProjektionsflcheergebendiefinalenprojiziertenPunkte.
EinBildsagtmehralstausendWorte:

Esistalsovollkommenunerheblich,wieweiteinPunktvonderProjektionsflcheentferntist.Dieorthographische
Projektionwerdenwirfrallunsere2DBedrfnisseverwenden.Wirkonfigurierensieso,dasswirdirektin
Bildschirmkoordinatenarbeitenknnen.DazuverwendenwirdieKlasseGLU,dieeinestatischeMethodenamens
glOrtho2Dbesitzt.Umdasgewnschte2DKoordinatenSystemzubekommen,rufenwirfolgendesauf:
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadidentity();
GLU.glOrtho2D(gl,0,activity.getViewportWidth(),0,activity.getViewportHeight())

ZuerstsagenwirOpenGL,dasswirdieProjektionsMatrixabjetztbearbeitenwollen.Projektionensind
TransformationenvonVerticesundwerdeninOpenGLberMatrizenabgebildet.JederVertex,denwiran
OpenGLschicken,wirdmiteinpaarMatrizenmultipliziert,umseinefinalePositionamBildschirmzuerrechnen.
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

25/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

DieProjektionsmatrixisteinedieserMatrizen.WirbrauchenunsaberGottseiDankhiernichtmitMatrizendirekt
herumschlagen.AlsnchstesladenwireineEinheitsMatrix.Manstellesichhiereinfachvor,dassderInhaltder
ProjektionsMatrixdadurchgelschtwirdunddieMatrixkeinenEinflussaufunsereVerticeshat.DieMultiplikation
mitdieserMatrixergibtdenselbenVektor.AbschlieendverwendenwirglOrtho2D,welcheseineorthographische
ProjektionsMatrixldt.DieParametergebendabeian,wiegrodieProjektionsflcheist(stimmtsonichtganz).
WirgebenhierdengesamtenBildschirman,dieAngabensindinPixel.AbnunknnenwirdieVertexPositionen
inBildschirmkoordinatenangeben,wobeidasKoordinatenSystemwiefolgtimPortraitundLandscapeModus
aussieht:

ZurVeranschaulichunghiernochdasGanzemiteinemMesh,daswirindiesemneuenKoordinatenSystemso
definieren:
mesh=newMesh(gl,3,false,false,false);
mesh.vertex(0,0,0);
mesh.vertex(50,0,0);
mesh.vertex(25,50,0);
WirerwartenalsoeinDreieck,dasuntenlinksnebendemUrsprunginErscheinungtritt.Unddastutesauch
(sieheSampleunter[http://code.google.com/p/android
gamedev/source/browse/trunk/src/com/badlogic/gamedev/samples/OrthoSample.java
http://code.google.com/p/android
gamedev/source/browse/trunk/src/com/badlogic/gamedev/samples/OrthoSample.java ]

http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

26/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

Damitknntenwirjetztschonfastunserersteskleines2DSpielchenimplementieren.Dawirabermoderne
Menschensind,wollenwiretwasin3Dmachen.DazubentigenwirdieperspektivischeProjektion.
DieperspektivischeProjektionistumeinStckchenschwererzudurchschauenalsdieorthographische
Projektion,funktioniertabernacheinemhnlichenPrinzip.WiederschickenwirLiniendurchalleVertices,diesmal
jedochnichtnormalzurProjektionsflche,sonderndurcheinenPunktvorderProjektionsflche(aufderanderen
SeitesinddieVertices).

DieserPunktistinsofernbesonders,alsdasserderPositiondesAugeseinesBetrachtersinunserer
dreidimensionalenWeltentspricht.

http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

27/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

DieperspektivischeProjektionwirddurchmehrereParameterdefiniert.ZumeinendurchdensogenanntenField
ofView.DiesistdasSichtfelddasmaninderyAchsebiszurProjektionsflcheabdeckt.Alsnchstesgibtesdie
nearunddiefarClippingPlanes.DienearClippingPlaneistunsererProjektionsebene,mangibthierdie
EntfernungzumBetrachteran.DiefarClippingPlaneistjeneEbeneabdernichtsmehrdargestelltwird.Jeder
VertexderhinterdieserEbeneliegtwirdnichtgezeichnet.AlsletztenParameterfreineperspektivische
ProjektionbrauchtmandasVerhltniszwischenBreiteundHhederProjektionsebene,auchAspectRatio
genannt.DiesenerrechnenwirausderViewportGre,dieinPixelnangegebenist.AlldieseParameter
gemeinsamdefiniereneinenSichtkegel,dervornedurchdienearClippingPlaneundhintendurchdieFar
ClippingPlanebegrenztist.Auchisterobenundunten,sowielinksundrechtsbegrenzt.DiesenSichtkegelnennt
manViewFrustum.GanzschnvielInformationaufeinmal,schauenwirunsan,wieeinfachdasinOpenGLgeht:
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();
floataspectRatio=(float)activity.getViewportWidth()/activity.getViewportHeight();
GLU.gluPerspective(gl,67,aspectRatio,1,100);
WiedersagenwirOpenGL,dasswirdieProjektionsMatrixndernwollenundladendanneineEinheitsMatrix.Als
nchstesberechnenwirdenaspectRatioalsViewportBreitedurchViewportHhe.DieserWertisteine
Dezimalzahl,daherderCastauf(float).ZuguterLetztverwendenwirwiederGLUunddessenMethode
gluPerspective.DerersteParameteristdieGLInstanz,derzweiteParameterdasFieldofViewinGrad,wobei67
GradungefhrdemSichtfeldnachobenundunteneinesMenschenentsprechen.DernchsteParametergibtdie
DistanzzurnearClippingPlanean,hiersetzenwirihnauf1.DerletzteParametergibtdieDistanzzurfarClipping
Planean,hier100,undwirsindauchschonfertigmitderKonfigurationderperspektivischenProjektion.
JetztstelltsichunsdieFrage,woinunserem3DKoordinatenSystemsichderBetrachterbefindet.Wirerinnern
uns,diepositivexAchsezeigtnachrechts,diepositiveyAchsenachobenunddiepositivezAchseausdem
Bildschirmheraus.DienegativezAchsezeigtsomitindenBildschirmhinein.DerBetrachterbefindetsichim
Ursprung,alsoandenKoordinaten(0,0,0)undschautgeradeentlangdernegativenzAchse.DienearClipping
PlanebefindetsichdamitanderzKoordinate1,diefarClippingPlaneanderzKoordinate101.FrunserMesh
bedeutetdas,dasswiresaufzirgendwozwischen1und100ansiedelnmssen.HiereinBeispielmitzwei
Dreiecken,einesaufz=2undeinzweitesaufz=5.BeideDreieckehabendieSelbeGre.
mesh=newMesh(gl,6,true,false,false);
mesh.color(0,1,0,1);
mesh.vertex(0f,0.5f,5);
mesh.color(0,1,0,1);
mesh.vertex(1f,0.5f,5);
mesh.color(0,1,0,1);
mesh.vertex(0.5f,0.5f,5);
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

28/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

mesh.color(1,0,0,1);
mesh.vertex(0.5f,0.5f,2);
mesh.color(1,0,0,1);
mesh.vertex(0.5f,0.5f,2);
mesh.color(1,0,0,1);
mesh.vertex(0,0.5f,2);
DasersteDreieckisteinwenignachrechtsverschobenundliegthinterdemzweitenDreieck.DieDreiecke
werdenvonOpenGLauchindieserReihenfolgegezeichnet,wodurchdashinteregrneDreieckvomvorderen
rotenDreieckberdecktwird:

Wiezuerwarten,erscheintdasgrneDreieckkleineralsdasrote,daesjaauchweiterentferntist.Wirhaben
somitdenSchrittindiedritteDimensiongeschafft!DenSampleCodefindetihrunter
[[http://code.google.com/p/android
gamedev/source/browse/trunk/src/com/badlogic/gamedev/samples/PerspectiveSample.java
http://code.google.com/p/android
gamedev/source/browse/trunk/src/com/badlogic/gamedev/samples/PerspectiveSample.java ]

Kamera,ZBufferundwielscheichdenSchirm
EineKamera,diemannichtbewegenkann,istuerstlangweilig.ZumGlckistesmitHilfederKlasseGLU
extremeinfach,daszundern.DazumssenwirunsaberzuerstvorAugenfhren,wieeineKamerafunktioniert.
ZumeinenhatsienatrlicheinePositioninunserer3DWelt.AuchmusssieeineRichtungbesitzen,indiesie
schaut.AusdemVektorKapitelwissenwir,wiewirdasabbildenknnen.EinBausteinfehltnoch:dersogenannte
UpVektor.DieserdefiniertdieyAchsederKamera,dieRichtungdefiniertdiezAchsederKameraunddiex
AchseknnenwirleichtberdassogenannteKreuzProduktausUpundRichtungsvektorerrechnen.Das
brauchenwiraberallesgarnicht,daunsdasdieGLUKlasseabnimmt.ZumVerstndnisdesUpVektors:man
stellesichvoreinPfeilragteinemsenkrechtausdemKopf.DasistderUpVektor.NeigtmanseinHauptnunnach
rechtsoderlinks,ndertsichauchdieserUpVektorundmitihmderWinkelunterdemmandasBildsieht.Aus
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

29/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

mathematischerSichtseinochangemerkt,dassdieserUpVektorundderRichtungsvektorEinheitsvektorensind
undnormalaufeinanderstehen,alsoim90GradWinkel.
DamitwirunsereWeltausderSichtderKamerasehen,mssenwirwiedereineMatrixvonOpenGLbemhen.
DiesenenntsichdieModelViewMatrix.DerViewTeilbezeichnetdabeidenUmstand,dassmanindieserMatrix
dieKameraMatrix(diesichausdenobengenanntenEigenschaftenderKameraergibt)ablegt.EinVertexwird
zuerstdurchdieModelViewMatrixtransformiert(perMultiplikation)unddannmitderProjektionsMatrix
multipliziert,umseinefinalePositionzubestimmen.Schauenwirunsalsoan,wiewirdieseModelViewMatrixmit
GLUsosetzenknnen,dasswirunsereWeltausderSichtdesKamerasehen:
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
GLU.glLookAt(gl,positionX,positionY,positionZ,zentrumX,zentrumY,zentrumZ,upX,upY,upZ);

DieerstenbeidenZeilenkennenwirjaschon.Indererstensagenwirjedoch,dasswirdieModelViewMatrix
verndernmchten.InderdrittenZeiledefinierenwirunsereKameraausderGLUdanneineMatrixerrechnetund
dieModelViewMatrixaufdiesesetzt.DerersteParameteristwiederdieGLInstanz.DienchstendreiParameter
gebendieKoordinatenderKameraan.zentrumX,zentrumYundzentrumZgebeneinenPunktinderWeltan,auf
dendieKamerablickensoll.NimmtmandiesenPunktundsubtrahiertmandavondieKameraPositionvektoriell,
erhltmandieRichtungderKameraunddamitderenzAchse.DieletztendreiParametergebendenoben
beschriebenenUpVektoran.DiesermusseinEinheitsVektorsein,alsodieLnge1besitzen,sonstknnen
komischeErgebnisseauftreten.
ZiehenwirunsereSzenemitdemrotenunddemgrnenDreieckausdemletztenBeispielheran.Wirwollendie
KamerajetzthinterdasgrneDreieckpositionieren(alsoz<5)undsieinRichtungUrsprungsehenlassen.Die
Neigunglassenwirdabeinormal,d.h.derUpVektorschautnachoben(0,1,0):
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
GLU.glLookAt(0,0,7,0,0,0,0,1,0);
DasErgebnissiehtsoaus(SampleCodeunter[http://code.google.com/p/android
gamedev/source/browse/trunk/src/com/badlogic/gamedev/samples/CameraSample.java
http://code.google.com/p/android
gamedev/source/browse/trunk/src/com/badlogic/gamedev/samples/CameraSample.java

http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

])

30/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

Dascheintetwasschiefgegangenzusein.EigentlichdrftedasroteDreieckjadasgrnenichtberdecken,tutes
aber.DasProblem:ImMeshhabenwirdasroteDreiecknachdemgrnendefiniertundinderReihenfolge
werdensieauchgezeichnet.Betrachtenwirdasganzevonvorne,stimmtalles.Vonuntenpasstesabernicht
mehr.Wastun?InAbhngigkeitvonderBlickrichtungdieOrdnungderDreieckendern?Wasmachtmandann
beisichberlappendenDreiecken,wodieOrdnungnichteindeutigist?
FralldieseProblemegibteseineLsungmitdemklingendenNamen[[http://de.wikipedia.org/wiki/ZBuffer Z
Buffer].DieseristquasieinZusatzzumFrameBuffer(wirerinnernuns,dortwerdenallePixelgespeichert)und
besitztdieSelbeGre.JedesPixeleinesgezeichnetenDreiecksbesitztnebenseinerxundyKoordinatenach
derTransformationmitderProjektionsundModelViewMatrixaucheinezKoordinate.IndenZBufferschreibt
OpenGLgenaudiesezKoordinateundmachtnochetwasbesondersschlaues:bevoresberhaupteinPixel
zeichnet,prftes,obimZBufferbereitseinPixelexistiert,dernheranderKameraliegt.IstdiesderFall,braucht
OpenGLdenaktuellenPixelnichtschreiben,daerjahinterdemaktuellenPixelliegt.Alles,waswiralsotun
mssen,istdiesenZBuffereinzuschaltenunddasgehtso:
gl.glEnable(GL10.GL_DEPTH_TEST);
Dazumssenwirabernochetwastun.UndzwardenZBufferinjedemFramelschen.Undwennwirschondabei
sind,dannknnenwirauchgleichdenFrameBuffermitlschen.Wiedasgeht,istimFolgendenzusehen.
gl.glClear(GL10.GL_COLOR_BUFFER_BIT|GL10.GL_DEPTH_BUFFER_BIT);

WerauchnochdieFarbebestimmenwill,dieimFrameBuffergelschtwerdensoll,kanndiesfolgendermaen
tun:
gl.glClearColor(red,green,blue,alpha);
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

31/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

DerFrameundderZBuffersolltenmglichstinjedemFramegelschtwerden.Normalerweisemacheichdas
immerganzamAnfangdesRendering,damitichdasnichtvergesse.SchauenwirunsaninfolgendemBildan,
wieunserDreieckSortierproblemjetztaussieht.

Ausgezeichnet!UndalldasmitnurzweizustzlichenBefehlen.MitdemZBufferkommenaberauchProbleme:
WennzweiDreieckeinderselbenEbeneliegenundberlappen,kommteszumsogenanntenZFighting .
Diestrittvorallemauf,wennmanbeimRendernmitorthographischerProjektionvergisst,denZBuffermit
glDisable(GL10.GL_DEPTH_TEST)auszuschalten.Wirmssenimmerdarandenken,dasvordemZeichnen
von2DElementenzumachen!
DasProblemderSortierungwirdbeitransparentenDreiecken,alsobeisolchen,durchdiederHintergrund
etwaszusehenist,nichtgelst.ManstellesicheinDreieckvor,dashintereinemandereninderSzeneliegt.
DasverdeckendeDreieckisttransparentundwirdvordemhinterenDreieckgerendert.Ergebnis:dashintere
Dreieckkannnichtdurchscheinen,daseinePixelgarnichterstindenFrameBuffergeschriebenwerden.ImZ
BufferbefindensichjaschondieWertefrdasvordereDreieck,dasnheranderKameraist.DiesesProblem
lstmanimAllgemeinen,indemmanzuerstallenichttransparentenObjektezeichnet,dannalletransparenten
ObjekteberdieDistanzzurKamerasortiertundindersortiertenReihenfolgerendert.
BeachtetmandiesebeidenProbleme,stehtdemvergnglichenGebrauchdesZBuffersnichtsimWeg!

LichtundSchatten
3Dalleinemachtnochkein3DGefhl.DasmenschlicheAugeverwendetnichtnurdasstereoskopischeSehen
zumAbschtzenvonTiefe,sondernauchandereHinweiseundhiervorallemLichtundSchatten.
OpenGLbietethiereinigesanMglichkeiten,zumindestwasLichtbetrifft.Schattenwurf,wiewireskennen,ist
nichtdirektinOpenGLinkludiert,kannaberebenfallssimuliertwerden.MitOpenGLESistdiesjedochzu
rechenaufwndig,wirwerdenunsdahernurumdasLichtkmmern.SchattenbekommenwirinderSparversion:
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

32/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

vomLichtabgewendeteSeitensinddunkler.
UmOpenGLdazuzubewegen,Lichtzusimulieren,mssenwirdieseseinfachanknipsen:
gl.glEnable(GL10.GL_LIGHTING);
DasAusschaltenfunktioniertanalog:
gl.glDisable(GL10.GL_LIGHTING);
Frunsere2DElementewerdenwirkeinLichtbrauchen.WirwollenaberdievollenFarbenhaben.Darum
mssenwirvordemZeichnender2DElementsdasLichtauchausschalten.
OpenGLkannverschiedeneLichtartenundLichtquellensimulieren.WoliegthierderUnterschied?AlsLichtarten
geltenambientesLicht,diffusesLicht,spekularesLichtundemissivesLicht(strenggenommenkeineLichtart).
AmbientesLichtkommtausallenRichtungenundhatkeinebestimmteQuelle.EshandeltsichumjenePhotonen,
dieschontausendeMalevoneinemObjektreflektiertwurdenundsofreinen"Grundlichtpegel"sorgen.Diffuses
LichtgehtvoneinerbestimmtenLichtquelleaus.EswirdinallemglichenRichtungenreflektiert.Dasistso,weil
diemeistenObjekte,dieLichtreflektieren,feineUnebenheitenaufweisen.SpekularesLichthingegenwirdscharf
reflektiert,z.B.aufeinemSpiegelundbildenaneinembestimmtenPunktamObjekteinsogenanntesHighlight,
einenberbeleuchtetenPunkt.EmissivesLichtistLicht,dasvombestrahltenObjektselbstausgeht.Dasfolgende
BildzeigtallevierTypen:ambient,diffuse,spekularundemissiv.

WirwerdenunsnurmitambientemunddiffusemLichtinOpenGLbeschftigen.SpekularesLichtbentigteinsehr
feinaufgelstesMeshunddamitvieleDreiecke,umrichtigzurGeltungzukommen.EmissivesLichtknnenwir
auchberdieFarbedesMeshessimulieren,wasinderRegeleinfacherist.
AlsLichtquellengeltenPunktlichtquellen,wieetwaeineLampe,derenStrahlenradialausstrahlen,direktionale
Lichtquellen,wieetwadieSonne,derenStrahlenaufgrundderEntfernungallesogutwieparallelbeiuns
auftreffenundSpotlichtquellen,wieeingerichteterScheinwerfer,dereinenLichtkegelbildet.Punktlichtquellen
undSpotlichtquellenbesitzeneinePositionimRaum.EinedirektionaleLichtquellewirdinOpenGLalsunendlich
weitentferntangenommenundhatdeswegennureineRichtung.WirwerdenunsnurmitPunktlichtquellenund
direktionalenLichtquellenbeschftigen.FrSpotlichtquellengiltwiederhnliches,wiefrspekularesLicht,sie
bentigenhochaufgelsteMeshes,umzurGeltungzukommen.JederLichttypimitiertambientes,diffusesund
spekularesLicht.WennwireineLichtquelledefinieren,mssenwirfrjedenLichttypendieFarbeangeben,die
dieLichtquellefrdiesenTypenimitiert.OpenGLESkanninsgesamt8Lichtquellenzugleichsimulieren.Diese
Lichtquellenwerdenvon0bis7durchnummeriertundwerdenmitdenKonstantenGL10.GL_LIGHT0bis
GL10.GL_LIGHT7identifiziert.Schauenwirunszuerstan,wiemandieFarbenderLichttypeneinesLichtes
definiert:
floatlightColor[]={1,1,1,1.0};
floatambientLightColor[]={0.2f,0.2f,0.2f,1.0};
gl.glLightfv(GL.GL_LIGHT0,GL10.GL_AMBIENT,lightColor,0);
gl.glLightfv(GL.GL_LIGHT0,GL10.GL_DIFFUSE,lightColor,0);
gl.glLightfv(GL.GL_LIGHT0,GL10.GL_SPECULAR,lightColor,0);
IndererstenZeilebastelnwireinenArraymitweierLichtfarbe.FrdieambienteKomponentedefinierenwirein
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

33/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

dunklesGrauinZeile2.Zeilen3bis5setztdanndieFarbefrjedenLichttypenderLichtquelle0.Soeinfachgeht
das.NatrlichkannmanfrjedenLichttypeineverschiedeneFarbeangeben,hierkannmanexperimentieren.
ObdieLichtquelleeindirektionalesLichtodereinPunktlichtist,definiertmanebenfallsberdieMethode
glLightfv.AnstattalszweitenParameterdenLichttypanzugeben(z.B:GL10.GL_AMBIENT)verwendetmanaber
dieKonstanteGL10.GL_POSITION.AuchbergebenwirwiedereinenfloatArraymitvierElementen.Istdasletzte
Elementgleich0,bedeutetdasfrOpenGL,dasswireindirektionalesLichthabenwollen.Diedreiersten
ElementegebendanndienegativeRichtungdesLichtsan.DieseRichtungmusseinEinheitsvektorsein!Undes
kannalsdiePositioneinerLichtquellegelten.DieRichtungistdannderVektorvonderLichtquellezumUrsprung.
Sehrverwirrend.IstdasvierteElementgleich1,heitdas,dasswireinPunktlichtwollen.DieerstendreiElemente
imArrayentsprechendannderPositiondesLichtsinderWelt.
EindirektionalesLichtvonlinkswrdedemzurFolgesodefiniert:
float[]direction={1,0,0,0};
gl.glLightfv(GL.GL_LIGHT0,GL10.GL_POSITION,direction,0);
WiemaneinePunktlichtquelledirektberdemUrsprungangibt,istimnchstenCodeStckzusehen.
float[]position={0,10,0,1};
gl.glLightfv(GL.GL_LIGHT0,GL10.GL_POSITION,position,0);
WieLichtvoneinemObjektreflektiertwird,hngtnichtnurvomLichttypundderLichtquelleab,auchdasMaterial
desObjektesspieltdabeieineRolle.OpenGLEShateinenrelativgutenMechanismus,umdasMaterialeines
Objektszudefinieren.Sogutdieseristso,solangsamisterleiderauch.WiedermssenwirfrjedenLichttypeine
Farbedefinieren.DieswrdenwirmitderMethodeglMaterialfvmachen.Dieseistaber,wiegesagt,extrem
langsam.WirnehmenhiereineAbkrzungundverwendeneinespezielleMethodevonOpenGLES.
gl.glEnable(GL_COLOR_MATERIAL);
DiesweistOpenGLESan,dassesanstatteinesdefiniertenMaterialseinfachdieFarbedesVertexhernehmen
sollunddiesesfrdieambienteunddiffuseKomponenteverwendensoll.InderRegelkommtmandamit,vor
allemaufkleinenBildschirmen,lockerdurch.
WersichnochandasKapitelMeshundTexturKlasseerinnernkann,denktvielleichtjetztandieNormalen,die
wirproVertexgleichwieFarbeoderTexturKoordinatenangebenknnen.Diesebrauchenwirauchunbedingt,
wennwirOpenGLsBeleuchtungsmodellverwendenwollen.WasistalsosoeineVertexNormale?Dazuein
kleinesBild.

http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

34/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

AnjederEckedesWrfelssitzen3Vertices.Wennwirdarbernachdenken,wirdschnellklarwarum:jedeSeite,
dieaufdieseEcketrifft,hateinDreieckandieserStelle.Dawiresnichtbesserwissen,wrdenwirfrdenWrfel
insgesamt6*2=12Dreieckehabenunddamit6*2*3=36Vertices.JederVertexhatnuneineNormale.Diese
NormaleistnormalzurEbene,inderdasDreieckliegtundragtausderVorderseitedesDreiecks.OpenGL
verwendetdieseNormale,umdenWinkeldesVerticeszurLichtquellezuberechnen.Darummssenwirin
Meshesunbedingtangeben,waswirbeleuchtenwollen.ImCodezurMeshKlasseknntihreuchanschauen,wie
mandieNormaleeinesVertexOpenGLbergibt.DerMechanismusist1:1derselbe,wiebeiFarbenundTextur
Koordinaten,darumgeheichhiernichtnochmalgesondertdraufein.Zurbungversuchenwireinfachschnell
einMeshzumachen,dasdendreiinderobigenZeichnungeingezeichnetenDreieckenentspricht.Wirwerdenes
sodefinieren,dassdasdunkelsteDreieckinderxyEbeneliegt.
mesh=newMesh(gl,9,false,false,true);
mesh.normal(0,0,1);
mesh.vertex(1,0,0);
mesh.normal(0,0,1);
mesh.vertex(1,1,0);
mesh.normal(0,0,1);
mesh.vertex(0,1,0);
mesh.normal(1,0,0);
mesh.vertex(1,0,0);
mesh.normal(1,0,0);
mesh.vertex(1,0,1);
mesh.normal(1,0,0);
mesh.vertex(1,1,0);
mesh.normal(0,1,0);
mesh.vertex(1,1,0);
mesh.normal(0,1,0);
mesh.vertex(1,1,1);
mesh.normal(0,1,0);
mesh.vertex(0,1,0);
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

35/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

Pfuh,ganzschnvielCodefrdreiDreiecke.WirwerdendaspterAbhilfeschaffen.BeimRendernmssenwir
jetzteinpaarDingeerledigen.ZumeinendieBeleuchtungeinschalten,danachdieLichtquelledefinieren.Dann
dieLichtquelleeinschaltenundvordemRenderndesMeshnochColorMaterialaktivieren.AlsLichtquelle
nehmenwireineweieDirektionale,dievonRechtsobenkommt(1,1,0):
gl.glEnable(GL10.GL_LIGHTING);
float[]lightColor={1,1,1,1};
float[]ambientLightColor={0.2f,0.2f,0.2f,1};
gl.glLightfv(GL10.GL_LIGHT0,GL10.GL_AMBIENT,ambientLightColor);
gl.glLightfv(GL10.GL_LIGHT0,GL10.GL_DIFFUSE,lightColor);
gl.glLightfv(GL10.GL_LIGHT0,GL10.GL_SPECULAR,lightColor);
float[]direction={1/(float)Math.sqrt(2),1/(float)Math.sqrt(2),0,0};
gl.glLightfv(GL10.GL_LIGHT0,GL10.GL_POSITION,direction);
gl.glEnable(GL10.GL_LIGHT0);
gl.glEnable(GL10.GL_COLOR_MATERIAL);
mesh.render(PrimitiveType.Triangles);
WiedereinHllenAufwand.WirhabenaucheinpaarFauxPasgeschossen.ZumeinenwrdenwirsoimMain
LooppermanentzweineuefloatArraysinstanzieren.DaswrdeirgendwanndenGarbageCollectorverstimmen,
dersichdanneinpaarhundertMillisekundenAuszeitnimmt,umaufzurumen.WirwerdenimSampleCodedie
beidenArrayszuKlassenMemberunsererSampleActivitymachenundsomitnureinmalinstanzieren.Zweitens
mssenwireineLichtquellenichtimmerneudefinieren.WennsichdieseberdenVerlaufnichtndert,reichtes
derenLichttypFarbennureinmalanzugeben.DiePosition/RichtungderLichtquellemssenwiraberimmernach
demAufrufvonGLU.glLookAtmachen,dadiePositionsonstineinemanderenKoordinatenSystemdefiniertwird.
Verwirrend.WersichbrigensfragtwarumwirdiedirectionElementedurchMath.sqrt(2)dividieren:Hier
normalisierenwirdenRichtungsvektor!((1,1,0)'=[1/|(1,1,0)|,1/|(1,1,0),0/|(1,1,0)]).
UmdieSituationimBildobennachzustellenwerdenwirauchdieKameraPositionundRichtungentsprechend
setzen.DengenauenCodeknntihrhiersehen[http://code.google.com/p/android
gamedev/source/browse/trunk/src/com/badlogic/gamedev/samples/LightSample.java
http://code.google.com/p/android
gamedev/source/browse/trunk/src/com/badlogic/gamedev/samples/LightSample.java ].DasErgebnissiehtso
aus:

http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

36/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

EinePunktlichtquellewrdekomplettanalogdazudefiniertundeingesetztwerden,mitdemUnterschiedimvierten
ElementimPositionsArray.UndwiedereinGeheimnisvonOpenGLgelftet!

Transformationen
Jetztwird'snochmalkurzmathematisch.Vielleichthatsichdereineoderanderebereitsgefragt,wiemandennein
MeshanverschiedenenPositionenmehrereMalezeichnenkann.Schlielichsiehtmandasjaauchinanderen
Spielen,z.B.ineinemEchtzeitstrategiespiel,woderselbeEinheitentypmehrereMalegezeichnetwird,nuran
verschiedenenPositionenundinunterschiedlicherAusrichtung.InOpenGLverwendetmandazuwiederMatrizen,
genauer,diebereitserwhnteModelViewMatrix.HiererklrtsichauchderersteTeildesNamensderMatrix:
ModelstehtfrdieMglichkeiteinModel(Mesh)inderWeltzuverschieben,zurotierenundzuskalieren(grer
undkleinermachen).
UntereinerTransformationverstehtmandieVerschiebung(Translation),SkalierungundRotationvonVerticesin
derWelt.DabeikannmanmehreresolcherTransformationenberdieModelViewMatrixkombinieren,z.B.zuerst
skalieren,dannrotierenundzumSchlussverschieben.MathematischgesehenentsprichtdasderMultiplikation
vonMatrizen.FrjedeTransformationwirdeineMatrixerstellt,diesewerdendannindergewnschten
ReihenfolgederTransformationenmiteinandermultipliziert.WiedermssenwirunszumGlcknichtdirektmit
Matrizenherumschlagen,OpenGLbietetunsverschiedeneMethodendiedaserstellenundmultiplizierender
Matrizenfrunserledigt.
FangenwirmitderTranslationan.DiesewirdbereinenTranslationsVektorangegebenderzuallenVertices,die
manrenderthinzuaddiertwird.
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

37/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

InOpenGLESerreichenwirdas,indemwirfolgendeMethodeverwenden:
gl.glTranslatef(x,y,z);
Wieunschwerzuerkennen,handeltessichbeidendreiParameternumdenTranslationsVektor.DieseMethode
erstelltinterneineTranslationsMatrixundmultipliziertdieaktuellaktiveMatrixdamit,z.B.dieModelViewMatrix
diewirberglMatrixModeausgewhlthaben.
DieSkalierungmultipliziertjedeKomponentederVertexPositionmiteinemSkalierungsfaktor.

OpenGLstelltdafrfolgendeMethodezurVerfgung:
gl.glScalef(scaleX,scaleY,scaleZ);
FrjedederdreiAchsengibteseineneigenenSkalierungsfaktor.WiederwirdinterneineMatrixerstellt,mitden
WertenfrdieSkalierungbeflltunddannmitderaktuellaktivenMatrixmultipliziert.
DieRotationisteinwenigschwererzuverstehen.GedrehtwirdimmerumdenUrsprung.Gleichzeitigmssenwir
eineAchseangeben,(dieimplizitdurchdenUrsprunggeht)umdiesichdieVerticesdrehensollen.

http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

38/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

DieOpenGLMethodedafr:
gl.glRotatef(angle,axisX,axisY,axisZ);
AnglegibtdenWinkelinGradan,axisXbisaxisZistdieRotationsachse,umdiegedrehtwerdensoll.Imobigen
Beispielistdiese(0,0,1).WiebeivielenanderenDingen,mussdieRotationsachseeinEinheitsvektorsein.
DiesedreiTransformationenknnenwirbeliebigmiteinanderkombinieren,z.B.verschieben,rotieren,skalieren
usw.AlsaktiveMatrixwhlenwirfrdieseTransformationenimmerdieModelViewMatrixberglMatrixMode.
SchauenwirunseinmalanwasdieKombinationvonTranslationundRotationbewirkt:

ZuerstverschiebenwirdasDreieckeinwenignachrechts,dannrotierenwirumdiepositivezAchse.Wennwir
dasganzeumdrehen,siehtdasErgebnissoaus:

http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

39/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

EinekomplettandereWirkung.WirmssenbeiderAnwendungvonTransformationenimmeraufdieReihenfolge
schauen.DasersteBeispielwrdenwirmitOpenGLsorealisieren:
gl.glRotatef(45,0,0,1);
gl.glTranslatef(2,0,0);
Hm,solltedasnichtumgekehrtsein?Wirwollenjazuerstverschiebenunddannrotieren.OpenGListdaanderer
Ansicht,dieletzteTransformation,diewirberdieTransformationsMethodenangeben,istimmerdieerste,die
aufdieVerticeswirkt.DiesresultiertausderArt,wieOpenGLMatrizenmultipliziertundsollunshiernichtweiter
kmmern.WirmssenunsnurdenUmstandmerken,dasswirTransformationenimmerinderumgekehrten
Reihenfolgeausfhrenmssen.
Wasichbisjetztverschwiegenhabe,istderZusammenhangzwischenTransformationenundderKamera.Wir
befllenin3DjadieModelViewMatrixperGLU.gluLookAtbereitsmitderKameraMatrix.Zeichnenwirnun
mehrereObjektemitjeweilseigenenTransformationen,msstenwirdieKameraMatrixvordemZeichneneines
ObjektesjedesMalneusetzen,dawirjadieModelViewMatrixbeimvorhergehendenObjektberschrieben
haben.UmdieserechtkostspieligeOperationzuvermeiden,gibteszweiBefehle:
gl.glPushMatrix();
gl.glPopMatrix();
InWirklichkeitgibtesfrjedeMatrixinOpenGL(Projektion,ModelView)nichtnureineMatrix,sonderneinen
StackanMatrizen.MitdenobenvorgestelltenMethodenmanipulierenwirimmerdieSpitzedesStack.Diebeiden
MethodenglPushMatrix()undglPopMatrix()erlaubenesuns,dieberglMatrixModeaktuellselektierteMatrixauf
denStackzulegen,bzw.diezuletztaufdenStackgelegteMatrixwiederzuraktuellenMatrixzumachen.Beim
pushenderMatrixwirdeineKopieangelegt,dieaktuelleMatrixbleibtdieselbe.
InSpielengehtmaninderRegelsovor:jedesObjektinderSpielwelthateineOrientierung(alsoeineRichtung,in
dieesschaut)undeinePosition.DieMeshesfrdieObjektesindimmerumdenUrsprungdefiniert.Zeichnetman
nundieObjekte,ldtmanzuallererstdieKameraMatrixindieModelViewMatrix.BeimZeichnenjedesObjektes
pushenwirdieModelViewMatrix,damitwireineKopiederKameraMatrixaufdemStackhaben.Dann
multiplizierenwirdieTransformationenaufdieModelViewMatrixundzeichnendasMeshdesObjekts.Das
Objektwirddamitrichtigtransformiert.DannpopenwirdieModelViewMatrixwiedervomStack.AufdieseWeise
beinhaltetdieserwiedernurdieKameraMatrix.DiesenProzesswiederholenwirfralleObjekte,diewirzeichnen.
SowerdenwirdasdannauchinunseremSpaceInvadersKlonmachen.EinSample,daseinwenigvorgreift,
(verwendetdenObjMeshLoader)findetihrunter[http://code.google.com/p/android
gamedev/source/browse/trunk/src/com/badlogic/gamedev/samples/MultipleObjectsSample.java
http://code.google.com/p/android
gamedev/source/browse/trunk/src/com/badlogic/gamedev/samples/MultipleObjectsSample.java ].DasSample
zeigtauch,wiemaneinenApplikationFullscreenmachtunddieOrientierungfixiert.DasGanzeistim
untenstehendenBildzusehen.

http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

40/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

DamithabenwirdenletztengroenBrocken,wasOpenGLbetrifft,abgearbeitet.HiergiltdasGleiche,wiefrLicht:
experimentieren,experimentieren,experimentieren.UmTransformationenzuverstehen,mussmansieinAktion
sehen.AlskleineAufgabeknntihrjaeinesderSampleshernehmenundeinumdieyAchserotierendesDreieck
produzieren.DazumsstihrnurinjedemFrameglRotatefmitdemaktuellenWinkelaufrufen.DenWinkelmuss
mannatrlichinjedemFrameerhhenund,wennergrerals360ist,wiederauf0zurcksetzen.

Textzeichnen
FrdieAnzeigevonScoresundhnlichembrauchenwireineMglichkeit,Textzuzeichnen.DiesistinOpenGL
vonHausausnichtintegriert,schlielichkannOpenGLohneunserzutunjawirklichnurDreieckezeichnen.Die
herkmmlicheHerangehensweisefrdieImplementierungvonTextinOpenGLfunktioniertaberrelativeinfach.
DasBetriebssystembietetinderRegelMglichkeiten,umandieBitmapsfreinzelneCharacters,sprichZeichen,
einerbestimmtenSchriftartzukommen.Alles,waswirmachenmssen,istdieseBitmapsineineTexturzu
zeichnenundunszumerken,woinderTexturwirdieBitmapfreinenCharacterfinden.WollenwirTextzeichnen,
mssenwirlediglicheinMesherstellen,diefrjedenCharacterimStringzweiDreieckebesitzt,dieeinViereck
bilden.DasViereckmussgenausogrosein,wiedieBitmapfrdenCharacter.Danachmappenwirdiesebeiden
DreieckesomitderCharacterTextur,dasdiesegenaudenAusschnittderTexturverwenden,wodieBitmapdes
Charactershingezeichnetwurde.brigensnenntmandieBitmapfrsoeinenCharacterauchGlyph.DieTextur
istdemzufolgeeinsogenannterGlyphCache.UmdieseganzemhseligeArbeiteinwenigzuvereinfachen,habe
icheineKlasseFontsowieeineKlasseTextgeschrieben,dieunsdieseganzeArbeitabnimmt.DieKlasseFont
hatdabeinureinpaarrelevanteFunktionen:
publicclassFont
{
publicFont(GL10gl,StringfontName,intsize,FontStylestyle)
publicFont(GL10gl,AssetManagerassets,Stringfile,intsize,FontStylestyle)
publicTextnewText(GL10gl)
publicvoiddispose()
}

DieerstenbeidenMethodensinddieKonstruktorenderKlasse.DerersteKonstruktorinstanzierteinenFontber
dieAngabeseinesNamens.DamitkannmanFonts,dieimSysteminstalliertsind,instanzieren.Derdritte
ParametergibtdieGredesFontsinPunktenan,derviertedenStildesFonts,alsoz.B.italicoderboldusw.Der
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

41/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

zweiteKonstruktorerlaubtdasLadeneinesTrueTypeFontsauseinerAssetDatei.Dazugebenwirden
AssetManageran,denwirvonunsererActivityerhalten,sowiedenDateinamendesFontAssets.Dierestlichen
ParameterentsprechendemerstenKonstruktor.DiedritteMethodeinstanzierteineInstanzderKlasseText.Diese
speichertdieDreieckeaufdenvonunsgewnschtenText.DieletzteMethodegibtdenFontundseine
Ressourcen(GlyphcacheTextur)wiederfrei.
DieKlasseTexthateinigeMethodenzumformatierenvonText,diewiraberfrunserenSpaceInvadersKlon
nichtbrauchen.HierdiewichtigstenMethoden.
publicclassText
{
publicvoidsetText(Stringtext);
publicvoidrender();
}

DieersteMethodesetztdenText,denwirzeichnenwollen.InternwerdendabeidieentsprechendenDreiecke
erstellt,dieaufdieGlyphcacheTexturdesFonts,vondemdieInstanzTextkommt,mappen.DieMethoderender
zeichnetdenTextdann,beginnendimUrsprung.WirknnensptereinfachTransformationenverwenden,umden
TextamBildschirmzuverschieben.Wichtigistauch,dasswirbeimZeichneneineorthographischeProjektion
verwenden,dieein2DPixelkoordinatensystemverwendet.DieKlasseTextkannauchmehrzeiligenTextrendern,
diesenlinksbndig,zentriertundrechtsbndigausrichtenoderhnliches.Einwenigdamitherumspielenundman
hatdenDrehheraus.
DamitauchnurwirklichdiePixeldesTextesgerendertwerden,mssenwirauchBlendingeinschalten.Blending
sorgtfrTransparenz,jederPixelineinerTexturmiteinemAlphawertkleinerals1wirddurchsichtig.Dasselbegilt
auchfrPolygone,derenVerticesFarbwertemitAlphawertenkleinerals1haben.Blendingschaltenwirin
OpenGLsoein:
gl.glEnable(GL10.GL_BLEND);
gl.glBlendFunc(GL10.GL_SRC_ALPHA,GL10.GL_ONE_MINUS_SRC_ALPHA);

DerersteAufrufschaltetBlendingein,derzweitelegtfest,wiegeblendetwird.Blendingisteinsehrkomplexer
Themenkreis,deshalbwerdeichBlendfunctionshiernichtbesprechen.DieobenangegebeneBlendfunction
reichtfr90%allerBedrfnisseaus.Siebesagt,dassdiePixeldesgeradegezeichnetenDreiecksmitdenPixeln
imFramebuffergeblendetwerdensollen.DieseBlendfunctionbrauchenwirfrunserenText,aberauchfrdas
AnzeigenvonanderendurchscheinendenObjekten,wiez.B.Explosionen.ZumAusschaltenvonBlendingreicht
einAufruf.
gl.glDisable(GL10.GL_BLEND);
EinkomplettesSamplezumZeichnenvonTextunterorthographischerProjektion,findetihrunter
[http://code.google.com/p/android
gamedev/source/browse/trunk/src/com/badlogic/gamedev/samples/TextSample.java
http://code.google.com/p/android
gamedev/source/browse/trunk/src/com/badlogic/gamedev/samples/TextSample.java ].

http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

42/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

Meshesladen
NachdemwirdenSchrittindiedritteDimensiongewagthaben,wreesnatrlichnichtschlecht,eineMglichkeit
zubesitzen,MeshesausanderenProgrammenzuladen.EinesdereinfachstenMeshFormateistdasWavefront
OBJFormat .IchhabemirdieFreiheitgenommen,dafreineneinfachenLaderzuschreiben.Dieserkannmit
ObjDateienumgehen,dienurDreieckebeinhalten.BautihralsoeureeigenenMeshesinWings3D oder
Blender ,knntihreureMeshesvondortausineineObjDateiexportierenundmitdemLaderladen.DerLader
besitztnureinestatischeMethode:
MeshMeshLoader.loadObj(GL10gl,InputStreamin)

ZumLadenbergebenwiralsonureinenInputStreamaufeinObjAssetunderhalteneineMeshzurck,diefix
undfertigeist.WenndasMeshNormalenoderTexturKoordinatenhat,werdendiesenatrlichmitgeladenund
knnendannmiteinerLichtquellebzw.Texturverwendetwerden.IchhabeinWings3DeinkleinesRaumschiff
gebautundmitGimpdafreineTexturerstellt.DieObjDateiunddieTexturverwendeichineinemweiteren
Sample,dasihrunter[http://code.google.com/p/android
gamedev/source/browse/trunk/src/com/badlogic/gamedev/samples/ObjSample.java
http://code.google.com/p/android
gamedev/source/browse/trunk/src/com/badlogic/gamedev/samples/ObjSample.java ]findet.EsistimGrundedas
LightSamplemitdemUnterschied,dassicheineTexturunddasMeshausderObjDateilade.Auerdemhabe
ichmirerlaubt,hierdieAufgabeausdemTransformationsKapitelsumzusetzen.DasSchiffdrehtsichhbsch.Hier
nocheinScreenshot.

http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

43/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

Jetzthabenwiraberwirklichallesbesprochen,waseszubesprechengibt.AufzumSound!

SoundPoolundMediaPlayer
SoundeffekteundMusikwerdenunsindiesemKapitelbeschftigen.FrbeidesstelltunsAndroidzweihandliche
KlassenzurVerfgung:SoundPoolfrSoundeffekteundMediaPlayerfrdasabspielenvonMusik.Fangenwirmit
SoundPoolan.
WiewirunserinnernsindSoundeffekteAudioDateiendiewiraufgrundihrerkleinenGrevollstndiginden
Speicherladen.AuchkanneinundderselbeSoundeffektmehrereMalegleichzeitigabzuspielensein.Genau
dieseAufgabenerledigtfrunsderSoundPool .Schauenwirunsanwie,manihninstanziert.
SoundPoolsoundPool=newSoundPool(5,AudioManager.STREAM_MUSIC,0);
Sehreinfach.DerersteParametergibtan,wievieleSoundeffektederSoundpoolmaximalgleichzeitigabspielen
kann.Hiergehteswirklichnurumdasabspielen,ladenknnenwirsoviele,wiewirSpeicherhaben.Derzweite
Parametergibtan,umwelchenStreamessichhandelt.AufAndroidgibtesverschiedeneKanlefrAudio,z.B.
denKlingeltonStreamoderebendenhiergewhltenMusikStream.DiesenwhlenwirimFallvonSoundeffekten
immer.DerletzteParameterhatzurzeitnochkeineFunktionundsolllautDokumentationauf0gesetztwerden,
waswirauchtun.
EinenSoundeffektzuladen,gehtauchsehreinfach.Wirgehendavonaus,dasswireineAudioDateinamens
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

44/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

"shot.wav"inunseremAssetVerzeichnishaben.Eswirdwiefolgtgeladen:
AssetFileDescriptordescriptor=getAssets().openFd("shot.wav");
intsoundID=soundPool.load(descriptor,1);

AlserstesbentigenwireinenAssetFileDescriptor,denwirunsfrdieAudioDateiindererstenZeileholen.
DiesenbergebenwirdanninderzweitenZeileandieMethodeSoundPool.loaddieunsdanndieAudioDateiin
denSpeicherldt.AlsRckgabewerterhaltenwir,frdiegeradegeladeneDatei,eineID,diewirspterangeben
mssen,wennwirdiesenSoundeffektabspielenwollen.EinekleineWarnung:EsdauerteineZeit,bisder
SoundPoolalleSoundeffektegeladenhat.DasmachterineinemseparatenThread,d.h.inunseremSpielwerden
wirdavonlaufzeittechnischnichtsmerken.Waswirabermerken,ist,dassindenerstenpaarSekundennachdem
LadendasAbspielenkeinenEffekthat.HierfrgibtesleidernochkeineLsung,daeinSpielabermeistensin
einemMenbeginntunddortkeineSoundeffekteverwendetwerden,istdasnormalerweisekeinProblem.
DasAbspielendesebengeladenenSoundEffektsistdannebensosimpel:

intvolume=(AudioManager)activity.getSystemService(Context.AUDIO_SERVICE).getStreamVolume(AudioManager
soundPool.play(soundID,volume,volume,1,0,1);

Alserstesmssenwirherausfinden,wielautdieMedienlautstrkeaktuellist.DieseInformationbekommenwirvon
derMethodegetStreamVolumederKlasseAudioManager,derenInstanzwirwiederumbergetSystemService
erhalten.AlsStreamgebenwirwiederdenMusikStreaman,dawirjamitdiesemArbeiten.Denzurckgegebenen
Wert,merkenwirunsundverwendenihninderzweitenZeile.HierweisenwirdenSoundPoolan,denzuvor
geladenenSoundeffektabzuspielen.DazugebenwiralserstenParameterdieIDan,diewirzuvorerhalten
haben,danndieLautstrkedeslinkenundrechtenKanals.DervierteParametergibtdiePriorittan,mitderder
Soundeffektabgespieltwerdensoll.DerWert1erweistunshierguteDienste.DerfnfteParametergibtan,obder
Soundeffektgeloopt,alsomehreremalehintereinanderabgespieltwerdensoll.Wiegebenhier0an,dawirdas
nichtwollen.DerletzteParametergibtan,mitwelcherGeschwindigkeitderSoundeffektabgespieltwerdensoll.1
bedeutetinnormalerGeschwindigkeit,2wrdedoppelteGeschwindigkeitbedeutenundsoweiter.Damitwissen
wirjetzt,wiewirSoundeffekteabspielenknnen.
EinSample,welchesbeimberhrendesBildschirmseinSchiegeruschmacht,findetihrunter
[http://code.google.com/p/android
gamedev/source/browse/trunk/src/com/badlogic/gamedev/samples/SoundSample.java
http://code.google.com/p/android
gamedev/source/browse/trunk/src/com/badlogic/gamedev/samples/SoundSample.java ].
FrdasAbspielenvonMusikverwendenwirdenMediaPlayer .DergibtsichvonderDokumentationherrecht
kompliziert,isteraberimGrundenicht.Schauenwirunszuerstan,wiewirihnInstanzieren:
MediaPlayermediaPlayer=newMediaPlayer();

Daswarwiedereinfach.AlsnchstesmssenwirdemMediaPlayersagen,waserabspielensoll.Dafrbrauchen
wirwiedereinenAssetFileDescriptor,wieschonbeidenSoundeffekten:
AssetFileDescriptordescriptor=getAssets().openFd("music.mp3");
mediaPlayer.setDataSource(descriptor.getFileDescriptor());
mediaPlayer.prepare();
mediaPlayer.start();

http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

45/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

WirholenunsalsodenFileDescriptoraufunserenMusikAsset.IndernchstenZeilesetzenwirdenMediaPlayer
dannvondieserDateiinKenntnis.InderdrittenZeilegebenwirdemMediaPlayerZeit,sichaufdasAbspielen
vorzubereiten.OhnedenAufrufvonMediaPlayer.preparespieltderMediaPlayernichts!Schlielichstartenwirdas
PlaybackderMusik.
DerMediaPlayerbietetallemglichenMethodenzumpausieren,zurckspulenundsoweiter.FrunsereZwecke
reichtdaseinmaligestartenabervollkommenaus.WaswirbeimMediaPlayernochbeachtenmssen,ist,dasswir
ihnbeimPausierenderApplikationwiederfreigebenmssen.Ansonstenspieltereinfachweiter.Wir
berschreibendazudieonPauseMethodeunsererGameActivity:
@Override
protectedvoidonPause()
{

super.onPause();

mediaPlayer.release();
}

NichtaufdenAufrufvonsuper.onPause()vergessen!EinSample,daseinekleineEigenkompositionspielt,findet
ihrunter[http://code.google.com/p/android
gamedev/source/browse/trunk/src/com/badlogic/gamedev/samples/MusicSample.java
http://code.google.com/p/android
gamedev/source/browse/trunk/src/com/badlogic/gamedev/samples/MusicSample.java

].

DaswareinerfrischendkurzesundeinfachesKapitel.WirhabenjetztallentigenToolszusammengetragen,die
wirfrdieEntwicklungeineskleinenSpielsbrauchen.AlldiehierbesprochenenSamplesundKlassenfindetihr
imProjekt.Ichempfehleeuchdamitherumzuspielen,damannursoeinGefhlfrdieDingeerhlt.Auchistdas
LesenderhierverlinktenDokumentationenkeineschlechteIdee.Themoreyouknow...
AufzuSpaceInvaders!

SpaceInvaders
WerSpaceInvadersnichtkenntsollsichzuersteinmalselbstOhrfeigen.NebenAsteroidswarSpaceInvadersdas
ersteShotem'up,daskommerzielluersterfolgreichwar.Ausgezeichnethatesdie,frdamaligeVerhltnisse,
groeMengeanObjekten,diegleichzeitigamBildschirmdargestelltwurden.DasSpielprinzipistdabeiextrem
simpel.AlsKommandeureineskleinenRaumschiffes,giltes,auerirdischeRaumschiffedavonabzuhalten,die
Erdezuberrennen.Dasschafftman,indemmanaufdieRaumschiffeschiet,diedannberdenJordangehen.
EinenKlondesOriginalskannmanunter[http://www.spaceinvaders.de/http://www.spaceinvaders.de/ ]spielen,
wasichhiermitjedemempfehle.HiernocheinkleinerScreenshot:

http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

46/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

BevorwirunsandieUmsetzungdesSpielsmachen,wollenwirseineeinzelnenTeileanalysieren,damitwireine
genaueVorstellungdavonhaben,waswirberhauptallesimplementierenmssen.

AnalysedesOriginals
SpaceInvadershateinaufdenBildschirmbegrenztesSpielfeld.EsgibtvierArtenvonInvaders,diedrei,dieoben
imScreenshotsichtbarsind,sowieeinUfo,dasvonZeitzuZeitamoberenBildschirmrandvorbeifliegt.DieInvader
sinddabeiineinemNetzingleichenAbstndenangeordnet.JedeReihebestehtauselfInvadern,insgesamtgibt
esfnfReihen.DieInvaderfahrenvonlinksnachrechts,danacheineReiheweitnachunten,dannvonrechts
nachlinks.Dasganzewiederholtsich,dieInvaderkommendabeiimmernherandasSchiffheran.Gleichzeitig
schieendieInvaderzufallsbasierthinundwieder.AmunterenBildschirmrandbefindensichSchildblcke,die
SchssederInvader,sowiedesSchiffes,abfangen.DieBlckewerdendurchSchssezerstrt.Siebildeneine
BarrierezwischendemSchiffunddenInvadern,dieimSpielverlaufverschwindet.SobaldeinInvaderaufHhe
derBlckeist,verschwindendiebisdahinverbleibendenBlckekomplett.KollidierteinInvadermitdemSchiff,
verliertdieseseinesvonseineninsgesamtdreiLeben.DasSchiffselbstkannimmernureinenSchussabfeuern.
DernchsteSchusskannerstabgefeuertwerden,wennderersteverschwundenist.DiesistderFall,wennein
InvadergetroffenwirdoderderSchussdasSpielfeldverlsst.HatderSpieleralleInvaderaufdemBildschirm
vernichtet,kommteineneueWelleanInvadern,dieschnellersindalsdievorhergehenden.DieBlckewerden
restauriertunddasganzebeginntvonvorne,bisderSpieleralleLebenverlorenhat.DerAbschusseinesInvaders
bringtPunkte.DieAnzahlderPunkteistdabeiabhngigvomTypendesInvaders.WirddasSchiffvoneinem
Schussgetroffen,explodiertes,ebensowiebeieinemgetroffenenInvader.EskanndannfreinigeSekundenicht
kontrolliertwerden.AnderPosition,anderdasSchiffzerstrtwurde,respawnedeswenigspter.DieKontrolle
desSchiffeserlaubtdasSteuernnachlinksundrechts,sowiedasAbfeuerneinesSchusses,wennnochkein
SchussdesSchiffesimSpielfeldist.
AusalldemGesagtenlassensichdieElementefrdasSpielrelativeinfachableiten:
Schiff
Invader
Explosion
Block
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

47/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

Schuss
Wirwerdenunsalsnchstesanschauen,wiewirunserenKlonstrukturieren.DiehierbesprochenenElemente
werdeninunserenKloneinflieen,einpaarDingewerdenwiradaptieren(mssen).

DasSpielfeld
UnserSpaceInvaderKlonsollimGrundeseinesHerzensnachwievorein2DSpielbleiben.Wirwerdenaus
diesemGrunddasSpielfeldselbstinunseremdreidimensionalenRaumindiexzEbeneverlegen.ImOriginal
wurdedasSpielfeldvomBildschirmselbstbegrenzt,dawirunsineinemdreidimensionalenRaumbefinden,
machenwirdieseBegrenzungknstlich.DieuntereGrenzedesSpielfeldesstelltdieXAchsedar(z=0).Aufdieser
werdenwirspterdasSchiffbewegen.DenoberenRanddesSpielfeldessetzenwiraufdenz=15.Linksund
rechtsbegrenzenwirdasSpielfeldaufx=13bzw.x=13.UnserSpielfeldistdamit(13+13)*15Einheitenground
liegtinderxzEbene,wobeiallunserezWerte<=0und>=15seinwerden,unserexWerte>=13und<=13.

Manbeachte,wiegesagt,dassdasSpielfeldimnegativenzBereichliegt.EinFeldhatdabeidieAbmessungen
2x2.DieyAchsespieltindiesemSpielkeineRolle,alleyWertewerdengleich0sein.
NebendenSpielfeldDimensionenmssenwirunsauchGedankenberdieGrendereinzelnenSpielelemente
machen.DerEinfachheithalber,werdenwirderenRadiusauf0.5undihrenDurchmesserdamitauf1festlegen.
DiesentsprichtweitestgehenddemOriginal,indemInvadersundSchiffungefhrgleichgrosind.Auchdie
initialePositionierungderInvaderundBlckemssenwirunsberlegen.ImOriginalstehendieInvaderam
oberenEndedesSpielfeldesmittig.ZwischendenInvadernistimmereinkleinerFreiraum.Wirwerdendas
nachbilden.AusPerformancegrndenmssenwirdieAnzahlderInvaderetwasherunterschrauben.Anstattfnf
ReihenzujeelfInvadern,werdenwirvierReihenzujeachtInvadernhaben.DieInvaderhabeneinen
Durchmesservon1,alsowerdenwirsieimAbstandvonzweiEinheitennebenunduntereinanderpositionieren.
DieInvaderderoberstenReihehabenalsozWertevon15,diedernchstenReihe13usw.DerInvaderganz
linkseinerReihesitztaufx=7,dernchsteauf5usw.DasGanzesiehtzuBeginndesSpielssoaus:

http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

48/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

AuchbeidenBlckenwerdenwireinwenigvomOriginalabweichen.IndiesemBestandeinBlockausmehreren
Subblcken.DiesenUmstandwerdenwirbernehmen.AusPerformancegrndenwerdenwirdieSubblckeaber
vereinfachen.EinBlockbildetausfnfSubblckeneineUForm.JederSubblockhatdabeidieGre1x1.Anstatt
vierBlcke,wieimOriginal,werdenwirnurdreiBlckehaben.DieseverteilenwirgleichmigberdasSpielfeld.
DasZentrumdeserstenBlocksbefindetsichdabeianPositionx=10,z=2.5,dernchsteanx=0,z=2.5undder
dritteanx=10,z=2.5.DiejeweilsfnfSubblckebauenwirumdieZentren.DasGanzesiehtdannwiefolgtaus:

DasSchiffwirdsichanderunterengelbenLiniedesSpielfeldsnachlinksundrechtsbewegenknnen,die
Invaderwerdenvonobennachuntenfliegenunddabeiimmeranderlinkenbzw.rechtenSpielfeldlinieindie
andereRichtungsteuernbeginnen.DamithabenwirunserSpielfelddefiniertundknnenunsjetztderSimulation
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

49/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

widmen.

DieSimulation
KernstckjedesSpiels,istdieSimulationderSpieleweltselbst.Diesesolltesounabhngig,wiemglichvonallen
anderenModulensein,wiezumBeispieldemGrafikmodul.Zielistes,dieobengenanntenAspektedesSpielszu
simulieren,d.h.dieInvader,dasSchiff,dieBlcke,SchsseundExplosionen.WirwerdenjedesdieserElemente
ineinereigenenKlasseabbilden.AlsgroeKlammererstellenwirauchnocheineSimulationsklasse,diealldie
ElementebeherbergtundfrdeneigentlichenSpielablaufsorgt.Dasbedeutet,dassdieSimulationsklassedafr
sorgt,dasssichdieInvaderbewegen,SchsseihrZieltreffenundsoweiter.DieSimulationwirddabeiim
KoordinatenSystemablaufen,dasimletztenKapitelbeschriebenwurde,alsoinderxyzEbene.Wirwerdenhier
jedeKlassederSimulationimDetailbesprechen.DieSimulationwerdenwirspterdanninunseremMainLoop
verwendenundausfhren.HierergibtsicheinHenneEiProblem:wasbeschreibichzuerst.MeinerMeinungnach
istdieStrukturdesProgrammszudiesemZeitpunktunerheblich,dasVerstehenderSimulation,isterstmalder
wichtigsteAspekt.DarumstrzenwirunsgleicheinmalaufdieKlassenderSimulation.
Randnotizen:WirbraucheneineKlasse,dieesunserlaubt,vektoriellzuarbeiten.ZudiesemZweckhabicheine
uerstsimpleVektorKlasseimplementiert.DiesebesitztMethoden,diesichmitdenmathematischen
AusdrckenimMathematikKapiteldecken.DieKlasseknntihreuchvorabunter
[http://code.google.com/p/android
gamedev/source/browse/trunk/src/com/badlogic/gamedev/spaceinvaders/simulation/Vector.java
http://code.google.com/p/android
gamedev/source/browse/trunk/src/com/badlogic/gamedev/spaceinvaders/simulation/Vector.java ]anschauen.
AmwichtigstendabeiwirdfrunsdieMethodezumMessenderDistanzzwischenzweiPunkten(hierflschlich
auchVektorengenannt)sein.DesWeiterenwerdenwirvonherkmmlichenBestPracticesder
Softwareentwicklungeinwenigabweichen.AnstattGetterundSetterMethodenfrjedeKlassezuerstellen,
machenwirsmtlicheAttributepublic.AlleMethodenunsererKlassenhabeneinenreinfunktionalenCharakter
undkapselnArbeitsgnge.Hintergrunddafr,istderPerformancezuwachs,denwirdadurchgewinnen.DieDalvik
VirtualMachineistzwarbereitsrelativgut,MethodenaufrufekostenaberdochZeit,diewir,speziellinSpielen,
nichthaben.GetterundSetterMethodensindindiesemRahmenmeistOverkillundsolltenvermiedenwerden.In
unseremsimplenSpaceInvadersKlonmagdiesnochkeinegroeAuswirkungaufdiePerformancehaben,
gestaltetmanaberkomplexereSpiele,kanneszuLaufzeiteinbuenkommen.Wirsindunsalsodesschlechten
Stilsvollkommenbewusst,nehmenihnaberausGrndenderPerformanceinKauf.
FangenwirmitderKlassefrBlckean.

BlockKlasse
BeginnenwirgleichmiteinwenigCode.HierdieBlockKlasse:
publicclassBlock
{
publicfinalstaticfloatBLOCK_RADIUS=0.5f;
publicVectorposition=newVector();

publicBlock(Vectorposition)
{
this.position.set(position);
}
}

EineInstanzdieserKlassebeschreibteinender(Sub)Blcke,wieimSpielfeldKapitelbeschrieben.Dieserbesitzt
einePosition,diewiralsAttributspeichern(position).ZustzlichhabenwireinestatischesundfinalesAttribut,das
denRadiuseinesBlocksdefiniert,indiesemFall0.5fEinheiten.DieseArtvonKonstantenwirdunsinden
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

50/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

Simulationsklassenfterbegegnen.SielegendieobendefiniertenMaeundEinheitenfest,diewirzum
AusfhrenderSimulationbentigen.DerKonstruktoristkeineZauberei,eswirdlediglichdiePositiondesBlocks
gesetzt.MehrmachtdieKlassenicht.DieBlckebewegensichnicht,daherbedarfeskeinesUpdatesder
Position.AuchSchssegibtderBlockkeineab.DieKlassedientlediglichzumSpeichernderPositioneines
Blockes.AufzurnchstenKlasse.

ExplosionKlasse
MitderExplosionKlassemodellierenwirExplosionen...EineExplosionbesitzteinePositionimRaum,sowieeine
Lebenszeit.Diesemisst,wielangedieExplosionbereitsgedauerthat.NacheinerbestimmtenZeitspannesolldie
Explosionschlielichwiederverschwinden.HierbegegnetunsderzweiteMechanismus,denmaninderRegel
nebenKonstanten,wiedemRadius,inSimulationsklassenfindet:dieupdateMethode.InjederIterationdesMain
LoopstoenwirdieSimulationselbstmitdemAufrufihrerupdateMethodean.DerMethodebergibtmandie
Zeitspanne,diemansimulierenmchte,normalerweisedieDeltaTime,diewirjaschonbravinderGameActivity
messen.DieSimulationwiederumruftvonjedemElementdessenupdateMethodeauf.DieElementesorgen
danndafr,dasssieihrenStatusentsprechenddervergangenenZeitanpassen.Daskanndaszeitbasierte
ndernvonPositionensein,dieReaktionaufSpielereignisse,wieSchsse,undsoweiter.ImFallunserer
ExplosionndertsichnurderenLebensdauer.DieseerhhenwireinfachbeijedemAufrufumdiebergebene
DeltaTime.HierderCodezurKlasse:
publicclassExplosion
{
publicstaticfinalfloatEXPLOSION_LIVE_TIME=1;
publicfloataliveTime=0;
publicfinalVectorposition=newVector();
publicExplosion(Vectorposition)
{
this.position.set(position);
}

publicvoidupdate(floatdelta)
{
aliveTime+=delta;
}
}

Keineberraschungen.DiemaximaleLebensdauereinerExplosiondefinierenwirwiederberdieKonstanteder
Klasseundsetzendieseauf1,freineSekunde.AuerdembesitztjedeInstanzderKlasseeinenMemberzur
SpeicherungihrerbereitsabgelaufenenLebensdauer,sowieihrerPosition.ZweiterewirdeinmalimKonstruktor
gesetzt.ZumKonstruktorgeselltsicheineweitereMethode,diebereitsbesprocheneupdateMethode.Diese
bekommtdieDeltaTimebergeben,diesieaufdasAttributLebensdaueraufaddiert.DiesesAttributwerdenwir
spterinderSimulationdazuverwenden,zuprfen,obdieExplosionbeendetistodernicht.AuchdieseKlasseist
wiedersehreinfach,wendenwirunsalsoeineretwaskomplizierterenKlassezu.

ShotKlasse
WiederNamebesagt,simuliertdieseKlasseeinenSchussinunseremSpiel.EinSchussdefiniertsichwieder
bereinePositionimRaum.AuerdembewegtsicheinSchuss,d.h.wirbrauchenwiedereineupdateMethode,
diediePositiondesSchusses,inAbhngigkeitvondervergangenenZeit(DeltaTime),ndert.DerSchussmuss
aucheineRichtungbesitzen,indieerfliegt.Dieseistabhngigdavon,obervomSchiffodervoneinemInvader
stammt.ImerstenFallbewegtsichderSchussimmerweiterindennegativenBereich(zwirdkleiner),imzweiten
FallbewegtsichderSchussindenpositivenBereich(zwirdgrer).Auchwollenwir,dassderSchussweiober
dasSpielfeldverlassenhat.Alldiesimplementierenwirwiefolgt:
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

51/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

publicclassShot
{
publicstaticfloatSHOT_VELOCITY=10;
publicfinalVectorposition=newVector();
publicbooleanisInvaderShot;
publicbooleanhasLeftField=false;
publicShot(Vectorposition,booleanisInvaderShot)
{
this.position.set(position);
this.isInvaderShot=isInvaderShot;
}
publicvoidupdate(floatdelta)
{
if(isInvaderShot)
position.z+=SHOT_VELOCITY*delta;
else
position.z=SHOT_VELOCITY*delta;

if(position.z>Simulation.PLAYFIELD_MAX_Z)
hasLeftField=true;
if(position.z<Simulation.PLAYFIELD_MIN_Z)
hasLeftField=true;
}
}

GehenwirzuerstdieAttributedurch.DieKonstanteSHOT_VELOCITYdefiniertdieGeschwindigkeit.Undzwarin
EinheitenproSekunde,mitdeneneinSchussfliegt.bersetztbedeutetderWert:ineinerSekundefliegtder
SchusszehnEinheitenweit.AuerdembesitztdieKlasseeinAttributfrdiePosition,eineMarkierung,obder
SchussvomSchiffstammtodervoneinemInvader,sowiedieInformation,obderSchussdasSpielfeldverlassen
hat.DerKonstruktorbietetwiederkeineberraschungenundsetztlediglichzweiAttribute.Schauenwirunsalso
dieupdateMethodegenaueran.
AlserstesndernwirdiePositiondesSchusses.SchssefliegenimmerentlangderzAchse,dahermssenwir
auchnichtdieseKoordinatederPositionndern.Dienderungenerfolgtberdassubtrahieren/addierender
GeschwindigkeitmultipliziertmitdervergangenenZeit.DieGeschwindigkeithabenwiralsKonstantedefiniert
(SHOT_VELOCITY)dieZeitbekommenwiralsParameter,sieentsprichderDeltaTime.EinSchussbewegtsich
alsoproFrameumSHOT_VELOCITY*DeltaTimeentlangderzAchse.DieRichtungistabhngigvomTypendes
Schusses,also,obervoneinemInvaderkommtodervomSchiff.
InderupdateMethodeprfenwirauch,obderSchussdasSpielfeldverlassenhat.Nachdemersichnurentlang
derzAchsebewegtundwirdavonausgehenknnen,dasservoneinemSchiff/Invaderabgefeuertwurdeund
somitgltigex/yKoordinatenhat,prfenwirauchnur,obderdasSpielfeldinderzAchseverlassenhat.Dazu
bietetdieKlasseSimulationzweistatischeAttribute,diediemaximaleundminimalezKoordinatedesSpielfeldes
angeben.ErgibtdiePrfung,dassderSchussnichtmehrimSpielfeldist,setzenwirdasAttributhasLeftFieldauf
true.DieSimulationwirddiesenWertspterdazuverwenden,umzubewerten,obderSchussausderSimulation
entferntwerdenkannodernicht.Esverhltsichhieralsogenauso,wiebeiderLebensdauerderExplosion
DaswichtigsteandieserKlasse,istdaszeitbasierteaktualisierenderPosition.DiesesPrinzipmsstihr
verinnerlichen,eswirdunsnocheinpaarMalbegegnen.

ShipKlasse
UnddasMustersetztsichfort.AuchunserSchiffbrauchtnatrlicheinePosition.EbensowieinderKlasseBlcke,
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

52/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

hatesaucheinenRadiusundeineHchstgeschwindigkeit.ZustzlichkannsicheinSchiffauchinLuftauflsen,
sprichexplodieren.Diesmssenwirirgendwievermerken,dadasSchiffindieserZeitnichtbeschossenwerden
kann.AuerdemhateinSchiffeineAnzahlanLeben(dreialsStandard).Schauenwirunsan,wiewirdas
implementieren.
publicclassShip
{
publicstaticfinalfloatSHIP_RADIUS=1;
publicstaticfinalfloatSHIP_VELOCITY=20;
publicfinalVectorposition=newVector();
publicintlives=3;
publicbooleanisExploding=false;
publicfloatexplodeTime=0;

publicvoidupdate(floatdelta)
{
if(isExploding)
{
explodeTime+=delta;
if(explodeTime>Explosion.EXPLOSION_LIVE_TIME)
{
isExploding=false;
explodeTime=0;
}
}
}
}

WiederbegegnenunszweiKonstanten,derSchiffradiussowiediemaximaleSchiffgeschwindigkeit.DiePosition
merkenwirunsinFormeinesVektors.EinweiteresAttributhltfest,wievieleLebendasSchiffnochbesitzt.Das
BooleanisExplodingspeichert,obdasSchiffgeradeexplodiert,dasAttributexplodeTimevermerkt,wielangedie
Explosionschondauert,analogzuraliveTimederExplosion.Konstruktorhabenwirkeinen,dadiePositionbereits
auf(0,0,0)initialisiertwird(KonstruktordesVektors).SchauenwirunsdieupdateMethodean.
WiederbekommenwiralsParameterdieDeltaTime.ExplodiertdasSchiff,rechnenwirdieseeinfachaufdas
AttributexplodeTimeauf.IstdasSchifflangegenugexplodiert,setzenwirisExploding,sowiedasAttribut
explodeTimewiederzurck.DasSchiffbefindetsichdanachwiederimnormalenZustand.
AufmerksameLeserwerdenbemerken,dassdasSchiffnichtbewegtwird.Dasmachenwirdannspterinder
SimulationaufBasisderBenutzereingabe.AuchdasAbziehenvonLeben,imFalleinerKollisionmiteinem
SchussoderInvader,wirdinderSimulationerledigt.

InvaderKlasse
JetztkommtdieersteetwaskomplexereSimulationsklasse.DerInvaderhatwieallesanderenatrlicherstmal
einePosition.AuchKonstantenfrRadiusundGeschwindigkeitgibteswieder.DasschwierigeamInvaderistsein
komplexesBewegungsmuster.Rekapitulierenwirdieseschnell:zuBeginnbewegtsicheinInvadernachlinks.
NacheinerbestimmtenDistanzbewegtersichumeineEinheitnachunten(positivz),umdanachnachrechts
einzuschlagen.NacheinerbestimmtenStreckenachrechts,fhrterwiedernachuntenunddannnachlinks,das
ganzewiederholtsich.DerInvaderhatalsodreiZustnde:fahrnachlinks,nachuntenundnachrechts.In
AbhngigkeitdesZustandsvernderterseinePositionentlangderxbzw.zAchse.DieDistanzen,dieeinInvader
nachlinksundrechtszurcklegenmuss,damiterindennchstenZustandwechselnkann,knnenwirschnaus
derBeschreibungdesSpielfeldesablesen.SchauenwirunsnochmaldasBilddazuan:

http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

53/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

AnfangslegtderInvadersechsEinheitennachlinkszurck.DanacheineEinheitnachunten,dann13Einheiten
nachrechts,eineEinheitnachunten,13Einheitennachlinksadinfinitum.Dieszuimplementierenscheint
anfnglichetwaskomplex,istaberbeigenauererBetrachtungrelativeinfach.Wirmerkenunsfrdenaktuellen
Status(links,rechts,runter),wieweitderInvaderschongewandertist.HaterdiemaximaleDistanzfrden
Zustanderreicht(13oder1),wechselnwirindennchstenZustand.DenZustandselbst,mssenwirunsnatrlich
auchmerken.
DieBewegungdesInvaderserfolgtnatrlichwiederzeitbasiertberdieMultiplikationderGeschwindigkeitmitder
DeltaTime.DasErgebnisrechnenwirdannentsprechenddemStatusaufdiePositionauf,entwederaufdiex
Koordinate(links,rechts)oderdiezKoordinate(runter).HierdergesamteCode:
publicclassInvader
{
publicstaticfloatINVADER_RADIUS=0.75f;
publicstaticfloatINVADER_VELOCITY=1;
publicstaticintINVADER_POINTS=40;
publicfinalstaticintSTATE_MOVE_LEFT=0;
publicfinalstaticintSTATE_MOVE_DOWN=1;
publicfinalstaticintSTATE_MOVE_RIGHT=2;

publicfinalVectorposition=newVector();
publicintstate=STATE_MOVE_LEFT;
publicbooleanwasLastStateLeft=true;
publicfloatmovedDistance=Simulation.PLAYFIELD_MAX_X/2;

publicInvader(Vectorposition)
{
this.position.set(position);
}

publicvoidupdate(floatdelta,floatspeedMultiplier)
{

movedDistance+=delta*INVADER_VELOCITY*speedMultiplier;
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

54/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

if(state==STATE_MOVE_LEFT)
{
position.x=delta*INVADER_VELOCITY*speedMultiplier;
if(movedDistance>Simulation.PLAYFIELD_MAX_X)
{
state=STATE_MOVE_DOWN;
movedDistance=0;
wasLastStateLeft=true;
}
}
if(state==STATE_MOVE_RIGHT)
{
position.x+=delta*INVADER_VELOCITY*speedMultiplier;
if(movedDistance>Simulation.PLAYFIELD_MAX_X)
{
state=STATE_MOVE_DOWN;
movedDistance=0;
wasLastStateLeft=false;
}
}
if(state==STATE_MOVE_DOWN)
{
position.z+=delta*INVADER_VELOCITY*speedMultiplier;
if(movedDistance>1)
{
if(wasLastStateLeft)
state=STATE_MOVE_RIGHT;
else
state=STATE_MOVE_LEFT;
movedDistance=0;
}
}
}
}

DieDefinitionderKonstantenfrdenRadiusunddieGeschwindigkeitsolltenkeinegroeberraschungsein.Die
nchstendreiKonstantenstehenfrdiedreiStatus,indersicheinInvaderbefindenkann.Natrlichhabenwir
auchwiedereinAttributfrdiePosition.EinweiteresAttributhltdenaktuellenStatusdesInvaders,densetzen
wirzuBeginnaufSTATE_MOVE_LEFT.AusorganisatorischenGrndenmerkenwirunsauch,obderletzte
Zustandnachlinksodernachrechtsgefhrthat.DasletzteAttributspeichertdiegefahreneDistanzfrden
aktuellenZustand,deninitialisierenwiraufdieHlftedesmaximalenxWertesderSimulation(sieheGrafik).Den
KonstruktorkennenwirinderFormauchschon.SchauenwirunsalsodieupdateMethodean.
Wasalserstesauffllt,isteinzweiterParameternamensspeedMultiplier.UnserKlonsolljaaufDaueretwas
fordernderwerden.DazulassenwireinfachdieInvaderschnellerwerden.NachjederWelleerhhenwirdiesen
Speedmultiplieretwas,waswiederumAuswirkungenaufdieGeschwindigkeitderInvaderhat.Dazumehrinder
BeschreibungderSimulationKlasse.Wirmerkenunsnur,dasswirdiesenWertbeiderZeitbasiertenBewegung
aufmultiplizierenmssen.
AlserstesaddierenwirindieserMethodedieimletztenFramezurckgelegtStreckeaufmovedDistance.Keine
groeSache,wirberechneneinfachdenzeitbasiertenWeg(malSpeedmultiplier).Alsnchstesprfenwir,in
welchemZustandwirunsbefinden.
SindwirimZustandSTATE_MOVE_LEFT,bewegenwirunserenInvaderzeitbasierteinStcknachlinks
(Geschwindigkeit*DeltaTime*Speedmultiplier=ImFramezurckgelegteStrecke).Danachwirdkontrolliert,ob
wirdiemaximaleDistanzfrdiesenZustandzurckgelegthaben.IstdiesderFall,wechselnwirdenStatusin
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

55/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

STATE_MOVE_DOWNundsetzenmovedDistanceauf0.Auchmerkenwiruns,dassderletztehorizontaleStatus
nachlinksging.
DasselbeSpielspielenwir,wennwirunsimZustandSTATE_MOVE_RIGHTbefinden.Anstattnachlinks,
bewegenwirunsnachrechts.SolltediemaximaleDistanzfrdenZustandberschrittensein,setzenwirden
StatuswiederaufSTATE_MOVE_DOWN,movedDistanceauf0undmerkenuns,dasswirnachrechtsgefahren
sind.
DieHandhabungdesZustandsSTATE_MOVE_DOWNlufteinwenigandersab.Zuerstbewegenwirunseinmal
zeitbasiertnachunten.HabenwirdiemaximaleDistanzfrdenStatusberschritten(>1),wechselnwirineinen
derhorizontalenZustnde.Fuhrenwirzuvornachlinks,mssenwirjetztnachrechtsfahrenundumgedreht.
NatrlichsetzenwirauchmovedDistancewiederzurck.
Pfuh,ganzschnesStckArbeit,soeinInvader.DamithabenwiraberdieletzteKlassefertigbesprochenund
knnenunsdereigentlichenSimulationsklassewidmen.

SimulationKlasse
DieAufgabederKlasse,istdasZusammenspielderverschiedenenSpielelementezuregeln.Zumeinensorgtdie
Klassedafr,dassalleElemente,dieeineupdateMethodebesitzen,auchaktualisiertwerden.Zumanderenprft
sieverschiedeneEreignisse,wiedieKollisionvonSchssenundfhrtentsprechendeReaktionenaus.
BeispielsweisedasverschwindenlassenvonInvadern,dasErzeugenvonExplosionenundsoweiter.Inunserem
FallbietetdieKlasseauchdreiMethoden,dievonauenangesteuertwerden.DiesesindfrdieBewegungdes
SchiffessowiedasFeuerneinesSchussesverantwortlich.WirwerdendieKlasseinkleinenStckchensezieren,
umihreFunktionsweisezuverstehen.BeginnenwirmitdenAttributen:
publicclassSimulation
{

publicfinalstaticfloatPLAYFIELD_MIN_X=14;
publicfinalstaticfloatPLAYFIELD_MAX_X=14;
publicfinalstaticfloatPLAYFIELD_MIN_Z=15;
publicfinalstaticfloatPLAYFIELD_MAX_Z=2;

publicArrayList<Invader>invaders=newArrayList<Invader>();
publicArrayList<Block>blocks=newArrayList<Block>();
publicArrayList<Shot>shots=newArrayList<Shot>();
publicArrayList<Explosion>explosions=newArrayList<Explosion>();
publicShipship;
publicShotshipShot=null;
publicSimulationListenerlistener;

publicfloatmultiplier=1;
publicintscore;
publicintwave=1;

privateArrayList<Shot>removedShots=newArrayList<Shot>();
privateArrayList<Explosion>removedExplosions=newArrayList<Explosion>();
...tobecontinued...

DieerstenvierAttributesindwiederKonstantenfrunserSpielfeld.DieerstenbeidengebendieBegrenzungen
aufderxAchsean,dieanderenbeidendieBegrenzungenaufderzAchse.
EsfolgendieListenfrdieverschiedenenSpielelemente.Wiederkeinegroeberraschung,wirverwenden
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

56/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

einfacheineArrayListsfrInvader,Blcke,SchsseundExplosionen.
AuchdasSchiffmssenwirspeichern,genausowiedenaktuellenSchussdesSchiffs.Dieserkommtauchindie
shotsListe,wirdaberseparatnochmalgespeichert,damitwirwissen,obeseinSchiffschussgibtodernicht.
DieSimulationerlaubtauchdasEinhngeneinesListeners.DerSinndahinter:AuenstehendeKlassen
bekommensoessentielleEreignisseinderSimulationmit,wieExplosionenoderdasAbfeuernvonSchssen.Wir
werdensptereinensolchenListenereinhngen,derdieentsprechendenSoundeffekte,frbestimmte
Ereignisse,abspielt.
DerMultiplikatoristunsimInvaderschonbegegnet,alsParameterfrdieupdateMethode.ZuBeginnder
Simulationsetzenwirdiesenauf1.Diesbedeutet,dassInvadermitderGeschwindigkeit
Invader.INVADER_VELOCITYfliegenwerden.SpterwerdenwirdiesenMultipliererhhen,damitdieInvader
schnellerwerden.
NatrlichspeichernwirauchdenaktuellenPunktestand,ohnediesenwredasSpielnurhalbsolustig.Des
Weiterenspeichernwirnoch,wievieleWellenanInvadernbereitsaufgetretensind.ReineStatistik,ohnegroe
Funktion.
DieletztenbeidenArrayListssindUtilityAttribute,diewirzumLschenvonSchssenundExplosionenbentigen.
Wirwollendiesenichtimmerneuinstanzieren,dasonstderGarbageCollectorpermanentanspringt.
InstanzierenwireineSimulation,sowollenwireinfixundfertigesSpielfelddarinvorfinden.Dortsollenalle
Elementesopositioniertsein,wieimAbschnittSpielfelddargelegt.DasBefllenderSimulationwerdenwirineine
eigeneMethodenamenspopulatepacken.SchauenwirunsKonstruktorundpopulatean:
...continued...
publicSimulation()
{
populate();
}

privatevoidpopulate()
{
ship=newShip();

for(introw=0;row<4;row++)
{
for(intcolumn=0;column<8;column++)
{
Invaderinvader=newInvader(newVector(PLAYFIELD_MAX_X/2+column*2f,0,PLAYFIELD_MIN_
invaders.add(invader);
}
}

for(intshield=0;shield<3;shield++)
{
blocks.add(newBlock(newVector(10+shield*101,0,2)));
blocks.add(newBlock(newVector(10+shield*101,0,3)));
blocks.add(newBlock(newVector(10+shield*10+0,0,3)));
blocks.add(newBlock(newVector(10+shield*10+1,0,3)));
blocks.add(newBlock(newVector(10+shield*10+1,0,2)));
}
}
...tobecontinued...

ImKonstruktorrufenwirlediglichpopulateauf,dasbernimmtdasBefllenderSimulation.InderMethode
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

57/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

populatebeginnenwirdamit,dasSchiffzuinstanzieren.AlsnchstesplatzierenwirdieInvaderaufdemSpielfeld,
wievorherschonbeschrieben.DieersteSchleifegehtdabeiberdievierReihen,dienchsteberdiejeweils
achtInvaderproReihe.DieneuerstelltenInvadergebenwirinunserAttributinvaders,damitwirsieauchspter
nochverfgbarhaben.Danacherstellenwirdie3*5Blcke,ebenfallssopositioniert,wieimAbschnittSpielfeld
beschrieben.AlsbungknntihreuchjadiePositionen,sowiesiesichausdemCodeergeben,errechnenund
mitderBeschreibungimSpielfeldabschnittvergleichen.
DieHauptarbeitderSimulation,istdaszeitbasierteaktualisierenallerSpielelemente.Dazuhabenwir,wiegehabt,
eineupdateMethode.VonauenwirddieserdieDeltaTimemitgeteilt.SehenwirunsdieMethodean:
...continued...
publicvoidupdate(floatdelta)
{

ship.update(delta);
updateInvaders(delta);
updateShots(delta);
updateExplosions(delta);
checkShipCollision();
checkInvaderCollision();
checkBlockCollision();
checkNextLevel();
}
...tobecontinued...
Schnaufgerumt,mitMethodenaufrufen,gehenwirdieeinzelnenElementedurch.Alserstesaktualisierenwir
dasSchiff.Wirerinnernunsdaran,dassbeimUpdate,imFalleinerExplosion,derenZeitgemessenwirdundein
entsprechenderVermerkgesetztwird.AnschlieendbringenwirdieInvader,dieSchsseunddieExplosionenauf
denaugenblicklichenStand.WirsehenunsdiedreiMethodengleichimDetailan.Alsnchstesschauenwir,ob
esKollisionenzwischendemSchiffundSchssenbzw.Invaderngab(checkShipCollision).Dasselbetunwirdann
auchfrdieInvader(checkInvaderCollision)undfrdieBlcke(checkBlockCollision).ZumSchlussprfenwir,ob
alleInvaderderaktuellenWellezerstrtwurden.IstdiesderFall,befllenwirdasSpielfeldmitneuenInvadern
(checkNextLevel)unddamitmitdernchstenWelle.
DasistdiegroeKlammer,dieinjederIterationeinenSchrittinderSimulationausfhrt.Wiewollenunsjetztmit
deninupdateaufgerufenenMethodenauseinandersetzen.GehenwirsiederReihenachdurch:
...continued...
privatevoidupdateInvaders(floatdelta)
{
for(inti=0;i<invaders.size();i++)
{
Invaderinvader=invaders.get(i);
invader.update(delta,multiplier);
}
}
...tobecontinued...

WiegeheneinfachdurchalleInvaderdurchundrufenderenupdateMethodeauf,mitderaktuellenDeltaTime,
sowiedemWertdesMultiplikators(derdieGeschwindigkeitjenachNummerderaktuellenWelleetwaserhht).
WirverwendenkeineIteratoreninderSchleife,dadieseunterAndroidbzw.DalvikObjekteinstanzieren,die
wiederumdenGarbageCollectoranwerfenwrden.Wiebereitserwhnt,solltenwirdasvermeiden.
AlsnchstesschauenwirunsupdateShotsan:

http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

58/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

...continued...
privatevoidupdateShots(floatdelta)
{
removedShots.clear();
for(inti=0;i<shots.size();i++)
{
Shotshot=shots.get(i);
shot.update(delta);
if(shot.hasLeftField)
removedShots.add(shot);
}

for(inti=0;i<removedShots.size();i++)
shots.remove(removedShots.get(i));

if(shipShot!=null&&shipShot.hasLeftField)
shipShot=null;

if(Math.random()<0.01*multiplier&&invaders.size()>0)
{

intindex=(int)(Math.random()*(invaders.size()1));
Shotshot=newShot(invaders.get(index).position,true);
shots.add(shot);
if(listener!=null)
listener.shot();
}

}
...tobecontinued...

Hierpassiertschoneinwenigmehr.ZuerstrufenwirdieupdateMethodevonjedemSchussauf.Wirerinnernuns,
dieseprft,obderSchussauerhalbdesSpielfeldsistundbewegtdenSchuss.InderSchleifeschauenwirnach
demUpdate,obderSchussdasSpielfeldverlassenhat,indemwirdasentsprechendeAttributprfen,undgeben
ihnindiesemFallindieListederzulschendenSchsse.
DienchsteSchleifelschtalleSchsse,diewirgeradeindieListeremovedShotsgegebenhabenausderListe
shots.DamitverschwindensiekomplettausdemSpiel.DawirnichtberIteratorenarbeiten,mssenwirdies,
etwasumstndlich,mitderremovedShotsListemachen.
Alsnchstesschauenwir,obaucheinSchussvomSchiffaufdemSpielfeldistundobdieserdasSpielfeld
verlassenhat.IstdiesderFall,setzenwirshipShotaufnull,womitwirspterwissen,dasskeinSchussdes
SchiffesmehramSpielfeldist.DasentfernendesSchussespassierteschonzuvorinderSchleife,dader
SchiffschussjaauchindershotsListeist.
DernchsteTeilisteinwenigschwererzuverstehen.DieInvadersollenjaebenfallshinundwiederschieen.
Dieseshinundwiederistzufallsbasiert.DazuholenwirunsberMath.random()eineZahlzwischen0und1.Ist
dieZahlkleinerals0.01*FaktorundgibtesmehralseinenInvader,erzeugenwireinenneuenSchuss.D.h.die
Wahrscheinlichkeit,dassineinemSimulationsupdateeinSchussvoneinemInvaderabgegebenwird,betrgtein
Prozent.
ImKrperderifAbfrage,suchenwirunsdannebenfallszufallsbasierteinenderInvaderausunderzeugenan
dessenPositioneinenneuenSchuss,denwirindieshotsListeeinfgen.AuerdemrufenwirhierzumerstenMal
eineneventuellenListenerauf,dersowei,dasseinSchussabgegebenwurde.Damithttenwirschonmaleinen
essentiellenMechanismus,nmlichdasSchieenderInvaderabgehakt.DieSchsse,diewirhierneu
hinzufgen,werdenimnchstenUpdateberdievorhergehendeSchleifewiederverarbeitet.
Alsnchstesschauenwirunsan,waswirmitdenExplosionenmachen:
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

59/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

...continued...
publicvoidupdateExplosions(floatdelta)
{
removedExplosions.clear();
for(inti=0;i<explosions.size();i++)
{
Explosionexplosion=explosions.get(i);
explosion.update(delta);
if(explosion.aliveTime>Explosion.EXPLOSION_LIVE_TIME)
removedExplosions.add(explosion);
}
for(inti=0;i<removedExplosions.size();i++)
explosions.remove(explosions.get(i));
}
...tobecontinued...

WieschonbeidenInvadern,gehenwirberalleExplosionenamSpielfeldundaktualisierensie.Warensielange
genugamLeben,gebenwirsieindieremovedExplosionListe,berdiewirsieindernchstenSchleifedann
endgltigvomSpielfeldentfernen.
DamithabenwiralleUpdateMethodenabgehandelt.EsverbleibennochdieKollisionsRoutinen.Startenwirmit
checkInvaderCollision:
...continued...
privatevoidcheckInvaderCollision()
{

if(shipShot==null)
return;

for(intj=0;j<invaders.size();j++)
{
Invaderinvader=invaders.get(j);
if(invader.position.distance(shipShot.position)<Invader.INVADER_RADIUS)
{

shots.remove(shipShot);
shipShot=null;
invaders.remove(invader);
explosions.add(newExplosion(invader.position));
if(listener!=null)
listener.explosion();
score+=Invader.INVADER_POINTS;
break;
}
}

}
...tobecontinued...

UnsereAufgabehier,istes,eineneventuellvorhandenenSchussdesSchiffesmitallenInvadernzuprfen.
BefindetsichderSchussinnerhalbdesRadiuseinesInvaders,sogehtdieserineinerExplosionhoch.
Alserstesschauenwir,obesberhaupteinenSchiffschussgibt.IstdiesnichtderFall,verabschiedenwiruns
gleichwieder.DanachgehenwirjedenInvaderdurch.IstdieDistanzzwischenInvaderundSchiffschusskleiner
alsderRadiuseinesInvader,lschenwirdenSchiffschussausshotsundsetzenshipShotaufnull.Danach
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

60/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

lschenwirauchdengetroffenenInvaderunderzeugeneineneueExplosionanderPositiondestotenInvaders.
IsteinListenergesetzt,rufenwirdiesenaufundteilenihmmit,dasseineExplosionstattgefundenhat.Zuguter
LetzterhhenwirdenPunktestandundbrechendieSchleifeab.
Esseihierangemerkt,dasswireinenkleinenShortcutgenommenhaben.NachdemwirdieInvadervonhinten
nachvorneindieinvadersListeeingefgthaben,kommenwirmitdieserMethodeaus.WrendieInvadernicht
nachTiefesortiert,msstenwirdieDistanzzujedemInvaderprfenundunsdenmerken,deramnchsten
getroffenenist.Dasbleibtunsabererspart,dawirjaschlausind.
DasSchiffmssenwirsowohlaufKollisionenmitSchssen,wieauchmitInvadernselbstprfen:
...continued...
privatevoidcheckShipCollision()
{
removedShots.clear();

if(!ship.isExploding)
{
for(inti=0;i<shots.size();i++)
{
Shotshot=shots.get(i);
if(!shot.isInvaderShot)
continue;

if(ship.position.distance(shot.position)<Ship.SHIP_RADIUS)
{

removedShots.add(shot);
shot.hasLeftField=true;
ship.lives;
ship.isExploding=true;
explosions.add(newExplosion(ship.position));
if(listener!=null)
listener.explosion();
break;
}

for(inti=0;i<removedShots.size();i++)

shots.remove(removedShots.get(i));
}

for(inti=0;i<invaders.size();i++)
{
Invaderinvader=invaders.get(i);
if(invader.position.distance(ship.position)<Ship.SHIP_RADIUS)
{
ship.lives;
invaders.remove(invader);
ship.isExploding=true;
explosions.add(newExplosion(invader.position));
explosions.add(newExplosion(ship.position));
if(listener!=null)
listener.explosion();
break;
}
}
}
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

61/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

...tobecontinued...

Zuerstprfenwir,obeinInvaderschussdasSchiffgetroffenhat.DasGanzetunwirabernur,wenndasSchiffnicht
explodiert.WurdedasSchiffgetroffen,lschenwirdenSchuss,verminderndieLebendesSchiffesumeinsund
erzeugeneineExplosion.AuerdemsagenwirdemSchiff,dassesexplodierensoll.Diesmachenwir,indemwir
dasentsprechendeAttributsetzen.DerListenerwirdaufgerufenunddieSchleifeabgebrochen.MehralseinMal
kanndasSchiffnichtgetroffenwerden.
Alsnchstesprfenwir,obeinInvadermitdemSchiffkollidiertist.HiermachenwirdasGleiche,wiebeiden
Shots,Distanzkontrollieren,Lebenabziehen,Explosionenerzeugen(frInvaderundSchiff)undInvaderlschen.
ZumSchlussrufenwirwiederdenListeneraufundbrechendieSchleifeab.Eigentlichallesnichtsoschlimm.
JetztmssenwirnochdieBlckeaufKollisionenmitSchssenberprfen:
...continued...
privatevoidcheckBlockCollision()
{
removedShots.clear();

for(inti=0;i<shots.size();i++)
{
Shotshot=shots.get(i);

for(intj=0;j<blocks.size();j++)
{
Blockblock=blocks.get(j);
if(block.position.distance(shot.position)<Block.BLOCK_RADIUS)
{

removedShots.add(shot);
shot.hasLeftField=true;
blocks.remove(block);
break;
}
}

for(inti=0;i<removedShots.size();i++)

shots.remove(removedShots.get(i));
}
...tobecontinued...

SelbesSpielwieimmer.WirprfenjedenSchussmitjedemBlock.WurdeeinBlockgetroffen,lschenwirsowohl
Block,alsauchSchuss.ExplosionengibtesindiesemFallkeine.
Schauenwirunsan,wiewirdienchsteWellelostreten:
...continued...
privatevoidcheckNextLevel()
{
if(invaders.size()==0&&ship.lives>0)
{
blocks.clear();
shots.clear();
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

62/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

shipShot=null;
VectorshipPosition=ship.position;
intlives=ship.lives;
populate();
ship.lives=lives;
ship.position.set(shipPosition);
multiplier+=0.1f;
wave++;
}
}
...tobecontinued...

EineerfrischendkurzeMethode.SindalleInvaderexplodiertundhatdasSchiffnochmindestenseinLeben,
befllenwirdieSimulationneu.DazulschenwirallevorhandenenBlckeundSchsseundsetzenshipShotauf
null.DannmerkenwirunsdieaktuelleSchiffsposition.DiesewirdjaimfolgendenAufrufderMethodepopulateauf
(0,0,0)gesetzt,selbigesmachenwirfrdieAnzahlderLeben.NachAufrufderpopulateMethodeistdie
SimulationmitneuenInvadernundBlckenbeflltundbesitzteineneueInstanzderKlasseShip.Diesersetzen
wirdieletztePositionundspeicherndieAnzahlderLeben.DanachgehenwirdenMultiplieranunderhhen
diesenum0.1.DiesentsprichteinerErhhungderInvaderGeschwindigkeitumzehnProzent.Abschlieend
erhhenwirnochdenWaveCounter,womitdieSimulationfreineneueRundebereitist.
AlsletztesPuzzlestckmssenwirunsnochanschauen,wiedasSchiffbewegtwirdundwireinenSchuss
abfeuernknnen.BeginnenwirmitderBewegungnachlinks:
...continued...
publicvoidmoveShipLeft(floatdelta,floatscale)
{
if(ship.isExploding)
return;

ship.position.x=delta*Ship.SHIP_VELOCITY*scale;
if(ship.position.x<PLAYFIELD_MIN_X)
ship.position.x=PLAYFIELD_MIN_X;
}
...tobecontinued...

AlsParametererhaltenwirdieDeltaTime,sowieeinenFaktorscale.Wasesmitdemzweitenaufsichhat,
erfahrenwirgleich.Zuerstprfenwiraber,obdasSchiffexplodiert.IstdasderFall,brauchenwirgarnichts
machen,dasicheinexplodiertesSchiffenichtmehrbewegt.
IstdasSchiffnichtzerstrtworden,wirdesnachlinksverschoben.DazumultiplizierenwirwiegewohntdieDelta
TimemitderSchiffsgeschwindigkeit.ZustzlichmultiplizierenwirauchnochdenFaktorscaledazu.Dieserkommt
vonauenundbewegtsichimBereich(0,1).AufmerksameLeserwerdenerahnen,woherderWertkommt:der
Accelerometergibtunsdiesen.MehrdazuspterimKapitelGameLoop,wowirdieVerarbeitungder
AccelerometerdatenzurSteuerungdesSchiffesbesprechen.
Alsletzteswirdkontrolliert,obdasSchiffdasSpielfeldverlassenhat.IstdiesderFall,setzenwiresaufdenletzten
erlaubtenPunktaufderxAchsezurck.
FrdieBewegungnachrechtsgibteseineanalogeMethode,dieichhiernichtanfhre,dasiedasGleiche,nurin
dieandereRichtungmacht.
ZuguterLetztdieMethodezumAbfeuerneinesSchusses:

http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

63/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

...continued...
publicvoidshot()
{
if(shipShot==null&&!ship.isExploding)
{
shipShot=newShot(ship.position,false);
shots.add(shipShot);
if(listener!=null)
listener.shot();
}
}

Zuerstwirdgeprft,obesbereitseinenSchussaufdasSchiffgibt,bzw.obdasSchiffexplodiertist.Istdiesnicht
derFall,erstellenwireinenneuenSchussanderaktuellenSchiffsposition,gebenihnindieListeshotsund
signalisierendemListener,dasseinSchussabgefeuertwurde.Daswaresauchschon!
DamithabenwiralleKlassenderSimulationbesprochen.AbgesehenvonderEingabederAccelerometerdaten
undTouchEvents,habenwirhiermiteinevollfunktionstchtigeSpielewelt,dieunabhngigvonjeglicheranderen
Komponentefunktioniert.UnserZielistalsoerreicht.Wasnochbleibt,istdasSchreibeneinesRenderers,der
unsereSimulationzeichnet,sowiedesGameLoopsunddesStartundGameOverScreens.Widmenwiruns
zuerstdemRenderer,diegrteKlassenachderSimulation.

DasRendering
NachdemwirdieSimulationsohbschgekapselthaben,wollenwirdasGanzejetztauchaufdenBildschirm
zaubern.DazubrauchenwirfrjedesElementimSpielverschiedeneRessourcen,d.h.MeshesundTexturen.
BevorwirunsCodeanschauen,widmenwirunskurzderErstellungderRessourcen.
AmSpielfeldgibtesvierverschiedene,sichtbareElemente:dasSchiff,dieInvader,SchsseundBlcke.Fralle
vierbrauchenwireinMeshundeventuellauchTexturen.IchhabefrdenSpaceInvadersKlonWings3D
verwendet,einPolygonmodellerzumNulltarif.FrdieErstellungderTexturenhabeichGimp verwendet,eine
OpenSourceGrafikapplikation.DerEinfachheithalbergibtesnureineinzigesModellfrInvader,einefliegende
Untertasse.FrdasSchiffgibtesaucheinkleinesMesh.DieBlckehabeichalsabgeflachteWrfelmodelliert
unddieSchssesindebenfallskleineWrfel.HiereinpaarScreenshotsderModelleinWings3D,sowiedie
verwendetenTexturen:

http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

64/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

65/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

Datei:shipTextur.png
Datei:invaderTextur.png
Wiezuerkennenist,sinddieeinzelnenMeshesschonrichtigskaliert.EinFelddesGittershatdieAbmessung1x1.
TexturengibtesnurfrInvaderunddasSchiff,BlckeundSchssefrbenwirspterberglColor4fein.Alle
MeshesbesitzenNormalenzurLichtberechnung,diewirnatrlichauchverwendenwollen.
ZustzlichzudenSpielobjektenbrauchenwirauchnocheinenhbschenHintergrundalsGrundlageder
Darstellung.DazuhatmeinschwedischerFreundZire (auchbekanntalsKillergoatfromhell)einhbschesBild
seinesSpielesGodsandIdols beigesteuert,einDankeschnandieserStelle.

http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

66/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

FrhbscheExplosionenhabeicheinenfreiimNetzerhltlichenGenerator
aus:

verwendet.DasErgebnissiehtso

MansiehtmehrereAnimationsphasenderExplosion.Wirwerdensptersehen,wiewirdieseaufeinMesh
bekommenunddieExplosionendamitzeichnen.
AlsFontzurDarstellungderPunkte,derLebenundderaktuellenWelle,habeichmirausdemInterneteinenfrei
verwendbarenFontnamens"CosmicAlien" besorgt.
WiedieMeshesunddasTexturierenimDetailfunktionieren,kannichhierleidernichterklren,daswrdeden
RahmendesTutorialsmehralssprengen.ImNetzgibtesdazuabermehralsgenugMaterial,ichverweiseden
geneigtenLeserdaherandieserStelleaufGoogle.
MitalldenRessourcenbewaffnet,wendenwirunsjetztdemRendererzu.

RendererKlasse
ZielderKlasseistes,erstensallebentigtenRessourcenzuladenundzuverwaltenundzweitens,dieSimulation
mitdiesenRessourcenzurendern.DarunterflltdasZeichnendesHintergrunds,derStatistiken,sowieder
Invader,desSchiffes,derSchsse,derExplosionenundderBlcke.NachgetanerArbeitsollderRendererauch
wiederalldieRessourcenfreigebenknnen.
UnsereSpielweltwerdenwirin3Dzeichnen,miteinerdirektionalenLichtquelle,diealleElementebeleuchtet.Die
KamerasolldabeiimmeretwasberdemSchiffschwebenundleichtnachuntenaufdasSpielfeldblicken.
AuerdemsollsiesichmitdemSchiffmitbewegen.DieInvadersollensichdrehen,dasSchiffsollsichjenach
AusrichtungdesAndroidGertsneigen.AlskleineMotivationhiereinBildderfertigenSzene:
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

67/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

SchauenwirunszuerstdieAttributederKlassean:
publicclassRenderer
{
MeshshipMesh;
TexturshipTextur;
MeshinvaderMesh;
TexturinvaderTextur;
MeshblockMesh;
MeshshotMesh;
MeshbackgroundMesh;
TexturbackgroundTextur;
MeshexplosionMesh;
TexturexplosionTextur;
Fontfont;
Texttext;
floatinvaderAngle=0;
intlastScore=0;
intlastLives=0;
intlastWave=0;
...tobecontinued...

FrdasSchiffunddieInvaderhabenwirjeweilseinMesh,sowieeineTextur.BlckeundSchssebesitzennur
einMesh,diefrbenwirspterhndischein.FrdenHintergrundhabenwirebenfallseinMesh,sowieeine
Textur,selbigesgiltfrExplosionen.DawiraucheinpaarStatistikenanzeigenwollen,besitztderRendererauch
einenFont,sowieeineTextKlasse.DerMemberinvaderAnglewirdzurSpeicherungdesaktuellenDrehwinkels
derInvaderbentigt.DierestlichendreiAttributemerkensichdieletztenWertefrLeben,WelleundPunktestand.
Dieverwendenwirspter,umnderungendieserWertezuregistrieren.NurbeinderungenbauenwirdieText
Klasseneu,dadiesdemGarbageCollectorbesserpasst.
Bevorwirirgendetwaszeichnen,mssenwirzuersteinmalallunsereRessourcenladen.DasGanzemachenwir
relativberraschungslosimKonstruktor:
...continued...
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

68/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

publicRenderer(GL10gl,GameActivityactivity)
{
try
{
shipMesh=MeshLoader.loadObj(gl,activity.getAssets().open("ship.obj"));
invaderMesh=MeshLoader.loadObj(gl,activity.getAssets().open("invader.obj"));
blockMesh=MeshLoader.loadObj(gl,activity.getAssets().open("block.obj"));
shotMesh=MeshLoader.loadObj(gl,activity.getAssets().open("shot.obj"));

backgroundMesh=newMesh(gl,4,false,true,false);
backgroundMesh.texCoord(0,0);
backgroundMesh.vertex(1,1,0);
backgroundMesh.texCoord(1,0);
backgroundMesh.vertex(1,1,0);
backgroundMesh.texCoord(1,1);
backgroundMesh.vertex(1,1,0);
backgroundMesh.texCoord(0,1);
backgroundMesh.vertex(1,1,0);

explosionMesh=newMesh(gl,4*16,false,true,false);
for(introw=0;row<4;row++)
{
for(intcolumn=0;column<4;column++)
{
explosionMesh.texCoord(0.25f+column*0.25f,0+row*0.25f);
explosionMesh.vertex(1,1,0);
explosionMesh.texCoord(0+column*0.25f,0+row*0.25f);
explosionMesh.vertex(1,1,0);
explosionMesh.texCoord(0f+column*0.25f,0.25f+row*0.25f);
explosionMesh.vertex(1,1,0);
explosionMesh.texCoord(0.25f+column*0.25f,0.25f+row*0.25f);
explosionMesh.vertex(1,1,0);
}
}

}
catch(Exceptionex)
{
Log.d("SpaceInvaders","couldn'tloadmeshes");
thrownewRuntimeException(ex);
}

try
{

Bitmapbitmap=BitmapFactory.decodeStream(activity.getAssets().open("ship.png"));
shipTextur=newTextur(gl,bitmap,TexturFilter.MipMap,TexturFilter.Nearest,TexturWrap.ClampToE
bitmap.recycle();

bitmap=BitmapFactory.decodeStream(activity.getAssets().open("invader.png"));
invaderTextur=newTextur(gl,bitmap,TexturFilter.MipMap,TexturFilter.Nearest,TexturWrap.Clamp
bitmap.recycle();

bitmap=BitmapFactory.decodeStream(activity.getAssets().open("planet.jpg"));
backgroundTextur=newTextur(gl,bitmap,TexturFilter.Nearest,TexturFilter.Nearest,TexturWrap.C
bitmap.recycle();

bitmap=BitmapFactory.decodeStream(activity.getAssets().open("explode.png"));
explosionTextur=newTextur(gl,bitmap,TexturFilter.MipMap,TexturFilter.Nearest,TexturWrap.Cla
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

69/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

bitmap.recycle();
}
catch(Exceptionex)
{
Log.d("SpaceInvaders","couldn'tloadTexturs");
thrownewRuntimeException(ex);
}

font=newFont(gl,activity.getAssets(),"font.ttf",16,FontStyle.Plain);
text=font.newText(gl);

float[]lightColor={1,1,1,1};
float[]ambientLightColor={0.0f,0.0f,0.0f,1};
gl.glLightfv(GL10.GL_LIGHT0,GL10.GL_AMBIENT,ambientLightColor,0);
gl.glLightfv(GL10.GL_LIGHT0,GL10.GL_DIFFUSE,lightColor,0);
gl.glLightfv(GL10.GL_LIGHT0,GL10.GL_SPECULAR,lightColor,0);
}
...tobecontinued...

VielCodezumLaden.GehenwiresderReihenachdurch.ZuerstladenwirdieMeshesfrdasSchiff,Invader,
BlckeundSchsse.Dieseliegen,wiegehabt,alsAssetsvor,dementsprechendladenwirsieauch.Alsnchstes
bastelnwirunseinMeshfrdasHintergrundbild.DiesesisteinRechteck,bestehendauszweiDreiecken,diewir
aufdieganzeHintergrundTexturmappen.DielinkeobereEckedesRechtecksliegtbei(1,1,0),dieuntereEcke
bei(1,1,0).Wirerinnernuns,dass,wennwirkeineProjektionsmatrixsetzen,dersichtbareTeildes
KoordinatensystemsamBildschirmdieselbenAbmessungenhat.Sptermssenwiralsolediglichdie
ProjektionsmatrixaufidentitysetzenunddiebeidenDreieckezeichnen,schonhabenwirdasHintergrundbildber
denganzenBildschirmgespannt.
AlsnchstesbauenwirdasMeshfrdieExplosionen.Hierwirdeswiedereinwenigschwieriger.Inder
ExplosionsTexturbefindensich4*4=16AnimationsstufenderExplosion.FrjededieserAnimationsstufen
bauenwirimMesheineigenesRechteckauszweiDreiecken,beginnendbeideroberstenlinkenAnimationsstufe.
EinsolchesRechteckhatdieAbmessungen1x1,wasungefhrdenAbmessungenderInvaderbzw.desSchiffes
inderxyAchseentspricht.InsgesamthabenwirindemMeshalso16RechteckeinSequenzderAnimationder
Explosion.DiesenUmstandnutzenwirdannspterbeimZeichnenderExplosionenaus.DieTechniknenntman
auchSprite Renderingundsiefunktionierthnlich,wieineinemTrickfilm.Mehrdazuspter.
NachdemwirjetztalleMeshesgeladenbzw.erstellthaben,knnenwirunsdenTexturenwidmen.Auchdiese
ladenwirwiederunspektakulrausdenAssets.ManbeachtehierdieAngabevonMipmappingbeimLaden.Dies
fhrtzueinemgroenPerformanceGewinnundsolltesogutwieimmerverwendetwerden.
AbschlieendladenwirnochdenFont,denwirverwendenwollenunderstelleneineneueInstanzderKlasse
Text,diewirspterzumRendernderStatistikenverwendenwerden.
Lichtbrauchenwirauch,darumsetzenwirdieLichttypFarbenfrdieLichtquelle0schoneinmalimKonstruktor.
WieimLichtKapitelbeschrieben,mssenwirdieseWertenichtjedesMalneusetzen,OpenGLmerktsichdasfr
uns.
Nachdemjetztallesgeladenist,knnenwirunsgleichdasRenderingselbstanschauen.Dazubesitztder
RendererdieMethoderender,dieeineGL10Instanz,dieGameActivityunddieInstanzderSimulation
entgegennimmt.DieSimulationbrauchenwirnatrlich,umzuwissen,wowelchesObjektgezeichnetwerdensoll:
...continued...
publicvoidrender(GL10gl,GameActivityactivity,Simulationsimulation)
{

gl.glClear(GL10.GL_COLOR_BUFFER_BIT|GL10.GL_DEPTH_BUFFER_BIT);
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

70/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

gl.glViewport(0,0,activity.getViewportWidth(),activity.getViewportHeight());

gl.glEnable(GL10.GL_TEXTUR_2D);

renderBackground(gl);

gl.glEnable(GL10.GL_DEPTH_TEST);
gl.glEnable(GL10.GL_CULL_FACE);

setProjectionAndCamera(gl,simulation.ship,activity);
setLighting(gl);

renderShip(gl,simulation.ship,activity);
renderInvaders(gl,simulation.invaders);

gl.glDisable(GL10.GL_TEXTUR_2D);
renderBlocks(gl,simulation.blocks);
gl.glDisable(GL10.GL_LIGHTING);
renderShots(gl,simulation.shots);
gl.glEnable(GL10.GL_TEXTUR_2D);
renderExplosions(gl,simulation.explosions);
gl.glDisable(GL10.GL_CULL_FACE);
gl.glDisable(GL10.GL_DEPTH_TEST);

set2DProjection(gl,activity);

gl.glEnable(GL10.GL_BLEND);
gl.glBlendFunc(GL10.GL_SRC_ALPHA,GL10.GL_ONE_MINUS_SRC_ALPHA);
gl.glTranslatef(0,activity.getViewportHeight(),0);
if(simulation.ship.lives!=lastLives||simulation.score!=lastScore||simulation.wave!=lastWave
{
text.setText("lives:"+simulation.ship.lives+"wave:"+simulation.wave+"score:"+simula
lastLives=simulation.ship.lives;
lastScore=simulation.score;
lastWave=simulation.wave;
}
text.render();
gl.glDisable(GL10.GL_BLEND);
gl.glDisable(GL10.GL_TEXTUR_2D);

invaderAngle+=activity.getDeltaTime()*90;
if(invaderAngle>360)
invaderAngle=360;
}

...tobecontinued...

Wirbeginnendamit,denFramebufferunddenZBufferzulschen.Danachsetzenwir,wiegehabt,denViewport.
AlsnchsteswollenwirdenHintergrundzeichnen.DazumssenwirzuerstTexturingeinschaltenundspringen
dannineineMethode,diedasRenderingselbstbernimmt.Diesewerdenwirunsspteranschauen.
NachdemderHintergrundkein3DObjektistundimmerganzgezeichnetwerdensoll,habenwirbiszudiesem
ZeitpunktdenZBuffernochnichteingeschaltet.Dasmachenwirjetzt,dawirdie3DObjekte,wieInvaderunddas
Schiff,zeichnenwollen.AuchschaltenwirsogenanntesBackfaceCulling dazu.Diesessorgtdafr,dassnur
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

71/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

jeneDreieckegezeichnetwerden,dieinRichtungdesBetrachters"blicken".
Bevorwirunsere3DObjektezeichnen,mssenwirnochdieKameraeinrichtenunddieLichtquellesetzen.Das
bernehmendieMethodensetProjectionAndCameraundsetLightingfruns,diewirunsspterimDetail
anschauenwerden.
WirhabenjetztalsodenZBufferaktiviert,BackfaceCullingeingeschaltet,dieProjektionsundKameraMatrix
gesetzt,sowiedasLichteingeschalten.AuchTexturingistnochinBetrieb.Jetztsindwirbereit,die3DObjektezu
zeichnen.DasZeichnendesSchiffesundderInvaderbernehmendieMethodenrenderShipundrenderInvader.
AlsnchstesschaltenwirTexturingaus,dawirdieBlckeunddieSchssezeichnen.Diesebesitzenjakeine
Texturen.HierhelfenunsdieMethodenrenderBlocksundrenderShotsaus.BevorwirdieSchssezeichnen,
schaltenwirdasLichtwiederaus,diesesollenjakeinenSchattenhaben.Alsletzte3DObjektezeichnenwirdie
Explosionen.FrdiesemssenwirTexturingwiedereinschalten.Wirzeichnensiedeshalbalsletztesinder
Reihenfolge,dadiesetransparentsind.Wiewirunserinnern,mssenwirtransparenteObjekteimmerzuletzt
zeichnen,daessonstzuProblemenmitdemZBufferkommt.AuchhierhabenwirwiedereinehandlicheMethode
namensrenderExplosions,dieunsdieArbeitabnimmt.
Alle3DObjektesindgezeichnet,daherknnenwirBackfaceCullingunddenZBufferwiederausschalten.Wir
wollennundieStatistikenzeichnen,dieja2DElementesind.Dazusetzenwireineentsprechende
Projektionsmatrix,diefrein2DKoordinatenSystemsorgt.DasmachtdieMethodeset2DProjection.Alsnchstes
schaltenwirBlendingein,daunserTexttransparenteStellenbesitztundsetzeneineTransformation,damitder
TextinderoberenlinkenEckedesBildschirmsgezeichnetwird.BevorwirdenTextzeichnen,prfenwirnoch,ob
sichdieStatistikwerte,imVergleichzudenzuletztgespeichertenWerten,genderthaben.IstdiesderFall,sagen
wirderTextInstanz,dasssieeinenneuenStringdarstellensollundmerkenunsdieneuenWerte.DieMethode
Text.setTextbautinternaufBasisdesbergebenenStringsneueDreieckezusammen,waseineetwas
kostspieligeOperationist,wennmanesjedesFramemacht.AuchwrederGarbageCollectorwenigerfreut,
wennwirinjedemFrameeinenneuenStringerzeugen.Darummachenwirdashieretwasumstndlich.DerText
istalsogesetztundwirknnenihneinfachzeichnen.DanachschaltenwirBlendingwiederab.
ZuguterLetzterhhenwirdasAttributinvaderAnglenochzeitbasiert.DiesenverwendenwiralsRotationswinkel
umdieyAchsefrdieInvader,diesichdamitdrehen.IneinerSekundedrehensiesichdabeium90Grad,wofr
dieMultiplikationderDeltaTimemit90sorgt.
Machenwirunsnochschnellbewusst,welchenStatusOpenGLnachverlassenderrenderMethodehat.ZBuffer,
Beleuchtung,TexturingundBlendingsindausgeschalten.DieProjektionsMatrixistaufeine2DProjektion
gesetzt,dieModelViewMatrixaufdieTransformationzurVerschiebungindieoberelinkeEcke.OpenGLbehlt
diesenStatusbiszumnchstenRenderAufruf.Esistimmerwichtig,sichberdiesenStatusimKlarenzuseinund
ihnsosauberwiemglichzuhalten,esknnensonstunerklrbareProblemeauftreten,wiedasFehlenvon
Texturen,dieeigentlichnurdarausresultieren,dassdasTexturingnichteingeschaltenist.Selbigesgiltauchfr
dieMatrizen,diedafrsorgenknnen,dassObjekteausdemBlickfeldverschobenwerden,nurweilman
vergessenhat,zuersteineidentityMatrixzuladenunddamitdenaltenInhaltzulschen.
SchauenwirunsjetztnochdieinderrenderMethodeverwendetenHelferMethodenan.Wirmachendasinder
ReihenfolgedesAuftretensinrender:
...continued...
privatevoidrenderBackground(GL10gl)
{
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
backgroundTextur.bind();
backgroundMesh.render(PrimitiveType.TriangleFan);
}
...tobecontinued...

http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

72/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

BeimRenderingdesHintergrundssetzenwirzuerstProjektionsundModelViewMatrixaufIdentity.Damit
schaffenwirein2DKoordinatensystem,dessensichtbarerTeilinxybei(1,1)bis(1,1)geht.UnserMeshfrden
HintergrundhatgenaudieselbenAbmessungen.WirbrauchendahernurmehrdieHintergrundTexturzubinden
unddasMeshzuzeichnen,schonhabenwirdenganzenBildschirmmitdemHintergrundbildgefllt.Einen
kleinenFehlerhatdasganze:DasHintergrundbildhatdieAbmessungen512x512Pixel.DerBildschirmhatdiese
hundertprozentignicht,dasBildwirdalsogestaucht.Alsbungknntihrjaversuchen,diesesProblemzulsen.
...continued...
privatevoidsetProjectionAndCamera(GL10gl,Shipship,GameActivityactivity)
{
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();
floataspectRatio=(float)activity.getViewportWidth()/activity.getViewportHeight();
GLU.gluPerspective(gl,67,aspectRatio,1,1000);

gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
GLU.gluLookAt(gl,ship.position.x,6,2,ship.position.x,0,4,0,1,0);
}
...tobecontinued...
AuchhierpassiertnichtsNeues.WieimKapitelzuProjektionenbeschrieben,setzenwirzuersteine
perspektivischeProjektionsMatrix.DieModelViewMatrixsetzenwirberGLU.gluLookAtaufeineKamera
Matrix.DieKamerabefindetsichdabeietwasoberhalbdesSchiffesundschautschrgnachuntenaufdas
Spielfeld.Interessantdabeiist,dassdiexPositionderKameravonderxPositiondesSchiffesabhngt.Mit
diesemKnifflassenwirdieKamerademSchifffolgen.
...continued...
float[]direction={1,0.5f,0,0};
privatevoidsetLighting(GL10gl)
{
gl.glEnable(GL10.GL_LIGHTING);
gl.glEnable(GL10.GL_LIGHT0);

gl.glLightfv(GL10.GL_LIGHT0,GL10.GL_POSITION,direction,0);
gl.glEnable(GL10.GL_COLOR_MATERIAL);

}
...tobecontinued...
AuchdasSetzendesLichtesbirgtkeineNeuerungen.ZuerstschaltenwirdasLichtein,dann,imSpeziellen,das
LichtmitNummer0.AlsnchstessetzenwirdieRichtungdesLichtes,daseindirektionalesist.DasArray,dasdie
Richtunghlt,instanzierenwirdabeinichtjedesMalneu,wirdenkenandenGarbageCollector.DasLichtkommt
indiesemFallvonrechtsoben.AbschlieendschaltenwirColorMaterialsnocheinundsindfertig.
...continued...
privatevoidrenderShip(GL10gl,Shipship,GameActivityactivity)
{
if(ship.isExploding)
return;
shipTextur.bind();
gl.glPushMatrix();
gl.glTranslatef(ship.position.x,ship.position.y,ship.position.z);
gl.glRotatef(45*(activity.getAccelerationOnYAxis()/5),0,0,1);
gl.glRotatef(180,0,1,0);
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

73/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

shipMesh.render(PrimitiveType.Triangles);
gl.glPopMatrix();
}
...tobecontinued...
ExplodiertdasSchiffgerade,brauchenwiresnatrlichnichtzeichnen.DasZeichnenderentsprechenden
ExplosionregelnwirdanninrenderExplosions.ExplodiertdasSchiffnicht,bindenwirzuerstdieTexturfrdas
Schiff.Danachfolgt,wasimmerfolgensollte,wirpushendieModelViewMatrix(diewirzuvorin
setProjectionAndCameraaktivierthaben).DienchstendreiAufrufeverschiebenundrotierendasSchiff.Wir
erinnernunswieder,daaswirdieReihenfolgeumkehrenmssen.Beginnenwiralsobeiderletzten
Transformation.DieserotiertdasSchiffum180GradumdieyAchse.DaichdasSchiffinWings3Dsogemacht
habe,dassesentlangderpositivenzAchseschaut,mssenwireshierindieentgegengesetzteRichtung
rotieren.AlsnchstessorgenwirmiteinerweiterenRotationfreinenschnenAnblick.Davonabhngig,wiedas
GertentlangseineryAchsegeneigtist,rotierenwirdasSchiffumdiezAchse.HltmandasGerteim
LandscapeModusundneigteswieeinLenkradnachlinksundrechts,schlgtderAccelerometerentlangdery
AchseimBereich(10,10)aus.DiesenWertenegierenwir(wegenderRotationsrichtung)unddividierenihndurch
5,womitwirineinenWertebereichvon(2,2)kommen.DieseWertemultiplizierenwirdannnochmit45Grad,
womitwiraufdenwirklichenRotationswinkeldesSchiffesumdiezAchsekommen.DieserWinkelliegtsomitim
Bereich(45*2,45*2).DieseMaximalwertewerdenerreicht,wennmandasGertimPortraitModushlt.Alsletzte
TransformationverschiebenwirdasrotierteSchiffnochanseinePositionimSpielfeld.EsfolgtdasRenderndes
MeshesunddaspopenderModelViewMatrix.DiehatdanachwiedernurdieKameraMatrixgesetztundwir
knnendienchstenObjektezeichnen.
...continued...
privatevoidrenderInvaders(GL10gl,ArrayList<Invader>invaders)
{
invaderTextur.bind();
for(inti=0;i<invaders.size();i++)
{
Invaderinvader=invaders.get(i);
gl.glPushMatrix();
gl.glTranslatef(invader.position.x,invader.position.y,invader.position.z);
gl.glRotatef(invaderAngle,0,1,0);
invaderMesh.render(PrimitiveType.Triangles);
gl.glPopMatrix();
}
}
...tobecontinued...
BeimZeichnenderInvadergehenwirhnlichvor.ZuerstsetzenwirdieInvaderTextur.Danachgehenwirberall
InvaderinderSimulation(diewirjaalsParameterinderRenderer.renderMethodebekommenhaben).Frjeden
InvaderpushenwirzuerstwiederdieModelViewMatrix,transformierendenInvaderanseinenrichtigenPlatzund
zeichnendasInvaderMesh.DanachpopenwirwiederdieModelViewMatrix.Wieersichtlich,rotierenwirjeden
InvaderauchumdieyAchse.DenWinkelerhhenwirinderRenderer.renderMethodezeitbasiert.
...continued...
privatevoidrenderBlocks(GL10gl,ArrayList<Block>blocks)
{

gl.glEnable(GL10.GL_BLEND);
gl.glBlendFunc(GL10.GL_SRC_ALPHA,GL10.GL_ONE_MINUS_SRC_ALPHA);
gl.glColor4f(0.2f,0.2f,1,0.7f);
for(inti=0;i<blocks.size();i++)
{
Blockblock=blocks.get(i);
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

74/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

gl.glPushMatrix();
gl.glTranslatef(block.position.x,block.position.y,block.position.z);
blockMesh.render(PrimitiveType.Triangles);
gl.glPopMatrix();
}
gl.glColor4f(1,1,1,1);
gl.glDisable(GL10.GL_BLEND);
}
...tobecontinued...
WieaufdemBildobenersichtlich,scheinendieBlckeleichtdurch.DeswegenaktivierenwirzuerstBlending.
AuchTexturhabenwirfrdieBlckekeine,TexturingistzudiesemZeitpunktschondeaktiviert.Wirfrbendie
BlckedaherhndischmitglColor4fmiteinemhbschenBlauein.DanachgehenwirwiederberalleBlcke,
pushendieModelViewMatrix,transformierendenBlock,renderndasBlockMeshundpopendieModelView
Matrix.AbschlieendsetzenwirdieZeichenfarbewiederaufweiundschaltenBlendingaus.
...continued...
privatevoidrenderShots(GL10gl,ArrayList<Shot>shots)
{
gl.glColor4f(1,1,0,1);
for(inti=0;i<shots.size();i++)
{
Shotshot=shots.get(i);
gl.glPushMatrix();
gl.glTranslatef(shot.position.x,shot.position.y,shot.position.z);
shotMesh.render(PrimitiveType.Triangles);
gl.glPopMatrix();
}

gl.glColor4f(1,1,1,1);
}
...tobecontinued...
AuchdieSchssesindschnellgezeichnet.Farbesetzen,beralleSchssegehen,push,transform,render,pop
unddieFarbewiederzurcksetzen.Langsamwirdeslangweilig.

privatevoidrenderExplosions(GL10gl,ArrayList<Explosion>explosions)
{
gl.glEnable(GL10.GL_BLEND);
gl.glBlendFunc(GL10.GL_SRC_ALPHA,GL10.GL_ONE_MINUS_SRC_ALPHA);
explosionTextur.bind();
for(inti=0;i<explosions.size();i++)
{
Explosionexplosion=explosions.get(i);
gl.glPushMatrix();
gl.glTranslatef(explosion.position.x,explosion.position.y,explosion.position.z);
explosionMesh.render(PrimitiveType.TriangleFan,(int)((explosion.aliveTime/Explosion.EXPLOSION_LI
gl.glPopMatrix();
}

gl.glDisable(GL10.GL_BLEND);
}

FrdieExplosionenbrauchenwirwiederBlending,daswirgleicheinmaleinschalten.Dannbindenwirdie
ExplosionsTexturundgehenberjedeExplosion.Hierwirdesinteressant.Wiegehabt,pushenwirzuerstund
transformierendann.BeimRenderndesMesheswendenwirabereinenkleinenTrickan.Abhngigdavon,wie
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

75/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

langedieExplosionschonamLebenist,zeichnenwirnureinesderRechteckeimExplosionsMesh.Wirerinnern
uns,dasswirdortjafrdie16AnimationsphasenjeweilseinRechteckdefinierthaben.DieMeshKlassebietet
nebendereinfachenMesh.render(PrimitiveTypetype)Methodeauchnocheinezweite,dieeserlaubt,einen
OffsetindasMeshanzugeben,sowiedieAnzahlderVertices,dieabdiesemOffsetverwendetwerdensollen.Wir
errechnenunseinfachdenOffsetdespassendenRechtecksundverwendendessenvierVerticeszumZeichnen
deraktuellenExplosion.DasOffsetergibtsichausderLebensdauerderExplosion,geteiltdurchdiemaximale
Lebensdauer,waseinenWertzwischen0und1ergibt.DiesenWertmultiplizierenwirdannmit15undcastenin
aufeinenint.NachdiesereinfachenFormellsstsiedieAnimationsstufesehreinfachberechnen.Istdie
Explosionz.B.seiteinerSekundeamLeben,whlenwirRechteckNummer(int)(1.0/2*15)=7.Dies
multiplizierenwirnochmitvier,dawirdasOffsetinVerticesangebenmssen.Undschonhabenwirunsere
Explosionenanimiert!NatrlichpopenwirdannwiederdieModelViewMatrixundschaltenBlendingwiederaus.
DamithabenwireigentlichdasganzeRenderingderSimulationbesprochen.Wirbrauchennurnocheine
Methode,diedieganzenMeshesundTexturenaufrumt:
...continued...
publicvoiddispose()
{
shipTextur.dispose();
invaderTextur.dispose();
backgroundTextur.dispose();
explosionTextur.dispose();
font.dispose();
text.dispose();
explosionMesh.dispose();
shipMesh.dispose();
invaderMesh.dispose();
shotMesh.dispose();
blockMesh.dispose();
backgroundMesh.dispose();
}
WirgebeneinfachallegeladenenTexturenundMesheswiederfrei,keinegroeSache.
DerRendererinKombinationmiteinerSimulationsInstanzwrejetztschonvollstndigeinsetzbar.Wirmssten
dasGanzenurineineGameActivitypacken,dieSimulationinjedemFrameaktualisierenunddenRenderer
anwerfen.Bevorwirdassabertun,wollenwirunsnochkurzumdenSoundkmmern.Solljaschlielichauch
krachen.

SoundundMusik
DiesesKapitelwirdwiedererfrischendeinfach.hnlichdemRendererbrauchenwireineKlasse,dieunsdas
LadenderSoundeffektundMusikschnkapselt.AuchmusssieunsdieMglichkeitbietenondemandeinen
Soundeffektabzuspielen,bzw.dieMusikzustoppen.WirbastelndazueineKlassenamensSoundManager,die
wirinsgleichimDetailanschauenwerden.
DieSoundeffektefrdenSpaceInvadersKlonhabeichmiteinemnettenToolnamenssfxr gemacht.Dieses
erlaubtdaseinfacheerstellenvon8BitartigenSoundeffektenmiteinemeinzigenMausklick.Diesoentstandenen
EffektehabeicheinwenigmitAudacity nachbearbeitet.EsgibteinenEffektfrdieExplosionenundeinenEffekt
frdieSchsse.
DieMusikhabeichvorUrzeitendaheimmitCubaseeingespielt.IsteinunaufregenderRockTrack.

SoundManagerKlasse
WieimRenderermssenwirzuersteinmalallesladen.DasmachenwirwiederimKonstruktor:
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

76/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

publicclassSoundManager
{
SoundPoolsoundPool;
AudioManageraudioManager;
MediaPlayermediaPlayer;
intshotID;
intexplosionID;

publicSoundManager(GameActivityactivity)
{
soundPool=newSoundPool(10,AudioManager.STREAM_MUSIC,0);
audioManager=(AudioManager)activity.getSystemService(Context.AUDIO_SERVICE);
activity.setVolumeControlStream(AudioManager.STREAM_MUSIC);

try
{
AssetFileDescriptordescriptor=activity.getAssets().openFd("shot.wav");
shotID=soundPool.load(descriptor,1);
descriptor=activity.getAssets().openFd("explosion.wav");
explosionID=soundPool.load(descriptor,1);
}
catch(Exceptionex)
{
Log.d("SoundSample","couldn'tloadsound'shot.wav'");
thrownewRuntimeException(ex);
}

mediaPlayer=newMediaPlayer();
try
{
AssetFileDescriptordescriptor=activity.getAssets().openFd("8.12.mp3");
mediaPlayer.setDataSource(descriptor.getFileDescriptor());
mediaPlayer.prepare();
mediaPlayer.setLooping(true);
mediaPlayer.start();

}
catch(Exceptionex)
{
ex.printStackTrace();
Log.d("SoundSample","couldn'tloadmusic'music.mp3'");
thrownewRuntimeException(ex);
}
}
...tobecontinued...
AlsAttributehaltenwirunsjeeinenSoundPool,einenAudioManagereinenMediaPlayer,sowiediespter
initialisiertenIDsderbeidenSoundeffekte.ImKonstruktorbauenwirzuersteinenSoundPool,derzehnEffekte
gleichzeitigspielenknnensoll.AlsnchstesholenwirunsdenAudioManager,denbrauchenwirspternoch.
DannsagenwirAndroid,dassbeiBettigungderVolumeTastendieMedienlautstrkegendertwerdensoll.Das
istwichtig,sonstwirdnurdieKlingeltonlautstrkeberdieTastengeregelt.
AlsnchstesladenwirdiebeidenSoundeffekteindenSoundPool.DieseliegenalsAssetsvor.Diebeiden
erhaltenenIds,speichernwirindenentsprechendenAttributen.
NachdemInstanzierendesMediaPlayerslassenwirdiesendieMusikDateiabspielenundzwargeloopt.
NatrlichgibtesnocheinpaarandereMethoden:
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

77/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

...continued...
publicvoidplayShotSound()
{
intvolume=audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
soundPool.play(shotID,volume,volume,1,0,1);
}

publicvoidplayExplosionSound()
{
intvolume=audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
soundPool.play(explosionID,volume,volume,1,0,1);
}
...tobecontinued...

ZweiMethodenwerdenesunssptererlauben,dieEffektefrSchsseundExplosionenabzuspielen.Auchhier
solltenwirschonalleskennen.WirholenunsjeweilsdieaktuelleMedienlautstrkeundspielendanndenEffekt
mitderentsprechendenIDab.
ZuguterLetztmssenwirnatrlichwiederaufrumen:
publicvoiddispose()
{
soundPool.release();
mediaPlayer.release();
}

WirgebensowohldenSoundPool,alsauchdenMediaPlayerfrei.Letzteresistwichtig,dasonstdieMusikauch
nachdemSchlieenderApplikationweiterluft.Wirdrfenalsospternichtvergessen,die
SoundManager.disposeMethodeaufzurufen.

SpaceInvadersActivity&Screens
EndlichknnenwirunsdemletztenBausteinunseresSpielswidmen,derActivityunddensogenanntenScreens.
EinScreenstellteinenZustandimSpieldar,inunseremFallistdasderStartBildschirm,derdasSpiellogozeigt,
auerdemeineAufforderung,denBildschirmzuberhren,deneigentlichenSpielBildschirm,derdieSimulation
laufenlsstundrendertunddenGameOverBildschirm,derdenerreichtenPunktestandanzeigt.DieAufteilungin
Screenserlaubtesuns,dieverschiedenenZustndedesSpielsindereigentlichenActivityzuverwalten.Auf
einenScreenfolgteinanderer.EinneuerScreenwirdaktiviert,sobaldderaktuellebeendetist.Ichhabedazu
folgendesInterfacedefiniert:
publicinterfaceGameScreen
{
publicvoidupdate(GameActivityactivity);
publicvoidrender(GL10gl,GameActivityactivity);
publicbooleanisDone();
publicvoiddispose();
}

DieupdateMethodesolldenZustanddesScreensaktualisieren.Darunterflltz.B.dasLaufenlassender
Simulation.DieMethoderendersollteselbsterklrendsein.DieMethodeisDoneerlaubtesuns,denScreenzu
fragen,oberfertigistunddernchsteScreenangezeigtwerdenkann.DieMethodedisposewerdenwiraufrufen,
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

78/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

wennwiraufdennchstenScreenschalten,damitdieRessourcendesaltenwiederfreigegebenwerden
(SoundManager,Renderer).
Wiebereitserwhnt,besitztunserSpaceInvadersKlondreiScreens,diejeweilsvonGameScreenableiten.Wir
werdendiesezuerstbesprechenunddann,alsletztenPunkt,dieActivitybetrachten,diealldiessteuert.

StartScreenKlasse
DerStartBildschirmsollzumeinenunserHintergrundbilddarstellen,sowieeinLogounddenHinweis,dassder
BenutzerdenSchirmberhrensoll.DerScreenwirdbeendet,sobaldderBenutzerdenScreenberhrthat.Das
Ganzeistnichtallzuschwerzuimplementieren,schauenwiresunsan:
publicclassStartScreenimplementsGameScreen
{
MeshbackgroundMesh;
TexturbackgroundTextur;
MeshtitleMesh;
TexturtitleTextur;
booleanisDone=false;
SoundManagersoundManager;
Fontfont;
Texttext;
StringpressText="TouchScreentoStart!";
...tobecontinued...

ErstmalleitenwirvonGameScreenab.AlsnchstesdefinierenwirunseinpaarAttribute.DazuzhlendieTextur
unddasMeshfrdenHintergrund,bzw.frdasLogo.EinBooleannamensisDonespeichert,obderScreenfertig
ist.EinenSoundManagerbrauchenwirauch,dawirHintergrundmusikabspielenwollen.AucheineInstanzvon
FontundTextbrauchenwirzurTouchAufforderung.DasletzteAttributistimEndeffektnureineKonstante,dieden
Aufforderungstexthlt.
publicStartScreen(GL10gl,GameActivityactivity)
{

backgroundMesh=newMesh(gl,4,false,true,false);
backgroundMesh.texCoord(0,0);
backgroundMesh.vertex(1,1,0);
backgroundMesh.texCoord(1,0);
backgroundMesh.vertex(1,1,0);
backgroundMesh.texCoord(1,1);
backgroundMesh.vertex(1,1,0);
backgroundMesh.texCoord(0,1);
backgroundMesh.vertex(1,1,0);

titleMesh=newMesh(gl,4,false,true,false);
titleMesh.texCoord(0,0);
titleMesh.vertex(256,256,0);
titleMesh.texCoord(1,0);
titleMesh.vertex(256,256,0);
titleMesh.texCoord(1,0.5f);
titleMesh.vertex(256,0,0);
titleMesh.texCoord(0,0.5f);
titleMesh.vertex(256,0,0);
try
{
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

79/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

Bitmapbitmap=BitmapFactory.decodeStream(activity.getAssets().open("planet.jpg"));
backgroundTextur=newTextur(gl,bitmap,TexturFilter.MipMap,TexturFilter.Nearest,TexturWrap.Cl
bitmap.recycle();

bitmap=BitmapFactory.decodeStream(activity.getAssets().open("title.png"));
titleTextur=newTextur(gl,bitmap,TexturFilter.Nearest,TexturFilter.Nearest,TexturWrap.ClampT
bitmap.recycle();
}
catch(Exceptionex)
{
Log.d("SpaceInvaders","couldn'tloadTexturs");
thrownewRuntimeException(ex);
}

soundManager=newSoundManager(activity);

font=newFont(gl,activity.getAssets(),"font.ttf",activity.getViewportWidth()>480?32:16,FontSt
text=font.newText(gl);
text.setText(pressText);
}
...tobecontinued...

Wiegewohnt,ladenwirhieralleunsereRessourcen.ZuerstbauenwirdasHintergrundMesh,sowieim
Renderer.AlsnchstesbauenwirdasMeshfrdasLogo.DiesesdefinierenwirumdenUrsprungin
Pixelkoordinaten.EshatdieAbmessungen512x256undgemappedaufdenoberenTeilfolgenderTextur:

http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

80/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

AlsnchstesladenwirdieTexturenfrHintergrundundLogo.Danachinstanzierenwireinenneuen
SoundManager.AbschlieenderstellenwirunsnocheinenFontundeineTextInstanz,derwirden
Aufforderungstextsetzen.Interessanthierbeiist,dassichdieGredesFontsabhngigvonderViewportBreite
mache.AndroiduntersttztjamittlerweilemehrereBildschirmauflsungen.Ein16PixelFontsiehtauf800x480zu
kleinaus,deswegenmachenwirimFalleinerhohenBildschirmauflsungdenFontdoppeltsogro.
...continued...
@Override
publicbooleanisDone()
{
returnisDone;
}
...tobecontinued...

DieersteInterfaceMethodeistrelativeinfach,sieliefertnurdenInhaltderisDoneVariablenzurckund
signalisiertsonachauen,obderScreenfertigistodernicht.
...continued...
@Override
publicvoidupdate(GameActivityactivity)
{
if(activity.isTouched())
isDone=true;
}
...tobecontinued...

Auchupdatehltsichsimpel.WirerfragenlediglichvonderGameActivity,obderScreenaktuellberhrtwirdund
setzenisDoneentsprechend.DamithabenwirauchschondieLogikdiesesScreenskomplettimplementiert.Fehlt
nochdasRendern:
...continued...
@Override
publicvoidrender(GL10gl,GameActivityactivity)
{
gl.glViewport(0,0,activity.getViewportWidth(),activity.getViewportHeight());
gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
gl.glEnable(GL10.GL_TEXTUR_2D);
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();

gl.glEnable(GL10.GL_BLEND);
gl.glBlendFunc(GL10.GL_SRC_ALPHA,GL10.GL_ONE_MINUS_SRC_ALPHA);

backgroundTextur.bind();
backgroundMesh.render(PrimitiveType.TriangleFan);
gl.glMatrixMode(GL10.GL_PROJECTION);
GLU.gluOrtho2D(gl,0,activity.getViewportWidth(),0,activity.getViewportHeight());
gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();

http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

81/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

gl.glLoadIdentity();
gl.glTranslatef(activity.getViewportWidth()/2,activity.getViewportHeight()256,0);
titleTextur.bind();
titleMesh.render(PrimitiveType.TriangleFan);
gl.glLoadIdentity();
gl.glTranslatef(activity.getViewportWidth()/2font.getStringWidth(pressText)/2,100,0);
text.render();

gl.glDisable(GL10.GL_TEXTUR_2D);
gl.glDisable(GL10.GL_BLEND);
}
...tobecontinued...

WirstartenwieimmermitdemSetzendesViewportunddemLschendesFramebuffers.Alsnchstessetzenwir
dieProjektionsundModelViewMatrixfrdasZeichnendesHintergrunds.Dasselbehabenwirjaschonim
Renderergemacht.BlendingundTexturingschaltenwirauchein.EsfolgtdasRenderndesHintergrunds.Danach
setzenwireineorthographischeProjektion,damitwirunswiederimBildschirmkoordinatensystembefinden.Das
LogozeichnenwirzentriertamoberenEndedesBildschirms.DenTextzeichnenwirzentriert100Pixelberdem
unterenBildschirmrand.AbschlieendschaltenwirTexturingundBlendingwiederausundsindfertig.
EineMethodefehltunsnoch:
...continued...
publicvoiddispose()
{
backgroundTextur.dispose();
titleTextur.dispose();
soundManager.dispose();
font.dispose();
text.dispose();
backgroundMesh.dispose();
titleMesh.dispose();
}
Wienichtanderszuerwarten,gebenwirhierwiederalleRessourcenfrei.Augenmerksollauchaufdasdispose
desSoundManagersgelegtwerden.DamitdrehenwirauchwiederdieMusikab,diesonstweiterlaufenwrde!

GameOverScreenKlasse
DieGameOverScreenKlasseistnahezuidentischmitderStartScreenKlasse.EinzigerUnterschiedist,dasswir
anstattdesLogosingroenLetternGameOveranzeigenundanstattderAufforderungzumBerhrendes
BildschirmsdieerreichtePunktezahlanzeigen,diewirberdenKonstruktorhereinbekommen.Logikund
RenderingsindidentischmitderStartScreenKlasse.IchersparemirhieralsodieAuflistungdesCodes.

GameLoopKlasse
DerinteressantesteScreen,istderGameLoopScreen.HierinstanzierenwirdenRendererunddieSimulationund
prozessierendenInputdesAccelerometers,umdasSchiffzubewegen.DurchdieschneKapselungistdiese
Klasseextremklein:
publicclassGameLoopimplementsGameScreen,SimulationListener
{
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

82/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

publicSimulationsimulation;
Rendererrenderer;
SoundManagersoundManager;
...tobecontinued...

WirhabennurdreiAttribute:dieSimulation,denRendererundeinenSoundManager.Sehrhbsch!
...continued...
publicGameLoop(GL10gl,GameActivityactivity)
{
simulation=newSimulation();
simulation.listener=this;
renderer=newRenderer(gl,activity);
soundManager=newSoundManager(activity);
}
publicGameLoop(GL10gl,GameActivityactivity,Simulationsimulation)
{
this.simulation=simulation;
this.simulation.listener=this;
renderer=newRenderer(gl,activity);
soundManager=newSoundManager(activity);
}
...tobecontinued...
KonstruktorengibteszweianderZahl.DerersteinstanziertseineSimulationselbst,derzweitenimmteine
Simulationvonauenentgegen.DenZweitenwerdenwirspterdazuverwenden,einpausiertesSpiel
fortzusetzen.NachdemAufrufdesKonstruktorshabenwireinenvollgeladenenRenderer,eineSimulationund
einenSoundManager,derbereitsdieHintergrundmusikabspielt.
....continued...
@Override
publicvoidupdate(GameActivityactivity)
{
processInput(activity);
simulation.update(activity.getDeltaTime());
}
...tobecontinued...
DieupdateMethodedesScreensistwiederdenkbareinfach.ZuerstrufenwirdieMethodeprocessInputauf,die
dieAccelerometereingabeinSchiffbewegungenumsetzt.DanachaktualisierenwireinfachdieSimulationmitder
aktuellenDeltaTime.

...continued...
privatevoidprocessInput(GameActivityactivity)
{

if(activity.getAccelerationOnYAxis()<0)
simulation.moveShipLeft(activity.getDeltaTime(),Math.abs(activity.getAccelerationOnYAxis())/10
else
simulation.moveShipRight(activity.getDeltaTime(),Math.abs(activity.getAccelerationOnYAxis())/10

if(activity.isTouched())
simulation.shot();
}
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

83/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

...tobecontinued...

IndieserMethodenehmenwirdieBenutzereingabeentgegen.WirerinnernunsnochandieMethoden
moveShipLeft/moveShipRightderSimulation.DiesebefeuernwirjetztinAbhngigkeitvomAusschlagdes
AccelerometersaufderyAchsedesGertes.WiewirausdemRenderingKapitelwissen,liegtdieserimBereich
(10,10).DurchdieDivisionmit10normierenwirdiesenauf(1,1).DieserFaktorbestimmtdann,wievielvonder
SchiffsgeschwindigkeitShip.SHIP_VELOCITYwirklichherangezogenwird,umdasSchiffzubewegen.Jemehr
derBenutzerdasGertneigt,destoschnellerfhrtdasSchiffnachlinksundrechts.Dasistalles,waswir
brauchen,umdasSchiffzubewegen.Sehreinfach,dankunsererKapselung.
AuchSchssewollenwirabgeben.DazufragenwirdieGameActivity,obderScreenberhrtwirdundrufenin
diesemFallSimulation.shotauf,diefrunsdasErstelleneinesSchusses,sowiedasPrfen,obschoneinSchuss
desSchiffesimSpielfeldist,bernimmt.MehrCodebrauchenwirfrdieVerarbeitungderBenutzereingabenicht!
...continued...
publicbooleanisDone()
{
returnsimulation.ship.lives==0;
}
...tobecontinued...
DerScreenistfertig,sobalddasSchiffkeinLebenmehrhat.
...continued...
@Override
publicvoidrender(GL10gl,GameActivityactivity)
{
renderer.render(gl,activity,simulation);
}
...tobecontinued...

DasRenderingbernimmtfrunsdieRendererKlasse,derwireinfachdieSimulationbergeben.
...continued...
@Override
publicvoiddispose()
{
renderer.dispose();
soundManager.dispose();
}
...tobecontinued...

UndauchdasAufrumengestaltetsichwiedersehreinfach.
AufmerksamenLesernistvielleichtaufgefallen,dassdieKlassedasInterfaceSimulationListenerimplementiert.
AuerdemsetzenwirindenKonstruktorenderSimulationdieInstanzderKlassealsListener.Dasnutzenwiraus,
umdieSoundeffekteabzuspielen!
...continued...
@Override
publicvoidexplosion()
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

84/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

{
soundManager.playExplosionSound();
}
@Override
publicvoidshot()
{
soundManager.playShotSound();
}
JedesMal,wenninderSimulationeinSchussflltodereineExplosionerzeugtwird,rufenwirjadenListenerauf.
IndiesemFallnutzenwirdasumdemSoundManagerzusagen,dasserdenentsprechendenEffektabspielen
soll.
UnddaswardasgesamteSpiel.EsfehltnurnochdieActivity,dannsindwirkomplettfertig!

SpaceInvadersActivity
ZumAbschlussmssenwirunseredreiScreensnochirgendwiekoordinieren.AuchmssenwirdenActivityLife
Cycleimplementieren.DasmachenwirineinerActivitynamensSpaceInvaders.DieActivitystartetmitdem
StartScreenundzeigtdiesensolangean,bisdessenisDoneMethodetruezurckgibt.ZudiesemZeitpunkt
schaltenwiraufdenGameLoopumunddasSpielstartet.DasGanzemachenwirdannauchfrdenGameLoop
bzw.denGameOverScreen.DieActivitymussabernatrlichauchaufonPauseundonResumeregieren.Wir
wollenja,dassdasSpielfortgesetztwerdenkann,wennesdurcheinenAnrufodereinemDruckaufdieHome
Tasteunterbrochenwird.HierhilftunsdieschneKapselungderSimulation.Schauenwirunsalsoan,wasdie
ActivityallesimCodemacht:

publicclassSpaceInvadersextendsGameActivityimplementsGameListener
{
GameScreenscreen;
Simulationsimulation=null;

publicvoidonCreate(Bundlebundle)
{
setRequestedOrientation(0);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FU

super.onCreate(bundle);
setGameListener(this);
if(bundle!=null&&bundle.containsKey("simulation"))
simulation=(Simulation)bundle.getSerializable("simulation");
Log.d("SpaceInvaders","created,simulation:"+(simulation!=null));
}
...tobecontinued...

DieActivityleitetnatrlichvonGameActivityab.DesWeiterenimplementiertsiedasGameListenerInterface.Als
MemberhatsieeinenGameScreenundauerdemeineSimulation.ImKonstruktorsetzenwirdasFensterder
ActivityzuerstindenLandscapeMode,schaltendieTitelleisteabundgehenFullscreen.Danachrufenwir
onCreatederVaterklasseGameActivityauf,diejadieOpenGLInitialisierungfrunsbernimmtundsetzenuns
selbstalsGameListener.
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

85/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

AbhngigvomLifeCyclekannderParameterbundlevonunszuvorgesetzteInstanzenverschiedenerKlassen
besitzen.FrdasimplementierenderResumeFhigkeitwerdenwirspterindiesesBundleindieSimulation
schreiben,wennwirunsimGameLoopbefinden.ImKonstruktorlesenwirlediglichdasBundleausund
berprfen,obeineSimulationdarinenthaltenist.Istdasso,speichernwirsieimAttributsimulationderActivity.
...continued...
@Override
publicvoidonSaveInstanceState(BundleoutState)
{
super.onSaveInstanceState(outState);
if(screeninstanceofGameLoop)
outState.putSerializable("simulation",((GameLoop)screen).simulation);
Log.d("SpaceInvaders","savedgamestate");
}
...tobecontinued...

DieseMethodewirdvomAndroidOSaufgerufen,bevordieActivityberdenJordangeht.Sieerlaubtesuns,
ZustndetemporrzuspeichernunddiesedannspterinonCreatebeimNeuerstellenderActivityzulesen.Ich
habemirdieFreiheitgenommen,alleKlassenderSimulationdasSerializableInterfaceimplementierenzulassen.
Damitbrauchenwirhierlediglichberprfen,obgeradederGameLoopaktiviertistundunsvondiesemdie
Simulationholen,diewirdannindasBundleunterdemSchlsselsimulationablegen.DamithttenwireinenTeil
desLifeCyclesfertig.
...continued...
@Override
publicvoidonPause()
{
super.onPause();
if(screen!=null)

screen.dispose();
if(screeninstanceofGameLoop)
simulation=((GameLoop)screen).simulation;
Log.d("SpaceInvaders","paused");
}

@Override
publicvoidonResume()
{
super.onResume();

Log.d("SpaceInvaders","resumed");
}

...tobecontinued...
AlsnchstesmssenwironPauseundonResumeimplementieren.Inbeidenrufenwirzuerstdieentsprechende
MethodederSuperklasseauf,dasistwichtigunddarfaufkeinenFallvergessenwerden.InonPausegebenwir
auchdenaktuellaktivenScreenfrei,wenndieserbereitsgesetztist.DannspeichernwirdieSimulationdes
GameLoopinunseremSimulationsObjekt,fallsderGameLoopaktivist.WirspeicherndieSimulationhier,dadie
GeschichtemitdemBundlenichtimmeranschlgtunddieActivitynichtneuerstelltwird.Alsnchstesfehlennoch
diebeidenMethodendesGameListenerInterface:
...continued...
@Override
publicvoidsetup(GameActivityactivity,GL10gl)
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

86/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

{
if(simulation!=null)
{
screen=newGameLoop(gl,activity,simulation);
simulation=null;
Log.d("SpaceInvaders","resumingpreviousgame");
}
else
{
screen=newStartScreen(gl,activity);
Log.d("SpaceInvaders","startinganewgame");
}
}
...tobecontinued...
DieseMethodewirdaufgerufen,wenndieGLSurfaceViewerstelltwurde.Dementsprechendinitialisierenwirhier
denScreen,denwiranfangsverwendenwollen.WelchenScreenwirverwenden,machenwirdavonabhngig,ob
dasAttributsimulationgesetztistodernicht.FrdenFall,dassesgesetztwurde(berdasBundleinonCreate
oderinonPause),erstellenwireinenneuenGameLoop,dermitdieserSimulationarbeitet.Andernfallserstellen
wireinenStartScreen,derdenUserauffordert,denBildschirmzuberhren.DieSimulationberlebtdas
PausierenderApplikationohneProbleme,lediglichderRenderermussneuerstelltwerden,dabeimPausieren
smtlicheRessourcen,wieTextursundMeshes,vonOpenGLverworfenwerden.Kommenwirzumletzten
CodeteilindiesemTutorial:
...continued...
longstart=System.nanoTime();
intframes=0;
@Override
publicvoidmainLoopIteration(GameActivityactivity,GL10gl)
{

screen.update(activity);
screen.render(gl,activity);

if(screen.isDone())
{
screen.dispose();
Log.d("SpaceInvaders","switchingscreen:"+screen);
if(screeninstanceofStartScreen)
screen=newGameLoop(gl,activity);
else
if(screeninstanceofGameLoop)
screen=newGameOverScreen(gl,activity,((GameLoop)screen).simulation.score);
else
if(screeninstanceofGameOverScreen)
screen=newStartScreen(gl,activity);

Log.d("SpaceInvaders","switchedtoscreen:"+screen);
}
frames++;
if(System.nanoTime()start>1000000000)
{
Log.d("SpaceInvaders","fps:"+frames);
frames=0;
start=System.nanoTime();
}
}
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

87/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

ZuerstwirdderaktuelleScreenaktualisiertundgerendert.AlsnchstesFragenwir,obderScreenfertigist,damit
wirdennchstenScreenaktivierenknnen.IstderScreenfertig,gebenwirzuerstallseineRessourcenfrei.Als
nchstesprfenwir,welcherScreenaktivwar.ImFalledesStartScreens,erstellenwireinenneuenGameLoop,
imFalledesGameLoop,erzeugenwireinenneuenGameOverScreenundimFalledesGameOverScreen
startenwirdenStartScreenneu.AbschlieenderhhenwireinAttributderKlasseFrameCounternamensframes
undprfen,obeineSekundeseitderletztenZeitMessungvergangenist.IstdiesderFall,gebenwirdieAnzahl
derFramesausundsetzenStartzeitundFrameCounteraufneueWerte.DamitknnenwirunsereFramespro
Sekundemessenundausgeben.AlswirklichallerletztenPunktmssenwirunsnochanschauen,wiewirdie
ActivityimAndroidManifest.xmldefinieren:

<activityandroid:name=".spaceinvaders.SpaceInvaders"android:label="SpaceInvaders"android:launchMode="
<intentfilter>
<actionandroid:name="android.intent.action.MAIN"/>
<categoryandroid:name="android.intent.category.LAUNCHER"/>
</intentfilter>
</activity>

Hiersollteeskeinegroenberraschungengeben.AmwichtigstenistderlaunchMode,denwiraufsingleTask
setzen.DiesmussfrdieGLSurfaceViewsosein,daessonstzueinpaarkleinerenProblemenkommenkann.
Wirsindsomitfertigundhabenunserersteskleines3DAndroidSpielgeschrieben,komplettmitSoundundallem,
wasdazugehrt.

PerformanceTipps
PerformanceisteingroerPunktinAndroid.DawirmiteinerVMarbeiten,diedenCodenurinterpretiert,also
nichtdirektalsCPUAnweisungenausfhrt,mssenwiraufeinpaarDingeachten.HiereinekleineAuswahlder
wichtigstenTipps:
GarbageCollection:derGarbageCollectorinAndroidsDalvikVMisteinBiest.Ersorgtdafr,dassnichtmehr
bentigteObjekteihrenSpeicherbereichfreigeben.DiesesFreigebendauertzwischen100und300
Millisekunden,wasineinemSpielfatalist.BemerkbarmachtsichdasdurcheinunschnesStockendes
Spielablaufs.Esgiltalsozuverhindern,dauerndneueObjekteanzulegen.Ambesteninstanziertmanbereits
voraballes,was,manbrauchtundverwertetnichtmehrbentigteObjektewieder.Damitkannmandem
GarbageCollectoreinSchnippchenschlagen.InunseremKlonhabenwirdiesesZielweitestgehenderreicht.
LediglichdasInstanzierenvonSchssenundExplosionenwidersprichtdemeinwenigundfhrtnachlngerer
LaufzeitzueinemkleinenAussetzer.
FloatingPointvs.FixedPoint:bishererhltlicheAndroidGertebesitzenkeineFloatingPointUnit(im
GegensatzzumiPhone).BeiderVerwendungvonFliekommazahlenwerdendieseinSoftwareemuliert,was
einigesanPerformancekostenkann.IndenfrhenNeunzigernhattenauchvieleDesktopPCsnochkeine
FPU,wasSpieleentwicklermeistdazuzwang,aufsogenannteFixedPointArithmetikumzusteigen.Dasselbe
knntemanauchaufAndroidtun,ichrateaberdavonab,zumindest,wennmannichtdieNDKverwendetund
dasganzeinCschreibt.DasImplementierenvonFixedPointArithmetikinJavaunterDalvikistnurminimal
schnelleralsdieVerwendungvonFliekommazahlen.DaderCodeunleserlicherwird,rateichnurin
uerstenNotfllenzurUmstellungaufFixedPoint.
OpenGLLighting:WerdasSpielaufG1oderHeroHardwareausprobierthat,wirdfeststellen,dassesbei
vollerInvaderzahlca.30fpsschafft,beiwenigenInvadernaufbiszu60fpskommt.Grunddafrist,dasswirdie
Beleuchtungeingeschaltenhaben.InfrhenAndroidGertenwirddieLichtberechnungmeistvonderCPU
ausgefhrtundistdementsprechendlangsam.SchaltetmandieseimSpaceInvadersKlonab,bekommtman
konstante60fps.Hiermussmanentscheiden,obdieverlorenePerformancedieansehnlichereGrafikWertist.
OpenGLMipMapping:ZeichnetmanObjektemitTexturen,mussdieGPUdieverwendetenTeilederTexturen
auslesen.JegrerdabeidieTextur,destomehrLeseoperationenbrauchtdieTexturierung.MitHilfevon
MipMappingkannmandiesumeinigesVerbessern.BeimMipMappingwerdenvonderoriginalenTextur
http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

88/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

kleinereVersionenangefertigt.DieGPUprftbeieingeschaltetemMipMappingdann,wiegrodastexturierte
DreieckamBildschirmistundwhlteineTextur,dieeineentsprechendeTexturhat.EinkleinesObjektbedient
sichdabeikleinererTexturen,waswiederumdazufhrt,dasswenigerPixelderTexturgelesenwerden
mssen.IchempfehledaherstandardmigMipMappingfrdenMinificationsfilterderTexturenzuverwenden.
DieTexturKlassebietetdieentsprechendeFunktionalitt.

AbschlieendeWorte
NatrlichkonnteichihmRahmendiesesTutorialsnichtalleAspektederSpieleentwicklungundOptimierung
beschreiben.SpieleentwicklungisteinLearningByDoingProzess.DasLesenvonvielQuellcodewird
niemandemerspartbleiben,derwirklichguteSpieleschreibenwill.ImNetzgibteszuallenmglichenThemen
UnmengenanMaterial,dassichsoauchaufAndroidanwendenlsst.Abschlieendmchteichdahernoch
einigeQuellenauflisten,diesichdergeneigteLeserzuGemtefhrenkann.

AllgemeineSpieleentwicklung
http://www.gamedev.net/
Subbereiche.

SeitemitunzhligenArtikelnzumThemaSpieleentwicklungundallihrer

http://www.gamasutra.com/
http://flipcode.com/archives/

IndustrieorientierteSeite,ebenfallsmitsehrvielenArtikeln
ArchivderdereinstausgezeichnetenFlipcodeSeite.Unmengenanntzlichen

ArtikelnzuallenThemenbereichen.
http://www.rbgrn.net/content/54gettingstartedAndroidgamedevelopment SeitevonRobertGreen,Inhaber
vonBatteryPoweredGamesundMachervonLightRacerundWixxel.Hateinensehrinteressanten
EntwicklerblogmitvielenTippsundTricks.

OpenGLES
http://www.khronos.org/opengles/sdk/1.1/docs/man/
MethodenderKlassenGL10undGL11

OpenGLES1.1ManualPages.Referenzfralle

http://www.khronos.org/opengles/sdk/1.1/docs/man/
OpenGLES1.0.

OpenGLES1.0Specification.DieSpezifikationvon

http://iphonedevelopment.blogspot.com/2009/05/openglesfromgrounduptableof.html OpenGLESfrom
thegroundup.OpenGLESTutorialfrsiPhone,kannaberleichtaufAndroidumgelegtwerden.
http://wiki.delphigl.com/index.php/Tutorial

DelphiGLTutorialWiki.GroeSammlungandeutschenTutorials

frOpenGL.DasmeistedavonlsstsichauchmitOpenGLESumsetzen.
BeiFragen,Anregungen,KorrekturenundDrohungeneinfacheineEMailanbadlogicgamesatgmaildotcom
schreiben.Ichbinsehrantwortfreudig.
Macht'sgut!

Kategorien:Entwicklung

http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

89/90

2/26/2015

Spieleentwicklung101AndroidWikiAndroidPIT

Die Themen auf AndroidPIT


MAGAZINAktuelleNewsrundumAndroid
APPSAlleAppsfrDeinSmartphone
HARDWARENewsundSpezifikationen
FORUMSeiTeildergrtenAndroidCommunityEuropas

AndroidPIT International
Deutsch

Home

androidpit.de

Developer

Werben

Testberichtanfordern

Jobs

Staff

beruns

Impressum

AGB

Hilfe

Folgeuns:

http://www.androidpit.de/de/android/wiki/view/Spieleentwicklung_101#toc61

90/90

Das könnte Ihnen auch gefallen