Best way to do collisions in 2d platformer

Blitz3D Forums/Blitz3D Programming/Best way to do collisions in 2d platformer

Roland(Posted 2007) [#1]
Hey All,

I know that this is probably a really basic question, but I've not done a 2d platformer before and it seems like there are quite a few options for doing collisions. I really want the controls and gameplay to feel good, so I'd love to hear from the pros what the best approach is.

I am open to doing pure 2d, sprites (2d in 3d), or using a lib like fastimage... it'd be great if it could be pixel perfect, though.

I have tried in the past to do this using standard collision methods, like imagescollide(), etc., but always find that i get jumpy and poor quality collisions. What's worked for you?

thanks,
roland


Ross C(Posted 2007) [#2]
Imagescollided() should do pixel perfect checks. But a few tricks can help you out. I don't think it's the commands you use to obtain collision information, it's the techinques (useless with a spell checker) you can use. For instances, a character going up a slope wouldn't use the image of the character, but rather a pixel, not drawn, to get the position of the feet, to obtain smooth slope moving.

Really depends what your after. Can you elaborate any?


Roland(Posted 2007) [#3]
Hi Ross --

that's exactly the type of stuff that i'd love to know more about... It seems like there are probably some tried and true tricks to getting good-feeling platform gameplay working right. I was just hoping for a head start.

The game that I'm going to be working on will want to feel kind of like super mario 2. I'll want a little bit of friction / slide on the ground, smooth jumping and most likely the majority of the level will be flat -- i haven't even thought about slopes yet.

Seems like whenever I try to use collisions, I end up fighting with my gravity and movement so that my controls look really jittery. So once I finish a jump, or walk into a block from the side i just keep bouncing back and forth when i collide instead of walking into it and stopping or falling and standing still. Does that make sense? Any help at all would be greatly appreciated!


Ross C(Posted 2007) [#4]
Hmmm, seems more like a problem with your code.

For instance, when jumping, you should set a flag saying your jumping, so you can't press the jump key again. The flag should only be unset, if you LAND on the ground, which would mean, you have to test the players feet.

Sounds complicated, but what i do, or have done in the past is too move the players along the horizontal axis first, test for collisions, then move on the vertical, and test for collisions.

That way, you can check for collisions in many ways:

Check for jumping into platforms left and right of the characters position.

Check to see if the players jump is upwards, then check for banging the head off the roof, then you can immediately change the jump direction to downwards.

Check to see if the players direction is downwards, then check to see if he has collided with the ground, stopping the fall, and resetting the jump flag, therefore enabling the player to press the jump key again.

With regards to checking if the player has hit the ground, you can at the same time, check to see if he has fallen in water, onto spikes, jumped on an enemies head. Some things that would result in a different action if the player had run into those things.

Another important thing is to check collisions, THEN move your image.

player_x
player_y
If ImagesCollide(player_image,player_x + move_x ,player_y + move_y , player_image_frame, ground , groundx, groundy , groundframe) = false then
   player_x = player_x + move_x
   player_y = player_y + move_y
End If


Note i don't move the image, until i've checked it's future co-ordinates (where the image will be). You should do this with all movements of the player, and enemies if they move.

If your new to 2d games and blitz, or programming in general, you should check out 2d tilemaps. It's basically the whole screen made up of smaller images. This is done to save memory mainly, and make things easier to deal with. Also makes level construction easy too, especially with an editor.

http://www.blitzbasic.com/toolbox/toolbox.php?tool=76

I have never used it, but seen many people recommend it :o)

Hope some of this helps you. Feel free to ask more stuff. I'm sure others more experienced in this field will post some good stuff too.


Roland(Posted 2007) [#5]
Hi again, and thanks for the quick reply!

This is really helpful. I haven't implemented a regular tile map yet, but here's a version using just a simple image type instead. Not optimized, but it's a start. The controls are definitely feeling more solid -- they need to be tweaked a little. Arrows to move, space to jump.

the whole drag thing is a little wonky right now and definitely needs refinement. Any suggestions would be really appreciated!!!!

thanks again,
roland

Graphics 800,600,0,2

SeedRnd MilliSecs()

Global gravity = 2
Global drag# = .8

Global tilewidth = 32

Type block
Field image
Field x
Field y
End Type


Type player
Field image
Field x#
Field y#
Field xspeed#
Field yspeed#
Field jumpstrength
Field jumping
Field maxjump

Field topimage
Field bottomimage
Field leftimage
Field rightimage
Field speed#
Field maxspeed#



End Type


For x=0 To 50
	
	b.block = New block
	b\x = Rand(25)*tilewidth
	b\y = (Rand(6)*tilewidth)+(11*tilewidth)
	b\image = CreateImage(tilewidth,tilewidth)
	SetBuffer(ImageBuffer(b\image))
	grey = Rand(50)+205
	ClsColor grey,grey,grey
	Cls
	SetBuffer BackBuffer()
Next


For x=0 To 24
	b.block = New block
	b\x = x*tilewidth
	b\y = 18*tilewidth
	b\image = CreateImage(tilewidth,tilewidth)
	SetBuffer(ImageBuffer(b\image))
	grey = Rand(50)+205
	ClsColor grey,grey,grey
	Cls
	SetBuffer BackBuffer()
Next



p.player = New player
p\image = CreateImage(tilewidth,tilewidth)
p\x = 9*tilewidth
p\y = 10*tilewidth
p\jumpstrength = -5
p\maxjump = -14
p\speed = 1
p\maxspeed =5
SetBuffer ImageBuffer(p\image)
Color 255,0,0
Oval 0,0,tilewidth,tilewidth


p\topimage = CreateImage(tilewidth/2,3)
SetBuffer ImageBuffer(p\topimage)
Color 0,255,0
Rect 0,0,tilewidth/2,3


p\bottomimage = CreateImage(tilewidth/2,3)
SetBuffer ImageBuffer(p\bottomimage)
Color 0,255,0
Rect 0,0,tilewidth/2,3

p\rightimage = CreateImage(3,tilewidth/2)
SetBuffer ImageBuffer(p\rightimage)
Color 0,255,0
Rect 0,0,3,tilewidth/2

p\leftimage = CreateImage(3,tilewidth/2)
SetBuffer ImageBuffer(p\leftimage)
Color 0,255,0
Rect 0,0,3,tilewidth/2


SetBuffer(BackBuffer())

ClsColor 0,0,0

While Not KeyHit(1)

Cls


drawblocks()
updateplayer()

Flip()

Wend



Function drawblocks()
For b.block = Each block

	DrawImage b\image,b\x,b\y,0

Next
End Function


Function updateplayer()

	For p.player = Each player
	
	p\yspeed = p\yspeed+gravity
	
	;If p\xspeed > 0 Then p\xspeed = p\xspeed-drag
	;If p\xspeed < 0 Then p\xspeed = p\xspeed+drag
	
	p\xspeed = p\xspeed*drag
	




	
	If KeyDown(203)
		p\xspeed = p\xspeed-p\speed
	EndIf
	
	If KeyDown(205)
		p\xspeed = p\xspeed+p\speed
	EndIf
	
	If p\xspeed > p\maxspeed Then p\speed = p\maxspeed
	If p\xspeed < -p\maxspeed Then p\speed = -p\maxspeed


	p\y = p\y+p\yspeed
	p\x = p\x+p\xspeed


	Repeat
		collidey = False	
		For b.block = Each block
		If ImagesCollide(p\bottomimage,p\x+tilewidth/4,p\y+tilewidth-3,0,b\image,b\x,b\y,0)
			collidey = True
			p\yspeed=0
		EndIf
		
		Next
		If collidey = True
			p\y = p\y-1
		EndIf
	Until collidey = False


	Repeat
		collideup = False	
		For b.block = Each block
		If ImagesCollide(p\topimage,p\x+tilewidth/4,p\y,0,b\image,b\x,b\y,0)
			collideup = True
			p\yspeed=0
			p\jumping = False
		EndIf
		
		Next
		If collideup = True
			p\y = p\y+1
		EndIf
	Until collideup = False


	Repeat
		collidel = False	
		For b.block = Each block
		If ImagesCollide(p\leftimage,p\x,p\y+tilewidth/4,0,b\image,b\x,b\y,0)
			collidel = True
			p\xspeed=0
		EndIf
		
		Next
			
		If collidel = True
			p\x = p\x+1
		EndIf
	Until collidel = False


	Repeat
		collider = False	
		For b.block = Each block
		If ImagesCollide(p\rightimage,p\x+tilewidth-3,p\y+tilewidth/4,0,b\image,b\x,b\y,0)
			collider = True
			p\xspeed=0
		EndIf
		
		Next
			
		If collider = True
			p\x = p\x-1
		EndIf
	Until collider = False


	If KeyHit(57) And p\yspeed = 0 And p\jumping = False
		p\jumping = True
	EndIf


	If KeyDown(57)
		If p\jumping = True	
	 		If p\yspeed > p\maxjump
				p\yspeed = p\yspeed+p\jumpstrength
			Else
				p\jumping = False
			EndIf
		EndIf
	Else
		p\jumping = False
	EndIf



	
	
	DrawImage p\topimage,p\x+tilewidth/4,p\y
	DrawImage p\bottomimage,p\x+tilewidth/4,p\y+tilewidth-3
	DrawImage p\leftimage,p\x,p\y+tilewidth/4
	DrawImage p\rightimage,p\x+tilewidth-3,p\y+tilewidth/4

	DrawImage p\image,p\x,p\y,0
	
	Next
End Function




Ross C(Posted 2007) [#6]
Ok, i'll check this out :o)


Ross C(Posted 2007) [#7]
Ok, here we go. First the code!

Graphics 800,600,0,2

SeedRnd MilliSecs()

Global gravity# = 0.7
Global drag# = .8

Global tilewidth = 32

Type block
Field image
Field x
Field y
End Type


Type player
Field image
Field x#
Field y#
Field xspeed#
Field yspeed#
Field jump_speed_angle#
Field jump_speed#
Field jump_strength#
Field jumping

Field topimage
Field top_x
Field top_y

Field bottomimage
Field bottom_x
Field bottom_y

Field leftimage
Field left_x
Field left_y

Field rightimage
Field right_x
Field right_y

Field speed#
Field maxspeed#



End Type


For x=0 To 50
	
	b.block = New block
	b\x = Rand(25)*tilewidth
	b\y = (Rand(6)*tilewidth)+(11*tilewidth)
	b\image = CreateImage(tilewidth,tilewidth)
	SetBuffer(ImageBuffer(b\image))
	grey = Rand(50)+205
	ClsColor grey,grey,grey
	Cls
	SetBuffer BackBuffer()
Next


For x=0 To 24
	b.block = New block
	b\x = x*tilewidth
	b\y = 18*tilewidth
	b\image = CreateImage(tilewidth,tilewidth)
	SetBuffer(ImageBuffer(b\image))
	grey = Rand(50)+205
	ClsColor grey,grey,grey
	Cls
	SetBuffer BackBuffer()
Next



p.player = New player
	p\image = CreateImage(tilewidth,tilewidth)
	p\x = 9*tilewidth
	p\y = 10*tilewidth
	; new variables for jumping
	p\jump_strength = 10 
	p\jump_speed_angle# = 0 ; leave this at zero
	p\jump_speed# = 5
	
	p\speed = 1
	p\maxspeed =5
	SetBuffer ImageBuffer(p\image)
	Color 255,0,0
	Oval 0,0,tilewidth,tilewidth


;	DrawImage p\topimage    , p\x+3           , p\y
;	DrawImage p\bottomimage , p\x+3           , p\y+tilewidth-3
;	DrawImage p\leftimage   , p\x             , p\y+3
;	DrawImage p\rightimage  , p\x+tilewidth-3 , p\y+3

p\topimage = CreateImage(tilewidth-6,3)
p\top_x = p\x + 3
p\top_y = p\y
SetBuffer ImageBuffer(p\topimage)
Color 0,255,0
Rect 0,0,tilewidth-6,3

p\top_x = p\x + 3
p\top_y = p\y
p\bottom_x = p\x + 3
p\bottom_y = p\y + tilewidth - 3
p\left_x = p\x
p\left_y = p\y + 3
p\right_x = p\x + tilewidth - 3
p\right_y = p\y + 3

p\bottomimage = CreateImage(tilewidth-6,3)
p\bottom_x = p\x + 3
p\bottom_y = p\y + tilewidth - 3
SetBuffer ImageBuffer(p\bottomimage)
Color 0,255,0
Rect 0,0,tilewidth-6,3

p\rightimage = CreateImage(3,tilewidth-6)
p\right_x = p\x + tilewidth - 3
p\right_y = p\y + 3
SetBuffer ImageBuffer(p\rightimage)
Color 0,255,0
Rect 0,0,3,tilewidth-6

p\leftimage = CreateImage(3,tilewidth-6)
p\left_x = p\x
p\left_y = p\y + 3
SetBuffer ImageBuffer(p\leftimage)
Color 0,255,0
Rect 0,0,3,tilewidth-6


SetBuffer(BackBuffer())

ClsColor 0,0,0

While Not KeyHit(1)
	
	Cls
	
	
	drawblocks()
	updateplayer()
	
	Flip()

Wend



Function drawblocks()
For b.block = Each block

	DrawImage b\image,b\x,b\y,0

Next
End Function


Function updateplayer()

	For p.player = Each player
	
	; grab x and y co-ords before you move anything
	old_px = p\x
	old_py = p\y
	
	If KeyDown(57) And p\jumping = 0
		p\jumping = 1
	EndIf

	
	If KeyDown(203)
		p\xspeed = p\xspeed-p\speed
	ElseIf KeyDown(205)
		p\xspeed = p\xspeed+p\speed
	Else
		p\xspeed = p\xspeed * drag
	End If


	; I have created 2 jump stages
	; p\jumping = 1 - this is when the player is jumping upwards.
	; p\jumping = 2 - this is when the player is falling
	
	; Below i am using cos to make use of the sin waves. Basically, inputting a value into COS
	; will produce a number, a ratio. If you were to drawing every single number from 0 to 180, you would come
	; across a half circle. As you know, a circle curves, reaching an extremety.
	
	; FOR EXAMPLE
	
	; angle = 0   - cos(0)   = 1
	; angle = 10  - cos(10)  = 0.984
	; angle = 20  - cos(20)  = 0.939
	; angle = 30  - cos(30)  = 0.866
	; angle = 40  - cos(40)  = 0.766
	; angle = 50  - cos(50)  = 0.642
	; angle = 60  - cos(60)  = 0.500
	; angle = 70  - cos(70)  = 0.342
	; angle = 80  - cos(80)  = 0.173
	; angle = 90  - cos(90)  = 0.000
	
	; as you can see from above, inputting a linear number, outputs an ever decreasing number. So, if you use this
	; for a jump, you can make the jump start off quick, and quickly dimishing, replicating the effect of gravity,
	; without fiddling about with physics. For the falling, continue to angles for 100 - 180, this will start off,
	; slowly, and increase exponentially, again, simulating the effect of gravity fairly well.
	

	If p\jumping = 1 Then
		p\jump_speed_angle = p\jump_speed_angle + p\jump_speed
		If p\jump_speed_angle >= 90 Then
			p\jump_speed_angle = 90
			p\jumping = 2
		End If
	ElseIf p\jumping = 2 Then
		p\jump_speed_angle = p\jump_speed_angle + p\jump_speed

		If p\jump_speed_angle => 180 Then
			p\jump_speed_angle = 180
		End If
	End If


	
	If p\xspeed > p\maxspeed Then p\xspeed = p\maxspeed
	If p\xspeed < -p\maxspeed Then p\xspeed = -p\maxspeed

	; below is where you add the jump/fall speed to the players co-ords. see below for expanation of COS
	; Firstly check to see if the jumping flag has been set. If so, adjust the players y position.
	; If NOT, then don't bother with them.
	If p\jumping <> 0 Then
		p\y = p\y-(Cos(p\jump_speed_angle) * p\jump_strength)
	End If
	
	p\x = p\x+p\xspeed

	; you must update the image edges, so the collisions will register properly
	update_edges(p.player)
	
	; you pass across the player object "p", as it's locally created to this function.
	; The same applies with "old_px" and "old_py". The collision routine needs to reset to these co-ords.
	update_collisions(p.player,old_px,old_py)

	; you must again update the image egdes, as the collision code may have reset the main image co-ords.
	update_edges(p.player)
	; check collisions with blocks
	

	Text 0,0,"jumping : "+p\jumping
	Text 0,10,"jump_speed_angle : "+ jump_speed_angle
	Text 0,20,"jump_speed : "+p\jump_speed
	

	DrawImage p\topimage    , p\top_x         , p\top_y
	DrawImage p\bottomimage , p\bottom_x      , p\bottom_y
	DrawImage p\leftimage   , p\left_x        , p\left_y
	DrawImage p\rightimage  , p\right_x       , p\right_y

	DrawImage p\image,p\x,p\y,0
	
	Next
End Function


; you must pass across the old p\x and p\y co-ords so the collision routine may reset positions, if future positions,
; cause a collision.
Function update_collisions(p.player,old_px,old_py)

	collide_down = 0
	collide_up = 0
	collide_left = 0
	collide_right = 0
	
		For b.block = Each block
			If ImagesCollide(p\bottomimage,p\bottom_x,p\bottom_y,0,b\image,b\x,b\y,0)
				
				p\jumping = 0 ; switching jumping OFF
				p\jump_speed_angle = 0 ; reset jump_speed_angle to 0
				collide_down = 1 ; set collide_down flag
				p\y = old_py
			EndIf
		
			If ImagesCollide(p\topimage,p\top_x,p\top_y,0,b\image,b\x,b\y,0)
				p\jumping = 2 
				p\jump_speed_angle = 90
				collide_up = 1
				p\y = old_py
			EndIf

		update_edges(p.player)

			If ImagesCollide(p\leftimage,p\left_x,p\left_y,0,b\image,b\x,b\y,0)
				p\xspeed=0
				collide_left = 1
				p\x = old_px
			EndIf

			If ImagesCollide(p\rightimage,p\right_x,p\right_y,0,b\image,b\x,b\y,0)
				p\xspeed=0
				collide_right = 1
				p\x = old_px
			EndIf

		Next

	If collide_down = 0 Then ; did not hit the ground
	
		If p\jumping = 0 Then
			p\jumping = 2 ; set to falling
			p\jump_speed_angle = 90 ; set the angle of cos to simulate falling
		End If
	End If

End Function



Function update_edges(p.player)

	p\top_x = p\x + 3
	p\top_y = p\y
	p\bottom_x = p\x + 3
	p\bottom_y = p\y + tilewidth - 3
	p\left_x = p\x
	p\left_y = p\y + 3
	p\right_x = p\x + tilewidth - 3
	p\right_y = p\y + 3

End Function



I reworked some of your code, created a few function to seperate everything. I also created some fields in your player type:

Field top_x
Field top_y
Field left_x
Field left_y
Field right_x
Field right_y
Field bottom_x
Field bottom_y

Field jump_speed_angle
Field jump_speed

NOTE: the "jump_speed_angle" field isn't really an angle of such, it's just named as such, as it's put into COS to retrieve a value.

I removed the max_jump field.

I implemented your jumping and falling using COS. I have provided a more detailed explanation inside the code via comments.

I have split the jumping into 3 stages.

p\jumping = 0 - the player is on flat ground. This is set to zero when a collision is detected below the player, via "p\bottom_image"

p\jumping = 1 - the player is actually jumping. Increments "jump_speed_angle" by "jump_speed"
eg - jump_speed_angle = jump_speed_angle + jump+speed

"jump_speed_angle" is limited to 90, as this is quater of a circle, and is the 0 point into COS wave. Once "jump_speed_angle" = 90, the p.jumping flag gets set to 2

p\jumping = 2 - the player is now falling. This variable is set to falling if:

- the player jump has reached it's peak.
- the player has walked off of a platform.

This increases the "jump_speed_angle" up to 180. This is another quarter of a circle which leads to the lowest point in the COS wave.

For example: "jump_speed_angle" = 0. Stick this into COS and it returns 1. So the jump starts at the highest speed. As "jump_speed_angle" = 90 the value COS returns is 0, meaning the speed is non-existant.

Increasing "jump_speed_angle" beyond 90 cause the values returned to go below 0. When "jump_speed_angle" reaches 180, COS will return -1. It will peak at that, and beyond 180, will return to 0, then 1.

That is why the code cuts off at 180, as it is the biggest minus number COS will return.

The beauty of this, is all you need to do is plug in the "jump_speed_angle" and it will return value going from 1 (jumping upwards) to -1 (falling downwards), without you having to do anything, except change the jumping_flag. Additionally, COS will produce numbers have increase and decrease exponentially, acting like gravity :o)

I have put all the co-ords of the edges images into their own variables, and made them longer, as the image was getting caught through the corners which were showing. I put them in their own variables just for reading sake. You need to update their co-ords though, as they are based on the main images co-ords. Call "update_edges(p.player" passing across the player object. You will need to do this anytime you update the main images co-ords.

I have put the collisions all into the one loop and stuck them in a function, so to cut down the amount of code you have to read through and tidied up the code. I have also recorded the player images co-ords at the beginning of the "updateplayer" so you can reset them, if the the new co-ords fall within a collision. You must pass them across to the function, along with the player object "p.player".

Added or changed the left/right movement to:

	If KeyDown(203)
		p\xspeed = p\xspeed-p\speed
	ElseIf KeyDown(205)
		p\xspeed = p\xspeed+p\speed
	Else
		p\xspeed = p\xspeed * drag
	End If


This will check to see if the left or right key has been pressed. If not, then it will apply drag. Therefore not applying drag whilst moving. Just simplifies things a touch.

Changed the jump code to a simple coupld of lines:

	If KeyDown(57) And p\jumping = 0
		p\jumping = 1
	EndIf


Check to see if jump key is pressed (best with Keydown() because keyhit() only registers once per loop. So you couldn't check for jump keyhit() twice per loop). If so, also check to see if the p\jumping flag is at 0. If so, set to 1 (jumping) and let the rest of the code handle this.

I think i've covered the things i have altered :o) Btw your code was very good. impressed by your implementation if this is a first go at it!

I'll be hanging about here for a while, so feel free to post back about anything in there :o) Good luck!


Ross C(Posted 2007) [#8]
I have put in an Update_Edges() call between checking the up and down images, and the left and right images. This will enable the player to slide once he lands.


Ross C(Posted 2007) [#9]
I would recommend making the player slightly smaller than a tile too. Will reduce the problems you'll get with struggling to fit into tight spaces :o)


Ross C(Posted 2007) [#10]
Ok, i have updated the code again. It was getting tricky using the four different image to detect collisions. Perhaps they would only be useful for bullet collisions (hit in the back or front?)

I have now split up the collision function into four different ones.

The code will check to see the jump status.

If p\jumping = 0 then check static collisions:

This will check one pixel below the image, to see if the player is still on flat land. As falling is disabled, there is nothing to pull the player to the ground. It also makes sure that if you restore the orginal co-ods after a downward collision, the player will remain flush with the ground.

If p\jumping = 1 then check upward collisions:

Basically, if your jumping upwards, there is no point in check the players feet for platform collisions. Only exceptions would be moving platforms.

If p\jumping = 2 then check downward collisions:

Check to see if the players updated co-ords are causing a collision. If so, then set the jumping status to 0.

I have made the player smaller, so you can make the jumps to tighter platforms.

Graphics 800,600,0,2

SeedRnd MilliSecs()

Global gravity# = 0.7
Global drag# = .8

Global tilewidth = 32

Type block
Field image
Field x
Field y
End Type


Type player
Field image
Field x#
Field y#
Field xspeed#
Field yspeed#
Field jump_speed_angle#
Field jump_speed#
Field jump_strength#
Field jumping

Field topimage
Field top_x
Field top_y

Field bottomimage
Field bottom_x
Field bottom_y

Field leftimage
Field left_x
Field left_y

Field rightimage
Field right_x
Field right_y

Field speed#
Field maxspeed#



End Type


For x=0 To 50
	
	b.block = New block
	b\x = Rand(25)*tilewidth
	b\y = (Rand(6)*tilewidth)+(11*tilewidth)
	b\image = CreateImage(tilewidth,tilewidth)
	SetBuffer(ImageBuffer(b\image))
	grey = Rand(50)+205
	ClsColor grey,grey,grey
	Cls
	SetBuffer BackBuffer()
Next


For x=0 To 24
	b.block = New block
	b\x = x*tilewidth
	b\y = 18*tilewidth
	b\image = CreateImage(tilewidth,tilewidth)
	SetBuffer(ImageBuffer(b\image))
	grey = Rand(50)+205
	ClsColor grey,grey,grey
	Cls
	SetBuffer BackBuffer()
Next



p.player = New player
	p\image = CreateImage(tilewidth/2,tilewidth/2)
	p\x = 9*tilewidth
	p\y = 10*tilewidth
	; new variables for jumping
	p\jump_strength = 10 
	p\jump_speed_angle# = 0 ; leave this at zero
	p\jump_speed# = 5
	
	p\speed = 1
	p\maxspeed =5
	SetBuffer ImageBuffer(p\image)
	Color 255,0,0
	Oval 0,0,tilewidth/2,tilewidth/2




p\topimage = CreateImage(ImageWidth(p\image),3)
p\top_x = p\x
p\top_y = p\y
SetBuffer ImageBuffer(p\topimage)
Color 0,255,0
Rect 0,0,ImageWidth(p\image),3


p\bottomimage = CreateImage(ImageWidth(p\image),3)
p\bottom_x = p\x
p\bottom_y = p\y + ImageHeight(p\image) - 3
SetBuffer ImageBuffer(p\bottomimage)
Color 0,255,0
Rect 0,0,ImageWidth(p\image),3

p\rightimage = CreateImage(3,ImageHeight(p\image))
p\right_x = p\x + ImageWidth(p\image) - 3
p\right_y = p\y
SetBuffer ImageBuffer(p\rightimage)
Color 0,255,0
Rect 0,0,3,ImageHeight(p\image)

p\leftimage = CreateImage(3,ImageWidth(p\image))
p\left_x = p\x
p\left_y = p\y
SetBuffer ImageBuffer(p\leftimage)
Color 0,255,0
Rect 0,0,3,ImageHeight(p\image)


SetBuffer(BackBuffer())

ClsColor 0,0,0

While Not KeyHit(1)
	
	Cls
	
	
	drawblocks()
	updateplayer()
	
	Flip()

Wend



Function drawblocks()
For b.block = Each block

	DrawImage b\image,b\x,b\y,0

Next
End Function


Function updateplayer()

	For p.player = Each player
	
	; grab x and y co-ords before you move anything
	old_px = p\x
	old_py = p\y
	
	If KeyDown(57) And p\jumping = 0
		p\jumping = 1
	EndIf

	
	If KeyDown(203)
		p\xspeed = p\xspeed-p\speed
	ElseIf KeyDown(205)
		p\xspeed = p\xspeed+p\speed
	Else
		p\xspeed = p\xspeed * drag
	End If


	; I have created 2 jump stages
	; p\jumping = 1 - this is when the player is jumping upwards.
	; p\jumping = 2 - this is when the player is falling
	
	; Below i am using cos to make use of the sin waves. Basically, inputting a value into COS
	; will produce a number, a ratio. If you were to drawing every single number from 0 to 180, you would come
	; across a half circle. As you know, a circle curves, reaching an extremety.
	
	; FOR EXAMPLE
	
	; angle = 0   - cos(0)   = 1
	; angle = 10  - cos(10)  = 0.984
	; angle = 20  - cos(20)  = 0.939
	; angle = 30  - cos(30)  = 0.866
	; angle = 40  - cos(40)  = 0.766
	; angle = 50  - cos(50)  = 0.642
	; angle = 60  - cos(60)  = 0.500
	; angle = 70  - cos(70)  = 0.342
	; angle = 80  - cos(80)  = 0.173
	; angle = 90  - cos(90)  = 0.000
	
	; as you can see from above, inputting a linear number, outputs an ever decreasing number. So, if you use this
	; for a jump, you can make the jump start off quick, and quickly dimishing, replicating the effect of gravity,
	; without fiddling about with physics. For the falling, continue to angles for 100 - 180, this will start off,
	; slowly, and increase exponentially, again, simulating the effect of gravity fairly well.
	

	If p\jumping = 1 Then
		p\jump_speed_angle = p\jump_speed_angle + p\jump_speed
		If p\jump_speed_angle >= 90 Then
			p\jump_speed_angle = 90
			p\jumping = 2
		End If
	ElseIf p\jumping = 2 Then
		p\jump_speed_angle = p\jump_speed_angle + p\jump_speed

		If p\jump_speed_angle => 180 Then
			p\jump_speed_angle = 180
		End If
	End If


	
	If p\xspeed > p\maxspeed Then p\xspeed = p\maxspeed
	If p\xspeed < -p\maxspeed Then p\xspeed = -p\maxspeed

	; below is where you add the jump/fall speed to the players co-ords. see below for expanation of COS
	; Firstly check to see if the jumping flag has been set. If so, adjust the players y position.
	; If NOT, then don't bother with them.
	If p\jumping <> 0 Then
		p\y = p\y-(Cos(p\jump_speed_angle) * p\jump_strength)
	End If
	
	If p\jumping = 0 Then ; check below, as player may run off of a platform
		update_static_vertical_collisions(p.player,old_py)
	ElseIf p\jumping = 1 Then ; check above
		update_collisions_up(p.player,old_py)
	ElseIf p\jumping = 2 Then ; check below the player, as he is falling
		update_collisions_down(p.player,old_py)
	End If

	update_edges(p.player)
	p\x = p\x+p\xspeed

	; you must update the image edges, so the collisions will register properly
	update_edges(p.player)
	update_collisions_horizontal(p.player,old_px)
	update_edges(p.player)

	
	Text 0,0,"jumping : "+p\jumping
	Text 0,10,"jump_speed_angle : "+ jump_speed_angle
	Text 0,20,"jump_speed : "+p\jump_speed
	

	DrawImage p\topimage    , p\top_x         , p\top_y
	DrawImage p\bottomimage , p\bottom_x      , p\bottom_y
	DrawImage p\leftimage   , p\left_x        , p\left_y
	DrawImage p\rightimage  , p\right_x       , p\right_y

	DrawImage p\image,p\x,p\y,0
	
	Next
End Function


; you must pass across the old p\x and p\y co-ords so the collision routine may reset positions, if future positions,
; cause a collision.

Function update_static_vertical_collisions(p.player,old_py)

		collide_down = 0
		collide_up = 0
			
		For b.block = Each block

			If ImagesCollide(p\image,p\x,p\y+1,0,b\image,b\x,b\y,0)
				
				p\jumping = 0 ; switching jumping OFF
				p\jump_speed_angle = 0 ; reset jump_speed_angle to 0
				collide_down = 1 ; set collide_down flag
				p\y = old_py
				update_edges(p.player)
			EndIf

		Next

	If collide_down = 0 Then ; did not hit the ground
	
		If p\jumping = 0 Then
			p\jumping = 2 ; set to falling
			p\jump_speed_angle = 90 ; set the angle of cos to simulate falling
		End If
	End If

End Function

Function update_collisions_down(p.player,old_py)


		collide_down = 0
		collide_up = 0
			
		For b.block = Each block

			If ImagesCollide(p\image,p\x,p\y,0,b\image,b\x,b\y,0)
				
				p\jumping = 0 ; switching jumping OFF
				p\jump_speed_angle = 0 ; reset jump_speed_angle to 0
				collide_down = 1 ; set collide_down flag
				p\y = old_py
				update_edges(p.player)
			EndIf

		Next

	If collide_down = 0 Then ; did not hit the ground
	
		If p\jumping = 0 Then
			p\jumping = 2 ; set to falling
			p\jump_speed_angle = 90 ; set the angle of cos to simulate falling
		End If
	End If

End Function


Function update_collisions_up(p.player,old_py)

		For b.block = Each block

			If ImagesCollide(p\image,p\x,p\y,0,b\image,b\x,b\y,0)
				p\jumping = 2 
				p\jump_speed_angle = 90
				collide_up = 1
				p\y = old_py
				update_edges(p.player)
			EndIf

		Next

End Function


Function update_collisions_horizontal(p.player,old_px)

			
		For b.block = Each block

			If ImagesCollide(p\image,p\x,p\y,0,b\image,b\x,b\y,0)
				p\xspeed=0
				p\x = old_px
			EndIf

		Next

End Function


Function update_edges(p.player)

	p\top_x = p\x
	p\top_y = p\y
	p\bottom_x = p\x
	p\bottom_y = p\y + ImageHeight(p\image) - 3
	p\right_x = p\x + ImageWidth(p\image) - 3
	p\right_y = p\y
	p\left_x = p\x
	p\left_y = p\y

End Function


Check it out and let me kwno what you think.

One for detecting collisions below the player, using the main player image.


Roland(Posted 2007) [#11]
Wow, this is really incredible! I think that i'm starting to get my head around it from reading your detailed instructions, but I haven't had a chance to try it out yet... hopefully i'll get to it tonight and let you know how it works out!!!

Thanks again for taking the time to work this out for me -- i really appreciate your help...

best,
roland


Ross C(Posted 2007) [#12]
No problem man. Just as long as you pass on what you learn to other people if they need the help :o)