Cross-platform (desktop and mobile) games

Various Android applications developed using Castle Game Engine

Mobile platforms (Android, iOS) are different from desktop platforms (Windows, Linux, Mac OS X, FreeBSD...) in many ways. But our engine hides a lot of these differences from you by exposing a nice cross-platform API.

1. Initializing a cross-platform game

To create a game that works on all platforms, write a simple platform-independent main game unit. This unit is usually called game.pas in our example projects, although you can of course use any name you like (like gameinitialize.pas or mygameinitialization.pas).

See the simplest initial project code in engine examples: castle_game_engine/examples/portable_game_skeleton/ . In particular see the game.pas unit inside, showing a cross-platform initialization. You can use it as a start of your projects.

This is a short implementation of a cross-platform "Hello world!" application:

{ Game initialization and logic. }
unit Game;
 
interface
 
implementation
 
uses CastleWindow, CastleControls, CastleLog, CastleUIControls,
  CastleApplicationProperties;
 
var
  Window: TCastleWindow;
  Status: TCastleLabel;
 
{ One-time initialization of resources. }
procedure ApplicationInitialize;
begin
  { For a scalable UI (adjusts to any window size in a smart way), use UIScaling }
  Window.Container.UIReferenceWidth := 1024;
  Window.Container.UIReferenceHeight := 768;
  Window.Container.UIScaling := usEncloseReferenceSize;
 
  Status := TCastleLabel.Create(Application);
  Status.Anchor(vpMiddle);
  Status.Anchor(hpMiddle);
  Status.Caption := 'Hello world!';
  Window.Controls.InsertFront(Status);
end;
 
initialization
  { Set ApplicationName early, as our log uses it.
    Optionally you could also set ApplicationProperties.Version here. }
  ApplicationProperties.ApplicationName := 'my_fantastic_game';
 
  { Start logging. Do this as early as possible,
    to log information and eventual warnings during initialization. }
  InitializeLog;
 
  { Initialize Application.OnInitialize. }
  Application.OnInitialize := @ApplicationInitialize;
 
  { Create and assign Application.MainWindow. }
  Window := TCastleWindow.Create(Application);
  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 Game unit should only assign a callback to Application.OnInitialize, and create and assign Application.MainWindow. Actual game 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 game.pas unit can be included by the main program or library (the .lpr file for Lazarus, .dpr file for Delphi). The build tool will automatically generate a main program or library code using this unit, you only need to indicate it by writing game_units="Game" in the CastleEngineManifest.xml.

Usually, all other game units are (directly or indirectly) used by this initialization unit. Although you can also extend the game_units attribute to include more units.

Create a CastleEngineManifest.xml file to compile your project using the build tool. It can be as simple as this:

<?xml version="1.0" encoding="utf-8"?>
<project name="my-cool-game" game_units="Game">
</project>

Compile and run it on your desktop using this on the command-line:

castle-engine compile
castle-engine run

If you have installed Android SDK, NDK and FPC cross-compiler for Android then you can also build and run for Android:

castle-engine package --os=android --cpu=arm
castle-engine install --os=android --cpu=arm
castle-engine run --os=android --cpu=arm

If you have installed FPC cross-compiler for iOS then you can also build for iOS:

castle-engine package --target=iOS
# And open in XCode the project inside
# castle-engine-output/ios/xcode_project/
# to compile and run on device or simulator.

2. Optionally create a standalone program file

This is not necessary, but optionally, to be able to run and debug the project from Lazarus, you can create a desktop program file like my_fantastic_game_standalone.lpr to run your game from Lazarus.

It may be as simple as this:

{$mode objfpc}{$H+}
{$apptype GUI}
program my_fantastic_game_standalone;
uses CastleWindow, Game;
begin
  Application.MainWindow.OpenAndRun;
end.

You can even generate a simple program skeleton (lpr and lpi files) using

castle-engine generate-program

You can customize the desktop xxx_standalone.lpr file to do some desktop-specific things. For example initialize window size or fullscreen or read command-line parameters. See examples how to do it: darkest_before_dawn program file (simple) or hotel_nuclear (more complicated).

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.

Note that you can edit and run the desktop version using Lazarus, to benefit from Lazarus editor, code tools, integrated debugger... Using our build tool does not prevent using Lazarus at all!

  • If you did not create the lpi file using castle-engine generate-program, you can create it manually: Simply create in Lazarus a new project using the New -> Project -> Simple Program option. Or (if you already have the xxx.lpr file) create the project using Project -> New Project From File....
  • Add to the project requirements packages castle_base and castle_window (from Project -> Project Inspector, you want to Add a New Requirement).
  • Save the project as my_fantastic_game_standalone.lpi.
  • ...and develop and run as usual.
  • Edit the main my_fantastic_game_standalone.lpr file using the Project -> View Project Source option in Lazarus.

3. Compiling and debugging on mobile platforms

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

Compiling and packaging cross-platform games is greatly simplified if you use our build tool. For Android and iOS, our build tool nicely hides from you a lot of complexity with creating a platform-specific application.

  • For Android, you get a ready working xxx.apk file.
  • For iOS, you get a ready project that can be installed using XCode.

4. 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 Application.TouchDevice value.

For example, to make 3D navigation working using touch controls on mobile or "mouse look" on desktops, you can

  1. Create an instance of TCastleWindowTouch (instead of the simpler TCastleWindow).

  2. Create Player instance (see the manual chapter about the Player)

  3. At initialization (e.g. inside Application.OnInitialize callback) do this:

    Window.AutomaticTouchInterface := Application.TouchDevice;
    Player.Camera.MouseLook := not Application.TouchDevice;

5. Things to avoid in cross-platform games

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

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

    On mobile platforms, you will usually want to hide the menu item to "Quit Game" completely. 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.

  • Do not create more than one TCastleWindowCustom 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, but for that you will have to just write your own program code. See e.g. castle_game_engine/examples/window/multi_window.lpr example. There's no way to do it portably, for Android, iOS, web browser plugin...