1
\$\begingroup\$

I've been working on a tile based game with a 2d array as fundamental ground for my tiles. Now my next step is to have the wall tiles be impenetrable, so that the player can walk ontop of them and collide against their sides/bottom.

So as this is loosely based on tonyPa tutorial, it uses the corners of the player character (top-left, top-right, bottom-left, bottom-right corners) to check if any corner hits a wall (walls are assigned as "1" in the 2d array). When a corner touches a tile that is recognized as "1" (wall), I want to check from what side the player is hitting, and then move the player next to the tiles side, to simulate a collision.

Now what I've noticed is, it's easy to check if any corner is touching a wall, but its harder to determine which side the corner is hitting. Because of this, my current code is not simulating a satisfying collision detection. As my hero walks on the ground, he randomly stops because the bottom corners not only touch the top of the tiles, but also the sides it seems.

UPDATE

So I changed my code based on the first answer by bummzack. Using center points of each side of player entity instead of each corner. And now my character slides down walls without getting stuck and walks across grounds fairly smoothly, but another problem arises. If the player jumps on a tile, and the center part of the bottom is NOT within the tiles width, he falls right through. And this is because I am just comparing a POINT (the center point specifically) and not the full bottom side against the top of the tile beneath the player.

Here's a link to the SWF-file, http://dl.dropbox.com/u/28271061/_TileGameFLA.swf , X to JUMP, Z to RUN, ARROWKEYS to MOVE, which should give you a visual example of what's happening:

Here is the UPDATED code (all collision code is within "collisionCheck" function)

    private function onKEYDOWN(event:KeyboardEvent):void{
        var pressedKey = event.keyCode;
        
        if(pressedKey == Keyboard.RIGHT){
            rightKey = true;
        }
        
        if(pressedKey == Keyboard.LEFT){
            leftKey = true;
        }
        if(pressedKey == Keyboard.Z){
            runKey = true;
        }
        if(pressedKey == Keyboard.X){
            jumpKey = true;
        }
    }
    
    private function onKEYUP(event:KeyboardEvent):void{
        var releasedKey = event.keyCode;
        
        if(releasedKey == Keyboard.RIGHT){
            rightKey = false;
        }
        
        if(releasedKey == Keyboard.LEFT){
            leftKey = false;
        }
        if(releasedKey == Keyboard.Z){
            runKey = false;
        }
        if(releasedKey == Keyboard.X){
            jumpKey = false;
        }
    }
    
    
    
    private function collisionCheck():void{
        
        centerY = Math.floor((theHero.y + (theHero.width/2))/ts);
        centerX = Math.floor((theHero.x + (theHero.width/2))/ts);
        top = Math.floor(theHero.y / ts);
        bottom = Math.floor((theHero.y + theHero.height) / ts);
        left = Math.floor(theHero.x / ts);
        right = Math.floor((theHero.x + theHero.width) / ts);
        
        //If there is no ground under the player, start falling.
        if(curMap[bottom][centerX] != 1 && curMap[bottom][centerX] != 1){
            gravity = 3;
            onGround = false;
        }
        
        if(vy > 0 && oldBottom < bottom){
            
            if(curMap[bottom][centerX] != 0){//If either on of the bottom corners are touching a "1"-tile (a wall)
                
                theHero.y = ((bottom - 1) * ts) + 2 ;
                gravity = 0;
                vy = 0;
                onGround = true;
                jumped = false;
                jumpSpeed = 9;
                
            }
        }
        
        if(vy < 0 && oldTop > top){
            if(curMap[top][centerX] != 0){
                theHero.y = (top + 1) * ts; 
                gravity = 3;
                vy = 0;
                jumpSpeed = 0;
            }
        }
        
        if(vx < 0 && oldLeft > left ){
            if(curMap[centerY][left] != 0){
                vx = 0;
                theHero.x = (left + 1) * ts;
                
            }
        }
        trace(curMap[bottom][centerX]);
        if(vx > 0 && oldRight < right ){
            if(curMap[centerY][right] != 0 ){
                vx = 0;
                theHero.x = ((right - 1) * ts )+ 1.8; // 1.8 so it doesnt touch wall, it goes right through if its the accurate 2.0
                
            }
        }
        
    }

    
    private function moveChar():void{
        
        if(rightKey){
            if(runKey){
                vx = 7;
            }else{
                vx = 4;
            }
        }
        if(leftKey){
            if(runKey){
                vx = -7;
            }else{
                vx = -4;
            }
        }
        
        //Stand still if you're not pressing left or right ON THE GROUND (in air you should after one key press)
        if(!leftKey && !rightKey && onGround){
            vx = 0;
        }
        
        if(jumpKey && jumpSpeed > 1 && !jumped){
            vy -= jumpSpeed;
            jumpSpeed *= 0.8;
            onGround = false;
            gravity = 3;
        }
        
        
        
        
        vy += gravity;
        
        if(vy > 14){
            vy = 14;
        }
        
        
        
    }
    
    private function onENTERFRAME(event:Event):void{
        
        if(myStage.stage.focus != myStage){
            myStage.stage.focus = myStage;
        }
        
        //These variables represent where the hero WAS before being moved
        //, this helps to see if hero approached the tile from the left/right/top/bottom
        //, thus gives us an additional way to check where player is coming from
        oldBottom = Math.floor((theHero.y + theHero.height) / ts);
        oldLeft = Math.floor(theHero.x / ts);
        oldRight = Math.floor(theHero.x + theHero.width) / ts;
        oldTop = Math.floor(theHero.y / ts);
        
        
        moveChar();
        
        theHero.y += vy;
        theHero.x += vx;
        
        collisionCheck();
        
        
    }

}

}

This is by far not the smartest way to check collisons, but I think it might work well enough. I just need someone to throw me in the right direction for making a solid collision detection engine for a simple tile based game.

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Instead of checking the corners of your sprite, I suggest you check the midpoints. Like in this image:

collision points

The bottom "sensor" checks for ground collisions, the front checks for "wall" collisions. Usually you'll need maximum two active sensors. Examples:

  • Player walks forward, FRONT and BOTTOM must be checked
  • Player stands still: BOTTOM must be checked
  • Player performs forward jump. While moving upward, FRONT and TOP must be checked, when falling, FRONT and BOTTOM must be checked.
  • etc.
\$\endgroup\$
14
  • \$\begingroup\$ Ok so I did that, and now the issue is that the player will go RIGHT through the tile UNLESS the midpoint (midLeft or midRight or midBottom etc..)is touching the wall. For example if I stand under a tile, and I jump, if the midpoint of the player is touching the tile, ONLY then does it collide, but if a corner of the player is touching the tile above, he goes right through it. So this doesn't make for an airtight collision detection. Am I missing something? \$\endgroup\$
    – Kid
    Commented May 29, 2011 at 16:17
  • \$\begingroup\$ It's not an airtight collision detection by any means (it will obviously never work flawlessly against objects that are smaller than half the size of the player). You could experiment with moving your "sensors", depending on the current player movement. Otherwise you might want to look into AABB and SAT collision checking. Or read here and here. \$\endgroup\$
    – bummzack
    Commented May 29, 2011 at 21:49
  • \$\begingroup\$ I am learning AABB and SAT from Metanets tutorial right now, although it seems I have to dig deeper in VECTORS and how to use them in order to apply AABB properly. But essentially, you are just looking at the width and height of the overlap rectangle, and then using that to calculate which side the player is coming from and by how much he penetrates the tile, right? (and then using that to push player back in right direction with right amount of pixels) Oh btw, I updated the code and the text with a link, take a look at the SWF and you'll see the new problems. \$\endgroup\$
    – Kid
    Commented May 29, 2011 at 22:58
  • \$\begingroup\$ @Kid that sounds about right. Keep in mind that your AABB check might return several collisions (player could collides with 2 tiles). I think your collision detection as it is now works pretty good though. Maybe you could just increase the bottom and top sensors to one at each corner and be fine? \$\endgroup\$
    – bummzack
    Commented May 30, 2011 at 6:56
  • \$\begingroup\$ @bummzack that is what I did at first, just checking each corner. But the issue is, my character kept getting stuck everywhere. Like jumping next to a wall would make the character stick instead of sliding down. And even walking on ground the player would stop at every tile, only thing you could do was to JUMP in order to move forward. This was because the left/bottom corner would detect a non-walkable tile or the right/bottom would. \$\endgroup\$
    – Kid
    Commented May 30, 2011 at 23:56

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .