welcome to the second issue of jnrdev, in this tutorial we'll add a new type of tiles to to the game from jnrdev #1: hills and slopes.
note: through the whole tutorial slope will be used for slope or hill (hills and slopes are the same, it's just a point of view).
to check for slopes we'll use a technique i developed for this tutorial:
"slope slope axis axis" collision detection (made-up-name(TM))
i often write it down as:
s
s
a
a
because the second slope testing is only used for "moving from slope to slope" cases.
the slope collision detection also works without the second testing, but with it some jittering is avoided.
it's quite simple:
if( we entered a slope ){ move player and set new y position end collision checking } else if( we left a slope ){ set player y to the height of the slope on the side the player left it //this is the second slope testing if( we are now on a slope ){ //we're moving from slope to slope move player and set new y position end collision checking } } do normal seperate axis testing now (only if we're not on a slope)
we'll use this point to check if we are on a slope
the player enters a slope:
after slope collision detection, no seperate axis collision detection is done, because we are now on a slope.
the player leaves a slope:
we aren't on a slope in this frame -> use seperate axis collision detection.
the player moves from slope to slope:
no seperate axis collision detection afterwards.
there are three approaches to this:
a) calculate the position
easy to implement, but you can only have slopes basing on a (mathematical) function.
b) use a array to save the height of the slope at every pixel (-> int slopeheights[TILESIZE])
nice, you can have different shaped slopes this way.
but aligning y horizontal to the slope is difficult this way (but you won't need that anyway)
c) use pixel perfect collision detection
cool, because the slope information is already stored in the image.
but you have to lock the surface or copy the information to a seperate bitmask
if you want to use this, use a seperate mask - surface locking is awful (slow) (especially when using openGL for 2d blitting)
i think c) and b) are the better methods, because you can have different shaped slopes.
but i'll use a), because it's easier to implement.
... ok, you got me - b) isn't harder to code, but i've already coded a) and am lazy - i'll improve the slope system in another tutorial.
this is how a) works:
i assume every slope is a 45° line (it simplifies things sooooo much)
CTile now uses
enum TileType{t_nonsolid, t_solid, t_slopeleft, t_sloperight};
instead of bool solid.
and CMap::map(...) is used instead of CMap::colmapxy(...) - it returns the TileType.
i've changed all functions but think(), draw(), CPlayer to private.
and i've added some new functions:
i don't know what i should explain first, how to react on hitting a slope or how the whole slope thing works.
i think the big picture first would be better, but there are arguments against it, so read as you wish - either 3.2.1 first, or 3.2.2
first we'll take a look at the function bool CPlayer::collision_slope(int sx, int sy, int &tsx;, int &tsy;).
it's used to determine if we hit a slope at sx, sy.
if we hit a slope tsx and tsy contain the tile coordinate of the slope,
the y coordinate is set to align with the slope
and tsx, tsy contain the tile coordinates of the slope.
bool CPlayer::collision_slope(int sx, int sy, int &tsx;, int &tsy;){ tsx = sx / TILESIZE; //map coordinates of the tile we check against tsy = sy / TILESIZE; TileType t = map.map(tsx, tsy); //if we found a slope we set align y to the slope. if(t == t_sloperight){ //sloperight -> \ y = (tsy+1)*TILESIZE - (TILESIZE - (sx%TILESIZE)) - h - 1; return true;; } else if(t == t_slopeleft){ //slopeleft -> / y = (tsy+1)*TILESIZE - sx%TILESIZE - h - 1 ; return true; } return false; }
the most complicate part here is the aligning of the y coordinate:
example: we hit a t_sloperight:
now take a look how y this is calculated when hitting a t_sloperight:
y = (tsy+1)*TILESIZE - (TILESIZE - (sx%TILESIZE)) - h - 1; y = (tsy+1)*TILESIZE //y pixel coordinate of the ground of the tile - (TILESIZE - sx%TILESIZE) //minus how far sx is inside the tile (16 pixels in the exapmle) - h //minus the height (sx is located at the bottom of the player, but y is at the top) - 1 (one) //we don't want to stick in a tile, this would cause complications in the next frame //(ok, it won't because of my slope technique, but it will matter when improving it)
t_slopeleft works the same, but we don't need to substract sx%TILESIZE from TILSIZE because we're counting from the other side.
the slope collision detection is done before the seperate axis checking described in jnrdev #1.
if a collision with a slope occurs, the seperate axis checking is skipped.
//check for slopes (only if moving down) if(vely > 0){ int tsx, tsy; //slope tile coordinates int sx = x + (w>>1) + velx; //slope chechpoint x coordinate if(collision_slope(sx, (y + h), tsx, tsy) ){ //we entered a slope (y is set by collision_slope) x += velx; //move on //y has been set by collision_slope unlockjump(); //we hit the ground - the player may jump again vely = 1; //test against the ground again in the next frame slope_prevtilex = tsx; //save slope koordinate slope_prevtiley = tsy; return; } else{ //we're not on a slope this frame - check if we left a slope //-1 ... we didn't move from slope to slope //0 ... we left a slope after moving down //1 ... we left a slope after moving up int what = -1; if(map.map(slope_prevtilex, slope_prevtiley) == t_sloperight){ if(velx > 0) //sloperight + velx > 0 = we left a slope after moving up the slope what = 0; else what = 1; //we left after moving down the slope } else if(map.map(slope_prevtilex, slope_prevtiley) == t_slopeleft){ if(velx < 0) //sloperight + velx > 0 = we left a slope after moving up the slope what = 0; else what = 1; //we left after moving down the slope } if(what != -1){ //if we left a slope and now are on a slope int sy; if(what == 1){ y = tsy*TILESIZE - h -1; //move y to top of the slope tile sy = y + h; } else{ y = (tsy+1)*TILESIZE - h -1; //move y to the bottom of the slope tile sy = y + h + TILESIZE; //test one tile lower than the bottom of the slope //(to test if we move down a long slope) //it's physically incorrect, but looks a lot smoother ingame } //check for slopes on new position if( collision_slope(sx, sy, tsx, tsy) ){ //slope on new pos (entered a slope after we left a slope) //-> we moved from slope to slope x += velx; //y has been set by collision_slope unlockjump(); vely = 1; slope_prevtilex = tsx; slope_prevtiley = tsy; return; } } } }
slope_prevtilex, slope_prevtiley is saved for checking if we were on a slope in the previous frame
i'm not completely satisfied with this solution, because it has some errors.
but these occure in very rare cases and the solution would need quite some code rewriting, so i'll leave it like it is.
instead just tell your level designer, which things he isn't allowed to put in the levels, these are situations no one would expect in a game anyway.
when adding time based calculations in a later tutorial we'll need to fix the errors anyway :)
most tile combinations only cause jittering / being stuck in a tile for one frame. but this causes nasty level bugs (wall climbing, ...)
and there's another bug in the slope collision detection:
this happens, because we allign the y coordinate immediatly to the slope without checking if we really collide with the slope.
this bug won't affect gameplay, but i just don't like bugs.
the source example is here: jnrdev2example.zip.
for running the game you will also need SDL.dll, which you can get here: SDL-1.2.7-win32.zip.
for compiling you need the SDL headers, which can be found here: SDL-1.2.7.zip.
note: you have to add some include / library directories if you use VC++, take a look at VisualC.html, which comes with SDL how to do that.
if the direct links to the sdl stuff don't work here's the official sdl site: www.libsdl.org.
i this topic (and my explanations mixed with my bad english) made this tutorial a bit confusing, but i think it worked out quite well in the end.
i know that there are still some flaws in the checking, but they don't affect gameplay and only appear in very rare cases.
i want to improve this system later, but first we'll build a map editor and bigger maps.
questions / critics / comments? forum