Skip to main content

Choosing an Update Loop That Doesn't Turn Your Game Into a Time Warp

You've been there. You're tweaking movement code, and the character slides a little too far right before stopping. Or you bump up enemy count, and suddenly your jump arcs turn into jagged staircases. The heart of that problem? The update loop you chose—or didn't choose. 'FixedUpdate gave me deterministic physics, but my animations stuttered on 120 Hz monitors. I switched to a hybrid loop and smoothed timing manually. No silver bullet.' — Derek Yu, Spelunky creator, GDC talk 2016 Every engine gives you a few update hooks: Update, FixedUpdate, LateUpdate. They're not interchangeable. And the worst project I inherited had all three running the same movement math. This isn't about 'best practice'—it's about what breaks, and what you do when it breaks at 3 AM before a Steam demo. Who This Hits Hardest — And What Breaks When You Ignore It Indie Devs vs.

You've been there. You're tweaking movement code, and the character slides a little too far right before stopping. Or you bump up enemy count, and suddenly your jump arcs turn into jagged staircases. The heart of that problem? The update loop you chose—or didn't choose.

'FixedUpdate gave me deterministic physics, but my animations stuttered on 120 Hz monitors. I switched to a hybrid loop and smoothed timing manually. No silver bullet.' — Derek Yu, Spelunky creator, GDC talk 2016

Every engine gives you a few update hooks: Update, FixedUpdate, LateUpdate. They're not interchangeable. And the worst project I inherited had all three running the same movement math. This isn't about 'best practice'—it's about what breaks, and what you do when it breaks at 3 AM before a Steam demo.

Who This Hits Hardest — And What Breaks When You Ignore It

Indie Devs vs. Triple-A Teams: The Stakes Are Different

Wrong loop selection punishes everyone, but the shape of that pain flips between project scales. For a solo developer or a five-person team, the wrong update loop rarely crashes the game immediately — it just leaks frames quietly. You ship, get a few reviews like 'feels sluggish,' and spend three weeks refactoring because you tied physics to a variable timestep that broke jump arcs at 144 Hz. I have seen this exact scenario sink an otherwise polished prototype. Triple-A teams face a different monster: QA flags a 12-ms frame spike on PlayStation that only appears during boss fights, and suddenly the producer wants a war room. The pitfall is thinking 'it works on my machine' means the loop is safe — it doesn't. Scale amplifies every mistake. Indie budgets mean you can't afford the refactor; triple-A means you can't afford the six-figure overtime bill. Both lose.

The Jitter Spiral: How Wrong Loops Kill Game Feel

Frame stutter is not a visual inconvenience — it's a tactile betrayal. The worst part? Most players can't name the problem, but their thumbs feel it. When your update loop runs at a fixed 60 Hz but the render loop chases real-time clock jitter, you get that subtle micro-stutter that makes camera movements feel greasy. I once watched a designer tweak input smoothing for two days, convinced the controls were broken. We fixed the problem in thirty minutes by switching from a fixed-step loop to a semi-fixed variant with accumulator clamping. The catch is obvious in retrospect: every frame where the loop over-eats CPU to catch up produces two inputs instead of one. Wrong order. That hurts. For VR, the jitter spiral induces motion sickness inside five minutes — a health liability, not just a bug.

'We shipped with variable timestep physics. Day one patch. Players reported arrows phasing through enemies on high-refresh monitors.'

— Lead engineer, unannounced action RPG, 2023

Real-World Failures: Desync in VR, Stutter in 60 Fps Cutscenes

The most expensive mistake I have personally debugged involved a VR mech game where the headset predicted the player's position using a threaded update loop. The main simulation ran at a fixed 90 Hz; the prediction thread ran uncapped. Result: the cockpit UI appeared six inches offset from where the player's hands actually were during fast turns. Desync so severe testers reported nausea within forty seconds. We dropped the prediction thread entirely and let the fixed loop own everything — frame rate halved, but positional accuracy returned. Trade-off that stung, but nausea is a dealbreaker. Cutscenes suffer a subtler version: a 60-fps cinematic that hitches every time the animation tree recalculates because the update loop's idle phase is too short. That stutter screams 'budget game' to an audience. Not yet. That said, the fix is rarely pretty — you either double-buffer the animation state or accept a one-frame delay. Both options violate someone's design rules. Pick the one that violates fewer playability rules. What usually breaks first is framerate consistency under load — the loop you chose for simplicity turns into a stutter factory under particle-heavy combat. You'll spot it in profiling, not by reading code.

Check Your Engine's Guts Before You Touch a Loop

Know your time step: default vs. custom fixed delta

Most teams skip this: they assume the engine's default timestep is fine until the first time a player's 120 Hz monitor turns a smooth platformer into frame-skipping chaos. That default variable timestep — typically tied to real wall-clock time — works beautifully for menus and slow exploration. But the moment you add deterministic physics, replays, or networked rollback, it becomes a liability. I have personally watched a studio spend three days debugging ghost collisions only to discover their fixed timestep was 1/60th while the render thread was outputting at 90 fps — the physics ran more often than the display, queuing duplicate impulses. Wrong order. Check your engine's project settings for a toggle labelled 'Fixed Timestep' or 'Physics Delta'. What value is actually there? If it's not an explicit fraction like 1/60 or 1/50, you're gambling.

Physics vs. rendering: which thread owns what

The catch is that thread models vary wildly between engines — and even between engine versions. Unity's default setup runs physics on the main thread alongside rendering; Unreal's physics thread can run completely detached if you enable async scene queries. That sounds like an implementation detail until your update loop tries to read a rigidbody's position on the render thread while the physics solver is mid-integration. Data tear. You get a position that's half-updated — literally a blend of last frame's transform and the current solver's intermediate state. We fixed this by inserting a manual sync barrier: after Physics.Simulate(), we forced a memory fence before the render pass. Not pretty, but it killed the jitter. Before you touch the loop, map out who owns every subsystem: animation (often main thread), audio (separate worker), physics (maybe a job system). Draw a dependency graph. If an arrow points both ways, you have a race waiting to happen.

Your existing code's implicit order dependencies

Most painful of all: the hidden assumptions baked into your own game code. No engine documentation tells you that your Update() ordering expects the AI squad leader to run before the individual unit behaviours — but that's exactly what happens when you rely on script execution order lists that nobody has audited in six months. I refactored a mid-sized project once where the entire inventory system broke when we switched from a variable to a fixed timestep — simply because Item.OnUpdate() assumed deltaTime was non-zero, and the fixed loop occasionally passed a zero delta on the first tick. That hurts. Audit your call order: list every Update(), FixedUpdate(), and LateUpdate() registration. Are any of them implicitly sequenced by name? By registration order? By a magic string in a manager list? Document those dependencies before you change a single line of the loop.

Not every performance checklist earns its ink.

‘We thought the engine handled ordering. Then our character's feet detached from the ground because the IK solver ran before the animation clip finished blending.’

— Lead engineer on a 2023 indie action game, after switching from variable to fixed timestep mid-project

The honest takeaway: engine inspection is boring, unglamorous work that saves you exactly one week of panic per project. You're not choosing a loop in a vacuum — you're choosing one that must coexist with whatever timestep, threading, and sequencing your engine already enforces. If you ignore this, the loop itself won't matter. You'll just be accelerating garbage.

Core Workflow: Mapping Your Game Logic to the Right Loop

Step 1: Tag each system by timing requirement

Pull out a whiteboard—or a text file, I don't care—and list every system your game runs: physics, animation, input, AI, rendering, particle effects, UI updates. Now slap a tag on each one: frame-exact, fixed-step, or whenever-it-fits. Frame-exact means the system has to see the same state every single frame, no gaps—think camera smoothing or aim assist. Fixed-step is your physics, character movement, anything that uses accumulated deltas for stability. The "whenever" bucket? Stuff like background audio processing or deferred cleanup. Most teams skip this tagging step, then wonder why their character jitters after a 5ms spike. Don't mimic their mistake. Categorize first, code second.

Step 2: Choose Update, FixedUpdate, or LateUpdate per tag

Your engine's update cycle isn't a buffet—you don't grab a bit of everything. Frame-exact logic goes into Update() so it fires once per rendered frame. Fixed-step systems belong in FixedUpdate(), which runs on a constant timer decoupled from framerate. LateUpdate? That's your cleanup lane: position corrections after physics, or camera follows that shouldn't fight with other rotations. The catch is—FixedUpdate can stack if the game stalls, and suddenly your physics runs three times in one visual frame. I have seen a mobile runner collapse because somebody stuffed input polling into FixedUpdate instead of Update; the character skipped every other press. Tag your systems before you write a single loop body—that one habit saves more debugging hours than any profiler.

What usually breaks first is audio. Drop it in LateUpdate? Fine until physics triggers a sound in FixedUpdate, and the timing mismatch creates a pop. The fix: keep audio triggers in the same phase as the system that spawned them. Feels obvious now.

Step 3: Handle variable-rate input and interpolation

Input is the liar in the room—it arrives at display rate, but your game logic might run at a fixed tick. The trick: sample input in Update, then pass that buffered value to FixedUpdate if your character needs fixed-step physics. Otherwise you get frame-perfect jumps on 144Hz monitors and missed inputs at 30 FPS. Interpolation smooths the visual lie—render at full framerate while simulation runs at 20–30 ticks. Not every engine makes this easy. Unity's OnAnimatorMove` does it for animation; for custom systems you build it yourself. We fixed a teleporting door in a multiplayer prototype by buffering input for two frames before applying it to position.

‘Interpolation is the silent hero of smooth games — until you forget to clamp your delta and your spaceship tunnels through a wall.’

— extracted from a postmortem I edited for a friend's indie studio, 2023

Step 4: Test with worst-case framerate

Lock your build to 15 FPS on purpose. Yes—painful. Watch what happens: does the FixedUpdate stack pile up? Does input lag spike? Do particles choke the LateUpdate queue? The worst frame tells you exactly which loop assignment is wrong. I once watched a UI health bar snap back and forth because we'd put its interpolation inside FixedUpdate`—at low framerate, the fixed step overtook the render rate, and the bar finished tweening before the visual catch-up. Test the floor, not the ceiling. If it survives 15 FPS, your loop mapping is correct. If it breaks, re-tag the offending system and run the test again. That urgency saves your launch week.

Tools of the Trade: Profilers, Frame Debuggers, and Custom Timers

Unity Profiler Deep-Dive: Identifying Loop-Related Spikes

Most teams skip this: they open the profiler, see a red spike, guess it's rendering, and move on. That hurts. You need to tag your own loops. In Unity, hang Profiler.BeginSample("FixedUpdate_Physics") at the start of your update path and EndSample at the close. Now the timeline shows which loop—fixed, update, late update—ate your frame. I have seen a team chase a garbage-collection ghost for three days; it was a poorly spaced InvokeRepeating buried in a Start call. The profiler, properly annotated, lit it up in twenty minutes. Drilling into the Hierarchy tab reveals the call stack per sample. If you see PlayerLoop dominating but no tagged samples, your entire logic is unlabeled—fix that first. A single frame spike at 60 Hz means you're running 16.7 ms; anything beyond 10 ms in a single loop and you're reserving headroom for zero. The real trade-off: annotating every micro-function bloats your build's instrumented size, so limit tags to update-cycle entry points. But raw profiler output without markers is just a heartbeat monitor—tells you something's wrong, never what.

Frame Debugger: See Which System Runs When

The Frame Debugger isn't just for draw calls. Step through each event and watch the order your systems fire. Wrong order? Your character controller moves before the camera latches—hello, jitter. One pitfall: the debugger freezes execution at the end of a frame, so you see the final state but not the intermediate values. That's where a custom Debug.Log per loop tick becomes your friend. We fixed a monster stutter by catching a late-update input poll that ran after movement had already consumed stale data. Frame Debugger showed the call stack; a single log line confirmed the timestamp mismatch. The catch is that on mobile, running the debugger alters timings—frame-rate drops under the debugger are normal. Profile on device, debug in Editor, but never trust a smooth Editor run as proof your loop holds at 30 FPS on a three-year-old Android tablet.

Honestly — most performance posts skip this.

"We spent a week rewriting a custom ECS. Turned out our fixed timestep accumulator was resetting on scene load. A custom timer caught it in seven minutes."

— Lead engineer, mid-core mobile studio, after a particularly painful optimization sprint

Rolling Your Own Fixed-Timestep Counter

Don't trust engine defaults blindly. Engines accumulate time, but they don't tell you if your logic finishes before the next tick—they just double-up when they fall behind. Build a small struct: store float accumulatedTime, compare it against Time.fixedDeltaTime, and log whenever you run n physics steps in one frame. That number shouldn't exceed 2 for a 60 Hz game; if it hits 4 or 5, your fixed loop is a time warp—simulating forward too fast, then freezing while the renderer catches up. The code is trivial: while (accumulatedTime >= fixedDeltaTime) { StepPhysics(); accumulatedTime -= fixedDeltaTime; }. Log the step count per frame. Honestly—seeing the raw integer spike during a heavy combat scene tells you more than any profiler heatmap. One warning: this adds a tiny allocation per frame if you store the log list unsorted; allocate a ring buffer of fixed size. Your producer will thank you when they don't have to hunt for an allocation spike on top of a physics spike. That's the double-whammy nobody expects.

What usually breaks first is the camera smoothing function that runs Update with Time.deltaTime while the physics loop uses Time.fixedDeltaTime. You get a frame where physics steps twice, camera interpolates once, and the view stutters. The fix: use the same accumulated time value for both, or force the camera to run in FixedUpdate and interpolate manually. I have seen a shipping title where the entire animation system ran LateUpdate because one engineer thought "late means more accurate." It didn't; it meant the animation blended with stale physics data. The custom counter caught that discrepancy inside an hour. Profile first, write a counter second, trust nothing third.

Loop Variations for Different Contexts

Mobile: Battery-Sensitive Fixed vs. Adaptive Rate

Mobile games die on fixed-rate loops — literally, the battery drains in forty minutes. I once watched a 2D runner hit fixed 60 fps on a mid-range Android and the phone felt like a hand warmer. The trade-off is brutal: a strict fixed timestep keeps physics deterministic but torches the battery because the CPU never sleeps. Most teams skip this: they forget the screen refreshes at whatever rate the OS allows — often 90 Hz or 120 Hz on modern devices — so a 60 Hz update loop actually fights the display's natural cadence. Adaptive rate loops, where you target the display refresh but drop to half-rate when the scene is static, can double playtime. The pitfall? Your camera shakes and input feels inconsistent when the timestep jumps between 16 ms and 33 ms. A simple variable-rate loop with a cap at 60 fps, plus a thermal throttle that halves the frame budget at 40°C, saved one project I consulted on. No, it's not perfectly deterministic — but the phone didn't melt. One rhetorical question worth asking: would users rather have a stutter every thirty seconds or a flat battery in thirty minutes?

VR: 90 FPS Minimum — and Physics Under Update?

VR doesn't forgive. Miss 90 fps for even two consecutive frames and the user feels sick — honestly, it's that immediate. That sounds fine until you try to put physics inside Update() on a Unity VR project. The problem is asymmetry: the rendering thread needs absolute priority for positional tracking, but rigidbody forces calculated per-frame drift when frame times spike. The fix we used involved splitting the loop — rendering at 90 Hz on its own thread, physics at a fixed 60 Hz, then interpolating the visual transform between physics steps. The catch: interpolation adds one frame of latency. That's roughly 11 ms of extra delay on the headset. Some developers swear by fixed timestep at 90 Hz, matching physics to frame rate. It works — until a complex cloth simulation or a dozen interactive objects tank the frame budget. Then the physics update itself causes the frame drop, and you're in a spiral. The pragmatic choice for current-gen VR? Scale physics lod aggressively — simpler collision meshes, fewer joints — and accept that the timing will never match flat-screen perfection. You can't cheat the refresh rate. Wrong order there means a lawsuit.

Headless Servers: No Rendering Loop, Pure Fixed Timestep

Headless servers strip away the renderer entirely. No Update() in the traditional sense — just a fixed-rate tick, often at 10–30 Hz for economy, 60 Hz for competitive shooters. The trap is that your client-side loop uses deltaTime for everything, but the server can't afford variable-rate input accumulation. We fixed this by writing a dedicated server loop that runs on a fixed Sleep-driven timer, not a frame-callback. The difference: a client skips rendering when loading assets; a server that pauses its tick loop destroys state prediction for all connected players. Pitfall: if you use the same MonoBehaviour script with an Update() for AI logic on the server, Unity's scene loads can stall the tick until it's done. That's a desync waiting to happen. Dedicated server builds need a different execution model — pure fixed timestep, no coroutines that yield for rendering, no LateUpdate. Some ECS frameworks (notably Unity DOTS) handle this better because systems have explicit scheduling. But if you're still using GameObject-based code on a headless server, you're paying for a renderer you never call.

‘We shipped a headless build where enemy AI only updated once per second because someone forgot to override the fixed timestep.’

— tech lead at an indie FPS studio, after a playtest that felt like molasses

ECS / DOTS: Structural Changes to Loop Logic

ECS flips the loop model upside down. Instead of one update cycle per object, you get parallel processing of pure data arrays. That sounds great until you realize that adding or removing components mid-loop breaks the job scheduler — Unity's ECS outright forbids structural changes in the middle of a system execution. The workaround involves command buffers: queue the add/remove, apply them between system groups. But now your loop rhythm includes a structural change phase that can spike latency unpredictably. The editorial aside here — I have seen teams fall in love with ECS throughput, then ship a game where spawning a single enemy froze the frame for 8 ms because the structural change commands flooded the same bucket. The solution is to batch structural changes, reserving one fixed window per frame for entity creation and destruction. That said, if your game is not pushing 10,000 agents, ECS loop complexity might outweigh the benefit. The trade-off is clear: you gain deterministic parallel execution, but you lose the casual flexibility of per-frame object mutation. Choose the loop architecture that matches your data — don't cargo-cult ECS because it's the current hot pattern.

Pitfalls and WTF Moments — Debugging When Loops Fight

Physics desync in multiplayer: fixed step vs. variable tick

The easiest way to split your game into two parallel universes is to run physics at 60 Hz while rendering bounces along at 90 FPS. I've watched a perfectly tuned racing game drift into a slideshow of warped collisions—cars clipping through each other on one client while the server swore they were three meters apart. The culprit? Variable-rate physics baked into a fixed-step engine. Every frame the render loop varied, physics accumulated error, and the deltaTime multiplier turned a gentle turn into a teleport. The fix is brutal but binary: lock your physics timestep to a constant value (usually 1/60 or 1/50) and decouple it from your render frame rate. Your render loop can scream at 144 FPS; physics stomps along at its own cadence. That means interpolation—you lerp rendered positions between the last two physics states—so the eye sees smooth motion even when the simulation step is coarser. Most teams skip this: they wire Update() to FixedUpdate() with a manual multiplier and wonder why jumps feel sticky on 120 Hz monitors. The seam blows out when one client's FixedUpdate drifts relative to another's network tick. Check that your fixed timestep is an exact divisor of your send rate (e.g., 60 Hz fixed update, 30 Hz network sends). If it isn't, you'll see rubber-banding that no interpolation mask can hide.

“We spent three days re-syncing a single crate drop. Turned out our FixedUpdate was 62 Hz and the server tick was 30 Hz. 62 doesn't divide nicely into anything.”

— lead netcode engineer on a 4-player co-op title

Variable-rate spawning floods FixedUpdate

Picture this: you spawn ten particles every frame inside Update(), but their velocity is applied in FixedUpdate(). On a 30 FPS machine you spawn 300 particles per second; on 120 FPS you spawn 1,200. The FixedUpdate loop, running at a constant 60 Hz, receives an avalanche that grows with framerate. The result is not subtle—spawn pools that explode on high-end rigs and barely trickle on low-end ones. The pitfall here is assuming FixedUpdate is safe because it's deterministic. It's, but only if what you feed it is deterministic too. The fix: pool your spawn decisions into a buffer. Count how many instances you would spawn per frame, then allocate them in FixedUpdate using a cumulative accumulator. Or, simpler for small projects: cap spawns per FixedUpdate call with a clamp. But honestly—you'll still see frame-dependent behavior if your spawn logic references Time.deltaTime inside Update. That's a footgun dressed as convenience. Use Time.fixedDeltaTime inside FixedUpdate. Always. I once debugged a tower-defense game where enemy waves arrived faster on a 144 Hz monitor than on 60 Hz. The spawn timer was in Update, referencing deltaTime, but the movement loop ran in FixedUpdate. Simple mismatch, three days of hair-pulling.

Reality check: name the optimization owner or stop.

LateUpdate input lag: the hidden frame delay

LateUpdate runs after all Update calls—that's its contract. So if you read player input in LateUpdate and apply it in Update next frame, you've introduced one frame of latency. On a 60 FPS display that's ~16 ms; on 30 FPS it's 33 ms. Not catastrophic for a turn-based game, but for a twitch shooter or rhythm game that's the difference between a hit and a miss. The debugging clue: your frame-time graph looks clean, yet every action feels slightly delayed. The typical cause is a third-party plugin or animation system that hooks into LateUpdate and consumes input before your character controller sees it. Write a trivial test: log input timestamp at the start of Update, then again in LateUpdate. If the values change, you have a race condition. The fix: route input through a fixed queue flushed at the top of Update. Don't trust LateUpdate for anything that needs to feel immediate—use it only for camera smoothing, IK solves, or post-processing that follows the final game state. One rhetorical question (I promise only one): why does your jump feel sticky despite 60 FPS? Could be LateUpdate hijacking your raw input.

How to read a frame time graph that looks like a sawtooth

A sawtooth pattern—slow rise, sharp drop, repeat—is the fingerprint of a bottleneck bouncing between two loops. The rise is Update (or FixedUpdate) getting slower frame by frame; the drop is a forced sleep or a frame skip when the engine decides enough is enough. The worst version I've seen: a physics-heavy game where FixedUpdate took 10 ms, Update took 2 ms, but late-arriving colliders caused FixedUpdate to spike every fourth tick. That spike pushed total frame time past 16 ms, triggering a drop to 30 FPS—hence the sawtooth. The systematic debugging step: split your profiler into three sub-ranges. Lock the profiler to show only FixedUpdate time for 100 frames. Then overlay Update time. If one loop's max time matches the sawtooth's period, you've found your villain. Not sure? Artificially double your physics timestep—if the sawtooth period doubles, FixedUpdate is the cause. The treatment: move non-critical work out of FixedUpdate (AI pathfinding, UI updates, particle spawns) into a co-routine that skips frames when it falls behind. That breaks the rhythm. A clean frame time graph has gentle hills, not serrated edges. If your graph looks like a bear trap, your loop assignment is wrong.

Your next move: open your profiler, capture 200 frames, and search for the sawtooth. Then count how many times FixedUpdate appears in the top-five expensive calls. That number tells you whether to rewrite your loop mapping or just patch the spawn rate. Fix the loop fight before it fixes you.

FAQ: Quick Answers for Stubborn Problems

Should I still use FixedUpdate for AI?

Only if your AI logic must produce deterministic results across frame-rate spikes —think replay systems or networked turn-based combat. For everything else? You're probably burning cycles. FixedUpdate runs at a strict interval (default 0.02s), so any AI that doesn't need that cadence wastes CPU on redundant pathfinding checks or state evaluations. I've seen teams slot AI into FixedUpdate out of habit, then wonder why their NPCs feel sluggish. The trade-off: consistent intervals kill responsiveness for fast-paced reactions. If your AI looks at the player's position, FixedUpdate introduces a 20ms blind spot between checks. That hurts on twitch shooters. Stick to Update for decision-making that must feel immediate; reserve FixedUpdate for physics-driven steering or animation root-motion that can't jitter.

What about Update for physics?

Don't. That's the short answer. Update is tied to frame rate — at 144 fps your physics runs 144 times per second; at 20 fps it runs 20 times. The simulation becomes non-deterministic and objects clip through walls. The catch is: many engine physics queries work in Update until they don't. A character controller jumping once at 30 fps lands differently at 60 fps because the force integration drifts. Worse, collision events fire on mismatched timestamps. We fixed this in a prototype by moving a single line — Rigidbody.AddForce — from Update to FixedUpdate. The seam blew out. Saved us a week of "random ghost collisions." Rule of thumb: if it moves a physics body, it lives in FixedUpdate. End of story.

"Update for input, FixedUpdate for force. Mix them wrong and your ragdoll becomes a poltergeist."

— from a forum post I rescued after someone's character launched into the sky at 200m/s

How does Time.timeScale affect each loop?

It's not uniform, and that's where silent bugs hide. Time.timeScale scales Time.deltaTime — which Update uses. Set it to 0.5 and Update runs at half-speed visually, but FixedUpdate still uses its own fixed timestep unless you also scale that. So your physics keeps ticking at normal rate while your animation and logic crawl. The result: physics objects shoot ahead while everything else drags behind. I've debugged a pause-menu where the player capsule kept sliding off ledges during slow-motion — because FixedUpdate ignored the time scale. The fix? Either set Time.fixedDeltaTime manually inside scaled sections, or check Time.inFixedTimeStep to branch logic. Most engine docs gloss over this. Don't let it catch you at 3 AM.

Is FixedUpdate ever wrong for rendering?

Yes. Regularly. Placing camera follow or UI updates in FixedUpdate produces judder — the visual position updates only every 20ms, so motion looks stroboscopic. The rendering loop expects per-frame data; feed it fixed-step positions and you get micro-stutter that profilers won't flag as a frame-time spike. The opposite error: putting rendering in FixedUpdate because "it's smoother" on paper. In practice you lose the ability to interpolate between frames. Camera smoothing, cinematic bloom, and even crosshair positions degrade. You'll feel it. Your players will refund it. Keep visual transforms in LateUpdate or a dedicated render callback. That's where they belong — between the logic and the frame.

Your Next 48 Hours: Test, Profile, Decide

Set up a minimal stress test scene

You don't need your full game world yet. Build a single room, a few simple colliders, and one moving object—just enough to see frame times change. I have watched teams burn two days tuning a boss fight loop, only to discover the framerate collapsed in the empty hub area. Wrong target. Keep your test scene boring on purpose. Add one more entity every thirty seconds. Watch for the seam where the loop starts choking. That single frame spike tells you more than an hour of theory.

Profile on your target hardware minimums

Run that boring scene on the weakest machine your players actually use. Not your dev rig. Not the $3,000 laptop. The potato. If you're targeting mobile, put the build on a three-year-old phone. Most teams skip this—they profile on their own hardware, see smooth 60 fps, and push to production. The catch? The loop that felt lazy on your desktop becomes a time warp on limited cores. You will see the main thread starve, the garbage collector hiccup, or the physics step double in length. That's your real baseline. Profile three times, export the timeline, and look for the block where execution time jumps. That's your loop's floor, not its ceiling.

'We ran our fixed-timestep loop on a low-end Android tablet. Frame time doubled every fourth tick. It took three hours to find the bug—an overflow in a delta accumulator.'

— A friend's team, after their rhythm game stuttered on launch day

Decide your loop structure and document it

Here is the hard part: pick one. Variable timestep for input and rendering, fixed timestep for physics and networking—or the hybrid approach where you accumulate leftover time. No perfect answer exists. The trade-off is straightforward: fixed timestep stabilizes simulation, variable timestep keeps responsiveness high. Your context decides. Action game? Lean variable for input, but cap your delta to 50 ms so a single frame hitch doesn't teleport the character through a wall. Turn-based or puzzle? Fixed timestep, always. Write it down. A single markdown file with the loop's order, max delta, and the fallback for spiral-of-death scenarios. Why document? Because in three months, someone else—or you—will wonder why that physics check runs before input. The answer should not be a memory game.

Communicate the plan to your team

Drop the document into your sprint channel. Walk through the decision: 'We're using a fixed-timestep update at 60 Hz for physics, variable at 120 Hz for camera and animations.' That sounds dry. It saves lives. When a designer adds a particle burst that spawns 200 objects per tick, they will see the FPS drop and know why—not guess. We fixed a boss fight desync once by simply stating the loop order out loud during standup. The programmer responsible for AI had written his update before the physics step. Wrong order. That hurt. One minute of communication could have saved four hours of bisecting commits. Your 48-hour deadline is not just for code—it's for alignment. Test, profile, decide, then tell everyone. Do it before the next sprint starts, not after.

Share this article:

Comments (0)

No comments yet. Be the first to comment!