Movimiento 2D y control del jugador con CharacterBody2D

Capítulo 5

Tiempo estimado de lectura: 7 minutos

+ Ejercicio

Concepto: CharacterBody2D, velocidad y física

CharacterBody2D es un nodo pensado para personajes controlados por código. Su idea central es simple: tú calculas una velocidad (un Vector2) en cada frame de física y luego le pides al motor que mueva el cuerpo respetando colisiones mediante move_and_slide(). En 2D, normalmente separas el movimiento en dos ejes: X (horizontal: izquierda/derecha) y Y (vertical: gravedad/salto). La gravedad acelera hacia abajo (aumenta velocity.y), y el salto aplica un impulso hacia arriba (un valor negativo en velocity.y).

En Godot 4, el patrón típico es implementar el movimiento dentro de _physics_process(delta), porque ahí el motor ejecuta la simulación física a un ritmo estable.

Paso a paso: preparar el Input Map (izquierda, derecha, saltar)

1) Crear acciones en Project Settings

Ve a Project > Project Settings > Input Map y crea estas acciones (nombres sugeridos):

  • move_left
  • move_right
  • jump

2) Asignar teclas (y opcionalmente mando)

Asigna al menos:

  • move_left: A y/o flecha izquierda
  • move_right: D y/o flecha derecha
  • jump: Space

Opcional (recomendado para pruebas): agrega entradas de gamepad como Left Stick Left/Right para movimiento y un botón (por ejemplo South) para salto. Mantener el Input Map limpio y consistente te permite cambiar controles sin tocar el código.

Continúa en nuestra aplicación.
  • Escuche el audio con la pantalla apagada.
  • Obtenga un certificado al finalizar.
  • ¡Más de 5000 cursos para que explores!
O continúa leyendo más abajo...
Download App

Descargar la aplicación

3) Verificación rápida desde código

Para comprobar que las acciones existen y responden, puedes imprimir temporalmente:

func _process(_delta):
	if Input.is_action_just_pressed("jump"):
		print("Jump presionado")

Si no imprime, revisa el nombre exacto de la acción (mayúsculas/minúsculas) y que tenga al menos una tecla asignada.

Paso a paso: escena del jugador con CharacterBody2D

1) Estructura mínima de nodos

Crea una escena Player.tscn con:

  • CharacterBody2D (raíz) llamado Player
  • CollisionShape2D como hijo (con un RectangleShape2D o CapsuleShape2D)
  • (Opcional) Sprite2D o AnimatedSprite2D para visualizar

Asegúrate de que el CollisionShape2D tenga tamaño razonable y que el pivote del jugador no quede “enterrado” en el suelo al iniciar.

2) Script base de movimiento

Adjunta un script a Player. Este ejemplo implementa movimiento horizontal, gravedad y salto usando velocity y move_and_slide():

extends CharacterBody2D

@export var speed: float = 220.0
@export var jump_velocity: float = -420.0
@export var gravity: float = 1200.0

func _physics_process(delta: float) -> void:
	# 1) Leer input horizontal (-1 izquierda, +1 derecha)
	var dir := Input.get_axis("move_left", "move_right")

	# 2) Aplicar velocidad horizontal
	velocity.x = dir * speed

	# 3) Gravedad (solo si no estás en el suelo, o siempre si prefieres)
	if not is_on_floor():
		velocity.y += gravity * delta
	else:
		# Opcional: estabiliza al tocar suelo para evitar acumulaciones pequeñas
		if velocity.y > 0:
			velocity.y = 0

	# 4) Salto (solo cuando estás en el suelo)
	if Input.is_action_just_pressed("jump") and is_on_floor():
		velocity.y = jump_velocity

	# 5) Mover con colisiones
	move_and_slide()

Claves del ejemplo: Input.get_axis() devuelve un valor continuo entre -1 y 1 combinando dos acciones; is_on_floor() te permite restringir el salto; la gravedad se integra con delta para que sea estable; y move_and_slide() usa la velocity actual para resolver el movimiento y las colisiones.

Depuración del movimiento: valores, límites y “sensación”

1) Mostrar valores en tiempo real

Para entender qué está pasando, imprime valores clave. Hazlo de forma controlada para no saturar la consola. Por ejemplo, imprime cada cierto tiempo:

var _t := 0.0

func _physics_process(delta):
	_t += delta
	# ... tu lógica de movimiento ...
	if _t >= 0.25:
		_t = 0.0
		print("vel=", velocity, " on_floor=", is_on_floor())
	move_and_slide()

Si velocity.y crece sin límite al caer, es normal (aceleración por gravedad), pero puede sentirse “pesado” o “rápido” según el valor de gravity. Ajusta gravity y jump_velocity en conjunto.

2) Ajuste práctico de parámetros (regla de trabajo)

  • Si el salto es muy bajo: aumenta la magnitud de jump_velocity (más negativo) o reduce gravity.
  • Si el personaje cae demasiado lento: aumenta gravity.
  • Si el movimiento horizontal se siente lento: sube speed.
  • Si “patina” al aterrizar: revisa que estés poniendo velocity.x según input y que tu colisión no tenga formas extrañas; luego considera aceleración/desaceleración (ver mejoras).

3) Límites para evitar valores extremos

En juegos de plataformas, suele ser útil limitar la velocidad vertical máxima para que las caídas no se vuelvan incontrolables:

@export var max_fall_speed: float = 900.0

# Dentro de _physics_process, tras aplicar gravedad:
velocity.y = min(velocity.y, max_fall_speed)

Esto no “rompe” la física del personaje; solo evita que la velocidad crezca indefinidamente en caídas largas.

4) Problemas comunes y cómo detectarlos

SíntomaCausa probableQué revisar
No se mueveAcciones mal nombradas o sin teclasInput Map, nombres exactos en get_axis
No saltais_on_floor() nunca es trueColisión del suelo, capas/máscaras, forma del collider
Se queda pegado en paredesColisión demasiado grande o geometría irregularTamaño del CollisionShape2D, tiles con bordes
Vibra al aterrizarPequeñas correcciones de colisión + velocidad residualForzar velocity.y = 0 al estar en suelo (como en el ejemplo)

Mejoras para un control más pulido

1) Aceleración y desaceleración (en lugar de velocidad instantánea)

Asignar velocity.x = dir * speed es directo, pero puede sentirse “robótico”. Una mejora clásica es acelerar hacia la velocidad objetivo y frenar gradualmente cuando no hay input.

@export var speed: float = 240.0
@export var accel: float = 1400.0
@export var decel: float = 1800.0

func _physics_process(delta):
	var dir := Input.get_axis("move_left", "move_right")
	var target_x := dir * speed

	if dir != 0:
		velocity.x = move_toward(velocity.x, target_x, accel * delta)
	else:
		velocity.x = move_toward(velocity.x, 0.0, decel * delta)

	# gravedad/salto como antes...
	move_and_slide()

move_toward() te da un cambio suave y controlable. Sube accel para respuesta más inmediata; sube decel para frenar más rápido (menos “hielo”).

2) Salto variable (altura según cuánto mantienes el botón)

Un salto variable hace que un toque corto produzca un salto bajo y mantener el botón produzca un salto alto. Una forma común: si el jugador suelta el botón mientras aún sube, recortas la velocidad vertical.

@export var jump_velocity: float = -420.0
@export var jump_cut_multiplier: float = 0.45

func _physics_process(delta):
	# ... aplicar gravedad ...

	if Input.is_action_just_pressed("jump") and is_on_floor():
		velocity.y = jump_velocity

	# Si suelta el salto mientras sube, recorta
	if Input.is_action_just_released("jump") and velocity.y < 0:
		velocity.y *= jump_cut_multiplier

	move_and_slide()

Ajusta jump_cut_multiplier: valores más bajos recortan más (saltos cortos más marcados). Esto mejora mucho la “sensación” sin complicar el código.

3) Límites de velocidad horizontal y control en el aire

Si añades aceleración, puede ser útil limitar la velocidad horizontal y diferenciar control en el aire (air control):

@export var max_speed_x: float = 260.0
@export var air_accel_multiplier: float = 0.6

func _physics_process(delta):
	var dir := Input.get_axis("move_left", "move_right")
	var target_x := dir * max_speed_x
	var a := accel
	if not is_on_floor():
		a *= air_accel_multiplier

	if dir != 0:
		velocity.x = move_toward(velocity.x, target_x, a * delta)
	else:
		velocity.x = move_toward(velocity.x, 0.0, decel * delta)

	velocity.x = clamp(velocity.x, -max_speed_x, max_speed_x)
	move_and_slide()

Reducir la aceleración en el aire evita que el personaje “gire en seco” mientras cae, algo que suele sentirse más natural en plataformas.

Ahora responde el ejercicio sobre el contenido:

¿Cuál es la razón principal para multiplicar la gravedad por delta al actualizar velocity.y en _physics_process?

¡Tienes razón! Felicitaciones, ahora pasa a la página siguiente.

¡Tú error! Inténtalo de nuevo.

Multiplicar por delta integra la gravedad de forma proporcional al tiempo transcurrido, logrando un comportamiento consistente en la simulación física y evitando que el resultado dependa del ritmo de actualización.

Siguiente capítulo

Colisiones, físicas y detección de áreas en Godot 2D

Arrow Right Icon
Portada de libro electrónico gratuitaGodot desde Cero: Crea tu Primer Juego 2D con GDScript
42%

Godot desde Cero: Crea tu Primer Juego 2D con GDScript

Nuevo curso

12 páginas

Descarga la aplicación para obtener una certificación gratuita y escuchar cursos en segundo plano, incluso con la pantalla apagada.