Visual Studio下SFML马里奥克隆游戏跳跃音效无卡顿播放求助
问题:SFML马里奥克隆游戏跳跃音效无sleep无法播放
我用Visual Studio开发基于SFML的超级马里奥克隆游戏。游戏通过registerAction()绑定键盘输入到动作,游戏循环调用GameEngine::sUserInput()检测按键输入,再触发对应场景的sDoAction();每个场景的循环还会调用sMovement()更新实体位置。
现在想实现玩家跳跃时播放音效,已在sMovement()的跳跃检测逻辑中添加音效代码,但音效只有在代码后加sleep语句才能播放,而sleep会导致游戏卡顿数秒。求无sleep的音效实现方案。
相关代码片段
Scene_Play::init()
void Scene_Play::init(const std::string& levelPath) { std::cout << "Level Scene init started" << std::endl; registerAction(sf::Keyboard::P, "PAUSE"); registerAction(sf::Keyboard::Escape, "QUIT"); registerAction(sf::Keyboard::T, "TOGGLE_TEXTURE"); // toggle drawring (T)extures registerAction(sf::Keyboard::C, "TOGGLE_COLLISION"); // toggle drawring (T)extures registerAction(sf::Keyboard::G, "TOGGLE_GRID"); // toggle drawring (T)extures registerAction(sf::Keyboard::W, "UP"); registerAction(sf::Keyboard::A, "LEFT"); registerAction(sf::Keyboard::D, "RIGHT"); registerAction(sf::Keyboard::Space, "FIRE"); m_gridText.setCharacterSize(2); m_gridText.setFont(m_game->getAssets().getFont("dogica")); // Game Score m_scoreText.setCharacterSize(30); m_scoreText.setFont(m_game->getAssets().getFont("Mario")); m_scoreText.setFillColor(sf::Color::White); // Level Timre m_timerText.setCharacterSize(30); m_timerText.setFont(m_game->getAssets().getFont("Mario")); m_timerText.setFillColor(sf::Color::White); sf::View view = m_game->window().getView(); view.setSize(width() / 4.0f, height() / 4.0f); m_game->window().setView(view); auto start = std::chrono::high_resolution_clock::now(); loadLevel(levelPath); auto end = std::chrono::high_resolution_clock::now(); std::cout << "Time taken to load Scene and its scene entities : " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start) << std::endl; }
Scene_Play::sDoAction()
void Scene_Play::sDoAction(const Action& action) { auto& input = m_player->getComponent<CInput>(); if (action.type() == "START") { if (action.name() == "TOGGLE_TEXTURE") { m_drawTexture = !m_drawTexture; } else if (action.name() == "TOGGLE_COLLISION") { m_drawCollision = !m_drawCollision; } else if (action.name() == "TOGGLE_GRID") { m_drawGrid = !m_drawGrid; } else if (action.name() == "PAUSE") { m_paused = !m_paused; } else if (action.name() == "QUIT") { onEnd(); } else if (action.name() == "UP") { input.up = true; } else if (action.name() == "DOWN") { input.down = true; } else if (action.name() == "RIGHT") { input.right = true; } else if (action.name() == "LEFT") { input.left = true; } else if (action.name() == "FIRE" && !input.shoot) { input.shoot = true; spawnBullet(m_player); } } else if (action.type() == "END") { if (action.name() == "UP") { input.up = false; } else if (action.name() == "DOWN") { input.down = false; } else if (action.name() == "RIGHT") { input.right = false; } else if (action.name() == "LEFT") { input.left = false; } else if (action.name() == "FIRE") { input.shoot = false; } } }
Scene_Play::sMovement()
void Scene_Play::sMovement() { // Player movement auto& input = m_player->getComponent<CInput>(); auto& transform = m_player->getComponent<CTransform>(); if (input.up && input.canJump) { transform.velocity.y -= m_playerConfig.JUMP; input.canJump = false; sf::Sound m_sound; m_sound.setBuffer(m_game->getAssets().getSound("MarioAir")); m_sound.play(); std::this_thread::sleep_for(std::chrono::milliseconds(100)); } float xSpeed = 0.0f; if (input.right) { xSpeed += m_playerConfig.SPEED; transform.scale.x = -1.0f; } if (input.left) { xSpeed -= m_playerConfig.SPEED; transform.scale.x = 1.0f; } transform.velocity.x = xSpeed; if (xSpeed == 0.0f) { if (m_isSuperMario && m_player->getComponent<CState>().state != "SuperMarioStand") { m_player->getComponent<CState>().state = "SuperMarioStand"; m_StateChanged = true; } else if (!m_isSuperMario && m_player->getComponent<CState>().state != "MarioStand") { m_player->getComponent<CState>().state = "MarioStand"; m_StateChanged = true; } } else { if (m_isSuperMario && m_player->getComponent<CState>().state != "SuperMarioRun") { m_player->getComponent<CState>().state = "SuperMarioRun"; m_StateChanged = true; } else if (!m_isSuperMario && m_player->getComponent<CState>().state != "MarioRun") { m_player->getComponent<CState>().state = "MarioRun"; m_StateChanged = true; } } auto& gravity = m_player->getComponent<CGravity>(); transform.velocity.y += gravity.gravity; if (transform.velocity.y >= m_playerConfig.MAXSPEED) { transform.velocity.y = m_playerConfig.MAXSPEED; } transform.prevPos = transform.pos; transform.pos += transform.velocity; // rest code for different input handle }
GameEngine::sUserInput()
void GameEngine::sUserInput() { sf::Event event; while (m_window.pollEvent(event)) { if (event.type == sf::Event::Closed) { std::cout << "Exiting the game!\n"; quit(); } if (event.type == sf::Event::KeyPressed && event.key.code == sf::Keyboard::X) { std::cout << "trying to save screeshot to " << "test.png" << std::endl; sf::Texture t; t.create(m_window.getSize().x, m_window.getSize().y); t.update(m_window); if (t.copyToImage().saveToFile("test.png")) { std::cout << "screenshot saved to " << "test.png" << std::endl; } } if (event.type == sf::Event::KeyPressed || event.type == sf::Event::KeyReleased) { if (getCurrentScene()->getActionMap().find(event.key.code) == getCurrentScene()->getActionMap().end()) { continue; } const std::string actionType = (event.type == sf::Event::KeyPressed) ? "START" : "END"; getCurrentScene()->sDoAction(Action(getCurrentScene()->getActionMap().at(event.key.code), actionType)); } } }
解决方案
问题核心是:你在sMovement()中创建的sf::Sound是局部变量,函数执行完毕后变量立即被销毁,音效还没开始播放就被终止。加sleep只是延迟了变量销毁时间,给了音效播放窗口,但会阻塞游戏循环。
方案1:将Sound对象改为类成员变量
在Scene_Play类中添加sf::Sound m_jumpSound;作为成员变量,在init()方法中提前绑定音效缓冲区:
void Scene_Play::init(const std::string& levelPath) { // ... 原有代码 ... m_jumpSound.setBuffer(m_game->getAssets().getSound("MarioAir")); // ... 原有代码 ... }
然后在跳跃逻辑中直接调用播放,删除sleep语句:
if (input.up && input.canJump) { transform.velocity.y -= m_playerConfig.JUMP; input.canJump = false; m_jumpSound.play(); }
方案2:使用音效池(适合多音效场景)
如果游戏有多个音效需要同时播放(如跳跃、吃金币、踩敌人),可以维护一个std::vector<sf::Sound>作为音效池,重用空闲的Sound对象:
// Scene_Play类成员 std::vector<sf::Sound> m_soundPool; const int POOL_SIZE = 5; // 根据需求调整 // init中初始化音效池 void Scene_Play::init(const std::string& levelPath) { // ... 原有代码 ... auto jumpBuffer = m_game->getAssets().getSound("MarioAir"); for (int i = 0; i < POOL_SIZE; ++i) { sf::Sound sound; sound.setBuffer(jumpBuffer); m_soundPool.push_back(sound); } // ... 原有代码 ... } // 播放音效的辅助函数 void Scene_Play::playJumpSound() { for (auto& sound : m_soundPool) { if (sound.getStatus() == sf::Sound::Stopped) { sound.play(); return; } } // 若所有音效都在播放,可选择忽略或扩容池 } // 跳跃时调用 if (input.up && input.canJump) { // ... 原有代码 ... playJumpSound(); // ... 原有代码 ... }
方案3:确保音频模块初始化
检查游戏启动时是否正确初始化SFML音频模块,可手动调用sf::SoundBuffer::ensureInit()确保音频系统正常工作。
内容的提问来源于stack exchange,提问作者Zoro
相关产品推荐
相关产品推荐

