Introdução ao desenvolvimento de jogos com Godot: Fundamentos e práticas - Parte 2

  512 vizualizações

Fev 12, 2024 por Calango Team

Neste tutorial, daremos continuidade ao nosso estudo de introdução aos jogos digitais usando a Gogodt Game Engine, GGE ou somente GODOT, sendo a nossa segunda parte onde iniciaremos a programação, se você já tem a cultura da programação, algumas partes podem ser ignoradas, mas seria interessante uma revisão e análise de diferenças na abordagem de jogos, principalmente se você vem de outra área de programação, como softwares organizacionais ou da indústria, como CRM, PDV, etc..., existem algumas nuanças na programação de jogos, o mesmo vale para o indivíduo que sai dos jogos e vai para as outras áreas mencionadas, sempre tem algo novo e diferente, seja como for, para seguir deste ponto em diante é fundamental que você faça a primeira parte deste treinamento, que é um artigo anterior.

Abaixo você tem um card de chamada para nosso treinamento, em sua primeira parte, cards ajudam na navegação entre artigos hiperlinkados, caso não tenha realizado a parte anterior é sugerido que o faça, pois o material utilizado e as configurações iniciais são realizadas nele, sem isso você provavelmente ficará perdido. Se já o realizou, pode prosseguir os estudos.


Introdução ao desenvolvimento de jogos com Godot: Fundamentos e práticas - Parte 1

O que será abordado na primeira parte? Introdução a Godot Game Engine, interface, Hierarquia e nós, Animação, Mapas e ladrilhos, Câmera, Colisão e mais.

Requisitos: Informática Básica

Iniciar


Cronograma de estudos

Introdução ao GDScript

A GGE utiliza a linguagem de programação GDScript que é baseada na linguagem Python, resumidamente, nosso código de programação deve respeitar uma cosia chamada "indentação", que significa que um espaço no começo de uma linha indica uma demarcação de estrutura interna... ficou difícil, calma, vamos chegar lá, mas veja o exemplo a seguir:

COMANDO-A
COMANDO-B

ESTRUTURA A
    COAMANDO A

Facilmente entendemos que na linha 1 temos um comando, na linha 2 outro comado, seja ele qual for, mas repare que a estrutura na linha 4 internamente tem um comando, como sei que é interno? Pois existe um espaço ou melhor uma tabulação para dentro da estrutura, este espaço a frente do comando da linha 5, indica que ele pertence a estrutura iniciada na linha 4, logo a ESTRUTURA A está "aninhando" (de ninho mesmo,  de acolher) um COMANDO A.

Veja a segunda analogia:

COMANDO-A
COMANDO-B

ESTRUTURA A
    COMANDO-A
    COMANDO-B

Novamente, no novo trecho de código temos mais uma indentação de um comando, agora na linha 6, sendo assim as linhas 5 e 6 estão dentro da estrutura da linha 4, ou melhor, aninhadas a ESTRUTURA A. Podemos ter estruturas mais elaboradas e com estruturas internas, uma dentro da outra:

COMANDO-A
COMANDO-B

ESTRUTURA A
    COMANDO-A
    COMANDO-B
    INTERNA-ESTRUTURA
        COMANDO-A

A nova estrutura na linha 7, tem comando interno a ela na linha 8, logo, a INTERNA_ESTRUTURA aninha um comando. Mas perceba que essa nova estrutura pertence a estrutura iniciada na linha 4. Lendo diretamente, significa que ao adentrarmos a ESTRUTURA A na linha 4, será executado dois comandos e adentraremos a INTERNA-ESTRUTURA que executará seu comando interno, essa é a lógica de execução.

Todavia, caso algo aconteça e impeça que executemos a ESTRUTURA A, nem seus comandos ou estruturas internas irão executar. Logo o aninhamento traz comandos e estruturas de cunho lógico, que só fazem sentido de serem executados se sua estrutura mais externa for chamada ou executada.

Essa forma de apresentação mais subjetiva pode não fazer sentido agora, mas a fiz para você reaver esta leitura quando necessitar. Mas acredite, esta é a lógica de aninhamento e indentação para o GDScript e para o Python.

Variáveis

Uma variável é uma porção de memória que podemos salvar ou persistir nossas informações, de forma mais definitiva ou temporária. Uma variável guarda durante o nosso jogo valores como pontos de vida, quantidade de moedas, a posição do jogador, dentre outras informações. Como já é prática nossa, vamos direto para a GGE e começar a fazer os primeiros experimentos com variáveis. Para criar um Script é bem simples, clique no nó do Player que iremos programar, clique no ícone de uma folha (script) ou pelo menu de ações (Botão secundário do mouse) acione a ação Adicionar Script, vide Figura 1.

Figura 1. Comandos de criação de Sript.

Quando realizada a ação será apresentada uma caixa de diálogo, vide Figura 2, nela selecionamos a linguagem (idioma) 1, em nossos estudos será apenas o GDScript, por padrão a classe de herança 2 é a classe do nó, isso é um conhecimento de orientação a objetos, não vamos aprofundar agora, no momento,  aceite que as possibilidades e funcionalidades de cada nó são acessíveis via programação, logo, dependendo do nó que você está adicionando um script ele pode ter mais ou menos funções que são herdadas da classe que constitui tal nó. Agora, atenção no caminho 3, pois se você recordar, criamos uma pasta específica para nossos códigos, a pasta Scripts, agora é só clicar em Criar 4.

Figura 2. Criando um Sript em um nó.

No ato da criação, você será redirecionado a viewport Scripts para codificar seu novo script, deixe o seu código como a apresentado:

extends KinematicBody2D

# Exemplo de declaração de comentários.
# o cerquilha inicia um comentário de uma linha
"""
Aqui termos um comentários de múltiplas linhas
com quebra de linhas, para textos e documentação mais extensa
"""

var a = 2 #aqui temos uma variável do tipo inteiro
var b = "text"  # aqui uma variável contendo um texto
var LOVE = "Amor"
var LoVE = "Amor"


# função cahamda ai iniciar o jogo
func _ready():
	print("Imprimindo no terminal")
	print(a, b, LOVE, LoVE)
	print(a, " ", b, " ", LOVE, " ", LoVE)
	# pass ou passar é um comando que pula a etapa

Lendo nosso código perceba que o mesmo acompanha muita exemplificação em marcações denominadas comentários, sendo toda linha iniciada com # (cerquilha) ou múltiplas linhas circundadas com """ (aspas triplas), isso não é entendido como código pela GGE, usamos isso para fazer anotações, chamar atenção e documentar o programa para nós programadores, isso não faz parte do que vai para os jogadores. Logo, sempre que precisar fazer uma observação, use comentários.

As linhas 10 a 13 declaram variáveis, veja que definimos variáveis com valor inteiro na linha 10, texto linha 11 a 13, podemos ainda ter valores reais (1.45353 por exemplo), lógicos (true que equivale a verdadeiro oi 1 e false que equivale a falso ou 0) e estruturas como vetores e matrizes. Uma coisa a se saber é que assim como o Python, o GDScript é case sensitive (sensitivo a maiúsculas e minúsculas), logo a palavra LOVE, LoVE ou love são coisas diferentes para a linguagem. Observe as linhas 12 e 13, elas guardam o mesmo valor, mas são variáveis diferentes. Evite acentuações e caracteres especiais além do _ (underline).

Como observado, para declarar uma variável colocamos o termo var seguido de um nome para esta variável, assim atribuímos = (sinal igual ou como denominamos o símbolo, atribuição) um valor. De forma sintática:

var nomeVariavel = valor

Imprimindo saídas no terminal

Para verificar qual valor cada variável guarda, temos uma função na linha 17, a função _ready(), o termo func indica que uma estrutura de função será criada, _ready é o nome desta função e os parênteses () trazem os parâmetros para satisfaze-la, no caso _ready não tem nada entre parênteses, logo, não possui parâmetros e os dois pontos : indicam o início de uma estrutura, demarca onde nossa função começa, como você aprendeu os comandos das linhas 18 a 20 estão aninhados (veja a indentação) a _ready(), logo serão executados quando a função for chamada.

Agora, _ready() é uma função especial, pois ela já é ativa da nossa GGE, significa que ela já existe e será chamada sempre que o a cena carregar este nosso nó, uma única vez, no momento que ele é inserido em nosso jogo, pois é uma função de inicialização. O que fizemos foi aninhar comandos a esta função. No caso colocamos o comand print, que imprime informações no terminal de Saída, isso é um recurso para nós desenvolvedores depurarmos nossos jogos e obtermos informações.

Figura 3. Viewport de Scripts, codificação e depuração.

Agora execute nossa cena, você terá algo como apresentado na Figura 3. No Painel de Cena/Hierarquia ao selecionar um nó, a partir do momento que este possui um script vinculado 2, podemos remover o mesmo clicando no símbolo de script 1, agora sinalizando associação, ação possível também através do menu de ações (Botão secundário do mouse), na opção Remover Script. No menu desta viewport, temos um menu 3 com uma variedade de opções comuns a programação, mecanismos de busca, formatação de código, abertura de outros arquivos e ações afins, hiperlink para Documentação Online 4 e o Pesquisa Ajuda 5 que é muito útil, pois abre código das classes nativas e tudo mais para consulta e exploração. À medida que abrimos novos arquivos eles são colocados na lista da viewport 6, assim como suas funções internas que durante a navegação entre um arquivo e outro são também listadas 7 para facilitar encontrar uma função especifica, podemos filtrar arquivos e funções durante nossa navegação. Por fim temos o terminal de Saída 8, onde podemos ver a impressão de nosso código exemplo.

Aqui vai nosso código mais atualizado:

extends KinematicBody2D

# Exemplo de declaração de comentários.
# o hashtag inicia um comentário de uma linha
"""
Aqui termos um comentários de múltiplas linhas
com quebra de linhas, para textos e documentação mais extensa
"""

var a = 2 #aqui temos uma variável do tipo inteiro
var b = "text"  # aqui uma variável contendo um texto
var LOVE = "Amor"
var LoVE = "Amor"
var boolean = true # troque para false depois
var real = 12.456



# função chamada ai iniciar o jogo
func _ready():
	print("Imprimindo no terminal")
	print(a, b, LOVE, LoVE)
	print(a, " ", b, " ", LOVE, " ", LoVE)
	print(boolean, real)
	# pass ou passar é um comando que pula a etapa

Operações aritméticas

Neste ponto em diante, por mais que adore exemplificar, preciso da sua expertise, você deve codificar e se assegurar que entendeu. Por isso, serei um pouquinho mais breve, pois a partir de agora o lance é programar.

Operações aritméticas compreendem a capacidade de fazer cálculos diversos como adição, subtração, multiplicação, divisão e outras, também de associar o conhecimento matemático. Vejamos exemplo de operações básicas, você pode continuar a fazer isso modificando o Player.gd é só apagar o que está lá e colocar este novo código, estamos aprendendo, sua colinha já está no site:

extends KinematicBody2D

#vamos entender como as operações aritméticas
var a = 10 # inteiro
var b = 3.0 #real

var grausCelsius = 36 #mude este valor para testes

func _ready():
	# realização de operações aritméticas 
	print(a + b) # soma
	print(a - b) # subtração
	print(a / b) # divisão real
	print(a * b) # multiplicação
	# castings (conversão de valor): int(v) converte v para inteiro float(v), converte v para um valor real
	print(a / int(b)) # divisão inteiros
	print(a % int(b)) # módulo, pega o resto da divisão
	
	# ordem de precedência matemática 
	# fórmula "0 °C + 273,15 = 273,15 K" que converte graus Celsius em Kelvin
	var formula = grausCelsius + 273.15
	print(grausCelsius, " C equivalem a ", formula, " kelvin.")
	# agora (0 °C × 9/5) + 32 = 32 °F, Clesius para Fahrenheit
	# como já declaramos a variável formula e só estamos reutilizando para outro cálculo, dispensa var, pois ela já existe
	formula = (grausCelsius * 9/5) + 32 
	print(grausCelsius, " C equivalem a ", formula, " fahrenheit.")
	# perdemos ponto flutuante (casas decimais) que pode gerar arredondamentos indesejados ou perca de dados
	# nestes casos, para evitar casting, simplesmente coloque os valores reais possíveis em real direto
	formula = (grausCelsius * 9.0/5.0) + 32.0
	print(grausCelsius, " C equivalem a ", formula, " fahrenheit.")

Comentei o código com carinho, estude-o. Ordem de precedência matemática, resolvemos os parênteses mais internos depois os mais externos em um fórmulas, a seguir a multiplicação, divisão... faça uma recapitulada nos cálculos!

Operações relacionais

São operações de comparação relacional, como maior, igual, menor, diferente:

extends KinematicBody2D

#vamos entender como as operações relacionais
var a = 10 # inteiro
var b = 3.0 #real

func _ready():
	# realização de operações aritméticas 
	print(a > b) # maior
	print(a >= b) # maior ou igual
	print(a < b) # menor
	print(a <= b) # menor ou igual
	print(a != b) # diferente
	print(a == b) # igual, não confundir com = que é atribuição

Os símbolos estão comentados, a exemplo a linha 9, estamos vendo a > b, ou seja a relação de comparação é "A é maior que B?" em seu terminal de saída você verá se essa relação é verdadeira (True) ou falsa (False).

Operações lógicas

São ações que nos permitem verificar a veracidade ou falsidade de uma sentença, sejam simples ou complexas.

São as principais ações de verificação lógica:

  • AND (e)
  • OR (ou)
  • NOT (Não)

Para entender melhor estas operações veja a tabela verdade a seguir:

Tabela 1. Tabela verdade.

p q p AND q p OR q
falso falso falso falso
falso verdadeiro falso verdadeiro
verdadeiro falso falso verdadeiro
verdadeiro verdadeiro verdadeiro verdadeiro

Na Tabela 1, temos duas preposições quais queres, p e q, veja que se uma delas for falsa na operação lógica AND, toda a sentença passa a ser falsa, ou seja, todas preposições envolvidas em uma relação AND tem que ser verdadeiras para que toda a sentença passe a ser verdadeira. Agora na relação OR, se ao menos uma das preposições for verdadeira, a sentença passa a ser verdadeira. Veja um exemplo prático:

extends KinematicBody2D

#Programa para ver obrigação eleitoral
var idade = 68 #mude a idade para ver novos resultados
var votou = false  # mude para ver novos comportamentos

func _ready():
	# ! é igual a NOT, podemos escrever "!votou" ou "not votou"
	# estamos negando o votou, ou seja, se for true, passa a ser false e vice versa.
	if idade >= 18 and !votou: # se tem idade e não votou, precisa
		print("Você precisa votar rapzinho!")
	
	if idade >= 18 and votou: # se tiver idade e já votou não pode mais
		print("Malandinho tem idade, mas já votou")
	
	if idade >= 68:
		print("Voto facultativo")

Faça alguns testes, mas se pararmos pra pensar, 68 anos ou mais não precisa ir votar, porque informamos que precisa, ae que está o estudo e aprimoramento de elementos lógicos, uma mudança no programa seria, entender que quem tem menos de 68 anos e 18 ou mais tem que votar, teríamos:

extends KinematicBody2D

#Programa para ver obrigação eleitoral
var idade = 67 #mude a idade para ver novos resultados
var votou = false  # mude para ver novos comportamentos

func _ready():
	# ! é igual a NOT, podemos escrever "!votou" ou "not votou"
	# estamos negando o votou, ou seja, se for true, passa a ser false e vice versa.
	if (idade >= 18 and idade < 68) and !votou: # se tem idade e não votou, precisa
		print("Você precisa votar rapazinho!")
	
	if idade >= 68:
		print("Voto facultativo")

Veja que na linha 10, temos primeiro duas sentenças a serem sanadas (idade >= 18 and idade < 68), afinal estão entre parênteses, depois o resultado desta sentença vira uma preposição a ser relacionada com and !votou. Vamos então ver os passos desta estrutura:

  1. idade >= 18, vamos chamar de preposição p
  2. idade < 68, preposição q
  3. !votou, preposição k
  4. primeira parte: (p and q), logo, p(67 >= 18, TRUE) and q(67 < 68, TRUE), logo TRUE and TRUE é igual a verdadeiro, vide Tabela 1.
  5. segunda parte: TRUE and k, logo TRUE and k(NOT false, contrário de false é true, logo, TRUE)
  6. TRUE and TRUE, resultado TRUE (verdadeiro), vide Tabela 1.

Logo se você é maior de 18 anos e tem menos de 68 anos, ainda não votou, precisa votar, essa é a sentença. Agora se tem 68 ou mais é facultativo. Pessoal, fiz o exercício pela memória, não liguem para a lei de votação aqui é só exemplo.

O negócio aqui é quebrar cabeça mesmo, estou exercitando vocês..... esse if é o que?

Estrutura condicional

É uma estrutura de seleção, que de acordo com uma sentença executa um bloco de instruções, a primeira delas que estudaremos é o if (se), que basicamente tem a seguinte estrutura

if <condições>:
    <COMANDOS>

Caso as condições ejam satisfeitas a estrutura irá executar seus comandos internos, foi o que aconteceu na prática anterior. Vamos a um exemplo:

extends KinematicBody2D

#ver idade
var idade = 67 #mude a idade para ver novos resultados

func _ready():
	if idade >= 18: 
		print("Você é de maior")

Simples, mas tem um problema, aqui só informamos as pessoas maiores de idade, neste caso a estrutura conta com um fluxo alternativo:

extends KinematicBody2D

#ver idade
var idade = 67 #mude a idade para ver novos resultados

func _ready():
	if idade >= 18:
		print("Você é de maior")
	else:
		print("Você é menor")

O else (senão) é executado sempre que condição do if não for satisfeita. Como você deve ter notado no exemplo anterior, o else é opcional para funcionamento do if.

Bom, vamos por partes, iniciamos aqui uma introdução a programação, caso tenha dificuldades nosso grupo no Discord pode ser uma boa fonte de estudo. Não vou apresentar todo o ABC da programação, mas vou introduzindo-a aos poucos, sempre me voltando a cumprir nosso projeto do tutorial.

Inputs (Entradas)

Agora que você compreendeu uma pequena fração da programação e estilo de se programar no GDScript, vamos voltar ao projeto. Abra agora o menu Projeto > Configurações do Projeto > Mapa de Entrada, nesta tela temos os botões de entrada do jogador, podemos ajustar o teclado, joystick entre outros dispositivos, por padrão temos já as principais teclas utilizadas em jogo configuradas, mas vamos colocar as nossas. Adicione a ação "pular" como indicado:

Figura 4. Adicionando ação no mapa de entradas.

Agora, na ação recém criada, insira o tipo de entrada para ela, em nosso exercício uma tecla:

Figura 5. Tipos de entradas para ações.

Faça o mesmo processo para teclas de moviemtnação para "direita" e "esquerda":

Figura 6. Ações criadas para nosso projeto.

Em nosso Palyer.gd, insira agora nosso código para o personagem:

extends KinematicBody2D

var aceleracao = 50

func _ready():
	print("Nasci pronto baby!")
	
func _process(delta):
	if (Input.is_action_pressed("esquerda")):
		move_and_slide(Vector2(-aceleracao, 0))
	
	if(Input.is_action_pressed("direita")):
		move_and_slide(Vector2(aceleracao, 0))

Com o fragmento acima, você irá conseguir mover seu personagem para direita e esquerda, tudo bem, provavelmente ele está flutuando, vamos trabalhar a física em breve. Você deve ter muitas perguntas, bom começamos pela variável aceleracao, que tem o valor 50, você pode modificar este valor que seria o multiplicador de aceleração para nosso personagem, ajustando assim a velocidade dele, enquanto a função _ready() roda apenas uma vez ao carregar, a função _process() executa a cada frame, então quando pressionamos um botão ela captura a mudança de estado entre estar ou não pressionando, validando as estruturas condicionais que monitoram a entrada em cada ação, executando a função interna move_and_slide (mova e deslizar), função da classe KinematicBody2D, como mencionado, a classe que seu script herda possui várias funcionalidades para facilitar nossa programação, esta função é feita justamente para movimentação de personagem, por isso a utilizamos.

O move_and_slide possui um parâmetro Vector2 (Vetor 2D) que é um vetor de duas posições X e Y respectivamente, então, na linha 10 e 13, movemos o personagem com o valor da aceleracao em X, o que muda é que para esquerda, o valor tem um sinal negativo na frente, lógico, pois queremos a mesma aceleração, mas em sentido oposto, o valor de Y é 0, pois estamos realizando movimento puramente horizontal.

Você reparou também que a estrutura if recebeu como expressão o valor Input.is_action_pressed("esquerda"), esquerda é a ação que nós cadastramos lá no Mapa de Entradas e colocamos a tecla A como recurso de entrada. Input é a classe que tem várias funcionalidades para lidar com as entradas do jogador, dentre estas funcionalidades, utilizamos a função is_action_pressed que recebe como parâmetro a ação em observância, logo, durante a execução da função _process(delta) que como verificamos, roda a cada frame, o is_action_pressed fica verificando se a ação esquerda foi acionada, o mesmo ocorre para direita.

Mas se você verificar, nosso personagem fica andando o tempo todo e para mesma direção, vamos sanar isso.

extends KinematicBody2D

var aceleracao = 50
onready var animSprite = $AnimatedSprite

func _ready():
	print("Nasci pronto baby!")
	
func _process(delta):
	if (Input.is_action_pressed("esquerda")):
		move_and_slide(Vector2(-aceleracao, 0))
		animSprite.flip_h = true
	
	if(Input.is_action_pressed("direita")):
		move_and_slide(Vector2(aceleracao, 0))
		animSprite.flip_h = false

Na linha 4 declaramos um variável denominada animSprite e ela recebe o valor $AnimatedSprite, que nada mais é que um nó filho de nosso Player, olhe o Painel de Cena/Hierarquia, antes da declaração temos uma definição onready (ao iniciar), então, a GGE quando cria nosso personagem, a partir desta definição, instância esta variável que recebe a referência do AnimatedSprite, com isso conseguimos acessar as funções da classe AnimatedSprite, como fazemos nas linhas 12 e 16, para fliparmos na horizontal (flip_h) o sprite, veja que funciona inteiramente.

Você até pode chamar direto, sem variável estas funções, chamando a referência diretamente, $AnimatedSprite.flip_h = true, mas na minha humilde experiência, isso em tempo de execução causa uns flicks (travadinhas) rápidos, mas testem.... Antes que me pergunte, sim, você poderia criar a variável animSprite, depois na função _ready() atribuir o valor de $AnimatedSprite, o uso do onready  é para abreviar este trabalho.

extends KinematicBody2D

var aceleracao = 100 # aceleração
var GRAVIDADE = 1200 # força da gravidade

var forcaPulo = -400
var pulando = false

var movimento = Vector2()

onready var animSprite = $AnimatedSprite

func _ready():
	print("Nasci pronto baby!")
	
func _process(delta):
	movimento.x = 0
	if Input.is_action_pressed("esquerda"):
		movimento.x -= aceleracao
		animSprite.flip_h = true
	
	if Input.is_action_pressed("direita"):
		movimento.x += aceleracao
		animSprite.flip_h = false
		
	#pulo
	if Input.is_action_just_pressed("pular"):
		pulando = true
		movimento.y = forcaPulo
	
	movimento.y += GRAVIDADE * delta
	
	#verifica que o pulo foi dado e quando atingirmos o solo a expressão toda retorna verdadeiro
	if (pulando and is_on_floor()):
		pulando = false
	
	move_and_slide(movimento, Vector2.UP) # somatório dos eventos

Uma dificuldade de enxergarmos de início em um motor gráfico é o tempo do delta, esse parâmetro que tem no _process(delta), vou resumir bem, o delta é uma fração do tempo da sua taxa de frames, exemplo, se você tem um game a 60 frames por segundo (FPS), o delta vai trazer o tempo de um frame no memento da execução do _process(), isso garante que se jogarmos em um PC mega rápido e em uma mais lento e mudarmos o FPS, o pulo e as velocidades envolvidas continuam com a mesma proporção, vamos aceitar isso por enquanto.

Nesta nova versão do programa, temos a GRAVIDADE, que age sob o corpo do personagem o fazendo cair, temos a forcaPulo, força do pulo quando efetuamos esta entrada, negativa, pois ele estará realizando força contrária a gravidade, uma variável flag (bandeira) para constatarmos que o usuário apertou ou não o pulo, pois só podemos pular, se encostarmos no chão. Criamos um Vetor2 denominado movimento que vai computar todas as entradas e fará a soma da gravidade exercida sob o corpo (nosso personagem), direção horizontal e pulo e só depois disso tudo realizamos a movimentação. Se analisarmos faz sentido, pois durante um jogo, o jogador pula e se move ao mesmo tempo, então em um frame, podemos gerar um cálculo de movimento mais robusto e só depois de validar estas entradas, movemos.

Na linha 17 sempre resetamos o movimento horizontal, valor X do vetor 2D movimento a cada loop (repetição), pois você já percebeu que o move_and_slide adiciona valor ao corpo em movimento, sendo acumulativo, quando vamos a direita ou a esquerda, fazemos a flipagem do sprite (linhas 20 e 24) e incrementamos (+= é um incremento, ou seja, some o valor a variável) ou decrementamos (-= é um decremento, subtrai o valor da variável), linhas 19 e 23. Na linha 27 ao pular, alteramos a variável pulando para true, informando ao nosso programa que o usuário pulou e que neste momento além do movimento horizontal entra junto a força de pulo, linha 28, independente de movimento, a cada frame temos a força da gravidade agindo, mas se aplicássemos toda ela de uma vez, nosso personagem iria sentir de forma dura essa valoração, por isso, a cada frame uma parte da gravidade é adicionada, por isso multiplicamos pelo valor do tempo do frame (delta), linha 31.

Durante a queda que a gravidade consequentemente vai propiciar, chega o momento que tocaremos o solo e é a função is_on_floor() que traz valor true se encostarmos no chão e false se estivermos no ar ou em queda, então a condição da linha 34 verifica primeiro se o usuário pulou (variável pulando) e se tocou o solo, se ambas proposições forem verdadeiras, a expressão se torna verdadeira e assim o programa reseta a variável pulando para false. Por fim, a linha 37 é executada com todos os valores ajustamos na variável movimento, tendo seus valores em X e Y computados, perceba que diferente de nosso código anterior, temos dois parâmetros na função move_and_slide, o primeiro é um vetor de movimentação e o segundo indica a direção do topo de nosso universo, o Vector2 tem estes presets predefinidos, Vector2.UP seria um Vector2(0, -1).

Se tudo estiver funcionando, seu personagem anda, pula e colide com seu mapa.

Figura 7. Personagem em movimento, com gravidade e ajustes.

Ajustando animações de personagem e estados

Bom, nós temos 4 situações possíveis para o personagem, andando, parado, pulando e caindo.

extends KinematicBody2D

var aceleracao = 100 # aceleração
var GRAVIDADE = 1200 # força da gravidade

var forcaPulo = -400
var pulando = false


var movimento = Vector2()

onready var animSprite = $AnimatedSprite

func _ready():
	print("Nasci pronto baby!")
	
func _process(delta):
	movimento.x = 0
	if Input.is_action_pressed("esquerda"):
		movimento.x -= aceleracao
		animSprite.flip_h = true
	
	if Input.is_action_pressed("direita"):
		movimento.x += aceleracao
		animSprite.flip_h = false
		
	#pulo
	if Input.is_action_just_pressed("pular") and is_on_floor():
		pulando = true
		movimento.y = forcaPulo
	
	movimento.y += GRAVIDADE * delta
	
	#verifica que  o pulo foi dado e quando atingirmos o solo a expressão toda retorna verdadeiro
	if (pulando and is_on_floor()):
		pulando = false
	
	
	if movimento.x != 0 and is_on_floor(): #verifica se existe movimento horizontal e se estamos no chão
		animSprite.animation = "Andando"
	elif movimento.y < 0: #pulando
		animSprite.animation = "Pulo"
	elif movimento.x == 0 and is_on_floor(): # parado
		animSprite.animation = "parado"
	else:
		animSprite.animation = "queda"
	
	move_and_slide(movimento, Vector2.UP) # somatório dos eventos

Olhe que interessante, conseguimos pela variável animSprite, alternar entre as animações que fizemos, digite o nome igual ao que você colocou em suas animações, o animSprite.animation recebe o nome da animação. Na linha 39 verificamos que se meu movimento horizontal (X) for diferente de 0, ou seja, tanto faz ser negativo quanto positivo e estou no chão, o personagem está em movimento, logo colocamos a respectiva animação, já em 41 como sabemos, a forcaPulo é negativa, enquanto existir tal força estamos acendendo o pulo, na linha 43, verificamos se não tem força horizontal de movimento e se estamos no chão, neste caso, estamos parados, por fim, se não estamos andando, pulando ou parados, estamos em queda.

O elif (então se) é uma estrutura complementar do if que nos permite aninhar um conjunto de validações, ele começa na primeira comparação da estrutura linha 39, se não passar na verificação, a estrutura passa para o primeiro elif (linha 41), se não validar para o seguinte e assim sucessivamente, agora se nenhum caso for satisfeito, o else (linha 45) entra em ação.

Com este ajuste fino, está bem mais realista e divertido!

Moedas

Agora vamos começar a criar mecânicas mais elaboradas, você entendeu como adicionar nós e animações, temos um spritesheet da moeda, logo, não vou fazer o passo a passo disso, vou te dar o caminho daqui para frente de algumas coisas que já fizemos!

Para criar a Moeda, você irá clicar no Mundo em seu Painel de Cenas/Hierarquia e adicionar um nó Area2D, dentro dele um AnimatedSprite, extraia frames do arquivo coin.png, depois adicione a Area2D um CollisionShape2D, ajuste a colisão, renomeie o nó Area2D para Moeda e deixa assim:

Figura 8. Painel de Cena/Hierarquia com a Moeda.

Você pode ajustar tamanho da Moeda se quiser e outros atributos como FPS e afins, coloquei ao lado do personagem para ver a proporção de tamanho, achei bacana assim:

Figura 9. Moeda com colisão e animação.

Instâncias de cenas, organizando nosso projeto

Você deve ter imaginado o seguinte, se tivermos 100 moedas em cena e criamos outra fase, terei que refazer a moeda? Se eu modificar ela, terei que sair trocando todas elas? Bom, do modo que estamos desenvolvendo sim, mas podemos subdividir nossa cena em cenas menores, pois na realidade é assim que se trabalha com GGE, temos cenas dentro de cenas, cada uma com nós nescessários, possibilitando o reuso e a divisão de tarefas!

Figura 10. Salvando um ramo de nós como cena.

Clique com o botão secundário do mouse em cima da Moeda e selecione no menu de ações a opção Salvar Ramo como Cena, perceba que a partir do momento que você salvar (use o diretório Cenas), salve como Moeda.tscn, depois disso veja que um símbolo de cena aparecerá ao lado da Moeda, se clicar nele, perceba que uma nova cena será aberta em outra aba da sua viewport, contendo os nós desta cena (a Moeda).

Figura 11. Abrindo uma cena para editar.

Separar ramos de nós em cenas nos possibilita duplicar a cena quantas vezes quiser, inserindo quantas moedas (instâncias) desejarmos em nosso nível, bastando clicar no símbolo Instanciar Cena Filha ou com o atalho Crtl + Shift + A, ou apenas pegando a nossa instância recém criada no Panel de Cena/Hierarquia e usando o atalho Crtl + D para duplicar. Coloque quantas moedas desejar:

Figura 12. Múltiplas instâncias em cena da Moeda.

Sinais

Para coletar nossas moedas, iremos abrir a cena Moeda.tscn e adicionar a Moeda um script, lembre-se de salvar na pasta Scripts, o nome do arquivo pode ser Moeda.gd. Com Moeda selecionada vá no Painel de Nó na aba Sinais e perceba que teremos acesso a várias funções de sinalização dos nós de nossas cenas, podendo associar os mesmos aos nossos scripts, para tanto, selecione o sinal body_entred e clique no mesmo.

Figura 13. Signals de nossos nós.

Na tela de conexão, associe o método a nosso script da moeda, o sinal irá gerar um método automaticamente dentro do Moeda.gd.

Figura 14. Conectando um signal a um nó com script.

Insira o seguinte código, perceba que removi os códigos que não utilizaremos par aficar mais enxuto:

extends Area2D

func _on_Moeda_body_entered(body):
	if body.get_name() == "Player":
		print("pegou a moeda")
		queue_free()

O método _on_Moeda_body_entered 3 foi criado com a conexão do signal body_entered, logo, toda vez que o sinal emitir resultado para seu acionamento o método conectado será acionado. Logo, quando algum corpo entra na Area2D ela emite o sinal, nossa função então verificar se é o nó Player 4, nosso jogador, imprime no prompt a frase "pegou a moeda" 5 e depois libera o objeto associado ao script (nossa moeda) da memória 6 com o comando queue_free().

Teste a ideia!

Coletando moedas

Para coletar moedas adicione uma variável chamada moedas ao Player.gd, ela servirá para armazenar quantas moedas coletamos no jogo:

[...]
export var moedas = 0
[...]

O termo export, exporta a variavel para o editor, assim podemos vê-la no editor ao selecionar nosso personagem, podendo modifica-la sem ficar tendo que abrir o código a todo momento, muito útil enquanto estamos desenvolvendo. Se quisermos testar se o código adiciona 1 vida a cada 100 moedas, por exemplo, iria demorar coleta-las, em seu teste você pode colocar 99 e ao pegar a próxima, testar se o script gera uma vida, isso somente para você desenvolvedor.

Figura 15. Variáveis do Script exportadas para o editor.

No código em Moeda.gd realize a seguinte modificação:

extends Area2D

func _on_Moeda_body_entered(body):
	if body.get_name() == "Player":
		body.moedas += 1
		print(body.moedas)
		queue_free()

Na atualização, deve ter ficado claro que o body é o nó Player, como agora ele tem a variável moeda, adicionamos 1 a cada moeda coletada 5 e imprimimos o estado da variável do jogador no terminal de saída 6 para verificarmos.

Figura 16. Saída de mensagens no terminal.

Vidas

Bom, se você chegou até aqui, parabéns! Evoluiu muito!!!

Agora para criar o recurso que aumenta o número de vidas, você fará o mesmo processo da moeda, criará um novo item Area2D e colocará animação e colisão como ensinado, variável vidas no personagem, script e signal de colisão de corpo atrelado a um método que aumenta o número de vidas e destrói o objeto de referência de vida. Lembre-se de desenvolver o recurso/asset de vida em outra cena, salvando os ramos necessários, nomeie a cena de Vida.tscn.

Figura 17. Item de vida.

Considerações finais

Primeiramente quero me desculpar com a demora. Estou muito atarefado com questões diárias e minha situação de deslocamento entre estados brasileiros. O tutorial era mais extenso, mas devido a demora em fazer artigos, resolvi resumir bem e entregar a vocês uma leitura completa e com um final.

Neste tutorial, o objetivo foi colocar você frente a frente com a lógica de programação e sua codificação, o GDScript é uma linguagem simples e muito intuitiva, sendo inspirada em linguagens modernas como Python. Caso deseje se aprimorar em desenvolvimento de jogos, irei colocar à disposição um curso de capacitação de Godot e Game Design:


Introdução ao curso multicampi de introdução aos jogos digitais

O curso foi desenvolvido para toda comunidade, sendo material de estudo, pesquisa e uma porta de entrada ao universo de criação de jogos digitais.

Requisitos: Saber utilizar o computador e instalar softwares.

Ir para o curso

Para aqueles que desejam realmente aprender a programar do básico, também deixo aqui um curso em Python:


Python

Curso de desenvolvimento com a linguagem Python, versão 3.

Requisitos: Saber utilizar o computador e instalar softwares.

Ir para o curso

Obrigado a todos e bons estudos!


Trabalho submetido 22 de Fevereiro de 2023 às 11:39, última modificação 12 de Fevereiro de 2024 às 13:13.
Marcadores: Tutorial   Equipamentos e recursos  

Licença Creative Commons
O trabalho Introdução ao desenvolvimento de jogos com Godot: Fundamentos e práticas - Parte 2 de Calango Team está licenciado com uma Licença Creative Commons - Atribuição-NãoComercial-CompartilhaIgual 4.0 Internacional.
Podem estar disponíveis autorizações adicionais às concedidas no âmbito desta licença em AUTORIZACOES.
0 Comentarios:

Procurar


Siga-nos

Itch.io Patreon Facebook Youtube Twitch TikTok
Quer colocar sua publicidade/apoio/parceria aqui!

Entre em contato pelo Facebook para conversar!