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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 01:45:08