Up to date

This page is up to date for Godot 4.2. If you still find outdated information, please open an issue.

Introducción a la física

En el desarrollo de videojuegos, muchas veces necesitarás saber cuando dos objetos se superponen o entran en contacto. Esto es conocido como detección de colisiones. Cuando una colisión es detectada, normalmente es deseable que algo suceda, esto es llamado respuesta a la colisión.

Godot ofrece varios objetos de colisión en 2D y 3D para proveer tanto detección como respuesta a una colisión. Tratar de decidir cuál utilizar para tu proyecto puede ser confuso. Puedes evitar problemas y simplificar el desarrollo si entiendes como funciona cada uno y cuáles son sus ventajas y desventajas.

En esta guía aprenderás:

  • Los cuatro tipos de objetos de colisión en Godot

  • Cómo funciona cada tipo de objeto de colisión

  • Cuándo y por qué seleccionar un tipo en lugar de otro

Nota

Los ejemplos de este documento utilizarán objetos 2D. Cada objeto 2D, físico y forma de colisión (CollisionShape) tiene su equivalente 3D y en la mayoría de los casos funcionan del mismo modo.

Objetos de colisión

Godot offers four kinds of collision objects which all extend CollisionObject2D. The last three listed below are physics bodies and additionally extend PhysicsBody2D.

  • Area2D

    Los nodos Area2D proveen detección e Influencia. Estos pueden detectar cuando objetos se superponen a ellos y emitir señales cuando cuerpos físicos entran o salen. Un Area2D también puede usarse para superponer o reemplazar propiedades físicas como gravedad o resistencia en una zona definida.

  • StaticBody2D

    Un cuerpo estático es uno que no es movido por el motor de física. Participa en detección de colisiones pero no se mueve en respuesta a la colisión. Son normalmente utilizados para objetos que son parte del entorno y no necesitan ningún comportamiento dinámico.

  • RigidBody2D

    Este es el nodo que implementa simulación de física 2D. Tú no controlas un RigidBody2D directamente, en lugar de ello, aplicas fuerzas (gravedad, impulsos, etc.) y el motor de física calcula el movimiento resultante. Para más información sobre el uso cuerpos rígidos, ver aquí.

  • CharacterBody2D

    Es un cuerpo que provee detección de colisiones, pero no física. Todo movimiento y respuestas a colisiones debe ser implementado en código.

Material de físicas

Static bodies and rigid bodies can be configured to use a PhysicsMaterial. This allows adjusting the friction and bounce of an object, and set if it's absorbent and/or rough.

Figuras de Colisión

Un cuerpo físico puede contener cuaquier número de objetos Shape2D como hijos. Estas figuras o shapes, son usadas para definir los límites de colisión y detectar contactos con otros objetos físicos.

Nota

Para detectar colisiones, al menos un recurso derivado de Shape2d debe ser asignado al objeto.

El modo más común de asignar un "shape" es agregando un CollisionShape2D o CollisionPolygon2D como hijo del objeto. Esos nodos permiten dibujar la forma directamente en el espacio de trabajo.

Importante

Ten cuidado de nunca escalar las formas de colisión (CollisionShape) en el editor. La propiedad "scale" en el inspector debe mantenerse (1,1). Cuando se cambia el tamaño del nodo correspondiente a la figura de colisión, siempre se deben utilizar los controles para cambiar tamaño, no los controles de Node2D para escala. Escalar la forma de colisión puede resultar en un comportamiento no esperado.

../../_images/player_coll_shape.png

Llamada interna para procesamiento de física

The physics engine runs at a fixed rate (a default of 60 iterations per second). This rate is typically different from the frame rate which fluctuates based on what is rendered and available resources.

It is important that all physics related code runs at this fixed rate. Therefore Godot differentiates between physics and idle processing. Code that runs each frame is called idle processing and code that runs on each physics tick is called physics processing. Godot provides two different callbacks, one for each of those processing rates.

The physics callback, Node._physics_process(), is called before each physics step. Any code that needs to access a body's properties should be run in here. This method will be passed a delta parameter, which is a floating-point number equal to the time passed in seconds since the last step. When using the default 60 Hz physics update rate, it will typically be equal to 0.01666... (but not always, see below).

Nota

Se recomienda utilizar siempre el parámetro delta cuando sea relevante en tus cálculos de físicas, para que así el juego se comporte correctamente si cambias la tasa de actualización de las físicas o si el dispositivo del jugador no puede mantenerlo.

Capas y Máscaras de Colisión

One of the most powerful, but frequently misunderstood, collision features is the collision layer system. This system allows you to build up complex interactions between a variety of objects. The key concepts are layers and masks. Each CollisionObject2D has 32 different physics layers it can interact with.

Veamos cada una de las propiedades:

  • collision_layer

    Esta describe las capas en las que el objeto aparece. Por defecto, todos los cuerpos están en la capa 1.

  • collision_mask

    Esto describe en qué capas el cuerpo buscará colisiones. Si un objeto no está en una de las máscaras, el cuerpo lo ignorará. Por defectos, todos los cuerpos buscan la capa 1.

Estas propiedades pueden ser configuradas por código o editadas mediante el Inspector.

Mantener un seguimiento de cada capa usada puede ser complicado, así que puede ser útil asignar nombres a cada una de las capas utilizadas. Los nombres pueden ser asignados en Ajustes del Proyecto -> Layer Names.

../../_images/physics_layer_names.png

Ejemplo de GUI

Tienes 4 tipos de nodos en tu juego: Walls (Paredes), Player (Jugador), Enemy (Enemigo) y Coin (Monedas). Player y Enemy deben colisionar con Wall. El nodo Player debe detectar colisiones tanto de Enemy como de Coin, pero Enemy y coin deben ignorarse mutuamente.

Comienza nombrando las capas 1 a 4 "walls", "player", "enemies", y "coins" y coloca cada nodo en su respectiva propiedad "layer". Luego asigna cada propiedad "mask" seleccionando las capas con las que debe interactuar. Por ejemplo Player debería verse así:

../../_images/player_collision_layers.png ../../_images/player_collision_mask.png

Ejemplo de código

In function calls, layers are specified as a bitmask. Where a function enables all layers by default, the layer mask will be given as 0xffffffff. Your code can use binary, hexadecimal, or decimal notation for layer masks, depending on your preference.

El código equivalente del ejemplo anterior en el que se habilitaron las capas 1, 3 y 4 sería el siguiente:

# Example: Setting mask value for enabling layers 1, 3 and 4

# Binary - set the bit corresponding to the layers you want to enable (1, 3, and 4) to 1, set all other bits to 0.
# Note: Layer 32 is the first bit, layer 1 is the last. The mask for layers 4,3 and 1 is therefore
0b00000000_00000000_00000000_00001101
# (This can be shortened to 0b1101)

# Hexadecimal equivalent (1101 binary converted to hexadecimal)
0x000d
# (This value can be shortened to 0xd)

# Decimal - Add the results of 2 to the power of (layer to be enabled - 1).
# (2^(1-1)) + (2^(3-1)) + (2^(4-1)) = 1 + 4 + 8 = 13
pow(2, 1-1) + pow(2, 3-1) + pow(2, 4-1)

Area2D

Los nodos de área (Area) proporcionan detección y influencia. Pueden detectar cuando los objetos se superponen y emitir señales cuando los cuerpos entran o salen. Las áreas también pueden utilizarse para anular propiedades físicas, como la gravedad o la amortiguación, en un área definida.

Estos son los tres tipos de usos principales de Area2D:

  • Sobreescribir los parámetros físicos (como la gravedad) en una región determinada.

  • Detectar cuando otros cuerpos entran o salen de una región o qué cuerpos están actualmente en una región.

  • Revisar la superposición con otras áreas.

Por defecto, los nodos tipo Area también reciben entradas (input) de ratón y táctiles.

StaticBody2D

Un cuerpo estático (static body) es uno que no es movido por el motor de física. Participa en detección de colisiones pero no se mueve en respuesta a ellas. Sin embargo, pueden provocar movimientos o rotaciones a un cuerpo que colisiona como si fuesen movidas cuando utilizan sus propiedades constant_linear_velocity (velocidad linear constante) y constant_angular_velocity (velocidad angular constante).

Los nodos StaticBody2D son usados mayormente par aobjetos que son parte del entorno o que no necesitan ningún comportamiento dinámico.

Ejemplos de usos para StaticBody2D:

  • Plataformas (incluyendo plataformas móviles)

  • Cintas transportadoras

  • Paredes y otros obstáculos sólidos

RigidBody2D

Este es el nodo que implementa la simulación de física 2D. No se puede controlar un RigidBody2D directamente. En su lugar, se le aplican fuerzas y el motor de física calcula el movimiento resultante, incluyendo colisiones con otros cuerpos, y respuestas a colisiones, como rebotes, rotación, etc.

Puedes modificar el comportamiento de un rigid body (cuerpo rígido) mediante propiedades como "Mass" (masa), "Friction"(fricción), o "Bounce" (rebote/elasticidad), estas pueden configurar en el Inspector.

El comportamiento de un cuerpo de este tipo también es afectado por las propiedades del mundo, como las asignadas en Ajustes del Proyecto -> Physics o pro entrar en un Area2D que está reemplazando las propiedades físicas globales.

Cuando un cuerpo rígido está inmóvil y permanece inmóvil por un tiempo, entra en reposo. Un cuerpo en reposo actúa como un cuerpo estático, y sus fuerzas no son calculadas por el motor de física. El cuerpo se activará cuando se ejerzan fuerzas, ya sea por una colisión o mediante código.

Usando RigidBody2D

Uno de los beneficios de utilizar un cuerpo rígido (rigid body) es que mucho de comportamiento se puede obtener "gratis", sin escribir una sola línea de código, si estás haciendo un juego tipo "Angry Birds", con bloques que caen, sólo necesitarás crear nodos RigidBody2D y ajustar sus propiedades. Apilar, hacer caer y rebotar será manejado por el motor de física.

However, if you do wish to have some control over the body, you should take care - altering the position, linear_velocity, or other physics properties of a rigid body can result in unexpected behavior. If you need to alter any of the physics-related properties, you should use the _integrate_forces() callback instead of _physics_process(). In this callback, you have access to the body's PhysicsDirectBodyState2D, which allows for safely changing properties and synchronizing them with the physics engine.

Por ejemplo, este es el código para una nave tipo "Asteroids":

extends RigidBody2D

var thrust = Vector2(0, -250)
var torque = 20000

func _integrate_forces(state):
    if Input.is_action_pressed("ui_up"):
        state.apply_force(thrust.rotated(rotation))
    else:
        state.apply_force(Vector2())
    var rotation_direction = 0
    if Input.is_action_pressed("ui_right"):
        rotation_direction += 1
    if Input.is_action_pressed("ui_left"):
        rotation_direction -= 1
    state.apply_torque(rotation_direction * torque)

Note que no estamos asignando linear_velocity o angular_velocity directamente, en su lugar aplicamos fuerzas (thrust - propulsión - y torque) al cuerpo y dejamos que el motor de física calcule el movimiento resultante.

Nota

Cuando un cuerpo rígido se duerme, la función _integrate_forces no es llamada. Para evitar este comportamiento debemos mantener el cuerpo despierto creando una colisión, aplicando una fuerza sobre él o desactivando la propiedad can_sleep (puede dormirse). Ten en cuenta que esto puede impactar negativamente en el desempeño.

Reporte de contactos

By default, rigid bodies do not keep track of contacts, because this can require a huge amount of memory if many bodies are in the scene. To enable contact reporting, set the max_contacts_reported property to a non-zero value. The contacts can then be obtained via PhysicsDirectBodyState2D.get_contact_count() and related functions.

El monitoreo de contactos mediante señales se puede activar mediante la propiedad contact_monitor. Ver RigidBody2D para conocer la lista de señales disponibles.

CharacterBody2D

CharacterBody2D bodies detect collisions with other bodies, but are not affected by physics properties like gravity or friction. Instead, they must be controlled by the user via code. The physics engine will not move a character body.

When moving a character body, you should not set its position directly. Instead, you use the move_and_collide() or move_and_slide() methods. These methods move the body along a given vector, and it will instantly stop if a collision is detected with another body. After the body has collided, any collision response must be coded manually.

Character collision response

After a collision, you may want the body to bounce, to slide along a wall, or to alter the properties of the object it hit. The way you handle collision response depends on which method you used to move the CharacterBody2D.

move_and_collide

Cuando se utiliza move_and_collide() (mover y colisionar), la función retorna un objeto del tipo KinematicCollision2D, el cual contiene información sobre la colisión y el cuerpo golpeado. Puedes usar esta información para determinar la respuesta.

Por ejemplo, si quieres obtener el punto en el espacio donde ocurrió una colisión:

extends PhysicsBody2D

var velocity = Vector2(250, 250)

func _physics_process(delta):
    var collision_info = move_and_collide(velocity * delta)
    if collision_info:
        var collision_point = collision_info.get_position()

O para rebotar del objeto colisionado:

extends PhysicsBody2D

var velocity = Vector2(250, 250)

func _physics_process(delta):
    var collision_info = move_and_collide(velocity * delta)
    if collision_info:
        velocity = velocity.bounce(collision_info.get_normal())

move_and_slide

Resbalar o "slide" es una respuesta común a colisiones. Imagina un jugador moviéndose a lo largo de las paredes en un juego de vista superior o top-down, o corriendo hacia arriba o hacia abajo en las pendientes de un jeugo de plataformas. Es posible codificar la respuesta para este tipo de colisiones utilizando move_and_collide(), pero move_and_slide() provee un modo conveniente de implementar el efecto de resbalar sobre superficies con mucho menos código.

Advertencia

move_and_slide() incluye automáticamente el "timestep" (paso temporal) en su cálculo, por lo que no se debe multiplicar el vector de velocidad por delta.

Por ejemplo, usa el siguiente código para hacer que un personaje pueda caminar por el piso (incluyendo pendientes) y salte cuando está en el piso:

extends CharacterBody2D

var run_speed = 350
var jump_speed = -1000
var gravity = 2500

func get_input():
    velocity.x = 0
    var right = Input.is_action_pressed('ui_right')
    var left = Input.is_action_pressed('ui_left')
    var jump = Input.is_action_just_pressed('ui_select')

    if is_on_floor() and jump:
        velocity.y = jump_speed
    if right:
        velocity.x += run_speed
    if left:
        velocity.x -= run_speed

func _physics_process(delta):
    velocity.y += gravity * delta
    get_input()
    move_and_slide()

Ver Personaje cinemático (2D) para más detalles sobre el uso de move_and_slide(), incluyendo proyecto de demostración con código detallado.