TGC Codebase Backup



2D Tile based scrolling with collision by pizzaman

30th Apr 2005 19:06
Summary

An example of how to scroll a tile based map in 2D, with collision implemented.



Description

This is an example of how to scroll a tile based map in 2D, with collision implemented. It uses data staments to store the map which is then transferred to an array. The array is then used to display the correct images on screen.

For the player (a box), 4 collision points are set around it, and if one of the collision points detects a tile that it cannot pass through, the players coordinates are set back to the coordinates before they collided.



Code
                                    ` This code was downloaded from The Game Creators
                                    ` It is reproduced here with full permission
                                    ` http://www.thegamecreators.com
                                    
                                    `****************************************************************************************
`**************************** HOW TILE BASED COLLISION WORKS ****************************
`****************************          by pizzaman           ****************************
`**************************** Date:             14/6/04      ****************************
`**************************** Time to complete: 1 hour       ****************************
`****************************************************************************************

`THE PRINCIPLE -
`The visible screen is devided up into boxes which we call tiles. This means that each
`tile is sitting on a range of coordinates. For example a 20 pixel by 20 pixel tile that
`is the top-left most tile is sitting on the coordinates (0,0) to (19,19). Since we know
`each tile is sitting on a range of coordinates we can check to see whether a sprite (or
`image) has the same coordinates as the tile. However, we must take into consideration
`that a sprite (or image) is usually bigger than a pixel, so we have to form a collision
`box around the sprite.

`HOW TO IMPLEMENT A TILE BASED COLLISION SYSTEM -
`Firstly we need a 2 dimensional array to store tile data in (Tile data is the image
`numbers of the of the images you are going to use; e.g. 1 could be a wall, 2 could be
`a grass image etc. Note: the image sizes should be the same as the tile size you will
`use.
`Secondly we store the data in the array. Note: you could already store data in an array
`and just load the array - this would be faster for loading times.
`Next we create some collision points around our sprite, usually 4 points which would
`be placed at the corners of the sprite creating a collision box, you could have more
`collision points if a collision box is not suitable.
`Next we have to figure out which tile in the array each collision point is on to do
`this you have to use the formula
`
`                 Tile = (x / Tile Sise, y / Tile size)
`
`                 where Tile is the tile in the array, x is the x coordinate of the
`                 collision point your checking for and y is the y coordinate of the
`                 collision point your checking for
`
`Now if the tile is a tile you dont want your sprite to go on; just reset the sprites
`coordinates to what they were before the collision and voila!!!! you've got tile
`based collision.

`****************************************************************************************
`*********************************** SCROLLING THE MAP **********************************
`****************************************************************************************

`THE PRINCIPLE -
`To scroll a tile based map, you simply have to move all the tiles in the given direction
`then when the tiles have moved more pixels then the size of the tiles, the tiles get
`reset to the positions they were in before they were moved, but their images changed
`to simulated the tiles have moved, and generating a seamless transition.

`HOW TO IMPLEMENT A SCROLLING TILE BASED MAP -
`Instead of using a system that relies on screen coordinates, we need to build our map
`on a global coordinate system where coordinates (0,0) is the first tile (usually the
`top-left most tile). Everything we do will be based in this global coordinate system,
`which will include the player and their collision system. The coordinates then will be
`transferred to screen coordinates, where the images can be pasted correctly.
`Note that an extra row and column of tiles are pasted to the screen, this is to
`generate the seamless scrolling.



`****************************************************************************************
`*********************************** Start of program ***********************************
`****************************************************************************************


`Declare some constants, they are constants because their values will not change
`thoughout the program. There are placed at this point in the program to be easily
`modified.
#constant SCREENSIZEX 1024
#constant SCREENSIZEY 768

`gosubs are here instead of actual code, just to make things look neat
gosub DeclareVariables
gosub screensetup
gosub createimages
gosub loaddata
gosub makeplayer

`start the main loop
do

   `exit the loop if space bar is pressed - effectively ending the program
   `Note: it has to be place here and not in a gosub as it wont work
   if spacekey() = 1 then exit

   gosub controls
   gosub collision
   gosub ScrollScreen
   gosub displayplayer
   gosub DisplayDebugInfo

   `update screen
   sync

loop

gosub cleanup

`end the program
end



`****************************************************************************************
`************************************* End of program ***********************************
`****************************************************************************************



DeclareVariables:

   `store how many horizontal/verticle tiles there are
   NumOfTilesX = 40
   NumOfTilesY = 30

   `store the size (in pixels) of a tile
   TileSizeX = 32
   TileSizeY = 32

   `store the speed of the tile
   SpeedX = 2
   SpeedY = 2

   `store the coordinates that will make the player be at the centre of the screen
   ScreenCentreX = SCREENSIZEX / 2
   ScreenCentreY = SCREENSIZEY / 2

   `Work out out how many tiles there are on screen for x and y directions
   TilesPerScreenX = SCREENSIZEX / TileSizeX
   TilesPerScreenY = SCREENSIZEY / TileSizeY

   `Works out if a tile is partially visible on screen, if so then add 1 to the
   `TilesPerScreen variable. This has the affect of drawing a whole column or/and row
   `of tiles to the screen, to cover up visual anomalies with tile sizes that do
   `not divide exactly into the screen resolution
   if SCREENSIZEX mod TileSizeX then TilesPerScreenX = TilesPerScreenX + 1
   if SCREENSIZEY mod TileSizeY then TilesPerScreenY = TilesPerScreenY + 1

   `Calculates the largest co-ordinates the map can scroll too
   MaxMapX = (TileSizeX * NumOfTilesX) - (SCREENSIZEX + SpeedX)
   MaxMapY = (TileSizeY * NumOfTilesY) - (SCREENSIZEY + SpeedY)

   `store predefined coordinates of player
   X1 = ScreenCentreX
   Y1 = ScreenCentreY

   `store predefined coordinates of map
   MapX = 0
   MapY = 0

return



screensetup:

   `delete anything stored in video memory
   flush video memory

   `set screen resolution
   set display mode 1024, 768, 32

   `setup syncing
   sync on
   sync rate 150

return



createimages:

   `create a green box
   box 0, 0, TileSizeX, TileSizeY, rgb(0, 255, 0), rgb(0, 225, 0), rgb(0, 225, 0), rgb(0, 200, 0)
   get image 1, 0, 0, TileSizeX, TileSizeY, 1

   `create a yellow/orange box as the player
   box 0, 0, TileSizeX, TileSizeY, rgb(255, 255, 0), rgb(255, 128, 0), rgb(255, 128, 0), rgb(255, 255, 0)
   get image 2, 0, 0, TileSizeX, TileSizeY, 1

   `create a red box
   cls rgb(255, 0, 0)
   get image 10, 0, 0, TileSizeX, TileSizeY, 1

   `create a blue box
   box 0, 0, TileSizeX, TileSizeY, rgb(0, 0, 255), rgb(0, 0, 225), rgb(0, 0, 225), rgb(0, 0, 200)
   get image 11,0,0,TileSizeX,TileSizeY,1

return



loaddata:

   `create an array called TileMap - we will use this to store the tile data.
   `an array is like a file cabinet - in each drawer of the file cabinet there are
   `folders that hold our data. Therefore in our "file cabinet" we have 20 drawers; and
   `inside of each drawer we 15 folders; which hold data we want to access.
   `Also arrays can have up to 5 dimensions (ours has 2 dimensions) - you can visualise
   `this as being more folders inside of the other folders and so on; and of course you
   `can just have 1 dimension array as well (in other words just the drawers)
   dim TileMap(NumOfTilesX - 1, NumOfTilesY - 1)

   `load data into the array - basically the computer will read 20 bits of data from one
   `data statment (at the end of the program), then move onto the next data statment
   `until all 15 data statments are read. Note: all 15 data statments could have been
   `combined into one large data statment of 300 items; the reason why its not currently
   `like that is its hard to read also you can kind of see the map the way its currently
   `written.
   for y = 0 to NumOfTilesY - 1

      for x = 0 to NumOfTilesX - 1

         read TileMap(x, y)

      next x

   next y

return



makeplayer:

   `make the player
   sprite 1, X1, Y1, 2

   `this sets the sprites properties; by placing a 0 in the backsave flag the background
   `behind sprite will not be saved by the computer, hence will speed up our program a
   `little. We are not using the backsave feature because we will be replacing the
   `background ourselves; this is due to when a sprite is created the 3d backdrop is
   `automatically created (a blue screen wiping any 2d images previously draw to the
   `screen) and cannot be turned off as long as you have a sprite on screen.
   set sprite 1, 0, 1

return



controls:

   `store the map's x and y coordinates into the variables OldMapX and OldMapY
   OldMapX = MapX
   OldMapY = MapY

   `store the x and y coordinates of the player into the variables oldX1 and oldY1
   OldX1 = X1
   OldY1 = Y1

   `makes the player move by altering the sprites coordinates
   `makes the map move by altering the maps coordinates, if the player is roughly at the
   `centre of the screen
   if leftkey()  = 1
      if MapX < X1 - (ScreenCentreX - 5) and MapX > X1 - (ScreenCentreX + 5) then MapX = MapX - SpeedX
      X1 = X1 - SpeedX
   endif
   if rightkey() = 1
      if MapX < X1 - (ScreenCentreX - 5) and MapX > X1 - (ScreenCentreX + 5) then MapX = MapX + SpeedX
      X1 = X1 + SpeedX
   endif
   if upkey()    = 1
      if MapY < Y1 - (ScreenCentreY - 5) and MapY > Y1 - (ScreenCentreY + 5) then MapY = MapY - SpeedY
      Y1 = Y1 - SpeedY
   endif
   if downkey()  = 1
      if MapY < Y1 - (ScreenCentreY - 5) and MapY > Y1 - (ScreenCentreY + 5) then MapY = MapY + SpeedY
      Y1 = Y1 + SpeedY
   endif

return



collision:

   `NOTE : you may have noticed the player sticking to the blocks, to stop this make the
   `       collision area of the player smaller and edit the players image to the same
   `       same size (so it doesn't look weird). You have to make the change in the
   `       players x and y sizes greater or equal to their speed or you wont notice a
   `       difference.



   `the following 8 lines of code stores the old and new left,right,top,bottom edges of
   `the sprite in the respective variables

   `gets which tiles the current left and right co-ordinates of the player are on
   LeftTileNum   = int(X1 / TileSizeX)
   RightTileNum  = int((X1 + (TileSizeX - 1)) / TileSizeX)
   `gets which tiles the current top and bottom co-ordinates of the player are on
   TopTileNum    = int(Y1 / TileSizeY)
   BottomTileNum = int((Y1 + (TileSizeY - 1)) / TileSizeY)

   `gets which tiles the old left and right co-ordinates of the player are on
   OldLeftTileNum   = int(oldX1 / TileSizeX)
   OldRightTileNum  = int((oldX1 + (TileSizeX - 1)) / TileSizeX)
   `gets which tiles the old top and bottom co-ordinates of the player are on
   OldTopTileNum    = int(oldY1 / TileSizeY)
   OldBottomTileNum = int((oldY1 + (TileSizeY - 1)) / TileSizeY)


   `this is where we check for those 4 collision points; if the collision point is on a
   `tile you are not able to pass through (in our case a tile that has an image number
   `of 10 or greater), then the old coordinates of the sprite are restored.
   `Note: to have sliding collision we must check the the current x tile against the
   `old y tile and vice versa.

   `checks for top-left collision point
   if TileMap(LeftTileNum,    OldTopTileNum)     >= 10 then X1 = oldX1 : MapX = OldMapX
   if TileMap(OldLeftTileNum, TopTileNum)        >= 10 then Y1 = oldY1 : MapY = OldMapY

   `checks for top-right collision point
   if TileMap(RightTileNum,    OldTopTileNum)    >= 10 then X1 = oldX1 : MapX = OldMapX
   if TileMap(OldRightTileNum, TopTileNum)       >= 10 then Y1 = oldY1 : MapY = OldMapY

   `checks for bottom-left collision point
   if TileMap(LeftTileNum,    OldBottomTileNum)  >= 10 then X1 = oldX1 : MapX = OldMapX
   if TileMap(OldLeftTileNum, BottomTileNum)     >= 10 then Y1 = oldY1 : MapY = OldMapY

   `checks for bottom-right collision point
   if TileMap(RightTileNum,    OldBottomTileNum) >= 10 then X1 = oldX1 : MapX = OldMapX
   if TileMap(OldRightTileNum, BottomTileNum)    >= 10 then Y1 = oldY1 : MapY = OldMapY

   `stops player from scrolling past the edges of the map by resetting the maps
   `global coordinates to their maximum values. The maximum values are calculated by
   `(TileSizeX * NumOfTilesInRow) - SpeedX               - for MapX
   `(TileSizeY * NumOfTilesInColumn) - SpeedY            - for MapY
   if MapX < 0        then MapX = 0
   if MapX >= MaxMapX then MapX = MaxMapX
   if MapY < 0        then MapY = 0
   if MapY >= MaxMapY then MapY = MaxMapY

return



ScrollScreen:

   `works out the tile number within array to start displaying tiles at
   TileNumX = MapX / TileSizeX
   TileNumY = MapY / TileSizeY

   `cache calculation to tempX and TempY variables for a minor speed increase
   `this calculation works out how many pixels to offset the position where the tile
   `images should be placed.
   TempX = MapX - (TileNumX * TileSizeX)
   TempY = MapY - (TileNumY * TileSizeY)

   `reset values
   Temp1 = 0
   Temp2 = 0

   `Calculates if the map has scrolled to the last tile, if so we must remove a column
   `or/and row of tiles from the screen as they have no more map data to draw; also
   `because of this it would create an error
   if TilesPerScreenX + TileNumX >= NumOfTilesX then Temp1 = 1
   if TilesPerScreenY + TileNumY >= NumOfTilesY then Temp2 = 1

   `this pastes the images to screen in the correct order by working out where to start
   `pasting the images from in the array, then displaying the images at the correct
   `screen coordinates
   for y = 0 to TilesPerScreenY - Temp2

      for x = 0 to TilesPerScreenX - Temp1


         paste image TileMap(x + TileNumX, y + TileNumY), (x * TileSizeX) - TempX, (y * TileSizeY) - TempY

      next x

   next y

return



displayplayer:

   `display sprite
   sprite 1,X1 - MapX,Y1 - MapY,2

return



DisplayDebugInfo:

   text 0, 0,"FPS  " + str$(screen fps())
   text 0,20,"MapX " + str$(MapX)
   text 0,40,"MapY " + str$(MapY)
   text 0,60,"X1   " + str$(MaxMapX)
   text 0,80,"Y1   " + str$(MaxMapY)

   text 100, 0,"Press spacebar to exit"

return



cleanup:

   `delete any media involved in this program
   for x = 1 to 11
      if image exist(x) then delete image x
      if sprite exist(x) then delete sprite x
   next x

   `delete anything thats left in the video memory - if anything
   flush video memory

return




`store tile data at the end of the program so its out of the way
data 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10
data 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,10
data 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,10
data 10, 1, 1,11,11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,11,11, 1, 1, 1, 1, 1, 1,11,11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,11,11, 1, 1,10
data 10, 1, 1,11,11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,11,11, 1, 1, 1, 1, 1, 1,11,11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,11,11, 1, 1,10
data 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,10
data 10, 1, 1,11, 1,11, 1, 1, 1, 1, 1, 1, 1, 1,11, 1,11, 1, 1, 1, 1, 1, 1,11, 1,11, 1, 1, 1, 1, 1, 1, 1, 1,11, 1,11, 1, 1,10
data 10, 1, 1,11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,11, 1, 1, 1, 1, 1, 1,11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,11, 1, 1,10
data 10, 1, 1,11, 1,11, 1, 1, 1, 1, 1, 1, 1, 1,11, 1,11, 1, 1, 1, 1, 1, 1,11, 1,11, 1, 1, 1, 1, 1, 1, 1, 1,11, 1,11, 1, 1,10
data 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,10
data 10, 1, 1,11,11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,11,11, 1, 1, 1, 1, 1, 1,11,11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,11,11, 1, 1,10
data 10, 1, 1,11,11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,11,11, 1, 1, 1, 1, 1, 1,11,11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,11,11, 1, 1,10
data 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,10
data 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,10
data 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,10
data 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,10
data 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,10
data 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,10
data 10, 1, 1,11,11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,11,11, 1, 1, 1, 1, 1, 1,11,11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,11,11, 1, 1,10
data 10, 1, 1,11,11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,11,11, 1, 1, 1, 1, 1, 1,11,11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,11,11, 1, 1,10
data 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,10
data 10, 1, 1,11, 1,11, 1, 1, 1, 1, 1, 1, 1, 1,11, 1,11, 1, 1, 1, 1, 1, 1,11, 1,11, 1, 1, 1, 1, 1, 1, 1, 1,11, 1,11, 1, 1,10
data 10, 1, 1,11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,11, 1, 1, 1, 1, 1, 1,11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,11, 1, 1,10
data 10, 1, 1,11, 1,11, 1, 1, 1, 1, 1, 1, 1, 1,11, 1,11, 1, 1, 1, 1, 1, 1,11, 1,11, 1, 1, 1, 1, 1, 1, 1, 1,11, 1,11, 1, 1,10
data 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,10
data 10, 1, 1,11,11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,11,11, 1, 1, 1, 1, 1, 1,11,11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,11,11, 1, 1,10
data 10, 1, 1,11,11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,11,11, 1, 1, 1, 1, 1, 1,11,11, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,11,11, 1, 1,10
data 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,10
data 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,10
data 10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10