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.PAS —
TBaseGame, the game objectSCREEN.PAS —
TScreen, 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;
Install
CleanupOnExitviaExitProc(handles Ctrl+C and Break gracefully)Load config:
Config^.LoadInitialize the resource manager:
ResMan.Init(True)(lazy loading), thenResMan.LoadFromXML(ResFilePath)— a failure here prints the error andHalt(1)sInitialize the RTC timer:
InitRTC(1024)— 1024 Hz, millisecond precisionInitialize the keyboard:
InitKeyboardInitialize Sound Blaster:
ResetDSP(Config^.SBPort, Config^.SBIRQ, Config^.SBDMA, 0)whenConfig^.SoundCard = SoundCard_SoundBlaster(2)Initialize the mouse:
InitMousewhenConfig^.UseMouse = 1Create framebuffers:
BackgroundBuffer := CreateFrameBufferBackBuffer := CreateFrameBufferScreenBuffer := 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:
SetScreen— apply a queued screen switch whenNextScreen <> nilUpdateMousewhen the mouse is initializedHandle the exit shortcut:
Alt+QclearsRunningScreen^.Update(DeltaTime)when a screen is activeWaitForVSyncClearKeyPressed
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:
Exit when
NextScreen = nilScreen^.Hidewhen a screen is activeScreen := NextScreenScreen^.Showon the new screenNextScreen := nil
What Done does
Reverse order of Start:
Free framebuffers:
BackBuffer, thenBackgroundBufferFree screens: iterate the screen map,
Dispose(ScreenPtr, Done)Free the screen map:
MapFree(ScreenMap)DoneMousewhen initializedUninstallHandlerwhen sound was initializedDoneKeyboardDoneRTCResMan.DoneDoneVGAwhen VGA was initializedClear the
CurrentGamepointer
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 roles —
BackgroundBufferholds static content,BackBufferis the working buffer,ScreenBufferis 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