SFML平台游戏中如何实现与特定Tile的碰撞检测?
问题描述
我正在用SFML和C++开发一款平台游戏,已经通过官方示例加载了瓦片集,尝试添加玩家与特定瓦片的碰撞检测,但目前会检测到与所有瓦片的碰撞,不知道如何仅针对特定索引的瓦片进行碰撞检测,玩家是简单矩形。
现有代码
world.cpp
bool World::loadTiles(const std::string& tile_set, sf::Vector2u tile_size, const int* tiles, unsigned int desired_width, unsigned int desired_height) { if (!m_tileset.loadFromFile(tile_set)) { std::cout << "ERROR:: Cannot load tileset from file" << "\n"; return false; } m_vertices.setPrimitiveType(sf::Quads); m_vertices.resize(desired_width * desired_height * 4); for (unsigned int i = 0; i < desired_width; ++i) { for (unsigned int j = 0; j < desired_height; ++j) { int tile_number = tiles[i + j * desired_width]; // Find tile position in tileset int tu = tile_number % (m_tileset.getSize().x / tile_size.x); int tv = tile_number / (m_tileset.getSize().x / tile_size.x); // Get pointer to current tile quad sf::Vertex* quad = &m_vertices[(i + j * desired_width) * 4]; // Define four corners of tile quad[0].position = sf::Vector2f(i * tile_size.x, j * tile_size.y); quad[1].position = sf::Vector2f((i + 1) * tile_size.x, j * tile_size.y); quad[2].position = sf::Vector2f((i + 1) * tile_size.x, (j + 1) * tile_size.y); quad[3].position = sf::Vector2f(i * tile_size.x, (j + 1) * tile_size.y); // Define tiles four texture coordinates quad[0].texCoords = sf::Vector2f(tu * tile_size.x, tv * tile_size.y); quad[1].texCoords = sf::Vector2f((tu + 1) * tile_size.x, tv * tile_size.y); quad[2].texCoords = sf::Vector2f((tu + 1) * tile_size.x, (tv + 1) * tile_size.y); quad[3].texCoords = sf::Vector2f(tu * tile_size.x, (tv + 1) * tile_size.y); } } return true; } void World::draw(sf::RenderTarget& target, sf::RenderStates states) const { states.transform *= getTransform(); states.texture = &m_tileset; target.draw(m_vertices, states); } void World::collision(Players& players) { auto player = players.player; if (m_vertices.getBounds().intersects(player.getGlobalBounds())) { std::cout << "collision" << "\n"; } }
world.hpp
class World : public sf::Drawable, public sf::Transformable { public: bool loadTiles(const std::string& tile_set, sf::Vector2u tile_size, const int* tiles, unsigned int desired_width, unsigned int desired_height); virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const; void collision(Players& players); private: sf::VertexArray m_vertices; sf::Texture m_tileset; };
game.cpp
int x = -1; const int level[] = { x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, x, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, }; int tile_width = 32; int tile_height = 32; int desired_width = 32; int desired_height = 17; World world_map; if (!world_map.loadTiles("content/ground-tileset.png", sf::Vector2u(tile_width, tile_height), level, desired_width, desired_height)) { std::cout << "ERROR:: Cannot load tile map from file" << "\n"; }
解决方案
你当前的碰撞检测逻辑有误:m_vertices.getBounds()返回的是整个顶点数组的外接矩形,只要玩家进入地图范围就会触发碰撞,而非单个瓦片。要实现特定瓦片的碰撞检测,需要保存瓦片数据并逐个检测目标瓦片。
步骤1:修改World类,添加必要成员变量
在world.hpp中,新增保存瓦片数据、瓦片尺寸和地图规格的成员:
class World : public sf::Drawable, public sf::Transformable { public: bool loadTiles(const std::string& tile_set, sf::Vector2u tile_size, const int* tiles, unsigned int desired_width, unsigned int desired_height); virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const; void collision(Players& players); private: sf::VertexArray m_vertices; sf::Texture m_tileset; std::vector<int> m_tiles; // 保存每个瓦片的索引 sf::Vector2u m_tileSize; // 单个瓦片的尺寸 unsigned int m_mapWidth; // 地图宽度(瓦片数) unsigned int m_mapHeight; // 地图高度(瓦片数) };
步骤2:更新loadTiles函数,保存瓦片数据
在world.cpp的loadTiles函数中,初始化新增的成员变量,并保存每个瓦片的索引:
bool World::loadTiles(const std::string& tile_set, sf::Vector2u tile_size, const int* tiles, unsigned int desired_width, unsigned int desired_height) { if (!m_tileset.loadFromFile(tile_set)) { std::cout << "ERROR:: Cannot load tileset from file" << "\n"; return false; } // 初始化成员变量 m_tiles.resize(desired_width * desired_height); m_tileSize = tile_size; m_mapWidth = desired_width; m_mapHeight = desired_height; m_vertices.setPrimitiveType(sf::Quads); m_vertices.resize(desired_width * desired_height * 4); for (unsigned int i = 0; i < desired_width; ++i) { for (unsigned int j = 0; j < desired_height; ++j) { int tile_number = tiles[i + j * desired_width]; m_tiles[i + j * desired_width] = tile_number; // 保存瓦片索引 // 原有瓦片绘制逻辑不变 int tu = tile_number % (m_tileset.getSize().x / tile_size.x); int tv = tile_number / (m_tileset.getSize().x / tile_size.x); sf::Vertex* quad = &m_vertices[(i + j * desired_width) * 4]; quad[0].position = sf::Vector2f(i * tile_size.x, j * tile_size.y); quad[1].position = sf::Vector2f((i + 1) * tile_size.x, j * tile_size.y); quad[2].position = sf::Vector2f((i + 1) * tile_size.x, (j + 1) * tile_size.y); quad[3].position = sf::Vector2f(i * tile_size.x, (j + 1) * tile_size.y); quad[0].texCoords = sf::Vector2f(tu * tile_size.x, tv * tile_size.y); quad[1].texCoords = sf::Vector2f((tu + 1) * tile_size.x, tv * tile_size.y); quad[2].texCoords = sf::Vector2f((tu + 1) * tile_size.x, (tv + 1) * tile_size.y); quad[3].texCoords = sf::Vector2f(tu * tile_size.x, (tv + 1) * tile_size.y); } } return true; }
步骤3:重写碰撞检测函数
替换world.cpp中的collision函数,只针对特定索引的瓦片(比如示例中的1和2)进行检测:
void World::collision(Players& players) { sf::FloatRect playerBounds = players.player.getGlobalBounds(); // 优化:只遍历玩家可能接触的瓦片范围,减少计算量 int startX = std::max(0, static_cast<int>(playerBounds.left / m_tileSize.x)); int endX = std::min(static_cast<int>(m_mapWidth - 1), static_cast<int>((playerBounds.left + playerBounds.width) / m_tileSize.x)); int startY = std::max(0, static_cast<int>(playerBounds.top / m_tileSize.y)); int endY = std::min(static_cast<int>(m_mapHeight - 1), static_cast<int>((playerBounds.top + playerBounds.height) / m_tileSize.y)); for (int y = startY; y <= endY; ++y) { for (int x = startX; x <= endX; ++x) { int tileIndex = x + y * m_mapWidth; int tileNumber = m_tiles[tileIndex]; // 只检测需要碰撞的瓦片索引,这里以1和2为例 if (tileNumber == 1 || tileNumber == 2) { // 计算瓦片的世界坐标边界 sf::FloatRect tileBounds( x * m_tileSize.x, y * m_tileSize.y, m_tileSize.x, m_tileSize.y ); // 应用World的变换(比如地图平移) tileBounds = getTransform().transformRect(tileBounds); if (tileBounds.intersects(playerBounds)) { std::cout << "碰撞到可碰撞瓦片" << "\n"; // 这里可以添加碰撞响应逻辑,比如阻止玩家移动、触发事件等 } } } } }
说明
- 新增的
m_tiles数组保存了每个瓦片的索引,让我们能区分哪些是需要碰撞的瓦片。 - 通过计算玩家所在的瓦片范围,避免遍历整个地图,提升性能。
- 使用
getTransform().transformRect()将瓦片的本地坐标转换为世界坐标,确保碰撞检测在正确的坐标系下进行。
内容的提问来源于stack exchange,提问作者Aiden Rand
相关产品推荐
相关产品推荐

