The Annoying Quirks of Godot’s Kinematic Bodies

To be clear, I like Godot and am impressed by how far it has come as an open-source game engine. I am quite confident Juan Linietsky is much more brilliant than I. Just because I’m being critical of something, however, this doesn’t mean I don’t like it. I just believe in constructive criticism. Nothing changes if nobody ever talks about it. Also, while I’m focused only on 2D right now, I believe much of this applies to 3D as well. This applies to Godot 3. I haven’t looked at where Godot 4 is headed with all this.

I am currently wrapping up the pain of porting a game to Godot that I first prototyped in Phaser. In brief, Phaser became so annoying to work with that I went looking for another engine that gave me easy access to the browser as a platform (via WebAssembly preferably). I landed on Godot — an engine I’d been poking at for years but hadn’t done much with yet.

The game prototype, Sendit Soccer, used Phaser’s Arcade Physics for lighter processing in a game that is likely to stress the browser by the time it is finished. Thus, in Godot, it sounded like KinematicBody2D was the way to go for light physics. The more-than-somewhat-confusing Kinematic Character page certainly tries hard to sell it as the best solution on the planet with a “a nice and simple API.” Also, this:

Kinematic characters: KinematicBody2D also has an API for moving objects (the move_and_collide and move_and_slide methods) while performing collision tests. This makes them really useful to implement characters that collide against a world, but don’t require advanced physics.

Yeah… it totally didn’t work out that way for me. Does anyone care? Not really. Why not? No clue. I mean, okay, sure, maybe it didn’t technically require “advanced physics” (whatever that really is) but it did require advanced gymnastics with complex code that proved to be harder to get working sanely than, for example, the “advanced physics” of my physics-heavy Unity game, GRITS Racing. (To be fair, however, some bits of the “hyper-advanced” physics that went beyond Box2D in GRITS Racing was perhaps harder than Godot’s kinematics.)

So, to finally get to the points/quirks/flags:

Red flag 1. Kinematic bodies in Godot don’t resolve their collisions at the same time as the other three types of physics bodies in Godot. Instead, they resolve collisions procedurally via their “API” rather than in the physics step for collisions.

Red flag 2. Even though kinematic bodies resolve collisions procedurally, they don’t resolve collisions in procedural order. This was the biggest surprise with kinematics in Godot and I couldn’t find it documented anywhere (or mentioned anywhere on the net for that matter). I suggested adding this to the docs in a report… that nobody appears to care about… and likely never will.

These first two quirks may not sound like a big deal… until you get into the nitty gritty of trying to build an interesting environment. In short, what they add up to is that collisions from chasing an object will happen one or more frames earlier than expected, and head-on collisions will happen one frame later.

The precise explanation of “why” is difficult to express, but I’ll try. What is going on (as determined by stepping through the code) is that move_and_collide() and move_and_slide() always collide with other objects according to their position last frame. This is true even if you already moved another object this frame. Thus, even if you use process_priority wisely, or simply move all your objects in a single procedure, you will likely find your collisions happening at points that basic logic tells you is wrong. While physics engines are definitely weird at times in how they resolve collisions, this is the weirdest quirk I’ve seen yet.

For example, let’s say you move_and_collide() object A on one line of code, then move_and_collide() object B on the next line. As far as object B is concerned, object A hasn’t moved yet. Thus, even if the two objects are moving at the same speed and direction, if object B is chasing object A at a close enough distance, it will collide with object A. Such inability to chase is not typical for physics engines as far as I’ve seen.

In other words, if you want a kinematic player to dribble a kinematic soccer ball, forget about it. You’ll have to find workaround. Such a workaround might be some collision-layer kung fu, or maybe toggling a collider off/on on both sides of your call to move_and_collide().

This can even cause problems with an extra collision during instantiation. I wasted a day on that one alone.

Now, all this makes some sense when I think about the possible engineering under the hood. Novice devs aren’t likely to care about this quirk (as evidenced by no one caring thus far [or they haven’t figured out their pain yet]) — and it might even be best that they don’t have to worry about process_priority etc. I’m not sure how much I care either since the late/early-frame side effects aren’t really noticeable in game (yet). But, hacking out the workarounds has proven to be a major time waster and has led to liking Godot a lot less than I ought. I wish there was a setting or engine option for when the buffered object positions are updated with move_and_collide(). I would opt for instantly… obviously.

Red flag 3. The “API” is inconsistent. Even though the API is essentially only two methods (as cited above) those two methods are bizarrely different in that one automatically consumes the physic step’s delta, while the other does not. This has caused so much confusion that I’ve seen more than one “expert” commenter on forums get it wrong (see the moderator’s unhelpful comment here, for example). Even Godot’s own docs don’t demonstrate proper delta use reliably, such as this unhelpful introduction to move_and_collide():

func _physics_process(delta):
    move_and_collide(Vector2(0, 1)) # Move down 1 pixel per physics frame

Furthermore, given how move_and_slide() tries to help you with using move_and_collide() to find and resolve all sliding collisions in a frame, it makes no sense Godot doesn’t also provide a move_and_bounce() to help you do the same if you have a bouncy body. (Who doesn’t like a bouncy body?)

Really, however, move_and_slide() is just a deceptive helper that encourages limiting all collisions to sliding. This is fine for game-dev noobs, I suppose, but not fine for the rest of us. If you want a more complex environment that involves both sliding and bouncing, you’ll find no help in the docs for such complexity. Not even hints as to where you might start with this (like with your own move_and_collide() loop, for example).

Red flag 4. The paradigms are mixed. Because area objects don’t technically collide (even though Area2D extends CollisionObject2D… go figure), you can’t detect a kinematic body colliding with areas with move_and_collide(). Instead, you have to then mix in another programming pattern for responding to area entry and exit events. Well, that or adopt a third pattern of looping through overlapping bodies and areas — overlaps as of the last frame, of course — a pattern they hint as being less than ideal — but I’m not so sure it is bad considering the other quirks in play here. (The docs are also confused on this third pattern: “this list [of overlaps] is modified once during the physics step” -vs- “the list of overlaps is updated once per frame and before the physics step.” Which is it?)

move_and_collide() does, however, return collisions with static bodies (in addition to other kinematic bodies). I presume this is also true with rigid bodies but I haven’t tested that (I don’t seem to have a use for kinematics and rigids in the same game — it’s either all one or the other thus far).

Side point: Another thing that gives me some trouble here is that collisions between kinematic bodies often don’t report both ways as expected. Or, sometimes, report a collision when expecting none. I don’t believe there’s a glitch here since, so far, I can figure out why when I dig in deep enough. It is due to the entire collection of these quirks — that is, what moves (or doesn’t move) where and when, what bounces where and when, and what corrections I might make to position and when. Thus, some bodies don’t always separate as expected (or when) and trigger extra collisions, and/or they aren’t always moving the direction as expected due to bounces and such. It gets to be a real mess quite quickly. Don’t believe Godot when it tries to tell you dynamic character controllers require more skill than kinematics. Nope. Godot’s kinematic character controller requires far FAR more skill than straight-up “advanced” physics when needing more than just the basics. If I wasn’t targeting a browser as the primary platform for Sendit Soccer, I would have abandoned Godot’s kinematics after the first week of wrestling with it.

Second side point: In Godot, the OOP is terribly unusual in that when you overload a default function like _physics_process(), that function gets called in all parent classes automatically as well. You can’t avoid it! Who designs OOP like this? It’s maddening and only makes all of this that much harder to contend with.

Red flag 5. Nobody cares. Why is this a red flag? Well, yeah, exactly! It’s a red flag that the rest of this is not a red flag to anyone else. I find it terribly odd that I could not find any conversation on the significant points here. The closest I could find is a YouTube video, Godot 3D KinematicBody Bug – move_and_collide(), which describes an issue I didn’t mention as a problem here (because, so far, that issue appears to be limited to 3D kinematics).

So, either I’m totally cracked in the brain (a real possibility I consider often, due to how often I find myself on a thought island) or Godot really is just a toy for beginner game devs like its rep says it is. While I respect the great amount of work and intelligence that has gone into Godot, stuff like this leaves me scratching my head as to how some parts can be so right while other parts are so wrong. It feels like none of the major developers of the engine are spending enough time making anything other than platformers and other canned and common stuff.

I see plenty of issues being reported on GitHub but I can’t say I see many good responses there. I’ve seen a few interesting conversations there, but far fewer that actually affect anything as far as I can tell (like the insane reluctance to fix the even-more-annoying bugs hampering class refactoring — if restarting Godot a dozen times daily fixes something, it must not be broken, right?). Hence, why I resorted to this blog post instead of GitHub. I get the vibe that there’s a clique of decision makers and you are either “in” or not. Although, I can’t say that’s a bad thing either. If open-source software doesn’t have good leadership, you end up with game engines like Flame and Phaser that are a total disaster of paradigms and patterns. Barf.

Summary

Note that these are quirks and red flags… and only that. It is hard to call anything here a real issue or bug that must be fixed. The current tooling gets the job done… but annoyingly so. I believe the flags here should be talked about more and, hopefully, someone will find this to be a conversation starter for something better down the road. If not, well, I tried.

Personally, I would like to see a 2D physics engine option with kinematics more inline with other engines (you know, velocity and event driven, etc.). I would also like to see yet another 2D engine with simplified/faster physics much like Phaser’s Arcade Physics (non-rotated rectangles and circles only). But, considering that the demand for that engine would likely only be for low-powered platforms like browsers, I expect little support for this idea.

Me, I’m primarily a middleware and front-end programmer and am barely scraping by with the contracts and open-source tools I already support. No time for pretending to be a lower-level programmer. I did, at least, contribute a few months to get this out: Godot Firebase Lite.

Godot can do better. Godot needs to do better.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s