Cross-platform (desktop, mobile, consoles...) projects

Various Android applications developed using Castle Game Engine

1. Introduction

Castle Game Engine supports many platforms:

The engine hides as much as possible differences between these platforms, exposing a nice cross-platform API.

2. Standard GameInitialize unit

New projects created using the CGE editor are automatically cross-platform. All the "New Project" templates (including "Empty", the simplest) follow the same approach.

The starting point of every cross-platform project is a unit that initializes Application.MainWindow. By default, this unit is called GameInitialize and it is present in your project in code/gameinitialize.pas. This unit looks like this:

{ Game initialization and logic. }
unit GameInitialize;
 
interface
 
implementation
 
uses SysUtils,
  CastleWindow, CastleLog, CastleUIState,
  GameStateMain;
 
var
  Window: TCastleWindow;
 
{ One-time initialization of resources. }
procedure ApplicationInitialize;
begin
  { Adjust container settings for a scalable UI (adjusts to any window size in a smart way). }
  Window.Container.LoadSettings('castle-data:/CastleSettings.xml');
 
  { Create TStateMain that will handle "main" state of the game.
    Larger games may use multiple states,
    e.g. TStateMainMenu ("main menu state"),
    TStatePlay ("playing the game state"),
    TStateCredits ("showing the credits state") etc. }
  StateMain := TStateMain.Create(Application);
  TUIState.Current := StateMain;
end;
 
initialization
  { Initialize Application.OnInitialize. }
  Application.OnInitialize := @ApplicationInitialize;
 
  { Create and assign Application.MainWindow. }
  Window := TCastleWindow.Create(Application);
  Window.ParseParameters; // allows to control window size / fullscreen on the command-line
  Application.MainWindow := Window;
 
  { You should not need to do *anything* more in the unit "initialization" section.
    Most of your game initialization should happen inside ApplicationInitialize.
    In particular, it is not allowed to read files before ApplicationInitialize
    is called (in case of non-desktop platforms, some necessary things
    may not be prepared yet). }
end.

The initialization section at the bottom of the GameInitialize unit should only assign a callback to Application.OnInitialize, and create and assign Application.MainWindow. Most of the actual initialization (loading images, resources, setting up player and such) should happen in the callback you assigned to Application.OnInitialize. At that point you know that your program is ready to load and prepare resources.

This GameInitialize unit can be included by the main program / library file. But usually you should not maintain yourself this main program / library file. The build tool will automatically generate the main program / library using the GameInitialize unit, as necessary for compilation on a particular platform.

3. Optional standalone program file

Optionally, to be able to run and debug the project from Lazarus or Delphi, we need a program file like xxx_standalone.dpr.

You should not create or maintain such file manually. Instead, it should be automatically generated for new projects. You can also always regenerate it using editor menu "Code -> Regenerate Project (overwrites LPI, DPR, DPROJ, CastleAutoGenerated)" or using command-line:

castle-engine generate-program

You should not customize the generated xxx_standalone.dpr file. While such customizations would work in the short term, they would prevent from regenerating this file. It's better to leave it auto-generated, and place your necessary initialization (even things like command-like parsing) in your units, like gameinitialize.pas.

To make our build tool use your customized program file (instead of the auto-generated one), be sure to set standalone_source in the CastleEngineManifest.xml. It is already set OK in new projects created using our editor.

4. Compiling and debugging on mobile platforms

Developing for mobile platforms requires installing some special tools. Everything is explained on these platform-specific pages:

5. Differences in input handling between mobile (touch) and desktop (mouse) platforms

To create portable games you have to think about different types of inputs available on mobile platforms vs desktop. The engine gives you various helpers, and abstracts various things (for example, mouse clicks and touches can be handled using the same API, you just don't see multi-touches on desktop). But it's not possible to 100% hide the differences, because some concepts just cannot work — e.g. mouse look cannot work on touch interfaces (since we don't get motion events when you don't press...), keyboard is uncomfortable on touch devices, multi-touch doesn't work on desktops with a single mouse and so on.

To account for this, you can adjust your input handling depending on the ApplicationProperties.TouchDevice value. It is automatically initialized to true on touch devices without keyboard / mouse (like mobile), and false elsewhere (like on typical desktops).

For navigation in 3D on mobile, we have a special UI control TCastleTouchNavigation. This allows to easily navigate (examine / walk / fly) in the viewport by dragging on special controls in the corners.

6. Things to avoid in cross-platform games

  • Do not call Window.Open or Window.Close or Application.Run inside the cross-platform unit like gameinitialize.pas.

    These methods should never be explicitly called on non-desktop platforms. Even on the desktop platforms, they should only be called from the main program file (xxx_standalone.dpr), which may be auto-generated by the build tool.

  • Do not call Application.Terminate on platforms where users don't expect it. Use ApplicationProperties.ShowUserInterfaceToQuit to show or hide the appropriate user interface, like a "Quit Game" button. Mobile applications generally don't have a buttton to quit — instead, mobile users just switch to a different application (or desktop) using the standard buttons.

    Also, the Application.Terminate may not be implemented on some platforms where ShowUserInterfaceToQuit is false.

  • Do not create more than one TCastleWindow instance. If you want your game to be truly portable to any device — you have to limit yourself to using only one window. For normal games that's probably natural anyway.

    Note that the engine still supports, and will always support, multiple-window programs. See e.g.castle_game_engine/examples/window/multi_window.dpr example. However, it only works on normal desktop systems. It is not possible to do portably (to seamlessly work on mobile and console systems) since other platforms don't have a concept of "window" that works like on desktops.