Hallo miteinander
Ich m?chte mit einem kleinen Tutorial meine Programmiererfahrungen mit GLBasic weitergeben.
Es handelt sich um ein Grundger?st f?r einen Horizontal-Shooter das Routinen wie Intro, Men?, 2-Layerscrolling, Kollision oder Sound beinhaltet.
Falls ihr Fragen oder Anregungen habt dan postet die bitte nicht in diesem Thread sondern da: http://www.glbasic.com/forum/index.php?topic=2948.0 (http://www.glbasic.com/forum/index.php?topic=2948.0). Danke.
EDIT 27.07.2009: Habe nun einen einfachen Tilemap-Editor inkl. Quellcode im Anhang hinzugef?gt.
Damit man auch was lernen kann werde ich das Tutorial aufteilen.
Wie das Ergebnis des Tutorials etwa aussieht k?nnt Ihr hier runterladen.
Windows Version:http://www.zshare.net/download/5735249659b47d37/ (http://www.zshare.net/download/5735249659b47d37/)
GP2X Version:http://www.zshare.net/download/57352743c3fbf5c9/ (http://www.zshare.net/download/57352743c3fbf5c9/)
Steuerung->GP2X: Schuss=B
Steuerung->Win: Schuss=X
Im ersten Teil beginnen wir mit dem Programmrumpf:
//=============================================================================
// T Y P E S
//=============================================================================
//=============================================================================
// C O N S T A N T S
//=============================================================================
// application states
GLOBAL AS_INTRO% = 1
GLOBAL AS_MENU% = 2
GLOBAL AS_LEVEL00% = 10
GLOBAL AS_EXIT% = 255
// button map
GLOBAL BUTTON_RIGHT% = 205
GLOBAL BUTTON_LEFT% = 203
GLOBAL BUTTON_DOWN% = 208
GLOBAL BUTTON_UP% = 200
GLOBAL BUTTON_A% = 44
GLOBAL BUTTON_B% = 45
GLOBAL BUTTON_R% = 54
GLOBAL BUTTON_START% = 28
GLOBAL BUTTON_VOLI% = 201
GLOBAL BUTTON_VOLD% = 209
//=============================================================================
// V A R I A B L E S
//=============================================================================
// application
GLOBAL AppState% = AS_INTRO
LOCAL done = FALSE
//-----------------------------------------------------------------------------
// I N I T
//-----------------------------------------------------------------------------
DRAWRECT 0,0,320,240,0
SHOWSCREEN
DRAWRECT 0,0,320,240,0
SHOWSCREEN
//-----------------------------------------------------------------------------
// M A I N L O O P
//-----------------------------------------------------------------------------
WHILE done=FALSE
SELECT AppState
CASE AS_INTRO ; AppState=RunIntro()
CASE AS_MENU ; AppState=RunMenu()
CASE AS_LEVEL00 ; AppState=RunLevel00()
CASE AS_EXIT ; done=TRUE
DEFAULT ; done=TRUE
ENDSELECT
WEND
//-----------------------------------------------------------------------------
// D E I N I T
//-----------------------------------------------------------------------------
DRAWRECT 0,0,320,240,0
SHOWSCREEN
DRAWRECT 0,0,320,240,0
SHOWSCREEN
Als erstes machen wir Platz f?r Type-Definitionen welche wir sp?ter hier einsetzen werden.
Anschliessend kommen die Konstanten welche wir im Programm immer GROSS schreiben werden. Mit AS_INTRO etc. beschreiben wir den jeweiligen Status des Programms (AS = application state). Danach folgen die Konstanten f?r die Tastenbelegung welche wir sp?ter noch ben?tigen werden.
Jetzt folgen die Variablen, wobei wir globale mit grossem und lokale mit kleinem Anfangsbuchstaben schreiben. Die globale Variable AppState initialisieren wir mit AS_INTRO da wir unser Programm mit dem INTRO starten wollen.
Dann l?schen wir den Framebuffer.
Nun kommt die Hauptschleife des Programms:
Wenn innerhalb der Schleife
WHILE done=FALSE
...
WEND
der Wert der Variable done auf TRUE gesetzt wird, l?schen wir wieder den Framebuffer und das Proramm endet.
SELECT AppState
CASE AS_INTRO ; AppState=RunIntro()
CASE AS_MENU ; AppState=RunMenu()
CASE AS_LEVEL00 ; AppState=RunLevel00()
CASE AS_EXIT ; done=TRUE
DEFAULT ; done=TRUE
ENDSELECT
Hier ?berpr?fen wir das auszuf?hrende Programmmodul. Da wir den Wert von AppStateauf AS_INTRO gesetzt haben wird nat?rlich das Modul RunIntro() aufgerufen. Die verschiedenen Module sind Funktionen welche wiederum einen Wert zur?ckgeben, der dann sofort wieder in der Variable AppState gespeichert wird. Beim n?chsten Schleifendurchlauf wird diese erneut abgefragt.
So. Das w?rs mal f?r's erste. Im zweiten Teil werden wir uns mit dem Intro besch?ftigen.
[attachment deleted by admin]
Im zweiten Teil von unserem Tutorial erstellen wir nun ausf?hrbaren Code. Dazu findet Ihr im Anhang das dazugeh?rige GLBasic Projekt.
Nachdem Ihr die ZIP-Datei entpackt habt sollten folgende Dateien vorhanden sein:
[isdo] -> dieser Ordner beinhaltet die Daten welches unser ausf?hrbares Programm ben?tigen wird
[gfx] -> hier befinden sich die ben?tigten Grafikdateien
intro.png -> Grafik f?r das Intro
menu.png -> Grafik f?r das Startmen?
2D Shooter Tutorial Part 2.gbap -> unsere GLBasic Projektdatei
isdo.gbas -> Programmmodul mit der Hauptschleife
intro.gbas -> Programmmodul mit dem Code f?r den sichtbaren Programmstart
menu.gbas -> Programmmodul mit dem Code f?r ein kleines Startmen?
Als erstes kommentieren wir folgende Zeile im Mainloop (isdo.gbas) aus, da wir diese erst etwas sp?ter ben?tigen werden.
//CASE AS_LEVEL00 ; AppState=RunLevel00()
Anschliessend schauen wir uns mal die Datei intro.gbas etwas genauer an.
//-----------------------------------------------------------------------------
// I N I T I N T R O
//-----------------------------------------------------------------------------
LOADBMP "./isdo/gfx/intro.png"
Hier wird die Grafik f?r unser kleines Intro in den Hintergrundbuffer geladen, damit wir anschliessend die verschiedenen Sprites heraus
"grabben" k?nnen. Dies sieht dann so aus:
GRABSPRITE 1,0,0,306,52
GRABSPRITE 2,0,52,75,21
GRABSPRITE 3,75,52,18,17
GRABSPRITE 4,112,52,193,32
Wenn Ihr die Grafik oben in intro.png ?ndert, k?nnt Ihr z.B. euer eigenes Logo erscheinen lassen.
Nun lassen wir die einzelnen Grafikteile erscheinen.
// mountainsoft
FOR i=-0.01 TO -1 STEP -0.02
ALPHAMODE 0
DRAWRECT 0,0,320,240,RGB(255,255,255)
ALPHAMODE i
DRAWSPRITE 1,7,94
SHOWSCREEN
NEXT
In der FOR..NEXT-Schleife bestimmen wir in der Variablen i wie die Grafik eingeblendet wird. Das DRAWRECT ist n?tig damit die Grafik nicht
sich selbst ?berblendet. Nachdem die Grafik voll eingeblendet ist; let's have a break.
SLEEP 3000
Danach blenden wir die Grafik wieder aus.
FOR i=-1 TO 0 STEP 0.02
ALPHAMODE 0
DRAWRECT 0,0,320,240,RGB(255,255,255)
ALPHAMODE i
DRAWSPRITE 1,7,94
SHOWSCREEN
NEXT
Zum Schluss l?schen wir wieder den Hintergrundbuffer.
ALPHAMODE 0
BLACKSCREEN
Mit
RETURN AS_MENU
?bergeben wir die Kontrolle wieder der Hauptschleife in isdo.gbas. Was w?rde wohl passieren wenn wir hier anstatt AS_MENU die
Konstante AS_INTRO zur?ckgeben w?rden...?
Nun zum Startmenu. Dieser Code steht in der Datei menu.gbas. Hier ben?tigen wir zuerst einige lokale Konstanten und Variablen.
// constants
LOCAL MS_STARTGAME% = 0
LOCAL MS_OPTIONS% = 1
LOCAL MS_PASSWORD% = 2
LOCAL MS_EXIT% = 3
// variables
LOCAL menuState% = 0
LOCAL buttonUp% = FALSE
LOCAL buttonDown% = FALSE
LOCAL done% = FALSE
Die MS_ steht f?r MenuState und beschreibt den jeweiligen Men?punkt. buttonUp und buttonDown ben?tigen
wir f?r den jeweiligen Buttonstatus dies ist vor allem beim GP2X n?tig!
//-----------------------------------------------------------------------------
// I N I T M E N U
//-----------------------------------------------------------------------------
LOADBMP "./isdo/gfx/menu.png"
GRABSPRITE 0,0,0 ,128,14
GRABSPRITE 1,0,22,128,14
GRABSPRITE 2,0,44,128,14
GRABSPRITE 3,0,66,128,14
GRABSPRITE 4,0,80,128,80
Wie beim Intro laden wir zuerst die Men?grafik und "grabben" anschliessend die einzelnen Bilder heraus. Hier sind es die einzelnen Men?eintr?ge.
In der Schleife
WHILE done=FALSE
....
....
WEND
RETURN AS_EXIT
wird der Wert von done ?berpr?ft falls dieser auf TRUE gesetzt ist, wird das Programm beendet. Wenn man genau hinsieht ist
aber diese Abfrage nicht unbedingt n?tig. Wir haben aber so beim Programmieren die M?glichkeit zu Testzwecken irgendwo durch done=TRUE
abzubrechen.
Nun kommt die erste Eingabeabfrage. Dazu fragen wir ab ob beim GP2X der B-Button bzw. bei Windows die X-Taste gedr?ckt wird.
IF KEY(BUTTON_B)
....
....
ENDIF
Je nachdem wird dann zum jeweiligen Men?punkt gesprungen. Vorerst ist dies in unserem Programm nur EXIT.
SELECT menuState
CASE MS_EXIT
RETURN AS_EXIT
ENDSELECT
Auch hier wieder: Was w?rde wohl passieren wenn wir hier anstatt AS_EXIT die Konstante AS_INTRO zur?ckgeben w?rden...?
Bei der ?berpr?fung der Pfeiltasten bzw. Pad auf die Auf- und Abbewegung m?ssen wir darauf achten, dass erst wieder zum n?chsten Me?punkt
gesprungen wird, wenn wir die Taste wieder loslassen. Ansonsten brennt uns der Men?cursor wie ein wildgewordenes Pferd durch.
IF KEY(BUTTON_DOWN)
IF menuState<MS_EXIT
IF buttonDown=FALSE
INC menuState,1
buttonDown=TRUE
ENDIF
ENDIF
ELSE
buttonDown=FALSE
ENDIF
Mit
DRAWRECT 0,0,320,240,0x0
DRAWSPRITE 4,96,128
DRAWSPRITE menuState,96,menuState*22+128
SHOWSCREEN
machen wir dann das Ganze auch noch sichtbar.
Das war's f?r den zweiten Teil. Hat doch nicht weh getan, oder?
Falls Ihr Fragen zu diesem Tutorial habt, oder etwas nicht verstanden, dann nur zu, wenn ich helfen kann probiere ich zu helfen.
[attachment deleted by admin]
Im dritten Teil gehts nun ans Eingemachte. Wir behandeln die Levelschleife und nehmen gleich noch das Scrolling mit hinzu.
Da manche Sachen nun im ersten Moment vielleicht etwas komplex erscheinen, kann ich euch folgende Tips geben:
- macht eine Ausdruck vom Code. Ich z.B. kann auf Papier ?bersichtlicher lesen als auf dem Bildschirm
- kreuzt an, was ihr vom Code bereits versteht bzw. was ihr noch nicht versteht
- versucht mal den einen oder andern Wert zu ver?ndern um zu sehen, was das bewirkt.
Im Anhang findet ihr wieder den aktuellen Beispiel-Code. Neu hinzugekommen sind die beiden Dateien level00.gbas welche die Levelschleife beinhaltet, library.gbas die allgemeine Funktionen und Subroutinen beinhalten wird. Dann noch isdo/gfx/level00.png (enth?lt Hinter- und Vordergrundgrafik) sowie isdo/level00.dat wo die Indexlisten f?r die Tilemaps (siehe weiter unten) gespeichert sind.
So. In isdo.gbas "befreien" wir wieder unsere auskommentierte Zeile.
CASE AS_LEVEL00 ; AppState=RunLevel00()
Dann ?ndern wir folgende Zeile von AS_INTRO zu AS_MENU damit wir das Intro jeweils ?berspringen k?nnen.
GLOBAL AppState% = AS_MENU
Wir werden unsere Grafik nach jeder Anzeige wieder neu aufbauen und zwar in dieser Reihenfolge:
1. Hintergrundgrafik (LayerBG)
2. Vordergrundgrafik (LayerFG)
3. Statusanzeige oben (im Moment nur schwarzes gef?lltes Rechteck)
Dazu m?ssen wir einige neue TYPE's definieren.
TYPE TLayerFG
Map%[4096]
ENDTYPE
ist eine Index-Liste welche die Tilenummern der Vordergrundgrafik enth?lt.
TYPE TLayerBG
Map%[2048]
ENDTYPE
ist eine Index-Liste welche die Tilenummern der Hintergrundgrafik enth?lt.
Die Tilegrafik ist in isdo/gfx/level00.png gespeichert und besteht aus 16x16 Tiles von welchen jedes 16x16 Pixel gross ist.
Unsere Tilemaps sind folgendermassen aufgebaut:
Vordegrund Hintergrund
[00][16][..][4080] [00][16][..][2032]
[01][17][..][4081] [01][17][..][2033]
[02][18][..][4082] [02][18][..][2034]
[03][19][..][4083] [03][19][..][2035]
[..][..][..][ .. ] [..][..][..][ .. ]
[..][..][..][ .. ] [..][..][..][ .. ]
[15][31][..][4095] [15][31][..][2047]
Als Beispiel:
LayerFG[0]=255
Damit setzen wir das unterste rechte Tile an die oberste linken Stelle in unserer Tilemap (Vordergrund).
Falls ihr zu den Tilemaps noch weitere Infos ben?tigt, dann fragt einfach nochmals nach.
TYPE TEvent
Map%[4096]
ENDTYPE
Diese Index-Liste enth?lt besondere Tilemap-Attribute auf welche wir erst zu einem sp?teren Zeitpunkt eingehen werden. Da sie aber auch in der Datei level00.dat integriert sind m?ssen wir sie schon jetzt definieren.
Nun m?ssen diese 3 Listen noch als globale Variablen definiert werden.
//=============================================================================
// V A R I A B L E S
//=============================================================================
GLOBAL LayerFG AS TLayerFG
GLOBAL LayerBG AS TLayerBG
GLOBAL Event AS TEvent
Neu hinzugekommen ist noch die globale Variable AppTimer. Dies ist ein Z?hler, der bei jedem Bildaufbau um 1 erh?ht wird.
GLOBAL AppTimer% = 0
Dann gibt's noch die beiden globalen Variablen ScrollX und ScrollY die wir f?r das Scrolling ben?tigen.
GLOBAL ScrollX = 0
GLOBAL ScrollY = 0
Die globale Konstante COL_TRANSPARENT definiert unsere Transparent-Farbe. In unserem Programm w?re das RGB(248,0,248).
GLOBAL COL_TRANSPARENT = 0xF800F8
... so, nochmals kurz durchatmen... und weiter...
Schauen wir uns mal die Datei level00.gbas an.
Zuerst erstellen wir einige lokale Hilfsvariablen.
LOCAL done% = FALSE
LOCAL x%,y%
LOCAL ix%,id%,p%
LOCAL val%
Dann setzen wir die Scrollposition an den Anfangspunkt.
ScrollX=0
ScrollY=0
Mit den folgenden Zeilen laden wir die Leveldaten in unsere 3 Listen.
// load layers
OPENFILE(1,"./isdo/level00.dat",TRUE)
FOR p=0 TO 4095
READUWORD 1,val
LayerFG.Map[p]=val
NEXT
FOR p=0 TO 2047
READUWORD 1,val
LayerBG.Map[p]=val
NEXT
FOR p=0 TO 4095
READUWORD 1,val
Event.Map[p]=val
NEXT
CLOSEFILE 1
Um die Leveldaten zu erstellen kann man sich entweder selbst einen Level-Editor basteln oder erstellt die Daten notfalls mit einem HEX-Editor.
Jetzt setzen wir die allgemeine Transparenzfarbe auf unseren vorgegebenen Wert.
SETTRANSPARENCY COL_TRANSPARENT
Mit
LOADBMP ""
stellen wir sicher das der Hintergrundbuffer leer ist. Sonst kann sich die neue Grafik mit einer noch eventuell vorhandenen ?berlagern.
Nun Laden wir unsere Tilegrafik und weisen jedem der 16x16 Pixel grossen Tiles eine eigene SpriteID zu. Dabei beginnen wir mit der Nummer 256 damit wir nach oben noch etwas Luft haben, falls wir sp?ter noch mehr Tiles ben?tigen w?rden.
LOADSPRITE "./isdo/gfx/level00.png",0
id=256
FOR y=0 TO 15
FOR x=0 TO 15
DRAWRECT 0,0,16,16,COL_TRANSPARENT
DRAWSPRITE 0,-x*16,-y*16
GRABSPRITE id,0,0,16,16
INC id,1
NEXT
NEXT
und l?schen wieder das tempor?re Sprite.
LOADSPRITE "",0
Ab jetzt folgt die Levelschleife. Das heisst, dass wir 60x pro Sekunde das Bild jedes Mal im Hintergrund aufbauen und anschliessend auf dem Bildschirm anzeigen lassen.
//-----------------------------------------------------------------------------
// M A I N L O O P
//-----------------------------------------------------------------------------
WHILE done=FALSE
IF KEY(BUTTON_START) THEN RETURN AS_MENU
INC AppTimer,1
INC ScrollX,0.5
GOSUB KeyHANDLER
GOSUB LayerBGDRAW
GOSUB LayerFGDRAW
GOSUB StatusDRAW
SHOWSCREEN
WEND
Gehen wir das Ganze Schritt f?r Schritt durch.
Zuerst programmieren wir uns einen Notausgang damit wir z.B. beim GP2X das Ger?t nicht immer ein- und ausschalten m?ssen. Also gelangen wir durch dr?cken des START-Buttons (GP2X) bzw. Eingabetaste (WIN) zur?ck zum Startmenu.
IF KEY(BUTTON_START) THEN RETURN AS_MENU
Bei jedem Bildaufbau erh?hen wir einen allgemeinen Z?hler jeweils um 1.
INC AppTimer,1
Danach legen wir fest um wieviele Pixel pro Bildaufbau sich der Vordergrund zur Seite bewegt. Ihr k?nnt ja Mal versuchen diesen Wert zu erh?hen oder zu senken.
INC ScrollX,0.5
Anschliessend lassen wir den KeyHANDLER in der library.gbas seine Arbeit verrichten. Dieser hat die Aufgebe alle Usereingaben zu verwalten.
//=============================================================================
SUB KeyHANDLER:
//=============================================================================
// debug options
IF KEY(2) THEN LIMITFPS 2
IF KEY(3) THEN LIMITFPS 30
IF KEY(4) THEN LIMITFPS 60
IF KEY(5) THEN LIMITFPS 80
ENDSUB // KeyHANDLER
Diese 4 Zeilen sind nur f?r Testzwecke gedacht. Durch die Tasten "1"-"4" kann die Bildwiederholfrequenz manuell ge?ndert werden. Das hat den Nutzen, dass man zum Beispiel genauer sehen kann, ob etwaige Kollisionen auch m?glichst exakt ausgef?hrt werden.
Mit
GOSUB LayerBGDRAW
GOSUB LayerFGDRAW
rufen wir 2 Unterprogramme in der library.gbas auf, die unsere beiden Layer zeichnen.
Wir berechnen die Position in der LayerBG.Map von welchem wir das erste Tile oben links zeichnen.
// draw background layer
p=INTEGER(ScrollX / 16 /2)*16
Dann berechnen wir die genaue Startposition in Pixeln, wo die erste Spalte gezeichnet werden soll.
dx=0-(bAND(ScrollX,31) /2)
Im Gegensatz zum Vodergrund m?ssen wir beim Hintergrund jeweils noch durch 2 teilen damit wir einen 2-Ebenen Effekt erzielen.
Nun definieren wir wieviele Spalten gezeichnet werden m?ssen.
FOR x=0 TO 20
Da unsere Ausgabe eine Aufl?sung von 320x240 Pixeln hat ergibt sich dieser Wert durch: 320/16=20. Da aber je nach Scrollposition schon vor dem sichtbaren Ausschnitt gezeichnet wird, w?rde man auf der rechten Seite den Bidaufbau sehen, was nat?rlich unsch?n ist. Also zeichnen wir noch eine Spalte mehr.
dy=32
Das ist die genaue Startposition in Pixeln, wo die erste Zeile gezeichnet werden soll. (Statusanzeige 32 Pixel hoch)
Wir zeichnen 13 Zeilen ((240-32)/16=13.
FOR y=0 TO 12
Hier m?ssen wir keine Zeile mehr dazu addieren, weil wir ja nicht nach oben oder unten scrollen.
Nun zeichnen wir das errechnete Tile an den pixelgenauen Zielort.
DRAWSPRITE LayerBG.Map[p],dx,dy
Jetzt gehen wir Zeile f?r Zeile nach unten bis wir am unteren Rand der Bildausgabe angekommen sind.
INC dy,16
INC p,1
NEXT
Danach geht's zur n?chsten Spalte.
INC dx,16
INC p,3
NEXT
Da unsere Layermap eine vertikale Dimension von 16 Zeilen hat, wir aber nur 13 Zeilen zeichnen, m?ssen wir noch 3 Listenpositionen ?berspringen.
Bleibt noch die Statusanzeige.
GOSUB StatusDRAW
Da machen wir's uns im Moment noch einfach und zeichnen nur ein schwarz gef?lltes Rechteck und rufen das entsprechende Unterprogramm in userer Library auf.
DRAWRECT 0,0,320,32,RGB(0,0,0)
Ich hoffe ich hab's einigermassen verst?ndlich erkl?rt. Ansonsten einfach nachhaken.
Das n?chste Mal k?mmern wir uns um die Steuerung unseres Helden.
[attachment deleted by admin]
Im 4. Teil werden wir unserem Helden Leben einhauchen und ihm beibringen, wie man schiesst.
Im angeh?ngten Beispielcode befinden sich neu die Grafikdaten f?r unser Spielersprite und die Sch?sse isdo/gfx/player.png.
In isdo.gbas definieren wir zuerst wieder einige neue Types, Konstanten und Variablen:
TYPE TPlayer
PosX = 64
PosY = 64
Timer% = 0
Dir% = 0
Speed = 1.0
ENDTYPE
PosX und PosY ist die Position, wo unser Spieler-Sprite angezeigt wird. Timer ist ein Z?hler der bei jedem Bildaufbau um 1 erh?ht wird. Dir ist die Bewegungsrichtung und Speed bestimmt die Bewegungsgeschwindigkeit.
TYPE TShot
State%[8]
PosX[8]
PosY[8]
Dir%[8]
Count% = 8
ENDTYPE
Hier definieren wir einen Typ f?r 8 Sch?sse. D.h. wir werden maximal 8 Sch?sse gleichzeitig anzeigen k?nnen.
State beschreibt den aktuellen Schussstatus, PosX und PosY die Position und Dir die Schussrichtung. Count ist die Anzahl der maximalen Sch?sse.
// sprites
GLOBAL SPR_PLAYER% = 3
GLOBAL SPR_SHOT% = 18
Damit wir uns beim Spritezeichnen nicht jedes Mal die entsprechenden Nummern merken m?ssen, bezeichnen wir diese mit einer ?bersichtlicheren Konstante.
// directions
GLOBAL DIR_UP% = 16
GLOBAL DIR_RIGHTUP% = 18
GLOBAL DIR_RIGTH% = 2
GLOBAL DIR_RIGHTDOWN% = 6
GLOBAL DIR_DOWN% = 4
GLOBAL DIR_LEFTDOWN% = 12
GLOBAL DIR_LEFT% = 8
GLOBAL DIR_LEFTUP% = 24
GLOBAL DIR_NONE% = 0
Das sind Richtungsangaben welche wir f?r das Spielersprite und die Sch?sse benutzen werden.
Dann m?ssen wir noch die Variablen definieren:
GLOBAL Player AS TPlayer
GLOBAL Shot AS TShot
und dann noch
GLOBAL ButtonB = TRUE
diese liefert uns den Status des B-Buttons bzw der "x"-Taste.
In level00.gbas m?ssen wir noch einige der neuen Variablen initialisieren.
Player.PosX=8
Player.PosY=64
SPR_SHOT=18
// make player sprites
LOADSPRITE "./isdo/gfx/player.png",0
id=1
FOR x=0 TO 3
DRAWRECT 0,0,16,32,COL_TRANSPARENT
DRAWSPRITE 0,-x*16,0
GRABSPRITE id,0,0,16,32
INC id,1
NEXT
Hier laden wir die Grafikdaten f?r unseren Helden. Nr.1+2 sind die Animationsphasen, wenn der Spieler geradeaus fliegt. Nr.3+4 wenn er sich nach oben rechts bewegt. Ihr k?nnt das Ganze ja noch f?r die restlichen Richtungen erweitern.
// make shots
DRAWRECT 0,0,128,32,COL_TRANSPARENT
DRAWSPRITE 0,0,-64
GRABSPRITE 16,0,0,8,8
GRABSPRITE 17,8,0,8,8
GRABSPRITE 18,16,0,8,8
GRABSPRITE 19,24,0,8,8
GRABSPRITE 20,32,0,8,8
GRABSPRITE 24,16,16,16,16
Mit diesen Zeilen erstellen wir die Sprites f?r die Sch?sse.
In der Levelschleife m?ssen wir zuerst den Status des B-Buttons auf TRUE setzen.
ButtonB=TRUE
Das ist wichtig da ansonsten folgendes passiert:
Wir befinden uns ja zuerst im Startmenu. Mit dem B-Button best?tigen wir dann unsere Auswahl. Beginnt nun der Level und der B-Button ist noch gedr?ckt, wird gleich scharf geschossen. Durch setzen von TRUE wird jedoch zuerst gewartet bis der Button losgelassen wird.
Den Z?hler f?r die Sprite-Animation m?ssen wir auch noch erh?hen.
INC Player.Timer,1
GOSUB ShotHANDLER
GOSUB PlayerHANDLER
Mit diesen zwei Unterprogrammen managen wir die Sch?sse und die Heldenanzeige.
Wechseln wir nun zu unserer Library in library.gbas.
Zuerst betrachten wir den PlayerHANDLER.
//=============================================================================
SUB PlayerHANDLER:
//=============================================================================
DRAWSPRITE SPR_PLAYER+(INTEGER((bAND(Player.Timer,3)/2))),Player.PosX,Player.PosY
ENDSUB // PlayerHANDLER
Diseser ist zust?ndig f?r das jeweilige Anzeigen des Spieler-Sprites an der aktuellen Position.
INTEGER((bAND(Player.Timer,3)/2))
gibt je nach Player.Timer 0 oder 1 zur?ck, was der jeweiligen Animationsphase entspricht. In unserem Programm haben wir nur deren 2.
Im KeyHANDLER gibt es auch ?nderungen.
Zuerst m?ssen wir lokale Variablen definieren
LOCAL kr%,kl%,kd%,ku%
diese speichern, ob die jeweilige Richtungstaste gedr?ck wird oder nicht. Dazu erstmal initialisieren.
kr=0;kl=0;kd=0;ku=0
Nun pr?fen wir ob in die jeweilige Richtung gedr?ckt wrid. Falls ja schreiben wir den Wert in die lokale Variable. Gleichzeitig ?ndern wir die aktuelle Position des Spieler-Sprites.
IF KEY(BUTTON_RIGHT)
kr=2
INC Player.PosX,Player.Speed
ENDIF
IF KEY(BUTTON_LEFT)
kl=8
DEC Player.PosX,Player.Speed
ENDIF
IF KEY(BUTTON_DOWN)
kd=4
INC Player.PosY,Player.Speed
ENDIF
IF KEY(BUTTON_UP)
ku=16
DEC Player.PosY,Player.Speed
ENDIF
Anschliessend addieren wir die einzelnen Variablen zu einer Bit-Maske. D.h. wir haben die bin?re Zeichenfolge "00000". Wenn nach rechts gesteuert wird sieht die Maske so aus "00010", wenn nach oben und rechts gesteuert wird so "10010". So ist es auch m?glich abzufragen, ob mehrere Tasten gedr?ckt werden. Die Maske speichern wir in Player.Dir.
Player.Dir=kr+kl+kd+ku
Jetzt kommt etwas un?bliches. SPR_PLAYER ist ja eigentlich eine KONSTANTE SpriteID. Wir benutzen sie hier jedoch ausnahmsweise als Variable und k?nnen somit bei einer Richtungs?nderung mit nur einem Befehl das Basissprite f?r die Animation ver?ndern. Ich hab's hier mal als Beispiel f?r oben rechts gemacht.
SPR_PLAYER=1
SELECT Player.Dir
CASE DIR_RIGHTUP
SPR_PLAYER=3
ENDSELECT
Nun bringen wir unserem Held das Schiessen bei.
// player shoot
IF KEY(BUTTON_B)
IF ButtonB=FALSE
ButtonB=TRUE
GOSUB ShotADD
ENDIF
ELSE
ButtonB=FALSE
ENDIF
Wenn der B_Button oder die "x"-Taste gedr?ckt wird springen wir zur Routine ShootADD welche einen Schuss zu unserer Szene hinzuf?gt.
Zuerst definieren wir einen lokale Z?hlvariable und initialisieren diese mit 0.
LOCAL ix%
ix=0
Dann durchlaufen wir 8x eine Schleife welche f?r jeden m?glichen Schuss eine Pr?fung durchf?hrt.
Loop1:
....
....
GOTO Loop1
ENDIF
Wir pr?fen ob der lokale Z?hler beim letzten Schuss angekommen ist.
IF ix=Shot.Count THEN RETURN
Falls ja, wird kein weiterer Schuss hinzugef?gt.
Anschliessend schauen wir ob der aktuelle Schusseintrag schon belegt ist.
IF Shot.State[ix]=0
Dann ?bergeben wir die Rchtungsmaske und die Startposition vom neuen Schuss.
Shot.Dir[ix]=Player.Dir
Shot.PosX[ix]=Player.PosX+8
Shot.PosY[ix]=Player.PosY+10
Shot.State[ix]=1
Shot.State ist eine Statusvariable die uns die aktuellen Phase des Schusses widergibt.
Im ShootHANDLER definieren wir einige lokale Variablen und durchlaufen dann in einer Schleife wieder alle 8 m?glichen Sch?sse.
//=============================================================================
SUB ShotHANDLER:
//=============================================================================
LOCAL ix%,ev%,x%,y%
FOR ix=0 TO Shot.Count-1
IF Shot.State[ix]
....
....
ENDIF
NEXT
Wir berechnen nun die Position des aktuellen Schusses in unserer Eventmap und lesen deren Wert aus.
x=INTEGER(ScrollX+Shot.PosX[ix]) / 16
y=INTEGER(ScrollY+Shot.PosY[ix]-32) / 16
ev=Event.Map[x*16+y]
Wie wir ja bereits wissen, besteht die Eventmap aus speziellen Attributen. Solch ein Attribut kann zum Beispiel sein, ob an derselben Stelle in der Vordergrundmap sich ein Hindernis (Wand oder ?hnliches) befindet. Praktisch bedeutet dies, dass wir den Schuss beenden sobald er auf eine Wand trifft und nicht einfach durch sie hindurchfliegt.
Trifft der Schuss auf eine Wand geben wir die aktuelle Schussm?glichkeit in der Indexliste mit
IF ev>0 THEN Shot.State[ix]=0
wieder frei.
Je nach Flugrichtung ver?ndern wir die Schussposition, zeichnen das entsprechende Sprite und ?berpr?fen noch, ob der Schuss am Bildrand angekommen ist.
CASE DIR_RIGHTUP
INC Shot.PosX[ix],2
DEC Shot.PosY[ix],2
DRAWSPRITE SPR_SHOT-1,Shot.PosX[ix],Shot.PosY[ix]
IF Shot.PosX[ix]>320
Shot.State[ix]=0
ENDIF
Im nachsten Teil behandeln wir dann die Gegner.
[attachment deleted by admin]
Im 5. Teil kommen wir nun zu dem wichtigsten Bestandteil dieses Tutorials, den Objekten. Wenn ihr diesen Teil verstanden habt, werdet ihr sehen, dass die Gameprogrammierung eigentlich gar keine allzu grosse Hexerei ist.
Im angeh?ngten Beispielcode befinden sich neu die Grafikdaten
isdo/gfx/player.png f?r unsere Objekte und
enemies.gbas.
Dies k?nnen z.B. sein:
Gegner
Powerups
Effekte
gegnerische Sch?sse
In
isdo.gbas f?gen wir einen neuen Typ hinzu:
TYPE TObject
State%[32]
Typ%[32]
PosX[32]
PosY[32]
Timer%[32]
Count% = 32
ENDTYPE
damit definieren wir einen Typ f?r 32 Objekte. D.h. wir werden maximal 32 Objekte gleichzeitig anzeigen k?nnen.
State beschreibt den aktuellen Objektstatus,
PosX und
PosY die Position und
Timer ist wieder ein Animationsz?hler.
Count ist die Anzahl der maximalen Objekte.
Weiter unten erstellen wir die dazugeh?rige Variable.
GLOBAL Object AS TObject
Dann erweitern wir unsere SpriteID's noch um
GLOBAL SPR_ENEMY1% = 512
GLOBAL SPR_ENEMY2% = 514
Da wir bei einem Spiel viele verschiedene Objekte verwenden, ist es von Vorteil, jedem Objekt eine Konstante zuzuweisen.
// object ID's
GLOBAL ID_ENEMY1% = 0x11
GLOBAL ID_ENEMY2% = 0x12
Wir definieren einmal 2 Gegner:
ENEMY1 gelber Gegner
ENEMY2 roter Gegner
In
level00.gbas laden wir unsere Objektgrafik und "grabben" die einzelnen Sprites heraus.
// make objects
LOADSPRITE "./isdo/gfx/object.png",0
id=512
FOR y=0 TO 15
FOR x=0 TO 15
DRAWRECT 0,0,16,16,COL_TRANSPARENT
DRAWSPRITE 0,-x*16,-y*16
GRABSPRITE id,0,0,16,16
INC id,1
NEXT
NEXT
Dann erweitern wir unsere Levelschleife mit dem
ObjectHANDLER
GOSUB ObjectHANDLER
den wir wiederum in der
library.gbas finden.
//=============================================================================
SUB ObjectHANDLER:
//=============================================================================
LOCAL ix%
FOR ix=0 TO Object.Count-1
IF Object.State[ix]
SELECT Object.Typ[ix]
CASE ID_ENEMY1
EnemyScripts(ix)
CASE ID_ENEMY2
EnemyScripts(ix)
ENDSELECT
ENDIF
NEXT
ENDSUB // ObjectHANDLER
Dieser kurze Programmabschnitt ist tats?chlich ein Hauptbestandteil von unserem Programm!
Wie bei den Sch?ssen durchlaufen wir eine Liste und pr?fen als Esrtes, ob der entsprechende Listeneintrag noch frei ist. Falls ja, schauen wir um was f?r einen Objekttyp es sich handelt. Je nach Objektyp verweisen wir dann auf ein Objektscript, wo dann das Objekt separat behandelt wird (Verhalten, Bewegung, Kollision etc.).
Beim Aufruf der Funktion
EnemyScripts ?bergeben wir mit
ix den aktuellen Objektlistenindex und somit die Kontrolle ?ber das entsprechende Objekt.
Zu diesem Zeitpunkt sind schone mehrere Objekteintr?ge vorhanden, da wir mit
IF ScrollX=128 THEN ObjectADD(ID_ENEMY1,320,120)
IF ScrollX=160 THEN ObjectADD(ID_ENEMY2,320,120)
in
level00.gbas bereits Objekte zu unserer Szene hinzugef?gt haben. Mit diesem Befehl ?bergeben wir den Typ und die Position eines neuen Objekts an den
ObjectHANDLER.
//=============================================================================
FUNCTION ObjectADD: typ%,posX,posY
//=============================================================================
LOCAL ix%
ix=0
Loop1:
IF ix=Object.Count THEN RETURN
IF Object.State[ix]=0
Object.State[ix]=1
Object.Typ[ix]=typ
Object.PosX[ix]=posX
Object.PosY[ix]=posY
ELSE
INC ix,1
GOTO Loop1
ENDIF
RETURN
ENDFUNCTION // ObjectADD
Schauen wir uns ein Script nun etwas genauer an. Die wichtigste Variable ist
Object.State. Sie beschreibt die Phase in der sich das Objekt befindet. In unserem Beispiel sind das 3 Phasen. In jeder dieser Phasen ver?ndern wir z.B. die Position. Wenn wir die letzte Phase durchlaufen haben, geben wir das Objekt wieder frei.
CASE 1
IF Object.PosX[ix] > 160
DEC Object.PosX[ix],2
ELSE
Object.State[ix]=2
ENDIF
CASE 2
IF Object.PosX[ix] > 64
DEC Object.PosX[ix],2
DEC Object.PosY[ix],1
ELSE
Object.State[ix]=3
ENDIF
CASE 3
IF Object.PosX[ix] > -16
DEC Object.PosX[ix],2
ELSE
Object.State[ix]=0
ENDIF
Nun brauchen wir das Objekt nur noch zu zeichnen
DRAWSPRITE SPR_ENEMY1+frame,Object.PosX[ix],Object.PosY[ix]
[attachment deleted by admin]
Im zweitletzten Teil von diesem Tutorial werden wir zuerst das Hinzuf?gen von Objekten automatisieren. Danach befassen wir uns mit der Kollisionsabfrage.
Beim letzten Teil haben wir ja mit
IF ScrollX=128 THEN ObjectADD(ID_ENEMY1,320,120)
IF ScrollX=160 THEN ObjectADD(ID_ENEMY2,320,120)
in
level00.gbas die Objekte manuell hinzugef?gt. In einem fertigen Spiel ben?tigt man jedoch viel mehr und es w?re sehr umst?ndlich, diese alle einzeln aufzurufen. Deshelb lesen wir die ben?tigten Daten aus einer vordefinierten Datei
level00.evt. Dazu m?ssen wir unseren bereits vorhandenen Event-Type erweitern.
IX% = 0
ID%[256]
Pos%[256]
X%[256]
Y%[256]
Dabei ist
IX ein Zeiger auf den aktuellen Listeneintrag,
ID ist die ObjektID,
Pos ist die Scrollposition wann das Objekt erscheinen soll und
X,Y ist die Position an der das Objekt erscheinen soll.
Mit
// init events
OPENFILE(1,"./isdo/level00.evt",TRUE)
FOR p=0 TO 254
READUWORD 1,val
Event.Pos[p]=val
NEXT
FOR p=0 TO 254
READUWORD 1,val
Event.ID[p]=val
NEXT
FOR p=0 TO 254
READUWORD 1,val
Event.X[p]=val
NEXT
FOR p=0 TO 254
READUWORD 1,val
Event.Y[p]=val
NEXT
in
level00.gbas laden wir diese Daten in unsere Eventliste. Die Abfrage wann ein Objekt erscheinen soll machen wir im Unterprogramm
EventHANDLER in
library.gbas.
//=============================================================================
SUB EventHANDLER:
//=============================================================================
LOCAL ix%,typ%
ix=Event.IX
IF ScrollX=Event.Pos[ix]
SELECT Event.ID[ix]
CASE ID_ENEMY1 TO ID_ENEMY2
ObjectADD(Event.ID[ix],Event.X[ix],Event.Y[ix])
ENDSELECT
INC Event.IX,1
ENDIF
ENDSUB // EventHANDLER
Erst fragen wir den aktuellen Index der Eventliste ab, dann ?berpr?fen wir den Typ und f?gen der Szene das entsprechende Objekt hinzu. Der Rest geht dann wie von alleine.
So, und nun die Kollisionsabfrage. Da gibt es verschiedene M?glichkeiten in GLBasic.
BOXCOLL ?berpr?ft, ob sich 2 Rechtecke ?berlappen
SPRCOLL ?berpr?ft, ob sich einzelne Pixel von 2 Sprites ?berlappen
Wir benutzen eine Routine, die auf BOXCOLL basiert. Dazu definieren wir f?r den Spieler, die Sch?sse und sowie die Objekte eine sogenannte Kollisionsbox. Diese Box kann auch kleiner oder gr?sser als das Sprite sein.
Also f?gen wir f?r die verschiedenen Types jeweils
CollTop
CollBottom
CollLeft
CollRight
hinzu.
Die Gr?sse der Box bestimmen wir bei den Objekten mit der Funktion
//=============================================================================
FUNCTION SetCollBox: ix%,left,top,right,bottom
//=============================================================================
Object.CollLeft[ix]=Object.PosX[ix]+left
Object.CollTop[ix]=Object.PosY[ix]+top
Object.CollRight[ix]=Object.PosX[ix]+right
Object.CollBottom[ix]=Object.PosY[ix]+bottom
Zu Testzwecken k?nnt ihr ja mal die folgenden 3 Zeilen auskommentieren und etwas herumexperimentieren. Dann werden die Boxen halbtransparent sichtbar.
//ALPHAMODE 0.5
//DRAWRECT Object.CollLeft[ix],Object.CollTop[ix],Object.PosX[ix]+right-Object.CollLeft[ix],Object.PosY[ix]+bottom-Object.CollTop[ix],0xFFFFFF
//ALPHAMODE 0.0
F?r den Spieler erstellen wir eine separate Box.
//=============================================================================
SUB PlayerHANDLER:
//=============================================================================
Player.CollTop=Player.PosY
Player.CollBottom=Player.PosY+31
Player.CollLeft=Player.PosX
Player.CollRight=Player.PosX+16
DRAWSPRITE SPR_PLAYER+(INTEGER((bAND(Player.Timer,3)/2))),Player.PosX,Player.PosY
ENDSUB // PlayerHANDLER
ebenso f?r die 8 Sch?sse im
ShotHANDLER.
Shot.CollTop [ix]=Shot.PosY[ix]
Shot.CollBottom[ix]=Shot.PosY[ix]+6
Shot.CollLeft [ix]=Shot.PosX[ix]
Shot.CollRight [ix]=Shot.PosX[ix]+6
Wichtig ist, dass die Zuweisung der Box bei jeder Positions?nderung gemacht wird.
Nun kommt die Abfrage ob ein Objekt mit dem Spieler oder mit einem Schuss kollidiert.
//=============================================================================
FUNCTION CheckCollPlayer: ix%
//=============================================================================
IF Player.CollTop>Object.CollBottom[ix] OR Player.CollBottom<Object.CollTop[ix] OR Player.CollLeft>Object.CollRight[ix] OR Player.CollRight<Object.CollLeft[ix]
RETURN COLL_NONE
ELSE
RETURN COLL_PLAYER
ENDIF
RETURN COLL_NONE
Mit
ix ?bergeben wir den Index f?r das Objekt mit welchem wir pr?fen. Falls das Objekt den Spieler ber?hrt geben wir den wert COLL_PLAYER zur?ck, ansonsten COLL_NONE.
Dasselbe gilt f?r die Sch?sse.
//=============================================================================
FUNCTION CheckCollShot: ix%
//=============================================================================
LOCAL i%
FOR i=0 TO Shot.Count-1
IF Shot.State[i]
IF Shot.CollTop[i]>Object.CollBottom[ix] OR Shot.CollBottom[i]<Object.CollTop[ix] OR Shot.CollLeft[i]>Object.CollRight[ix] OR Shot.CollRight[i]<Object.CollLeft[ix]
RETURN COLL_NONE
ELSE
Shot.State[i]=0
RETURN COLL_SHOT
ENDIF
ENDIF
NEXT
RETURN COLL_NONE
ENDFUNCTION // CheckCollShot
Hier geben wir jedoch den Wert COLL_SHOT zur?ck und l?schen gleichzeitig noch den aktuellen Schusseintrag.
Den Befehl zur Kollisionsabfrage geben wir f?r jedes Objekt in den Scripts. Als Beispiel nehmen wir einmal ID_ENEMY1 in
enemies.gbas:
SetCollBox(ix,0,0,15,15)
legt die Gr?sse der Kollisionsbox fest.
Dann ?berpr?fen wir ob wir (das ENEMY1) von einem Schuss getroffen wurden.
IF CheckCollShot(ix)=COLL_SHOT
Object.State[ix]=0
EffectADD(ID_EFFECT1,Object.PosX[ix],Object.PosY[ix])
ENDIF
Falls ja, l?schen wir das Objekt aus der Liste und f?gen noch einen Effekt (Explosion) hinzu.
Effekte sind genau gleich aufgebaut wie Objekte. Sie ben?tigen aber keine Kollisionsbox. Der Grund dass wir hier zwischen Effekten und Objekten einen Unterschied machen ist
GOSUB KeyHANDLER
GOSUB LayerBGDRAW
GOSUB ShotHANDLER
GOSUB EventHANDLER
GOSUB ObjectHANDLER
GOSUB PlayerHANDLER
GOSUB LayerFGDRAW
GOSUB EffectHANDLER
GOSUB StatusDRAW
dass wir die Effekte NACH den Objekten Zeichnen. Sonst s?he man unter Umst?nden ja die Effekte gar nicht mehr.
[attachment deleted by admin]
Im letzten Teil schauen wir, wie ein Power-Up hinzugef?gt werden kann und wie dadurch die Schusskraft ver?ndert wird.
Und zum Abschluss f?gen wir noch eine Hintergrundmusik sowie einige Soundeffekte dazu. Wie immer findet ihr im Anhang den Beispiel-Code.
Wenn ihr den letzten Teil durchgearbeitet habt, wisst ihr ja, dass wir 2 verschiedene Gegnertypen haben: ENEMY1 und ENEMY2.
Im Programm selber kann man ENEMY2 als ROTEN Gegner erkennen. Nun soll dieser, falls er getroffen wird, ein Power-Up hinterlassen.
Dazu m?ssen wir nur in unserm EnemySCRIPT ein neues Objekt hinzuf?gen.
ObjectADD(ID_POWERUP,Object.PosX[ix],Object.PosY[ix])
Das Script f?r das Power-Up findet ihr wiederum in der Datei items.gbas.
CASE ID_POWERUP
SELECT Object.State[ix]
CASE 1
IF Object.PosX[ix]>-16
DEC Object.PosX[ix],0.5
ELSE
Object.State[ix]=0
ENDIF
ENDSELECT
DRAWSPRITE SPR_POWERUP,Object.PosX[ix],Object.PosY[ix]
SetCollBox(ix,0,0,15,15)
IF CheckCollPlayer(ix)=COLL_PLAYER
Object.State[ix]=0
PLAYSOUND(SFX_EXTRA,GetPan(Object.PosX[ix]),0.7)
SPR_SHOT=24
ENDIF
ENDSELECT
Dieses Item ?berpr?fen wir nun auf die Kollision mit dem Spieler und falls eine stattfindet l?schen wir das Item in der Objektliste und ver?ndern unseren Schuss.
Nun Zum Thema Sound. Das is unter GLBasic eine einfache Sache.
In der Datei level000.gbas laden wir unsere Soundeffekte und weisen ihnen gleich eine Konstante zu.
LOADSOUND "./isdo/sfx/shot.wav",SFX_SHOT,4
LOADSOUND "./isdo/sfx/explosion.wav",SFX_EXPLOSION,4
LOADSOUND "./isdo/sfx/extra.wav",SFX_EXTRA,4
Dann laden wir unsere Hintergrundmusik, stellen die Lautst?rke ein und lassen sie laufen.
PLAYMUSIC "./isdo/sfx/music.wav",TRUE
MUSICVOLUME 0.9
Eine Soundeffekt wird in GLBasic mit dem Befehl PLAYSOUND abgespielt.
Wenn Ihr zum Beispiel das ItemSCRIPT beachtet wird mit
PLAYSOUND(SFX_EXTRA,GetPan(Object.PosX[ix]),0.7)
Der Sound mit der ID SFX_EXTRA mit der Lautst?rke 0.7 abgespielt.
Die Funktion GetPan in unserer Library bewirkt, dass der Soundeffekt je nach Position des Objekts eher Links oder Rechts ert?nt.
So. Nun hoffe ich, dass ich dem einen oder anderen mit diesem Tutorial etwas geholfen habe. Und wie gesagt wenn ich irgendwo helfen kann, versuch ich's.
Als kleinen Bonus k?nnt Ihr hier isdo_090406.zip - 11.13MB (http://www.zshare.net/download/5828251862a0850c/) einen ca. 1-min?tigen Ausschnitt aus meinem Projekt I.S.D.O herunterladen.
Die Spielerkollision ist noch ausgeschaltet. Die Hintergrundmusik stamm aus dem "MOD-Archiv".
Steuerung->WIN:
startmenu: cursorkey up/down = menucursor up and down
"x"-key = select menuentry
game: cursorkey up/down = move
"x"-key = shoot
ENTER-key = return to startmenu
Viel Spass!! =D
[attachment deleted by admin]
Hi!
It's safer to use CONSTANT than GLOBAL or LOCAL when you declare constants. It's impossible to change their values afterwards, and they are compiled as constants, not using the r.a.m. which is faster to run.
-
I noticed a few GRABSPRITE. This means the game will be totally fine only on PCs/mac. On android or iOS you will get white squares -and it's not Gernot's fault.
A way around it -a bit slower- is to draw everything in a different screen (use CREATESCREEN), i.e. on its associated image. Then you can work on the image at will, like SPRITE2MEM, SETPIXEL etc. When ready just draw the image on the main screen with USESCREEN -1.