- "In this project, NOTHING is being checked by timeDelta (although I am using some timers)"
Oh! I didn't even think of timers! I don't use them in my projects. Those use TimeDelta().
Here, I made a quick test project to demonstrate. It's simple: there's an object with a timer, a scene variable that goes up by one every frame, and some text. The text displays:
- The amount of frames.
- The theoretical time if we derived it using Frames/60.
- The value of the object's timer.
The first time I run this program, I run it using the property "Max FPS: 60." The second time, I run it using the property "Max FPS: 30."
(The values have been rounded down to 2 places to avoid flashing lights)
While our theoretical Frame-based time is slowing down in the 30 fps version, the Object Timer is keeping up. QED: Timers are Time-based and not Frame-based.
One thing GDevelop is bad at (which, TBF, I've NEVER seen an engine do this well) is clearly distinguishing what is a Time-based system and what is a Frame-based system. For example, I had to figure out through trial-and-error that the animation system is Time-based and not Frame-based (though, this can be remediated by manipulating TimeScale, which essentially is just an artificial modification of TimeDelta()).
- "The collision is being checked every frame, but only with the closest enemy for contact with the player, and only the group of nearby enemies for contact with the blade (while it exists). My gut says skimping on collision per frame is wrong, but maybe my instinct is wrong."
Sorry!! I didn't mean to imply that you had to cut frames!! I said "every spike, every frame" as an emphasis. Every frame is good!
It's hard for me to give advice in this area, since optimization is extremely personal to each individual project, and I don't know how the code is structured. I'm not sure if you've run the debugger's profiler yet, but that's a very useful tool for determining where the biggest loads are (as long as the code is organized using event groups).
My gut instinct is that the enemies shouldn't be causing problems, since there aren't that many of them; the spikes caught my interest because there's many of them, and the levels I had the most trouble with had a lot of them.
Whenever I'm working with something like this, I usually decide what the largest distance the player could be from a colliding object, add a bit to that, then compare the position of the object within that margin. For example, if I decide that the player will never be farther than 32 pixels and in collision with a spike (my projects are very low-res) my conditions will look like this:
[EDIT: The image is not loading, here's a text transcription:]
- The X position of Spike >= Player.X() – 32
- The X position of Spike <= Player.X() + 32
- The Y position of Spike >= Player.Y() – 32
- The Y position of Spike <= Player.Y() + 32
- Player is in collision with Spike
Essentially, I'm seeing if "Spike" falls within a 64x64 box around the player before checking for collisions. This box might need to be smaller or larger depending on the specifics. It might initially look complicated and heavy, but the operation to compare these values is very quick, much quicker than an object collision, and as it goes down these conditions, it's comparing less and less spikes as they're removed from the object picking list.
Apologies for this monster of a reply! Thank you for your time!