Start

Theory

Reference

Examples

Game loop & timing

The game loop is the heartbeat of a game: read input, advance the simulation by the time that passed, draw, repeat.

The concept

Each iteration of the loop is a frame. Simulation code must be told how long the previous frame took (the delta time) so movement stays the same speed on a 30 Hz and a 144 Hz display. Engines differ on whether the simulation uses a variable step (one update per frame with the real delta) or a fixed step (update at a constant rate, possibly several times per frame); a variable step is simpler, a fixed step is more deterministic.

Anatomy of a frame

Every frame does the same four jobs in the same order: input (drain the OS event queue and update input state), update (advance the simulation), render (draw the result) and present (swap the finished image onto the screen). Doing input first means the update sees the freshest state; presenting last is what makes a frame visible at all.

Delta time

Without delta time, x += 5 each frame moves twice as far at 120 FPS as at 60 FPS. Multiplying by the seconds since the last frame, x += speed * dt, makes motion depend on time and not on frame rate. A very long frame (a breakpoint, a window drag) produces a huge delta that can push objects through walls, which is why engines often clamp it.

Variable versus fixed step

A variable step is the simplest option and fine for most games. A fixed step runs the simulation at a constant rate (say 60 times a second) and accumulates leftover real time, so results are reproducible and physics stays stable; the cost is more machinery, and rendering then often needs to interpolate between two simulation states.

Pacing the frame

Without a limit a loop runs as fast as the machine allows, burning CPU and battery for no visible gain. Engines either wait for the display's vertical sync or sleep until the target frame time has passed. Time can also be scaled (slow motion) or paused, which is why a clock distinguishes scaled game time from unscaled real time: a pause menu still needs real time to animate.

How ASGE does it

Application

asge::Application<TGame> owns the video, input and audio systems and constructs your TGame in place. Run() executes the loop below and stops on an OS quit event or when a game state requests TransitionKind::Quit.

Application.hpp
// asge::Application<TGame>::Run(), abridged
while ( m_Running )
{
    GET_TIMESYSTEM.Tick();                       // advance the clock
    m_InputSys.NewFrame();
    while ( SDL_PollEvent( &event ) )            // drain OS events
        processEvent( &event );
    m_Game->Update( time::DeltaTime(), m_InputSys.GetState() );
    m_Game->Render( m_VideoSys.GetRenderer() );
    m_VideoSys.GetRenderer().Present();
}

ASGE uses a variable timestep: one Update per frame, with the scaled delta time. The target frame rate comes from ApplicationConfig::s_TargetFps.

One frame inside Game

Game<TStateId>::Update runs the systems in the only valid order:

  1. UI interaction
  2. the active state's own Update() (your game logic) - states never call systems themselves
  3. gravity, movement, path following
  4. UI layout, then one transform propagation
  5. collision detection and resolution, then trigger events
  6. audio

Render then resolves pending assets, clears with the visible state's colour, draws the scene through the render pipeline, and finally lets the states draw on top.

Game states

Screens are IGameState objects on a GameStateStack. A state's Update may return a Transition to Push, Pop, Replace or Quit. Your Game subclass implements CreateState() to build a state from its id.

Time

The asge::time namespace exposes the clock: DeltaTime() (scaled and pause-aware), UnscaledDeltaTime(), ElapsedTime(), FrameIndex(), Scale(), Paused(); Scale(float), Pause(bool) and TargetFPS(fps) are the setters. Pausing makes the scaled delta zero while the unscaled one keeps running, which is what you want for a pause menu.

Profiling

PROFILE_SCOPE("name") times the enclosing scope with a ScopedTimer and records it into a named TimingBucket held by the TimingProfiler singleton.

timing.cpp
asge::time::Scale( 0.5f );            // slow motion
asge::time::Paused();                    // true while paused
float dt  = asge::time::DeltaTime();     // scaled, zero while paused
float raw = asge::time::UnscaledDeltaTime();

void Update()
{
    PROFILE_SCOPE( "Update" );           // times this scope into a named bucket
    // ...
}

See also

Source: Application/Application.hpp, Game/Game.hpp, Core/Time/Time.hpp. Scoped profiler example · Moving box