Engine Internals

Units: BaseGame Screen

Exact initialization order, main loop body and teardown sequence for TBaseGame. You do not need any of this to write a game — read it when you are debugging startup order, overriding Update, or adding a subsystem.

Architecture

BASEGAME.PAS and SCREEN.PAS are a reusable framework with no game-specific dependencies:

  • BASEGAME.PASTBaseGame, the game object

  • SCREEN.PASTScreen, the screen/state object

Games extend TBaseGame with their own resources, state and initialization. The shipped XiClone game does this as TXiCloneGame in XIGAME.PAS.

What Start does

procedure Start; virtual;
  1. Install CleanupOnExit via ExitProc (handles Ctrl+C and Break gracefully)

  2. Load config: Config^.Load

  3. Initialize the resource manager: ResMan.Init(True) (lazy loading), then ResMan.LoadFromXML(ResFilePath)a failure here prints the error and Halt(1)s

  4. Initialize the RTC timer: InitRTC(1024) — 1024 Hz, millisecond precision

  5. Initialize the keyboard: InitKeyboard

  6. Initialize Sound Blaster: ResetDSP(Config^.SBPort, Config^.SBIRQ, Config^.SBDMA, 0) when Config^.SoundCard = SoundCard_SoundBlaster (2)

  7. Initialize the mouse: InitMouse when Config^.UseMouse = 1

  8. Create framebuffers:

    • BackgroundBuffer := CreateFrameBuffer

    • BackBuffer := CreateFrameBuffer

    • ScreenBuffer := GetScreenBuffer

InitVGA is not called here. It is deferred to Run so screens can be created and registered before the display mode changes.

The main loop

procedure TBaseGame.Run;
begin
  InitVGA;  { VGA initialized here, not in Start }
  VGAInitialized := True;
  Running := True;

  { PostInit all screens - called once for ALL registered screens }
  for I := 0 to StrMap.MAX_ENTRIES - 1 do
  begin
    Entry := ScreenMap.Entries[I];
    if (Entry <> nil) and Entry^.Used then
    begin
      ScreenPtr := PScreen(Entry^.Value);
      if ScreenPtr <> nil then
        ScreenPtr^.PostInit;
    end;
  end;

  LastTime := GetTimeSeconds;

  while Running do
  begin
    { Calculate delta time }
    CurrentTime := GetTimeSeconds;
    DeltaTime := CurrentTime - LastTime;
    LastTime := CurrentTime;

    Update(DeltaTime);
  end;
end;

The per-frame Update

procedure Update(DeltaTime: Real); virtual;

Virtual — override it to hook the frame, calling inherited Update to keep the default behaviour:

  1. SetScreen — apply a queued screen switch when NextScreen <> nil

  2. UpdateMouse when the mouse is initialized

  3. Handle the exit shortcut: Alt+Q clears Running

  4. Screen^.Update(DeltaTime) when a screen is active

  5. WaitForVSync

  6. ClearKeyPressed

Screens render themselves — step 4 is where all drawing happens.

Screen switching

procedure SetScreen; virtual;

Called automatically by Update; call it directly only if you need an immediate switch:

  1. Exit when NextScreen = nil

  2. Screen^.Hide when a screen is active

  3. Screen := NextScreen

  4. Screen^.Show on the new screen

  5. NextScreen := nil

What Done does

Reverse order of Start:

  1. Free framebuffers: BackBuffer, then BackgroundBuffer

  2. Free screens: iterate the screen map, Dispose(ScreenPtr, Done)

  3. Free the screen map: MapFree(ScreenMap)

  4. DoneMouse when initialized

  5. UninstallHandler when sound was initialized

  6. DoneKeyboard

  7. DoneRTC

  8. ResMan.Done

  9. DoneVGA when VGA was initialized

  10. Clear the CurrentGame pointer

Note that ResMan.Done runs before DoneVGA — resources are released while the display mode is still set.

CleanupOnExit calls Done through the module-level CurrentGame pointer, so an abnormal exit still restores text mode and unhooks every interrupt.

TBaseGame fields

type
  PBaseGame = ^TBaseGame;
  TBaseGame = object
    { Configuration & Resources }
    Config: PConfig;                 { Game configuration pointer (caller owns) }
    ResFilePath: String;             { Path to resources XML }
    ResMan: TResourceManager;        { Resource manager (see RESMAN.PAS) }

    { Timing }
    CurrentTime: Real;               { Current time in seconds (from GetTimeSeconds) }
    LastTime: Real;                  { Previous frame time in seconds }
    DeltaTime: Real;                 { Time elapsed since last frame (seconds) }

    { State }
    Running: Boolean;                { Main loop control flag }

    { Screen Management }
    Screen: PScreen;                 { Current active screen }
    NextScreen: PScreen;             { Next screen to switch to (queued) }
    ScreenMap: TStringMap;           { Name -> PScreen mapping }

    { Framebuffers }
    BackgroundBuffer: PFrameBuffer;  { Static background (cleared once) }
    BackBuffer: PFrameBuffer;        { Working render buffer }
    ScreenBuffer: PFrameBuffer;      { VGA display buffer (from GetScreenBuffer) }

    { Internal state }
    VGAInitialized: Boolean;         { VGA mode 13h initialized }
    MouseInitialized: Boolean;       { Mouse driver initialized }
    SoundInitialized: Boolean;       { Sound Blaster initialized }
  end;

Dependencies

  • CONFIG — TConfig, LoadConfig, SoundCard constants

  • RESMAN — TResourceManager

  • RTCTIMER — InitRTC, DoneRTC, GetTimeSeconds

  • KEYBOARD — InitKeyboard, DoneKeyboard, IsKeyPressed, ClearKeyPressed

  • SBDSP — ResetDSP, UninstallHandler

  • MOUSE — InitMouse, DoneMouse

  • VGA — CreateFrameBuffer, FreeFrameBuffer, GetScreenBuffer, ClearFrameBuffer, CopyFrameBuffer, RenderFrameBuffer

  • STRMAP — TStringMap (screen name to PScreen mapping)

Notes

  • DeltaTime convention — Real, in seconds, from GetTimeSeconds.

  • Sound card checks — the music methods exit early when Config.SoundCard = SoundCard_None.

  • Framebuffer rolesBackgroundBuffer holds static content, BackBuffer is the working buffer, ScreenBuffer is the VGA hardware buffer and must never be freed.

  • Alt+Q stops the game. TODO: make this optional.

Future Enhancements

  • Screen transitions — fade in/out, wipes

  • Screen stack — push/pop for pause menus and dialogs

  • Fixed timestep — decouple update rate from render rate for deterministic physics

See Also

  • TBaseGame — how to actually use the framework

  • TScreen — the screen lifecycle