Quick answer: When you combine two input axes (horizontal and vertical) into a Vector2, the resulting diagonal vector has a magnitude of approximately 1.414 instead of 1.0. This means your character moves about 41% faster diagonally unless you normalize the input vector.
Here is how to fix diagonal movement faster than cardinal Godot. Your character moves at a reasonable speed going left, right, up, or down. But the moment a player holds two directional keys at once, the character accelerates noticeably faster along the diagonal. This is one of the most common movement bugs in 2D games built with Godot 4, and it comes down to a single missing function call.
The Symptom
You have a CharacterBody2D with basic movement code. When you press just the right arrow, the character moves at your intended speed. When you press right and up simultaneously, the character moves roughly 41% faster than it should. In a top-down game this is immediately obvious — your character covers more ground diagonally than it does going straight. In a platformer, it might manifest as slightly longer horizontal jumps when holding a direction key during the jump arc.
Players will notice this. Speedrunners will absolutely exploit it. And if your game has any kind of competitive element, it becomes a balance-breaking issue. The root cause is mathematical, and once you understand it, the fix is trivial.
What Causes This
The problem is basic vector math. When you read input for horizontal and vertical axes separately and combine them into a Vector2, a cardinal direction gives you a vector like Vector2(1, 0) or Vector2(0, -1) — both with a magnitude (length) of exactly 1.0. But when you press both right and up, you get Vector2(1, -1), which has a magnitude of approximately 1.414 (the square root of 2).
Here is what the broken code typically looks like:
# Broken: diagonal movement is ~41% faster
extends CharacterBody2D
const SPEED = 200.0
func _physics_process(delta):
var direction = Vector2.ZERO
direction.x = Input.get_axis("move_left", "move_right")
direction.y = Input.get_axis("move_up", "move_down")
velocity = direction * SPEED
move_and_slide()
When direction is Vector2(1, -1), the resulting velocity becomes Vector2(200, -200), which has a total speed of approximately 283 pixels per second instead of 200. You are multiplying a vector that is already longer than 1.0 by your speed constant, so the final speed exceeds what you intended.
The Fix
The simplest and most correct fix is to normalize the input vector before multiplying by speed. Normalization scales the vector so its magnitude is exactly 1.0, regardless of direction. Godot provides two approaches.
Option 1: Use Input.get_vector() — this is the recommended approach in Godot 4 because it handles normalization internally:
# Fixed: Input.get_vector() returns a normalized vector
extends CharacterBody2D
const SPEED = 200.0
func _physics_process(delta):
var direction = Input.get_vector(
"move_left", "move_right",
"move_up", "move_down"
)
velocity = direction * SPEED
move_and_slide()
Option 2: Manually normalize — if you need to build the vector yourself for any reason, call .normalized() on it:
# Fixed: manual normalization
extends CharacterBody2D
const SPEED = 200.0
func _physics_process(delta):
var direction = Vector2.ZERO
direction.x = Input.get_axis("move_left", "move_right")
direction.y = Input.get_axis("move_up", "move_down")
if direction.length() > 0:
direction = direction.normalized()
velocity = direction * SPEED
move_and_slide()
Note the length() > 0 check before normalizing. Calling .normalized() on a zero-length vector returns Vector2(0, 0) in Godot 4, so this check is technically optional, but it makes the intent explicit and avoids any edge case surprises.
Option 3: Use limit_length() for analog sticks — if your game supports gamepad analog sticks and you want players to be able to walk slowly with a partial tilt, normalized() is too aggressive because it snaps any nonzero input to full speed. Instead, use limit_length():
# Fixed: preserves analog magnitude while capping diagonal
extends CharacterBody2D
const SPEED = 200.0
func _physics_process(delta):
var direction = Input.get_vector(
"move_left", "move_right",
"move_up", "move_down"
)
direction = direction.limit_length(1.0)
velocity = direction * SPEED
move_and_slide()
With limit_length(1.0), a half-tilt on the analog stick preserves that partial magnitude (e.g., 0.5), but a full diagonal tilt gets capped at 1.0 instead of overshooting to 1.414.
Why This Works
Normalization transforms any vector into a unit vector — a vector that points in the same direction but has a length of exactly 1.0. When you multiply a unit vector by your speed constant, the resulting velocity magnitude equals your speed constant, regardless of whether the direction is cardinal or diagonal.
Mathematically, Vector2(1, -1).normalized() returns Vector2(0.707, -0.707). Multiplied by 200, that gives Vector2(141.4, -141.4), which has a total magnitude of exactly 200 — matching your cardinal movement speed perfectly.
Input.get_vector() does this normalization internally and also handles deadzone processing for analog input, which is why it is the preferred approach in Godot 4. It replaces the older pattern of reading two separate axes and combining them manually.
"The fastest way to break game feel is inconsistent movement speed. Normalize your vectors, and your players will never notice the math — which is exactly how it should be."
Related Issues
Diagonal speed is just one of several input-related movement bugs that catch Godot developers off guard. If you are working on character movement, you may also want to check these related fixes:
- Input.is_action_just_pressed() triggering multiple times — another common input handling bug where a single press registers more than once depending on where you check it.
- Analog stick drift causing unwanted movement — if your game supports gamepads, improper deadzone configuration can cause ghost movement even when the player is not touching the stick.
- CharacterBody2D snapping to ground after jumping — a related physics issue where floor snapping interferes with jump velocity on the first frame.