Coding the player¶
In this lesson, we'll add player movement, animation, and set it up to detect collisions.
To do so, we need to add some functionality that we can't get from a built-in
node, so we'll add a script. Click the Player
node and click the "Attach
Script" button:

Na janela de configurações de roteiro, você pode deixar as configurações padrões do jeito que estão. Apenas clique em "Criar":
Nota
Se você estiver criando um script em C# ou em outras linguagens, selecione a linguagem em no menu suspenso Idioma antes de clicar em Criar.

Nota
If this is your first time encountering GDScript, please read Scripting languages before continuing.
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;
using System;
public class Player : Area2D
{
[Export]
public int Speed = 400; // How fast the player will move (pixels/sec).
public Vector2 ScreenSize; // Size of the game window.
}
// A `player.gdns` file has already been created for you. Attach it to the Player node.
// Create two files `player.cpp` and `player.hpp` next to `entry.cpp` in `src`.
// This code goes in `player.hpp`. We also define the methods we'll be using here.
#ifndef PLAYER_H
#define PLAYER_H
#include <AnimatedSprite.hpp>
#include <Area2D.hpp>
#include <CollisionShape2D.hpp>
#include <Godot.hpp>
#include <Input.hpp>
class Player : public godot::Area2D {
GODOT_CLASS(Player, godot::Area2D)
godot::AnimatedSprite *_animated_sprite;
godot::CollisionShape2D *_collision_shape;
godot::Input *_input;
godot::Vector2 _screen_size; // Size of the game window.
public:
real_t speed = 400; // How fast the player will move (pixels/sec).
void _init() {}
void _ready();
void _process(const double p_delta);
void start(const godot::Vector2 p_position);
void _on_Player_body_entered(godot::Node2D *_body);
static void _register_methods();
};
#endif // PLAYER_H
Usar a palavra-chave export
na primeira variável, speed
, nos permite definir seu valor pelo Inspetor. Isto pode ser útil para os valores que você deseja ser capaz de ajustar do mesmo jeito que se faz com as propriedades internas de um nó. Clique no nó Jogador
e você verá que a propriedade agora aparece na seção "Variáveis de Script" do Inspetor. Lembre-se, se você modificar o valor aqui, ele irá sobrepor o valor que está definido no script.
Aviso
Se você estiver usando o C#, precisará (re) construir os assemblies do projeto sempre que desejar ver novas variáveis exportadas ou sinais. Essa construção pode ser acionada manualmente clicando na palavra "Mono" na parte inferior da janela do editor para revelar o Painel Mono e, em seguida, clicando no botão "Build Project".

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;
}
// This code goes in `player.cpp`.
#include "player.hpp"
void Player::_ready() {
_animated_sprite = get_node<godot::AnimatedSprite>("AnimatedSprite");
_collision_shape = get_node<godot::CollisionShape2D>("CollisionShape2D");
_input = godot::Input::get_singleton();
_screen_size = get_viewport_rect().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:
Verificação de entradas.
Movimentação em uma certa direção.
Reprodução da animação apropriada.
First, we need to check for input - is the player pressing a key? For this game, we have 4 direction inputs to check. Input actions are defined in the Project Settings under "Input Map". Here, you can define custom events and assign different keys, mouse events, or other inputs to them. For this game, we will map the arrow keys to the four directions.
Click on Project -> Project Settings to open the project settings window and
click on the Input Map tab at the top. Type "move_right" in the top bar and
click the "Add" button to add the move_right
action.

We need to assign a key to this action. Click the "+" icon on the right, then click the "Key" option in the drop-down menu. A dialog asks you to type in the desired key. Press the right arrow on your keyboard and click "Ok".

Repeat these steps to add three more mappings:
move_left
mapped to the left arrow key.move_up
mapped to the up arrow key.And
move_down
mapped to the down arrow key.
Your input map tab should look like this:

Click the "Close" button to close the project settings.
Nota
We only mapped one key to each input action, but you can map multiple keys, joystick buttons, or mouse buttons to the same input action.
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
$AnimatedSprite.play()
else:
$AnimatedSprite.stop()
public override void _Process(float 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 animatedSprite = GetNode<AnimatedSprite>("AnimatedSprite");
if (velocity.Length() > 0)
{
velocity = velocity.Normalized() * Speed;
animatedSprite.Play();
}
else
{
animatedSprite.Stop();
}
}
// This code goes in `player.cpp`.
void Player::_process(const double p_delta) {
godot::Vector2 velocity(0, 0);
velocity.x = _input->get_action_strength("move_right") - _input->get_action_strength("move_left");
velocity.y = _input->get_action_strength("move_down") - _input->get_action_strength("move_up");
if (velocity.length() > 0) {
velocity = velocity.normalized() * speed;
_animated_sprite->play();
} else {
_animated_sprite->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 a velocidade, o que significa que podemos definir seu comprimento (módulo) para 1
e multiplicar pela 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 Vector math. É bom conhecê-la, mas não será necessário para o resto deste tutorial.
We also check whether the player is moving so we can call play()
or
stop()
on the AnimatedSprite.
Dica
$
is shorthand for get_node()
. So in the code above,
$AnimatedSprite.play()
is the same as
get_node("AnimatedSprite").play()
.
In GDScript, $
returns the node at the relative path from the
current node, or returns null
if the node is not found. Since
AnimatedSprite is a child of the current node, we can use
$AnimatedSprite
.
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 (fixar) um valor significa restringi-lo a um determinado intervalo. Adicione o seguinte código embaixo da função ``_process``(Tenha certeza de que não está identado ao else):
position += velocity * delta
position.x = clamp(position.x, 0, screen_size.x)
position.y = clamp(position.y, 0, screen_size.y)
Position += velocity * delta;
Position = new Vector2(
x: Mathf.Clamp(Position.x, 0, ScreenSize.x),
y: Mathf.Clamp(Position.y, 0, ScreenSize.y)
);
godot::Vector2 position = get_position();
position += velocity * (real_t)p_delta;
position.x = godot::Math::clamp(position.x, (real_t)0.0, _screen_size.x);
position.y = godot::Math::clamp(position.y, (real_t)0.0, _screen_size.y);
set_position(position);
Dica
O parâmetro delta na função _process() se refere ao comprimento do frame - a quantidade de tempo que o frame anterior levou para ser completado. Usando este valor você se certifica que seu movimento será constante, mesmo quando o frame rate sofrer alterações.
Click "Play Scene" (F6, Cmd + R on macOS) and confirm you can move the player around the screen in all directions.
Aviso
Se encontrar um erro no painel "Depurador" que diz
A tentativa de chamar a função 'play' na base 'instance null' em uma instância nula
this likely means you spelled the name of the AnimatedSprite node
wrong. Node names are case-sensitive and $NodeName
must match
the name you see in the scene tree.
Selecionado as Animações¶
Now that the player can move, we need to change which animation the
AnimatedSprite is playing based on its direction. We have the "walk" animation,
which shows the player walking to the right. This animation should be flipped
horizontally using the flip_h
property for left movement. We also have the
"up" animation, which should be flipped vertically with flip_v
for downward
movement. Let's place this code at the end of the _process()
function:
if velocity.x != 0:
$AnimatedSprite.animation = "walk"
$AnimatedSprite.flip_v = false
# See the note below about boolean assignment.
$AnimatedSprite.flip_h = velocity.x < 0
elif velocity.y != 0:
$AnimatedSprite.animation = "up"
$AnimatedSprite.flip_v = velocity.y > 0
if (velocity.x != 0)
{
animatedSprite.Animation = "walk";
animatedSprite.FlipV = false;
// See the note below about boolean assignment.
animatedSprite.FlipH = velocity.x < 0;
}
else if (velocity.y != 0)
{
animatedSprite.Animation = "up";
animatedSprite.FlipV = velocity.y > 0;
}
if (velocity.x != 0) {
_animated_sprite->set_animation("right");
_animated_sprite->set_flip_v(false);
// See the note below about boolean assignment.
_animated_sprite->set_flip_h(velocity.x < 0);
} else if (velocity.y != 0) {
_animated_sprite->set_animation("up");
_animated_sprite->set_flip_v(velocity.y > 0);
}
Nota
As atribuiçoes booleanas no código acima são um encurtamento comum para programadores. Já que estamos fazendo um teste de comparação(booleano) e atribuindo um valor booleano, é possível fazer os dois ao mesmo tempo. Compare esse código a atribuição booleana encurtada, acima:
if velocity.x < 0:
$AnimatedSprite.flip_h = true
else:
$AnimatedSprite.flip_h = false
if (velocity.x < 0)
{
animatedSprite.FlipH = true;
}
else
{
animatedSprite.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 "Walk"
você também deve usar a letra maiúscula "W" no código.
Quando tiver certeza que a movimentação está funcionando corretamente, adicione esta linha à _ready()
para que o jogador fique oculto no início do jogo:
hide()
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 trecho a seguir no início do script, depois de extends Area2D
:
signal hit
// Don't forget to rebuild the project so the editor knows about the new signal.
[Signal]
public delegate void Hit();
// This code goes in `player.cpp`.
// We need to register the signal here, and while we're here, we can also
// register the other methods and register the speed property.
void Player::_register_methods() {
godot::register_method("_ready", &Player::_ready);
godot::register_method("_process", &Player::_process);
godot::register_method("start", &Player::start);
godot::register_method("_on_Player_body_entered", &Player::_on_Player_body_entered);
godot::register_property("speed", &Player::speed, (real_t)400.0);
// This below line is the signal.
godot::register_signal<Player>("hit", godot::Dictionary());
}
Isto define um sinal personalizado chamado "hit" (atingir) que faremos com que nosso jogador emita (envie) quando colidir com um inimigo. Iremos utilizar Area2D
para detectar a colisão. Selecione o nó Jogador
e clique na aba "Nó" ao lado da aba "Inspetor" para ver a lista de sinais que o jogador pode emitir:

Observe que o nosso sinal personalizado "hit" também está lá! Já que nossos inimigos serão nós do tipo RigidBody2D
, queremos o sinal body_entered( Object body )
; ele será emitido quando um corpo entrar em contato com o jogador. Clique em "Conectar.." e a janela "Conectando Sinal" aparece. Nós não precisamos alterar nenhuma destas configurações – o Godot criará automaticamente uma função chamada _on_Jogador_body_entered
no script do jogador. Esta função será chamada sempre que o sinal for emitido - ela trata do sinal.

Note que o icone verde indica que um sinal está conectado a esta função. Adicione esse código à função:
func _on_Player_body_entered(body):
hide() # Player disappears after being hit.
emit_signal("hit")
# Must be deferred as we can't change physics properties on a physics callback.
$CollisionShape2D.set_deferred("disabled", true)
public void OnPlayerBodyEntered(PhysicsBody2D body)
{
Hide(); // Player disappears after being hit.
EmitSignal(nameof(Hit));
// Must be deferred as we can't change physics properties on a physics callback.
GetNode<CollisionShape2D>("CollisionShape2D").SetDeferred("disabled", true);
}
// This code goes in `player.cpp`.
void Player::_on_Player_body_entered(godot::Node2D *_body) {
hide(); // Player disappears after being hit.
emit_signal("hit");
// Must be deferred as we can't change physics properties on a physics callback.
_collision_shape->set_deferred("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 da engine. Usando set_deferred ()
nos permite que o Godot aguarde para desabilitar a forma até que seja seguro fazê-lo.
A última peça para nosso jogador é adicionar uma função que possamos chamar para reiniciar o jogador para começarmos um novo jogo.
func start(pos):
position = pos
show()
$CollisionShape2D.disabled = false
public void Start(Vector2 pos)
{
Position = pos;
Show();
GetNode<CollisionShape2D>("CollisionShape2D").Disabled = false;
}
// This code goes in `player.cpp`.
void Player::start(const godot::Vector2 p_position) {
set_position(p_position);
show();
_collision_shape->set_disabled(false);
}
With the player working, we'll work on the enemy in the next lesson.