sigh, more tile based collision detection, sorry!

BlitzPlus Forums/BlitzPlus Programming/sigh, more tile based collision detection, sorry!

IanUK(Posted 2013) [#1]
Hi

Sorry, yet another tb collision post! But at least my code works! I was reading this article and based my code on roughly on theirs: http://www.tonypa.pri.ee/tbw/tut05.html

I know my code is messy and could be reduced considerably, but I just wanted to get something working and i'll refactor it when ive got somewheres. Im not a coding noob and I dont like copying and pasting unless I know what its doing, so most of the code is mine, the movement code i wrote from reading a sonic guide.

Its 4am so forgive me, but if you have a look at my CheckCollision2(), it basically calls GetMyCorners(curX, newY), then does Y detection, then calls GetMyCorders(newX, newY) and does X detection.

I cant understand why I need to call GetMyCorners a third time and add 1 to newX. I know why it works, because otherwise its 1px short of detecting the right hand tile, but the guide above doesnt have to do this.

Anyway, any help would be useful, and heres some code:

oh you'll need a "graphics\tiles1.png" 32x32, just one tile tho

Cheers!


Const SCR_W# = 400
Const SCR_H# = 400

Graphics SCR_W, SCR_H

SetBuffer BackBuffer()

Type Player
	Field w, h
	Field MapX#, MapY#, ScrX#, ScrY#
	Field XSpeed#, YSpeed#, MoveX#, MoveY#
	Field Airborne, Jumping, AbleToJump
	Field upY, downY, leftX, rightX, standingY
	Field topLeftType, topRightType, bottomLeftType, bottomRightType
	Field standingLeftType, standingRightType
	Field xtile, ytile
End Type

Global p.Player = New Player
PrepPlayerStart()

GameTimer = CreateTimer(60)

Global FloorY = 580

Color 255,255,255


; math functions


Function Min(n1%, n2%)
	If n1% < n2% Then Return n1% Else Return n2%
End Function

Function MinFloat#(n1#, n2#)
	If n1# < n2# Then Return n1# Else Return n2#
End Function


Function Max(n1%, n2%)
	If n1% > n2% Then Return n1% Else Return n2%
End Function

Function MaxFloat#(n1#, n2#)
	If n1# > n2# Then Return n1# Else Return n2#
End Function


;;;; MOVE VARS
;Global XSpeed# = 0
Global Acc# = 0.046875
Global Dec# = 0.5
Global Frc# = 0.046875
Global MaxXSpeed# = 6

Global Gravity# = 0.21875

Global JumpSpeed# = -6
Global SmallJumpSpeed# = -4
Global MaxYSpeed# = 12

;;;; IN-AIR VARS
Global AirMaxXSpeed# = 6
Global Air# = 0.09375 ; acc and speed really, no such thing as AirSpeed or frc or dec, just 'air'

;;;;  MAP VARS
Global CAM_W# = SCR_W
Global CAM_H# = SCR_H

Global CamX# = 0
Global CamY# = 0

Global TILE_SIZE = 32
Global MAP_W = 20 ; in tiles
Global MAP_H = 20

Dim map(MAP_H,MAP_W)
Dim tileType(MAP_H,MAP_W)

Const TILE_SOLID = 1
Const TILE_EMPTY = 0

ReadMapData()
ReadTileTypeData()

Global NumTiles = 1

Global img_tiles = LoadAnimImage("graphics\tiles1.png",32,32,0,NumTiles)

Global MapWidthPx = MAP_W * TILE_SIZE
Global MapHeightPx = MAP_H * TILE_SIZE

ClsColor 164,211,238

;;;; MAIN LOOP
While Not KeyHit(1)
	
	UpdatePlayerVel()
	
	; gravity
	ApplyGravity()
	
	; set move distances to speed increment
	p\MoveX = p\XSpeed
	p\MoveY = p\YSpeed
	
	; reduce move distances if going out of map bounds
	EnforceMapBounds2()
	
	; reduce move distances if collision occurs
	CheckCollision2()
	
	; add move distances to map position
	UpdatePlayerMapPos()
	
	; position camera around player
	UpdateCamPos()
	
	; move player screen position according to how much camera has moved
	UpdatePlayerScrPos()
	
	DrawMap()
	
	Rect p\ScrX, p\ScrY, 10, 10, 1
	
	;Line 0,300,399,300
	Text 0,10,"XSpeed:" + p\XSpeed + " | YSpeed: " + p\YSpeed
	Text 0,23,"ScrX:"+p\ScrX+" | ScrY:"+p\ScrY
	Text 0,36,"MoveX:"+p\MoveX+" | MoveY:"+p\MoveY
	Text 0,49,"MapX:"+p\MapX+"|MapY:"+p\MapY
	WaitTimer GameTimer

	Flip
	Cls
Wend

End

Function UpdatePlayerMapPos()
	
	p\MapX = p\MapX + p\MoveX
	p\MapY = p\MapY + p\MoveY
	p\xtile = Floor(p\MapX / TILE_SIZE)
	p\ytile = Floor(p\MapY / TILE_SIZE)
	;Print p\MapY
	;WaitKey
End Function

Function PlayerLanded()
	p\AbleToJump = True
	p\Jumping = False
	p\Airborne = False
	p\YSpeed = 0
	JumpKeyPressed = False
End Function

Function GoneAirborne()
	p\AbleToJump = False
	p\Jumping = False
	p\Airborne = True
	JumpKeyPressed = False
End Function

Function ApplyGravity()
	If p\Airborne Then
		p\YSpeed = p\YSpeed + Gravity
		If p\YSpeed > MaxYSpeed Then p\YSpeed = MaxYSpeed
	EndIf
End Function

Function PrepPlayerStart()
	p\MapX = 0
	p\MapY = 0
	p\ScrX = 0
	p\ScrY = 0
	p\XSpeed = 0
	p\YSpeed = 0
	p\MoveX = 0
	p\MoveY = 0
	p\Airborne = True
	p\Jumping = False
	p\AbleToJump = False
	p\w = 10
	p\h = 10
End Function

Function EnforceMapBounds2()
	
	If p\MapX + p\MoveX + 10 > MapWidthPx Then
		p\MoveX = MapWidthPx - p\MapX - 10
		p\XSpeed = 0
		;DebugLog("1")
	ElseIf p\MapX + p\MoveX < 0 Then
		p\MoveX = p\MapX * -1
		p\XSpeed = 0
		;DebugLog("2")
	EndIf
	
	If p\MapY + p\MoveY + 10 > MapHeightPx Then
		p\MoveY = MapHeightPx - p\MapY - 10
		p\YSpeed = 0
		;DebugLog("3")
	ElseIf p\MapY + p\MoveY < 0 Then
		p\MoveY = p\MapY * -1
		p\YSpeed = 0
		;DebugLog("4")
	EndIf
	
End Function

Function DrawMap()

	offsetX# = CamX
	offsetY# = CamY
	
	startX = Floor(offsetX / TILE_SIZE)
	endX = Floor((offsetX + CAM_W-1) / TILE_SIZE)
	startY = Floor(offsetY / TILE_SIZE)
	endY = Floor((offsetY + CAM_H-1) / TILE_SIZE)
	
	;DebugLog "startX:" + startX + " | endX:" + endX + " | startY:" + startY + " | endY:" + endY
	
	For y = startY To endY
		For x = startX To endX
			t = Int(map(y,x))-1
			;Print "map("+y+","+x+") = " + t
			If t > -1 DrawTile(t, x * TILE_SIZE - offsetX, y * TILE_SIZE - offsetY)
		Next
	Next
End Function

Function DrawTile(t, x, y)
	DrawImage img_tiles,x,y,t
End Function

Function ReadMapData()
	Restore mapData
	For y = 0 To 19
		For x = 0 To 19
			Read map(y,x)
		Next
	Next
End Function

Function ReadTileTypeData()
	Restore tileTypeData
	For y = 0 To 19
		For x = 0 To 19
			Read tileType(y,x)
		Next
	Next
End Function

Function GetMyCorners(x#, y#, ob.Player)
	
	; tile co-ords
	ob\upY = Floor( y / TILE_SIZE )
	ob\downY = Floor( (y + ob\h - 1) / TILE_SIZE )
	ob\leftX = Floor( x / TILE_SIZE )
	ob\rightX = Floor( (x + ob\w - 1) / TILE_SIZE )
	ob\standingY = Floor( (y + ob\h) / TILE_SIZE )
	
	;DebugLog "upY:"+ob\upY+"|downY:"+ob\downY+"|leftX:"+ob\leftX+"|rightX:"+ob\rightX+"|ob\w:"+ob\w+"|h:"+ob\h
	
	ob\topLeftType = tileType(ob\upY, ob\leftX)
	ob\topRightType = tileType(ob\upY, ob\rightX)
	ob\bottomLeftType = tileType(ob\downY, ob\leftX)
	ob\bottomRightType = tileType(ob\downY, ob\rightX)
	
	ob\standingLeftType = tileType(ob\standingY, ob\leftX)
	ob\standingRightType = tileType(ob\standingY, ob\rightX)
	
End Function

Function CheckCollision2()
	
	Local newY# = p\MapY + p\MoveY
	
	;DebugLog p\w
	GetMyCorners(p\MapX, newY, p)
	
	Local dirY = Sgn(p\MoveY)
	;If p\MoveY < 0 dirY = -1
	;If p\MoveY > 0 dirY = 1
	
	; UP
	If dirY < 0 Then
		If p\topLeftType = TILE_SOLID Or p\topRightType = TILE_SOLID Then
			p\MoveY = ((p\upY+1) * TILE_SIZE) - p\MapY
			p\YSpeed = 0
			DebugLog "boink head"
		EndIf
	EndIf
	
	; DOWN
	If dirY > 0 Then
		If p\bottomLeftType = TILE_SOLID Or p\bottomRightType = TILE_SOLID Then
			;DebugLog "old MoveY:"+p\MoveY+"|new MoveY:"
			p\MoveY = (p\downY * TILE_SIZE) - (p\MapY+p\h)
			;DebugLog p\MoveY
			DebugLog "landed"
			p\YSpeed = 0
			PlayerLanded()
		EndIf
	EndIf
	
	newY = p\MapY + p\MoveY
	Local newX# = p\MapX + p\MoveX
	Local dirX = Sgn(p\MoveX)
	;If p\MoveX < 0 dirX = -1
	;If p\MoveX > 0 dirX = 1
	
	GetMyCorners(newX, newY, p)
	
	; LEFT
	If dirX < 0 Then
		If p\topLeftType = TILE_SOLID Or p\bottomLeftType = TILE_SOLID Then
			p\MoveX = ((p\leftX+1) * TILE_SIZE) - p\MapX
			p\XSpeed = 0
		EndIf
	EndIf
	
	GetMyCorners(newX+1, newY, p)
	
	; RIGHT
	If dirX > 0 Then
		If p\topRightType = TILE_SOLID Or p\bottomRightType = TILE_SOLID Then
			p\MoveX = ((p\rightX) * TILE_SIZE) - (p\MapX+p\w)
			p\XSpeed = 0
		EndIf
	EndIf
	
	GetMyCorners(p\MapX+p\MoveX, newY, p)
	
	If p\Airborne = False And ( p\standingLeftType = TILE_EMPTY And p\standingRightType = TILE_EMPTY ) GoneAirborne()
	;p\Airborne = p\standingLeftType = TILE_EMPTY And p\standingRightType = TILE_EMPTY
	;p\Airborne = True
End Function

Function UpdateCamPos()
	
	CamX = MaxFloat(0, p\MapX - CAM_W/2 + 5)
	CamY = MaxFloat(0, p\MapY - CAM_H/2 - 50)
	
	If CamX + CAM_W > MapWidthPx CamX = MapWidthPx - CAM_W
	If CamY + CAM_H > MapHeightPx CamY = MapHeightPx - CAM_H
	;DebugLog "CamX: "+CamX+"|CamY:"+CamY
	;DebugLog "pMoveX:"+p\MoveX+"|pMapX:"+p\MapX
End Function

Function UpdatePlayerScrPos()
	
	p\ScrX = p\MapX - CamX
	p\ScrY = p\MapY - CamY
	
End Function

Function UpdatePlayerVel()

	Local LeftDown = KeyDown(203)
	Local RightDown = KeyDown(205)
	Local JumpHit = KeyHit(200)
	Local JumpDown = KeyDown(200)
	
	If ( JumpHit And p\AbleToJump = True ) Then
		p\AbleToJump = False
		p\Jumping = True
		p\Airborne = True
		p\YSpeed = JumpSpeed
		JumpKeyPressed = False		
	EndIf
	
	JumpKeyPressed = JumpKeyPressed Or (p\Jumping And JumpDown = False)
	
	; catch small jumps
	If p\Jumping And JumpKeyPressed And p\YSpeed < SmallJumpSpeed Then
		p\YSpeed = SmallJumpSpeed
	EndIf

	;;;; AIR-DRAG AND IN-AIR PHYSICS
	If p\Airborne
		
		;;;; MOVE CODE (in air)
		If LeftDown Then
			p\XSpeed = p\XSpeed - Air
		ElseIf RightDown Then
			p\XSpeed = p\XSpeed + Air
		EndIf
		
		; limit to max air XSpeed
		If Abs(p\XSpeed) > AirMaxXSpeed Then
			p\XSpeed = AirMaxXSpeed * Sgn(p\XSpeed)
		EndIf
		
		;Else ; is it right to apply air drag if no x keys are pressed or what?
		
			If p\YSpeed < 0 And p\YSpeed > SmallJumpSpeed Then
				; flooring basically enforces X to being > 0.125
				p\XSpeed = p\XSpeed - (Floor(p\XSpeed / 0.125) / 256)
				; dont apply air drag for small jumps
				; NOTE: this should be done before applying gravity really
				;  have just moved gravity to after jump + move code
				
			EndIf
		;EndIf
		
	Else
	
		;;;; MOVE CODE (on floor)
		; pressing left
		If LeftDown > 0 Then
		
			If p\XSpeed > 0 Then ; if going right then dec
				p\XSpeed = p\XSpeed - Dec
			ElseIf p\XSpeed > -MaxXSpeed Then ; if we are going left but less than max, inc by acc
				p\XSpeed = p\XSpeed - Acc ; TODO: could this actually allow a speed greater than max speed?
				;   might move above check outside of if, so it xspeed is never faster than max
			Else
				; move this into if above if we want to be able to move quicker than max
				;  by an external force, eg, jump pad or something, because using this code, 
				;  if we are pushed to great than max, and user presses that same direction,
				;  then line below will set the speed to max, which will be lower than the actual speed
				; Or maybe MaxXSpeed can be set if we use a jump pad or something, then reset it sometime
				p\XSpeed = -MaxXSpeed ; otherwise cap out at max
			EndIf
			
		; pressin right
		ElseIf RightDown Then
			
			If p\XSpeed < 0 Then ; if going left then dec
				p\XSpeed = p\XSpeed + Dec
			ElseIf p\XSpeed < MaxXSpeed Then ; if we are going right but less than max, inc by acc
				p\XSpeed = p\XSpeed + Acc
			Else
				p\XSpeed = MaxXSpeed ; otherwise cap out at max
			EndIf
			
		; no X pressin, apply friction
		Else
		
			If p\XSpeed <> 0 Then
				p\XSpeed = p\XSpeed - (MinFloat(Abs(p\XSpeed), Frc) * Sgn(p\XSpeed))
			EndIf
			
		EndIf
	EndIf
End Function

.mapData
Data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data 0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0
Data 0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,1,1,0,0,0
Data 1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1
Data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0


.tileTypeData
Data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data 0,0,0,0,0,0,0,1,1,1,1,0,0,0,0,0,0,0,0,0
Data 0,0,0,0,0,0,0,1,0,0,1,0,0,0,0,1,1,0,0,0
Data 1,1,1,1,1,1,1,1,0,0,1,1,1,1,1,1,1,1,1,1
Data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
Data 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0



Kryzon(Posted 2013) [#2]
Hi, wanted to say if you need to change your collision paradigm, I describe an intuitive one here: http://blitzbasic.com/Community/posts.php?topic=99818#1174401


IanUK(Posted 2013) [#3]
Thanks for the reply Kryzon. I'm a bit busy atm so I've just had a quick read and it looks good thanks, I appreciate that. I have read quite a few different methods over the last week but just to get something working I started simple. Its just wierd I must be missing something in that I think I've adapted the guides' method correctly but I'm forced to make an extra step not in the guide. I'll take a better look at it later hopefully. All I want to do really is get something working, nothing special just something I can build on bit by bit. If anyone has suggestions on my code that would be useful too.


Kryzon(Posted 2013) [#4]
I think in that article he handles the player by the center, and you seem to handling it by the top-left corner.
That changes the logic a bit, so you are probably compensating for this.

EDIT: Another good handle position for platform games is at the feet, bottom-center. It just makes comparing with platforms easier.


IanUK(Posted 2013) [#5]
Yeah that threw me to start with, until I realised, wondered why he was adding width and height on all the time. I can see why a bottom handle would be useful but I'm not sure my brain would take to making the switch, too long in the tooth. I'll print your article off and read it properly. Can't sit in front of a machine too long.


Kryzon(Posted 2013) [#6]
Hi. Make sure you print the last post as well, it has more clear code on the collision function: http://blitzbasic.com/Community/posts.php?topic=99818#1174643

I'm certain you can improve the method more for your game, since you're looking for an extendable solution.
I imagine for a tile game you need something like a new type of collider such as "line" colliders, between two defined points.

You'd use these lines to 'outline' clusters of solid tiles; So it's much faster than checking collisions against each tile - you only need to compare against the outline of the whole cluster, as tiles within the cluster will never get to touch the player, only the outer ones (that's why it's faster):



This is actually getting me interested. If you need any help with that, we'll work on it.


IanUK(Posted 2013) [#7]
Kryzon, that would be good. I've dropped you an email, maybe we could exchange ideas and come up with something good.


Kryzon(Posted 2013) [#8]
Sure thing.
It'd be more contributing to post code here (for any interested others to see as well, especially ones that get here by googling keywords), but I'll check it out.

EDIT: I gave some more thought on that article you were referencing about tile collisions.
It's way faster to use with tile maps since it only checks the four corners of the moving object, instead of comparing with every single collideable tile in the map.
It works, but then you also need to stick to the conditions the method implies.

These conditions are:
- No movable object should move more than the tile size in a single step (else the corner checking might miss any solid tiles the object goes through, if it lands too far).
- No object should be bigger than two tiles in any direction, else a solid tile might slip through the middle (since the algorithm only checks the four corners).



More on the speed limitation. If you can leave the "back sides" (sides that face opposite the direction of movement) in place, and only move the "front sides" (sides that face the direction of movement), you end up getting the whole region the object will travel.
Then you need to check for tiles inside this region and proceed in a way I haven't thought of yet, give it a day...