no need for a separate vector. also you don't need adresses if, for example, all sand tiles are going to be the same and do the same thing.
if you have a tilemap like:
tile_map = [[0, 0, 0, 1, 0,],
[0, 0, 0, 1, 0,],
[2, 2, 0, 1, 0,],
[2, 2, 1, 1, 0,],
[0, 0, 0, 1, 0,]];
and each tile is 32x32 pixels, all you have to do to get a particular tile ID is divide mouse position by tile size. after mouse clicking, it would be something like that:
//Inside your events loop
sf::Vector2i tile_size(32, 32);
if (event.type == sf::Event::MouseButtonReleased && event.mouseButton.button == sf::Mouse::Left){
int tile_id = tile_map[sf::Mouse::getPosition().x/tile_size.x][sf::Mouse::getPosition().y/tile_size.y];
}
EDIT: i was tempted to make a full working example :D
#include <iostream>
#include <SFML/Graphics.hpp>
int main(){
std::vector<int> tile_map = {0, 2, 3, 2, 1,
0, 2, 2, 2, 0,
0, 2, 0, 2, 0,
0, 0, 1, 1, 1,
0, 2, 1, 1, 0};
sf::Vector2i map_size(5, 5); //Number of rows and columns of the map. in this example must match the vector above, since I didn't add any checker methods to ensure everything fits well.
sf::Vector2i tile_size(32, 32); //Tile size in pixels
sf::Vector2i tex_tile_size(32, 32); //Original tile size in the texture file, in pixels
sf::VertexArray vertices(sf::Quads, map_size.x*map_size.y*4); //4 vertices for each tile
sf::Texture tileset;
tileset.loadFromFile("tileset.png");
sf::RenderWindow window(sf::VideoMode(map_size.x*tile_size.x, map_size.y*tile_size.y), "SFML Tilemap");
for (int y=0; y<map_size.y; y++){
for (int x=0; x<map_size.x; x++){
const int n = y*map_size.x+x;
sf::Vertex* quad = &vertices[n*4];
quad[0].position = sf::Vector2f(x*tile_size.x, y*tile_size.y);
quad[1].position = sf::Vector2f((x+1)*tile_size.x, y*tile_size.y);
quad[2].position = sf::Vector2f((x+1)*tile_size.x, (y+1)*tile_size.y);
quad[3].position = sf::Vector2f(x*tile_size.x, (y+1)*tile_size.y);
const int tile_id = tile_map[n];
const int tx = tile_id % (tileset.getSize().x / tex_tile_size.x);
const int ty = tile_id / (tileset.getSize().x / tex_tile_size.x);
quad[0].texCoords = sf::Vector2f(tx*tex_tile_size.x, ty*tex_tile_size.y);
quad[1].texCoords = sf::Vector2f((tx+1)*tex_tile_size.x, ty*tex_tile_size.y);
quad[2].texCoords = sf::Vector2f((tx+1)*tex_tile_size.x, (ty+1)*tex_tile_size.y);
quad[3].texCoords = sf::Vector2f(tx*tex_tile_size.x, (ty+1)*tex_tile_size.y);
}
}
while (window.isOpen()){
sf::Event event;
while (window.pollEvent(event)){
if (event.type == sf::Event::Closed){
window.close();
}
if (event.type == sf::Event::MouseButtonReleased && event.mouseButton.button == sf::Mouse::Left){
sf::Vector2i current_tile(sf::Mouse::getPosition(window).x/tile_size.x, sf::Mouse::getPosition(window).y/tile_size.y);
int tile_id = tile_map[current_tile.y*map_size.x+current_tile.x];
std::cout << "You clicked tile [" << current_tile.x << "][" << current_tile.y << "]. ID of this tile is " << tile_id << std::endl;
}
}
window.clear(sf::Color::White);
window.draw(vertices, &tileset);
window.display();
sf::sleep(sf::milliseconds(10));
}
return 0;
}
(https://i.postimg.cc/V6Zcx0rc/Captura-de-tela-de-2021-04-05-14-17-05.png) (https://postimages.org/)
have fun!
if I understand correctly, you don't need convexShapes. just adjust the sf::VertexArray accordingly to each height. maybe something like:
quad[0].position = sf::Vector2f(x*tile_size.x, y*tile_size.y+height_increase);
Use point in polygon calculation in all cases (you can find various implementations). Actually you must have all 4 coordinates already since you are able to draw them. Based on your previous code, you know x, y, tile_width and tile_height of each tile. Let's say tile in base is rectangle with size 64 x 64 pix. Plus you have y offset from m_heightmap.
In the for loop will be:
int current_tile = -1; // Not valid
int id = 0;
top_left.x = tile.x;
top_left.y = tile.y + m_heightmap[some_index];
top_right.x = tile.x + tile_width;
top_right.y = tile.y + m_heightmap[some_index];
bottom_right.x = tile.x + tile_width;
bottom_right.y = tile.y + tile_height + m_heightmap[some_index];
bottom_left.x = tile.x;
bottom_left.y = tile.y + tile_height + m_heightmap[some_index];
if (PointInPolygon(mouse, top_left, top_right, bottom_right, bottom_left)
{
current_tile = id;
};
id++;
There are probably two concepts in the basics
1) Calculate tile coordinates in for loop
2) Pre-generate coordinates when creating a map and store it in tile classes or structs
Second approach is less messy (at least from my experience) and you can use data wherever it is needed.
There is no simple way how to get tile id in 3D or pseudo 3D maps, it leads into various problems. Thats why games have often flat maps or height is rather an illusion like in Transport Tycoon :) For example if height offset will be extend to other tile above, point in polygon can be valid in more than 1 case (>1 tile is selected). There is more ways, you can fill tiles with color and check ID based on that, use some raycast etc.
It's calculation whether point (mouse) is inside polygon (4 verticles of map tile).
Checking all map tiles vs mouse cursor can be slow with such algorithm, you can get approximate region of nearest tiles first (like sf::Vector2i current_tile(sf::Mouse::getPosition(window).x/tile_size.x, sf::Mouse::getPosition(window).y/tile_size.y); for 2x2 tiles etc.) and check those only.
#include <SFML/Graphics.hpp>
#include <SFML/Window.hpp>
#include <vector>
int PointInPolygon(const sf::Vector2f& p, const std::vector<sf::Vector2f>& v)
{
int i, j, c = 0;
for (i = 0, j = v.size() - 1; i < v.size(); j = i++)
{
if (((v[i].y > p.y) != (v[j].y > p.y)) &&
(p.x < (v[j].x - v[i].x) * (p.y - v[i].y) / (v[j].y - v[i].y) + v[i].x))
c = !c;
}
return c;
}
int main()
{
sf::RenderWindow window(sf::VideoMode(800, 600), "SFML works!");
window.setVerticalSyncEnabled(true);
std::vector<sf::Vector2f> v{ {25 , 50}, {100 , 50}, {100 , 150}, {45 , 160} };
sf::ConvexShape polygon;
polygon.setPointCount(4);
polygon.setPoint(0, v[0]);
polygon.setPoint(1, v[1]);
polygon.setPoint(2, v[2]);
polygon.setPoint(3, v[3]);
polygon.setOutlineColor(sf::Color::Red);
polygon.setOutlineThickness(2);
while (window.isOpen())
{
sf::Event event;
while (window.pollEvent(event))
{
switch (event.type)
{
case sf::Event::Closed:
window.close();
break;
default: break;
}
}
if (PointInPolygon(static_cast<sf::Vector2f>(sf::Mouse::getPosition(window)), v))
{
polygon.setFillColor(sf::Color::Green);
}
else
polygon.setFillColor(sf::Color::Red);
window.clear();
window.draw(polygon);
window.display();
}
return 0;
}
Yes Paul!
It works! :) So I'm still copying all tiles from vertices into std::vector<sf::ConvexShape> tileShapes;
Meanwhile I made a function void checkTiles which checks mouse position and calculates region of 3 tiles based on 32x32 tile size. It selects closest tile to mouse and 1 up from it and 1 down from it. I didn't check to left and right because tile sizes on X doesn't change and is always 32. This function saves int variable representing tile ID into std::vector<unsigned int> vec; and it matches the tile sequence of std::vector<sf::ConvexShape> tileShapes;
Then based on what ID's I'm having currently I'm checking for tileShapes[vec] and saving all 4 corner positions into std::vector<sf::Vector2f> v1, v2 and v3. Then I'm running your algorithm on these 3 vectors and checking which v is true in a separate function - unsigned int currentTile(mousePosView). And then in gamestate rendering it like that: this->window->draw(this->map.tileShapes[map.currentTile(mousePosView)]);
Only thing I'm struggling with now is that when mouse position on X axis is right on the edge of 2 tiles it's calling tileShape[0] for some reason :/
Any ideas?
Here is my code:
#pragma once
#include <SFML/Graphics.hpp>
class TileMap : public sf::Drawable, public sf::Transformable
{
public:
std::vector<sf::ConvexShape> tileShapes;
std::vector<unsigned int> vec;
std::vector<sf::Vector2f> v;
std::vector<sf::Vector2f> v1;
std::vector<sf::Vector2f> v2;
std::vector<sf::Vector2f> v3;
void checkTiles(sf::Vector2f mousePosView, sf::Vector2i tileSize, unsigned int width, unsigned int height)
{
vec[0] = unsigned int(((int)mousePosView.x / tileSize.x + ((int)mousePosView.y / tileSize.y) * width));
if((int)mousePosView.y < (height * 32) - 32)
vec[1] = unsigned int(((int)mousePosView.x / tileSize.x + (((int)mousePosView.y / tileSize.y) + 1) * width));
else
vec[1] = unsigned int(((int)mousePosView.x / tileSize.x + ((int)mousePosView.y / tileSize.y) * width));
if ((int)mousePosView.y > 32)
vec[2] = unsigned int(((int)mousePosView.x / tileSize.x + (((int)mousePosView.y / tileSize.y) - 1) * width));
else
vec[2] = unsigned int(((int)mousePosView.x / tileSize.x + ((int)mousePosView.y / tileSize.y) * width));
if ((int)mousePosView.y < height * 32 && (int)mousePosView.x < width * 32)
{
v1 = { {tileShapes[vec[0]].getPoint(0)}, {tileShapes[vec[0]].getPoint(1)}, {tileShapes[vec[0]].getPoint(2)}, {tileShapes[vec[0]].getPoint(3)} };
v3 = { {tileShapes[vec[2]].getPoint(0)}, {tileShapes[vec[2]].getPoint(1)}, {tileShapes[vec[2]].getPoint(2)}, {tileShapes[vec[2]].getPoint(3)} };
v2 = { {tileShapes[vec[1]].getPoint(0)}, {tileShapes[vec[1]].getPoint(1)}, {tileShapes[vec[1]].getPoint(2)}, {tileShapes[vec[1]].getPoint(3)} };
}
}
int PointInPolygon(const sf::Vector2f& p, const std::vector<sf::Vector2f>& v)
{
int i, j, c = 0;
for (i = 0, j = v.size() - 1; i < v.size(); j = i++)
{
if (((v[i].y > p.y) != (v[j].y > p.y)) &&
(p.x < (v[j].x - v[i].x) * (p.y - v[i].y) / (v[j].y - v[i].y) + v[i].x))
c = !c;
}
return c;
}
unsigned int currentTile(sf::Vector2f mousePosView)
{
if (PointInPolygon(static_cast<sf::Vector2f>(mousePosView), v1))
{
return vec[0];
}
else if (PointInPolygon(static_cast<sf::Vector2f>(mousePosView), v2))
{
return vec[1];
}
else if (PointInPolygon(static_cast<sf::Vector2f>(mousePosView), v3))
{
return vec[2];
}
}
bool load(const std::string& tileset, sf::Vector2i tileSize, const int* tiles, unsigned int width, unsigned int height, std::vector<std::vector<int>> m_heightmap, sf::Color gridColor)
{
// load the tileset texture
if (!m_tileset.loadFromFile(tileset))
return false;
// resize the vertex array to fit the level size
m_vertices.setPrimitiveType(sf::Quads);
m_vertices.resize(width * height * 4);
m_gridX.setPrimitiveType(sf::Lines);
m_gridX.resize(width * height * 4);
m_gridY.setPrimitiveType(sf::Lines);
m_gridY.resize(width * height * 4);
tileShapes.resize(width * height);
v.resize(4);
vec.resize(3);
for (auto i = 0; i < tileShapes.size(); i++) {
tileShapes[i].setPointCount(4);
tileShapes[i].setOrigin(0, 0);
}
// populate the vertex array, with one quad per tile
for (unsigned int x = 0; x < width; ++x)
for (unsigned int y = 0; y < height; ++y)
{
// get the current tile number
int tileNumber = tiles[x + y * width];
// find its position in the tileset texture
int tu = tileNumber % (m_tileset.getSize().x / tileSize.x);
int tv = tileNumber / (m_tileset.getSize().x / tileSize.x);
// get a pointer to the current tile's quad
sf::Vertex* quad = &m_vertices[(x + y * width) * 4];
sf::Vertex* gridX = &m_gridX[(x + y * width) * 4];
sf::Vertex* gridY = &m_gridY[(x + y * width) * 4];
// define its 4 corners
quad[0].position = sf::Vector2f(x * tileSize.x, y * tileSize.y + m_heightmap[x][y]);
quad[1].position = sf::Vector2f((x + 1) * tileSize.x, y * tileSize.y + m_heightmap[x + 1][y]);
quad[2].position = sf::Vector2f((x + 1) * tileSize.x, (y + 1) * tileSize.y + m_heightmap[x + 1][y + 1]);
quad[3].position = sf::Vector2f(x * tileSize.x, (y + 1) * tileSize.y + m_heightmap[x][y + 1]);
tileShapes[x + y * width].setPoint(0, quad[0].position);
tileShapes[x + y * width].setPoint(1, quad[1].position);
tileShapes[x + y * width].setPoint(2, quad[2].position);
tileShapes[x + y * width].setPoint(3, quad[3].position);
gridX[0].position = quad[0].position;
gridX[1].position = quad[1].position;
gridX[2].position = quad[3].position;
gridX[3].position = quad[2].position;
gridY[0].position = quad[0].position;
gridY[1].position = quad[3].position;
gridY[2].position = quad[1].position;
gridY[3].position = quad[2].position;
gridX[0].color = gridColor;
gridX[1].color = gridColor;
gridX[2].color = gridColor;
gridX[3].color = gridColor;
gridY[0].color = gridColor;
gridY[1].color = gridColor;
gridY[2].color = gridColor;
gridY[3].color = gridColor;
// define its 4 texture coordinates
quad[0].texCoords = sf::Vector2f(tu * tileSize.x, tv * tileSize.y);
quad[1].texCoords = sf::Vector2f((tu + 1) * tileSize.x, tv * tileSize.y);
quad[2].texCoords = sf::Vector2f((tu + 1) * tileSize.x, (tv + 1) * tileSize.y);
quad[3].texCoords = sf::Vector2f(tu * tileSize.x, (tv + 1) * tileSize.y);
}
return true;
}
private:
virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const
{
// apply the transform
states.transform *= getTransform();
// apply the tileset texture
states.texture = &m_tileset;
// draw the vertex array
target.draw(m_vertices, states);
target.draw(m_gridX);
target.draw(m_gridY);
}
sf::VertexArray m_vertices;
sf::VertexArray m_gridX;
sf::VertexArray m_gridY;
sf::Texture m_tileset;
};