Videojuegos en Godot: ¿Qué tiene que saber una bala?

📆 12 de diciembre de 2025

Aunque no todos los juegos son shooters, es muy habitual tener que programar el comportamiento de un proyectil, ya sea para usarlo contra los enemigos o para amenazar la seguridad de nuestro jugador.

En Godot, para disparar tenemos que instanciar la escena del proyectil y dejar que siga su camino. Eso implica que la escena tiene que tener una serie de nodos y algo de código que regule su comportamiento.

En cuanto a nodos, lo más básico es añadir un Area2D con su CollisionShape2D y un Sprite2D. Con esto ya tendríamos un dibujo con un área que puede utilizarse para detectar colisiones. Otra variante es utilizar un Node2D, un Sprite2D y un HitBox. El HitBox, que puede ser una escena independiente, contiene, a su vez, un Area2D y una CollisionShape2D. A mí me gusta más emplear esta combinación porque luego permite diferenciar facilmente la detección de los impactos utilizando el HitBox como filtro.

Un nodo que yo añado también es un VisibleOnScreenNotifier2D, que me permite conectar una señal de tipo screen_exited, que puedo utilizar para que el proyectil se autodestruya cuando desaparece de la pantalla, de ese modo no vamos llenando la memoria de balas perdidas.

En cuanto al código, lo más normal es que necesitemos saber el daño que causará el impacto (damage), la velocidad a la que viajará el proyectil (speed) y si la dirección del movimiento será positiva o negativa (direction). Para calcular el movimiento iremos sumando (direction = 1) o restando (direction = -1) de forma continua a su posición.

Luego el código podría quedar algo como así:

Script del nodo principal: bullet.gd

class_name Bullet extend Node2D

var speed: int
var damage: int
var direction: int

@export var screen_notifier: VisibleOnScreenNotifier2D

func _ready() -> void:
    screen_notifer.screen_exited.connect(func():
        await get_tree().create_timer(0.1).timeout
        clean()

func _physics_process(delta: float) -> void:
    global_position.x += speed * direction * delta


func clean() -> void:
    call_deferred("queue_free")

En este script conecto la señal screen_exited del VisibleOnScreenNotifier2D a una función lambda en la que se espera a que pase un tiempo antes de destruir el proyectil. Eso se consigue con await, que permite en este caso esperar a que se emita la señal timeout de un timer creado al vuelo con get_tree().create_timer(0.1).

Script de HitBox: hitbox.gd

class_name HitBox extends Area2D

var damage: int

func _ready() -> void:
    damage = owner.damage

En este caso se asigna el valor damage de Bullet (owner) a la variable damage de HitBox. Así, en el caso de que impacte con un HurtBox, éste puede saber que daño le ha producido.

Aunque este esquema es muy sencillo presenta algunas pegas, por ejemplo, que si la bala choca con un muro, lo atravesará, porque no es un objeto físico. Para eso podemos optar por añadir un Area2D que funcione como detector de muros o, si hemos optado por la opción de que Bullet sea un Area2D en vez de un Node2D, manejar directamente la detección de muros en el script principal.

Puedes compartir el enlace si te gustó: Videojuegos en Godot: ¿Qué tiene que saber una bala?

También puedes dejarme un mensaje:





(Esto funciona gracias a Un-static Forms)