Game Engine (TBaseGame)

Units: BaseGame Screen

TBaseGame is the framework you build your game on. It owns the game loop, so you do not have to write one.

What you get for free

Without the framework you would hand-roll VGA setup, a delta-time loop, and interrupt teardown — and forgetting a single Done call hangs the machine. TBaseGame does all of it:

  • Subsystem setup — VGA, keyboard, mouse, Sound Blaster, the RTC timer and the framebuffers, all brought up in the right order

  • The main loop — delta timing, screen updates, VSync, keyboard state, every frame

  • Screen management — named screens with a safe, deferred switch

  • Resources — a resource manager fed by an XML file, so images, fonts, sprites and music load by name

  • Guaranteed cleanup — subsystems shut down in reverse order, and an exit handler runs even on Ctrl+C or a runtime error

You write two things: a game object that extends TBaseGame, and one or more screens that extend TScreen.

A complete program

This is the real structure the shipped XiClone game uses:

program MyGame;

uses
  MyGameUnit, GameScr;  { MyGameUnit provides Game and GameConfig }

var
  GameScreen: PGameScreen;

begin
  { Configuration }
  GameConfig.Init('CONFIG.INI');

  { Bring up the engine }
  Game.Init(@GameConfig, 'DATA\RES.XML');
  Game.Start;

  { Create and register screens }
  New(GameScreen, Init);
  Game.AddScreen('game', GameScreen);

  { Run }
  Game.SetNextScreen('game');
  Game.Run;

  { Cleanup }
  Game.Done;
  GameConfig.Done;
end.

That is the whole entry point. No InitVGA, no timer arithmetic, no interrupt unhooking — Start, Run and Done cover it.

Order matters: Init then Start, then register screens, then Run. Registering after Start means your screens can already reach resources your game object loaded.

Extending TBaseGame

Put your game object in its own unit. This is where game-wide resources and state live, so screens can reach them through the global Game:

unit MyGameUnit;

interface

uses
  VGA, VGAFont, BaseGame, Screen, Config;

type
  TMyGame = object(TBaseGame)
    { Game-wide resources }
    TitleImage: PImage;
    PlayerSprite: PImage;
    TitleFont: PFont;
    Palette: PPalette;

    { Game-wide state }
    HighScore: LongInt;

    constructor Init(AConfig: PConfig; const ResXmlPath: String);
    destructor Done; virtual;
    procedure Start; virtual;
  end;

var
  Game: TMyGame;        { Global game instance }
  GameConfig: TConfig;  { Global config instance }

implementation

constructor TMyGame.Init(AConfig: PConfig; const ResXmlPath: String);
begin
  inherited Init(AConfig, ResXmlPath);

  { Plain state only - resources are not available yet }
  HighScore := 0;
  TitleImage := nil;
  PlayerSprite := nil;
  TitleFont := nil;
  Palette := nil;
end;

procedure TMyGame.Start;
begin
  inherited Start;  { Always first - brings up the subsystems }

  { Now the resource manager is live, so grab what the game needs }
  TitleImage := ResMan.GetImage('title');
  PlayerSprite := ResMan.GetImage('player');
  TitleFont := ResMan.GetFont('large');
  Palette := ResMan.GetPalette('default');
end;

destructor TMyGame.Done;
begin
  { ResMan frees everything it loaded - only free what you created yourself }
  inherited Done;
end;

end.

The rule: Init sets plain fields, Start loads resources. The resource manager is not ready until inherited Start has run, so a ResMan.GetImage in Init returns nothing useful.

BASEGAME.PAS deliberately declares no global Game variable — your unit provides it.

API

Constructor

constructor Init(AConfig: PConfig; ResXmlPath: String);
  • AConfig — pointer to an initialized TConfig. The caller owns it: you initialize it before Game.Init and free it after Game.Done.

  • ResXmlPath — path to the resources XML file for the resource manager

Lifecycle

procedure Start; virtual;

Loads config and brings up the resource manager, timer, input, sound and framebuffers. Override this to load your game’s resources, calling inherited Start first.

VGA is deliberately not initialized here — it is initialized in Run, so screens can be created and registered before the display mode changes.

procedure Run;

Initializes VGA, calls PostInit on every registered screen, then runs the main loop until something calls Stop.

destructor Done; virtual;

Shuts every subsystem down in reverse order and frees all registered screens.

procedure Stop;

Ends the loop by clearing Running. The current frame finishes, then Run returns.

procedure ResetTiming;

Sets LastTime to now. Call it after a pause or any long blocking operation, otherwise the next frame sees a huge delta time.

Screens

procedure AddScreen(Name: String; AScreen: PScreen); virtual;
procedure SetNextScreen(Name: String); virtual;
function GetScreen(Name: String): PScreen;

AddScreen registers a screen under a name. SetNextScreen queues a switch — it takes effect at the start of the next Update, never mid-frame. The old screen’s Hide and the new screen’s Show are called for you. See TScreen.

The game owns every registered screen and frees them in Done.

Music

procedure PlayMusic(Name: String); virtual;
procedure PauseMusic; virtual;
procedure StopMusic; virtual;

Play a music resource by name. All three exit immediately when Config.SoundCard = SoundCard_None, so they are safe to call unconditionally.

Fields you will use

Field

Purpose

ResMan

Resource manager — images, fonts, sprites, music

Config

Pointer to the config object

DeltaTime

Seconds since the previous frame

BackBuffer

Working render buffer — draw here

BackgroundBuffer

Static background, drawn once and copied from

ScreenBuffer

The VGA display buffer (a pointer — never free it)

Screen

The currently active screen

Running

Loop flag — clear it with Stop

The three-buffer split is what makes partial redraws cheap: keep the untouched background in BackgroundBuffer, restore from it into BackBuffer, draw the moving parts, and push only what changed. See dirty rectangles.

Notes

  • Alt+Q stops the game by default.

  • PostInit runs for all registered screens once, after VGA is up — that is the place to set the palette.

  • Cleanup is installed automatically, so Ctrl+C and runtime errors still restore text mode and unhook interrupts.

See Also

  • TScreen — the screen/state object where your game code lives

  • Config — settings file, sound card and input configuration

  • Resource Manager — the XML-driven asset loader

  • Engine Internals — exact init order, loop body and teardown sequence