Introdução à Godot Engine

A Godot Engine é uma poderosa e flexível ferramenta de desenvolvimento de jogos de código aberto. Criada inicialmente por Juan Linietsky e Ariel Manzur, ela foi lançada em 2014 e rapidamente ganhou popularidade devido à sua acessibilidade e recursos robustos. Projetada para ser uma alternativa viável a outras engines de jogos proprietárias, a Godot se destaca por ser gratuita e por oferecer total liberdade aos desenvolvedores para criar, modificar e distribuir seus jogos sem restrições de licenciamento.

Características principais

editar
  1. Multiplataforma: A Godot permite o desenvolvimento de jogos para diversas plataformas, incluindo Windows, macOS, Linux, Android, iOS, sendo até mesmo viável exportar jogos para a Web. Isso facilita a publicação dos jogos em múltiplos dispositivos com pouco esforço adicional.
  2. Suporte a 2D e 3D: A engine possui ferramentas integradas para o desenvolvimento tanto de jogos 2D quanto 3D.
  3. Sistema de Scripts: A Godot utiliza sua própria linguagem de script, chamada GDScript, relativamente semelhante à Python. Além disso, também suporta outras linguagens como C#, VisualScript e C++ para quem prefere uma abordagem diferente ou mais performance.
  4. Cenas e Nós: A arquitetura da Godot é baseada em cenas e nós, em que cada cena é composta por uma hierarquia de nós. Esta estrutura modular permite uma grande flexibilidade e reutilização de componentes, facilitando a organização e o desenvolvimento do jogo. Esse aspecto será aprofundado mais à frente no tutorial.
  5. Desempenho: A Godot é leve e eficiente, projetada para rodar bem sem sacrificar a qualidade gráfica ou a complexidade do jogo.

Instalação da Godot

editar

Siga esse passo-a-passo para instalar a Godot na sua máquina:

  • Faça o download clicando no botão azul Godot Engine
  • Salve em algum lugar que você vai se lembrar
  • Extraia os arquivos do .zip que foi baixado
  • Abra o executável

Interface da Godot

editar

Estes são os pontos principais sobre a interface da Godot:

  • Barra de Menus: Localizada no topo, contém menus principais como "Scene", "Project", "Debug", "Help", etc., oferecendo acesso rápido a diversas funcionalidades e configurações do projeto.
  • Viewport: A área central onde os desenvolvedores podem visualizar e editar suas cenas em 2D ou 3D. Inclui ferramentas para navegação, zoom, e seleção de objetos.
  • Scene Dock: À esquerda, exibe a hierarquia de nós da cena atual, permitindo adicionar, remover e organizar nós facilmente.
  • FileSystem Dock: À esquerda, abaixo do Scene Dock, mostra a estrutura de arquivos do projeto, facilitando o acesso a recursos como scripts, imagens, e cenas.
  • Inspector: À direita, exibe as propriedades do nó ou recurso atualmente selecionado, permitindo ajustes detalhados e específicos.
  • Node Dock: À direita, abaixo do Inspector, exibe os nós filhos e as conexões de sinais do nó selecionado, ajudando na organização e interação entre os nós.
  • Script Editor: Localizado na parte inferior ou como uma aba separada, é um editor de código integrado com destaque de sintaxe, auto-complete, e ferramentas de depuração para escrita de scripts em GDScript, C#, ou outras linguagens suportadas.
  • Animation Player: Um editor de animações integrado que permite criar e editar animações para objetos, ajustando propriedades ao longo do tempo.
  • Output Panel: Localizado na parte inferior, exibe mensagens de log, erros, e outras informações úteis durante o desenvolvimento e depuração.
  • Toolbar: Contém botões para operações comuns como salvar, rodar o projeto, mudar entre modos 2D e 3D e ajustar o layout da interface.
  • Asset Library: Provém acesso direto a uma biblioteca de recursos externos que podem ser integrados ao projeto, incluindo plugins, assets gráficos e scripts.
  • Editor Settings: Permite personalizar a interface do editor, incluindo temas, atalhos de teclado, e outras preferências do usuário.

Esquema de nós

editar

O sistema de nós (nodes) da Godot consiste no núcleo da sua arquitetura de desenvolvimento, responsável por oferecer uma maneira flexível e modular de criar jogos e aplicações. Neste esquema, tudo no jogo ou aplicação é um nó, sendo esses nós organizados em uma árvore de cena (scene tree). Cada nó possui uma funcionalidade específica e pode ser combinado com outros para criar comportamentos complexos. Abaixo está uma descrição detalhada desse sistema.

Nó Básico (Node)

editar
  • O nó básico é a unidade fundamental de construção no Godot.
  • Serve como a classe base para todos os outros tipos de nós.
  • Pode ser adicionado à árvore de cena e possui métodos para gerenciamento de hierarquia, sinais e grupos.

Cena (Scene)

editar
  • Uma cena consiste em uma coleção de nós organizados em uma hierarquia.
  • Pode ser salva em arquivos .tscn ou .scn, que podem ser instanciados em outras cenas, permitindo a reutilização e modularidade.
  • Uma cena típica pode representar um personagem, um nível de jogo, uma interface de usuário, etc.

Mini-projeto

editar

Nesta seção, será descrito o processo de criação de um projeto básico em Godot, com o objetivo de familiarizar o leitor com a engine e suas funcionalidades principais.

Mostrando alguma coisa na tela

editar
  • Crie uma cena nova chamada Mundo com root Node2D.
  • Salve a cena com nome mundo.tscn.
  • Adicione um nó Sprite como filho de Mundo.
  • Arraste o icon.png para a aba de Texture do Sprite.
  • Execute o programa.

Movimentando a coisa na tela de forma não interativa

editar
  • Renomeie o nó Sprite2D para Jogador
  • Adicione um script no nó Jogador chamado jogador.gd
extends Sprite2D


func _ready() -> void:
	pass


func _process(delta: float) -> void:
	self.position.x += 10.0;
  • Rode o jogo e veja ele se movendo!
  • Devemos usar o valor de `delta` da função para que o movimento passe a ser independente do fps do jogo
extends Sprite2D


func _ready() -> void:
	pass


func _process(delta: float) -> void:
	self.position.x += 200.0 * delta;
  • Se você rodar o jogo agora, provavelmente não vai notar muita diferença, mas agora o movimento está acontecendo de forma independente do fps
  • Podemos extrair também o valor 200 em uma variável exportada no editor, para facilidade de modificação
extends Sprite2D

@export var velocidade := 200.0;


func _ready() -> void:
	pass


func _process(delta: float) -> void:
	self.position.x += velocidade * delta;

Movimentando a coisa na tela de forma interativa

editar
  • Navegue para Project > Project Settings > Input Map
  • Adicione ações
    • jogador_esquerda
    • jogador_direita
    • jogador_cima
    • jogador_baixo
  • Adicione teclas para essas ações
  • Atualize o script jogador.gd para utilizar os novos inputs
extends Sprite2D

@export var velocidade := 200.0;


func _physics_process(delta: float) -> void:
	var direcao := Vector2.ZERO;
	if Input.is_action_pressed("jogador_direita"):
		direcao.x += 1;
	if Input.is_action_pressed("jogador_esquerda"):
		direcao.x += -1;
	if Input.is_action_pressed("jogador_baixo"):
		direcao.y += 1
	if Input.is_action_pressed("jogador_cima"):
		direcao.y += -1;

	self.position += direcao.normalized() * velocidade * delta;
  • Podemos extrair o código de pegar a direção do movimento para sua própria função
extends Sprite2D

@export var velocidade := 200.0;

var direcao := Vector2.ZERO;


func pega_input() -> void:
	direcao = Vector2.ZERO;
	if Input.is_action_pressed("jogador_direita"):
		direcao.x += 1;
	if Input.is_action_pressed("jogador_esquerda"):
		direcao.x += -1;
	if Input.is_action_pressed("jogador_baixo"):
		direcao.y += 1;
	if Input.is_action_pressed("jogador_cima"):
		direcao.y += -1;


func _physics_process(delta: float) -> void:
	pega_input();
	self.position += direcao.normalized() * velocidade * delta;

Pegando input de controle

editar
  • Adicione os analógicos do controle para as ações de input definidas no tópico anterior
  • Atualize o deadzone das ações
  • Atualize o código de input do jogador.gd para utilizar as funcionalidade do analógico
extends Sprite2D

@export var velocidade := 200.0;

var direcao := Vector2.ZERO;


func pega_input() -> void:
	direcao = Vector2.ZERO;
	direcao.x = Input.get_action_strength("jogador_direita") - Input.get_action_strength("jogador_esquerda");
	direcao.y = Input.get_action_strength("jogador_baixo") - Input.get_action_strength("jogador_cima");


func _physics_process(delta: float) -> void:
	pega_input();
	if direcao.length_squared() > 1: # Isso é para fazer o movimento ter sensibilidade com o analógico
		direcao = direcao.normalized();
	self.position += direcao * velocidade * delta;

Adicionando colisões

editar
  • Vamos criar uma nova cena chamada Jogador baseado em um CharacterBody2D
  • Adicione um Sprite como filho de Jogador e coloque a imagem icon.png no campo Texture
  • Adicione um CollisionShape2D como filho de Jogador
  • Adicione uma RectangleShape2D ao campo Shape de CollisionShape2D e redimensione ela para ter o mesmo tamanho da Sprite
  • Arraste o script jogador.gd do explorador de arquivos até o nó Jogador
  • Edite o script para funcionar com um KinematicBody2D
extends Sprite2D

@export var velocidade := 200.0;

var direcao := Vector2.ZERO;


func pega_input() -> void:
	direcao = Vector2.ZERO;
	direcao.x = Input.get_action_strength("jogador_direita") - Input.get_action_strength("jogador_esquerda");
	direcao.y = Input.get_action_strength("jogador_baixo") - Input.get_action_strength("jogador_cima");


func _physics_process(delta: float) -> void:
	pega_input();
	if direcao.length_squared() > 1: # Isso é para fazer o movimento ter sensibilidade com o analógico
		direcao = direcao.normalized();
	self.position += direcao * velocidade * delta;
  • Delete o nó Jogador da cena World
  • Adicione 3 StaticBody2D à cena World e adicione CollisionShape2Ds a cada um dos StaticBody2D
  • Arraste o arquivo jogador.tscn do explorador de arquivos para a árvore de nós da cena World para instanciar um jogador
  • Perceba que, se rodar o jogo agora, as colisões não vão ocorrer ainda. Para isso, precisamos fazer mais algumas alterações no código do jogador
extends CharacterBody2D

@export var velocidade := 200.0;

var direcao := Vector2.ZERO;


func pega_input() -> void:
	direcao = Vector2.ZERO;
	direcao.x = Input.get_action_strength("jogador_direita") - Input.get_action_strength("jogador_esquerda");
	direcao.y = Input.get_action_strength("jogador_baixo") - Input.get_action_strength("jogador_cima");


func _physics_process(_delta: float) -> void:
	pega_input();
	if direcao.length_squared() > 1: # Isso é para fazer o movimento ter sensibilidade com o analógico
		direcao = direcao.normalized();
	velocity = direcao * velocidade;
	move_and_slide();

Projeto final: Jogo de Plataforma

editar

A partir do mini-projeto feito anteriormente, será desenvolvido um jogo de plataforma básico que irá adicionar conceitos mais complexos na parte de programação.

Modificando o movimento

editar
  • Na movimentação de plataforma, o jogador se movimenta com as setas apenas da esquerda para a direita
extends CharacterBody2D

@export var velocidade := 200.0;

var direcao := Vector2.ZERO;


func pega_input() -> void:
	direcao = Vector2.ZERO;
	direcao.x = Input.get_action_strength("jogador_direita") - Input.get_action_strength("jogador_esquerda");


func _physics_process(_delta: float) -> void:
	pega_input();
	if direcao.length_squared() > 1:
		direcao = direcao.normalized();
	velocity = direcao * velocidade;
	move_and_slide();

Mudando o mundo

editar
  • Precisamos atualizar também a cena World para representar melhor um jogo de plataforma
    • Modifique os StaticBody2Ds da cena para melhor refletir um jogo de plataforma

Adicionando gravidade

editar
  • Para um jogo de plataforma, também é necessário que haja gravidade
    • Isso requer algumas modificações no código do jogador
extends CharacterBody2D

@export var gravidade := 20.0;
@export var velocidade := 200.0;

var direcao := Vector2.ZERO;


func pega_input() -> void:
	direcao = Vector2.ZERO;
	direcao.x = Input.get_action_strength("jogador_direita") - Input.get_action_strength("jogador_esquerda");


func _physics_process(_delta: float) -> void:
	pega_input();
	if direcao.length_squared() > 1:
		direcao = direcao.normalized();
	velocity.x = direcao.x * velocidade;
	velocity.y += gravidade;
	self.move_and_slide();

Adicionando pulo

editar
  • Navegue para Project > Project Settings > Input Map e adicione uma nova ação jogador_pulo
  • Mapeie alguma tecla/botão do controle para essa ação
  • Modifique o código do jogador para utilizar o pulo
extends CharacterBody2D

@export var gravidade := 20.0;
@export var velocidade := 200.0;
@export var velocidade_pulo := 700.0;

var direcao := Vector2.ZERO;
var acabou_de_pular: bool = false;


func pega_input() -> void:
	direcao = Vector2.ZERO;
	direcao.x = Input.get_action_strength("jogador_direita") - Input.get_action_strength("jogador_esquerda");

	acabou_de_pular = Input.is_action_just_pressed("jogador_pulo") and self.is_on_floor();


func _physics_process(_delta: float) -> void:
	pega_input();
	if direcao.length_squared() > 1:
		direcao = direcao.normalized();
	velocity.x = direcao.x * velocidade;
	velocity.y += gravidade;

	if acabou_de_pular:
		velocity.y = -velocidade_pulo;

	move_and_slide();

Adicionando câmera

editar
  • Adicione um nó Câmera2D à cena Jogador como filho do nó Jogador
  • Marque a opção Current da Câmera2D
  • Na aba Smoothing das propriedades da Câmera2D, marque a opção Enabled
  • Marque as opções Drag Margin H Enabled e Drag Margin V Enabled
  • Na aba Limit:
    • Coloque o valor de -200 na propriedade Left
    • Coloque o valor de -100 na propriedade Top
    • Coloque o valor de 1224 na propriedade Right
    • Coloque o valor de 700 na propriedade Bottom
    • Marque a opção Smoothed
    • Note que esses valores devem ser modificados dependendo das condições do seu nível
      • Você pode testar esses valores arrastando o nó Jogador na cena World no editor da Godot

Adicionando tiros

editar
  • Crie uma cena chamada Bala que herde de Area2D
  • Adicione um Sprite como filho de Bala e arraste o arquivo icon.png para o campo Texture
  • Adicione uma CollisionShape2D como filho de Bala e adicione uma RectangleShape2D no campo Shape
    • Redimensione a colisão para ficar do mesmo tamanho do Sprite
    • Adicione um script bala.gd no nó Bala
class_name Bala
extends Area2D

@export var velocidade := 1000.0;

var direcao := 1;

func _physics_process(delta: float) -> void:
	self.position.x += direcao * velocidade * delta;

	if abs(self.position.x) > 1000: self.queue_free();
  • Agora, navegue para Project > Project Settings > Input Map e adicione uma ação de input chamada jogador_tiro
    • Adicione uma tecla/botão do controle para essa ação
  • Modifique o código do jogador para utilizar a ação e atirar
extends CharacterBody2D

@export var bala_cena: PackedScene;
@export var gravidade := 20.0;
@export var velocidade := 200.0;
@export var velocidade_pulo := 700.0;

var direcao := Vector2.ZERO;
var olhando_para := 1;
var acabou_de_pular: bool = false;
var acabou_de_atirar: bool = false;


func pega_input() -> void:
	direcao = Vector2.ZERO;
	direcao.x = Input.get_action_strength("jogador_direita") - Input.get_action_strength("jogador_esquerda");

	acabou_de_pular = Input.is_action_just_pressed("jogador_pulo") and self.is_on_floor();
	acabou_de_atirar = Input.is_action_just_pressed("jogador_tiro");


func _physics_process(_delta: float) -> void:
	pega_input();
	if direcao.length_squared() > 1:
		direcao = direcao.normalized();

	if abs(direcao.x) > 0:
		olhando_para = sign(direcao.x) as int;

	velocity.x = direcao.x * velocidade;
	velocity.y += gravidade;

	if acabou_de_pular:
		velocity.y = -velocidade_pulo;

	if acabou_de_atirar:
		var nova_bala := bala_cena.instantiate() as Bala;
		self.get_parent().add_child(nova_bala);
		nova_bala.direcao = olhando_para;
		nova_bala.global_position = self.global_position;

	move_and_slide();

Adicionando inimigo que toma dano

editar
  • Crie uma nova cena chamada Inimigo com base em uma Area2D
  • Adicione um Sprite como filho do nó Inimigo e adicione a imagem icon.png ao campo Texture
    • No campo Visibility modifique a cor da opção Modulate para uma cor vermelha
  • Adicione uma CollisionShape2D como filha do nó Inimigo e adicione uma RectangleShape2D no campo Shape
    • Redimensione o retângulo para ficar do mesmo tamanho do Sprite
  • Adicione alguns inimigos na cena World
  • Adicione um script inimigo.gd ao nó Inimigo
  • Selecione o nó Inimigo na árvore de nós, depois, na aba Node do inspetor, conecte o sinal area_entered no Inimigo
extends Area2D


func _on_area_entered(area: Area2D) -> void:
	pass # Replace with function body.

- Agora, modifique o script `inimigo.gd` para adicionar lógica de vida e morte

extends Area2D

var vida := 5;


func _on_area_entered(area: Area2D) -> void:
	if area is Bala:
		vida -= 1;
		area.queue_free();

		if vida == 0: morre();


func morre() -> void:
	print("inimigo morreu!!");
	self.queue_free();

Conteúdo extra para estudo

editar