GLBasic forum

Codesnippets => Code Snippets => Topic started by: MrTAToad on 2010-Jun-29

Title: JNR Platform Example Code
Post by: MrTAToad on 2010-Jun-29
This is the code for the JNR platform examples.  Included is currently example 1 and 2

Example 1:

Code (glbasic) Select
// ---------------------------------
// Project: JNRExample#1
// Start: Saturday, June 26, 2010
// IDE Version: 8.006

CONSTANT MAPWIDTH% = 32 //width of the map
CONSTANT MAPHEIGHT% = 24 //height of the map
CONSTANT TILESIZE% = 20 //size of the tiles, should be dynamically read

CONSTANT PLAYERHEIGHT% = 37 //should be dynamically read from a player definition file
CONSTANT PLAYERWIDTH% = 11 //--""--



CONSTANT WAITTIME% = 15 //delay between frames


CONSTANT GRAVITATION% = 1 //gravitation - 1 pixel per frame (too fast, will be changed somewhen (playerx,y -> float, grav: 0.xx))

CONSTANT VELMOVING% = 4 //velocity (speed) for moving left, right
CONSTANT VELJUMP% = 13 //velocity for jumping

TYPE tTile
solid%=FALSE
spr%=-1
ENDTYPE

TYPE TMap
tiles[] AS tTile
spr_t%[]

FUNCTION TMap_Initialise%:spr_t%[]
DIM self.tiles[MAPWIDTH%][MAPHEIGHT%]
self.spr_t%[]=spr_t%[]
RETURN TRUE
ENDFUNCTION

FUNCTION loadMap%:file$
LOCAL handle%
LOCAL t%,i%,j%

IF DOESFILEEXIST(file$)
handle%=GENFILE()
IF handle%>=0
IF OPENFILE(handle%,file$,1)
FOR j%=0 TO MAPHEIGHT%-1
FOR i%=0 TO MAPWIDTH%-1
READBYTE handle%,self.tiles[i%][j%].solid%
READLONG handle%,t%
IF t%<>0 THEN self.tiles[i%][j%].spr%=self.spr_t%[t%-1]
NEXT
NEXT
RETURN TRUE
ELSE
RETURN FALSE
ENDIF
ENDIF
ENDIF
ENDFUNCTION

FUNCTION TMap_draw%:
LOCAL i%,j%

FOR i%=0 TO MAPWIDTH%-1
FOR j%=0 TO MAPHEIGHT%-1
IF self.tiles[i%][j%].spr%<>-1 THEN DRAWSPRITE self.tiles[i%][j%].spr%,i%*TILESIZE%,j%*TILESIZE%
NEXT
NEXT
ENDFUNCTION

FUNCTION colmapxy%:x%,y%
RETURN self.tiles[x%][y%].solid
ENDFUNCTION
ENDTYPE

TYPE TPlayer
x%;y%
h%;w%
velx%;vely%
faceright%
lockjump%
spr_player%[]
map AS TMap

FUNCTION TPlayer_Initialise%:spr_player%[],map AS TMap
self.spr_player%[]=spr_player%[]
self.x%=100
self.y%=100
self.h%= PLAYERHEIGHT; //save player height and width
self.w%= PLAYERWIDTH;
self.velx%=0
self.vely%=0
self.lockjump%=FALSE
self.faceright%=TRUE
self.map=map
ENDFUNCTION

FUNCTION think%:
LOCAL tilecoord%

self.velx=0; //don't move left / right by default

IF KEY(205)
self.velx=VELMOVING% // Move right
self.faceright%=TRUE
ELSE
IF KEY(203)
self.velx=0-VELMOVING% // Move left
self.faceright=FALSE
ENDIF
ENDIF

IF KEY(42) AND self.lockjump=FALSE // Can the player jump ?
self.vely=0-VELJUMP%
self.lockjump%=TRUE
ENDIF

IF self.velx%>0
IF collision_ver(self.x+self.velx+self.w%,self.y%,tilecoord%) //collision on the right side.
self.x%=tilecoord%*20-self.w%-1; //move to the edge of the tile (tile on the right -> mind the player width)
ELSE
INC self.x%,self.velx%
ENDIF
ELSE
IF self.velx%<0
IF collision_ver(self.x+self.velx,self.y%,tilecoord%) //collision on the left side
self.x%=(tilecoord%+1)*20+1; //move to the edge of the tile
ELSE
INC self.x%,self.velx%
ENDIF
ENDIF
ENDIF

IF self.vely%<0
IF collision_hor(self.x%,self.y%+self.vely%,tilecoord%)
self.y%=(tilecoord%+1)*20+1
self.vely%=0
ELSE
INC self.y%,self.vely%
INC self.vely%,GRAVITATION%
ENDIF
ELSE
IF collision_hor(self.x%,self.y%+self.vely%+self.h%,tilecoord%)
self.y%=tilecoord%*20-self.h%-1
self.vely%=1 // so we test against the ground again int the next frame (0 would test against the ground in the next+1 frame)

IF KEY(42)=0
self.lockjump%=FALSE
ENDIF
ELSE
INC self.y%,self.vely%
INC self.vely%,GRAVITATION%

IF self.vely%>=TILESIZE%
self.vely%=TILESIZE%
ENDIF

self.lockjump%=TRUE
ENDIF
ENDIF
ENDFUNCTION

FUNCTION TPlayer_draw%:
IF self.faceright%
DRAWSPRITE self.spr_player%[1],self.x%-4,self.y%-2
ELSE
DRAWSPRITE self.spr_player%[0],self.x%-17,self.y%-2
ENDIF
ENDFUNCTION

FUNCTION collision_ver%:x%,y%,BYREF tilecoordx%
LOCAL tileypixels%,testend%,tilecoordy%

tileypixels% = y%-MOD(y%,20)
testend% = y% + self.h%;

tilecoordx% = x%/20;

tilecoordy% = tileypixels/20;

WHILE tileypixels <= testend
IF self.map.colmapxy(tilecoordx%,tilecoordy%)
RETURN TRUE
ENDIF

INC tilecoordy%
INC tileypixels%,20
WEND

RETURN FALSE
ENDFUNCTION

FUNCTION collision_hor%:x%,y%,BYREF tilecoordy%
LOCAL tilexpixels%,testend%,tilecoordx%

tilexpixels = self.x%-MOD(x%,20) //calculate the x position (pixels!) of the tiles we check against
testend = x% + self.w //calculate the end of testing (just to save the x+w calculation each for loop)

tilecoordy% = y%/20; //calculate the y position (map coordinates!) of the tiles we want to test

tilecoordx% = tilexpixels%/20; //calculate map x coordinate for first tile


//loop while the start point (pixels!) of the test tile is inside the players bounding box
WHILE tilexpixels <= testend
IF self.map.colmapxy(tilecoordx%, tilecoordy%) //is a solid tile is found at tilecoordx, tilecoordy?
RETURN TRUE;
ENDIF

INC tilecoordx //increase tile x map coordinate
INC tilexpixels,20; //increase tile x pixel coordinate
WEND

RETURN FALSE
ENDFUNCTION
ENDTYPE


LOCAL spr_t%[]; DIM spr_t%[3]
LOCAL spr_player%[]; DIM spr_player%[2]
LOCAL spr_background%
LOCAL map AS TMap
LOCAL player AS TPlayer

SETTRANSPARENCY RGB(255,0,255)
spr_t%[0]=GENSPRITE(); LOADSPRITE "Media/t1.bmp",spr_t%[0]
spr_t%[1]=GENSPRITE(); LOADSPRITE "Media/t2.bmp",spr_t%[1]
spr_t%[2]=GENSPRITE(); LOADSPRITE "Media/t3.bmp",spr_t%[2]
spr_player%[0]=GENSPRITE(); LOADSPRITE "Media/left.bmp",spr_player%[0]
spr_player%[1]=GENSPRITE(); LOADSPRITE "Media/right.bmp",spr_player%[1]
spr_background%=GENSPRITE(); LOADSPRITE "Media/bg.bmp",spr_background%

IF map.TMap_Initialise(spr_t%[])=FALSE
DEBUG "Map could not be initialised"
RETURN FALSE
ENDIF

IF map.loadMap("Media/maps/map01.map")=FALSE
DEBUG "Map could not be loaded"
RETURN FALSE
ENDIF

player.TPlayer_Initialise(spr_player%[],map)

WHILE TRUE
//update objects
player.think();


//draw everything
DRAWSPRITE spr_background%,0,0
map.TMap_draw()
player.TPlayer_draw()
SHOWSCREEN
WEND


Example 2 :

Code (glbasic) Select
// ---------------------------------
// Project: JNRExample#2
// Start: Sunday, June 27, 2010
// IDE Version: 8.006

CONSTANT MAPWIDTH% = 32 //width of the map
CONSTANT MAPHEIGHT% = 24 //height of the map
CONSTANT TILESIZE% = 20 //size of the tiles, should be dynamically read

CONSTANT PLAYERHEIGHT% = 37 //should be dynamically read from a player definition file
CONSTANT PLAYERWIDTH% = 11 //--""--



CONSTANT WAITTIME% = 16 //delay between frames


CONSTANT GRAVITATION% = 1 //gravitation - 1 pixel per frame (too fast, will be changed somewhen (playerx,y -> float, grav: 0.xx))

CONSTANT VELMOVING% = 4 //velocity (speed) for moving left, right
CONSTANT VELJUMP% = 13 //velocity for jumping

CONSTANT t_nonsolid% = 0
CONSTANT t_solid% = 1
CONSTANT t_slopeleft% = 2
CONSTANT t_sloperight% = 3

TYPE tTile
type%
spr%
ENDTYPE

TYPE TMap
tiles[] AS tTile
spr_t%[]

FUNCTION TMap_Initialise%:spr_t%[]
LOCAL x%,y%

DIM self.tiles[MAPWIDTH%][MAPHEIGHT%]
self.spr_t%[]=spr_t%[]

FOR y%=0 TO MAPHEIGHT%-1
FOR x%=0 TO MAPWIDTH%-1
self.tiles[x%][y%].type%=t_nonsolid% // Need to initialise here as constants cant be assigned in TYPES
self.tiles[x%][y%].spr%=-1
NEXT
NEXT

RETURN TRUE
ENDFUNCTION

FUNCTION loadMap%:file$
LOCAL handle%
LOCAL t%,i%,j%

IF DOESFILEEXIST(file$)
handle%=GENFILE()
IF handle%>=0
IF OPENFILE(handle%,file$,1)
FOR j%=0 TO MAPHEIGHT%-1
FOR i%=0 TO MAPWIDTH%-1
READLONG handle%,self.tiles[i%][j%].type%
READLONG handle%,t%
IF t%<>128 THEN self.tiles[i%][j%].spr%=self.spr_t%[t%]
NEXT
NEXT
RETURN TRUE
ELSE
RETURN FALSE
ENDIF
ENDIF
ENDIF
ENDFUNCTION

FUNCTION TMap_draw%:
LOCAL i%,j%

FOR i%=0 TO MAPWIDTH%-1
FOR j%=0 TO MAPHEIGHT%-1
IF self.tiles[i%][j%].spr%<>-1 THEN DRAWSPRITE self.tiles[i%][j%].spr%,i%*TILESIZE%,j%*TILESIZE%
NEXT
NEXT
ENDFUNCTION

FUNCTION TMap_map%:x%,y%
RETURN self.tiles[x%][y%].type%
ENDFUNCTION
ENDTYPE

TYPE TPlayer
x%;y% //x, y coordinate (top left of the player rectangle)
h%;w% //height, width
velx%;vely% //velocity on x, y axis
faceright% //player facing right? -> graphics
lockjump% //may the player jump
jumping%
spr_player%[]
map AS TMap
slope_prevtilex%//the tile at slopex, slopey in the last frame
slope_prevtiley%

// Passing the players array and the TMap TYPE is a bit inefficient
FUNCTION TPlayer_Initialise%:spr_players%[],map AS TMap
self.spr_player%[]=spr_players%[]
self.x%=100
self.y%=100
self.h%= PLAYERHEIGHT; //save player height and width
self.w%= PLAYERWIDTH;
self.velx%=0
self.vely%=0
self.lockjump%=FALSE
self.faceright%=TRUE
self.jumping%=FALSE
self.map=map
self.slope_prevtilex% = (self.x + ASR(self.w,1))/ TILESIZE;
self.slope_prevtiley% = (self.y + self.h) / TILESIZE;
ENDFUNCTION

FUNCTION unlockjump%: //this function is called if the player hits the ground
// this if is quite tricky:
// the player may jump again:
// a) if he fell of an edge (!jumping) - without releasing the jump key on the ground
// b) if he jumped - only when he releases the jump key on the ground
IF NOT(self.jumping%) OR NOT(KEY(42))
self.lockjump%=FALSE
self.jumping%=FALSE
ENDIF
ENDFUNCTION

FUNCTION think%:
LOCAL tilecoord%

self.velx%=0; //don't move left / right by default

IF KEY(205)
self.velx%=VELMOVING% //move right
self.faceright%=TRUE
ELSE
IF KEY(203)
self.velx%=0-VELMOVING% //move right
self.faceright%=FALSE
ENDIF
ENDIF

IF KEY(42) AND self.lockjump%=FALSE //if the player isn't jumping already
self.vely%=0-VELJUMP% //jump!
self.lockjump%=TRUE //player is not allowed to jump anymore
ENDIF

collision_detection_map()
ENDFUNCTION

FUNCTION TPlayer_draw%:
IF self.faceright%
DRAWSPRITE self.spr_player%[1],self.x%-4,self.y%-2
ELSE
DRAWSPRITE self.spr_player%[0],self.x%-17,self.y%-2
ENDIF
ENDFUNCTION

FUNCTION collision_hor_up%:x%,y%,BYREF tilecoordy%
LOCAL tilexpixels%,testend%,tilecoordx%

tilexpixels% = x%-MOD(x%,TILESIZE%)
testend% = x% + self.w

tilecoordy% = y%/TILESIZE%

tilecoordx% = tilexpixels%/TILESIZE

WHILE tilexpixels% <= testend%
IF self.map.TMap_map(tilecoordx%, tilecoordy%)<>t_nonsolid% //only this changed: when jumping (moving up) we don't want to go through slopes
RETURN TRUE;
ENDIF

INC tilecoordx%
INC tilexpixels%,TILESIZE%
WEND

RETURN FALSE
ENDFUNCTION

FUNCTION collision_hor_down%:x%,y%,BYREF tilecoordy%
LOCAL tilexpixels%,testend%,tilecoordx%

tilexpixels% = x%-MOD(x%,TILESIZE%) //calculate the x position (pixels!) of the tiles we check against
testend% = x% + self.w% //calculate the end of testing (just to save the x+w calculation each for loop)

tilecoordy% = y/TILESIZE //calculate the y position (map coordinates!) of the tiles we want to test

tilecoordx% = tilexpixels%/TILESIZE //calculate map x coordinate for first tile


//loop while the start point (pixels!) of the test tile is inside the players bounding box
WHILE tilexpixels% <= testend%
IF self.map.TMap_map(tilecoordx%, tilecoordy%)=t_solid //is a solid tile is found at tilecoordx, tilecoordy?
RETURN TRUE
ENDIF

INC tilecoordx% //increase tile x map coordinate
INC tilexpixels%,TILESIZE% //increase tile x pixel coordinate
WEND

RETURN FALSE
ENDFUNCTION

// FOR explanation see CPlayer::collision_hor()
FUNCTION collision_ver%:x%,y%,BYREF tilecoordx%
LOCAL tileypixels%,testend%,tilecoordy%

tileypixels = y%-MOD(y%,TILESIZE%)
testend = y% + self.h%

tilecoordx% = x/TILESIZE

tilecoordy% = tileypixels/TILESIZE

WHILE tileypixels <= testend
IF self.map.TMap_map(tilecoordx%, tilecoordy%) = t_solid%
RETURN TRUE
ENDIF

INC tilecoordy%
INC tileypixels%,TILESIZE
WEND

RETURN FALSE
ENDFUNCTION

FUNCTION collision_slope%:sx%,sy%,BYREF tsx%,BYREF tsy%
LOCAL t%

tsx = sx% / TILESIZE //map coordinates of the tile we check against
tsy = sy% / TILESIZE

t% = self.map.TMap_map(tsx, tsy)

//if we found a slope we set align y to the slope.
//take a look at jnrdev #2, why it's calculated this way
IF t% = t_sloperight%
//sloperight -> \
self.y =  (tsy%+1)*TILESIZE - (TILESIZE - MOD(sx%,TILESIZE))  - self.h% - 1;
RETURN TRUE;
ELSEIF t% = t_slopeleft%
//slopeleft -> /
self.y =  (tsy+1)*TILESIZE - MOD(sx%,TILESIZE%)  - self.h% - 1 ;
RETURN TRUE;
ENDIF

RETURN FALSE;
ENDFUNCTION

FUNCTION collision_detection_map%:
LOCAL tsx%, tsy%,sx%,sy%,what%,tilecoord% //slope tile coordinates

//check for slopes (only if moving down)
IF self.vely > 0
sx = self.x% + ASR(self.w%,1) + self.velx //slope chechpoint x coordinate

IF collision_slope(sx%, (self.y + self.h), tsx%, tsy%) //we entered a slope (y is set by collision_slope)
INC self.x%,self.velx% //move on
//y has been set by collision_slope

unlockjump(); //we hit the ground - the player may jump again

self.vely = 1 //test against the ground again in the next frame

self.slope_prevtilex% = tsx% //save slope koordinate
self.slope_prevtiley% = tsy%

RETURN TRUE
ELSE
//we're not on a slope this frame - check if we left a slope
//-1 ... we didn't move from slope to slope
//0 ... we left a slope after moving down
//1 ... we left a slope after moving up
what% = -1

IF self.map.TMap_map(self.slope_prevtilex%, self.slope_prevtiley%) = t_sloperight%
IF self.velx > 0 //sloperight + velx > 0  = we left a slope after moving up the slope
what = 0;
ELSE
what = 1; //we left after moving down the slope
ENDIF
ELSEIF self.map.TMap_map(self.slope_prevtilex, self.slope_prevtiley) = t_slopeleft%
IF self.velx < 0 //sloperight + velx > 0  = we left a slope after moving up the slope
what = 0;
ELSE
what = 1; //we left after moving down the slope
ENDIF
ENDIF

IF what%<>-1 //if we left a slope and now are on a slope
IF what% = 1
self.y% =  tsy*TILESIZE% - self.h -1; //move y to top of the slope tile
sy% = self.y + self.h%
ELSE
self.y% =  (tsy+1)*TILESIZE - self.h -1; //move y to the bottom of the slope tile
sy% = self.y + self.h% + TILESIZE% //test one tile lower than the bottom of the slope (to test if we move down a long slope)
//it's physically incorrect, but looks a lot smoother ingame
ENDIF

//check for slopes on new position
IF collision_slope(sx, sy, tsx, tsy) //slope on new pos (entered a slope after we left a slope)
INC self.x%,self.velx% //-> we moved from slope to slope

unlockjump();

self.vely% = 1;

self.slope_prevtilex% = tsx%
self.slope_prevtiley% = tsy%

RETURN TRUE
ENDIF
ENDIF
ENDIF
ENDIF

//no slope collisions were found -> check for collisions with the map
//x axis first (--)
IF self.velx > 0 //moving right
IF collision_ver(self.x%+self.velx%+self.w, self.y, tilecoord%) //collision on the right side.
self.x = tilecoord%*TILESIZE% -self.w-1; //move to the edge of the tile (tile on the right -> mind the player width)
ELSE //no collision
INC self.x%,self.velx
ENDIF
ELSEIF self.velx < 0 //moving left
IF collision_ver(self.x%+self.velx%, self.y%, tilecoord%) //collision on the left side
self.x% = (tilecoord%+1)*TILESIZE% +1; //move to the edge of the tile
ELSE
INC self.x%,self.velx%
ENDIF
ENDIF

//THEN y axis (|)
IF self.vely%<0
IF collision_hor_up(self.x%,self.y%+self.vely%,tilecoord%)
self.y% = (tilecoord%+1)*TILESIZE% +1;
self.vely% = 0
ELSE
INC self.y%,self.vely%
INC self.vely%,GRAVITATION%
ENDIF
ELSE
//moving down / on ground
IF collision_hor_down(self.x%, self.y%+self.vely%+self.h%, tilecoord%) //on ground
self.y% = tilecoord%*TILESIZE% -self.h%-1;
self.vely% = 1; //1 so we test against the ground again int the next frame (0 would test against the ground in the next+1 frame)

unlockjump()
ELSE //falling (in air)
INC self.y,self.vely%
INC self.vely%,GRAVITATION%

IF self.vely%>= TILESIZE% //if the speed is higher than this we might fall through a tile
self.vely% = TILESIZE%
ENDIF

self.lockjump = TRUE; //don't allow jumping after falling of an edge
ENDIF
ENDIF

self.slope_prevtilex = (self.x% + ASR(self.w%,1)) / TILESIZE%
self.slope_prevtiley = (self.y% + self.h%) / TILESIZE%
RETURN FALSE
ENDFUNCTION
ENDTYPE

LOCAL spr_t%[]; DIM spr_t%[7]
LOCAL spr_player%[]; DIM spr_player%[2]
LOCAL spr_background%
LOCAL map AS TMap
LOCAL player AS TPlayer

LIMITFPS 30
SETTRANSPARENCY RGB(255,0,255)
spr_t%[0]=GENSPRITE(); LOADSPRITE "Media/gfx/t1.bmp",spr_t%[0]
spr_t%[1]=GENSPRITE(); LOADSPRITE "Media/gfx/t2.bmp",spr_t%[1]
spr_t%[2]=GENSPRITE(); LOADSPRITE "Media/gfx/t3.bmp",spr_t%[2]
spr_t%[3]=GENSPRITE(); LOADSPRITE "Media/gfx/tsloper.bmp",spr_t%[3]
spr_t%[4]=GENSPRITE(); LOADSPRITE "Media/gfx/tslopel.bmp",spr_t%[4]
spr_t%[5]=GENSPRITE(); LOADSPRITE "Media/gfx/t6.bmp",spr_t%[5]
spr_t%[6]=GENSPRITE(); LOADSPRITE "Media/gfx/t7.bmp",spr_t%[6]
spr_player%[0]=GENSPRITE(); LOADSPRITE "Media/gfx/left.bmp",spr_player%[0]
spr_player%[1]=GENSPRITE(); LOADSPRITE "Media/gfx/right.bmp",spr_player%[1]
spr_background%=GENSPRITE(); LOADSPRITE "Media/gfx/bg.bmp",spr_background%

IF map.TMap_Initialise(spr_t%[])=FALSE
DEBUG "Map could not be initialised"
RETURN FALSE
ENDIF

IF map.loadMap("Media/maps/map01.map")=FALSE
DEBUG "Map could not be loaded"
RETURN FALSE
ENDIF

player.TPlayer_Initialise(spr_player%[],map)

WHILE TRUE
//update objects
player.think()


//draw everything
DRAWSPRITE spr_background%,0,0
map.TMap_draw()
player.TPlayer_draw()
SHOWSCREEN
WEND


Example #3 :

Code can now be downloaded

[attachment deleted by admin]
Title: Re: JNR Platform Example Code
Post by: bigsofty on 2010-Jun-29
Very interesting, its a great tutorial, well done!
Title: Re: JNR Platform Example Code
Post by: MrTAToad on 2010-Jun-29
Thanks for that.  There is one more tutorial left to do now, and that's with scrolling.

One thing I should mention is that due to use extended types and NOT, the code will only work with V8
Title: Re: JNR Platform Example Code
Post by: com_1 on 2010-Jun-29
Congratulations. (how long(time) have you killed for this ?)

By the way, "Example 2" is slower than "Example 1".

I have no time to understand your code, but I think that the problem relates to the definition of position on the diagonal. (right ?)

Need help ?
Title: Re: JNR Platform Example Code
Post by: Wampus on 2010-Jun-29
This might well come in useful. Thanks.  :)
Title: Re: JNR Platform Example Code
Post by: MrTAToad on 2010-Jun-30
QuoteCongratulations. (how long(time) have you killed for this ?)
I used LIMITFPS to slow down the program - remove or change the value of 30 to -1 to get the program to run as fast as possible.

QuoteThis might well come in useful. Thanks.
Glad you like it!