0

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.

  • Use a [one-shot-if](https://stackoverflow.com/questions/56738380/most-elegant-way-to-write-a-one-shot-if) :) No really: You need to implement an (mouse-) on_enter and on_exit method for the relevant ui widgets – nada Sep 05 '19 at 09:58
  • You might also want to consider naming your game something other than _terraria_, a game with that name already exists. – nada Sep 05 '19 at 10:09
  • I know it already exists, I'm trying to recreate the entire game in c++ in order to compile it in 64 bit. Don't worry I will not distribute it, it's only a learning project. –  Sep 05 '19 at 10:31
  • trying to implement mod support using C++ would be hairy.. also I thought that Terraria supports x64 bit for longtime <. – Swift - Friday Pie Sep 05 '19 at 10:43
  • The only 64 bit launcher is this seperate tmodloader (https://forums.terraria.org/index.php?threads/1-3-tmodloader-fna-32bit-64bit-branch-of-tml.75644/) from the original tmodloader that is indeed 64bit. The original tmodloader and the game itself are in 32 bit unless you're playing on linux or mac, can't remember which version was 64bit only. –  Sep 05 '19 at 11:00

1 Answers1

0

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.

Eric
  • 1,183
  • 6
  • 17
  • Does that mean I have to create a seperate class for the Main menu buttons and instantiate it inside the menu constructor? –  Sep 05 '19 at 11:13
  • You dont "have to" but it would be probably a good idea. The logic of what happens when you select a button belongs to that button and not to something else. Also imagine what happens when your menu grows. With like 20 buttons or so having all the select logic of all buttons combined in a single function creates a huge mess. Also you dont have to change your code that selects things when adding new buttons at all. You can just store all clickable elements in a list and loop over them. To add a new button you then just need to create a new button object and add it to that list. – Eric Sep 05 '19 at 11:30
  • Thank you a lot! I implemented the mouse events and now the sound plays exactly as I wanted it. I'll consider the button class aswell. –  Sep 05 '19 at 11:43