Introdução à física

É frequente, no desenvolvimento de jogos, precisar saber quando dois objetos se interceptam ou fazem contato durante o jogo. Isso é conhecido como detecção de colisão. Quando uma colisão é detectada, você tipicamente quer que alguma coisa aconteça. Isso é chamado de resposta à colisão.

Godot oferece uma variedade de objetos de colisão em 2D e 3D para prover tanto detecção de quanto resposta a colisões. Tentar decidir qual usar em seu projeto pode ser confuso. Você pode evitar problemas e simplificar o desenvolvimento se entender como cada um funciona e quais são suas vantagens e desvantagens.

In this guide, you will learn:

  • Os quatro tipos de objeto de colisão do Godot
  • Como cada objeto de colisão funciona
  • Quando e por que escolher um tipo em detrimento de outro

Nota

Os exemplos deste documento usarão objetos 2D. Todo objeto de física e forma de colisão 2D tem seu equivalente direto em 3D e, na maioria dos casos, funcionam exatamente da mesma forma.

Collision objects

Godot oferece quatro tipos de corpos físicos, estendendo CollisionObject2D:

  • Area2D
    Area2D nodes provide detection and influence. They can detect when objects overlap and can emit signals when bodies enter or exit. An Area2D can also be used to override physics properties, such as gravity or damping, in a defined area.

The other three bodies extend PhysicsBody2D:

  • StaticBody2D
    Um corpo estático é aquele que não é movido pelo motor de física. Ele participa da detecção de colisão, mas não se move em resposta a uma. É mais comumente usado para objetos que sejam parte do ambiente ou que não necessitam de ter qualquer comportamento dinâmico.
  • RigidBody2D
    Este é o nó que implementa a física 2D simulada. Você não controla um RigidBody2D diretamente, e sim aplica forças nele (gravidade, impulsos, etc.) e o motor de física calcula o movimento resultante. Leia mais sobre o uso de corpos rígidos.
  • KinematicBody2D
    Um corpo que provê detecção de colisão, mas nenhuma física. Todos os movimentos e respostas a colisões devem ser implementados em código.

Collision shapes

Um corpo físico por conter qualquer quantidade de objetos Shape2D como filhos. Essas formas são usadas para definir os limites de colisão do objeto e detectar contato com outros.

Nota

Para detectar colisões, pelo menos um Shape2D deve ser atribuído ao objeto.

A maneira mais comum de atribuir uma forma é adicionar um CollisionShape2D ou CollisionPolygon2D como filho do objeto. Esses nós lhe permitem desenhar o formato diretamente no espaço de trabalho do editor.

Importante

Be careful to never scale your collision shapes in the editor. The “Scale” property in the Inspector should remain (1, 1). When changing the size of the collision shape, you should always use the size handles, not the Node2D scale handles. Scaling a shape can result in unexpected collision behavior.

../../_images/player_coll_shape1.png

Chamadas de retorno do processamento da física

O motor da física pode criar múltiplas threads para melhorar seu desempenho, então pode usar todo o tempo de um quadro para processar a física. Por conta disso, o valor das variáveis de estado de um corpo como posição ou velocidade linear pode não estar preciso para o quadro atual.

In order to avoid this inaccuracy, any code that needs to access a body’s properties should be run in the Node._physics_process() callback, which is called before each physics step at a constant frame rate (60 times per second by default).

Collision layers and masks

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 20 different physics layers it can interact with.

Vamos olhar cada propriedade por vez:

  • collision_layer
    Descreve as camadas que onde o objeto aparece. Por padrão, todos os corpos estão na camada 1.
  • collision_mask
    Descreve quais camadas o corpo irá varrer em busca de colisões. Se um objeto não estiver em uma camada da máscara, o corpo o ignorará. Por padrão, todos corpos varrem a camada 1.

Essas propriedades podem ser configurada por código ou por edição no Inspetor.

Manter registro da finalidade que você está dando para cada camada pode ser difícil. Então, pode ser útil atribuir nomes às camadas em uso. Você pode nomeá-las nas Configurações do Projeto -> Nomes das Camadas.

../../_images/physics_layer_names.png

Exemplo:

Você tem quatro tipos de nós no seu jogo: Muros, Jogador, Inimigo e Moeda. Tanto o Jogador quanto o Inimigo colidem com Muros. O nó Jogador deveria detectar colisões com Inimigo e Moeda, mas Inimigo e Moeda devem se ignorar.

Comece nomeando as camadas 1 a 4 como “muros”, “jogador”, “inimigos” e “moedas” e coloque cada tipo de nó em sua camada respectiva usando a propriedade “Camada” (Layer). Então configure a propriedade “Máscara” (Mask) de cada nó selecionando as camadas com que ele deveria interagir. Por exemplo, as configurações do Jogador (Player) deveriam se parecer com isto:

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

Area2D

Area nodes provide detection and influence. They can detect when objects overlap and emit signals when bodies enter or exit. Areas can also be used to override physics properties, such as gravity or damping, in a defined area.

Existem três usos principais para Area2D:

  • Overriding physics parameters (such as gravity) in a given region.
  • Detectar quando outros corpos entram em ou saem de uma região ou quais estão atualmente em um região.
  • Verificar se outras áreas se sobrepõem.

Por padrão, áreas também recebem entradas de mouse e tela de toque.

StaticBody2D

Um corpo estático é aquele que não é movido pelo motor de física. Ele participa da detecção de colisão, mas não se move em resposta à ela. Entretanto, ele pode transmitir movimento e rotação a um corpo rígido como se estivesse se movendo, usando suas propriedades constant_linear_velocity e constant_angular_velocity (velocidades lineares e angulares constantes, respectivamente).

Nós StaticBody2D são mais comumente usado em objetos que são parte do ambiente ou que não precisam ter um comportamento dinâmico.

Exemplos de uso para StaticBody2D:

  • Plataformas (inclusive as móveis)
  • Esteiras transportadoras
  • Paredes e outros obstáculos

RigidBody2D

This is the node that implements simulated 2D physics. You do not control a RigidBody2D directly. Instead, you apply forces to it and the physics engine calculates the resulting movement, including collisions with other bodies, and collision responses, such as bouncing, rotating, etc.

Você pode modificar o comportamento de um corpo rígido através de propriedades como “Mass” (massa), “Friction” (atrito) ou “Bounce” (restituição/quique), configuráveis no Inspetor.

O comportamento do corpo também é afetado pelas propriedades do mundo, definidas em Configurações de projeto -> Física, ou entrando em uma Area2D que sobrepõe as propriedades da física global.

When a rigid body is at rest and hasn’t moved for a while, it goes to sleep. A sleeping body acts like a static body, and its forces are not calculated by the physics engine. The body will wake up when forces are applied, either by a collision or via code.

Modos do corpo rígido

Um corpo rígido pode ser configurado como um dos quatro modos:

  • Rígido (Rigid) - O corpo se comporta como um objeto físico. Colide com outros corpos e responde a forças que lhe forem aplicadas. Este é o modo padrão.
  • Estático (Static) - O corpo se comporta como um ref:StaticBody2D <class_StaticBody2D> e não se move.
  • Character - Similar to “Rigid” mode, but the body cannot rotate.
  • Cinemático (Kinematic) - O corpo se comporta como um KinematicBody2D e deve ser movido através de código.

Usando RigidBody2D

Um dos benefícios de usar um corpo rígido é que muito do comportamento dele pode ser obtido “de graça” sem escrever código algum. Por exemplo, se você está fazendo um jogo no estilo “Angry Birds” com blocos caindo, você só precisaria criar RigidBody2Ds e ajustar suas propriedades. Empilhamento, queda e quique seriam calculados automaticamente pelor 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 Physics2DDirectBodyState, which allows for safely changing properties and synchronizing them with the physics engine.

Por exemplo, aqui está o código para uma nave no estilo de “Asteroids”:

extends RigidBody2D

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

func _integrate_forces(state):
    if Input.is_action_pressed("ui_up"):
        applied_force = thrust.rotated(rotation)
    else:
        applied_force = Vector2()
    var rotation_dir = 0
    if Input.is_action_pressed("ui_right"):
        rotation_dir += 1
    if Input.is_action_pressed("ui_left"):
        rotation_dir -= 1
    applied_torque = rotation_dir * torque
class Spaceship : RigidBody2D
{
    private Vector2 thrust = new Vector2(0, 250);
    private float torque = 20000;

    public override void _IntegrateForces(Physics2DDirectBodyState state)
    {
        if (Input.IsActionPressed("ui_up"))
            SetAppliedForce(thrust.Rotated(Rotation));
        else
            SetAppliedForce(new Vector2());

        var rotationDir = 0;
        if (Input.IsActionPressed("ui_right"))
            rotationDir += 1;
        if (Input.IsActionPressed("ui_left"))
            rotationDir -= 1;
        SetAppliedTorque(rotationDir * torque);
    }
}

Note que nós não estamos configurando as propriedades linear_velocity ou angular_velocity diretamente, mas sim aplicando forças (thrust (impulso) e torque) ao corpo, e deixando o motor de física calcular o movimento resultante.

Nota

When a rigid body goes to sleep, the _integrate_forces() function will not be called. To override this behavior, you will need to keep the body awake by creating a collision, applying a force to it, or by disabling the can_sleep property. Be aware that this can have a negative effect on performance.

Relato de contato

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 contacts_reported property to a non-zero value. The contacts can then be obtained via Physics2DDirectBodyState.get_contact_count() and related functions.

Contact monitoring via signals can be enabled via the contact_monitor property. See RigidBody2D for the list of available signals.

KinematicBody2D

Corpos KinematicBody2D detectam colisões com outros corpos, mas não são afetados por propriedades físicas como gravidade e atrito. Em vez disso, eles devem ser controlados pelo usuário via código. O motor de física não moverá um corpo cinemático.

Ao mover um corpo cinemático, você não deveria definir sua posição diretamente. Em vez disso, use os métodos move_and_collide() ou move_and_slide(). Eles movem o corpo ao longo de um certo vetor, que irá parar instantaneamente se uma colisão for detectada com outro corpo. Depois de o corpo colidir, quaisquer reações à colisão devem ser programadas manualmente.

Kinematic 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 KinematicBody2D.

move_and_collide

When using move_and_collide(), the function returns a KinematicCollision2D object, which contains information about the collision and the colliding body. You can use this information to determine the response.

For example, if you want to find the point in space where the collision occurred:

extends KinematicBody2D

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.position
class Body : KinematicBody2D
{
    private Vector2 velocity = new Vector2(250, 250);

    public override void _PhysicsProcess(float delta)
    {
        var collisionInfo = MoveAndCollide(velocity * delta);
        if (collisionInfo != null)
        {
            var collisionPoint = collisionInfo.GetPosition();
        }
    }
}

Or to bounce off of the colliding object:

extends KinematicBody2D

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.normal)
class Body : KinematicBody2D
{
    private Vector2 velocity = new Vector2(250, 250);

    public override void _PhysicsProcess(float delta)
    {
        var collisionInfo = MoveAndCollide(velocity * delta);
        if (collisionInfo != null)
            velocity = velocity.Bounce(collisionInfo.Normal);
    }
}

move_and_slide

Sliding is a common collision response; imagine a player moving along walls in a top-down game or running up and down slopes in a platformer. While it’s possible to code this response yourself after using move_and_collide(), move_and_slide() provides a convenient way to implement sliding movement without writing much code.

Aviso

move_and_slide() automatically includes the timestep in its calculation, so you should not multiply the velocity vector by delta.

For example, use the following code to make a character that can walk along the ground (including slopes) and jump when standing on the ground:

extends KinematicBody2D

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

var velocity = Vector2()

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()
    velocity = move_and_slide(velocity, Vector2(0, -1))
class Body : KinematicBody2D
{
    private float runSpeed = 350;
    private float jumpSpeed = -1000;
    private float gravity = 2500;

    private Vector2 velocity = new Vector2();

    private void getInput()
    {
        velocity.x = 0;

        var right = Input.IsActionPressed("ui_right");
        var left = Input.IsActionPressed("ui_left");
        var jump = Input.IsActionPressed("ui_select");

        if (IsOnFloor() && jump)
            velocity.y = jumpSpeed;
        if (right)
            velocity.x += runSpeed;
        if (left)
            velocity.x -= runSpeed;
    }

    public override void _PhysicsProcess(float delta)
    {
        velocity.y += gravity * delta;
    }
}

See Kinematic character (2D) for more details on using move_and_slide(), including a demo project with detailed code.