Pulando e esmagando monstros

Nesta parte, adicionaremos a habilidade de pular, para esmagar os monstros. Na próxima lição, faremos o jogador morrer quando um monstro o atingir no chão.

Primeiro, temos que alterar algumas configurações relacionadas às interações físicas. Entre no mundo de :ref:'physics <doc_physics_introduction_collision_layers_and_masks>layers'.

Controlando as interações físicas

Os corpos da física têm acesso a duas propriedades complementares: camadas e máscaras. As camadas definem em que camada(s) de física um objeto está.

As máscaras controlam as camadas que um corpo irá escutar e detectar. Isto afeta a detecção de colisões. Quando se deseja que dois corpos interajam, é necessário que pelo menos um tenha uma máscara correspondente ao outro.

Se isso for confuso, não se preocupe, veremos três exemplos em um segundo.

O ponto importante é que você pode usar camadas e máscaras para filtrar as interações físicas, controlar o desempenho e remover a necessidade de condições extras em seu código.

Por padrão, todos os corpos e áreas de física são definidos como camada e máscara ''0''. Isso significa que todos eles colidem uns com os outros.

As camadas físicas são representadas por números, mas podemos dar-lhes nomes para acompanhar o que é o quê.

Definindo os nomes de camadas

Vamos dar um nome às nossas camadas físicas. Vá para Projeto -> Configurações do Projeto.

imagem0

No menu à esquerda, navegue até Nomes de Camada -> Física 3D. Você pode ver uma lista de camadas com um campo ao lado de cada uma delas à direita. Você pode definir seus nomes lá. Nomeie as três primeiras camadas jogador, inimigos e mundo, respectivamente.

imagem1

Agora, podemos atribuí-los aos nossos nós de física.

Atribuindo camadas e máscaras

Na cena Principal, selecione o nó Ground. No Inspetor, expanda a seção Colisão. Lá, você pode ver as camadas e máscaras do nó como uma grade de botões.

imagem2

O solo é parte do mundo, por isso queremos que ele seja parte da terceira camada. Clique no botão iluminado para desativar a primeira Camada e ativar a terceira. Em seguida, desative a Máscara clicando nela.

imagem3

Como mencionei acima, a propriedade Mask permite que um nó ouça a interação com outros objetos físicos, mas não precisamos que ele tenha colisões. A Ground não precisa ouvir nada; ela só está lá para evitar que as criaturas caiam.

Observe que você pode clicar no botão "..." no lado direito das propriedades para ver uma lista de caixas de seleção nomeadas.

imagem4

A seguir estão o Player e o Inimigo. Abra Player.tscn clicando duas vezes no arquivo no painel Sistema de Arquivos.

Selecione o nó Player e configure seu Collision -> Mask tanto para "inimigos" quanto para "mundo". Você pode deixar a propriedade padrão Camada, pois a primeira camada é a do "jogador".

imagem5

Em seguida, abra a cena Mob clicando duas vezes em Mob.tscn e selecione o nó Mob.

Configurar sua Colisão -> Camada para "inimigos" e desestabilizar sua Colisão -> Máscara, deixando a máscara vazia.

|imagem 6|

Estas configurações significam que os monstros se moverão uns através dos outros. Se você quiser que os monstros colidam e deslizem uns contra os outros, ligue a máscara de "inimigos".

Nota

Os inimigos não precisam mascarar a camada "mundo" porque só se movem no plano XZ. Nós não aplicamos nenhuma gravidade a eles no desenvolvimento.

Pulando

O próprio mecanismo de salto requer apenas duas linhas de código. Abra o script Player. Precisamos de um valor para controlar a força do salto e atualizar _physics_process() para codificar o salto.

Após a linha que define fall_acceleration, no topo do script, adicione o jump_impulse.

#...
# Vertical impulse applied to the character upon jumping in meters per second.
export var jump_impulse = 20

Dentro de _physics_process(), adicione o seguinte código antes da linha onde chamamos move_and_slide().

func _physics_process(delta):
    #...

    # Jumping.
    if is_on_floor() and Input.is_action_just_pressed("jump"):
        velocity.y += jump_impulse

    #...

Isso é tudo que você precisa para pular!

O método is_on_floor() é uma ferramenta da classe KinematicBody. Ele retorna true se o corpo colidiu com o chão neste quadro. É por isso que aplicamos gravidade ao Player: para colidimos com o chão em vez de flutuar sobre ele como os monstros.

Se o personagem está no chão e o jogador pressiona "saltar", nós instantaneamente damos a eles muita velocidade vertical. Nos jogos, você realmente quer que os controles sejam responsivos e que dão aumentos instantâneos de velocidade como esses, embora irrealistas, são ótimos.

Observe que o eixo Y é positivo para cima. Isso é diferente de 2D, onde o eixo Y é positivo para baixo.

Esmagando monstros

Vamos adicionar a seguir a mecânica de esmagar. Vamos fazer o personagem saltar sobre os monstros e matá-los ao mesmo tempo.

Precisamos detectar colisões com um monstro e diferenciá-las das colisões com o chão. Para fazer isso, podemos usar a funcionalidade de marcação group do Godot.

Abra a cena Mob.tscn novamente e selecione o nó Mob. Vá para o painel à direita para ver uma lista de sinais. O painel tem duas guias: Sinais, que você já usou, e Grupos, que permite atribuir tags a nós.

Clique nele para abrir um campo onde você pode escrever um nome de tag. Digite "inimigo" no campo e clique no botão Adicionar.

image7

Um ícone aparece no painel Cena para indicar que o nó faz parte de pelo menos um grupo.

image8

Agora podemos usar o grupo do código para distinguir colisões com monstros de colisões com o chão.

Programando a mecânica de esmagar

Volte para o script Player para programar as ações de esmagar e saltar.

Na parte superior do script, precisamos de outra propriedade, bounce_impulse. Ao esmagar um inimigo, não queremos necessariamente que o personagem vá tão alto quanto ao pular.

# Vertical impulse applied to the character upon bouncing over a mob in
# meters per second.
export var bounce_impulse = 16

Em seguida, na parte inferior de ''_physics_process()'', adicione a seguinte repetição. Com ''move_and_slide()'', o Godot faz o corpo se mover às vezes várias vezes seguidas para suavizar o movimento do personagem. Então, temos que fazer uma repetição sobre todas as colisões que possam ter acontecido.

Em cada iteração da repetição, verificamos se caímos em um inimigo. Se assim for, nós o matamos e saltamos.

Com este código, se nenhuma colisão ocorrer em um determinado quadro, a repetição não será executada.

func _physics_process(delta):
    #...
    for index in range(get_slide_count()):
        # We check every collision that occurred this frame.
        var collision = get_slide_collision(index)
        # If we collide with a monster...
        if collision.collider.is_in_group("mob"):
            var mob = collision.collider
            # ...we check that we are hitting it from above.
            if Vector3.UP.dot(collision.normal) > 0.1:
                # If so, we squash it and bounce.
                mob.squash()
                velocity.y = bounce_impulse

São muitas funções novas. Aqui estão mais algumas informações sobre eles.

As funções get_slide_count() e get_slide_collision() vêm ambas da classe KinematicBody e estão relacionadas com move_and_slide().

get_slide_collision() retorna um objeto KinematicCollision que contém informações sobre onde e como ocorreu a colisão. Por exemplo, utilizamos sua propriedade collider para verificar se colidimos com um "inimigo", chamando is_in_group() no: collision.collider.is_in_group("mob").

Nota

O método is_in_group() está disponível em cada Node.

Para verificar se estamos pousando no monstro, utilizamos o produto vetorial pontual: Vector3.UP.dot(collision.normal) > 0.1. A colisão normal é um vetor 3D que é perpendicular ao plano onde ocorreu a colisão. O produto pontal nos permite compará-lo com a direção ascendente.

Com os produtos pontuais, quando o resultado é maior que 0, os dois vetores estão a um ângulo inferior a 90 graus. Um valor maior que 0.1 nos diz que estamos aproximadamente acima do monstro.

Estamos chamando uma função indefinida, mob.squash(). Temos que adicioná-la à classe do Inimigo.

Abra o script Mob.gd clicando duas vezes sobre ele no painel Sistema de Arquivos. Na parte superior do script, queremos definir um novo sinal chamado squashed. E na parte inferior, você pode adicionar a função squash, onde emitimos o sinal e destruímos o inimigo.

# Emitted when the player jumped on the mob.
signal squashed

# ...


func squash():
    emit_signal("squashed")
    queue_free()

Usaremos o sinal para adicionar pontos à pontuação na próxima lição.

Com isso, você deve ser capaz de matar monstros pulando sobre eles. Você pode pressionar F5 para experimentar o jogo e definir Principal.tscn como cena principal do seu projeto.

No entanto, o jogador ainda não morre. Vamos trabalhar nisso na próxima parte.