-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Overall program structure
This page is a work in progress. You can help by contributing to the page.
The goal of this page is to provide an abstract overview of the components that make up (Open)RCT2, and how they fit together and/or communicate.
The main entry point for non-Windows systems (Windows instead uses its own DLL proxy) is located in the Cli.cpp
file. The main
function here calls CommandLine::HandleCommandDefault
which handles a few simple commands (about
, help
) and sets some defaults for global variables like if the game should run in headless mode and paths where game data can be found. Once it checks that all of that happened properly it sets the game to run headless with no graphics (because no graphics have been loaded yet) and creates the game context by calling CreateContext
.
The game context is comprised of 3 main components: PlatformEnvironment
, AudioContext
, and UiContext
. The PlatformEnvironment
informs OpenRCT2 in how to go about loading paths depending on the OS environment it's running in. AudioContext
handles playing music and sound effects. UiContext
handles the window or screen that OpenRCT2 is presented on. The default game context that is created has dummy audio and ui contexts, and is returned.
After the game context is created we can call RunOpenRCT2
which will try to Initialise
and then Launch
the game.
Initialise
handles a lot of stuff but the most prominent things it handles are creating the exception handling, creating the AssetPackManager
, creating the window, makes sure things are loaded that need to be, initializes Audio
, copies files, initializes all viewports, initializes the context, sets the active scene, and initializes repositories and the script engine.
click for full list
- creating the exception handling
- whether to show a changelog
- handles configuring the language
- gets or prompts for the installation path of RCT2
- creating the
AssetPackManager
for managing assets - creating the
DiscordService
if enabled for interacting with discord - issuing various warnings
- creating the window
- ensuring the user content directories exist
- initializes
Audio
- populates audio devices
- initializes ride sounds and info
- sets whether game sounds are on or off
- initializes the chat
- copies original user files
- loads the base graphics
- initializes lighting fx
- initializes all viewports
- initializes the game context
- sets the active scene
- initializes the repositories
- initializes the script engine
If Initialise
completes with no error and returns true
the we can Launch
the game. First we check to see if there is a new version of the game so we can inform the player if there is. Then, we switch to the startup scene which by default is set in OpenRCT2.cpp
to StartupAction::Title
. If the game is running in headless mode, then StartupAction::None
or StartupAction::Open
are the only actions. Otherwise, depending on the startup action we will decide what scene to load and set it as the active scene and initialize the game networking. By default the title scene is chosen.
After the startup scene is set we can finally jump into the game loop by calling RunGameLoop
. The game loop is mainly concerned with keeping track of if the game is finished
(if the player quits/closes the game) and if it isn't then it checks to see if a variable frame should be run to capture state, and then calls RunFrame
to run the next frame of gameplay.
At this point we know we are dealing with logic for a single-frame of gameplay. Any code put here will most likely run once per frame, so be mindful of performance, but don't optimize until necessary. The first point of order is to get the time that has elapsed since the last frame referred to as deltaTime
(delta often standing for "the change in", so the "change in time since the last frame") which is an important variable in most, if not all games to ensure smooth interpolation between frames since the time between frames can differ each time.
We then check again to see if a variable frame should be run to capture any change in state. If it changes from running a variable frame to a fixed frame we need to reset the entity positions back to their end of tick positions of the previous tick. Then, we update the time accumulators (which keep track of game ticks and real time) and then we either RunVariableFrame
or RunFixedFrame
.
When running a variable frame we first check if we should draw anything and grab a reference to the EntityTweener
in case we have to. Then we let the UI process its messages, which really are just the window inputs captured by SDL
(the third party library used for window management) for that frame. There are a lot of events that are handled in this function so there will be a section dedicated to input handling and this will be gone over in depth there.
Next we check if the accumulated ticks is greater or equal to the set kGameUpdateTimeMS
(which is the game update interval in milliseconds, (1000 / 40fps) = 25ms
) and if so, we loop running tweener.PreTick
if we shouldDraw
, then we Tick
the game, subtract kGameUpdateTimeMS
from the ticks accumulated, and then we run tweener.PostTick
if we shouldDraw
. This repeats until ticks accumulated is less than kGameUpdateTimeMS
. There should probably be a more in-depth section about how the game handles tracking ticks and time and the math behind it.
Once all of the ticks have run we call ContextHandleInput
to allow the context to do its input handling which is the game specific input. Once all of the game input is handled we can update all of the windows by calling WindowUpdateAll
to reflect the changes in each window. Then, if we shouldDraw
we tween and then Draw
.
When running a fixed frame we go straight to letting the UI do its input handling. Then, we wait for the ticks accumulated to become greater or equal to kGameUpdateTimeMS
. Once that condition is met we loop and only Tick
until it is no longer true.
Similarly to a variable frame, once all of the ticks have run that need to we let the context do its input handling by calling ContextHandleInput
and then let all of the windows update by calling WindowUpdateAll
. Then, if we ShouldDraw
, we call Draw
.
Pre ticks currently only apply to the tweener and is only run in variable frames. The tweener.PreTick
function first Restore
s all entities which loops through all entities and moves their position by calling ent->MoveTo(const &CoordsXYZ)
which will check to see if the position is valid before moving the entity to that position. Then it Reset
s by clearing all Entities
, PrePos
and PostPos
. Lastly it repopulates the Entites
which also populates PrePos
.
Tick
is called in both variable and fixed frames, and at a high level is a wrapper function that calls the various Tick
functions of services that need to Tick
. Right now there is not a unified interface for ticking. Multiple objects just have Tick
functions defined within them. I can see an opportunity for a unified interface here for better modularity and extensibility.
As well as ticking the various things that need ticked, this wrapper function also takes care of updating gCurrentDeltaTime
and some other time related housekeeping. It also takes care of updating the chat and processing the evaluation queue of the scripting engine.
Post ticks currently only apply to the tweener and is only run in variable frames. The tweener.PostTick
function loops through all Entities
and populates PostPos
for each entity.
Draw
at a high level takes care of running the drawing and painting functions using an object that implements IDrawingEngine
. Specific details for drawing will be elsewhere, but this interface allows us to implement different renderers in a standardized way. Currently there are only two drawing engines, the software based X8DrawingEngine
and the hardware based OpenGLDrawingEngine
.
outdated
Every save file has a list of all objects that are required for the scenario. Objects are split into 11 different types. There is a maximum number of objects for each type that can be loaded in each save. When the game first starts it creates an installed objects list of all available objects that are in the object folder. When a save is loaded it checks the installed objects list for a location of the object and loads it. Every object has a checksum to prevent corruption issues. The checksum is checked every time an object is loaded. For more information about the object structure see Objects.
- Home
- FAQ & Common Issues
- Roadmap
- Installation
- Building
- Features
- Development
- Benchmarking & stress testing OpenRCT2
- Coding Style
- Commit Messages
- Overall program structure
- Data Structures
- CSS1.DAT
- Custom Music and Ride Music Objects
- Game Actions
- G1 Elements Layout
- game.cfg structure
- Maps
- Music Cleanup
- Objects
- Official extended scenery set
- Peep AI
- Peep Sprite Type
- RCT1 ride and vehicle types and their RCT2 equivalents
- RCT12_MAX_SOMETHING versus MAX_SOMETHING
- Ride rating calculation
- SV6 Ride Structure
- Settings in config.ini
- Sizes and angles in the game world
- Sprite List csg1.dat
- Sprite List g1.dat
- Strings used in RCT1
- Strings used in the game
- TD6 format
- Terminology
- Track Data
- Track Designs
- Track drawers, RTDs and vehicle types
- Track types
- Vehicle Sprite Layout
- Widget colours
- Debugging OpenRCT2 on macOS
- OpenGL renderer
- Rebase and Sync fork with OpenRCT2
- Release Checklist
- Replay System
- Using minidumps from crash reports
- Using Track Block Get Previous
- History
- Testing