Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Tilemap collision with player  (Read 19263 times)

0 Members and 2 Guests are viewing this topic.

etixpp

  • Jr. Member
  • **
  • Posts: 82
    • View Profile
    • FoxFire Development Website
    • Email
Tilemap collision with player
« on: June 01, 2013, 04:08:51 am »
Hello, i am new to sfml and created a tilemap like in the example on tutorial, now i wanted to check collosion from player with something like fence, i know how to do this, at example with player.getglobalbounds() and so on but how can i get globalbounds of special parts of the tile map? I am kinda confused there :/

Tilemap is like Example tilemap here:
http://www.sfml-dev.org/tutorials/2.0/graphics-vertex-array.php
Player ist a normal Sprite ...
Things like running and so on work just fine atm

paulomag2106

  • Newbie
  • *
  • Posts: 16
    • View Profile
Re: Tilemap collision with player
« Reply #1 on: December 19, 2013, 02:13:34 am »
I have basically the same doubts as etixpp. how do i get from the tilemap, suggested in the tutorial, the information i need for collision detection? would i need to create floatrects in for loop? how would that work?

I'm a beginner when it comes to programming, but I really want to try.

eigenbom

  • Full Member
  • ***
  • Posts: 228
    • View Profile
Re: Tilemap collision with player
« Reply #2 on: December 19, 2013, 06:13:34 am »
The simplest approach is to separate the world coordinates from the screen coordinates. Let's make our game follow a grid, so the player is always in a specific tile and has integer coordinates. For example

Code: [Select]
struct Player {
  int tileX;
  int tileY;
};

Player p;
if (press left) p.tileX -= 1;
// etc..

When we draw the player, we then map his tileX, tileY world coordinates to the screen. For example:
Code: [Select]
... draw ...
sf::RectangleShape shape(sf::Vector2f(playerWidth,playerHeight));
shape.setFillColor(sf::Color::Red);
shape.setPosition(player.tileX * 32, player.tileY * 32);

(The 32 is how many screen pixels wide a tile is.)

We can now detect what tile the player is on, just by looking into that const int level array. For example:
Code: [Select]
int tileUnderPlayer = level[player.tileX + player.tileY*16];
if (tileUnderPlayer==1){
  // player is in the water!!
}

Lastly .. to stop the player from entering a tile, we need to detect which tile he is about to step on before he actually moves there. E.g.,
Code: [Select]
if (press left){
  if (player.tileX==0){ /* do nothing, we are at the edge */ }
  else {
     int newX = player.tileX - 1;
     int newY = player.tileY;
   
     int tile = level[newX  + newY*16];
     if (tile==1){
       // can't move here, there's water here!

     }
     else {
        // Can move here..
        player.tileX = newX;
        player.tileY = newY;
     }
  }
}

Hope this helps!

(Be extra careful when changing tileX, tileY to make sure it doesn't go out of bounds, otherwise your array lookup might throw a memory access violation.)





paulomag2106

  • Newbie
  • *
  • Posts: 16
    • View Profile
Re: Tilemap collision with player
« Reply #3 on: December 19, 2013, 02:32:32 pm »
Thank you very much, eigenbom. I'll try to do this on my own code, and see if I can make my player move more freely instead of increments of Tilesize.

Anyway, it's a great start!

thank you!  :)

paulomag2106

  • Newbie
  • *
  • Posts: 16
    • View Profile
Re: Tilemap collision with player
« Reply #4 on: December 19, 2013, 07:47:51 pm »
I have another question TT_TT....

sorry for being such a noob, but... if, by following the tutorial, we have a 1 dimension array/list... how can I separate the X from the Y position of things when checking for collisions?

because following your example, and creating the TileUnderPlayer int, the X position interferes in the math, making the behavior of the object all weird Oo....

eigenbom

  • Full Member
  • ***
  • Posts: 228
    • View Profile
Re: Tilemap collision with player
« Reply #5 on: December 20, 2013, 12:06:00 am »
Don't be sorry, it's called learning .. you'll always be doing it! :)

Here's a toy example:

Code: [Select]
static const int TILES_WIDE = 4, TILES_HIGH = 3;
const int level[TILES_WIDE * TILES_HIGH] = {
  0, 0, 0, 0,
  1, 1, 0, 0,
  0, 1, 1, 1
};

To access the tile that is i units across and j units down, we just do..

Code: [Select]
int i = 2;
int j = 3;
int tileAtIJ = level[i + j*TILES_WIDE];

We are mapping from 2 integers (i,j) into a single index into the array. Choose any i,j and compute the index i + j*TILES_WIDE, and then validate yourself that it maps to the correct element.

Actually, now that I look at the tutorial, I see that code is already there, in the Tilemap::load function...

Code: [Select]
// get the current tile number
  int tileNumber = tiles[i + j * width];

As your game gets more complicated you'll want to build your own data structures. For example you could build a class called Map2D:

Code: [Select]
class Map2D {
public:
  Map2D(int w, int h){ .... init .... }
  int getTileAt(int i, int j){ return mLevelData[i + j*mWidth]; }
  void setTileAt(int i, int j, int tile){ mLevelData[i + j*mWidth] = tile;}
protected:
  int mWidth, mHeight;
  std::vector<int> mLevelData;
};

// ....

Map2D map(4, 3);
int i = 3, j = 2;
int tileIJ = map.getTileAt(i,j);

// etc..


paulomag2106

  • Newbie
  • *
  • Posts: 16
    • View Profile
Re: Tilemap collision with player
« Reply #6 on: December 20, 2013, 01:00:43 pm »
wow, thanks a lot, eigenbom! you were very helpful!

i'll attempt to implement it on my "game". :D

paulomag2106

  • Newbie
  • *
  • Posts: 16
    • View Profile
Re: Tilemap collision with player
« Reply #7 on: December 21, 2013, 04:23:05 pm »
Alright!! collision works PERFECTLY with the tileX tileY system! Hooray... Now, I need to find a way to make the player be able to move freely, other than in increments of *tile size*...

tileX and tileY have to be full integers in order to get level[tileX + tileY * tile size] correctly. And the player has to move in multiples of tile size, otherwise, he will collide with a tiny-micro-puny-map(is this a map made for ants?).

anybody got an idea how I could work around that =/?

Thanks, everybody, in advance.

eigenbom

  • Full Member
  • ***
  • Posts: 228
    • View Profile
Re: Tilemap collision with player
« Reply #8 on: December 23, 2013, 10:25:05 am »
What I described above is only suitable if your entities (characters, monsters, etc) exist only on tiles. If you want the entities to be able to exist between tiles then that's an entirely different problem.

However, you could animate the player's position between tile coordinates when he moves. That way, the entity will always be an tileX,tileY position, but the player sees it moving smoothly between tiles. Hope that makes sense.

If you really want the entities to exist anywhere (not just exactly on top of tiles), then you'll need a different approach -- one that uses collision detection. Basically you would model the player as a rectangle that can have any screen space coordinates. Then whenever you move the player you would see which tile each of the corners of the players rectangle falls into, and then if there is a block there, you prohibit the player movement. Personally, I would just use integer coordinates like above and maybe have some smooth animation of the player walking between tiles.

Anyway good luck, I'm off on a xmas break now.. :)


paulomag2106

  • Newbie
  • *
  • Posts: 16
    • View Profile
Re: Tilemap collision with player
« Reply #9 on: December 23, 2013, 11:11:41 am »
What you described makes perfect sense. I believe that's how games like Flashback and Prince of Persia (the original) worked, right?

I'll try another way around it, though, cause I wanted to create something that allows free movement for the character.

But anyway, eigenbom, you were VERY helpful and on the spot with my doubts and questions, so I can only be ever thankful!

Cheers and Merry Christmas ^^!

eigenbom

  • Full Member
  • ***
  • Posts: 228
    • View Profile
Re: Tilemap collision with player
« Reply #10 on: December 24, 2013, 12:14:33 am »
No worries, and good luck!

So you'll need two separate collision test types. The first checks against the world geometry and the second checks against other entities roaming the map. The second you can do by looping through all the entities and doing rectangle-rectangle intersection tests. And the first you can do quite simply (but only if your entities are the same size or smaller than the tiles), by checking the tiles that each of the four corners of the entity's rectangle fall into. So ...

Code: [Select]
sf::Vector2i playerPos = ....;

// main loop ...
readInput();
if (player wants to move){
  // create the move direction from the keyboard input (e.g., if LEFT moveVector = sf::Vector(-1,0); )
  sf::Vector2i moveVector = ...;
  // np = position player wants to move to..
  sf::Vector2i np = playerPos + moveVector;
  // build collision rectangle (check order of IntRect params, I'm guessing...)
  sf::IntRect playerRect = sf::IntRect(np.x - playerWidth/2., np.y - playerHeight/2., playerWidth, playerHeight);
 
  if (!RectOverlapsWorld(playerRect, world)){
    // then allow player to move there..
    playerPos = np;
  }
}

// .. functions etc ...
static const int TILE_EMPTY = 0;
static const int TILE_WATER = 1;

bool RectOverlapsWorld(const sf::IntRect& rect, const World& world){
  sf::Vector2i topLeftScreenSpace = sf::Vector2i(rect.x, rect.y);
  sf::Vector2i topLeftTileSpace = sf::Vector2i(topLeftScreenSpace.x  / tileWidth, topLeftScreenSpace.y / tileHeight);
  // you need to implement isValidTileCoordinate(...)
  if (world.map.isValidTileCoordinate(topLeftTileSpace)){
    // world.map is of type Map2D above
    int tile = world.map.getTileAt(topLeftTileSpace.x, topLeftTileSpace.y);
    if (tile!=TILE_EMPTY){
      // then top left of rectangle is overlapping a water tile
      return true;   
    }
  }
 
  // top left coordinate doesn't overlap, so now check top-right, bottom-left, and bottom-right
  // ...

  // if all these checks pass then
  return false;
}


paulomag2106

  • Newbie
  • *
  • Posts: 16
    • View Profile
Re: Tilemap collision with player
« Reply #11 on: December 31, 2013, 04:03:31 pm »
Sorry I'm taking so long to answer. Between Christmas and New Year's, I haven't been too active with programming.

I KINDA understand your code, eigenbom, I guess I'll try to implement it to see if works... either way, you've been of much help!

I'll come back once I've tried it to say if it worked or not. If it DOES work, I'm sure a bunch of people might be interested in it ;)!

paulomag2106

  • Newbie
  • *
  • Posts: 16
    • View Profile
Re: Tilemap collision with player
« Reply #12 on: January 05, 2014, 12:58:01 pm »
sorry for annoying you so much, eigenbom, but when you say "bool RectOverlapsWorld(const sf::IntRect& rect, const World& world)" in the example code... what kind of class/struct is that?

because it is not a standart class from sfml library, nor did I declare it yet anywhere in my code, neither did you in your example... I'm confused =S...

paulomag2106

  • Newbie
  • *
  • Posts: 16
    • View Profile
Re: Tilemap collision with player
« Reply #13 on: January 05, 2014, 02:19:32 pm »
Oh, sorry, when I asked what you meant, I was talking about the part "World& world" reference in the bool function...

what would that "World" class be?

Thanks!

eigenbom

  • Full Member
  • ***
  • Posts: 228
    • View Profile
Re: Tilemap collision with player
« Reply #14 on: January 06, 2014, 02:41:21 am »
Gah, I wrote up a big reply but the SFML forum seemed to go down just as I posted it. :(

Suffice to say that World is just a made-up class/struct. Sorry I wasn't clear enough. It might look like this after you implement it:

Code: [Select]
struct World {
  int time; // e.g., time in the world (in hours)
  Map2D map;
  std::list<Monster> monsters;
  std::list<Bomb> bombs;
  Player player;
};

Here Map2D, Monster, Bomb, and Player are also made-up structs. I defined what Map2D might look like in a previous post.

Good luck.