Godot exemplo passo a passo

De Aulas

Links relacionados: Jogos Digitais

Dodge the Creeps!

Godot dodge preview.gif

Configuração do Projeto

Baixar o arquivo dodge_assets.zip

Alterar a resolução Width 480 e Height 720:

Projet -> Project Settings -> Display -> Window

Organização do Projeto

Scenes
  • Main
  • Player
  • Mob
  • HUD

A pasta principal res://:

Godot filesystem dock.png

Player

Node Structure

  • clique No botão + e crie um novo Node Area2D com nome Player:
Godot add node.png

Clique no icone a direita para travar o objeto (para não alterar o tamanho acidentalmente).

Godot lock children.png

Salvar a scene em Scene -> Save ou Ctrl+S.

Animação de Sprite

Clique em Player e adicione um node AnimatedSprite.


Tpl note.png

Isso porque um AnimatedSprite requer um recurso de SpriteFrames, que é uma lista de animações que podem ser apresentadas.

Para criar uma animação:

  1. procure pela propriedade Frames no Inspector;
  2. clique <null> -> New SpriteFrames;
  3. no mesmo local, clique em <SpriteFrames> para abrir o painel para configuração dos SpriteFrames, conforme imagem abaixo.
Godot spriteframes panel.png

Na esquerda está localizada a lista das animações. Siga os procedimentos:

  1. clique na default e mude o seu nome para right;
  2. clique no botão adicionar para criar uma segunda animação chamada up;
  3. araste as duas imagens, conforme visto abaixo, para cada animação, nomeadas de playerGrey_up[1/2] e playergrey_walk[1/2] no painel Animation Frames.
Godot spriteframes panel2.png

Redimensione as imagens no node AnimatedSprite, na aba Inspector, em Transform -> Scale para (0.5, 0.5).

Godot player scale.png

Sdicione um CollisionShape2D como filho de Player.

Em Shape no Inspector, clique em <null> -> New CapsuleShape2D. Redimensione o desenho para cobrir o sprite:

Godot player coll shape.png


Tpl note.png

Não redimensione a figura para muito fora da imagem. Tente deixar o mais próximo do tamanho do sprite.

Godot player scene nodes.png

Movimentando o Player

Criar scripts: Clique no node Player e no botão adicionar script à direita:

Godot add script button.png

Deixar tudo no padrão e clicar em Create:

Godot attach node window.png

Declarar as variáveis para o nosso objeto:

extends Area2D

export (int) var SPEED  # how fast the player will move (pixels/sec)
var screensize  # size of the game window

A palavra-chave export na primeira variável SPEED nos permite setar seu valor no Inspector. Lá, clique no node Player e configure a propriedade speed para 400.

Godot export variable.png

A função _ready() é chamadad quando o node entra na scene tree, que é uma boa hora para saber o tamanho da janela do jogo:

func _ready():
    screensize = get_viewport_rect().size

A função _process() define o que o personagem faz e é chamada em cada frame. Usada para atualizar os elementos do nosso jogo.

Nesse jogo vamos:

  • Verificar as entradas (teclas direcionais);
  • Mover o personagem conforme a direção dada;
  • Rodar a animação correspondente.


Tpl note.png

veja que as funções acima constituem o gameloop: process_input -> execute -> render

func _process(delta):
    var velocity = Vector2() # the player's movement vector
    if Input.is_action_pressed("ui_right"):
        velocity.x += 1
    if Input.is_action_pressed("ui_left"):
        velocity.x -= 1
    if Input.is_action_pressed("ui_down"):
        velocity.y += 1
    if Input.is_action_pressed("ui_up"):
        velocity.y -= 1
    if velocity.length() > 0:
        velocity = velocity.normalized() * SPEED
        $AnimatedSprite.play()
    else:
        $AnimatedSprite.stop()

Criamos a variável velocity para obter a direção total

MAS, se segurar duas direcionais (ex: para cima e para direita) o objeto irá mais rápido.

Usar normalização para resolver

Verificamos se o jogador está se movendo para que possamos iniciar ou interromper a animação do AnimatedSprite.


Tplnote Bulbgraph.png

O sinal $ retorna o node do caminho relativo desse node, ou retorna null se o node não foi encontrado. Sendo que o node AnimatedSprite é filho do corrente node, nos podemos usar apenas $AnimatedSprite.

Na verdade, o sinal $ é um atalho para get_node(). Então, o código $AnimatedSprite.play() é o mesmo que get_node("AnimatedSprite").play().


Atualizar a posição do Player (clamp() não deixa o personagem sair da tela):

position += velocity * delta
position.x = clamp(position.x, 0, screensize.x)
position.y = clamp(position.y, 0, screensize.y)


Tplnote Bulbgraph.png

Fazer um clamping em um valor, significa restringi-lo à um dado alcance (range).

Clique em Play Scene ou pressione a tecla F6 e verifique que você consegue mover o personagem na tela para todas as direções.

Escolhendo as Animações

Mudando a animação no AnimatedSprite para corresponder à direção

right pode ser espelhada horizontalmente usando a propriedade flip_h para o movimento para a esquerda e a animação.

up pode ser espelhada verticalmente usando o flip_v para o movimento para baixo.

Vamos adicionar o seguinte código na função _process():

if velocity.x != 0:
    $AnimatedSprite.animation = "right"
    $AnimatedSprite.flip_v = false
    $AnimatedSprite.flip_h = velocity.x < 0
elif velocity.y != 0:
    $AnimatedSprite.animation = "up"
    $AnimatedSprite.flip_v = velocity.y > 0

Adicione a seguinte linha abaixo função _ready() para garantir que o personagem vai estar invisível quando o jogo começar.

hide()

Preparando para as Colisões

Queremos que o nosso personagem detecte quando ele se choca com os inimigos.

Mas não temos inimigos ainda!

Podemos utilizar um signal.

Adicione a linha abaixo no início do nosso script, depois de extends Area2d:

signal hit

O personagem emite um hit quando colidir algum inimigo.

Iremos usar o Area2D para detectar a colisão.

Selecione o node Player e clique na aba Node, ao lado da aba Inspector, para ver a lista de sinais que o nosso personagem pode emitir:

Godot player signals.png

Nosso hit aparece alí.

Como os inimigos serão nodes do tipo RigidBody2D, usaremos a função body_entered(Object body) que será chamado quando o corpo colidir com o nosso personagem.

Clique em Connect.. e então Connect novamente na janela Connecting Signal.

Não precisamos alterar qualquer configuração.

O Godot irá criar automaticamente a função chamada _on_Player_body_entered no script do nosso personagem.


Tplnote Bulbgraph.png

Ao conectar um sinal, em vez de fazer com que o Godot crie uma função pra você, você pode fornecer também o nome de uma função já existente, da qual deseja vincular o sinal

Adicione o seguinte código na função criada:

func _on_Player_body_entered( body ):
    hide() # Player disappears after being hit
    emit_signal("hit")
    $CollisionShape2D.disabled = true


Tpl note.png

Desabilitar a área de colisão significa que as colisões não serão mais detectadas. Assim, garantimos que o gatilho do signa hit não será acionado.

Adicione a função que será chamada para reinicializar o personagem quando um novo jogo inicia.

func start(pos):
    position = pos
    show()
    $CollisionShape2D.disabled = false

Inimigo (Mob)

Para criar a scene' do nosso inimigo, clique em Scene -> New Scene e renomeie para Mob.

A scene Mob irá usar os seguintes nodes:

Não se esqueça de configurar o Mob para não poder ser selecionado, assim como fizemos na scene Player.

Nas propriedades do RigidBody2D, configure a Gravity Scale para 0. Assim o mob não vai ficar caindo. Também, abaixo da sessão PhysicsBody2D, clique na propriedade Mask e retire a seleção da primeira caixa. Isso irá garantir que os mobs não irão colidir uns com os outros.

Godot set collision mask.png

Configure o AnimatedSprite tal como foi feito no Player. Veja que agora temos três animações: fly, swim e walk. Configure a propriedade Playing no Inspector para On e ajuste a Speed (FPS) tal como é visto na imagem abaixo. Iremos selecionar uma dessas animações randomicamente para que nossos mobs tenham uma certa variedade.

Godot mob animations.gif

O fly' deve ser setado para 3 FPS, e os outros dois para 4 FPS.

Assim como as imagens do Player, as imagens dos mobs precisam ser diminuídas. Para isso, configure a propriedade Scale do AnimatedSprite para (0.75, 0.75).

Em tempo, tal como na scene Player', adicione uma CapsuleShape2D para as colisões. Para alinhar a figura com a imagem, será necessário alterar a propriedade Rotation Degrees para 90 sob o Node2D.

Script do Inimigo

Para começar, adicione um script para o Mob e coloque nele as seguintes variáveis:

extends RigidBody2D

export (int) var MIN_SPEED # minimum speed range
export (int) var MAX_SPEED # maximum speed range
var mob_types = ["walk", "swim", "fly"]

Iremos colocar um valor aleatório entre MIN_SPEED e MAX_SPEED para a velocidade que o mob se movimenta. Seria chato se todos eles se movimentassem com a mesma velocidade. No Inspector, altere essas variáveis para 150 e 250. Também teremos um vetor contendo os nomes das 3 animações que serão selecionadas aleatoriamente para cada mob.

Vamos arrumar nosso script _ready() para escolher aleatoriamente uma das 3 animações.

func _ready():
    $AnimatedSprite.animation = mob_types[randi() % mob_types.size()]


Tpl note.png

você precisa usar a função randomize() se quiser que a sequência de números aleatórios seja diferente a cada vez que a scene for executada. Iremos colocar o randomize() na nossa scene Main. Para retornar um número inteiro aleatório entre 0 e n-1, usaremos randi() %n.

Agora iremos fazer com que os mobs deletem a si mesmos quando eles saem da janela. Para isso, conecte o signal screen_exited() do node Visibility e adicione o seguinte código:

func _on_Visibility_screen_exited():
    queue_free()

Com isso completamos a scene Mob.

Principal (Main)

Agora chegou o momento de juntar tudo. Vamos criar uma nova scene ea dicionar um Node chamado Main. Clique no botão Instance e selecione sua scene já salva chamada Player.tscn.

Godot instance scene.png


Tpl note.png

Para aprender um pouco mais sobre instanciação, veja Instancing

Agora adicione os seguintes nodes como filhos de Main, e altere o nome deles em conformidade com o que é mostrado abaixo:

  • Timer (MobTimer): para controlar a criação de mobs;
  • Timer (ScoreTime): para incrementar a pontuação a cada segundo;
  • Timer (StartTimer): para dar um tempinho depois do início;
  • Position2D (StartPosition): para indicar a posição inicial do personagem do jogador.

Depois configure a propriedade Wait Time de cada um dos nodes Timer conforme as especificações a seguir:

  • MobTimer: 0.5;
  • ScoreTimer: 1;
  • StartTimer: 2.

Ainda, altere a propriedade One Shot do StartTimer para On e altere a Position do node StartPosition para (240, 450).

Criação de Mobs (spawning)

O node Main irá criar novos mobs que queremos que apareça em uma posição aleatória fora da tela. Adicione um node Path2D chamado MobPath como filho de Main. Quando você selecionar o Path2D, verá que aparecem alguns novos botões em cima do editor:

Godot path2d buttons.png

Selecione o botão do meio (Add Point e desenhe o caminho clicando nos cantos da cena. Certifique-se de que o Snap to Grid está selecionado. Essa opção pode ser encontrada sob o botão Snapping options à esquerda do botão Lock, aparecendo como uma série de três pontos verticais.

Godot draw path2d.gif


Tpl note.png

Desenhe o caminho no sentido horário, ou seus mobs irão aparecer indo para fora e não para dentro!

Depois de colocar o ponto 4 na imagem, clique no botão Close Curve e as linhas irão se completar.

Agora que o path está definido, adicione um node PathFollow2D como filho de MobPath e o renomeie para MobSpawnLocation. Este node irá rotoacionar automaticamente e seguir o caminho conforme ele se move. Isso serve para que possamos usá-lo para selecionar uma posição e direção aleatórias ao longo do caminho.

Main Script

Adicione um script para a scene Main. No início do script vamos usar um export (PackedScene) para nos permitir escolher a scene Mob que queremos instanciar.

extends Node

export (PackedScene) var Mob
var score

func _ready():
    randomize()

Arraste o Mob.tscn do painel FileSystem para a propriedade Mob sobre o Script Variables do node Main.

func game_over():
    $ScoreTimer.stop()
    $MobTimer.stop()

func new_game():
    score = 0
    $Player.start($StartPosition.position)
    $StartTimer.start()

Agora conecte o signal timeout() de cada um dos nodes Timer. O StartTimer irá iniciar os outros dois timers. ScoreTimer irá incrementar a pontuação em 1.

func _on_StartTimer_timeout():
    $MobTimer.start()
    $ScoreTimer.start()

func _on_ScoreTimer_timeout():
    score += 1

Na função _on_MobTimer_timeout() iremos criar uma instancia mob colocando ela em uma posição inicial aleatória por meio do Path2D e configurando a movimentação do mob.

O node PathFollow2D irá automaticamente rotacionar o mob para seguir um caminho específico.

Veja que a nova instância precisa ser adicionada à scene usando um add_child().

func _on_MobTimer_timeout():
    # choose a random location on Path2D
    $MobPath/MobSpawnLocation.set_offset(randi())
    # create a Mob instance and add it to the scene
    var mob = Mob.instance()
    add_child(mob)
    # set the mob's direction perpendicular to the path direction
    var direction = $MobPath/MobSpawnLocation.rotation + PI/2
    # set the mob's position to a random location
    mob.position = $MobPath/MobSpawnLocation.position
    # add some randomness to the direction
    direction += rand_range(-PI/4, PI/4)
    mob.rotation = direction
    # choose the velocity
    mob.set_linear_velocity(Vector2(rand_range(mob.MIN_SPEED, mob.MAX_SPEED), 0).rotated(direction))


Tpl note.png

Nas funções que trabalham com ângulo, o GDScript usa radianos e não graus. Se você se sentir mais confortável para trabalhar com graus, será necessário utilizar as funções deg2rad() e ra2deg() para fazer a conversão entre os dois.

HUD

A HUD (do inglês: heads-up display - tela de alerta) é a sigla para representação dos objetos do jogo, tais como: vida, magia, pontos de experiência (XP), etc.

Como parte final para nosso jogo, vamos criar uma HUD. Nela teremos informações como pontuação, mensagem Game Over e um botão para reinicializar o jogo. A HUD será nossa interface do usuário (UI - User Interface).

Para isso, crie uma nova scene e adicione um node CanvvasLayer chamado HUD.

O node http://docs.godotengine.org/en/3.0/classes/class_canvaslayer.html#class-canvaslayer CanvasLayer] vai nos permitir desenhar nosso elementos da interface do usuário em cima do resto do jogo, ou seja, as informações irão aparecer na frente de todos os outros objetos do jogo.

A HUD terá as seguintes informações:

  • Pontuação, que será alterada pelo ScoreTimer;
  • Uma mensagem, tal como Game Over ou Get Ready!;
  • Um botão Start para iniciar o jogo.

O node básico para os elementos da nossa UI é o Control. Para criar nossa UI, usaremos dois tipos de nodes Control: Label e Button.

Crie os seguintes nodes filhos do node HUD:

  • Label chamado ScoreLabel;
  • Label chamado MessageLabel;
  • Button chamado StartButton;
  • Timer chamado MessageTimer.


Tpl note.png

Âncoras e Margens: Os nodes de Control têm uma posição e tamanho, mas também possuem âncoras e margens. As âncoras definem a origem - ponto de referência para as bordas do node. As margens são atualizadas automaticamente quando você move ou redimensiona um node Control. Eles representam a distância das bordas do node de Control até sua âncora. Veja Design interfaces with the Control nodes para saber mais detalhes.

Organize os nodes tal como mostrado na figura abaixo. Clique no botão Anchor (âncora) para configurar a âncora do node do Control.

Godot ui anchor.png

Você pode arrastar os nodes para os lugares manualmente ou pode, mais precisamente, usar as seguintes configurações:

ScoreLabel
  • Layout: “Center Top”
  • Margin:
    • Left: -25
    • Top: 0
    • Right: 25
    • Bottom: 100
  • Text: 0
MessageLabel
  • Layout: “Center Bottom”
  • Margin:
    • Left: -100
    • Top: -200
    • Right: 100
    • Bottom: -100
  • Text: Dodge the Creeps!
StartButton
  • Layout: “Center”
  • Margin:
    • Left: -60
    • Top: 70
    • Right: 60
    • Bottom: 150
  • Text: Start

A fonte padrão para os nodes Control são muito pequenas e não ficam muito bem quando redimensionadas. Há uma fonte inclusa nos assets do jogo chamada Xolonium-Regular.ttf. Para usar essa fonte, faça o seguinte procedimento para cada elemento da árvore do node Control:

1. Sob o Custom Fonts, escolha New DynamicFont

Godot custom font1.png

2. Clique no DynamicFont adicionado, em Font Data escolha Load e selecione o arquivo Xolonium-Regular.ttf. Você precisa também alterar o tamanho da fonte em Size para o valor 64.

Godot custom font2.png

Agora adicione o script abaixo para o HUD:

extends CanvasLayer

signal start_game

O signal start_game irão informar ao node Main que o botão foi pressionado.

func show_message(text):
    $MessageLabel.text = text
    $MessageLabel.show()
    $MessageTimer.start()

Essa função é chamada quando queremos mostrar uma mensagem temporariamente, tal como a Get Ready. Em MessageTimer, altere o Wait Time para 2 e altere a propriedade One Shot para On.

func show_game_over():
    show_message("Game Over")
    yield($MessageTimer, "timeout")
    $StartButton.show()
    $MessageLabel.text = "Dodge the\nCreeps!"
    $MessageLabel.show()

Essa função é chamada quando o jogador perde. Isso irá mostrar a mensagem Game Over por 2 segundos e retornar para a tela de título, que irá mostrar o botão Start.

func update_score(score):
    $ScoreLabel.text = str(score)

Essa função é chamada no Main sempre que a pontuação for alterada.

Conecte o signal timeout() da MessageTimer e o signal pressed() do StartButton.

func _on_StartButton_pressed():
    $StartButton.hide()
    emit_signal("start_game")

func _on_MessageTimer_timeout():
    $MessageLabel.hide()

Conectando o HUD ao Main

Com nossa scene HUD criada, salve-a e volte para Main'. Instancie a scene HUD em Main tal como foi feito com Player e coloque-o bem abaixo na árvore. A árvore completa deve se parecer com a apresentada abaixo. Verifique se não está faltando nada:

Godot completed main scene.png

Depois precisaremos conectar as funcionalidades da HUD ao nosso script do Main. Isso requer algumas adições na scene Main.

Na aba Node, conecte o signal start_game do HUD à função new_game() do script da scene Main.

Em new_game(), atualize a pontuação para mostrar a mensagem Get Ready:

$HUD.update_score(score)
$HUD.show_message("Get Ready")

Em game_over() precisaremos chamar a função correspondente do HUD:

$HUD.update_score(score) $HUD.show_game_over() $HUD.show_message("Get Ready")

Finalmente, adicione em _on_ScoreTimer_timeout() as linhas abaixo. Elas servirão para manter em sincronia a pontuação com a informação da pontuação na tela.

$HUD.update_score(score)

Agora você está pronto para jogar! Clique no botão Play the Project. Você deverá informar a scene principal, então escolha a Main.tscn.

Finalizando

Temos agora um jogo completamente funcional, mas ainda estão sobrando alguns elementos para dar uma refinada na experiência do jogo. Sinta-se livre para alterar o gameplay com suas próprias ideias.

Fundo

Se você não achar fundo cinza padrão muito atraente, você pode alterar a cor. Uma forma de fazer isso é usar um node ColorRect. Crie-o como primeiro node filho de Main, para que ele seja o primeiro node de Main a ser desenhado. Caso contrário, ele será desenhado em cima dos outros nodes. O node ColorRect possui apenas uma propriedade, a Color. Escolha a cor que te agradar melhor e redimencione o tamanho do ColorRect para que ele cubra a tela.

Efeitos Sonoros

Sons e música podem ser adicionados para dar mais efeitos e melhorar a experiência do jogo. No seu game, na pasta assets, você vai encontrar dois arquivos de som: "House In a Forest Loop.ogg" para a música de fundo e "gameover.wav" para quando o jogador perder.

Adicione dois nodes AudioStreamPlayer como filhos de Main. Renomeie-os para Music e DeathSound. Em cada um deles, clique na propriedade Stream, selecione Load e escolha o arquivo de áudio correspondente.

Para tocar a música, adicione $Music.play() na função new_game() e $Music.stop() na função game_over().

Finalmente, adicione $DeathSound.play() na função game_over().

Partículas

Como um último toque visual, vamos adicionar um efeito que deixa um rastro que segue o movimento do personagem do jogador. Escolha seu scene Player e adicione um node Particules2D chamado Trail.

Existem várias propriedades que você pode trabalhar para configurar partículas. Sinta-se livre para experimentar e criar diferentes efeitos. Para esse jogo de exemplo, usaremos a seguinte configuração:

Godot particle trail settings.png

Você também precisa criar um Material clicando em <null> e então New ParticlesMaterial. As configurações para ele serão as seguintes:

Godot particle trail settings2.png

Para criar um gradiente para as configurações da Color Ramp, iremos usar um gradiente que usa o alpha (transparência) do sprite de 0.5 (semitransparente) até 0.0 (totalmente transparente).

Clique em New GradientTexture e, abaixo de Gradient, clique em New Gradient. Você irá ver a janela abaixo:

Godot color gradient ui.png

As caixinhas a esquerda e a direita representam a cor inicial e final. Clique em cada uma delas e no quadro maior na direita para escolher a cor. Para a primeira cor, defina o valor de A (alpha) para mais ou menos na metade. Para o segundo, defina o todo para 0.


Tpl note.png

Veja também Particles2D para maiores detalhes sobre o uso de efeitos de partículas.


Arquivos do Projeto

Você pode encontrar uma versão completa desse projeto aqui: https://github.com/kidscancode/Godot3_dodge/releases


Tpl note.png

Esse tutorial é uma tradução livre de um trecho da documentação do Godot Engine.