XNA Menu release problem

Started by
2 comments, last by Spool 7 years, 11 months ago

Hy everyone, so I've being trying to understand how can I release my Main Menu from memory and screen and so on when
the game is started. So far I have done this, so in my Menu class I have a class called MenuButton and it looks like this:


public class MenuButton
        {
            public Texture2D buttonTxd;
            public Rectangle buttonRect;
            public int ButtonHeight;
            public int ButtonWidth;

            public bool isClicked()
            {
                if (Game1.GetCursorRect().Intersects(buttonRect) && Mouse.GetState().LeftButton == ButtonState.Pressed)
                {
                    return true;
                }
                else return false;
            }

            public bool isHovered()
            {
                if (Game1.GetCursorRect().Intersects(buttonRect))
                {
                    return true;
                }
                else return false;
            }            

        }

This is inside Menu class and Menu class also dosen't contain a constructor becouse I am mainly doing all the work inside Menu class.

Now after MenuButton class I create 2 new instances of that Class, one is called Play and one is called Quit .

Then I have Initialize and Load Content functions, which yous initialize buttons and some Menu variables and well Load Content loads Textures soundEffect and a Song.

Now at the end of my Menu class I have these 3 functions:


// Menu Release
        public void Dispose()
        {
            Play = null; // Button
            Quit = null; // Button
            hButtonSnd = null; // Sound played when button is hovered
            GC.Collect();         
        }

        public bool isDefined(MenuButton mb1) // isDefined 1 arg
        {
            if (mb1 == null)
            {
                return false;
            }
            else return true; 
        }

        public bool isDefined(MenuButton mb1, MenuButton mb2) // isDefined 2 args
        {
            if (mb1 == null || mb2 == null)
            {
                return false;
            }
            else return true;
        }

Between that and Load Content() are ofcourse my Update and Draw methods, now here is an Update one:


public void Update()
        {
            if((Play.isHovered() || Quit.isHovered()) && isDefined(Play, Quit)) // As you can see I also checked if 
                                                                                //  Play and Quit weren't null
            {
                if (EnableHoverSound)
                {
                    hButtonSnd.Play();
                    EnableHoverSound = false;
                } 
            }
            else EnableHoverSound = true;


            if (Play.isClicked())
            {
                PlayGame = 1;
                MediaPlayer.Stop();
                Dispose(); // Sets buttons and a sound to NULL and GC.Collect()
               
            }
        }

Now ofcourse I have to show you some code from my Game1.cs file, at the beggining of Game1 I declare a new instance of Menu menu = new Menu(); ,called menu.Initialize() in Game1 Initialize method and also menu.LoadContent(Content), in LoadContent() method, and now this is how my Game1 Update() looks like:



protected override void Update(GameTime gameTime)
        {
            if (menu.PlayGame == 1) // this is the same as menu.Play.isClicked()
            {
                if (fade.A == 1) // related to cursor fading if Play is clicked
                    fade.A++;
                else fade.A--;
            }
 
            elapsed_time += (float)gameTime.ElapsedGameTime.TotalMilliseconds;
            if (elapsed_time >= 1000.0f)
            {
                fps = total_frames;
                total_frames = 0;
                elapsed_time = 0;
            }
 
            if (menu.Quit.isClicked() && menu.isDefined(menu.Quit) || Keyboard.GetState().IsKeyDown(Keys.Escape))
            {
                this.Exit();
                MediaPlayer.Stop();
            }
            cursorRect = new Rectangle(Mouse.GetState().X, Mouse.GetState().Y, curText.Width, curText.Height);
 
            if (menu.isDefined(menu.Play, menu.Quit))
            {
                menu.Update();
            }
            base.Update(gameTime);        
}

And finally I have to show you just my Draw() method here:


protected override void Draw(GameTime gameTime)
        {
            total_frames++;
            GraphicsDevice.Clear(fade);
            spriteBatch.Begin();
            if (menu.isDefined(menu.Play, menu.Quit))
            {
                menu.Draw(spriteBatch);
            }
            spriteBatch.Draw(curText, cursorRect, fade);
            DrawHelp();
            spriteBatch.End();

            base.Draw(gameTime);
        }

Now after showing you all this code here is what my game does:

1.I enter the game, I hear my Song playing, all good

2. I hover my Play and Quit buttons and they play a sound like they should

3. If I Click Quit button the game will quit

4. If I Click Play button The Song will stop, sounds also, no more Play and Quit buttons thats what I want, BUT THEN THE GAME FREEZES

I cant move moouse cursor, it wont minimize, I have to do Start + L to get to that chose user Screen to get back in and shut the thing with taskmgr.exe

This might be a long post but I wanted to post all relevant code so that some good guys could just read it quickly and say aha here is your mistake. Hahah, thanks in advanced :)

Advertisement

OKAY NEW POST:

I know my code looks bad so I cleaned it up a bit, and now the place where I should

Unload Play and Quit button textures is here in my Game1 Draw() method that looks like this:


 protected override void Draw(GameTime gameTime)
        {
            total_frames++;
            spriteBatch.Begin();

            switch(currentGameState)
            {
                case GameState.MainMenu:
                    {
                        menu.Draw(spriteBatch);
                    }break;

                case GameState.GamePlay:
                    {
                        GraphicsDevice.Clear(Color.CornflowerBlue);                        
                        MediaPlayer.Stop();
                        menu = null;
                        menu.Play.buttonTxd.Dispose();
                        menu.Quit.buttonTxd.Dispose();
                        recentAction("Shit cleaned up!");
                    }
                    break;
            }
        
            spriteBatch.Draw(curTexture, cursorRect, Color.Red);
            DrawHelp();
            spriteBatch.End();

            base.Draw(gameTime);
        }

So I have came to conclusion that no matter where in my code I want to dispose or set to null my Play or Quit button

the game will always freeze, if GameState.GamePlay would look like this:


case GameState.GamePlay:
                    {
                        GraphicsDevice.Clear(Color.CornflowerBlue);                        
                        MediaPlayer.Stop();
                        menu = null;
                        // menu.Play.buttonTxd.Dispose(); // no button
                        // menu.Quit.buttonTxd.Dispose(); // disposal works fine whyyy?
                        recentAction("Shit cleaned up!");
                    }
                    break;

then It would work perfectly, but I want to know why I cant clean those textures :( also putting texture disposal before menu = null; will do the same

(Some might say: you set menu to null, garbage collector cleans it, it cant clean those textures then)

Why is that so that I can clean up my menu instance but can those textures that I loaded in menu class why can't I unload them,

well actually I mean I probably can but what am I doing wrong here?

TL;DR You're disposing of code that the system is being told to use, causing a NullException and crashing your program.

In context of your reply, the code:


case GameState.GamePlay:
                    {
                        GraphicsDevice.Clear(Color.CornflowerBlue);                        
                        MediaPlayer.Stop();
                        menu = null;
                        // menu.Play.buttonTxd.Dispose(); // no button
                        // menu.Quit.buttonTxd.Dispose(); // disposal works fine whyyy?
                        recentAction("Shit cleaned up!");
                    }
                    break;

would work because you're not trying to access the nulled menu object. When you null an object, you effectively unlink it from memory (which is not to say that the garbage collector cleans it up; it actually creates a memory leak if done improperly). When you try to follow that nullification with a menu.Play... object access call, you're trying to access the member of something that has no link in memory anymore. This causes a crash on your edited code. Overall, I'd recommend not nullifying anything in your Draw method. It should handle your display rendering code and nothing else.

Problem looks to be in how you're handling your disposals and nulls, which are two different things, and when those nulled objects come back to haunt you in code. Through menu.Update(), the object goes on to nullify its members, which isn't how Dispose works; you're doing more of a Clear since you intend to reuse the Menu class and you're just removing references to other objects (Texture2D). If you want to release the texture memory, you should call yourTextureMember.Dispose(), then null the member. This will set it so the GC knows to clean it a bit later.


// MenuButton Dispose with flag
public void Dispose(bool disposing){
    if(disposing){
        this.Dispose();
    }
}

// Actual disposal code goes here
protected void Dispose(){
    // Clear references
    // Check nulls, call dispose (if available) for any other referenced objects

    // If object is used nowhere else, call its Dispose() and null it
    if(buttonTxd != null)
        buttonTxd.Dispose();
    buttonTxd = null;

    // If object is a reference and is used by other objects elsewhere
    // Just set the reference to null
    buttonTxd = null;
}

So, since Game1.Update() calls itself 60 times a second, the next run of the Game1.Update method checks for menu.Quit. This button is now null, so you are trying to call null.isClicked(), which will result in a NullException being fired and a crash in your program.

Be mindful of the objects you're nulling/attempting to dispose and when your code still relies on them. I'd also suggest not manually calling GC.Collect(). Let C# handle that for you.

One more thing to add. Is there a problem that the menu is causeing that is requiring you to dispose of it? If not just let C# handle it for you. And then if there is a problem htat arises then try to solve the problem.

This topic is closed to new replies.

Advertisement