You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 23:27:36