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.

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.

../../_images/drag_drop_mob.webp

Você pode renomear o nó recém-criado mob para Character.

imagem0

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).

imagem1

Adicione um CollisionShape3D.

imagem2

No Inspetor, atribua um BoxShape3D à propriedade Shape.

../../_images/08.create_box_shape3D.webp

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.

imagem4

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.

imagem5

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

image6

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.

image7

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()

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))

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)

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().

image9

Conecte o sinal para o Mob

image10

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()

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()