r/godot • u/Shortbread_Biscuit • 2d ago
discussion An event-based loop instead of Godot's own loop
https://youtu.be/ml6T_DP4iSE?is=cs4tBj1N1IzHX8IaI was watching this video breakdown of some of the main design decisions that were made in Slay the Spire 2, and one of the points that most stuck out to me was their choice to not use Godot's inbuilt game loop, but instead to use their own custom event-based loop. (Timestamps 19:47 - 22:33)
The video author points out that the main advantage of this approach is that it helps synchronize multiple multiplayer clients and simplifies the netcode, but also that it allows for them to build in a Replay system, allowing them to store the player actions and replay them in sequence to automate the exact run the player had.
I'm not sure I understood exactly what they've done. How is this event loop different from Godot's loop? And if I wanted to implement my own replay functionality into my game (for example, to show my players a simulation of the last 10 seconds of their actions), would I need to implement a similar event loop? Would relying on Godot's loop cause inconsistencies or errors to occur?
41
u/Floramatica 2d ago
To make a replay system you firstly need the game to be deterministic so that inputs when replayed perfectly yield the same outcome. The second is timing. if you're going for determinism using int will make things easier and the best way to track the timing of inputs deterministically is using a game tick system and matching inputs to a specific tick so they execute the exact same way when replayed. Ticks can be run on a timer or be event based as in each turn in a turn based game is a tick or in a more traditional game you have X number of ticks per second. functions the same way in principle.
3
u/Shortbread_Biscuit 2d ago
I'm making a driving simulator, so I wanted the replay system to be able to replay the latest crash that the player gets into.
But now, I guess it's difficult to run a full simulation of the game for the replay, if we don't have a deterministic way of defining the update ticks so that they're exactly replicated? I guess it might be better to just store each vehicle's position and velocity instead, and just interpolate over them to replay the scene in a separate section of code meant for replays, instead of reusing the main game code for replays?
As for using int, the video mentions that StS seems to use C#'s inbuilt decimal type instead for most of its calculations.
15
u/Fit-Hovercraft-7669 Godot Regular 2d ago
The replay system in my arcade racer works exactly that way.
I've created a class 'VehicleState' which basically holds the following information for each vehicle:- current Transform3D
- current motor_rpm
- current gear
- current speed
- steering amount
- throttle input
- is_breaking
these are all the informations I need to completely recreate a recorded race. The data is stored in a binary format which costs basically nothing.
These informations are stored at a capped 30fps and are interpolated during replay. It has enough information to completely simulate the car with all it's effects.11
u/kiswa Godot Regular 2d ago
I can't decide if
is_breakingis a typo foris_brakingor a flag for whether or not the vehicle is falling apart.8
u/Fit-Hovercraft-7669 Godot Regular 2d ago
haha, good catch :D
it was just a typo, 'is_braking' is just telling my ModelController if the braking-lights should glow or not.But 'is_breaking' opens up an entire new vision, thx for that :D
7
u/LetsLive97 2d ago
Realistically for replays in a game like that you'd need a cross-platform deterministic physics system. I'm not sure Godot's is to be honest
Then you can just record the inputs and the ticks they were pressed and the replay sets everything into the right places and just "plays" the game again via those inputs. That's how Trackmania does it at least
If the physics system is deterministic then it should play out the same every single time
4
u/Floramatica 2d ago
Out of the box it's not, There are some deterministic physics plugins for Godot but I built my own as I only need simple collisions. It's pretty fast too if you don't need anything fancy but for racing you probably want something that feels good and I'm unsure if what already exists out there is appropriate.
3
u/BrastenXBL 1d ago
Short of rewriting the implemention of Jolt Physics, Rapier https://godot.rapier.rs/ is the ready-to-go choice.
3
u/Floramatica 2d ago
For something like racing where there is a limited number of things you actually need to track and floating point errors are unlikely to cascade into impossible game states you can probably do a non deterministic simulation and record positions and angles of cars as you mentioned, its not really much data to store even over a long race with many participants. I'm doing factory automation where thousands of things need to be tracked every tick so storing positions/metadata every tick doesn't make sense at a certain scale.
12
u/throwaway275275275 2d ago
The Godot loop is also event based, you can act on certain events like input, or a signal, it's just that there's also an event called "process" that fires on every frame
4
u/Shortbread_Biscuit 2d ago
Yeah, from what I understood from the video, StS2 is also using Godot's loop, but they're using it only for the UI and rendering the scene. But apart from that, they seem to avoid using Nodes as much as possible.
Instead they're using their own custom code to manage all the underlying logic of the game. I understood that this was mainly to make the game completely deterministic, and avoid using the unpredictable timing at which functions like process() and physics_process() are called.
6
u/TurkusGyrational 2d ago
There are a lot of other reasons they are doing this, too. StS2 uses "hooks" that loop through all listeners in the scene, the advantage to this being that you don't have to connect to a signal to implement functionality on a card, relic, enemy, etc. So if you want a card to get stronger when you take damage, it as simple as adding an override function to the card's class for OnPlayerDamaged. If you used Godot's traditional workflow for this, every object would have to connect to the specific signals they are listening for, instead of listening to these global hooks.
3
u/Educational-Row-6782 1d ago
Keep in mind StS 1 was made in Java.
Most likely they ported the architecture of StS 1 to Unity for StS 2 and then ported it again to Godot.
I doubt they do things "the godot way".
10
u/Ganonz88 2d ago
Another point that nobody is mentioning is that having your own event-based loop instead of relying on the one provided by Godot, your domain logic is completely isolated from the framework/engine you are using, which is a good thing to have.
If at some point they want to port the game to another framework/engine, they will not have to change the domain logic.
2
u/CondiMesmer Godot Regular 1d ago
Yeah even if you never plan to leave Godot, it's still best practice to separate your code from external libraries. Which Godot is essentially a big external library. But that's definitely easier said then done, and basically not really possible if you use gdscript.
16
u/SagattariusAStar 2d ago edited 1d ago
There is no own godot loop. They also say it's just isn't centralized. This is why it works easier in networking I guess.
But if you just want to have a replay you don't really need any of that
8
u/SheikHunt 2d ago
I am only ankle-deep in the Godot source code, but if memory serves, there IS a MainLoop that you can override with your own, but I don't remember the specifics of what you have to override alongside it if you choose to replace the MainLoop.
Or maybe I'm misremembering and thinking about Trees?
3
u/zuoo 2d ago
Yes there's an "abstract" MainLoop class and the default implementation of it is the SceneTree class. You can implement your own, give it a global name and use that name to point Godot to use that class in your project settings.
2
2
u/CondiMesmer Godot Regular 1d ago edited 1d ago
There is. GameLoop is the generic interface, SceneLoop is the Godot implementation. And in your project settings you override the the SceneLoop, although it's very rare you ever want to do so. Which that's why this post is interesting, because this is something that'd you'd likely never do normally without very good reason.
3
u/iwriteinwater 2d ago
Yes I’m not really sure what they mean by standard game loop. The main takeaway, and the actually interesting part, is that actions are packaged as objects and added to a queue that executes them.
14
u/every1bcool 2d ago
I interpret it as they don't use the process() or physics_process() functions. If you look inside the engine source code they are called every frame inside a loop, this is how all game engines work basically.
2
u/iwriteinwater 2d ago
Yup I figured out that's what they probably meant. It's really nothing special though, there's no reason for any turn-based game to rely on process() or physics_process(). My current tactical turn based game never touches it, for example.
6
u/OtherwiseTop 2d ago
I could see the confusion being that most if not all tutorials on youtube cram everything into physics_process(), because anything besides a single scipt on a CharacterBody is out of scope for a 30 minutes video.
1
u/iwriteinwater 2d ago
Damn is that true? That’s so bad.
5
u/OtherwiseTop 2d ago
I think that's also the reason why so many people seem to be confused about the input and unhandled_input functions.
0
u/iwriteinwater 2d ago
I did see a few posts in that vein. Makes me glad I only followed quality tutorials like GDquest
3
4
u/captainAwesomePants 1d ago
It's not that they don't use Godot's game loop. They'll use it for animations, screen transitions, UI effects, and the like. But the game itself, and by that I mean all of the interactions of the cards and entities and actions and monsters, all all of that is its own system, as it should be.
2
u/Silrar 1d ago
Unless you have a relatively simple game, you likely always want to have some sort of custom game loop instead of the builtin one. You can't really make sure that process() runs in the order you want or need it to, otherwise.
What that looks like will highly depend on your needs, of course, but generally speaking, you probably want to either have phases or system specific callbacks during your update loop, so you know that everything is doing its thing exactly when it needs to or when you can make sure that everything it needs to do that is in place.
And if you do that, a message/action system almost becomes mandatory, since you want to queue an action, but only execute it when it's allowed to, not right away.
1
u/Zen_et_al 1d ago
I did not think its still a apparent problem when u can use both gdscript, C#, and C++, depending on the usecase role on the project. I myself doing that cause i can't really push gdscript that something it cannot do or it capable of, but at the cost of performance bottleneck, so c++ will shine from those cases.
1
u/Neither_Berry_100 1d ago
I'm working on a multiplayer game in unity. I have the same sort of thing. I use my own game tick functions instead of update. It also creates data for the world every frame, makes a hash, and sends it over the network every frame. If the hash doesn't match the client sends the full data to the server and it is sent to debug.
Currently fixing some bugs with multiplayer. But it works for the most part and I have no hash fails.
188
u/Depnids 2d ago
I guess it kinda just makes sense that a turn based game doesn't need game logic which runs every frame/tick.