Search code examples
c++sfml

How do I only play a sound once using sfml inside the main game loop


I've got 2 classes, one is entirely Menu setup and render and the other is the main game class that is being called in main(). I've made a method that gets the user mouse position and if it is on one of the menu texts it will "select" the menu item and play a sound. Now the problem is that the sound does not play once but keeps repeating unless you hover away from any of the texts.

I've tried using a clock like another person that I found online, but it doesn't seem to work exactly the way it was supposed to because I have what he had split into 2 classes instead of it all being in 1 place.

This is the solution I was talking about: https://en.sfml-dev.org/forums/index.php?topic=15297.0

This is the code to run the program inside the main class, that is being called in main() inside main.cpp.

void Terraria::Run()
{
    Menu Menu(mTerraria, mTerraria.getSize().x, mTerraria.getSize().y);
    Menu.mMainTheme.play();


    while (mTerraria.isOpen())
    {
        sf::Event event;
        Menu.GetMousePosition(mTerraria);

        while (mTerraria.pollEvent(event))
        {
            switch (event.type)
            {
            case sf::Event::MouseButtonReleased:
            {
                    switch (event.key.code)
                    {
                    case sf::Mouse::Left:
                        switch (Menu.GetPressedItem())
                        {
                        case 0:
                        {
                            break;
                        }
                        case 1:
                        {
                            break;
                        }
                        case 2:
                        {
                            break;
                        }
                        case 3:
                        {
                            break;
                        }
                        case 4:
                        {
                            Menu.mMenuOpen.play();
                            mTerraria.close();
                            break;
                        }
                        }
                    }
                }
            default:
                HandlePlayerInput(event.key.code);
                break;

            case sf::Event::Closed:
            {
                mTerraria.close();
            }
            }

            mTerraria.clear();
            Menu.Render(mTerraria);
            mTerraria.display();
            }
        }


}

This is the function it calls on line 10 of the while loop. It's inside the second class called Menu.

void Menu::GetMousePosition(sf::RenderWindow& mTerraria)
{
    UnSelect();

    if (mMenu[0].getGlobalBounds().contains(sf::Mouse::getPosition(mTerraria).x, sf::Mouse::getPosition(mTerraria).y))
    {
        Select(mMenu[0]);
        OnSP = true;
        OnMP = false;
        OnAchievements = false;
        OnOptions = false;
        OnExit = false;
        SelectedItemIndex = 0;
    }

    else if (mMenu[1].getGlobalBounds().contains(sf::Mouse::getPosition(mTerraria).x, sf::Mouse::getPosition(mTerraria).y))
    {
        Select(mMenu[1]);
        OnSP = false;
        OnMP = true;
        OnAchievements = false;
        OnOptions = false;
        OnExit = false;
        SelectedItemIndex = 1;
    }

    else if (mMenu[2].getGlobalBounds().contains(sf::Mouse::getPosition(mTerraria).x, sf::Mouse::getPosition(mTerraria).y))
    {
        Select(mMenu[2]);
        OnSP = false;
        OnMP = false;
        OnAchievements = true;
        OnOptions = false;
        OnExit = false;
        SelectedItemIndex = 2;
    }

    else if (mMenu[3].getGlobalBounds().contains(sf::Mouse::getPosition(mTerraria).x, sf::Mouse::getPosition(mTerraria).y))
    {
        Select(mMenu[3]);
        OnSP = false;
        OnMP = false;
        OnAchievements = false;
        OnOptions = true;
        OnExit = false;
        SelectedItemIndex = 3;
    }
    else if (mMenu[4].getGlobalBounds().contains(sf::Mouse::getPosition(mTerraria).x, sf::Mouse::getPosition(mTerraria).y))
    {
        Select(mMenu[4]);
        OnSP = false;
        OnMP = false;
        OnAchievements = false;
        OnOptions = false;
        OnExit = true;
        SelectedItemIndex = 4;
    }
    else
    {
        OnSP = false;
        OnMP = false;
        OnAchievements = false;
        OnOptions = false;
        OnExit = false;
    }
}

This is the Select() function that is called every time the mouse is over any of the text. Again inside the Menu class.

void Menu::Select(sf::Text &mMenu)
{
    mMenu.setColor(sf::Color::Yellow);
    mMenu.setScale(sf::Vector2f(1.1f, 1.1f));
    mMenuTick.play();
}

I need the sound to play only once when you hover over any of the texts and then not play until you either hover away and hover on to the next, or hover over the next text. I am aware that it's being called millions of time because of the while loop.


Solution

  • A very simple way to do this would be to check the id of the currently selected gui elementand only call select() if your mouse is over an element that isn't the currently selected one.

    Your GetMousePosition checks would then change to something like this:

    if (mMenu[0].getGlobalBounds().contains(sf::Mouse::getPosition(mTerraria).x, sf::Mouse::getPosition(mTerraria).y))
    {
        if SelectedItemIndex == 0 then return;        
    
        Select(mMenu[0]);
        OnSP = true;
        OnMP = false;
        OnAchievements = false;
        OnOptions = false;
        OnExit = false;
        SelectedItemIndex = 0;
    }
    

    That way you simply exit the whole function when your mouse is over a button that already is selected.

    However while this works, i strongly recommend going for proper MouseEnter and MouseLeave events instead. Also doing the select with a bunch of if/else statements and passing the button to a select function isnt very good. Select() should be a member of your button instead. You then just write a function that gets you the button your mouse is over and can call button.select() on it. That makes your code much cleaner in the long run.