Game Engine Core Architecture
Units: DGECore DGEScr
Central game loop framework with screen management, resource loading, and subsystem initialization.
Architecture Overview
DGECORE and DGESCR provide a reusable framework with no game-specific dependencies. Games extend TGame to create their own game object with game-specific resources and state.
Framework (DGECORE.PAS, DGESCR.PAS)
DGECORE.PAS: Defines
TGamebase object (reusable framework)DGESCR.PAS: Defines
TScreenbase object (screen/state pattern)
Game-Specific Implementation
Games extend TGame to add:
Game-specific resources (sprites, fonts, etc.)
Game-specific state (UI systems, dialog state, etc.)
Game-specific initialization
Example: XiClone extends TGame as TXiCloneGame in XIGAME.PAS
TGame Object
Unit: DGECore
Main game object that manages the entire application lifecycle.
Constructor
constructor Init(AConfig: PConfig; ResXmlPath: String);
AConfig: Pointer to initialized TConfig object (caller owns, game holds reference)ResXmlPath: Path to resources XML file (for TResourceManager)
Note: Sets internal CurrentGame pointer for exit handler. The game does NOT own the config object - caller is responsible for initialization and cleanup.
Methods
procedure Start; virtual;
Initialize all subsystems and prepare for game loop:
Set up
CleanupOnExitprocedure (handles Ctrl+C/Break gracefully)Load config:
Config^.Load(loads from Config^.Path)Initialize resource manager:
ResMan.Init(True)(lazy loading), thenResMan.LoadFromXML(ResXmlPath)Initialize RTCTimer:
InitRTC(1024)(1024 Hz for millisecond precision)Initialize keyboard:
InitKeyboardInitialize Sound Blaster:
ResetDSP(Config^.SBPort, Config^.SBIRQ, Config^.SBDMA, 0)ifConfig^.SoundCard = SoundCard_SoundBlaster(2)Initialize mouse:
InitMouseifConfig^.UseMouse = 1Create framebuffers:
BackgroundBuffer := CreateFrameBuffer(cleared once, used for static backgrounds)BackBuffer := CreateFrameBuffer(working buffer)ScreenBuffer := GetScreenBuffer(VGA display buffer)
Virtual: Override in derived game objects to add game-specific initialization (e.g., load fonts, sprites).
Note: VGA initialization (InitVGA) is deferred until Run is called. This allows screens to be created and registered before VGA mode is set.
procedure Run;
Main game loop - initializes VGA, calls PostInit on all screens, then runs the main loop:
procedure TGame.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 (calls SetScreen, UpdateMouse, Screen^.Update, WaitForVSync, ClearKeyPressed) }
Update(DeltaTime);
end;
end;
Note:
VGA is initialized at the start of
Run, not inStart. This allows screens to be created and registered before VGA mode is set.All screens’
PostInitmethods are called once before the main loop starts. Use this to set palette, show loading screen.Screens are responsible for their own rendering. The default
UpdatecallsScreen^.Update(DeltaTime), which should handle drawing toGame.BackBufferand callingRenderFrameBufferorFlushDirtyRects.
destructor Done; virtual;
Shutdown all subsystems (reverse order of Start):
Free framebuffers:
FreeFrameBuffer(BackgroundBuffer),FreeFrameBuffer(BackBuffer)Free screens: iterate screen map and call
Dispose(Screen, Done)Free screen map:
MapFree(ScreenMap)Uninitialize mouse:
DoneMouse(if initialized)Uninitialize Sound Blaster:
UninstallHandler(if initialized)Uninitialize keyboard:
DoneKeyboardUninitialize RTC timer:
DoneRTCFree resource manager:
ResMan.DoneClose VGA:
DoneVGA(if initialized)Clear
CurrentGamepointer
Virtual: Override in derived game objects to cleanup game-specific resources (call inherited at end).
procedure CleanupOnExit;
ExitProc handler - ensures Done is called on abnormal termination (Ctrl+C, Runtime Error, etc.)
Uses module-level CurrentGame pointer to avoid dependency on global Game variable.
procedure PlayMusic(Name: String); virtual;
Load and play music track by resource name
Exitimmediately ifConfig.SoundCard = SoundCard_None(0)Uses ResMan to load music file
procedure PauseMusic; virtual;
Pause current music playback
ExitifConfig.SoundCard = SoundCard_None
procedure StopMusic; virtual;
Stop current music playback
ExitifConfig.SoundCard = SoundCard_None
procedure SetNextScreen(Name: String); virtual;
Queue a screen switch (deferred until next Update):
Look up screen in
ScreenMapby nameSet
NextScreenfield to the found screen
Note: Screen switch happens in Update, not immediately. This prevents issues with switching screens mid-frame.
procedure SetScreen; virtual;
Apply queued screen switch (called automatically in Update):
Exit if
NextScreen = nilIf current
Screen <> nil: callScreen^.HideSet
Screen := NextScreenIf new
Screen <> nil: callScreen^.ShowSet
NextScreen := nil
Note: This is called internally by Update. Don’t call directly unless you need immediate screen switching.
function GetScreen(Name: String): PScreen;
Retrieve a screen by name from ScreenMap:
Returns
PScreenif foundReturns
nilif not found
procedure AddScreen(Name: String; AScreen: PScreen); virtual;
Register a screen in the screen map: MapPut(ScreenMap, Name, AScreen)
procedure Stop;
Stop the game loop by setting Running := False. The current frame will complete, then Run will exit.
procedure ResetTiming;
Reset the timing system by setting LastTime := GetTimeSeconds. Useful when resuming from a pause or after a long blocking operation to prevent a large delta time spike.
procedure Update(DeltaTime: Real); virtual;
Virtual method - can be overridden in derived game objects.
Default implementation:
Call
SetScreento apply queued screen switch (ifNextScreen <> nil)Update mouse if initialized:
UpdateMouseHandle exit shortcut:
Alt+Qstops the game (setsRunning := False)If
Screen <> nil: callScreen^.Update(DeltaTime)Wait for VSync:
WaitForVSyncClear keyboard state:
ClearKeyPressed
Properties
type
PGame = ^TGame;
TGame = 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;
TScreen Object
Unit: DGEScr
Abstract screen/state object for menu screens, gameplay, etc.
Constructor
constructor Init;
Base constructor - override in derived screens. No parameters needed since screens access the global Game instance.
Methods
destructor Done; virtual;
Cleanup screen resources. Virtual - override to free screen-specific resources.
procedure PostInit; virtual;
Called once after VGA initialization, before the main loop starts. Virtual - override to:
Set palette (for example)
Note: This is called for ALL screens during Game.Run, before the main loop starts. At this point VGA is initialized and framebuffers are available.
procedure Update(DeltaTime: Real); virtual;
Per-frame update logic. Virtual - override for game-specific logic.
procedure Show; virtual;
Called when screen becomes active. Virtual - override to:
Initialize screen-specific resources
Load screen assets
Reset screen state
procedure Hide; virtual;
Called when screen becomes inactive. Virtual - override to:
Save screen state if needed
Pause screen-specific timers
Optionally free screen assets
Properties
type
PScreen = ^TScreen;
TScreen = object
{ No fields in base - add fields in derived screens }
end;
Extending TGame for Your Game
Create a game-specific object that extends TGame:
unit MyGame;
interface
uses
VGA, VGAFont, DGECore, DGEScr, Config;
type
TMyGame = object(TGame)
{ Game-specific resources }
PlayerSprite: PImage;
TitleFont: PFont;
{ Game-specific state }
HighScore: LongInt;
{ Override initialization }
constructor Init(AConfig: PConfig; const ResXmlPath: String);
destructor Done; virtual;
procedure Start; virtual;
{ Game-specific methods }
procedure SaveHighScore;
end;
var
Game: TMyGame; { Global game instance }
implementation
constructor TMyGame.Init(AConfig: PConfig; const ResXmlPath: String);
begin
inherited Init(AConfig, ResXmlPath);
{ Initialize game-specific state }
HighScore := 0;
end;
destructor TMyGame.Done;
begin
{ Parent handles cleanup }
inherited Done;
end;
procedure TMyGame.Start;
begin
{ Call parent Start (initializes framework) }
inherited Start;
{ Load game-specific resources (after VGA init in Run) }
{ For resources that need VGA, load them here after inherited Start }
end;
procedure TMyGame.SaveHighScore;
begin
{ Game-specific logic }
end;
end.
Usage Example
program MyGame;
uses
MyGame, Config, Keyboard; { MyGame provides Game: TMyGame }
type
PMenuScreen = ^TMenuScreen;
TMenuScreen = object(TScreen)
procedure PostInit; virtual;
procedure Update(DeltaTime: Real); virtual;
procedure Show; virtual;
procedure Hide; virtual;
end;
PGameplayScreen = ^TGameplayScreen;
TGameplayScreen = object(TScreen)
procedure PostInit; virtual;
procedure Update(DeltaTime: Real); virtual;
procedure Show; virtual;
procedure Hide; virtual;
end;
var
GameConfig: TConfig;
MenuScreen: PMenuScreen;
GameplayScreen: PGameplayScreen;
{ MenuScreen implementation }
procedure TMenuScreen.PostInit;
begin
{ Load graphics resources that require VGA mode }
{ Called once before main loop, after InitVGA }
end;
procedure TMenuScreen.Update(DeltaTime: Real);
begin
{ Handle menu input - queue screen switch }
if IsKeyPressed(Key_Enter) then
Game.SetNextScreen('gameplay'); { Deferred switch }
end;
procedure TMenuScreen.Show;
begin
{ Screen activated - show cursor, play music, etc. }
end;
procedure TMenuScreen.Hide;
begin
{ Screen deactivated - hide cursor, pause music, etc. }
end;
{ GameplayScreen implementation }
procedure TGameplayScreen.PostInit;
begin
{ Load graphics resources that require VGA mode }
{ Called once before main loop, after InitVGA }
end;
procedure TGameplayScreen.Update(DeltaTime: Real);
begin
{ Game logic here }
if IsKeyPressed(Key_Escape) then
Game.SetNextScreen('menu'); { Return to menu }
end;
procedure TGameplayScreen.Show;
begin
{ Screen activated - load level, reset state, etc. }
end;
procedure TGameplayScreen.Hide;
begin
{ Screen deactivated - pause game, save state, etc. }
end;
begin
{ Initialize config }
GameConfig.Init('CONFIG.INI');
{ Initialize game (uses Game: TMyGame from MyGame) }
Game.Init(@GameConfig, 'DATA\RES.XML');
{ Create and register screens }
New(MenuScreen, Init);
Game.AddScreen('menu', MenuScreen);
New(GameplayScreen, Init);
Game.AddScreen('gameplay', GameplayScreen);
{ Start and run }
Game.Start; { Initialize all subsystems, loads config }
Game.SetNextScreen('menu'); { Queue initial screen }
Game.Run; { Main loop }
{ Cleanup }
Game.Done;
GameConfig.Done;
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 -> PScreen mapping)
Notes
No global in framework:
DGECORE.PASdoes not declare a globalGamevariable. Games provide their own by extendingTGame.Extend TGame: Create a game-specific object (e.g.,
TMyGame = object(TGame)) with game resources and state.Override Start: Load game-specific resources in your overridden
Startmethod (call inherited first).Virtual methods: All
TScreenmethods are virtual. OverridePostInit,Update,Show,Hideas needed.VGA initialization timing: VGA is initialized in
Run, notStart. This allows screens to be created and registered before VGA mode is set.PostInit lifecycle: Called once for ALL screens during
Run, before the main loop starts.Deferred screen switching: Use
SetNextScreen('name')to queue screen switches. The switch happens in the nextUpdatecall. This prevents issues with switching screens mid-frame.ExitProc:
CleanupOnExitis automatically installed byStartto ensure cleanup on abnormal exit (Ctrl+C, Runtime Error). Uses module-levelCurrentGamepointer.DeltaTime convention: Real (seconds), calculated as
CurrentTime - LastTimeviaGetTimeSeconds.Exit shortcut:
Alt+Qstops the game (Mortal Kombat style). TODO: Make this optional.Sound card checks: Music functions exit early if
Config.SoundCard = SoundCard_Noneto avoid unnecessary work.Screen lifecycle: Screens are created by the program, registered with
AddScreen, and switched withSetNextScreen. The game owns and frees all screens inDone.Framebuffer usage:
BackgroundBuffer: Static content (cleared once, never redrawn)BackBuffer: Working buffer for compositingScreenBuffer: VGA hardware buffer (pointer, don’t free)
Future Enhancements
Screen transitions: Fade in/out, wipes, etc.
Screen stack: Push/pop screens for pause menus, dialogs
Fixed timestep: Decouple update rate from render rate for deterministic physics