In terms of game engine internals, you usually get to choose between deterministic behaviour and a slightly-faster-but-not-deterministic one. GameMaker originally made a number of these choices in the name of shielding the user from oddities of the computer, but this later became a design choice.
For example, take collision handling.
- GameMaker has a choice of its own collision system and Box2D
- Unity uses Box2D for 2d and PhysX for 3d.
- Determinism in PhysX is a huge can of worms so let's focus on 2d.
- Box2D in itself has limited determinism¹, and slightly less so in Unity². GameMaker's Box2D implementation isn't much more deterministic than Box2D itself, which means that different platforms / different builds / different CPU architectures aren't guaranteed to match up together.
- GameMaker's collision system (which is what most games use) uses an R-Tree and offers the user a handful of functions for checking for collisions with objects/types - the user does movement logic themselves (usually by doing single-unit steps towards the collider once it's been established that it's in the way) and they are probably not doing any extravagant math in there. But even if they do, we have the other thing:
Epsilon³: When you do `num == 5` in GameMaker, what really happens is `abs(num - 5) < eps)` There is a small cost to doing this instead of a raw check, but this spares the user of dealing with floating-point errors in vast majority of circumstances. Conveniently, this is also good for determinism - something like `a = 1; ...; b = a / 3` can have a different result depending on CPU model, but this is now irrelevant - `b >= 1` will reliably trigger regardless of whether it ends up being a tiny bit more or a tiny bit less than 1.
With GameMaker's recent push for out-of-box deterministic multiplayer⁴, determinism across the engine logic became a greater focus, and the small number of known ways to shoot yourself in a foot shrinks as time goes on.
Unity does not, and realistically cannot embrace determinism in games - even .NET types tend to have undefined order of operation if that means being a tiny bit faster⁵.
And thus we have the situation where GameMaker games are generally deterministic by default (and my GMTogether tool takes advantage of this) while Unity games pursuing determinism have to replace almost entirety of the engine's and .NETs standard library with custom versions using fixed-point arithmetic and predictable order of operations.
Perhaps I should organize this and make it a post of its own...
Thanks for the comprehensive reply and the great insights! Makes sense why the only available commercial Unity deterministic netcode (Photon Quantum) uses fixed-point math and have their own physics engine.
Glad to hear your journey in netcode! Just a question, why is it harder to make a deterministic netcode in unity vs in gamemaker?
In terms of game engine internals, you usually get to choose between deterministic behaviour and a slightly-faster-but-not-deterministic one. GameMaker originally made a number of these choices in the name of shielding the user from oddities of the computer, but this later became a design choice.
For example, take collision handling.
- GameMaker has a choice of its own collision system and Box2D
- Unity uses Box2D for 2d and PhysX for 3d.
- Determinism in PhysX is a huge can of worms so let's focus on 2d.
- Box2D in itself has limited determinism¹, and slightly less so in Unity². GameMaker's Box2D implementation isn't much more deterministic than Box2D itself, which means that different platforms / different builds / different CPU architectures aren't guaranteed to match up together.
- GameMaker's collision system (which is what most games use) uses an R-Tree and offers the user a handful of functions for checking for collisions with objects/types - the user does movement logic themselves (usually by doing single-unit steps towards the collider once it's been established that it's in the way) and they are probably not doing any extravagant math in there. But even if they do, we have the other thing:
Epsilon³: When you do `num == 5` in GameMaker, what really happens is `abs(num - 5) < eps)` There is a small cost to doing this instead of a raw check, but this spares the user of dealing with floating-point errors in vast majority of circumstances. Conveniently, this is also good for determinism - something like `a = 1; ...; b = a / 3` can have a different result depending on CPU model, but this is now irrelevant - `b >= 1` will reliably trigger regardless of whether it ends up being a tiny bit more or a tiny bit less than 1.
With GameMaker's recent push for out-of-box deterministic multiplayer⁴, determinism across the engine logic became a greater focus, and the small number of known ways to shoot yourself in a foot shrinks as time goes on.
Unity does not, and realistically cannot embrace determinism in games - even .NET types tend to have undefined order of operation if that means being a tiny bit faster⁵.
And thus we have the situation where GameMaker games are generally deterministic by default (and my GMTogether tool takes advantage of this) while Unity games pursuing determinism have to replace almost entirety of the engine's and .NETs standard library with custom versions using fixed-point arithmetic and predictable order of operations.
Perhaps I should organize this and make it a post of its own...
[1] https://box2d.org/documentation/md__d_1__git_hub_box2d_docs__f_a_q.html#autotoc_md155
[2] https://support.unity.com/hc/en-us/articles/360015178512-Determinism-with-2D-Physics
[3] https://manual.yoyogames.com/#rhsearch=Strings&t=GameMaker_Language%2FGML_Reference%2FMaths_And_Numbers%2FNumber_Functions%2Fmath_set_epsilon.htm
[4] https://gamemaker.io/en/blog/gamemaker-multiplayer
[5] https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.dictionary-2?redirectedfrom=MSDN&view=netcore-3.1#remarks
Thanks for the comprehensive reply and the great insights! Makes sense why the only available commercial Unity deterministic netcode (Photon Quantum) uses fixed-point math and have their own physics engine.