Advertisement

Which design pattern to dodge the craziness of immediate mode?

Started by April 14, 2024 04:10 PM
7 comments, last by Aybe One 8 months ago

Let me explain in simple terms.

For my new engine for old game in Unity, time has come to get the in-game menu back on its feet.

I encountered the following issue:

void Update()
{
	// problem 1, upstream logic always returns same value according state (obvious you'll tell me)
	bool isPaused = ...;
	
	// problem 2, it gets forcibly shown when paused (obvious once again)
	SetRaceMenuVisibility(isPaused);
	
	// basically, I had to comment out previous line to wire-up my menu
}

I guess you get the point, immediate mode: things are always done in the loop.

Which means that my menu would always get shown, problem is there is focusing and selection, and therefore initialization/show cannot be done repeatedly.

So I came up with that N handlers pattern:

private static Action RacePauseHandler = RacePauseHandler1;

private static void RacePauseHandler1()
{
	// if user paused the game, switch to handler that will open menu
	if (Choice != -1) {
		RacePauseHandler = RacePauseHandler2;
	}
}

private static void RacePauseHandler2() // this gets called only once, exactly what we want
{
	// open in-game menu (quick and dirty but that's not the point)
	Object.FindAnyObjectByType<MenuManager>().ActivateDefault();

	// switch to handler that either NOP or rinse/repeat whole process
	RacePauseHandler = RacePauseHandler3;
}

private static void RacePauseHandler3()
{
	// if user unpaused the game, switch back to handler that acts upon enabled pause
	if (Choice == -1) {
		RacePauseHandler = RacePauseHandler1;
	}
}

void Update()
{
	bool isPaused = ...; // like above, no change in upstream logic

	RacePauseHandler(); // this will juggle with 3 methods
}

Basically, I end up with the 2nd method only called once (what I want), the menu works, it isn't constantly initialized as it was previously without that pattern to dodge the consequences of immediate mode.

Only problem is, it's quite bulky (I might have to do it for N other things).

Do you know of a more effective approach to tackle these kind of issues?

(knowing that I can't change (yet) most of upstream logic (little by little only), thus why I need this self-defensive code)

Hope that makes sense, thanks! 😁

I'm having a hard time understanding your issue but let me try.

Aybe One said:

initialization/show cannot be done repeatedly.

Well, you can simply introduce additional flag.

void Update()
{
	bool isPaused = ...;
	SetRaceMenuVisibility(isPaused);
}

void SetRaceMenuVisibility(bool isPaused)
{
	if(isPaused && !isVisible)
	{
		Show();
	}
	
	if(!isPaused && isVisible)
	{
		Hide();
	}
}

Aybe One said:
Do you know of a more effective approach to tackle these kind of issues?

When you want to turn on/off your menu as a response to a value being changed or an input being pressed you can either detect those input/value changes in a loop, as you do in your example or you can use Observer pattern in order to react to an Event/Action that is being triggered.

public static class EventManager
{
	public static UnityAction<bool> OnGamePausedAction;
}

public class PlayerInputHandler
{
	void Update()
	{
		if(Input.GetKeyDown(KeyCode.Escape)
		{
			PauseHandler.TogglePause();
		}
	}
}

public class PauseHandler
{
	public bool IsPaused;
	
	public void Pause()
	{
		IsPaused = true;
	}
	
	public void UnPause()
	{
		IsPaused = false;
	}
	
	public void TogglePause()
	{
		if(!IsPaused)
			Pause();
		else
			UnPause();
			
		OnGamePausedAction?.Invoke(IsPaused);
	}
}		

public class PauseMenu
{
	void OnEnable()
	{
		EventManager.OnGamePausedAction += OnGamePaused;
	}
	
	void OnDisable()
	{
		EventManager.OnGamePausedAction -= OnGamePaused;
	}
	
	private void OnGamePaused(bool isPaused)
	{
		if(isPaused)
			Show();
		else
			Hide();
	}
}


I don't know if I understand your problem correctly so my answer might not be on point which I apologize for.

None

Advertisement

Basically, what I was trying to achieve is as follows:

            if (IsPaused)
            {
                if (MenuManager.Active == false)
                {
                    MenuManager.Activate(true);
                }
            }
            else
            {
                if (MenuManager.Active)
                {
                    MenuManager.Activate(false);
                }
            }

To sum it up, handle the pause using no extra methods/flags/variables, etc.

Thing is, it quickly gets out of control when you keep introducing new members…

So as can be seen above, right choice was to extend the menu manager with a property and a method.

Thanks! 🙂

What I've done is use the idea of Stages. Each stage inherits from a Stage_Base class. The base stage has a pure virtual function like this : void Update(float dt)= 0;

I have stages for things like

  1. Displaying a title screen
  2. Playing the game
  3. Pausing the game
  4. Options (video, audio, controls, etc)

I start out with pCurrentStage = pTitleStage.

The TitleStage::Update function draws the title screen, which includes the text ‘Press any key to begin'. It also checks for keys being pressed, and when this happens it reassigns pCurrentStage = pGameStage.

So now pCurrentStage→Update(df) is running the game loop. It also checks to see if the ‘p’ key is pressed. If it is, it reassigns pCurrentStage = pPauseStage.

Now pCurrentStage→Update(dt) is running and displaying the text ‘Game paused.. press any key to continue' When a key is pressed , it sets pCurrentStage=pGameStage and the game continues.

Each stage has a pointer to a ‘previous stage’ so if you hit escape, it will return you to a previous stage, which is useful when you have menus within menus.

You don't need to mess around with flags, If/else or switch statements. Just change the variable that points to the current stage.

That's pretty much what the code base does here using delegates. When I first converted the C to pure C# using OpenGL, this approach worked fine. But as I moved to Unity and started proper refactor, original immediate behavior is insane to deal with at times.

I do have an history as you've suggested for the menu, so simple, yet so efficient.

Just had an idea to avoid constantly updating the interface: try go event-based with INotifyPropertyChanged.

Well well well… 🤣

I just tried the event-based pattern and it's incredibly bloated!

What I needed basically:

  • race is starting, camera approaching ship → no UI
  • ship passes start line → show race position
    • but (LOL) I need first block because position wouldn't show up until I get past another ship…

Which sums up to this, not including making properties observable:

var playerShip = RaceManager.Instance.Ships[Whatever1.ownShip];

playerShip.PropertyChanged += (sender, args) =>
{
    if (args.PropertyName != nameof(ShipData.LapNumber))
    {
        return;
    }

    var ship = sender as ShipData;

    if (ship.LapNumber > 0)
    {
        Effects.Controller.Display.SetLapPosition(Menus.winTable[Whatever1.ownShip].RacePosition);
    }
};

Menus.winTable[Whatever1.ownShip].PropertyChanged += (sender, args) =>
{
    if (args.PropertyName != nameof(Championship.RacePosition))
    {
        return;
    }

    if (playerShip.HasPastLine == false)
    {
        return;
    }

    var championship = sender as Championship;

    Effects.Controller.Display.SetLapPosition(championship.RacePosition);
};

It's pretty bloated but I can conditionally and sparingly update UI compared to immediate style.

But compared to immediate mode which was a one-liner, this now is 34 lines…

In the original code there was insane use of bools, like InMenu, ReallyInMenu , now I understand why! 🤣

Will process that during a good night of sleep and hopefully by next morning I will find a fix.😁

Advertisement

There are a couple concepts that are easy to mix.

You've got a game simulation, you've got a game's Time, and you've got the real passage of time for animations and needed for things like menu animation effects.

How you deal with them will be up to the game.

Probably the easiest is to set the game's time scale to 0 (e.g. Time.timeScale=0 to pause Time.timeScale=1 to resume) . Update will get called and you can process things that need to be processed but anything relying on Time like animations, Invoke, WaitForSeconds, anything using Time.deltaTime, all will be paused because they rely on scaled time values where you might pause the exciting race music and effects sounds but continue playing your game's theme song. Because music is often a different beast you can pause some audio streams but not others, controlling it however makes sense in your game. You can then use Time.unscaledTime for menu animations, or in any systems that need to get that time while still paused. This is the way the system was designed to work, so you'll find most Unity systems do the right thing out of the box for it. You can continue to drive your UI code, you can mark animations to run from any of the three Normal (scaled time), AnimatePhysics (with physics time) and UnscaledTime (wall clock time passing), depending on if you want the system to pause or not.

Most often for an in-game pause, Time.timeScale is want you'll want to do.

There are plenty of alternatives, might set a flag somewhere in your simulation that you can check your own shared value, you can build your own custom time management that relies on multiple sources including Time.timeScale (commonly done), you can do something similar with all the states and state machines where individual behaviors check the state to determine what they should be doing.

There are many situations where you want menus to be up and running but still have a simulation running, such as in the front end when you've got the world going in the background in an attract loop. In that scenario keep the time scale set and running, just don't start the clock on whatever is running your game simulation, let it remain in an out-of-game state.

Lots of options, many can be mixed together in creative ways.

Time stuff… yes, I yet have to dig into it, I will, ASAP!

The event-based approach, although bloated at first, is definitely the right way to go:

  • can have a single OnPropertyChanged() where I manage UI updates
  • completely remove UI-related stuff from main logic
  • only update UI when XYZ has changed

And that is a clear winner over the long term!

Tried looking at stuff online but as always, once slightly complex, either nothing or it's well hidden:

I'll stick with INotifyPropertyChanged for now and see how it really ends up over the next days!

This topic is closed to new replies.

Advertisement