Managing States

1. Introduction

State is a class descending from TUIState. It determines what is currently displayed, and is the primary place where you can react to events (handle user input, passage of time, clicks on buttons and more).

In a larger application you will likely have more than one state. If you want to display something "completely different" (like a game, or main menu, or a "game over screen") then it’s most natural to do this by switching to a different state.

You can add new state to your application using the menu item Code → New Unit → Unit With State…​ in CGE editor. It just creates a new Pascal unit that defines a new TUIState descendant and loads a new user interface design.

Multiple viewports and basic game UI UI dialog, in a state over the game UI

2. Changing state

At runtime, you can change from one state into another using:

  1. You can set TUIState.Current := StateXxx to make the new state the one and only currently active state.

    This is the simplest way to change current state. For example use this to change from main menu, to loading, to playing game, to game over states.

    Usually the implementation of one state has code to change it into another state. For example, this is how StateMainMenu can react to user pressing Enter to switch to StatePlay:

    function TStateMainMenu.Press(const Event: TInputPressRelease): Boolean;
    begin
      Result := inherited;
    
      if Event.IsKey(keyEnter) then
      begin
        TUIState.Current := StatePlay;
        Exit(ExclusiveEvents);
      end;
    end;
  2. You can alternatively use TUIState.Push to push new state on top of the stack, making it the front-most state (but not necessarily the only state active right now). Such state will usually pop itself from the stack, using TUIState.Pop, although you can also set TUIState.Current to just change whole stack into a single new state.

    Using a state stack makes sense when you want to display one state on top of another. For example, you may want to push options state to make options UI visible on top of the game. The game can even be still animated underneath (it is up to you to pause the game if you want, e.g. by changing Viewport.Items.Paused).

    For example, this is how StatePlay could display StateOptions on top, when user presses Escape:

    function TStatePlay.Press(const Event: TInputPressRelease): Boolean;
    begin
      Result := inherited;
      if Result then Exit; // allow the ancestor to handle keys
    
      if Event.IsKey(keyEscape) and
         (TUIState.CurrentTop = StatePlay) then
      begin
        TUIState.Push(StatePause);
        Exit(true);
      end;
    end;

    The StateOptions can in turn pop itself from the stack when user presses Escape again:

    function TStateOptions.Press(const Event: TInputPressRelease): Boolean;
    begin
      Result := inherited;
      if Result then Exit; // allow the ancestor to handle keys
    
      if Event.IsKey(keyEscape) then
      begin
        // parameter StatePause is optional here, just allows to make additional check
        TUIState.Pop(StatePause);
        Exit(true);
      end;
    end;

3. Creating new states

While in theory you can create instances of the TUIState at any point, in practice it is usually most comfortable to create all of them at the beginning of the application, in Application.OnInitialize handler.

If you use the Code → New Unit → Unit With State…​ editor menu item, it will automatically edit your Application.OnInitialize handler in (by default) gameinitialize.pas unit to create the new state. So it will look like this:

{ One-time initialization of resources. }
procedure ApplicationInitialize;
begin
  ...

  { Create game states and set initial state }
  StatePlay := TStatePlay.Create(Application);
  StateMainMenu := TStateMainMenu.Create(Application);

  TUIState.Current := StateMenu;
end;

Each state loads the user interface appropriate for the given state. The advised way to do this is to set TUIState.DesignUrl in the overridden state constructor, like this:

constructor TStateMain.Create(AOwner: TComponent);
begin
  inherited;
  DesignUrl := 'castle-data:/gamestatemain.castle-user-interface';
end;

If you use the Code → New Unit → Unit With State…​ editor menu item, the above code is also automatically created for you.

4. Reacting to state start, stop, pause, remove

You can override a number of state methods to react to the state becoming active (when it is started) and resumed (when it is started and it is the top of state stack).

  • TUIState.Start is executed when the state starts. This is your typical place to initialize things for this state.

    To create a component that lives only until the state stops, you can assign a special owner TUIState.FreeAtStop to this component. This is essentially equivalent to just using owner nil and manually freeing the component in TUIState.Stop.

  • TUIState.Stop is executed when the state stops.

  • TUIState.Resume is executed when the state is started, and moreover it becomes the top state on the stack.

  • TUIState.Pause is executed when the state is started, but it is no longer the top state on the stack.

    Note that the state is not automatically paused for the user in any way, i.e. a paused state can still animate anything, process inputs and generally act like a normal state. It is your responsibility to pause any animations you want in the TUIState.Pause method, if you want it.

    The paused state will also continue to receive user input (mouse and key) that was not processed by the states higher on the stack. The higher state should return true from their input methods, like Press, to mark the input as handled. You can also set TUIState.InterceptInput to true on a higher state to make it pretend that it handles all inputs, thus the inputs will not reach states lower on the stack.

5. Examples

Explore the "3D FPS game" and "2D game" templates, by creating 2 new projects from these templates. Each of these templates creates 2 states, "MainMenu" and "Play". They follow the same pattern:

  1. Class TStateMainMenu, unit code/statemainmenu.pas, instance StateMainMenu, design data/statemainmenu.castle-user-interface.

  2. Class TStatePlay, unit code/stateplay.pas, instance StatePlay, design data/stateplay.castle-user-interface.

Many examples in the engine show even more complicates states setup:


To improve this documentation just edit the source of this page in AsciiDoctor (simple wiki-like syntax) and create a pull request to Castle Game Engine WWW (cge-www) repository.