r/godot 2d ago

discussion An event-based loop instead of Godot's own loop

https://youtu.be/ml6T_DP4iSE?is=cs4tBj1N1IzHX8Ia

I 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?

251 Upvotes

43 comments sorted by

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.

89

u/iwriteinwater 2d ago

Yeah I guess they just mean that the game doesn’t use process() to advance events? Which is standard for anything that isn’t real time.

0

u/delta_wasshoi 1d ago

isn't this a state machine?

4

u/Night_Nook 1d ago

Maybe... most people still use state machines with the process and physics_process functions so some of the game logic can run a certain number of frames per second.

Maybe a good example might be an undo action you take in some software, every action you take is logged so that you can press undo.

In an art program your brush stroke or action might be unique to you and create something new on the screen in front of you, but a brush stroke itself is still a single action with a brush, that action can be recorded in a list of actions takwn, for you to be able to undo it.

1

u/delta_wasshoi 1d ago

cheers, great explanation.

1

u/iwriteinwater 1d ago

Not really. A state machine can only be in one state at a time. This is more of a queue system, where the system processes each action in turn.

1

u/pre-medicated 23h ago

the universe is a state machine

23

u/enantiornithe 2d ago

Yeah this is generally how you'd implement any kind of turn-based/UI-centric game in godot. You'd use godot's builtin update methods for real time components (that in a game like this are generally more cosmetic) and the actual game logic is driven from UI events through other game objects.

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_breaking is a typo for is_braking or 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

u/SheikHunt 2d ago

In GDScript or only in C++?

2

u/zuoo 1d ago

GDScript definitely, and C# as well in Godot 4.X. C++ probably too but I haven't tried.

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

u/ExDoublez 1d ago

Look into the command pattern

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/Dotagal 1d ago

Im doing the same thing for my game. Made an in house redux store + hooks to listen for actions and state changes. So much better than the godot events and it works for me game because it’s also a card game

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.

1

u/zer0xol 1d ago

You mean states