14. On-screen menu

Our TCastleWindow and TCastleControl have a list of 2D controls visible of the screen. By default, the only thing present there is a scene manager (since scene manager acts as a 2D viewport through which you see the 3D world). This way the scene manager (it's viewport) is visible on the window, which in turn means that the 3D world is visible too.

You can add your own 2D controls using the Window.Controls.Add method. There are many predefined GUI controls available in our engine, look for TUIControl descendants, for example in CastleControls unit. You can also derive your own controls with ease.

For a simple on-screen menu, where all the menu items are displayed vertically on the screen, use the TCastleOnScreenMenu control. For Lazarus: drop TCastleOnScreenMenu on the form. For TCastleWindow: just create TCastleOnScreenMenu instance. Fill it's TCastleOnScreenMenu.Items property (each line is a menu entry), and assign a handler for the TCastleOnScreenMenu.OnClick event (to react when user chose menu item, by clicking or pressing enter key). Inside OnClick event, the TCastleOnScreenMenu.CurrentItem property tells you which item was clicked. You also have to add this to controls list.

uses ..., CastleOnScreenMenu;
 
var
  Window: TCastleWindow;
  OnScreenMenu1: TCastleOnScreenMenu;
 
procedure TEventHandler.OnScreenMenu1Click(Sender: TObject);
begin
  case OnScreenMenu1.CurrentItem of
    0: // ... load new game
    1: // ... quit
  end;
end;
 
... // use this at initialization:
  { Note: if you use Lazarus, you can create TCastleOnScreenMenu by dropping
    it on a form and you can initialize many properties by Object Inspector. }
  OnScreenMenu1 := TCastleOnScreenMenu.Create(Application);
  OnScreenMenu1.Items.Add('New game');
  OnScreenMenu1.Items.Add('Quit');
  OnScreenMenu1.OnClick := @EventHandler.OnScreenMenu1Click;
  OnScreenMenu1.Position := Vector2Integer(100, 100);
  // Maybe also adjust OnScreenMenu1.PositionRelativeMenu*
 
  Window.Controls.Insert(0, OnScreenMenu1);

There is an example of this in examples/lazarus/model_3d_with_2d_controls/ example in engine sources.

On-screen menu over a 3D world

You can use various UI controls on top of each other. So you can have TCastleOnScreenMenu displayed on top of a 3D world (by default, scene manager already acts as a viewport). You can control the existence of any UI control either by removing/adding it from the Controls list, or by changing it's Exists property.

If the game is already started, in single player games, you usually want to pause the game when the on-screen menu is displayed. You can do this easily by SceneManager.Paused property. Like this:

...
{ global / static variables }
var
  GameMenu: TCastleOnScreeMenu;
  GameMenuClosed: boolean;
 
... // use this at initialization:
  { somewhere at the beginning prepare the menu }
  GameMenu := TCastleOnScreeMenu.Create(...);
  { see example above for how to initialize and implement TCastleOnScreeMenu.
    Make sure that one of the menu items, like "Back",
    sets GameMenuClosed := true when clicked. }
 
... // use this when you want to actually show menu:
  SceneManager.Paused := true;
  GameMenuClosed := false;
  Window.Controls.Add(GameMenu);
  repeat
    Application.ProcessMessage(true, true);
  until GameMenuClosed;
  Window.Controls.Remove(GameMenu);
  SceneManager.Paused := false;

As the scene manager handles a lot of stuff automatically, processing events and calling Update methods of all 3D objects periodically, pausing it effectively pauses your whole 3D world, while still allowing it to be displayed as a background under the on-screen menu. Alternatively you could also hide the 3D world entirely, by changing SceneManager.Exists property to false — the SceneManager with Exists=false is not only paused, it's also invisible.

Background under on-screen menu

In addition to previous ideas, you may want to change the menu TCastleOnScreenMenu.FullSize to true. Otherwise, menu receives input only when mouse hovers over it. When FullSize = true, the menu obscures completely controls under it as far as key processing is concerned (although controls behind are still visible as a background).

  • So if you want your menu to be displayed and used orthogonally to the "live" 3D world underneath, leave FullSize = false.

  • For initial game menu with items like "New Game", you probably want to disable the camera input for this scene. This allows you to display interactive 3D scene in the background, but block user from interacting with it (after all, the user should only interact with your menu on the initial screen). To do this simply set FullSize = true.

If you want to place a static 2D image under menu, you can use TCastleImageControl underneath, instead of a 3D scene.