So here’s a theory how it’s related to “cheats”. I figure every now and then I should post something that’s not a rant.
I’m normally an app dev, and app development is kind of “comfy” compared to game dev in some ways. If I want to start an animation and I need to make sure I don’t do things until it finishes, that’s more or less how I write my code. “Start the animation and tell me when it finishes” is how I talk to the computer. That’s because apps are event-driven and don’t tend to do a lot of rendering work unless the user’s done something like pushed a button. So if there’s something I don’t want to happen while the animation’s running, I can note “hey the animation is going” somewhere, then wait to get rid of that note until it finishes, and the thing can check if the animation is running before it starts. The bigger the app, the more things like that, but in general smart app devs design their system so it’s easy to understand which things care about which other things.
Game dev is loop-driven and has to render every frame whether there is input or not. That puts more pressure on the devs to avoid practices app devs consider smart. A lot more information about what’s going on in the entire game needs to be available to all of the code. This makes me cringe as an app dev but to some extent it’s a fact of game dev.
The loop-driven nature makes animations more complex too. They can’t say “do this and tell me when it’s done” because there’s no concept of “waiting”. Every frame happens, and all of the calculations need to be done. Unity has a feature called “coroutines” that helps deal with some of this complexity. It lets you write your animation in a way that’s kind of like, “Do this for 90 frames, then update this variable, then do this for 80 frames, then update that variable.” But that feature adds a different kind of complexity: it separates each animation’s code from the others, so it’s harder to understand which animations interact.
Now, the way a game typically implements a faster speed is called “frame skipping”. Normally, the game loop works like this:
for every frame:
Process the user input.
Do all the AI things.
Render the frame.
To “frame skip”, you pretend like multiple frames happen every 1 frame. So a 4x game loop would look like:
for every frame:
Do this 4 times:
Process the user input.
Do all the AI things.
Now do this 1 time:
Render the frame.
This is where stuff starts to break. Imagine we have an animation that goes something like this:
If this frame is number 60:
Set the "don't let the user tap an ally" flag.
Or if this frame is number 240:
Let the user tap an ally again.
We know something bad happens if the user taps an ally in between frames 60 and 240. So we try to tell the UI to disable tapping allies. But the code to do that might be part of “Render the frame” since it has to do with UI:
if "don't let the user tap an ally" is true:
Disable tap events for allies.
otherwise:
Enable tap events for allies.
Bam. This is a bug at 4x speed. Suppose the very first frame of a 4x set tries to set that flag. There’s a bit of time between when that happens and when the rendering code tries to disable tap events. If you tap during that small window, the game is going to queue up a tap for the next “process user input” step. If that step believes “the UI will disable taps if they aren’t allowed”, it’s going to act as if the tap was legal. So now bad stuff happens.
When I’m talking about one measly animation and one measly problem with if a user taps at the wrong time, it seems so easy to keep it all straight. But in a game like GoW, there’s dozens of different animation routines, dozens of different “should this be allowed” flags, and dozens of variables that affect other aspects of the system. No human can possibly keep track of how they all interact. It’s possible if they fix this problem, it creates a worse problem. It seems obvious to “disable tap events” instead of just setting a flag, but I can think of at least five technical reasons why that might be “impossible” or “worse”.
That’s why, in app development, I work hard to compartmentalize code to make relationships obvious. But I get a luxurious 250ms per “frame”, in some cases the user might not do anything that causes me to render for a whole 10-15 seconds, and if I’m really worried I can show a “Processing…” indicator to tell the user to hold up! Gems of War has 16.7ms per frame or else it stutters. That causes you to cut corners, and cut corners are sharper than normal corners.