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.
Crie uma cena com, mais uma vez, um nó KinematicBody como sua raiz. Nomeie-o Inimigo. Adicione um nó Spatial como filho dele, nomeie-o Pivô. E arraste e solte o arquivo mob.glb
do painel Sistema de Arquivos no Pivô para adicionar o modelo 3D do monstro à cena. Você pode renomear o nó Inimigo recém-criado para Personagem.
Precisamos de uma forma de colisão para que nosso corpo funcione. Clique com o botão direito do mouse sobre o nó Inimigo, a cena raiz, e clique em Adicionar nó Filho(a).
Adicione um CollisionShape.
No Inspetor, atribua um BoxShape à 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, VisibilityNotifier, e vamos usá-lo para destruir nossos inimigos.
Nota
Quando você continua instanciando um objeto nos jogos, há uma técnica que você pode usar para evitar o custo de criar e destruir instâncias o tempo todo chamadas de "pooling". Ela consiste em pré-criar um conjunto de objetos e reutilizá-los repetidamente.
Ao trabalhar com o GDScript, você não precisa se preocupar com isso. A principal razão para utilizar as "pools" é evitar travamentos com linguagens "garbage-collected" como C# ou Lua. O GDScript usa uma técnica diferente para gerenciar a memória, contagem de referência, que não tem essa ressalva. Você pode aprender mais sobre isso aqui Gerenciamento de memória.
Selecione o nó Inimigo e adicione um VisibilityNotifier 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. Vamos fazer isso em duas etapas. Primeiro, escreveremos um script do Inimigo que define uma função para inicializar o monstro. Em seguida, vamos programar o mecanismo de surgimento aleatório na cena Principal e chamar a função a partir daí.
Anexe um script ao Inimigo.
Aqui está o código de movimento para começarmos. Definimos duas propriedades, min_speed
e max_speed
, para definir uma faixa de velocidade aleatória. Definimos e inicializamos então o velocity
.
extends KinematicBody
# Minimum speed of the mob in meters per second.
export var min_speed = 10
# Maximum speed of the mob in meters per second.
export var max_speed = 18
var velocity = Vector3.ZERO
func _physics_process(_delta):
move_and_slide(velocity)
public class Mob : KinematicBody
{
// 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 int MinSpeed = 10;
// Maximum speed of the mob in meters per second
[Export]
public int MaxSpeed = 18;
private Vector3 _velocity = Vector3.Zero;
public override void _PhysicsProcess(float delta)
{
MoveAndSlide(_velocity);
}
}
Da mesma forma que o jogador, movemos o inimigo a cada quadro chamando o método `` move_and_slide()``do KinematicBody
método. Desta vez, não atualizamos o velocity
cada frame: queremos que o monstro se mova a uma velocidade constante e saia da tela, mesmo que fosse para acertar um obstáculo.
Você talvez veja um aviso no GDScript de que o valor de retorno de move_and_slide()
não é utilizado. Isto é esperado. Você pode simplesmente ignorar o aviso ou, se quiser ocultá-lo completamente, adicionar o comentário # warning-ignore:return_value_discarded
logo acima da linha move_and_slide(velocity)
. Para ler mais sobre o sistema de aviso GDScript, veja Sistema de alertas do GDScript.
É preciso definir outra função para calcular a velocidade de partida. Esta função vai virar o monstro em direção ao jogador e randomizar tanto seu ângulo de movimento quanto sua velocidade.
A função terá como argumentos start_position
, a posição de surgimento do inimigo, e o player_position
como seus argumentos.
Posicionamos o inimigo em start_position
e o giramos em direção ao jogador utilizando o método look_at_from_position()
, e randomizamos o ângulo girando uma quantidade aleatória ao redor do eixo Y. Abaixo, rand_range()
produz um valor aleatório entre -PI / 4
radians e PI / 4
radians.
# We will call this function from the Main scene.
func initialize(start_position, player_position):
# We position the mob and turn it so that it looks at the player.
look_at_from_position(start_position, player_position, Vector3.UP)
# And rotate it randomly so it doesn't move exactly toward the player.
rotate_y(rand_range(-PI / 4, PI / 4))
// We will call this function from the Main scene
public void Initialize(Vector3 startPosition, Vector3 playerPosition)
{
// We position the mob and turn it so that it looks at the player.
LookAtFromPosition(startPosition, playerPosition, Vector3.Up);
// And rotate it randomly so it doesn't move exactly toward the player.
RotateY((float)GD.RandRange(-Mathf.Pi / 4.0, Mathf.Pi / 4.0));
}
Calculamos então uma velocidade aleatória utilizando rand_range()
mais uma vez e a utilizamos para calcular a velocidade.
Começamos criando um vetor 3D apontando para frente, multiplicando-o por nosso random_speed
, e finalmente o giramos utilizando o método rotated()
da classe Vector3
.
func initialize(start_position, player_position):
# ...
# We calculate a random speed.
var random_speed = rand_range(min_speed, max_speed)
# We calculate a forward velocity that represents the speed.
velocity = Vector3.FORWARD * random_speed
# We then rotate the vector based on the mob's Y rotation to move in the direction it's looking.
velocity = velocity.rotated(Vector3.UP, rotation.y)
public void Initialize(Vector3 startPosition, Vector3 playerPosition)
{
// ...
// We calculate a random speed.
float randomSpeed = (float)GD.RandRange(MinSpeed, MaxSpeed);
// We calculate a forward velocity that represents the speed.
_velocity = Vector3.Forward * randomSpeed;
// We then rotate the vector based on the mob's Y rotation to move in the direction it's looking
_velocity = _velocity.Rotated(Vector3.Up, Rotation.y);
}
Saindo da tela¶
Ainda temos que destruir os inimigos quando eles deixam a tela. Para isso, conectaremos o sinal screen_exited
do nosso nó VisibilityNotifier ao Inimigo.
Volte para o viewport 3D clicando na etiqueta 3D na parte superior do editor. Você também pode pressionar Ctrl + F2 (Alt + 2 em macOS).
Selecione o nó VisibilityNotifier e, no lado direito da interface, navegue até painel Nó. Clique duas vezes no sinal screen_exited().
Conecte o sinal para o Mob.
Isto o levará de volta ao editor de scripts e adicionará uma nova função para você, _on_VisibilityNotifier_screen_exited()
. A partir dela, chame o método queue_free()
. Isto destruirá a instância do inimigo quando a caixa do VisibilityNotifier deixar a tela.
func _on_VisibilityNotifier_screen_exited():
queue_free()
// We also specified this function name in PascalCase in the editor's connection window
public 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 Mob.gd
completo para referência.
extends KinematicBody
# Minimum speed of the mob in meters per second.
export var min_speed = 10
# Maximum speed of the mob in meters per second.
export var max_speed = 18
var velocity = Vector3.ZERO
func _physics_process(_delta):
move_and_slide(velocity)
func initialize(start_position, player_position):
look_at_from_position(start_position, player_position, Vector3.UP)
rotate_y(rand_range(-PI / 4, PI / 4))
var random_speed = rand_range(min_speed, max_speed)
velocity = Vector3.FORWARD * random_speed
velocity = velocity.rotated(Vector3.UP, rotation.y)
func _on_VisibilityNotifier_screen_exited():
queue_free()
public class Mob : KinematicBody
{
// Minimum speed of the mob in meters per second
[Export]
public int MinSpeed = 10;
// Maximum speed of the mob in meters per second
[Export]
public int MaxSpeed = 18;
private Vector3 _velocity = Vector3.Zero;
public override void _PhysicsProcess(float delta)
{
MoveAndSlide(_velocity);
}
// We will call this function from the Main scene
public void Initialize(Vector3 startPosition, Vector3 playerPosition)
{
LookAtFromPosition(startPosition, playerPosition, Vector3.Up);
RotateY((float)GD.RandRange(-Mathf.Pi / 4.0, Mathf.Pi / 4.0));
var randomSpeed = (float)GD.RandRange(MinSpeed, MaxSpeed);
_velocity = Vector3.Forward * randomSpeed;
_velocity = _velocity.Rotated(Vector3.Up, Rotation.y);
}
// We also specified this function name in PascalCase in the editor's connection window
public void OnVisibilityNotifierScreenExited()
{
QueueFree();
}
}