2d Tile map collision help

BlitzMax Forums/BlitzMax Beginners Area/2d Tile map collision help

RetroRusty(Posted 2009) [#1]
Hi,

Can someone please explain how collisions work using a simple map array and a collision map.

I have been told that this is the best way to do collision, but if someone has a simple example of how to do this, it would really help me out.

Thanks


ImaginaryHuman(Posted 2009) [#2]
Usually you have a `visible tilemap` comprising images which you draw on-screen, and an `invisible collision map` which is often at a higher resolution, against which you do collision tests.

At its simplest, all you are doing is finding the bounding rectagles around the player/enemy - which is a simple matter of taking their x,y coordinates in map-space and deciding how many collision tiles they cover and checking all of those tiles to see if it is `solid` or `pass through`. Then when you've found out which tiles around the player/enemy are blocking it from moving in a certain direction, you can take action or not allow movement in that direction.

You could also simply get the tile in the collision map which the character/enemy is close to, treat it like a bounding rectangle, and see if the two overlap.

So as a real basic example, maybe you check the tile which would be immediate below the feet of your game character. If that tile is considered solid, you do not allow the player to move down the screen - ie he `lands on` the tile. He can walk left and right on the tiles or jump up but he can't go down. Then you also check to the left, right, and above, to see if he can move left, right, or whether he's bumping his head.

If it helps, instead of thinking of your game world as 2d viewed from the side, imagine it as viewed from above, where you are navigating around a maze of walls and you keep bumping into some. You simply are trying to only allow movement if the tile in the direction of your movement is not solid.


Amon(Posted 2009) [#3]
Here's my plat code. Using tiles. Tiles can be read in via data statements or via a tile map editor.

Thanks go to jesse and a few other peeps for helping me to get it to work. Jesse being the guy that provided the "mod" based landing on tile code. :)

Enjoy! :)

Also after this code I'll also post the code to a simple Single Screen Tilemap editor which reads and saves files. Hopefully it should get you on your way to making that next great platform game or tilebased game. :)

http://www.lunaticninja.com/storage/plat_tiles.rar

SuperStrict

Graphics 800 , 600

Global Tiles:TImage = LoadImage("tile.png") 
Global Player:TImage = LoadImage("player1632.png") 

Const MAPWIDTH:Int = 800/32
Const MAPHEIGHT:Int = 600/32

Global Map:Int[MAPWIDTH , MAPHEIGHT]

Global PlayerX:Float
Global PlayerY:Float

Global Player_Width:Int = 16
Global Player_Height:Int = 32

Global CheckPlayerPosition:Int = 0

Global Direction:Int = -1

Global Jump:Int = 0
Const Gravity:Float = 0.2
Global JumpHeight:Float = 5.3
Global CanJump:Int = 1
Global Falling:Int = 0

ReadLevelData() ' We Read the level data

While Not KeyHit(KEY_ESCAPE)
	
	Cls
	
	DrawMap()
	CheckIfPlayerCollideWithTile()
	DrawPlayer()
	MovePLayer()
	DoJump()
	
	
	Flip
	
Wend

'######################################################
'#### Function DrawPLayer()							  #
'#### Draws Player to Screen and gets Player Position #
'######################################################
Function DrawPlayer()
	If CheckPlayerPosition = 0 
							   		
	For Local x:Int = 0 Until MAPWIDTH
		For Local y:Int = 0 Until MAPHEIGHT
			Select Map[x, y]
				Case 2
					PlayerX = getX(x) 
					PlayerY = getY(y) 
					CheckPlayerPosition = 1
			End Select
		Next
	Next
	EndIf
	DrawText "PlayerX = " +PlayerX , 0 , 20 
	
	DrawImage Player, PlayerX , PlayerY , 0	
End Function

'#################################################
'###	Function MovePLayer()					 #	
'## 	Moves Player and checks outer boundaries #	
'#################################################
Function MovePLayer()
	
	If KeyDown(KEY_LEFT) And Not KeyDown(KEY_RIGHT)
			Direction = 0
			PlayerX:-2
	ElseIf KeyDown(KEY_RIGHT) And Not KeyDown(KEY_LEFT)
			Direction = 1
			PlayerX:+2
	End If
	
	If PlayerX - 32 <= 0
		PlayerX:+2
	ElseIf PlayerX >= 800 - 32
		PlayerX:-2
	End If
End Function

'##########################################################
'### Function CheckIfPlayerCollideWithTile()			  #
'### Checks to see if the player has collided with a tile #
'### Checks also if the Player is falling                 #
'##########################################################
Function CheckIfPlayerCollideWithTile()
'#Region 
	Select Direction
		Case 1
		If Jump = 0
			If Map[(PlayerX - Player_Width) / 32 + 1, PlayerY / 32]= 1
				PlayerX:-2
			EndIf
		EndIf
		If Jump = 1 and PlayerX >= 784 - 32
			If Map[(PlayerX - Player_Width) / 32 + 1, PlayerY / 32]= 1
				PlayerX:-2
			EndIf
		End If
		Case 0
		If Jump = 0
			If Map[(PlayerX + Player_Width) / 32, PlayerY / 32]= 1
				PlayerX:+2
			End If
		EndIf
	End Select
		
'#End Region 

	If Falling = 1
		PlayerY:+3.2
		If Map[(PlayerX + Player_Width) / 32, PlayerY / 32 + 1]= 1 or Map[(PlayerX - Player_Width * 2) / 32 + 1, PlayerY / 32 + 1]= 1
			
			If (PlayerY mod 32.0) <= 6.4
			                             
				PlayerY = PlayerY - (PlayerY mod 32) 
					
						Jump = 0                        
						Falling = 0
						CanJump = 1
						JumpHeight = 5.5
								
			EndIf
		EndIf
	End If
	
	If not Map[(PlayerX + Player_Width) / 32, PlayerY / 32 + 1]and not Map[(PlayerX - Player_Width * 2) / 32 + 1, PlayerY / 32 + 1]
		If Jump = 0
			Falling = 1
		End If
	End If
	
	
		
End Function

'##############################################
'### Function DoJump()                        #
'### Makes our player jump                    #
'##############################################
Function DoJump()
	If KeyHit(KEY_SPACE) and CanJump = 1
		Jump = 1
		CanJump = 0
	End If
	
	If Jump = 1
		PlayerY:-JumpHeight
		JumpHeight:-Gravity
		If JumpHeight <= - 1.0 or PlayerY < 32
			Falling = 1
		EndIf
	End If
	
	
End Function

'########################################
'### Function DrawMap()                 #
'### We Draw the Map to screen          #
'########################################
Function DrawMap()
	For Local x:Int = 0 Until MAPWIDTH
		For Local y:Int = 0 Until MAPHEIGHT
			Select Map[x, y]
				Case 1
					DrawImage Tiles, x * 32, y * 32, 0
			End Select
		Next
	Next
End Function

'#######################################################
'### Function ReadLevelData()            			   #
'### We read the data stored in the defdata statements #
'#######################################################
Function ReadLevelData()
	For Local y:Int = 0 Until MAPHEIGHT
		For Local x:Int = 0 Until MAPWIDTH
			Local Data:Int
			ReadData Data
			Map[x, y]= Data
		Next
	Next
End Function

Function getX:Int(x:Int)
   Return 32 * x '+ offsetX
End Function

Function getY:Int(y:Int)
   Return 32 * y '+ offsetX
End Function

DefData 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
DefData 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
DefData 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1
DefData 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
DefData 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1
DefData 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
DefData 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1
DefData 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
DefData 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
DefData 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
DefData 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1
DefData 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
DefData 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1
DefData 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
DefData 1, 0, 0, 0, 2, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1
DefData 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1
DefData 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1
DefData 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




Here is the very simple Single Screen Tilemap Editor.

http://www.lunaticninja.com/storage/TileMapEditor.zip

'// A simple TileMap Editor which saves and Loads Maps.
'// Programmed Amon
'// v1.0

SuperStrict

'// We setup our Graphics Mode
Graphics 800, 600

'// We Load our images, 6 frames in an AnimImage
Global Tiles:TImage = LoadAnimImage("tiles.png", 32, 32, 0, 6) 

'// We setup 2 Constants to hold our MapWidth and MapHeight.
Const MAPWIDTH:Int = 25 '// 800/32 = 25 tiles going across the screen
Const MAPHEIGHT:Int = 18 ' 600/32 = 18 tiles going down the screen

Const TILESIZE:Int = 32 '// Our tiles are 32x32 pixels square

'// We setup an Array to hold our map data
Global MapArray:Int[MAPWIDTH, MAPHEIGHT] 

'// We init and prefill the MapArray with a value
For Local x:Int = 0 Until MAPWIDTH
	For Local y:Int = 0 Until MAPHEIGHT
		MapArray[x, y] = - 1
	Next
Next

'// We set a Global to store the current tile number
Global TileSelected:Int = 0

'// We create another variable for holding the mapfile number we save with
'// This will increase by 1 everytime we save a map.
Global iter:Int = 0

'// We setup our loop
While Not KeyHit(KEY_ESCAPE) 
	Cls
		DrawMap() 
		SelectTile() 
		Place_Tile() 
		
		SaveMap() 
		LoadMap() 
	Flip
Wend

'// Our DrawMap Function
Function DrawMap() 
	For Local y:Int = 0 Until MAPHEIGHT
		For Local x:Int = 0 Until MAPWIDTH
			'//There are 2 ways we can draw the map to screen
			'// We can use the array data to to select which frame we draw
			'// or we can write if or case statements for the individual tile.
			'// I'll show both below.
			
			'// Method 1 - Draw with frame data from array. Comment out the line below and comment
			'// The second method to see how each works
			
			'DrawImage Tiles, x * TILESIZE, y * TILESIZE, MapArray[x, y] 
			
			'// above we do x*Tilesize, y * Tilesize because as the for loop loops it goes from
			'// 0 to MAPWIDTH/MAPHEIGHT which is 25 and 18. So , 0 * Tilesize(32) is 0, 1*32 = 32 and so on and it will draw
			'// the tiles according to where they are in the array.
		
			'// Method 2 - If statements
			If MapArray[x, y] = 0
				DrawImage Tiles, x * TILESIZE, y * TILESIZE, 0
			ElseIf MapArray[x, y] = 1
				DrawImage Tiles, x * TILESIZE, y * TILESIZE, 1
			ElseIf MapArray[x, y] = 2
				DrawImage Tiles, x * TILESIZE, y * TILESIZE, 2
			ElseIf MapArray[x, y] = 3
				DrawImage Tiles, x * TILESIZE, y * TILESIZE, 3
			ElseIf MapArray[x, y] = 4
				DrawImage Tiles, x * TILESIZE, y * TILESIZE, 4
			ElseIf MapArray[x, y] = 5
				DrawImage Tiles, x * TILESIZE, y * TILESIZE, 5
			EndIf
		Next
	Next
End Function

'// Our SelectTile Function
Function SelectTile() 
	'// If we hit the space bar we increase the tileSelected variable by 1
	'// if Tileselected = 2 then we know we will be drawing image frame 2 to the screen.
	'// if Tileselected is Greater than 5 (how many images we have in our animimage) then
	'// we set it to 0.

	If KeyHit(KEY_SPACE) 
		TileSelected:+1
		If TileSelected > 5 Then TileSelected = 0
	End If
End Function


'// Our Place_Tile Function
Function Place_Tile() 
	If MouseX() > 0 And MouseX() < 800 '// if the mouse in the screen boundaries
		If MouseY() > 0 And MouseY() < 600 - 50 '// if the mouse is within the screen and array boundaries
			If MouseDown(MOUSE_LEFT)   '// If we hit or hold MouseLeft
				'// What this next line does is find what position we are within the MapArray
				'// and places a tile in that cell. For example if mousex() position = 64 and MouseY() position = 64
				'// and we divide by our TileSize then we know that 64/32 = 2 so our mouse will place a tile
				'// in the place in the MapArray and that screen location.
				MapArray[MouseX() / TILESIZE, MouseY() / TILESIZE] = TileSelected
			End If
		End If
	End If
End Function


'// Our LoadMap Function
Function LoadMap() 
	If KeyHit(KEY_F5) 
		Local MapFile:String = RequestFile("MapFile", "map")    '//We load the map file
		Local FileToRead:TStream = ReadFile(MapFile) 
		If FileToRead '// if the mapfile has loaded
			For Local y:Int = 0 Until MAPHEIGHT
				For Local x:Int = 0 Until MAPWIDTH
					MapArray[x, y] = ReadInt(FileToRead) 
				Next
			Next
		End If
		
	End If
End Function

'// Our SaveMap Function
Function SaveMap() 
	'// Below we save our Map when we press F6
	If KeyHit(KEY_F6) 
		Local Mapfile:String = "Map" + "_" + iter + ".map" '// Create a MapFile name add an underscore and what
												  '// iter equals to the end of it
		
		Local Filewrite:TStream = WriteFile(MapFile)  '// We tell max that we want to write a stream called 
		If Filewrite ' if the stream exists
			For Local y:Int = 0 Until MAPHEIGHT '// Loop through our array
				For Local x:Int = 0 Until MAPWIDTH'// and save the map data to the file
					WriteInt FileWrite, MapArray[x, y]  '// by writing ints to file
				Next
			Next
		EndIf
		CloseFile FileWrite '// We close the file when we've finished with it
		iter:+1 '// add 1 to iter
		Cls
		DrawText "MapSaved", 380, 585 '// Display a map save text
		Flip
		Delay 1000
	End If
End Function



Hope it helps. :)


Jesse(Posted 2009) [#4]
and here is a really simplified version in case you need more help:


edited - been messing with this:

it will work even if you change the tilesize.


I have a better example in the code archives:
http://www.blitzbasic.com/codearcs/codearcs.php?code=2235