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...
Projetando a cena inimigo
Nesta parte, você vai programar os monstros, que chamaremos de inimigos. Na próxima lição, vamos fazê-los surgir aleatoriamente ao redor da área jogável.
Vamos projetar os próprios monstros em uma nova cena. A estrutura do nó vai ser semelhante à cena player.tscn.
Crie uma cena que tenha, mais uma vez, um nó CharacterBody3D como raiz. Nomeie-o como Mob. Adicione um nó filho Node3D e nomeie-o como Pivot. Em seguida, arraste e solte o arquivo mob.glb da aba Arquivos sobre o Pivot para adicionar o modelo 3D do monstro à cena.
Você pode renomear o nó recém-criado mob para Character.

Precisamos de uma forma de colisão para que nosso corpo funcione. Clique com o botão direito do mouse sobre o nó Mob, a cena raiz, e clique em Adicionar nó Filho(a).

Adicione um CollisionShape3D.

No Inspetor, atribua um BoxShape3D à propriedade Shape.
Devemos mudar seu tamanho para adequá-lo melhor ao modelo 3D. Você pode fazer isso interativamente clicando e arrastando os pontos laranja.
A caixa deve tocar o chão e ser um pouco mais fina do que o modelo. Os motores físicos funcionam de tal forma que se a esfera do jogador tocar até mesmo o canto da caixa, ocorrerá uma colisão. Se a caixa for um pouco grande demais em comparação com o modelo 3D, você pode morrer a uma distância do monstro, e o jogo será injusto para os jogadores.

Note que minha caixa é mais alta do que o monstro. Está tudo bem neste jogo porque estamos olhando para a cena de cima e usando uma perspectiva fixa. As formas de colisão não têm que combinar exatamente com o modelo. É o modo como você sente o jogo quando o testa que deve ditar sua a forma e tamanho.
Removendo monstros fora da tela
Vamos fazer os monstros surgirem em intervalos regulares de tempo no nível do jogo. Se não tivermos cuidado, sua contagem pode aumentar até o infinito, e não queremos isso. Cada instância do Inimigo tem tanto uma memória quanto um custo de processamento, e não queremos pagar por isso quando o inimigo está fora da tela.
Depois que um monstro sai da tela, não precisamos mais dele, então podemos excluí-lo. Godot tem um nó que detecta quando os objetos saem da tela, VisibleOnScreenNotifier3D, e vamos usá-lo para destruir nossos inimigos.
Nota
When you keep instantiating an object, there's a technique you can use to avoid the cost of creating and destroying instances all the time called pooling. It consists of pre-creating an array of objects and reusing them over and over.
Ao trabalhar com GDScript, isso geralmente não é necessário. O principal motivo para usar pools é evitar travamentos em linguagens com coleta de lixo, como C# ou Lua. O GDScript utiliza uma técnica diferente de gerenciamento de memória, chamada contagem de referências, que não possui essa limitação. Você pode aprender mais sobre isso aqui: Gerenciamento de memória.
Selecione o nó Mob e adicione um VisibleOnScreenNotifier3D como filho dele. Outra caixa, rosa desta vez, aparece. Quando essa caixa sair completamente da tela, o nó emitirá um sinal.

Redimensione-a usando os pontos laranja até cobrir todo o modelo 3D.

Programando a movimentação dos inimigos
Vamos implementar o movimento do monstro. Faremos isso em duas etapas. Primeiro, escreveremos um script no Mob que define uma função para inicializar o monstro. Em seguida, codificaremos o mecanismo de surgimento aleatório na cena main.tscn e chamaremos a função a partir dali.
Anexe um script ao Mob.

Aqui está o código de movimento inicial. Definimos duas propriedades, min_speed e max_speed, para definir um intervalo de velocidade aleatória, que usaremos posteriormente para definir CharacterBody3D.velocity.
extends CharacterBody3D
# Minimum speed of the mob in meters per second.
@export var min_speed = 10.0
# Maximum speed of the mob in meters per second.
@export var max_speed = 18.0
func _physics_process(_delta):
move_and_slide()
using Godot;
public partial class Mob : CharacterBody3D
{
// Don't forget to rebuild the project so the editor knows about the new export variable.
// Minimum speed of the mob in meters per second
[Export]
public float MinSpeed { get; set; } = 10.0f;
// Maximum speed of the mob in meters per second
[Export]
public float MaxSpeed { get; set; } = 18.0f;
public override void _PhysicsProcess(double delta)
{
MoveAndSlide();
}
}
Assim como o jogador, movemos o inimigo a cada quadro chamando a função CharacterBody3D.move_and_slide(). Desta vez, não atualizamos a velocity a cada quadro; queremos que o monstro se mova a uma velocidade constante e saia da tela, mesmo que colida com algum obstáculo.
Precisamos definir outra função para calcular a CharacterBody3D.velocity. Esta função fará o monstro se virar em direção ao jogador e tornará aleatórios tanto o seu ângulo de movimento quanto a sua velocidade.
A função receberá start_position, a posição inicial de surgimento do inimigo, e player_position como argumentos.
Posicionamos o inimigo em start_position e o orientamos em direção ao jogador usando o método look_at_from_position(), adicionando uma variação aleatória ao ângulo através de uma rotação aleatória em torno do eixo Y. Abaixo, randf_range() gera um valor aleatório entre -PI / 4 radianos e PI / 4 radianos.
# This function will be called from the Main scene.
func initialize(start_position, player_position):
# We position the mob by placing it at start_position
# and rotate it towards player_position, so it looks at the player.
look_at_from_position(start_position, player_position, Vector3.UP)
# Rotate this mob randomly within range of -45 and +45 degrees,
# so that it doesn't move directly towards the player.
rotate_y(randf_range(-PI / 4, PI / 4))
// This function will be called from the Main scene.
public void Initialize(Vector3 startPosition, Vector3 playerPosition)
{
// We position the mob by placing it at startPosition
// and rotate it towards playerPosition, so it looks at the player.
LookAtFromPosition(startPosition, playerPosition, Vector3.Up);
// Rotate this mob randomly within range of -45 and +45 degrees,
// so that it doesn't move directly towards the player.
RotateY((float)GD.RandRange(-Mathf.Pi / 4.0, Mathf.Pi / 4.0));
}
We got a random position, now we need a random_speed. randf_range() will be useful as it gives random float values, and we will use min_speed and max_speed.
random_speed is just an float, and we just use it to multiply our CharacterBody3D.velocity. After random_speed is applied, we rotate CharacterBody3D.velocity Vector3 towards the player.
func initialize(start_position, player_position):
# ...
# We calculate a random speed (float)
var random_speed = randf_range(min_speed, max_speed)
# We calculate a forward velocity that represents the speed.
velocity = Vector3.FORWARD * random_speed
# We then rotate the velocity vector based on the mob's Y rotation
# in order to move in the direction the mob is looking.
velocity = velocity.rotated(Vector3.UP, rotation.y)
public void Initialize(Vector3 startPosition, Vector3 playerPosition)
{
// ...
// We calculate a random speed (float).
float randomSpeed = GD.RandRange(MinSpeed, MaxSpeed);
// We calculate a forward velocity that represents the speed.
Velocity = Vector3.Forward * randomSpeed;
// We then rotate the velocity vector based on the mob's Y rotation
// in order to move in the direction the mob is looking.
Velocity = Velocity.Rotated(Vector3.Up, Rotation.Y);
}
Saindo da tela
Ainda temos que destruir os mobs quando eles saírem da tela. Para fazer isso, conectaremos o sinal screen_exited do nosso nó VisibleOnScreenNotifier3D ao Mob.
Selecione o nó VisibleOnScreenNotifier3D e, no lado direito da interface, navegue até a aba Signals (Sinais). Clique duas vezes no sinal screen_exited().

Conecte o sinal para o Mob

Isso adicionará uma nova função para você no script do seu mob, _on_visible_on_screen_notifier_3d_screen_exited(). A partir dela, chame o método queue_free(). Esta função destrói a instância na qual foi chamada.
func _on_visible_on_screen_notifier_3d_screen_exited():
queue_free()
// We also specified this function name in PascalCase in the editor's connection window.
private void OnVisibilityNotifierScreenExited()
{
QueueFree();
}
Nosso monstro está pronto para entrar no jogo! Na próxima parte, você fará os monstros surgirem na fase do jogo.
Aqui está o script completo mob.gd para referência.
extends CharacterBody3D
# Minimum speed of the mob in meters per second.
@export var min_speed = 10.0
# Maximum speed of the mob in meters per second.
@export var max_speed = 18.0
func _physics_process(_delta):
move_and_slide()
# This function will be called from the Main scene.
func initialize(start_position, player_position):
# We position the mob by placing it at start_position
# and rotate it towards player_position, so it looks at the player.
look_at_from_position(start_position, player_position, Vector3.UP)
# Rotate this mob randomly within range of -45 and +45 degrees,
# so that it doesn't move directly towards the player.
rotate_y(randf_range(-PI / 4, PI / 4))
# We calculate a random speed (float)
var random_speed = randf_range(min_speed, max_speed)
# We calculate a forward velocity that represents the speed.
velocity = Vector3.FORWARD * random_speed
# We then rotate the velocity vector based on the mob's Y rotation
# in order to move in the direction the mob is looking.
velocity = velocity.rotated(Vector3.UP, rotation.y)
func _on_visible_on_screen_notifier_3d_screen_exited():
queue_free()
using Godot;
public partial class Mob : CharacterBody3D
{
// Minimum speed of the mob in meters per second.
[Export]
public float MinSpeed { get; set; } = 10.0f;
// Maximum speed of the mob in meters per second.
[Export]
public float MaxSpeed { get; set; } = 18.0f;
public override void _PhysicsProcess(double delta)
{
MoveAndSlide();
}
// This function will be called from the Main scene.
public void Initialize(Vector3 startPosition, Vector3 playerPosition)
{
// We position the mob by placing it at startPosition
// and rotate it towards playerPosition, so it looks at the player.
LookAtFromPosition(startPosition, playerPosition, Vector3.Up);
// Rotate this mob randomly within range of -45 and +45 degrees,
// so that it doesn't move directly towards the player.
RotateY((float)GD.RandRange(-Mathf.Pi / 4.0, Mathf.Pi / 4.0));
// We calculate a random speed (float).
float randomSpeed = GD.RandRange(MinSpeed, MaxSpeed);
// We calculate a forward velocity that represents the speed.
Velocity = Vector3.Forward * randomSpeed;
// We then rotate the velocity vector based on the mob's Y rotation
// in order to move in the direction the mob is looking.
Velocity = Velocity.Rotated(Vector3.Up, Rotation.Y);
}
// We also specified this function name in PascalCase in the editor's connection window.
private void OnVisibilityNotifierScreenExited()
{
QueueFree();
}
}