Managing Views

1. Introduction

View is a class descending from TCastleView. 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 view. 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 view.

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

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

2. Changing view

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

  1. You can set Container.View := ViewXxx to make the new view the one and only currently active view.

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

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

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

    Using a view stack makes sense when you want to display one view on top of another. For example, you may want to push options view 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 ViewPlay could display ViewOptions on top, when user presses Escape:

    function TViewPlay.Press(const Event: TInputPressRelease): Boolean;
    begin
      Result := inherited;
      if Result then Exit; // allow the ancestor to handle keys
    
      if Event.IsKey(keyEscape) and
         (Container.FrontView = ViewPlay) then
      begin
        Container.PushView(ViewOptions);
        Exit(true);
      end;
    end;

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

    function TViewOptions.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 Self is optional here, just allows to make additional check
        Container.PopView(Self);
        Exit(true);
      end;
    end;

3. Creating new views

While in theory you can create instances of the TCastleView 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 → View…​" editor menu item, it will automatically edit your Application.OnInitialize handler in (by default) gameinitialize.pas unit to create the new view. So it will look like this:

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

  { Create game views and set initial view }
  ViewPlay := TViewPlay.Create(Application);
  ViewMainMenu := TViewMainMenu.Create(Application);

  Window.Container.View := ViewMenu;
end;

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

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

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

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

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

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

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

  • TCastleView.Stop is executed when the view stops.

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

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

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

    The paused view will also continue to receive user input (mouse and key) that was not processed by the views higher on the stack. The higher view should return true from their input methods, like Press, to mark the input as handled. You can also set TCastleView.InterceptInput to true on a higher view to make it pretend that it handles all inputs, thus the inputs will not reach views 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 views, "MainMenu" and "Play". They follow the same pattern:

  1. Class TViewMainMenu, unit code/gameviewmainmenu.pas, instance ViewMainMenu, design data/gameviewmainmenu.castle-user-interface.

  2. Class TViewPlay, unit code/gameviewplay.pas, instance ViewPlay, design data/gameviewplay.castle-user-interface.

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


To improve this documentation just edit this page and create a pull request to cge-www repository.