Main forum > Tutorials
Horizontal Shooter Tutorial
Steinbock:
Hi, all
With this tutorial i would like to share my experiences with programming in GLBasic.
It's a framework for a horizontal shooter game and contains routines like intro, startmenu, 2-layer scrolling, collision or sound.
My english is not very well. But nevertheless i hope you will understand it.
If you have any questions or suggestions then please do not post them in this thread but here:http://www.glbasic.com/forum/index.php?topic=2949.0 Thanks.
I will split this tutorial into different parts. So you will have time to learn step by step.
A short example of how it looks at the end is avalable for download here:
Windows version:http://www.zshare.net/download/5735249659b47d37/
GP2X version:http://www.zshare.net/download/57352743c3fbf5c9/
EDIT 27.07.2009: added a simple tilemap editor with sourcecode in attachement
--- Code: (glbasic) ---control->GP2X:
startmenu: pad up/down = menucursor up and down
B-button = select menuentry
game: pad = move
B-button = shoot
START-button = return to startmenu
control->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
--- End code ---
In the first part we start with the program body:
--- Code: (glbasic) ---//=============================================================================
// 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
--- End code ---
First we reserve place for type definitions which we will add later.
Thereafter follows the constants which we will write always BIG in our program. With AS_INTRO etc. we describe the current state of the program (AS = Application State). Then follows some constants for assignment of keys.
Now we define the variables, whereby we write GLOBALS with a big and LOCALS with a small initial letter. We initialise the global variable AppState with AS_INTRO because we start our program with a little intro.
Then we clear the framebuffer.
Now the mainloop of the program:
If inside the loop
--- Code: (glbasic) ---WHILE done=FALSE
...
WEND
--- End code ---
the value of variable done is set to TRUE, we clear the framebuffer again and the program ends.
--- Code: (glbasic) --- 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
--- End code ---
Here we check the program module to be performed. As we have setted the value of AppState to AS_INTRO it calls the module RunIntro().
The different modules are FUNCTIONS which will return a value. This value will be stored again in variable AppState. On next pass of loop, it will be checked again.
So. That's for first. In the second part we will deal with the intro.
[attachment deleted by admin]
Steinbock:
In the second part of our tutorial we build executable code. In the attachement you will find the associated GLBasic project.
After unpacking the ZIP-file there should be the following files:
--- Code: (glbasic) ---[isdo] -> this folder contains the datas from our program
[gfx] -> here are required graphic datas located
intro.png -> graphic for intro
menu.png -> graphic for startmenu
2D Shooter Tutorial Part 2.gbap -> our GLBasic project file
isdo.gbas -> program module with program loop
intro.gbas -> program module with code for visual program start
menu.gbas -> program module with code for a small startmenu
--- End code ---
First we comment out following line in the mainloop (isdo.gbas). At the moment we don't need it.
--- Code: (glbasic) --- //CASE AS_LEVEL00 ; AppState=RunLevel00()
--- End code ---
After this let's have a closer look to the file intro.gbas.
--- Code: (glbasic) ---//-----------------------------------------------------------------------------
// I N I T I N T R O
//-----------------------------------------------------------------------------
LOADBMP "./isdo/gfx/intro.png"
--- End code ---
Here we load the graphic for our small intro in the backbuffer, so afterwards we can "grab out" then different sprites. That looks like this:
--- Code: (glbasic) --- GRABSPRITE 1,0,0,306,52
GRABSPRITE 2,0,52,75,21
GRABSPRITE 3,75,52,18,17
GRABSPRITE 4,112,52,193,32
--- End code ---
If you change the graphics on top of intro.png, you will be able to appear your own logo.
Now we let fade in the singel graphic parts.
--- Code: (glbasic) --- // 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
--- End code ---
In the FOR..NEXT loop we define in variable i how much the graphic would be fade in. DRAWRECT is necessary that the grapchic will not overlay itself. Right after this; let's have a break
--- Code: (glbasic) --- SLEEP 3000
--- End code ---
Then we fade out the graphic.
--- Code: (glbasic) --- 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
--- End code ---
At the end we clear the backbuffer again.
--- Code: (glbasic) --- ALPHAMODE 0
BLACKSCREEN
--- End code ---
With
--- Code: (glbasic) --- RETURN AS_MENU
--- End code ---
we pass the control to the mainloop in isdo.gbas again. What would happen if we return the constant AS_INTRO instead of AS_MENU?
Now the startmenu. This code is written in file menu.gbas. Here we need first som local constants and variables.
--- Code: (glbasic) ---// 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
--- End code ---
MS_ stands for MenuState and describes the appropriate menupoint. buttonUp and buttonDown are used for button states, especially with GP2X.
--- Code: (glbasic) ---//-----------------------------------------------------------------------------
// 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
--- End code ---
Like in the intro, first we load the menugraphic and then "grab" the single sprites out of it.
In the loop
--- Code: (glbasic) --- WHILE done=FALSE
....
....
WEND
RETURN AS_EXIT
--- End code ---
we check the value of done. If it is set to TRUE the program ends. If we look closer we can see that this query is not absolute necessary. But this way we have the possibility to break the program with done=TRUE for test purposes.
Now the first input check. For this we check B-button (GP2X) or "x"-key and look if it is pressed.
--- Code: (glbasic) --- IF KEY(BUTTON_B)
....
....
ENDIF
--- End code ---
According to this we jump to the particular menupoint. For the moment it's only EXIT.
--- Code: (glbasic) --- SELECT menuState
CASE MS_EXIT
RETURN AS_EXIT
ENDSELECT
--- End code ---
And here again: What would happen if we return the constant AS_INTRO instead of AS_EXIT?
When we check the cursorkeys or pad for up and down movement, we have to pay attention, that we jump olny to the next menupoint if we have released the cursorkeys or pad. Otherwise the menucursor is running like a wild horse.
--- Code: (glbasic) --- IF KEY(BUTTON_DOWN)
IF menuState<MS_EXIT
IF buttonDown=FALSE
INC menuState,1
buttonDown=TRUE
ENDIF
ENDIF
ELSE
buttonDown=FALSE
ENDIF
--- End code ---
With
--- Code: (glbasic) --- DRAWRECT 0,0,320,240,0x0
DRAWSPRITE 4,96,128
DRAWSPRITE menuState,96,menuState*22+128
SHOWSCREEN
--- End code ---
we visualise the whole thing.
That's it for the second part. Doesn't hurt, right?
In case you have questions to this tutorial or do not understand something, please ask. If i can help, i will try.
[attachment deleted by admin]
Steinbock:
In the third part we handle the levelloop and add the scrolling.
Because some things will seem to be more complex, i can give you following tips:
- make a printout from the code. For me code on paper is more readable than on screen.
- mark the code you already understand respectively what you don't understand.
- try to change a value just to see what happens.
In the attachement you will find again the sample code. New added are the two files level00.gbas which contains the levelloop and library.gbas which contains our generally functions and subroutines. Then, isdo/gfx/level00.png contains back- and foregroundgraphics and also isdo/level00.dat where the indexlists for the tilemaps (see later) are stored.
Ok. In isdo.gbas we free our out commented line again.
--- Code: (glbasic) --- CASE AS_LEVEL00 ; AppState=RunLevel00()
--- End code ---
Then we change following line from AS_INTRO to AS_MENU so we can skip the intro.
--- Code: (glbasic) ---GLOBAL AppState% = AS_MENU
--- End code ---
We will draw our graphics after each visual display in this order:
1. Backgroundgrapchic (LayerBG)
2. Foregroundgraphic (LayerFG)
3. Statusdisplay on top (at the moment only a black filled rectangle)
For this we have to define some new types.
--- Code: (glbasic) ---TYPE TLayerFG
Map%[4096]
ENDTYPE
--- End code ---
is an indexlist which contain the tilenumbers of the foregroundgraphic.
--- Code: (glbasic) ---TYPE TLayerBG
Map%[2048]
ENDTYPE
--- End code ---
is an indexlist which contain the tilenumbers of the backgroundgraphic.
The tilegraphic is stored in isdo/gfx/level00.png and consists on 16x16 tiles and every single one of them has a size of 16x16 pixels.
Our tilemap is build as follows:
--- Code: (glbasic) --- foreground background
[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]
--- End code ---
For example:
--- Code: (glbasic) --- LayerFG[0]=255
--- End code ---
with this we put the bottommost right tile at the uppermost position in or tilemap (foreground).
If you still have any questions according the tilemaps, then please ask again.
--- Code: (glbasic) ---TYPE TEvent
Map%[4096]
ENDTYPE
--- End code ---
This index-list contain special tileattributes. We need them later. Bute since they are icluded in the file level00.dat we have to define them now.
Now we must define this 3 lists as global variables.
--- Code: (glbasic) ---//=============================================================================
// V A R I A B L E S
//=============================================================================
GLOBAL LayerFG AS TLayerFG
GLOBAL LayerBG AS TLayerBG
GLOBAL Event AS TEvent
--- End code ---
New added is also the global variable AppTimer. This is a counter wich will be icreased by 1 on every screen refresh.
--- Code: (glbasic) ---GLOBAL AppTimer% = 0
--- End code ---
Then we have the two global variables ScrollX and ScrollY. We need them for scrolling.
--- Code: (glbasic) ---GLOBAL ScrollX = 0
GLOBAL ScrollY = 0
--- End code ---
The global constant COL_TRANSPARENT defines our transparent color value. In or program this means RGB(248,0,248).
--- Code: (glbasic) ---GLOBAL COL_TRANSPARENT = 0xF800F8
--- End code ---
... ok, let's take a deep breath... and go on...
We look now in the file level00.gbas.
First we create some local auxiliary variables.
--- Code: (glbasic) ---LOCAL done% = FALSE
LOCAL x%,y%
LOCAL ix%,id%,p%
LOCAL val%
--- End code ---
Then we set the scrollposition to startpoint.
--- Code: (glbasic) --- ScrollX=0
ScrollY=0
--- End code ---
With the following lines we load our leveldatas in our 3 indexlists.
--- Code: (glbasic) --- // 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
--- End code ---
To create leveldatas ether you can make your own leveleditor or you can create them in worst case by a HEX-editor.
Now we set the general transparencycolor to our predefined value.
--- Code: (glbasic) --- SETTRANSPARENCY COL_TRANSPARENT
--- End code ---
With
--- Code: (glbasic) --- LOADBMP ""
--- End code ---
we ensure the backgroundbuffer is empty. Otherwise it is possible that the new graphic will overlay an other.
Now we load our tilegraphic and assing to each of the 16x16 pixel size tiles an own spriteID. We start with the number 256 in case we have to expand if we need more tiles later.
--- Code: (glbasic) --- 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
--- End code ---
Then we clear the temporary sprite again.
--- Code: (glbasic) --- LOADSPRITE "",0
--- End code ---
From now on we are in the mainloop. That means, 60x per second we draw the whole screen in the backbuffer, and draw it just after that on the visual display.
--- Code: (glbasic) ---//-----------------------------------------------------------------------------
// 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
--- End code ---
We go on step bei step.
First we program an emergency exit. So we don't always have to turn on and off our device. Through pressing START-button (GP2X) or ENTER-key (WIN) we can return to the startmenu.
--- Code: (glbasic) --- IF KEY(BUTTON_START) THEN RETURN AS_MENU
--- End code ---
With every screen refresh we increase a general counter by 1.
--- Code: (glbasic) --- INC AppTimer,1
--- End code ---
Then we determine on how many pixels we will scroll each time (foregrund layer). If you like you can try to change this value.
--- Code: (glbasic) --- INC ScrollX,0.5
--- End code ---
Afterwards let's do the KeyHANDLER in library.gbas his work. His task is to handle all user inputs.
--- Code: (glbasic) ---//=============================================================================
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
--- End code ---
These 4 lines are only for test pruposes. Through pressing the keys "1"-"4" we can change the display refresh manually. This has the benefit to slow down everything, so we can see for example, if the collisions occours as accurate as possible.
With
--- Code: (glbasic) --- GOSUB LayerBGDRAW
GOSUB LayerFGDRAW
--- End code ---
we call 2 subroutines in library.gbas. These are drawing our two layers.
We calculate the position in LayerBG.Map from where we draw the first tile on top left.
--- Code: (glbasic) --- // draw background layer
p=INTEGER(ScrollX / 16 /2)*16
--- End code ---
Then we calculate the exact startposition in pixels, where we draw the first column of tiles.
--- Code: (glbasic) --- dx=0-(bAND(ScrollX,31) /2)
--- End code ---
In opposite to the foreground we have to divide by 2 on the background, so we reach a 2-layer effect.
Now we defin how many columns have to be drawn.
--- Code: (glbasic) --- FOR x=0 TO 20
--- End code ---
Since our screen resolution is 320x240 pixels this value results in: 320/16=20. But depending on scrollposition drawing starts outside the visible area on the left side. In this cases on the right side you can see the image-buildup. That's not very aesthetic. So we have to draw one more column.
--- Code: (glbasic) --- dy=32
--- End code ---
This is the exact startposition in pixels where we draw the first line of tiles (remember status display is 32 pixel heigh).
We draw 13 lines ((240-32)/16=13.
--- Code: (glbasic) --- FOR y=0 TO 12
--- End code ---
Here we don't have to add a line because we are not scrolling up or down.
Now we draw the calculated tile to its position (in pixel) on screen.
--- Code: (glbasic) --- DRAWSPRITE LayerBG.Map[p],dx,dy
--- End code ---
From now on we go line for line downwards until we reach the buttom border of screen.
--- Code: (glbasic) --- INC dy,16
INC p,1
NEXT
--- End code ---
Then we take the next column.
--- Code: (glbasic) --- INC dx,16
INC p,3
NEXT
--- End code ---
Since our layermap consit a dimension of 16 lines but we draw only 13 of them, we have to overjump 3 listpositions.
Still remains the statusdisplay.
--- Code: (glbasic) --- GOSUB StatusDRAW
--- End code ---
At the moment we take it easy and draw only a black filled rectangle with a subroutine in our library.
Library auf.
--- Code: (glbasic) --- DRAWRECT 0,0,320,32,RGB(0,0,0)
--- End code ---
I hopei have everything explained fairly understandable. If not just go into it.
Next time we take control of our "Hero".
[attachment deleted by admin]
Steinbock:
In the forth part we breathe life into our hero and teach him how to shoot.
In the attached samplecode there is a new graphicfile for playersprites and shots isdo/gfx/player.png.
In isdo.gbas first we define again some new types, constants and variables:
--- Code: (glbasic) ---TYPE TPlayer
PosX = 64
PosY = 64
Timer% = 0
Dir% = 0
Speed = 1.0
ENDTYPE
--- End code ---
PosX und PosY is the position of our playersprite. Timer is a counter we increase every screenrefresh by 1. Dir is the move-direction and Speed defines the move-speed.
--- Code: (glbasic) ---TYPE TShot
State%[8]
PosX[8]
PosY[8]
Dir%[8]
Count% = 8
ENDTYPE
--- End code ---
Here we define a type for 8 shots. This means we will bable to show max. 8 shots at the same time.
State describes the current shot-state, PosX and PosY the shot-position and Dir the shot-direction. Count is the maximum of shots we can display at the same time.
--- Code: (glbasic) ---// sprites
GLOBAL SPR_PLAYER% = 3
GLOBAL SPR_SHOT% = 18
--- End code ---
In order that we don't have to memorize the appropriate spritenumber, we describe them with a clear constant.
--- Code: (glbasic) ---// 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
--- End code ---
These are direction values wich we use for playersprite and shots.
Then we must define the variables:
--- Code: (glbasic) ---GLOBAL Player AS TPlayer
GLOBAL Shot AS TShot
--- End code ---
and then
--- Code: (glbasic) ---GLOBAL ButtonB = TRUE
--- End code ---
this provide us the state of button B or "x"-key.
In level00.gbas we initialise some of the new variables.
--- Code: (glbasic) --- Player.PosX=8
Player.PosY=64
SPR_SHOT=18
--- End code ---
--- Code: (glbasic) --- // 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
--- End code ---
here we load graphicdatas for our hero. Nr.1+2 are animation-steps if the player flies straightforward. Nr.3+4 if he moves upright. You can expand this for the rest of directions, if you like.
--- Code: (glbasic) --- // 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
--- End code ---
With these lines we build sprites for the shots.
In the levelloop we must first set the state of button-B to TRUE.
--- Code: (glbasic) --- ButtonB=TRUE
--- End code ---
This is important, otherwise the following happens.
First we are in the startmenu. With button-B we confirm our choice. If the level starts and our button-B is still pressed, we shoot instantly.
Through setting on TRUE the program will wait until the release of button.
We increase the counter for spriteanimation.
--- Code: (glbasic) --- INC Player.Timer,1
--- End code ---
--- Code: (glbasic) --- GOSUB ShotHANDLER
GOSUB PlayerHANDLER
--- End code ---
With these two subprograms we manage the shots and the display of hero.
Now let's change to our library in library.gbas.
First we look in the code of PlayerHANDLER.
--- Code: (glbasic) ---//=============================================================================
SUB PlayerHANDLER:
//=============================================================================
DRAWSPRITE SPR_PLAYER+(INTEGER((bAND(Player.Timer,3)/2))),Player.PosX,Player.PosY
ENDSUB // PlayerHANDLER
--- End code ---
This is responsible for display the playersprite at the current position.
--- Code: (glbasic) ---INTEGER((bAND(Player.Timer,3)/2))
--- End code ---
gives back according to Player.Timer 0 or 1, wich corresponds to each animation-step. In our program we have only 2.
In the KeyHANDLER there are also a few changes.
First we must define some local variables
--- Code: (glbasic) ---LOCAL kr%,kl%,kd%,ku%
--- End code ---
They store if the corresponding direction is pressed or not. For this, first initialize.
--- Code: (glbasic) --- kr=0;kl=0;kd=0;ku=0
--- End code ---
Now we check if directions are pressed or not. If yes, we write the value in the local variable. Simultaneously we change the position of player-sprite.
--- Code: (glbasic) ---
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
--- End code ---
Afterwards we add the single variables to a bitmask. This means we have a binary string of "00000". If we press right-button the bitmask looks like "00010", if we press right-button+up-button "10010". That way it's possible to request multiple buttonstates. This mask we save in Player.Dir.
--- Code: (glbasic) --- Player.Dir=kr+kl+kd+ku
--- End code ---
Now to something unusual. SPR_PLAYER is per definition a constant spriteID. But here we use it as a variable, so by a change of direction we can change the basissprite for animation with just one command. As an example i've done it here for upright.
--- Code: (glbasic) --- SPR_PLAYER=1
SELECT Player.Dir
CASE DIR_RIGHTUP
SPR_PLAYER=3
ENDSELECT
--- End code ---
Now we teach our hero to shoot.
--- Code: (glbasic) --- // player shoot
IF KEY(BUTTON_B)
IF ButtonB=FALSE
ButtonB=TRUE
GOSUB ShotADD
ENDIF
ELSE
ButtonB=FALSE
ENDIF
--- End code ---
By pressing button-B we jump to routine ShootADD which adds a shot to our scene.
In this routine we first define a local counte variable and init with 0.
--- Code: (glbasic) ---LOCAL ix%
ix=0
--- End code ---
Then we run 8 times through a loop wich checks for every possible shot.
--- Code: (glbasic) ---Loop1:
....
....
GOTO Loop1
ENDIF
--- End code ---
We chek if the counter reaches the las shot.
--- Code: (glbasic) --- IF ix=Shot.Count THEN RETURN
--- End code ---
If yes, we add no other shot.
Thereafter we check if the current shot-entry is still in using.
--- Code: (glbasic) --- IF Shot.State[ix]=0
--- End code ---
Then we pass dhe direction-mask and startposition of the new shot.
--- Code: (glbasic) --- Shot.Dir[ix]=Player.Dir
Shot.PosX[ix]=Player.PosX+8
Shot.PosY[ix]=Player.PosY+10
Shot.State[ix]=1
--- End code ---
Shot.State is a state variable wich gives us the current step of the shot.
In ShootHANDLER we define some local variables and running in a loop all 8 shots again.
--- Code: (glbasic) ---//=============================================================================
SUB ShotHANDLER:
//=============================================================================
LOCAL ix%,ev%,x%,y%
FOR ix=0 TO Shot.Count-1
IF Shot.State[ix]
....
....
ENDIF
NEXT
--- End code ---
Now we calculate dthe actual shot-position in our eventmap and read out this value.
--- Code: (glbasic) --- x=INTEGER(ScrollX+Shot.PosX[ix]) / 16
y=INTEGER(ScrollY+Shot.PosY[ix]-32) / 16
ev=Event.Map[x*16+y]
--- End code ---
As we alredy know, the eventmap contain special attributes. Such an attribute can be a wall or a block at the same position in the LayerFG.Map. Practicaly this means, we end the shot as soon as there is a wall and don't let it fly through.
If the shot hit the wall we free the current shotentry.
--- Code: (glbasic) --- IF ev>0 THEN Shot.State[ix]=0
--- End code ---
.
According to the flightdirection we adjust the shotposition, draw the corresponding sprite and check if the shot reach screenborders.
--- Code: (glbasic) --- 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
--- End code ---
In the next part we will fight against enemies.
[attachment deleted by admin]
Steinbock:
In this part we come to the most important part of this tutorial, the objects. If you understood this part, then you will see, gameprogramming isn't as difficult as it seems.
This time in the attached sample code there are new graphicdatas isdo/gfx/player.png for our objects and enemies.gbas.
Objects can be for example:
Enemies
Powerups
Effects
Enemyshots
For this we insert a new type in isdo.gbas:
--- Code: (glbasic) ---TYPE TObject
State%[32]
Typ%[32]
PosX[32]
PosY[32]
Timer%[32]
Count% = 32
ENDTYPE
--- End code ---
With this we define a type for 32 objects. This means we will be able to display max. 32 objects at the same time.
State describes the current objectstate, PosX and PosY the position and Timer is used as an animation-conter. Count is the maximum of objects.
We build the corresponding variable.
--- Code: (glbasic) ---GLOBAL Object AS TObject
--- End code ---
Then we expand our spriteID list.
--- Code: (glbasic) ---GLOBAL SPR_ENEMY1% = 512
GLOBAL SPR_ENEMY2% = 514
--- End code ---
Because we need many different objects in a game, it's an advantage to assing a constant to every object.
--- Code: (glbasic) ---// object ID's
GLOBAL ID_ENEMY1% = 0x11
GLOBAL ID_ENEMY2% = 0x12
--- End code ---
We define 2 enemies:
ENEMY1 yellow enemy
ENEMY2 red enemy
In level00.gbas we load our objectgraphics and "grab" out the single sprites.
--- Code: (glbasic) --- // 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
--- End code ---
Then we expand our levelloop with the ObjectHANDLER
--- Code: (glbasic) --- GOSUB ObjectHANDLER
--- End code ---
wich we will find in library.gbas.
--- Code: (glbasic) ---//=============================================================================
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
--- End code ---
This small program section is indeed a main component of our program!
As with the shots we running through a list and check first, if the listentry is already free. If yes, we look wath kind of objecttype it is.
Depending on objecttype then we refer to an objectscript, where the object would be handled separatly (attitude, moving, collision etc).
On call-up function EnemyScripts we pass with ix the current objectlist entry and hence the control over the corresponding object.
At this point there are already a few values in the objects entries, because with
--- Code: (glbasic) --- IF ScrollX=128 THEN ObjectADD(ID_ENEMY1,320,120)
IF ScrollX=160 THEN ObjectADD(ID_ENEMY2,320,120)
--- End code ---
in level00.gbas we added some objects to our scene.
With this command we pass type and position of a new object to the ObjectHANDLER.
--- Code: (glbasic) ---//=============================================================================
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
--- End code ---
Now let's have a closer look to a script. The most important variable is Object.State. It describes the phase in which the object is located. In our example we have 3 phases. In each of this phase we change for example the position. If we finished the last phase, then we release the object again.
--- Code: (glbasic) --- 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
--- End code ---
Now we only have to draw the object.
--- Code: (glbasic) --- DRAWSPRITE SPR_ENEMY1+frame,Object.PosX[ix],Object.PosY[ix]
--- End code ---
[attachment deleted by admin]
Navigation
[0] Message Index
[#] Next page
Go to full version