collisions my sprite agains my tile map
  • KillerkoKillerko February 2011
    hello everybody,
    not sure if this is the right forum to ask but i couldn't find any "beginners" section here.

    i'm new to hge and win programming in general and well and i'm having some trouble with collisions in my simple 2d platformer

    i have a simple 2d array tile map drawn on the screen, there are only 2 blocks 0 and 1, where 1 is the block i want my sprite to collide with.
    every block, my sprite included is 32x32 pixels and my sprite is moving fluently pixel by pixel (i get that by modifing the first 2 hge tutorials)

    now the problem is i can't get it to work properly.. i managed to get some result where i could jump around and not pass through the blocks by checking if the block below (and other sides) my sprite has 0 or 1 but it was always imperfect and my character was getting stuck or just passed thru blocks etc.. I even disabled jumping and gravity just to make things simple but still couldnt make it work fluently..
    I even tried to move my char by just adding 1 to x and y axes to make it super simple but it was still shaking when i touched the wall or get stuck, or just passed thru wall completly..

    im a lot of confused now.. i have tried to visualise my bounding box of the sprite and even the blocks of the map close to me but i still can't find a way how to actually use it to do a simple working collision that would not get stuck or sticky or any other way broken...

    image

    if somebody could point me to the right direction, in what order should I do the whole thing.. (get keypress, change x,y values, check for collision, change x,y values?, etc..) or write a sample code with explanation.. I just want the sprite to smoothly colide with the blocks in all directions.
  • bagabaga February 2011
    You can convert position of character from screen coords to tile index using:
    1
    2
    grid_column = Math.floor(pos.x / cell_width); 
    grid_row = Math.floor(pos.y / cell_height);


    Then test for collision with nearest tiles (only 4 tests required)

    After collision detected just compute penetration depth of character vs tile AABBs and apply it on character position.

    Good tutorials:
    http://www.metanetsoftware.com/technique/tutorialA.html
    http://www.metanetsoftware.com/technique/tutorialB.html
  • VeylenderVeylender February 2011
    Well i guess you are already got used to the hgeRect helperclass....

    I got the same problems when i first tried it, but i found out why ....

    my character only had one bounding box ... if this single one intersects with another one you get stuck ... why ? he doesnt know what side collided so every movement gets disabled

    so i created 5 boundingboxes
    1 at top,
    1 at bottom
    1 reight/left
    and one for the whole sprite (hit detection for shooters etc..)

    but thefirst 4 boxes are only 1 pixel thick and also positioned on the top,bot... like a fence.
    always checking those 4 wnd you can always tell where the character collides..

    then you can say "if left rect collides the left movement gets disabled but right is still working" ........

    also a good tip is (at least how i found it out to be working pretty good) to make loops for movement.
    for example:
    when i want my char to move ... lets say 5 pixels to the right .. a loop gets started:

    move the character and the box position 1 pixel to the right... check if collision...
    if collision -> stop loop, move char 1 pixel to the left, collision right = true
    else -> continue the loop normally

    this is how i made it once and it is working perfectly fine ....
  • KillerkoKillerko February 2011
    2_baga: yes thats what I did originaly as you can see from the pic I posted I can detect tiles my object is intersecting, problem was when I tried to do something with it it didn't work very well.. I didn't know from what direction player is aproaching the tile.. but I sorted that somehow by comparing the penetration of x and y axes and pushed player back only on the axis which penetration was smaller.. that improved things a bit but it was still not ok and "losing speed occured at certain condisions". btw the tutorials look good, I just hope i will be able implement it ^^;

    2_Veylender: this is exactly what I have tried now, instead of using only 1 bounding box around my character I now have 4 additional boxes, 2 on left right side and 2 on top bottom, just 1 pixel wide like you sugested and this time its working a lot better as I can clearly say from what direction player approched the tile so I can adjust his possition and speed correctly.
    there are still problems tho, like you said when I move faster than 1-2 pixels per frame it causes problem because for example when I landing from jump, other (left and right) bounding boxes collide with the tiles too and this will cause to reset my X speed of the character and Im getting stuck again...
    I solved this by reducing the length of the bounging boxes, so they are not touching anymore and there is a small gap between them:
    image

    I have also tried your loop solution but was not able to implement it on the first try... will have to try again as I dont like the solution that I have now and it can still cause problem if my character will move faster than 3-4 pixels per frame.

    this is the simple code I have:
    image
    where bbox1-4 are the green bounding boxes around my char converted to tile numbers and if any of those is 1 that means there is collision to sort, blockBox1-4 are the same but not in tiles but in pixels and mapX,Y are the map offset (because my map is scrolling), and spriteSize is width and heigth of my sprite, dx and dy is the speed my characted is going right now

    anyway thanks to both of you, I have made progress! still not perfect and need more work to be something I will be happy with...
    I also think a lot about how could I implement those edges for my tiles to my tile map.. like its said in the tutorial, it looks very good the system they have as for now I only detect "whole tiles" nothing else and I know for sure this wont be enough for me.. and I will need "slopes" and similar tiles too.

    still, this is my first project and for about the month that Ive started to learn c++ is quite a progress I made as I now have scrolling map with paralax scrolling in the back, camera adjusting its possition based on the facing and position of my character, shooting, and first enemy :)[/code]
  • kvakvskvakvs February 2011
    If your character is never larger than 1 cell, then its enough to test collision for 4 character corners as points.
  • KillerkoKillerko March 2011
    2_kvakvs: I had my character the same size as all the tiles.. 32x32 pixels and checking only 4 corners didn't work very well.. I was getting stuck when touching the walls.

    but I made my character smaller now (or better said I made the bounding boxes smaller, the character is still the same 32x32 sprite)

    here is my first video with working collisions:


    and here is my second one with working slopes! (that took some time)


    I was reading a lot about separating axis theorem recently and would love to get that working as that would solve everything collision-concerned... but don't think I have the ability to implement that (yet) ..I have no idea where to start with that :roll:
  • and here is my second one with working slopes! (that took some time)



    That looks awesome! Would it be something you'd like to share? I've been working on a platformer and my slope collision support is failing miserably.
  • KillerkoKillerko March 2011
    hey Jonathan, sure I have no problem with sharing as I'm only learning myself...
    (i mean that the code might not be the best you have ever seen :oops: )

    before collision detection I call getBoundingBoxes() to get all the variables i need:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    void getBoundingBoxes()
    {
    // shrink bounding box to fit character on the sprite more closely
    heroineWalk->GetBoundingBox(x, y, &heroineBox);
    heroineBox.x1+=5;
    heroineBox.x2-=5;
    heroineBox.y1++;
    heroineBox.y2--;
     
    // boxes for left and right side of the player (1,2) and top and bottom (3,4)
    blockBox1.x1=heroineBox.x1-0;
    blockBox1.x2=heroineBox.x1-0;
    blockBox1.y1=heroineBox.y1+4;
    blockBox1.y2=heroineBox.y2-14;
     
    blockBox2.x1=heroineBox.x2+0;
    blockBox2.x2=heroineBox.x2+0;
    blockBox2.y1=heroineBox.y1+4;
    blockBox2.y2=heroineBox.y2-14;
     
    blockBox3.x1=heroineBox.x1+4;
    blockBox3.x2=heroineBox.x2-4;
    blockBox3.y1=heroineBox.y1-0;
    blockBox3.y2=heroineBox.y1-0;
     
    blockBox4.x1=heroineBox.x1+6;
    blockBox4.x2=heroineBox.x2-6;
    blockBox4.y1=heroineBox.y2+0;
    blockBox4.y2=heroineBox.y2+0;
    // get tile numbers for all the points in my bounding boxes
    bbox1x1 = (blockBox1.x1-mapX)/spriteSize;
    bbox1y1 = (blockBox1.y1-mapY)/spriteSize;
    bbox1x2 = (blockBox1.x2-mapX)/spriteSize;
    bbox1y2 = (blockBox1.y2-mapY)/spriteSize;
    bbox2x1 = (blockBox2.x1-mapX)/spriteSize;
    bbox2y1 = (blockBox2.y1-mapY)/spriteSize;
    bbox2x2 = (blockBox2.x2-mapX)/spriteSize;
    bbox2y2 = (blockBox2.y2-mapY)/spriteSize;
    bbox3x1 = (blockBox3.x1-mapX)/spriteSize;
    bbox3y1 = (blockBox3.y1-mapY)/spriteSize;
    bbox3x2 = (blockBox3.x2-mapX)/spriteSize;
    bbox3y2 = (blockBox3.y2-mapY)/spriteSize;
    bbox4x1 = (blockBox4.x1-mapX)/spriteSize;
    bbox4y1 = (blockBox4.y1-mapY)/spriteSize;
    bbox4x2 = (blockBox4.x2-mapX)/spriteSize;
    bbox4y2 = (blockBox4.y2-mapY)/spriteSize;
    bbox4xM = (blockBox4.x2-mapX-5)/spriteSize;
    bbox4y = (blockBox4.y2-mapY-1)/spriteSize;
    }


    and here is the collision detection for all the tiles (if you can make any sense of it)
    first I check for my character moving left "if (dx<0)" then right "if (dx>0)" then up "if (dy<0)" and then the last one the most important if the char is moving down "if (dy>=0)"
    the numbers 1-2 are basic square tiles 21-23 is not used here but its the same as 1-2 and 30-35 represent all the slope tiles, 36-37 is the newest addition the horizontal half tiles (32x16 pixels rectangle) you can touch these from either bottom or stand on them, but you can't touch them from side (yet) as for the most of the other slope tiles

    and I have fixed the character shaking when standing on the slope and half tiles yesterday too, you can see the shake in the video too

    30 = 45 degree forwardslash tile "/"
    31 = 45 degree backwardslash tile "\"
    32 and 33 = 22.5 degree forward half tiles "_-"
    34 and 35 = 22.5 degree backward half tiles "-_"
    36 = horizontal bottom half tile "▄"
    37 = horizontal upper helf tile "▀"
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    			if (dx<0) {
    if (((map[bbox1y1][bbox1x1]==1 || map[bbox1y2][bbox1x1]==1) || (map[bbox1y1][bbox1x1]==2 || map[bbox1y2][bbox1x1]==2) || (map[bbox1y1][bbox1x1]==21 || map[bbox1y2][bbox1x1]==21) || (map[bbox1y1][bbox1x1]==22 || map[bbox1y2][bbox1x1]==22) || (map[bbox1y1][bbox1x1]==23 || map[bbox1y2][bbox1x1]==23) || ((map[bbox1y1][bbox1x1]==30 || map[bbox1y2][bbox1x1]==30) && (blockBox1.x2-mapX-(bbox1x1*spriteSize))>11)))
    {
    x-=blockBox1.x1-mapX-(bbox1x1*spriteSize+spriteSize);
    dx=0;
    }
    }
    if (dx>0) {
    if (((map[bbox2y1][bbox2x1]==1 || map[bbox2y2][bbox2x1]==1) || (map[bbox2y1][bbox2x1]==2 || map[bbox2y2][bbox2x1]==2) || (map[bbox2y1][bbox2x1]==21 || map[bbox2y2][bbox2x1]==21) || (map[bbox2y1][bbox2x1]==22 || map[bbox2y2][bbox2x1]==22) || (map[bbox2y1][bbox2x1]==23 || map[bbox2y2][bbox2x1]==23) || ((map[bbox2y1][bbox2x1]==31 || map[bbox2y2][bbox2x1]==31) && (blockBox2.x2-mapX-(bbox2x1*spriteSize))<11)))
    {
    x-=blockBox2.x2-mapX-(bbox2x1*spriteSize);
    dx=0;
    }
    }
    if (dy<0) {
    if ((map[bbox3y1][bbox3x1]==1 || map[bbox3y1][bbox3x2]==1) || (map[bbox3y1][bbox3x1]==2 || map[bbox3y1][bbox3x2]==2) || (map[bbox3y1][bbox3x1]==21 || map[bbox3y1][bbox3x2]==21) || (map[bbox3y1][bbox3x1]==22 || map[bbox3y1][bbox3x2]==22) || (map[bbox3y1][bbox3x1]==23 || map[bbox3y1][bbox3x2]==23))
    {
    y-=blockBox3.y1-mapY-(bbox3y1*spriteSize+spriteSize);
    dy=0;
    } else
    {
    if (map[bbox3y1][bbox3x1]==37 || map[bbox3y1][bbox3x2]==37)
    {
    if (((bbox3y1*spriteSize+spriteSize)-(blockBox3.y2-mapY))>spriteSize/2)
    {
    y+=1; dy=0;
    }
    }
    if (map[bbox3y1][bbox3x1]==36 || map[bbox3y1][bbox3x2]==36)
    {
    y+=1; dy=0;
    }
    }
    }
    if (dy>=0)
    {
    if (map[bbox4y][bbox4xM]>=30 && map[bbox4y][bbox4xM]<=37)
    {
    y--; getBoundingBoxes();
    }
    if (map[bbox4y1][bbox4xM]==1 || (map[bbox4y1][bbox4x1]==2 || map[bbox4y1][bbox4x2]==2) || (map[bbox4y1][bbox4x1]==21 || map[bbox4y1][bbox4x2]==21) || (map[bbox4y1][bbox4x1]==22 || map[bbox4y1][bbox4x2]==22) || (map[bbox4y1][bbox4x1]==23 || map[bbox4y1][bbox4x2]==23))
    {
    y-=blockBox4.y1-mapY-(bbox4y1*spriteSize);
    dy=0;
    } else
    {
    if (map[bbox4y1][bbox4xM]==30)
    {
    if ((blockBox4.x2-mapX-5)-(bbox4xM*spriteSize)>(bbox4y1*spriteSize+spriteSize)-(blockBox4.y2-mapY))
    {
    y=(bbox4y1*spriteSize)-(blockBox4.x2-5-mapX-(bbox4xM*spriteSize))+16+mapY+1; dy=0; inAir=false;
    }
    y++; getBoundingBoxes();
    goto skip;
    }
    if (map[bbox4y1][bbox4xM]==31)
    {
    if ((bbox4xM*spriteSize+spriteSize)-(blockBox4.x2-mapX-5)>(bbox4y1*spriteSize+spriteSize)-(blockBox4.y2-mapY))
    {
    y=(bbox4y1*spriteSize)+(blockBox4.x2-5-mapX-(bbox4xM*spriteSize))-16+mapY+1; dy=0; inAir=false;
    }
    y++; getBoundingBoxes();
    goto skip;
    }
    if (map[bbox4y1][bbox4xM]==32)
    {
    if ((blockBox4.x2-mapX-5)-(bbox4xM*spriteSize)>((bbox4y1*spriteSize+spriteSize)-(blockBox4.y2-mapY))*2)
    {
    y=(bbox4y1*spriteSize)-(blockBox4.x2-5-mapX-(bbox4xM*spriteSize))/2+16+mapY+1; dy=0; inAir=false;
    }
    y++; getBoundingBoxes();
    goto skip;
    }
    if (map[bbox4y1][bbox4xM]==33)
    {
    if (((blockBox4.x2-mapX-5)-(bbox4xM*spriteSize))/2+16>((bbox4y1*spriteSize+spriteSize)-(blockBox4.y2-mapY)))
    {
    y=(bbox4y1*spriteSize)-(blockBox4.x2-5-mapX-(bbox4xM*spriteSize))/2+mapY+1; dy=0; inAir=false;
    }
    y++; getBoundingBoxes();
    goto skip;
    }
    if (map[bbox4y1][bbox4xM]==34)
    {
    if (((bbox4xM*spriteSize+spriteSize)-(blockBox4.x2-mapX-5))/2+16>((bbox4y1*spriteSize+spriteSize)-(blockBox4.y2-mapY)))
    {
    y=(bbox4y1*spriteSize)+(blockBox4.x2-5-mapX-(bbox4xM*spriteSize))/2-16+mapY+1; dy=0; inAir=false;
    }
    y++; getBoundingBoxes();
    goto skip;
    }
    if (map[bbox4y1][bbox4xM]==35)
    {
    if ((bbox4xM*spriteSize+spriteSize)-(blockBox4.x2-mapX-5)>((bbox4y1*spriteSize+spriteSize)-(blockBox4.y2-mapY))*2)
    {
    y=(bbox4y1*spriteSize)+(blockBox4.x2-5-mapX-(bbox4xM*spriteSize))/2+mapY+1; dy=0; inAir=false;
    }
    y++; getBoundingBoxes();
    goto skip;
    }
    if (map[bbox4y1][bbox4xM]==36)
    {
    if (((bbox4y1*spriteSize+spriteSize)-(blockBox4.y2-mapY)-1)<=spriteSize/2)
    {
    y-=spriteSize/2+1-((bbox4y1*spriteSize+spriteSize)-(blockBox4.y2-mapY)); dy=0; inAir=false;
    }
    y++; getBoundingBoxes();
    goto skip;
    }
    if (map[bbox4y1][bbox4xM]==37)
    {
    if (((bbox4y1*spriteSize+spriteSize)-(blockBox4.y2-mapY))<=spriteSize)
    {
    y-=spriteSize-((bbox4y1*spriteSize+spriteSize)-(blockBox4.y2-mapY)); dy=0; inAir=false;
    }
    }
    if (map[bbox4y][bbox4xM]>=30 && map[bbox4y][bbox4xM]<=37)
    {
    y++; getBoundingBoxes();
    }
    }
    skip:
    if (!map[bbox4y2][bbox4xM]>=30 && !map[bbox4y2][bbox4xM]<=37) inAir=true;
    }
  • VeylenderVeylender March 2011
    Wow and you said you sarted with c++ only 4 weeks ago ?

    Thats kinda .... impressive.
    I can see great things coming from you ^^
  • KillerkoKillerko March 2011
    thanks Veylender,
    but it's just a bunch of IF/ELSE statements nothing more, and it doesn't work very well either.. and every time I try to fix some bugs or add a new custom tile "I create" about 2 new bugs for each one I fix -_- ...so I gave up on those for now...

    I still have a looong way to learn c++.. something like classes is something i have never used before and my head hurts everytime I think about pointers...

    As windows programming goes I have never ever done any before I started early this year, but I do have some programming background from long long ago of time of ZX spectrum or even some pascal (and later some C) in DOS :D but that was very long ago and then I went another path of an 3d artist which became my profession

    anyway I have made some progress with my little game and made a new video:


    and there is also link for the compiled executable if somebody would be bored too much to give it a try haha
  • DaiShivaDaiShiva March 2011
    That's pretty awesome, reminds me of Cave Story.
  • evilsquareevilsquare March 2011
    look at these eyes - she is like sister of that boy! x)

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Sign In Apply for Membership

In this Discussion

Who's Online (0)