Platformer Pixel Collisions Via Ray Casting
This example demo's a basic platform game control system with acceleration, jumping, falling and collision. The collision is really the only interesting part of the demo (since the rest of is cut and pasted from an example you can find the PlayBasic/Projects/Tutorials/Physics folder.
In this demo, we're using an image as our collision environment, the image is a 2 colour representation of the 'hard floor' areas that a character can stand upon. When the character is moved/updated we're using the ray Casting function PixelRunLength() on this collision image to detect our position relative to the floor. Then applying some simple logic to determine what that character should do, given how far from the height it is.
Characters have 3 modes. They're either Walking, Jumping or Falling.
If the character is Walking then we need to get some idea of the terrain they're walking upon, so we run a ray down from the characters origin point until it hits the ground. If you're walking upon a slope, within this demo we're going to assume the slope never raises / drops move than the characters height in gradient. So if we're running up/down hill, then this assumption will hold us upon the ground, until the ground if too far away. In that situation, the character changes from walk mode to falling mode.
If the character is Jumping, then we don't need to check for ground collisions at all, only when it's falling.
When the character is falling, we're casting a ray down from it's current position. This time we're assuming the ground is further away then the characters height. If it is, we keep falling, if it isn't, then it's landed upon something. So we use the distance from ground to reposition the character relatively.
We could expand upon this logic further and detect situations were the character is only just above the ground perhaps. Which can happen if they try and jump up onto something and almost miss. Here, you could trigger a climbing animation or perhaps the character moves into a hanging by hands mode or something. In the demo it just forces a reposition, which can be a bit clunky.. :)
Anyway, have fun.
Related Examples
Sonic the Hedgehog Loop Mock Up (https://www.underwaredesign.com/forums/index.php?topic=3851.0)
Platform Game Movement Based on Ray Intersection (https://www.underwaredesign.com/forums/index.php?topic=3916.0)
Advanced Platform Collision (Vector based) (https://www.underwaredesign.com/forums/index.php?topic=1364.0)
Walking On Sprites (https://www.underwaredesign.com/forums/index.php?topic=3338.0)
Walking on Sprite Collision Map (https://www.underwaredesign.com/forums/index.php?topic=3339.0)
Simple platformer with slopes (https://www.underwaredesign.com/forums/index.php?topic=4560.0)
Download
Attached.
Hi Kevin
This looks and plays nice , i will look through code and try to suss out how you did it
i love the semi-circle collision detection part of demo , i think this looks a lot nicer in a platform game than just straight lines as platform
mick :)
Not sure if this is allowed but this is my attempt to do it but without the addimage functioning, I'm glad that I was able to get how it works...(sort of). But it took me months just to get this far.
Here is the code:
[pbcode]
setfps 60
Constant Gravity# = 9.8
Constant JumpState_Off = 0
Constant JumpState_JumpingUp = 1
Constant JumpState_FallingDown = 2
//Create Screen Type
type tScreenBounds
Left
Right
Floor
endtype
Dim bounds as tScreenBounds
bounds.left = 32
bounds.right = getscreenwidth()-bounds.left
bounds.floor = getscreenheight()
//Create Player Type
type tPlayer
x#, y#
AccelX#
AccelStepX#
TopSpeedX#
AccelY#
AccelStepY#
TopSpeedY#
JumpState
CollisionImg
endtype
Dim Player as tplayer
player.x# = getscreenwidth()/2
player.y# = getscreenheight()/2
player.AccelX#=0
player.AccelStepX#=0.75
player.TopSpeedX#=10
player.AccelY#=0
player.AccelStepY#=Gravity#/30
player.TopSpeedY#=Gravity#*2
player.JumpState=JumpState_Off
CollisionImg=Draw_Collision_Map()
player.Collisionimg=CollisionImg
Do
cls 0
Drawimage Collisionimg,0,0,true
Handle_Player()
sync
loop
function Handle_Player()
If Rightkey()=true
player.accelx#=player.accelx#+player.accelstepx#
if player.accelX#>player.topspeedx# then player.accelX#=player.topspeedx#
endif
if LeftKey()=true
player.accelx#=player.accelx#-player.accelstepx#
if player.accelx#<-player.topspeedx# then player.accelx#=-player.topspeedX#
endif
If SpaceKey()
if player.Jumpstate=Jumpstate_off
player.jumpstate=JumpState_JumpingUp
player.accely#=-Gravity#
endif
endif
player.x#=player.x#+player.accelx#
player.y#=player.y#+player.accely#
if player.x#<bounds.left then player.x#=bounds.left
if player.x#>bounds.right then player.x#=bounds.right
width=64
height=32
height2=height*2
;If JumpState_Off make the player equal the red line ray so that it prefectly stops at the created grounds...
if player.JumpState=Jumpstate_Off
x=int(player.x#)
y=int(player.y#)-height
DistancetoFloor=FindDistancetoImpact(player.Collisionimg,x,y)
linec x,y,x,y+DistancetoFloor,RGB(255,0,0)
if DistancetoFloor<=height
player.AccelY#=0
player.y#=y+DistancetoFloor
else
player.JumpState=JumpState_FallingDown
player.AccelY#=0
endif
endif
;If JumpState_JumpingUp draw the redline Ray so the player can compute how far they are from the ground in realtime...
If player.JumpState=JumpState_FallingDown
x=int(player.x#)
y=int(player.y#)-height
DistancetoFloor=FindDistancetoImpact(player.Collisionimg,x,y)
linec x,y,x,y+DistancetoFloor,RGB(255,0,0)
if DistancetoFloor<=height
GroundY=y+DistancetoFloor ;Create Variable GroundY to represent y+DistancetoFloor
player.JumpState=JumpState_Off
player.AccelY#=0
Player.y#=GroundY
endif
endif
if player.y#=>bounds.floor-height
player.JumpState=JumpState_Off
player.AccelY#=0
player.y#=bounds.floor-height
endif
circlec player.x#,player.y#-height,32,1,rgb(255,0,0)
if player.AccelX#<>0
if player.AccelX#<0
player.accelX#=player.accelX#+(player.accelStepX#/2)
if player.accelX#>0 then player.accelX#=0
endif
if player.accelX#>0
player.accelx#=player.accelX#-(player.accelstepX#/2)
if player.accelX#<0 then player.accelX#=0
endif
endif
Select player.JumpState
case JumpState_JumpingUp
player.accelY#=player.accelY#+player.accelStepY#
if player.accelY#>0
player.accelY#=0
player.JumpState=JumpState_FallingDown
endif
case JumpState_FallingDown
player.accelY#=player.accelY#+player.accelStepY#
EndSelect
print player.y#
endfunction
function Draw_Collision_Map()
w=getscreenwidth()
h=getscreenheight()
img=newfximage(w,h)
rendertoimage img
line 0,h-50,w,h-50
line 200,h-200,600,h-100
line 0,h/2,150,h/2
line 650,h/2,w,h/2
line 200,(h/2)-100,600,(h/2)-200
rendertoscreen
endfunction img
function FindDistancetoImpact(img,Xpos,Ypos)
if Xpos>-1 and Xpos<getimagewidth(img) //If Xpos is more than -1 and less than imagewidth()"800x600" do this...
if Ypos<0 then Ypos=0
OldSurface=getsurface()
rendertoimage img
lockbuffer
NullPixel = Point(0,0)
Runs=PixelRunLength(Xpos,Ypos,0,1,RGB(0,0,0))-1
unlockbuffer
rendertoimage oldsurface
endif
endfunction Runs
[/pbcode]
But I'm still confused about some of the variables here, I was able to remove some code from your program and it still works perfectly like (Oldsurface). Also when it comes to the "FindDistancetoImpact" function, I can't seem to figure out what a few of the codes is doing, when I saw NullPixel=(0,0) and removed it, the ball would suddenly start flying up pass the top of the screen, so I would like to know what is this controlling???
And the rendertoimage img and rendertoimage oldsurface, usually I thought I was suppose to always type "rendertoscreen" when I am done, so how does this work?
Also my ball seems to bounce when it is going down the slope compared to yours, why is that?
QuoteBut I'm still confused about some of the variables here, I was able to remove some code from your program and it still works perfectly like (Oldsurface).
Just in case you're not sure, The point of the asking PlayBASIC what the current surface is, then restoring it at the end of a function. Is cleanliness. If your writing a function that alters the current surface, ink colour, current font or whatever. Then it's best to preserve these before you change them, so these states can be restored upon exit. So when you call your function from other parts of your program, the function doesn't unexpectedly change the state in some way.
Quote
Also when it comes to the "FindDistancetoImpact" function, I can't seem to figure out what a few of the codes is doing, when I saw NullPixel=(0,0) and removed it, the ball would suddenly start flying up pass the top of the screen, so I would like to know what is this controlling???
Bellow is taken from the manual,
* Note: Before you can use PixelRunLength safely. You must lock the draw surface, then it's highly recommended you read a pixel from this surface using Point(). This will ensure the address of the surface are seeded correctly internally for PixelRunLength
When we're drawing or reading from a graphics surface (the screen or an Image), the Pb gfx engine has certain fixed overheads that come doing any operation. Primarly it has to work out the correct rendering method for the surface your drawing to, then prepare it self for this operation. The preparation is seeding the internally buffer pointer and pixel format. You can think of this overhead as nanny code. So the nanny code protects you from the real world underneath!
While most built in commands in PB have nanny code in them, some don't.
PixelRunLength is one of them.
FastPoint &
FastDot are some others. These commands have no nanny code protection, given they're going to be called in high frequency. As while the nanny code might save you from poking your eye out, it can add considerable overhead to every operation.
[pbcode]
function FindDistancetoImpact(img,Xpos,Ypos)
if Xpos>-1 and Xpos<getimagewidth(img) //If Xpos is more than -1 and less than imagewidth()"800x600" do this...
if Ypos<0 then Ypos=0
OldSurface=getsurface()
rendertoimage img
lockbuffer
NullPixel = Point(0,0)
Runs=PixelRunLength(Xpos,Ypos,0,1,RGB(0,0,0))-1
unlockbuffer
rendertoimage oldsurface
endif
endfunction Runs
[/pbcode]
In the function above, what i'm doing here is first preserving the the current surface the GFX engine is rendering to, then I'm changing to the surface in question then locking it. The thing is, since we haven't drawn anything to this surface, we can't be sure if the internal frame buffer pointers and pixel formats have been initialized. But we can do this via using POINT(). When we call point, the nanny code within it (the core GFX engine) will work out the type of the surface and rendering routines we've selected , then initialize the pointers internally. Now given that PixelRunLength() has no nanny code in it, it simply uses gfx engines the current settings.
Quote
And the rendertoimage img and rendertoimage oldsurface, usually I thought I was suppose to always type "rendertoscreen" when I am done, so how does this work?
No.
Platformer Pixel Collisions Via Ray Casting V0.02 (Requires PlayBasic V1.64M or newer) The attached example uses is an extension the above example, where as the previous demo runs collision upon an image, this one builds a map representation and then runs collision upon that using the RayHitMapPixels() (https://playbasic.com/help.php?page=MAPS.RAYHITMAPPIXELS) function.
Requires: PlayBASIC V1.64M Beta 12 (or above)
Video: Download: