Attention: Here be dragons
This is the latest
(unstable) version of this documentation, which may document features
not available in or compatible with released stable versions of Godot.
Checking the stable version of the documentation...
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.
Neste guia, você aprenderá:
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.
Aviso
A física no Godot, independentemente do motor de física, não é determinística; a natureza do determinismo em motores de física é muito complexa e depende de muitos fatores, o que significa que não há garantia de que a física rodará da mesma forma em situações aparentemente idênticas.
Objetos de Colisão
O Godot oferece quatro tipos de objetos de colisão, todos estendendo CollisionObject2D. Os três últimos listados abaixo são corpos físicos e estendem adicionalmente PhysicsBody2D.
- Area2D
Os nós
Area2Dfornecem detecção e influência. Eles podem detectar quando os objetos se sobrepõem e podem emitir sinais quando os corpos entram ou saem. UmaArea2Dtambém pode ser usado para substituir propriedades físicas, como gravidade ou amortecimento, em uma área definida.
- 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
RigidBody2Ddiretamente, 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.
- CharacterBody2D
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.
Material físico
Corpos estáticos e rígidos podem ser configurados para usar um PhysicsMaterial. Isso permite ajustar o atrito e o quique (bounce) de um objeto, e definir se ele é absorvente e/ou rugoso.
Formas de colisão
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
Tenha cuidado para nunca alterar a escala das suas formas de colisão no editor. A propriedade "Scale" no Inspetor deve permanecer como (1, 1). Ao alterar o tamanho da forma de colisão, você deve sempre usar as alças de tamanho, e não as alças de escala do Node2D. Alterar a escala de uma forma pode resultar em um comportamento de colisão inesperado.
Chamadas de retorno do processamento da física
O mecanismo de física roda em uma taxa fixa (um padrão de 60 iterações por segundo). Essa taxa é normalmente diferente da taxa de quadros (frame rate), que flutua com base no que é renderizado e nos recursos disponíveis.
É importante que todo código relacionado à física rode nessa taxa fixa. Portanto, o Godot diferencia entre o processamento de física e o processamento ocioso (*idle*). O código que roda a cada quadro é chamado de processamento idle e o código que roda a cada tique de física é chamado de processamento de física. O Godot fornece dois callbacks diferentes, um para cada uma dessas taxas de processamento.
O callback de física, Node._physics_process(), é chamado antes de cada passo de física. Qualquer código que precise acessar as propriedades de um corpo deve ser executado aqui. Este método receberá um parâmetro delta, que é um número de ponto flutuante igual ao tempo decorrido em segundos desde o último passo. Ao usar a taxa de atualização de física padrão de 60 Hz, ele normalmente será igual a 0.01666... (mas nem sempre, veja abaixo).
Nota
É recomendado sempre usar o parâmetro delta quando relevante em seus cálculos de física, para que o jogo se comporte corretamente se você alterar a taxa de atualização da física ou se o dispositivo do jogador não conseguir acompanhar.
Camadas e Máscaras de colisão
Um dos recursos de colisão mais poderosos, mas frequentemente incompreendido, é o sistema de camadas de colisão. Esse sistema permite construir interações complexas entre uma variedade de objetos. Os conceitos fundamentais são camadas (layers) e máscaras (masks). Cada CollisionObject2D possui 32 camadas de física diferentes com as quais pode interagir.
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 o controle de para que você está usando cada camada pode ser difícil, por isso você pode achar útil atribuir nomes às camadas que está utilizando. Os nomes podem ser atribuídos em Configurações do Projeto > Nomes das Camadas > Física 2D.
Exemplo de GUI
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:
Exemplo de código
Em chamadas de função, as camadas são especificadas como uma máscara de bits (bitmask). Onde uma função habilita todas as camadas por padrão, a máscara de camada será dada como 0xffffffff. Seu código pode usar notação binária, hexadecimal ou decimal para máscaras de camada, dependendo da sua preferência.
O equivalente em código do exemplo acima, onde as camadas 1, 3 e 4 foram ativadas, seria o seguinte:
# 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
#
# We can use the `<<` operator to shift the bit to the left by the layer number we want to enable.
# This is a faster way to multiply by powers of 2 than `pow()`.
# Additionally, we use the `|` (binary OR) operator to combine the results of each layer.
# This ensures we don't add the same layer multiple times, which would behave incorrectly.
(1 << 1 - 1) | (1 << 3 - 1) | (1 << 4 - 1)
# The above can alternatively be written as:
# pow(2, 1 - 1) + pow(2, 3 - 1) + pow(2, 4 - 1)
Você também pode definir bits de forma independente chamando set_collision_layer_value(layer_number, value) ou set_collision_mask_value(layer_number, value) em qualquer CollisionObject2D da seguinte forma:
# Example: Setting mask value to enable layers 1, 3, and 4.
var collider: CollisionObject2D = $CollisionObject2D # Any given collider.
collider.set_collision_mask_value(1, true)
collider.set_collision_mask_value(3, true)
collider.set_collision_mask_value(4, true)
Anotações de exportação podem ser usadas para exportar máscaras de bits no editor com uma interface gráfica amigável:
@export_flags_2d_physics var layers_2d_physics
Anotações de exportação adicionais estão disponíveis para camadas de renderização e navegação, tanto em 2D quanto em 3D. Veja Exportando sinalizadores de bits.
Area2D
Os nós de área fornecem detecção e influência. Eles podem detectar quando os objetos se sobrepõem e emitir sinais quando os corpos entram ou saem. As áreas também podem ser usadas para anular propriedades físicas, tais como gravidade ou amortecimento, em uma área definida.
Existem três usos principais para Area2D:
Substituição dos parâmetros físicos (como gravidade) em uma dada região.
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
Este é o nó que um implementa física 2D simulada. Você não controla um RigidBody2D diretamente. Em vez disso, você lhe aplica forças e o motor da física calcula o movimento resultante, inclusive colisões com outros corpos, além de respostas a colisões como quicar, girar, 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, conforme definido em Configurações do Projeto > Física, ou ao entrar em uma Area2D que esteja substituindo as propriedades de física globais.
Quando um corpo rígido está em repouso e não se move por um tempo, ele adormece. Um corpo adormecido age como um corpo estático, e suas forças não são calculadas pelo motor de física. O corpo acordará quando forças forem aplicadas, seja por colisão ou por 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.
No entanto, se você realmente deseja ter algum controle sobre o corpo, deve ter cuidado: alterar a position, a linear_velocity ou outras propriedades físicas de um corpo rígido pode resultar em comportamento inesperado. Se precisar alterar qualquer uma das propriedades relacionadas à física, você deve usar o callback _integrate_forces() em vez de _physics_process(). Nesse callback, você tem acesso ao PhysicsDirectBodyState2D do corpo, o que permite alterar propriedades com segurança e sincronizá-las com o motor de física.
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"):
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)
using Godot;
public partial class Spaceship : RigidBody2D
{
private Vector2 _thrust = new Vector2(0, -250);
private float _torque = 20000;
public override void _IntegrateForces(PhysicsDirectBodyState2D state)
{
if (Input.IsActionPressed("ui_up"))
{
state.ApplyForce(_thrust.Rotated(Rotation));
}
else
{
state.ApplyForce(new Vector2());
}
var rotationDir = 0;
if (Input.IsActionPressed("ui_right"))
{
rotationDir += 1;
}
if (Input.IsActionPressed("ui_left"))
{
rotationDir -= 1;
}
state.ApplyTorque(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
Quando um corpo rígido adormece, a função _integrate_forces() não será chamada. Para sobrepor este comportamento, você precisará manter o corpo acordado, criando uma colisão, aplicando forças a ele ou desabilitando a propriedade can_sleep. Fique ciente que isso pode ter um efeito negativo no desempenho.
Relato de contato
Por padrão, corpos rígidos não rastreiam contatos, pois isso pode exigir uma enorme quantidade de memória se houver muitos corpos na cena. Para habilitar o relatório de contatos, defina a propriedade max_contacts_reported com um valor diferente de zero. Os contatos podem então ser obtidos por meio de PhysicsDirectBodyState2D.get_contact_count() e funções relacionadas.
Monitoramento de contato via sinais pode ser habilitado através da propriedade contact_monitor. Veja RigidBody2D para a lista de sinais disponíveis.
CharacterBody2D
Os corpos CharacterBody2D detectam colisões com outros corpos, mas não são afetados por propriedades de física como gravidade ou fricção. Em vez disso, eles devem ser controlados pelo usuário via código. O motor de física não moverá um corpo de personagem por conta própria.
Ao mover um corpo de personagem, você não deve definir sua position diretamente. Em vez disso, use os métodos move_and_collide() ou move_and_slide(). Esses métodos movem o corpo ao longo de um determinado vetor, e ele parará instantaneamente se uma colisão for detectada com outro corpo. Após a colisão do corpo, qualquer resposta de colisão deve ser programada manualmente.
Resposta de colisão do personagem
Após uma colisão, você pode querer que o corpo rebata, deslize ao longo de uma parede ou altere as propriedades do objeto que ele atingiu. A maneira como você lida com a resposta de colisão depende de qual método você usou para mover o CharacterBody2D.
move_and_collide
Ao usar move_and_collide(), a função retorna um objeto KinematicCollision2D, que contém informações sobre a colisão e o corpo colidido. Você pode usar essas informações para determinar a resposta.
Por exemplo, se você quiser encontrar o ponto no espaço onde a colisão ocorreu:
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()
using Godot;
public partial class Body : PhysicsBody2D
{
private Vector2 _velocity = new Vector2(250, 250);
public override void _PhysicsProcess(double delta)
{
var collisionInfo = MoveAndCollide(_velocity * (float)delta);
if (collisionInfo != null)
{
var collisionPoint = collisionInfo.GetPosition();
}
}
}
Ou para rebater no objeto colidido:
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())
using Godot;
public partial class Body : PhysicsBody2D
{
private Vector2 _velocity = new Vector2(250, 250);
public override void _PhysicsProcess(double delta)
{
var collisionInfo = MoveAndCollide(_velocity * (float)delta);
if (collisionInfo != null)
{
_velocity = _velocity.Bounce(collisionInfo.GetNormal());
}
}
}
move_and_slide
Deslizar é uma resposta de colisão comum; imagine um jogador se movendo ao longo de paredes em um jogo top-down ou correndo subindo e descendo rampas em um jogo de plataforma. Embora seja possível codificar essa resposta você mesmo após usar move_and_collide(), o move_and_slide() fornece uma maneira conveniente de implementar o movimento de deslizamento sem escrever muito código.
Aviso
move_and_slide() inclui automaticamente o intervalo de tempo (delta) em seu cálculo, então você não deve multiplicar o vetor de velocidade por delta. Isso não se aplica à gravity, pois ela é uma aceleração e depende do tempo, precisando ser escalonada por delta.
Por exemplo, use o seguinte código para criar um personagem que pode caminhar pelo chão (incluindo rampas) e pular quando estiver parado no chão:
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()
using Godot;
public partial class Body : CharacterBody2D
{
private float _runSpeed = 350;
private float _jumpSpeed = -1000;
private float _gravity = 2500;
private void GetInput()
{
var velocity = Velocity;
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;
}
Velocity = velocity;
}
public override void _PhysicsProcess(double delta)
{
var velocity = Velocity;
velocity.Y += _gravity * (float)delta;
Velocity = velocity;
GetInput();
MoveAndSlide();
}
}
Veja Personagem cinemático (2D) para mais detalhes sobre o uso de move_and_slide(), incluindo um projeto de demonstração com código detalhado.