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...
Programando o jogador
Nesta lição, adicionaremos movimento e animação do jogador e o configuraremos para detectar colisões.
Para isso, precisamos adicionar algumas funcionalidades que não conseguimos em um nó embutido, então adicionaremos um script. Clique no nó Player e depois clique no botão "Adicionar Script":
Na janela de configurações do script, você pode deixar as configurações padrão inalteradas. Basta clicar em "Criar":
Nota
If you're creating a C# script or other languages, select the language from the drop down menu before hitting create.
Nota
Se essa é a primeira vez que você se depara com GDScript, por favor, leia Linguagens de script antes de continuar.
Comece declarando as variáveis membro que este objeto irá precisar:
extends Area2D
@export var speed = 400 # How fast the player will move (pixels/sec).
var screen_size # Size of the game window.
using Godot;
public partial class Player : Area2D
{
[Export]
public int Speed { get; set; } = 400; // How fast the player will move (pixels/sec).
public Vector2 ScreenSize; // Size of the game window.
}
Usar a palavra-chave export na primeira variável speed permite definir seu valor no Inspetor. Isso pode ser útil para valores que você deseja ajustar da mesma forma que as propriedades integradas de um nó. Clique no nó Player e verá que a propriedade agora aparece no Inspetor em uma nova seção com o nome do script. Lembre-se: se você alterar o valor aqui, ele substituirá o valor padrão especificado no script (o script não será modificado).
Aviso
Se você estiver usando C#, você precisa (re)compilar os arquivos assembly do projeto sempre que quiser ver novas variáveis ou sinais de exportação. Esta compilação pode ser ativada manualmente clicando no botão Build no canto superior direito do editor.
Seu script player.gd já deve conter uma função _ready() e _process(). Se você não selecionou o modelo padrão mostrado acima, crie estas funções enquante segue ao tutorial.
A função _ready() é chamada quando um nó entra na árvore de cena, que é uma boa hora para descobrir o tamanho da janela do jogo:
func _ready():
screen_size = get_viewport_rect().size
public override void _Ready()
{
ScreenSize = GetViewportRect().Size;
}
Agora podemos usar a função _process() para definir o que o jogador fará. _process() é chamada a cada quadro, então usaremos isso para atualizar os elementos de nosso jogo que esperamos que mudem frequentemente. Para o jogador, precisamos fazer o seguinte:
Verificar as entradas.
Movimentar na direção desejada.
Reproduzir a animação apropriada.
Primeiro, precisamos verificar as entradas – o jogador está pressionando uma tecla? Para este jogo, temos 4 entradas de direção para verificar. Ações de entrada são definidas nas Configurações do Projeto na aba "Mapa de entrada". Nela você pode definir eventos personalizados e atribuir diferentes teclas, eventos de mouse ou outras entradas para eles. Para este jogo, vamos mapear as teclas de seta do teclado para as quatro direções.
Clique em Projeto -> Configurações do Projeto para abrir a janela das configurações do projeto e clique na aba Mapa de Entrada, no topo. Digite "mover_direita" na barra do topo e clique no botão "Adicionar" para adicionar a ação mover_direita.
Nós devemos assinalar uma tecla à esta ação. Clique no ícone "+" a direita, para abrir a janela do gerenciador de eventos.
O campos "Escutando por Entradas..." deve estar selecionado automaticamente. Pressione a tecla "direita" no seu teclado, e o menu deverá se parecer com isso.
Selecione o botão "ok". A tecla "direita" está agora associada à ação move_right.
Repita estes passos para adicionar três outros mapeamentos:
mover_esquerdamapeado para a tecla seta para esquerda.mover_cimamapeado para tecla seta para cima.E
mover_baixomapeado para tecla seta para baixo.
Sua aba de mapa de entrada deveria se parecer com isto:
Clique no botão "Fechar" para fechar as configurações de projeto.
Nota
Nós mapeamos apenas uma tecla para cada ação de entrada, mas você pode mapear múltiplas teclas, botões de joystick ou botões de mouse para a mesma ação de entrada.
Você pode detectar se uma tecla é pressionada usando Input.is_action_pressed(), que retorna true (verdadeiro) se é pressionada ou false (falso) em caso contrário.
func _process(delta):
var velocity = Vector2.ZERO # The player's movement vector.
if Input.is_action_pressed("move_right"):
velocity.x += 1
if Input.is_action_pressed("move_left"):
velocity.x -= 1
if Input.is_action_pressed("move_down"):
velocity.y += 1
if Input.is_action_pressed("move_up"):
velocity.y -= 1
if velocity.length() > 0:
velocity = velocity.normalized() * speed
$AnimatedSprite2D.play()
else:
$AnimatedSprite2D.stop()
public override void _Process(double delta)
{
var velocity = Vector2.Zero; // The player's movement vector.
if (Input.IsActionPressed("move_right"))
{
velocity.X += 1;
}
if (Input.IsActionPressed("move_left"))
{
velocity.X -= 1;
}
if (Input.IsActionPressed("move_down"))
{
velocity.Y += 1;
}
if (Input.IsActionPressed("move_up"))
{
velocity.Y -= 1;
}
var animatedSprite2D = GetNode<AnimatedSprite2D>("AnimatedSprite2D");
if (velocity.Length() > 0)
{
velocity = velocity.Normalized() * Speed;
animatedSprite2D.Play();
}
else
{
animatedSprite2D.Stop();
}
}
Iniciamos definindo a velocidade como sendo (0, 0) - por padrão o jogador não deve estar se movendo. Então nós verificamos cada entrada e adicionamos/subtraímos da velocidade para obter a direção resultante. Por exemplo, se você segurar direita e baixo ao mesmo tempo, o vetor resultante velocidade será (1, 1). Neste caso, já que estamos adicionando um movimento vertical e um horizontal, o jogador iria se mover mais rápido do que se apenas se movesse horizontalmente.
Podemos evitar isso se normalizarmos o vetor da velocidade, o que significa que podemos definir seu comprimento (módulo) para 1 e multiplicar pela intensidade da velocidade desejada. Isso resolve o problema de movimentação mais rápida nas diagonais.
Dica
Se você nunca usou matemática vetorial antes, ou precisa refrescar a memória, você pode ver uma explicação do uso de vetores no Godot em Matemática vetorial. É bom conhecê-la, mas não será necessário para o resto deste tutorial.
Nós também verificamos se o jogador está se movendo, para que possamos chamar play() (reproduzir) ou stop() (parar) no AnimatedSprite.
Dica
$ é uma forma abreviada de get_node(). Portanto, no código acima, $AnimatedSprite2D.play() é o mesmo que get_node("AnimatedSprite2D").play().
No GDScript, $ retorna o nó no caminho relativo a partir do nó atual ou retorna null se o nó não for encontrado. Como AnimatedSprite2D é um filho do nó atual, podemos usar $AnimatedSprite2D.
Agora que temos uma direção de movimento, podemos atualizar a posição do jogador. Podemos também usar clamp() para impedir que ele saia da tela. Clamp (limitar) um valor significa restringi-lo a uma determinada escala. Adicione o seguinte código ao final da função _process (tenha certeza de que não está indentado debaixo do else):
position += velocity * delta
position = position.clamp(Vector2.ZERO, screen_size)
Position += velocity * (float)delta;
Position = new Vector2(
x: Mathf.Clamp(Position.X, 0, ScreenSize.X),
y: Mathf.Clamp(Position.Y, 0, ScreenSize.Y)
);
Dica
The delta parameter in the _process() function refers to the frame
length, which is the amount of time that the previous frame took to complete.
Using this value ensures that your movement will remain consistent even
if the frame rate changes.
Clique em "Run Current Scene" (F6, Cmd + R no macOS) e confirme que você consegue mover o jogador pela tela em todas as direções.
Aviso
Se encontrar um erro no painel "Depurador" que diz
Attempt to call function 'play' in base 'null instance' on a null
instance
Isso provavelmente significa que você escreveu o nome do nó AnimatedSprite2D incorretamente. Os nomes dos nós diferenciam maiúsculas de minúsculas, e $NodeName deve corresponder exatamente ao nome exibido na árvore da cena.
Selecionado as Animações
Agora que o jogador pode se mover, precisamos alterar qual animação o AnimatedSprite2D está reproduzindo com base na direção do movimento. Temos a animação "walk", que mostra o jogador andando para a direita. Essa animação deve ser invertida horizontalmente usando a propriedade flip_h para movimento à esquerda. Também temos a animação "up", que deve ser invertida verticalmente com flip_v para movimento para baixo. Vamos colocar este código no final da função _process():
if velocity.x != 0:
$AnimatedSprite2D.animation = "walk"
$AnimatedSprite2D.flip_v = false
# See the note below about the following boolean assignment.
$AnimatedSprite2D.flip_h = velocity.x < 0
elif velocity.y != 0:
$AnimatedSprite2D.animation = "up"
$AnimatedSprite2D.flip_v = velocity.y > 0
if (velocity.X != 0)
{
animatedSprite2D.Animation = "walk";
animatedSprite2D.FlipV = false;
// See the note below about the following boolean assignment.
animatedSprite2D.FlipH = velocity.X < 0;
}
else if (velocity.Y != 0)
{
animatedSprite2D.Animation = "up";
animatedSprite2D.FlipV = velocity.Y > 0;
}
Nota
As atribuições boolianas no código acima são um encurtamento comum para programadores. Já que estamos fazendo um teste de comparação (booliana) e também atribuindo um valor booliano, é possível fazer os dois ao mesmo tempo. Compare este código a atribuição booliana encurtada para uma linha acima:
if velocity.x < 0:
$AnimatedSprite2D.flip_h = true
else:
$AnimatedSprite2D.flip_h = false
if (velocity.X < 0)
{
animatedSprite2D.FlipH = true;
}
else
{
animatedSprite2D.FlipH = false;
}
Execute a cena novamente e verifique se as animações estão corretas em cada uma das direções.
Dica
Um erro comum aqui é digitar os nomes das animações de forma errada. O nome das animações no painel SpriteFrames deve ser igual ao que foi digitado no código. Se você nomeou a animação "Caminhada" você também deve usar a letra maiúscula "C" no código.
Quando tiver certeza de que a movimentação está funcionando corretamente, adicione esta linha à _ready() para que o jogador fique oculto no início do jogo:
hide()
Hide();
Preparando para colisões
Nós queremos que o Jogador detecte quando é atingido por um inimigo, mas nós não fizemos nenhum inimigo ainda! Não tem problema, porque nós iremos utilizar a funcionalidade de sinal do Godot para fazer com que funcione.
Adicione o seguinte no topo do script. Se você estiver usando o GDScript, adicione-o após extends Area2D. Se você estiver usando C#, adicione-o depois de public partial class Player : Area2D:
signal hit
// Don't forget to rebuild the project so the editor knows about the new signal.
[Signal]
public delegate void HitEventHandler();
Isso define um sinal personalizado chamado "hit" que faremos nosso jogador emitir (enviar) quando colidir com um inimigo. Usaremos Area2D para detectar a colisão. Selecione o nó Player e clique na aba Signals ao lado da aba Inspector para ver a lista de sinais que o jogador pode emitir:
Notice our custom "hit" signal is there as well! Since our enemies are going to
be RigidBody2D nodes, we want the body_entered(body: Node2D) signal. This
signal will be emitted when a body touches the player. Click and
the Connect a Signal window appears.
Godot will create a function with that exact name directly in the script for you. You don't need to change the default settings right now.
Aviso
Se estiver usando um editor de textos externo (por exemplo, Visual Studio Code), um bug atualmente previne o Godot de faze-lo. Você será transferido para o seu editor externo, mas a nova função não estará lá.
Neste caso, você deverá escrever a função dentro do script do Player.
Observe o ícone verde indicando que um sinal está conectado a esta função; oque não significa que esta função exista, apenas que o sinal irá tentar se conectar a uma função com este nome, então verifique se o nome da função está correto!
Em seguida, adicione este código à função:
func _on_body_entered(_body):
hide() # Player disappears after being hit.
hit.emit()
# Must be deferred as we can't change physics properties on a physics callback.
$CollisionShape2D.set_deferred("disabled", true)
// We also specified this function name in PascalCase in the editor's connection window.
private void OnBodyEntered(Node2D body)
{
Hide(); // Player disappears after being hit.
EmitSignal(SignalName.Hit);
// Must be deferred as we can't change physics properties on a physics callback.
GetNode<CollisionShape2D>("CollisionShape2D").SetDeferred(CollisionShape2D.PropertyName.Disabled, true);
}
Cada vez que um inimigo atinge o jogador, o sinal será emitido. Precisamos desativar a colisão do jogador para que não acione o sinal hit mais de uma vez.
Nota
Desativar o formato de colisão da área pode causar um erro se ocorrer no meio do processamento de colisão do motor de jogo. Usar set_deferred() fala para o Godot aguardar para desabilitar a forma até que seja seguro fazê-lo.
A última peça é adicionar uma função que possamos chamar para reiniciar/reconfigurar o jogador quando começarmos um novo jogo.
func start(pos):
position = pos
show()
$CollisionShape2D.disabled = false
public void Start(Vector2 position)
{
Position = position;
Show();
GetNode<CollisionShape2D>("CollisionShape2D").Disabled = false;
}
Com o jogador funcionando, vamos trabalhar com o inimigo na próxima lição.