Architecture of Game Engines

Czech technical University in Prague
Faculty of Information Technology
Department of Software Engineering
© Adam Vesecký, MI-APH, 2019

Game Engine Parts

Application, Loop, System

Game Application

  1. Handle everything we need to set up the application
  2. Register each handler
  3. Initialize rendering window
  4. Start the game loop
  5. Quit the application

Game loop

  • Process inputs
  • Process game logic
  • Erase the screen and draw game objects
  • Repeat

Example: Atomic Game Engine Initialization

Example: Duke Nukem 3D (1996)

Example: Doom 3 (2004)

Game Engine Parts

  • Typical game engine consists of core systems and a set of modules and 3rd party libraries attached to them
  • Heavy engines such as Unity or Unreal have their main parts tightly coupled together
  • Light engines such as Godot or PhaserJS prefer aggregation over integration

Engine Architecture

Engine Architecture

Hardware

  • PC, Xbox, PS4, Switch,...

Drivers

  • NVidia, Sound Blaster Audigy, Gamepad driver,...

OS

  • Windows, Linux, MacOS, Android, iOS,...

3rd Party SDKs

  • DirectX, OpenGL, Vulkan, Boost, Havok, STL

Core Systems

  • Threading lib, File System, Network layer, Movie player, Math library, Parsers, Memory Allocator

Modules

  • Scripting system, Dynamic Object Model, Messaging system, World streaming, DSP, 3D Audio Model, Match-making management, Skeletal animation, Inverse Kinematics, LERP, Rigid Bodies, Shaders, Debug Drawing, Spatial Subdivision, LOD system, Particle system, HDR Lighting

Game

  • HUD, Terrain renderer, Vehicle system, Puzzle system, Dialogue system, State machines, Camera, Decision Making

List of game genres

Which Engine do I Need?

  • Various games require various engines
  • Still, their core components remain similar
    • every game engine needs a  game loop
    • almost every game has assets
    • every game needs a renderer
    • game objects are structured in a scene graph
    • we couldn't do much without a physics engine
    • complex audio processing requires an advanced audio engine
    • for 3D games we definitely need an editor

Game Engine Modules

Main modules

  • Game Loop - heartbeat of all games
  • Scene Manager - manages objects and structures them in a scene graph
  • Resource Manager - manages assets, controls a cache
  • Input Manager - handles inputs (keyboard, mouse, touch, joystick, gamepad,...)
  • Memory Manager - memory allocator and deallocator
  • Rigidbody Engine - event-based collision detection
  • Physics Engine - handles behavior of objects based on forces and impulses
  • Rendering Engine - renders the game, takes care of the rendering pipeline
  • Animation Engine - handles animations
  • Scripting Engine - bridge between the engine and interpreted languages (JS, Lua, C#,...)
  • Multimedia Engine - plays music, clips, sounds, video
  • AI Engine - abstract engine for AI (path finding, states, behavioral trees, ...)
  • Networking Engine - handles multipeer communication

Other modules

  • GUI framework, Level Editor, Camera, Event System, World Streaming, Security, LOD, Profiler,...

Game Loop

Game Loop

  • Simple, yet the most important part of the game engine
  • Each turn advances the state of the game
  • Sometimes it's coordinated with the platform's event loop
  • Optimal time step for rendering: 60FPS = 16.6 ms per frame
  • Audio is usually separated as it requires more frequent updates (~200 FPS)
  • Certain systems don't need to be updated so frequently (AI)
In general, a program spends 90% of its time in 10% of the code. The game loop will be firmly in those 10%

Simple Game Loop

Multi-threaded game loop

Cooperative game loop

  • Implemented via small, relatively independent jobs
  • Used in Ultime VIII (1994) for the first time

Update method

Fixed time step

  • each update advances game time by a certain amount of time
  • precise and stable
  • the game may slow down

Variable time step

  • each update advances game time based on how much real time passed since the last frame
  • natural
  • non-deterministic and unstable (physics)

Adaptive time step

  • switches between variable and fixed time step
  • based on thresholds or a more sophisticated approach
  • better dealing with breakpoints

Example: Atomic Game Engine Update 1/3

int Application::Run() {

        Setup();

        if (!engine_->Initialize(engineParameters_)) {

            return ErrorExit();

        }

 

        Start();

 

#if !defined(IOS&& !defined(__EMSCRIPTEN__)

        while (!engine_->IsExiting())

            engine_->RunFrame();

        Stop();

#else

#if defined(IOS)

        SDL_iPhoneSetAnimationCallback(GetWindow(), 1&RunFrame, engine_);

#elif defined(__EMSCRIPTEN__)

        emscripten_set_main_loop_arg(RunFrame, engine_, 01);

#endif

#endif

        return exitCode_;

}

Example: Atomic Game Engine Update 2/3

void Engine::RunFrame() {

    Time* time = GetSubsystem<Time>();

    Input* input = GetSubsystem<Input>();

    Audio* audio = GetSubsystem<Audio>();

 

    time->BeginFrame(timeStep_);

    // ... process input and audio

    Update();

 

    fpsTimeSinceUpdate_ += timeStep_;

    ++fpsFramesSinceUpdate_;

    if (fpsTimeSinceUpdate_ > ENGINE_FPS_UPDATE_INTERVAL) {

        fps_ = (int)(fpsFramesSinceUpdate_ / fpsTimeSinceUpdate_);

        fpsFramesSinceUpdate_ = 0;

        fpsTimeSinceUpdate_ = 0;

    }

    

    Render();

    ApplyFrameLimit();

    time->EndFrame();

}

Example: Atomic Game Engine Update 3/3

void Engine::Update() { 

    VariantMap& eventData = GetEventDataMap();

    eventData[P_TIMESTEP] = timeStep_;

    SendEvent(E_UPDATE, eventData);

 

    // Logic post-update event

    SendEvent(E_POSTUPDATE, eventData);

    // Rendering update event

    SendEvent(E_RENDERUPDATE, eventData);

    // Post-render update event

    SendEvent(E_POSTRENDERUPDATE, eventData);

}

 

void Engine::Render() {

    // If device is lost, BeginFrame will fail and we skip rendering

    Graphics* graphics = GetSubsystem<Graphics>();

    if (!graphics->BeginFrame()) return;

 

    GetSubsystem<Renderer>()->Render();

    GetSubsystem<UI>()->Render();

    graphics->EndFrame();

}

Example: Unity Game Loop

Update inconsistencies

Game objects are consistent before and after every update

  • yet they may be inconsistent during the update
  • major source of confusion and bugs
  • PixiJS updates all dependent transformations instantly

One-frame-off lag

  • the state of some objects lags one frame behind the states of the others
  • possible solutions: bucket update, script execution order (Unity)

Object 2 reads updated state of Object 1 but not updated state of Object 3

Scene Graph

Scene Graph

Scene Graph

  • essential structure of every interactive application
  • a way of ordering the data into a hierarchy
  • N-Tree or a regular graph
  • parent nodes affect child nodes (translation, rotation, scale,...)
  • leaves usually represent atomic units (shapes, vertices, meshes)
  • implementation: arrays, oct-trees, quad-trees, bounding volume hierarchies,...

Scene Manager

  • manages objects in the scene
  • similar to HTML Document Object Model and Event Manager
  • responsibility: sending messages, searching for objects, applying transformation constraints,...
  • Unity Engine - game objects form a hierarchy
  • Unreal Engine - components form a hierarchy

Example: Scene Hierarchy

Example: Scene Hierarchy

Example: Unity Scene Graph

Example: Blender3D Scene Graph

Files

Resource Manager

  • Provides access to all resources (assets)
    • meshes, materials, shaders, animations, textures, clips, levels
    • many assets are not used in their original format
    • engines usually encode their resource metadata in XML files
    • Resource Cache - used for faster access
  • Manages lifetime of each resource
    • most managers maintain some kind of registry
  • Ensures that only one copy of each resource exists in memory
    • resource GUID - usually path of the file, guaranteed to be unique
  • Loads required resources and unloads those no longer needed
    • loading is simpler than unloading
  • Handles streaming

Assets

File Formats: ZIP

  • Many games use their own compression format derived from ZIP

ZIP

  • lossless archive format
  • compressions: shrink, DEFLATE, bzip2, none
  • directory structure

zlib

  • open-source libary
  • implements DEFLATE compression

File Formats: JPEG

  • lossy compression for digital images
  • not very good for pixel art or masking (compression noise)
  • Color Transform - from RGB to YCbCr
  • Down-sampling - chroma reduction
  • DCT - discrete cosine transform, from color domain into frequency domain
  • Quantization - removal of high frequency components
  • JPEG compression procedure:

Other image formats

PNG

  • Portable Network Graphics
  • supports lossless data compression and alpha channel (filters and DEFLATE)
  • good results for simple images (not real pictures)

TGA

  • Truevision TGA
  • 8-32 bits/pixel
  • raw data or lossless RLE compression
  • favorite format for textures

DXT

  • compressed texture optimized for sampling
  • used in realtime rendering
  • DXT1 for RGB, DXT5 for RGBA

File formats: MP3

  • loosy format
  • reduces accuracy by psychoacoustic analysis

Other music formats

AIFF

  • Audio Interchange File Format
  • uncompressed pulse-code modulation
  • may contain loops and samples

WAV

  • linear pulse-code modulation
  • supports also compressed data
  • used when time involved in compressing is a concern (short sounds)

OGG

  • more advanced and a bit smaller in size than MP3
  • open-source (in sharp contrast with MP3)

Trackers

  • .xml, .mod, .it, .s3m files
  • contain PCM samples, notes and effects

3D file formats

  • encode 3D geometry, material, scene and animations
  • geometry encoding - approximate mesh, precise mesh and constructive solid geometry CSG
  • COLLADA - .DAE, sharing models accross CAD software
  • OBJ - neutral format for interoperability, doesn't support animations
  • X3D - popular 3D format for WEB

Audio

Note

It's difficult to persuade people to spend time and money on high-quality sound in games. At the same time, most users would better get a new 3D accelerator than a new sound card.Unknown author from ixbtlabs, 2003

Audio Engine

Audio pipeline

  • for each 3D sound, a dry digital PCM signal must be synthesized
  • distance-based attenuation - provides a sense of distance
  • reverb - provides accoustics
  • dry signals - arrive via an unobstructed path
  • wet signals - echo (early reflections) + tail (late reverberations)
  • audio buffer must be fed periodically - dropping audio is worse than dropped frames

Audio Assets

Audio clips

  • digital sound asset (MP3, OGG, WAV)
  • module asset (MOD, S3M, IT, XM), not used anymore, yet it's fun to play around with them
  • MIDI - sequencer-related data, nowadays mainly for recording and preprocessing

Sound cues

  • collection of audio clips with metadata

Sound banks

  • package of sound clips and cues

Streaming sounds

  • small ring buffer for music and speech

VRWorks

  • path-traced geometric audio from NVidia
  • based on Acoustic Raytracer technology
  • uses GPU to compute acoustic environmental model
  • implemented in Unreal Engine

WebAudio API

  • high-level JavaScript API for audio processing
  • offers capabilities found in modern game audio engines
  • features: modular routing, spatialized audio, convolution engine, biquad filters,...
  • audio operations are performed with audio nodes that are linked together
  • nodes: BiquadFilterNode, ConvolverNode, DelayNode, GainNode, PeriodicWave,...
  • used in many engines that support HTML5 and WebGL

Input

Input Manager

Detects input events from devices

Atomic events

  • KEY_DOWN
  • KEY_UP
  • MOUSE_BUTTON_DOWN
  • MOUSE_BUTTON_UP
  • MOUSE_WHEEL
  • MOUSE_MOTION

Compound events

  • FLING
  • PINCH_TO_ZOOM
  • DOUBLE_TAP

Special events

  • cheat codes
  • fighting combos

Input Devices

Getting the state of the device

  • polling - compare against previous state
  • callbacks - handled by upper SW layer
  • via a protocol (wireless device)

Devices

  • keyboard, touch sensor
  • one-axis controller - single analog state
  • two-axis controller - mouse and joystick
  • three-axis controller - accelerometer
  • camera, VR lens, Azure Kinect

Dead zone

  • area of a control interface that has no input effect (analog joystick)

Normalization

  • axis are mapped to a Cartesian space, not a circular space
  • input must be normalized

Example: Unity Input Manager

Special Events

Sequence detection

  • cheats: IDDQD, IDKFA
  • chords: combo moves in fighting games

Controller input remapping

  • button ID -> action ID mapping
  • can be implemented as a table

Context-sensitive inputs

  • different modes = different handlers (walking, driving, flying)
  • implemented via a state machine, table or polymorphism

Example: Doom 2 cheat detection

// Returns 1 if the cheat was successful, 0 if failed.

int cht_CheckCheat(cheatseq_t* chtchar key ) {

    int i;

    int rc = 0;

 

    if (firsttime) {

      firsttime = 0;

      for (i=0;i<256;i++) cheat_xlate_table[i] = SCRAMBLE(i);

    }

 

    // initialize if first time

    if (!cht->p) cht->p = cht->sequence;

    if (*cht->p == 0*(cht->p++= key;

    else if (cheat_xlate_table[(unsigned char)key] == *cht->p) cht->p++;

    else cht->p = cht->sequence;

 

    if (*cht->p == 1) cht->p++;

    else if (*cht->p == 0xff) { // end of sequence character

        cht->p = cht->sequence;

        rc = 1;

    }

    return rc;

}

Memory

Memory Manager

Main issue

  • the default memory manager that comes with default C-runtime libraries is not suitable for
    most game applications
  • game engines usually implement their own allocator

Custom allocators

  • stack-based
  • pool-based
  • heap-based
  • bucket allocators

Stack-Based allocator

  • mostly used in games that have levels and in-game loops as single-frame allocators
  • implementations: single-ended, double-ended
  1. allocate a large contiguous block of memory
  2. maintain a pointer to the top of the stack
  3. everything above the pointer is considered as a free area
  4. deallocate in an order opposite to that which blocks were allocated

Pool-based allocator

  • allocates lots of small blocks of memory, each of the same size
  • doesn't suffer from memory fragmentation
  • entities have to be of the same size

Example: Atomic GE Allocation

void* AllocatorReserve(AllocatorBlock* allocator) {

    if (!allocator->free_) {

        // Free nodes have been exhausted. Allocate a new larger block

        unsigned newCapacity = (allocator->capacity_ + 1>> 1;

        AllocatorReserveBlock(allocator, allocator->nodeSize_, newCapacity);

        allocator->capacity_ += newCapacity;

    }

 

    // We should have new free node(s) chained

    AllocatorNode* freeNode = allocator->free_;

    void* ptr = (reinterpret_cast<unsigned char*>(freeNode)) + sizeof(AllocatorNode);

    allocator->free_ = freeNode->next_;

    freeNode->next_ = 0;

    return ptr;

}

============================

// create node from void* and call the constructor

Node* newNode = static_cast<Node*>(AllocatorReserve(allocator_));

new(newNode) Node();

// ... do some stuff 

// delete node

(newNode)->~Node();

AllocatorFree(allocator_, newNode);

Loading approaches

Level loading

  • used in Tomb Raider, Doom,...
  • requires a loading screen
  • only one game chunk is loaded at a time

Air locks

  • used in Half-Life 2, Portal, Inside, new Wolfenstein,...
  • larger block contains the whole scene
  • smaller block represents an air lock (usually a small room/hall)
  • when the player enters the are from which can neither see the previous area nor return to it, next scene is loaded

World streams

  • used in open-world games: GTA, WoW, ARMA, Read Dead Redemption, Witcher,...
  • the world is divided into regions
  • when the player enters region B and is far enough that chunk A can no longer be seen, the engine unloads chunk A and starts loading chunk C
  • LOD (level of detail) - chunks are loaded with variable granularity (only meshes)

Example: Loading Screen

Tomb Raider (2015)

Doom 4 (2016)

Raptor (1994)

Example: Air Lock

Portal (2007)

Duke Nukem (1991)

Example: Open World

Arma III (2012)

Comanche (1992)

Lecture 2 Review

  • Components: Scene Graph, Game Loop, Resource Manager, Input Manager
  • Game loop: optimal time step for game model: 60 FPS = 16.6 ms per frame
    • Time steps: fixed, variable, adaptive
    • One-frame-off-lag: state of some objects lags one frame behind
  • Scene graph - a way of ordering the data into a hierarchy
  • Input Events: Atomic events, Compound events, Special events
    • Dead zone: area of a control interface that has no input effect
    • Normalization: axis are mapped to a Cartesian space, the input must be normalized
  • Memory allocator: stack-based, pool-based, heap-based
  • Loading practices: Level loading, Air locks, World streams

Goodbye quote

Technology is incrediblePokemon series