Mudanças entre as edições de "Godot Engine: Bad Turtles"
(→Player) |
|||
(24 revisões intermediárias pelo mesmo usuário não estão sendo mostradas) | |||
Linha 1: | Linha 1: | ||
+ | |||
+ | |||
Linha 4: | Linha 6: | ||
Afluentes : [[Jogos Digitais]], [[Usabilidade, desenvolvimento web, mobile e jogos]] | Afluentes : [[Jogos Digitais]], [[Usabilidade, desenvolvimento web, mobile e jogos]] | ||
+ | |||
+ | = ''Screenshot'' = | ||
+ | [[Arquivo:Godot labirionto screenshot.png|centro|miniaturadaimagem|690x690px|Screenshot.]] | ||
= Recursos = | = Recursos = | ||
+ | |||
+ | * '''Assets''': [https://saulo.arisa.com.br/aulas/unisul/games/godot/labirinto_assets.zip labirinto_assets.zip] | ||
+ | * '''Projeto no GitHub''': https://github.com/saulopz/badturtles/ | ||
= Projeto = | = Projeto = | ||
Linha 18: | Linha 26: | ||
[[Arquivo:Godot labirinto criando projeto.png|centro|miniaturadaimagem|572x572px|Criação do projeto]] | [[Arquivo:Godot labirinto criando projeto.png|centro|miniaturadaimagem|572x572px|Criação do projeto]] | ||
+ | |||
+ | = Cenas = | ||
+ | Vamos começar a criar a estrutura do nosso projeto por meio das cenas. | ||
+ | |||
+ | <!-- Veja que ainda não vamos tratar das colisões entre os elementos. Vamos deixar pra tratar isso mais tarde, depois que todas as cenas já tiverem sido definidas, assim teremos uma visão melhor dos elementos e como eles interagem entre si. --> | ||
== Cena Player == | == Cena Player == | ||
+ | Vamos começar criando nosso personagem principal do jogo. Aquele que iremos controlar. | ||
+ | |||
+ | * Crie uma nova cena. | ||
+ | * Adicione um nó raiz do tipo <code>CharacterBody2D</code> e renomeie-o para <code>Player</code>. | ||
+ | * Salve a cena como <code>player.tscn</code>. Como já renomeamos o nó raiz, ele já vai sugerir esse nome mesmo. | ||
+ | * Como nós filhos de Player: | ||
+ | ** Um nó do tipo <code>AnimatedSprite2D</code>. | ||
+ | ** Um nó do tipo <code>CollisionShape2D</code>. | ||
+ | ** Um nó do tipo <code>Timer</code>. | ||
+ | *** Esse temporizador indica o tempo que o personagem ficará em modo de super poder a cada vez que ele come um doce. Durante esse período, ele pode matar as tartarugas ao invés de ser morto. | ||
+ | *** Configurando em Inspector: | ||
+ | **** Wait Time = 5 | ||
+ | **** Em One Shot selecionar Ativo. | ||
+ | |||
+ | [[Arquivo:Godot labirinto cena player.png|centro|commoldura|Cena Player.]] | ||
+ | |||
+ | * Na aba Inspector, Em SpriteFrames está como [vazio]. Clique ali e selecione novo SpriteFrames. | ||
+ | * Clique em SpriteFrames agora, onde estava escrito [vazio]. | ||
+ | |||
+ | [[Arquivo:Godot labirionto player adicionar animacao.png|centro|miniaturadaimagem|326x326px|Adicionando animação.]] | ||
+ | |||
+ | * Agora nossa ferramenta de animação <code>SpriteFrames</code> é aberta. Renomeie a animação ''<code>default</code>'' para normal e adicione os sprites do player em sequencia. Depois crie uma nova animação chamada super e adicione o boneco que parece com o player, só que com outras cores. | ||
+ | |||
+ | [[Arquivo:Godot labirionto player animacoes.png|centro|miniaturadaimagem|380x380px|Animações do Player.]] | ||
+ | |||
+ | * Agora precisamos adicionar a colisão do player. | ||
+ | * Vá para a árvore de nós da cena Player e selecione CollisionShape2D. | ||
+ | * Em Inspector, temos Shape como [vazio]. Clique em [vazio] e crie um Novo CircleShape2D. | ||
+ | |||
+ | [[Arquivo:Godot labirinto colisao player.png|centro|miniaturadaimagem|263x263px|Colisão do player.]] | ||
+ | |||
+ | * Na área de desenho, ajuste o círculo para o tamanho que será a colisão. | ||
+ | |||
+ | [[Arquivo:Godot labirinto colisao player2.png|centro|miniaturadaimagem|180x180px|Colisão do Player.]] | ||
== Cena Turtle == | == Cena Turtle == | ||
+ | |||
+ | * Para criar a cena Turtle, siga os mesmos procedimentos do Player, mas com o nome de Turtle. | ||
+ | * Na animação, temos apenas a ''defaut'', então pode deixar o mesmo nome e colocar ali nossas tartaruguinhas. | ||
+ | |||
+ | [[Arquivo:Godot labirinto turtle.png|centro|miniaturadaimagem|850x850px|Cena Turtle.]] | ||
+ | |||
+ | * Depois adicione um AudioStreamPlayer2D e renomeie para SoundKill. Esse será o som que tocará quando a tartaruga morrer. | ||
+ | * E adicione um Timer. Não precisa configurar ele, vamos trabalhar com esse Nó em tempo de execução via nosso ''script''. | ||
+ | ** Ele servirá para, a cada determinado tempo aleatório, entre 0.0 e 2.0, alterar a direção da qual a tartaruga caminha. | ||
+ | [[Arquivo:Godot labirinto cena turtle.png|centro|commoldura|Cena Turtle.]] | ||
== Cena Candy == | == Cena Candy == | ||
+ | Para a cena Candy, siga os mesmos passos de Turtle, mas pode usar um nó raiz do tipo StaticBody2D mesmo. | ||
+ | [[Arquivo:Godot labirinto cena candy.png|centro|commoldura|Cena Candy.]] | ||
+ | Quando chegar na parte da animação. Coloque em sequencia as imagens para dar a impressão de aumentando e diminuindo. | ||
+ | |||
+ | Então as imagens ficarão na sequencia: 00, 01, 02, 03, 02, 01. | ||
+ | [[Arquivo:Godot labirinto candy animacao.png|centro|miniaturadaimagem|668x668px|Animação Candy.]]Agora, para fechar, tem alguns ícones em cima a esquerda do editor de SpriteFrames. Deixe selecionado o terceiro e o quarto. O terceiro inicia a animação quando uma instância da cena é aberta e o quarto deixa no modo loop. | ||
== Cena Maze == | == Cena Maze == | ||
Linha 59: | Linha 122: | ||
[[Arquivo:Godot labirinto tileset adicionando fisica.png|centro|miniaturadaimagem|363x363px|Adicionando física aos tilesets.]] | [[Arquivo:Godot labirinto tileset adicionando fisica.png|centro|miniaturadaimagem|363x363px|Adicionando física aos tilesets.]] | ||
+ | |||
+ | * Observe que agora, quando selecionamos um elemento à direita, temos um novo elemento em Tile Base que se chama Física. Selecione o campo Física. | ||
+ | |||
+ | [[Arquivo:Godot labirinto tileset fisica.png|centro|miniaturadaimagem|628x628px|Física nos TileSets.]] | ||
+ | |||
+ | * Ao selecionar Física, abrimos nossa Physics Layer 0. | ||
+ | * Selecione novamente nosso elemento que parece uma caixinha e pressione a tecla F. | ||
+ | * A caixa agora tem colisão. | ||
+ | |||
+ | [[Arquivo:Godot labirinto tileset colisao tecla f.png|centro|miniaturadaimagem|832x832px|Tecla F para selecionar nosso tileset inteiro.]] | ||
+ | |||
+ | * Agora clique na aba TileMap lá em baixo. | ||
+ | * Selecione qualquer um dos tiles e com o botão direito do mouse, adicione-o ao nosso game. | ||
+ | * Com o botão esquerdo, você pode apagar um tile. | ||
+ | * Faça isso até que o ambiente esteja conforme você deseja. | ||
+ | |||
+ | [[Arquivo:Godot labirinto modelando maze.png|centro|miniaturadaimagem|782x782px|Modelando o labirinto.]] | ||
== Cena HUD == | == Cena HUD == | ||
+ | Para criar o HUD: | ||
+ | |||
+ | * Crie um nó raiz do tipo Node2D e renomeie para HUD; | ||
+ | * Salve com hud.tscn; | ||
+ | * Crie 4 nós tipo Label e renomeie eles: | ||
+ | ** LabelScore: com o texto <code>SCORE:</code> | ||
+ | ** InfoScore: pode colocar 000 como texto só para posicionar no local correto, mas isso será alterado em tempo de execução. | ||
+ | ** LabelTimeout: com o texto <code>TIMEOUT:</code> | ||
+ | ** InfoTimeout: com o texto 000, mas também iremos alterar em tempo de execução. | ||
+ | * Posicione os Labels como achar melhor no layout do game. Mas deixei o Label com seu Info correspondente próximos. | ||
+ | [[Arquivo:Godot labirinto cena hud.png|centro|commoldura|Cena HUD.]] | ||
== Cena World == | == Cena World == | ||
+ | Para a cena World, faremos: | ||
+ | |||
+ | * Crie uma nova cena; | ||
+ | * Adicione um nó raiz do tipo Node2D e renomeie ele para World; | ||
+ | * Salve com world.tscn; | ||
+ | * Instancie seguintes os elementos na correntinha, ao lado do sinal de + em Cena: | ||
+ | ** Maze | ||
+ | ** Candy (coloque alguns candys espalhados pelo ambiente. Usar a combinação de teclas <code>CTRL+D</code> com um elemento selecionado e ele vai copiar em cima do atual. Mova ele para onde quiser. | ||
+ | ** Player, posicionando no local preferido. | ||
+ | ** Turtle: crie alguns turtles espalhados no ambiente. | ||
+ | ** HUD: só instancie, ele vai estar onde precisa estar. | ||
+ | |||
+ | [[Arquivo:Godot labirinto cena world.png|centro|commoldura|Cena World.]] | ||
+ | Depois disso criaremos alguns novos nós: | ||
+ | |||
+ | * Timer: | ||
+ | ** Crie um nó do tipo Timer; | ||
+ | ** Em Inspector, altere <code>Wait Time</code> para 10s | ||
+ | ** Deixe selecionado One Shot e Autostart | ||
+ | * MusicNormal e MusicSuper: | ||
+ | ** Crie dois nós do tipo <code>AudioStreamPlayer2D</code> | ||
+ | ** Renomeie esses nós para MusicNormal e MusicSuper respectivamente; | ||
+ | ** Em cada nó, arraste a música correspondente com extensão OGG para o atributo <code>Stream</code> em Inspector. | ||
+ | ** Em MusicNormal, habilite o atributo <code>Autoplay</code>, pois é ela que vai começar a tocar. | ||
+ | ** Para que as músicas possam ficar em loop, em cada um dos arquivos ogg de música | ||
+ | *** selecione eles em Arquivos | ||
+ | *** vá na aba Import e selecione Repetir como Ativo. | ||
+ | *** clique no botão reimportar | ||
+ | * Adicione um nó do tipo <code>TextureRect</code> e renomeie para <code>GameOverImage</code>. | ||
+ | ** Arraste a imagem gameover.png para Texture em Inspector | ||
+ | ** Em Inspector... CanvasItem... Visibility... retire a seleção de Ativo em Visible. | ||
+ | * Adicione um AudioStreamPlayer2D | ||
+ | ** renomeie ele para SoundGameOver | ||
+ | ** arraste o arquivo gameover.wave para Stream em Inspector | ||
+ | |||
+ | A tela da cena World vai ficar mais ou menos como a imagem a seguir. | ||
+ | [[Arquivo:Godot labirinto tela world.png|centro|miniaturadaimagem|697x697px|Tela World.]] | ||
+ | |||
+ | <!-- | ||
+ | = Camadas de Colisões = | ||
+ | Vamos fazer as detecções de colisão entre os elementos ativos. Dessa foram, podemos pensar na seguinte lógica: | ||
+ | |||
+ | * Player (camada 2) | ||
+ | ** detecta Maze para não atravessar as caixas/paredes; | ||
+ | ** detecta Turtle para o combate; | ||
+ | ** detecta Candy para poder comer; | ||
+ | * Turtle (camada 3) | ||
+ | ** detecta Maze para não atravessar as caixas/paredes; | ||
+ | ** detecta Turtle para o combate; | ||
+ | ** NÃO detecta Candy, então pode passar por cima; | ||
+ | * Candy (camada 4) | ||
+ | ** não precisa detectar nada. | ||
+ | |||
+ | Então podemos configurar: | ||
+ | |||
+ | * Maze (camada 1): detectando camadas todas as camadas (1, 2, 3 e 4). | ||
+ | * Player: detectando todas as camadas. | ||
+ | * Turtle: só não detectando Candy na camada 4. | ||
+ | * Candy: só não detectando Turtle na camada 3. | ||
+ | |||
+ | Para configurar as camadas, precisamos fazer o seguinte: | ||
+ | |||
+ | * Clique no nó raiz da cenas específica; | ||
+ | * Vá em Inspector... CanvasItem... Visibility... | ||
+ | * Em Light Mask você informa a camada de colisão do elemento | ||
+ | * Em Visibility Layer são quais camadas esse elemento irá detectar a colisão | ||
+ | |||
+ | Abaixo temos o exemplo de configuração de camadas de colisão de Candy. | ||
+ | [[Arquivo:Godot labirinto collision layers.png|centro|miniaturadaimagem|280x280px|Camadas de colisão.]] | ||
+ | --> | ||
= ''Scripts'' = | = ''Scripts'' = | ||
+ | Agora vamos criar nosso scripts. Veja que os ''scripts'' estão todos documentados com comentários no GDScript. | ||
+ | |||
+ | == Player == | ||
+ | Para criar o ''script'' do Player: | ||
+ | |||
+ | * Vá na aba da Cena e, com o nó Player selecionado, clique no ícone para adicionar script, conforme imagem abaixo: | ||
+ | |||
+ | [[Arquivo:Godot labirinto player novo script.png|centro|commoldura|Criando Scripts.]] | ||
+ | |||
+ | * Estamos criando um <code>GDScript</code> de um objeto tipo <code>CharacterBody2D</code>, no caso Player. | ||
+ | * Ele tenta trazer como padrão em Modelo o <code>CharacterBody2D: Basic Movement</code>. Mas vamos colocar como <code>Node: Default</code>, pois será mais adequado para nosso exemplo. Não precisamos de tudo o que o Basic Movement traz. | ||
+ | |||
+ | [[Arquivo:Godot labirinto player novo script2.png|centro|commoldura|Criando Scripts.]] | ||
+ | |||
+ | <syntaxhighlight lang="python"> | ||
+ | extends CharacterBody2D | ||
+ | |||
+ | var SPEED: int = 200 # velocidade | ||
+ | var screensize: Vector2 # tamanho da tela | ||
+ | var super_power: bool = false # modo de super poder | ||
+ | |||
+ | # metodo chamado ao criar o objeto | ||
+ | func _ready() -> void: | ||
+ | screensize = get_viewport_rect().size | ||
+ | |||
+ | # metodo chamado para matar o personagem | ||
+ | func kill() -> void: | ||
+ | hide() # deixamos ele invisivel | ||
+ | $CollisionShape2D.disabled = true # desabilitamos as colisoes | ||
+ | get_parent().game_over() # avisamos ao World que morremos | ||
+ | |||
+ | # Entramos em modo de super poder | ||
+ | func do_super() -> void: | ||
+ | super_power = true # sabemos que temos super poderes | ||
+ | SPEED += 10 # a velocidade eh incrementada | ||
+ | $AnimatedSprite2D.animation = "super" # alteramos a animacao para super | ||
+ | get_parent().change_music(true) # mudamos para musica mais animada | ||
+ | $Timer.start() # iniciamos um timeout de super | ||
+ | |||
+ | # Essa funcao processa a fisica do jogo e sera chamada | ||
+ | # a cada frame do jogo. Eh parte do game loop | ||
+ | func _physics_process(delta) -> void: | ||
+ | var vel = Vector2() # Crio um vetor 2D | ||
+ | if Input.is_action_pressed("ui_down"): # Se digitada tecla para baixo | ||
+ | vel.y += SPEED # Aumenta y do vetor | ||
+ | rotation_degrees = 0 # Rotaciona a imagem 0 graus | ||
+ | elif Input.is_action_pressed("ui_right"): # Senao, se tecla para direita | ||
+ | vel.x += SPEED # Aumenta x do vetor | ||
+ | rotation_degrees = -90 # Rotaciona a imagem -90 graus | ||
+ | elif Input.is_action_pressed("ui_up"): # Senao se tecla para cima | ||
+ | vel.y -= SPEED # Diminui y do eixo vertical | ||
+ | rotation_degrees = 180 # Rotaciona para 180 graus | ||
+ | elif Input.is_action_pressed("ui_left"): # Senao se tecla para esquerda | ||
+ | vel.x -= SPEED # Diminui x do eixo horizontal | ||
+ | rotation_degrees = 90 # Rotaciona a imagem 90 graus | ||
+ | # Se movimentou, o vetor vel sera maior que zero | ||
+ | if vel.length() > 0: | ||
+ | var obj = move_and_collide(vel * delta) # move e pega colisao | ||
+ | if obj: # se colidiu, retorna em obj | ||
+ | if 'Turtle' in obj.get_collider().name: # Se for Turtle | ||
+ | if super_power: # Se estiver no modo super | ||
+ | var other = obj.get_collider() # Pega o objeto Turtle | ||
+ | other.kill() # E mata ele | ||
+ | get_parent().make_point() # Faz ponto no jogo | ||
+ | else: # Senao | ||
+ | kill() # Morre | ||
+ | elif 'Candy' in obj.get_collider().name:# Se for um Candy | ||
+ | var other = obj.get_collider() # Pega o objeto Candy | ||
+ | other.kill() # Mata o Candy | ||
+ | do_super() # E entra no modo super | ||
+ | $AnimatedSprite2D.play() # Da play na animacao | ||
+ | else: # Senao, se nao movimentou, vel = 0 | ||
+ | $AnimatedSprite2D.stop() # E para a animacao | ||
+ | # Se sair da tela, eixo x ou y, volta pra tela | ||
+ | position.x = clamp(position.x, 25, screensize.x - 25) | ||
+ | position.y = clamp(position.y, 25, screensize.y - 25) | ||
+ | |||
+ | # Essa funcao eh chamada quando o timeout do modo super | ||
+ | # chega a zero, voltando entao para o modo normal. | ||
+ | func _on_timer_timeout() -> void: | ||
+ | super_power = false # Desliga o modo super | ||
+ | SPEED -= 10 # Volta a velocidade normal | ||
+ | $AnimatedSprite2D.animation = "normal" # Volta a animacao normal | ||
+ | get_parent().change_music(false) # E volta a musica mais calma | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | == Turtle == | ||
+ | |||
+ | <syntaxhighlight lang="python"> | ||
+ | extends CharacterBody2D | ||
+ | |||
+ | # Constantes de direcoes | ||
+ | const DOWN: int = 0 # Para baixo | ||
+ | const RIGHT: int = 1 # Para a direita | ||
+ | const UP: int = 2 # Para cima | ||
+ | const LEFT: int = 3 # Para a esquerda | ||
+ | |||
+ | var SPEED: int = 100 # Velocidade | ||
+ | var direction: int = DOWN # Direcao | ||
+ | var screensize: Vector2 # Tamanho da tela | ||
+ | |||
+ | # Metodo chamado ao criar o objeto | ||
+ | func _ready() -> void: | ||
+ | screensize = get_viewport_rect().size # pega o tamanho da tela do jogo | ||
+ | randomize() # executa o randomizador | ||
+ | new_goal() # inicia com um novo objetivo | ||
+ | |||
+ | # Cria um novo objetivo para o objeto | ||
+ | func new_goal() -> void: | ||
+ | direction = randi() % 4 # Pega uma direcao aleatoria | ||
+ | $Timer.start((randi() % 20) / 10.0) # Um timeout aleatorio de 0.0 a 2.0 | ||
+ | |||
+ | # Metodo para matar o personagem | ||
+ | func kill() -> void: | ||
+ | hide() # Esconde o objeto | ||
+ | $CollisionShape2D.disabled = true # Desabilita as colisoes | ||
+ | $SoundKill.play() # Toca o som de morte do personagem | ||
+ | |||
+ | # Metodo que executa as fisicas do objeto | ||
+ | func _physics_process(delta) -> void: | ||
+ | # Inicialmente vamos seguir conforme a direcao objetivo ja definida | ||
+ | # Entao criamos um vetor vel para pegar as informacoes de direcao | ||
+ | var vel = Vector2() # Criamos um Vetor 2D | ||
+ | if direction == DOWN: # Se direcao pra baixo | ||
+ | vel.y += SPEED # Adicionamos SPEED ao y do vetor | ||
+ | rotation_degrees = 0 # E rotacionamos para 0 | ||
+ | elif direction == RIGHT: # Senao, se for para a direita | ||
+ | vel.x += SPEED # Aumentamos o x | ||
+ | rotation_degrees = -90 # E rotacionamos 90 graus negativos | ||
+ | elif direction == UP: # Senao, se for para cima | ||
+ | vel.y -= SPEED # Diminuimos y, ficando negativo | ||
+ | rotation_degrees = 180 # E rotacionamos para 180 graus | ||
+ | elif direction == LEFT: # Senao se direcao eh para a esquerda | ||
+ | vel.x -= SPEED # Diminuimos SPEED de x | ||
+ | rotation_degrees = 90 # E rotacionamos 90 graus | ||
+ | # Move o objeto conforme direcao multiplicado pelo delta | ||
+ | var obj = move_and_collide(vel * delta) # Movemos e detectamos colisao | ||
+ | if obj: # se colidirmos, temos um obj | ||
+ | if 'Player' in obj.get_collider().name: # Se no nome do obj tiver Player | ||
+ | var other = obj.get_collider() # Pegamos com quem colidimos | ||
+ | if other.super_power: # Se ele estiver no modo super | ||
+ | kill() # Essa Turtle morre | ||
+ | else: # Senao | ||
+ | other.kill() # Matamos o Player | ||
+ | else: # Se colidiu e nao eh o Player | ||
+ | new_goal() # Altera o objetivo | ||
+ | $AnimatedSprite2D.play() # Da play na animacao | ||
+ | # Se sair da tela, eixo x ou y, volta pra tela | ||
+ | position.x = clamp(position.x, 25, screensize.x - 25) | ||
+ | position.y = clamp(position.y, 25, screensize.y - 25) | ||
+ | |||
+ | # Funcao chamada quando der timeout no Timer | ||
+ | func _on_timer_timeout() -> void: | ||
+ | new_goal() # Gera um novo objetivo | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | == Candy == | ||
+ | |||
+ | <syntaxhighlight lang="python"> | ||
+ | extends StaticBody2D | ||
+ | |||
+ | # funcao chamada para excluir o objeto do jogo | ||
+ | func kill() -> void: | ||
+ | hide() # escondemos o objeto | ||
+ | $CollisionShape2D.disabled = true # desabilitamos as collisoes | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | == HUD == | ||
+ | |||
+ | <syntaxhighlight lang="python"> | ||
+ | extends Node2D | ||
+ | |||
+ | # Nosso HUD nao tem nenhum processamento. Ele serve | ||
+ | # apenas para visualizacao das informacoes. Veja que | ||
+ | # a cada game loop buscamos as informacoes do World | ||
+ | # com get_parent() e colocamos elas nos Labels | ||
+ | func _physics_process(_delta: float) -> void: | ||
+ | $InfoScore.text = str(get_parent().score) | ||
+ | $InfoTimeout.text = str(int(get_parent().get_node("Timer").time_left)) | ||
+ | |||
+ | </syntaxhighlight> | ||
+ | |||
+ | == World == | ||
+ | |||
+ | <syntaxhighlight lang="python"> | ||
+ | extends Node2D | ||
+ | |||
+ | # variavel contendo nossa pontuacao | ||
+ | var score: int = 0 | ||
+ | |||
+ | # Comportamento do jogo quando o Player morre | ||
+ | func game_over() -> void: | ||
+ | $MusicNormal.stop() # Para a musica normal | ||
+ | $MusicSuper.stop() # Para a musica Super se estiver tocando | ||
+ | $SoundGameOver.play() # Toca o som de Game Over | ||
+ | $GameOverImage.visible = true # Torna a imagem de Game Over visivel | ||
+ | |||
+ | # Altera o tipo da musica. Se a entrada for True, | ||
+ | # toca a musica Super, senao a musica normal | ||
+ | func change_music(superPower) -> void: | ||
+ | if superPower: # Se esta no modo super | ||
+ | $MusicNormal.stop() # Desliga a musica normal | ||
+ | $MusicSuper.play() # E liga a musica animada | ||
+ | else: # Senao | ||
+ | $MusicSuper.stop() # Desliga a musica animada | ||
+ | $MusicNormal.play() # E liga a musica normal | ||
+ | |||
+ | # O Player chama essa funcao quando mata uma tartaruga. | ||
+ | # Nao eh algo muito legal de se fazer, matar tartarugas, | ||
+ | # mas eh soh um jogo de exemplo, entao vamos relevar. | ||
+ | func make_point() -> void: | ||
+ | $Timer.start($Timer.time_left + 5) # Aumenta o tempo restante em 5 | ||
+ | score += 1 # Aumenta a pontuacao em 1 | ||
+ | |||
+ | # Essa funcao eh chamada quando o Timer chega a 0, | ||
+ | # ou seja, deu timeout. | ||
+ | func _on_timer_timeout() -> void: | ||
+ | $Player.kill() # O Player morre | ||
+ | </syntaxhighlight> |
Edição atual tal como às 18h31min de 24 de setembro de 2024
Afluentes : Jogos Digitais, Usabilidade, desenvolvimento web, mobile e jogos
Screenshot
Recursos
- Assets: labirinto_assets.zip
- Projeto no GitHub: https://github.com/saulopz/badturtles/
Projeto
- Abra o Godot e crie um novo projeto. Nesse ponto é importante configurarmos algumas informações:
- Nome do Projeto: Nome do jogo.
- Caminho do Projeto: Pasta onde o projeto vai ficar. É importante saber essa localização, pois é onde colocaremos nossos recursos (assets) imagens, sons, etc.
- Renderizador: A forma como o motor vai usar a renderização. Temos três modos:
- Avançado: Fornece o máximo de recursos e renderização para nosso game.
- Mobile: Tem recursos específicos para smartphones e limitações de renderização para que o jogo seja compatível com esses dispositivos.
- Compatibilidade: É a forma com maior compatibilidade com dispositivos mobile e Web. Contudo, possui recursos limitados e sua forma de renderização é a mais simples.
Cenas
Vamos começar a criar a estrutura do nosso projeto por meio das cenas.
Cena Player
Vamos começar criando nosso personagem principal do jogo. Aquele que iremos controlar.
- Crie uma nova cena.
- Adicione um nó raiz do tipo
CharacterBody2D
e renomeie-o paraPlayer
. - Salve a cena como
player.tscn
. Como já renomeamos o nó raiz, ele já vai sugerir esse nome mesmo. - Como nós filhos de Player:
- Um nó do tipo
AnimatedSprite2D
. - Um nó do tipo
CollisionShape2D
. - Um nó do tipo
Timer
.- Esse temporizador indica o tempo que o personagem ficará em modo de super poder a cada vez que ele come um doce. Durante esse período, ele pode matar as tartarugas ao invés de ser morto.
- Configurando em Inspector:
- Wait Time = 5
- Em One Shot selecionar Ativo.
- Um nó do tipo
- Na aba Inspector, Em SpriteFrames está como [vazio]. Clique ali e selecione novo SpriteFrames.
- Clique em SpriteFrames agora, onde estava escrito [vazio].
- Agora nossa ferramenta de animação
SpriteFrames
é aberta. Renomeie a animaçãodefault
para normal e adicione os sprites do player em sequencia. Depois crie uma nova animação chamada super e adicione o boneco que parece com o player, só que com outras cores.
- Agora precisamos adicionar a colisão do player.
- Vá para a árvore de nós da cena Player e selecione CollisionShape2D.
- Em Inspector, temos Shape como [vazio]. Clique em [vazio] e crie um Novo CircleShape2D.
- Na área de desenho, ajuste o círculo para o tamanho que será a colisão.
Cena Turtle
- Para criar a cena Turtle, siga os mesmos procedimentos do Player, mas com o nome de Turtle.
- Na animação, temos apenas a defaut, então pode deixar o mesmo nome e colocar ali nossas tartaruguinhas.
- Depois adicione um AudioStreamPlayer2D e renomeie para SoundKill. Esse será o som que tocará quando a tartaruga morrer.
- E adicione um Timer. Não precisa configurar ele, vamos trabalhar com esse Nó em tempo de execução via nosso script.
- Ele servirá para, a cada determinado tempo aleatório, entre 0.0 e 2.0, alterar a direção da qual a tartaruga caminha.
Cena Candy
Para a cena Candy, siga os mesmos passos de Turtle, mas pode usar um nó raiz do tipo StaticBody2D mesmo.
Quando chegar na parte da animação. Coloque em sequencia as imagens para dar a impressão de aumentando e diminuindo.
Então as imagens ficarão na sequencia: 00, 01, 02, 03, 02, 01.
Agora, para fechar, tem alguns ícones em cima a esquerda do editor de SpriteFrames. Deixe selecionado o terceiro e o quarto. O terceiro inicia a animação quando uma instância da cena é aberta e o quarto deixa no modo loop.
Cena Maze
A cena Maze é o labirinto onde nossos elementos vão transitar e conterá elementos de colisão.
- Crie uma nova cena.
- Crie um nó raiz do tipo Node2D e renomeie para Maze.
- Salve a cena como maze.tscn.
- Crie um nó filho do tipo TileMap.
- Vá para a aba Inspector, à direita da nossa ferramenta.
- Em Tile Set, crie um
Novo TileSet
.
- Vamos customizar as células em que vamos criar nosso labirinto para o tamanho de 64x64.
- Para isso, altere o
Rendering Quadrant Size
para 64.
- Cique em TileSet e agora altere
Tile Size
para x = 64 e y = 64.
Agora vamos começar a trabalhar com nosso tileset. Nos recursos disponíveis do exemplo, temos um tileset muito simples chamado tileset.png, mas que vai servir para nossa aprendizagem.
- Arraste o tileset.png para a ferramenta TileSet
- Observe que ele vai pedir se já quer separar conforme o tamanho que definimos. Selecione SIM.
Para que possamos ter colisão, temos que dizer isso para nosso Nó TileSet.
- Vá na aba Inspector. Selecione Physics Layer, e clique em Adicionar Elemento.
- Observe que agora, quando selecionamos um elemento à direita, temos um novo elemento em Tile Base que se chama Física. Selecione o campo Física.
- Ao selecionar Física, abrimos nossa Physics Layer 0.
- Selecione novamente nosso elemento que parece uma caixinha e pressione a tecla F.
- A caixa agora tem colisão.
- Agora clique na aba TileMap lá em baixo.
- Selecione qualquer um dos tiles e com o botão direito do mouse, adicione-o ao nosso game.
- Com o botão esquerdo, você pode apagar um tile.
- Faça isso até que o ambiente esteja conforme você deseja.
Cena HUD
Para criar o HUD:
- Crie um nó raiz do tipo Node2D e renomeie para HUD;
- Salve com hud.tscn;
- Crie 4 nós tipo Label e renomeie eles:
- LabelScore: com o texto
SCORE:
- InfoScore: pode colocar 000 como texto só para posicionar no local correto, mas isso será alterado em tempo de execução.
- LabelTimeout: com o texto
TIMEOUT:
- InfoTimeout: com o texto 000, mas também iremos alterar em tempo de execução.
- LabelScore: com o texto
- Posicione os Labels como achar melhor no layout do game. Mas deixei o Label com seu Info correspondente próximos.
Cena World
Para a cena World, faremos:
- Crie uma nova cena;
- Adicione um nó raiz do tipo Node2D e renomeie ele para World;
- Salve com world.tscn;
- Instancie seguintes os elementos na correntinha, ao lado do sinal de + em Cena:
- Maze
- Candy (coloque alguns candys espalhados pelo ambiente. Usar a combinação de teclas
CTRL+D
com um elemento selecionado e ele vai copiar em cima do atual. Mova ele para onde quiser. - Player, posicionando no local preferido.
- Turtle: crie alguns turtles espalhados no ambiente.
- HUD: só instancie, ele vai estar onde precisa estar.
Depois disso criaremos alguns novos nós:
- Timer:
- Crie um nó do tipo Timer;
- Em Inspector, altere
Wait Time
para 10s - Deixe selecionado One Shot e Autostart
- MusicNormal e MusicSuper:
- Crie dois nós do tipo
AudioStreamPlayer2D
- Renomeie esses nós para MusicNormal e MusicSuper respectivamente;
- Em cada nó, arraste a música correspondente com extensão OGG para o atributo
Stream
em Inspector. - Em MusicNormal, habilite o atributo
Autoplay
, pois é ela que vai começar a tocar. - Para que as músicas possam ficar em loop, em cada um dos arquivos ogg de música
- selecione eles em Arquivos
- vá na aba Import e selecione Repetir como Ativo.
- clique no botão reimportar
- Crie dois nós do tipo
- Adicione um nó do tipo
TextureRect
e renomeie paraGameOverImage
.- Arraste a imagem gameover.png para Texture em Inspector
- Em Inspector... CanvasItem... Visibility... retire a seleção de Ativo em Visible.
- Adicione um AudioStreamPlayer2D
- renomeie ele para SoundGameOver
- arraste o arquivo gameover.wave para Stream em Inspector
A tela da cena World vai ficar mais ou menos como a imagem a seguir.
Scripts
Agora vamos criar nosso scripts. Veja que os scripts estão todos documentados com comentários no GDScript.
Player
Para criar o script do Player:
- Vá na aba da Cena e, com o nó Player selecionado, clique no ícone para adicionar script, conforme imagem abaixo:
- Estamos criando um
GDScript
de um objeto tipoCharacterBody2D
, no caso Player. - Ele tenta trazer como padrão em Modelo o
CharacterBody2D: Basic Movement
. Mas vamos colocar comoNode: Default
, pois será mais adequado para nosso exemplo. Não precisamos de tudo o que o Basic Movement traz.
extends CharacterBody2D
var SPEED: int = 200 # velocidade
var screensize: Vector2 # tamanho da tela
var super_power: bool = false # modo de super poder
# metodo chamado ao criar o objeto
func _ready() -> void:
screensize = get_viewport_rect().size
# metodo chamado para matar o personagem
func kill() -> void:
hide() # deixamos ele invisivel
$CollisionShape2D.disabled = true # desabilitamos as colisoes
get_parent().game_over() # avisamos ao World que morremos
# Entramos em modo de super poder
func do_super() -> void:
super_power = true # sabemos que temos super poderes
SPEED += 10 # a velocidade eh incrementada
$AnimatedSprite2D.animation = "super" # alteramos a animacao para super
get_parent().change_music(true) # mudamos para musica mais animada
$Timer.start() # iniciamos um timeout de super
# Essa funcao processa a fisica do jogo e sera chamada
# a cada frame do jogo. Eh parte do game loop
func _physics_process(delta) -> void:
var vel = Vector2() # Crio um vetor 2D
if Input.is_action_pressed("ui_down"): # Se digitada tecla para baixo
vel.y += SPEED # Aumenta y do vetor
rotation_degrees = 0 # Rotaciona a imagem 0 graus
elif Input.is_action_pressed("ui_right"): # Senao, se tecla para direita
vel.x += SPEED # Aumenta x do vetor
rotation_degrees = -90 # Rotaciona a imagem -90 graus
elif Input.is_action_pressed("ui_up"): # Senao se tecla para cima
vel.y -= SPEED # Diminui y do eixo vertical
rotation_degrees = 180 # Rotaciona para 180 graus
elif Input.is_action_pressed("ui_left"): # Senao se tecla para esquerda
vel.x -= SPEED # Diminui x do eixo horizontal
rotation_degrees = 90 # Rotaciona a imagem 90 graus
# Se movimentou, o vetor vel sera maior que zero
if vel.length() > 0:
var obj = move_and_collide(vel * delta) # move e pega colisao
if obj: # se colidiu, retorna em obj
if 'Turtle' in obj.get_collider().name: # Se for Turtle
if super_power: # Se estiver no modo super
var other = obj.get_collider() # Pega o objeto Turtle
other.kill() # E mata ele
get_parent().make_point() # Faz ponto no jogo
else: # Senao
kill() # Morre
elif 'Candy' in obj.get_collider().name:# Se for um Candy
var other = obj.get_collider() # Pega o objeto Candy
other.kill() # Mata o Candy
do_super() # E entra no modo super
$AnimatedSprite2D.play() # Da play na animacao
else: # Senao, se nao movimentou, vel = 0
$AnimatedSprite2D.stop() # E para a animacao
# Se sair da tela, eixo x ou y, volta pra tela
position.x = clamp(position.x, 25, screensize.x - 25)
position.y = clamp(position.y, 25, screensize.y - 25)
# Essa funcao eh chamada quando o timeout do modo super
# chega a zero, voltando entao para o modo normal.
func _on_timer_timeout() -> void:
super_power = false # Desliga o modo super
SPEED -= 10 # Volta a velocidade normal
$AnimatedSprite2D.animation = "normal" # Volta a animacao normal
get_parent().change_music(false) # E volta a musica mais calma
Turtle
extends CharacterBody2D
# Constantes de direcoes
const DOWN: int = 0 # Para baixo
const RIGHT: int = 1 # Para a direita
const UP: int = 2 # Para cima
const LEFT: int = 3 # Para a esquerda
var SPEED: int = 100 # Velocidade
var direction: int = DOWN # Direcao
var screensize: Vector2 # Tamanho da tela
# Metodo chamado ao criar o objeto
func _ready() -> void:
screensize = get_viewport_rect().size # pega o tamanho da tela do jogo
randomize() # executa o randomizador
new_goal() # inicia com um novo objetivo
# Cria um novo objetivo para o objeto
func new_goal() -> void:
direction = randi() % 4 # Pega uma direcao aleatoria
$Timer.start((randi() % 20) / 10.0) # Um timeout aleatorio de 0.0 a 2.0
# Metodo para matar o personagem
func kill() -> void:
hide() # Esconde o objeto
$CollisionShape2D.disabled = true # Desabilita as colisoes
$SoundKill.play() # Toca o som de morte do personagem
# Metodo que executa as fisicas do objeto
func _physics_process(delta) -> void:
# Inicialmente vamos seguir conforme a direcao objetivo ja definida
# Entao criamos um vetor vel para pegar as informacoes de direcao
var vel = Vector2() # Criamos um Vetor 2D
if direction == DOWN: # Se direcao pra baixo
vel.y += SPEED # Adicionamos SPEED ao y do vetor
rotation_degrees = 0 # E rotacionamos para 0
elif direction == RIGHT: # Senao, se for para a direita
vel.x += SPEED # Aumentamos o x
rotation_degrees = -90 # E rotacionamos 90 graus negativos
elif direction == UP: # Senao, se for para cima
vel.y -= SPEED # Diminuimos y, ficando negativo
rotation_degrees = 180 # E rotacionamos para 180 graus
elif direction == LEFT: # Senao se direcao eh para a esquerda
vel.x -= SPEED # Diminuimos SPEED de x
rotation_degrees = 90 # E rotacionamos 90 graus
# Move o objeto conforme direcao multiplicado pelo delta
var obj = move_and_collide(vel * delta) # Movemos e detectamos colisao
if obj: # se colidirmos, temos um obj
if 'Player' in obj.get_collider().name: # Se no nome do obj tiver Player
var other = obj.get_collider() # Pegamos com quem colidimos
if other.super_power: # Se ele estiver no modo super
kill() # Essa Turtle morre
else: # Senao
other.kill() # Matamos o Player
else: # Se colidiu e nao eh o Player
new_goal() # Altera o objetivo
$AnimatedSprite2D.play() # Da play na animacao
# Se sair da tela, eixo x ou y, volta pra tela
position.x = clamp(position.x, 25, screensize.x - 25)
position.y = clamp(position.y, 25, screensize.y - 25)
# Funcao chamada quando der timeout no Timer
func _on_timer_timeout() -> void:
new_goal() # Gera um novo objetivo
Candy
extends StaticBody2D
# funcao chamada para excluir o objeto do jogo
func kill() -> void:
hide() # escondemos o objeto
$CollisionShape2D.disabled = true # desabilitamos as collisoes
HUD
extends Node2D
# Nosso HUD nao tem nenhum processamento. Ele serve
# apenas para visualizacao das informacoes. Veja que
# a cada game loop buscamos as informacoes do World
# com get_parent() e colocamos elas nos Labels
func _physics_process(_delta: float) -> void:
$InfoScore.text = str(get_parent().score)
$InfoTimeout.text = str(int(get_parent().get_node("Timer").time_left))
World
extends Node2D
# variavel contendo nossa pontuacao
var score: int = 0
# Comportamento do jogo quando o Player morre
func game_over() -> void:
$MusicNormal.stop() # Para a musica normal
$MusicSuper.stop() # Para a musica Super se estiver tocando
$SoundGameOver.play() # Toca o som de Game Over
$GameOverImage.visible = true # Torna a imagem de Game Over visivel
# Altera o tipo da musica. Se a entrada for True,
# toca a musica Super, senao a musica normal
func change_music(superPower) -> void:
if superPower: # Se esta no modo super
$MusicNormal.stop() # Desliga a musica normal
$MusicSuper.play() # E liga a musica animada
else: # Senao
$MusicSuper.stop() # Desliga a musica animada
$MusicNormal.play() # E liga a musica normal
# O Player chama essa funcao quando mata uma tartaruga.
# Nao eh algo muito legal de se fazer, matar tartarugas,
# mas eh soh um jogo de exemplo, entao vamos relevar.
func make_point() -> void:
$Timer.start($Timer.time_left + 5) # Aumenta o tempo restante em 5
score += 1 # Aumenta a pontuacao em 1
# Essa funcao eh chamada quando o Timer chega a 0,
# ou seja, deu timeout.
func _on_timer_timeout() -> void:
$Player.kill() # O Player morre