SFML等距城镇游戏关闭时Vector下标越界问题排查
问题描述
我正在开发一款简单的等距视角城镇开发模拟游戏,核心类为Player类与World类。目前遇到的问题为:程序启动后运行流程符合预期,但当我准备点击窗口右上角“X”按钮关闭程序、鼠标移动到窗口右上角关闭按钮位置时,程序中断并抛出Vector subscript out of range错误,我在代码中并未手动创建vector容器。

经测试,不创建Player对象时不会触发该错误,因此判断问题出在Player类相关逻辑中。
相关代码
Player.hpp
#ifndef PLAYER_HEADER #define PLAYER_HEADER #include "Main.hpp" #include "ToScreenF.hpp" #include <iostream> class Player { public: sf::Vector2i MousePos; sf::Vector2i inCell; sf::Vector2i cellOffset; sf::Vector2i cellSelected; sf::Vector2f cellSelectedInWorldSpace; sf::Image selectedTileImage; sf::Texture selectedTileTexture; sf::Sprite selectedTileSprite; sf::Image CheatImage; sf::Color color; Player(std::string FILE); ~Player(); void Update(sf::RenderWindow* Window, sf::Vector2f TileSize, sf::Vector2f vOrigin, int WorldWidth, int WorldHeight); }; #endif PLAYER_HEADER
Player.cpp
#include "Player.hpp" Player::Player(std::string FILE) { selectedTileImage.loadFromFile(FILE); selectedTileImage.createMaskFromColor(sf::Color::White); selectedTileTexture.loadFromImage(selectedTileImage); selectedTileSprite.setTexture(selectedTileTexture); CheatImage.loadFromFile("tileCheat.png"); } void Player::Update(sf::RenderWindow* Window, sf::Vector2f TileSize, sf::Vector2f vOrigin, int WorldWidth, int WorldHeight) { MousePos = {sf::Mouse::getPosition((*Window))}; inCell = {(int)(MousePos.x / TileSize.x), (int)(MousePos.y / TileSize.y) }; cellOffset = { MousePos.x % (int)TileSize.x, MousePos.y % (int)TileSize.y }; cellSelected = { (inCell.y - (int)vOrigin.y) + (inCell.x - (int)vOrigin.x), (inCell.y - (int)vOrigin.y) - (inCell.x - (int)vOrigin.x) }; color = CheatImage.getPixel(cellOffset.x, cellOffset.y); if (color == sf::Color::Red) { cellSelected.x += -1; cellSelected.y += 0; }; if (color == sf::Color::Blue) { cellSelected.x += 0; cellSelected.y += -1; }; if (color == sf::Color::Green) { cellSelected.x += 0; cellSelected.y += 1; }; if (color == sf::Color::Yellow) { cellSelected.x += 1; cellSelected.y += 0; }; if (cellSelected.x < 0) cellSelected.x = 0; if (cellSelected.x > (WorldWidth - 1)) cellSelected.x = 19; if (cellSelected.y < 0) cellSelected.y = 0; if (cellSelected.y > (WorldHeight - 1)) cellSelected.y = 19; cellSelectedInWorldSpace = ToScreen(cellSelected.x, cellSelected.y, TileSize, vOrigin); selectedTileSprite.setPosition(cellSelectedInWorldSpace); Window->draw(selectedTileSprite); std::cout << cellSelected.x << " " << cellSelected.y << std::endl; }
ToScreenF.cpp
#include "ToScreenF.hpp" sf::Vector2f ToScreen(int x, int y, sf::Vector2f TileSize, sf::Vector2f vOrigin) { return sf::Vector2f { (vOrigin.x * TileSize.x) + (x - y) * (TileSize.x / 2), (vOrigin.y * TileSize.y) + (x + y) * (TileSize.y / 2) }; }
TApplication.hpp
#ifndef TAPPLICATION_HEADER #define TAPPLICATION_HEADER #include "Main.hpp" #include "World.hpp" #include "Player.hpp" namespace Application { class TApplication { protected: sf::RenderWindow *Window; World *GameWorld; Player* _Player; public: TApplication(); ~TApplication(); void Init(); void Run(); void End(); }; }
TApplication.cpp
#include "TApplication.hpp" namespace Application { TApplication::TApplication() : Window(nullptr) { } TApplication:: ~TApplication() { } void TApplication::Init() { if (Window == nullptr) Window = new sf::RenderWindow(sf::VideoMode(1200, 800), "Town Builder Simulator"); GameWorld = new World("BasicTile.png", 100, 100); _Player = new Player("selectedTile.png"); } void TApplication::Run() { sf::Event event; while (Window->isOpen()) { while(Window->pollEvent(event)) { if (event.type == sf::Event::Closed) { Window->close(); } } Window->clear(); GameWorld->Draw(Window); _Player->Update(Window, sf::Vector2f(40, 20), sf::Vector2f(10, 10), GameWorld->WorldWidth, GameWorld->WorldHeight); Window->display(); } } void TApplication::End() { if (Window != nullptr) { delete Window; delete GameWorld; Window = nullptr; } } }
Main.hpp
#ifndef MAIN_HEADER #define MAIN_HEADER #include <SFML/Graphics.hpp> #include <SFML/Window.hpp> #include <SFML/Audio.hpp> #include <SFML/Network.hpp> #include <SFML/System.hpp> #endif
问题原因
报错核心原因是鼠标移出窗口客户区时坐标越界,触发SFML内部vector容器的下标访问错误:
- 当鼠标移动到窗口右上角关闭按钮位置时,已经脱离窗口客户区范围,此时
sf::Mouse::getPosition返回的坐标会出现负值,或者大于窗口宽高值 - 代码中没有对鼠标坐标、计算得到的
cellOffset做范围校验,直接传入CheatImage.getPixel()访问像素 - SFML的
sf::Image内部用vector存储像素数据,你的CheatImage尺寸和瓦片一致为40*20,合法坐标范围x为039、y为019,传入超出范围的坐标时就会抛出vector下标越界错误,和你观察到的现象完全吻合。
代码另外存在三个隐藏问题:
- 边界钳制逻辑硬编码:
cellSelected超出世界范围时直接赋值为19,当世界尺寸大于20时逻辑错误 Init函数的if判断未加花括号,GameWorld和_Player的初始化不受Window == nullptr条件约束,存在逻辑隐患End函数未释放_Player对象内存,存在内存泄漏
修复方案
- 修改
Player::Update函数,增加坐标范围校验,修正边界钳制逻辑:
// 记得在文件头引入<algorithm>头文件以使用std::clamp,C++17以下版本可手动写范围判断 void Player::Update(sf::RenderWindow* Window, sf::Vector2f TileSize, sf::Vector2f vOrigin, int WorldWidth, int WorldHeight) { MousePos = sf::Mouse::getPosition(*Window); // 鼠标不在窗口客户区内时直接返回,不做后续计算 sf::Vector2u winSize = Window->getSize(); if (MousePos.x < 0 || MousePos.x >= (int)winSize.x || MousePos.y < 0 || MousePos.y >= (int)winSize.y) { return; } inCell = {(int)(MousePos.x / TileSize.x), (int)(MousePos.y / TileSize.y) }; cellOffset = { MousePos.x % (int)TileSize.x, MousePos.y % (int)TileSize.y }; // 双重校验偏移量在CheatImage尺寸范围内,避免越界 sf::Vector2u cheatSize = CheatImage.getSize(); if (cellOffset.x < 0 || cellOffset.x >= (int)cheatSize.x || cellOffset.y <0 || cellOffset.y >= (int)cheatSize.y) { return; } cellSelected = { (inCell.y - (int)vOrigin.y) + (inCell.x - (int)vOrigin.x), (inCell.y - (int)vOrigin.y) - (inCell.x - (int)vOrigin.x) }; color = CheatImage.getPixel(cellOffset.x, cellOffset.y); if (color == sf::Color::Red) cellSelected.x -= 1; if (color == sf::Color::Blue) cellSelected.y -= 1; if (color == sf::Color::Green) cellSelected.y += 1; if (color == sf::Color::Yellow) cellSelected.x += 1; // 修正边界钳制逻辑,不要硬编码19 cellSelected.x = std::clamp(cellSelected.x, 0, WorldWidth - 1); cellSelected.y = std::clamp(cellSelected.y, 0, WorldHeight - 1); cellSelectedInWorldSpace = ToScreen(cellSelected.x, cellSelected.y, TileSize, vOrigin); selectedTileSprite.setPosition(cellSelectedInWorldSpace); Window->draw(selectedTileSprite); std::cout << cellSelected.x << " " << cellSelected.y << std::endl; }
如果使用的C版本低于C17,可替换std::clamp为手动判断:
if (cellSelected.x < 0) cellSelected.x = 0; if (cellSelected.x >= WorldWidth) cellSelected.x = WorldWidth - 1; if (cellSelected.y < 0) cellSelected.y = 0; if (cellSelected.y >= WorldHeight) cellSelected.y = WorldHeight - 1;
- 修正
TApplication中的逻辑错误与内存泄漏:
void TApplication::Init() { if (Window == nullptr) { Window = new sf::RenderWindow(sf::VideoMode(1200, 800), "Town Builder Simulator"); GameWorld = new World("BasicTile.png", 100, 100); _Player = new Player("selectedTile.png"); } } void TApplication::End() { if (Window != nullptr) { delete Window; delete GameWorld; delete _Player; // 补充Player对象的内存释放 Window = nullptr; GameWorld = nullptr; _Player = nullptr; } }
内容的提问来源于stack exchange,提问作者luk_chesnok_xren
相关产品推荐
相关产品推荐

