Mudanças entre as edições de "Godot Engine: SkyFire"
(→Ship) |
|||
(35 revisões intermediárias pelo mesmo usuário não estão sendo mostradas) | |||
Linha 2: | Linha 2: | ||
Afluentes : [[Jogos Digitais]], [[Usabilidade, desenvolvimento web, mobile e jogos]] | Afluentes : [[Jogos Digitais]], [[Usabilidade, desenvolvimento web, mobile e jogos]] | ||
− | = | + | = Screenshot = |
+ | Já vimos alguns games passo a passo, agora vamos tentar um desafio. A partir dos ''assets'', da estrutura do projeto, os ''scripts'' e algumas dicas, vamos tentar criar o game SkyFire. Abaixo temos um ''screenshot''.<center>[[Image:Godot skyfire.jpg|500px]]</center> | ||
− | * [https://saulo.arisa.com.br | + | == Assets == |
+ | * [https://saulo.arisa.com.br/aulas/unisul/games/assets/skyfire_godot.zip Assets] | ||
+ | * [https://github.com/saulopz/skyfire Projeto SkyFire no GitHub] | ||
= Estrutura = | = Estrutura = | ||
+ | Veja que: | ||
− | * | + | * todas as Cenas que possuem ''scripts'' coloquei uma tag [''script''] no nó que ele deve ser criado. |
− | ** A ( | + | * Depois de criado os ''scripts'', em alguns nós da cena devemos criar sinais. |
− | ** | + | * Crie os sinais só depois da estrutura e do ''script'' criados. |
− | * Ship ( | + | * A Cena <code>Sky</code> vou explicar um pouco melhor porque vamos precisar fazer um ''scrolling'' na imagem de fundo e vamos usar um recurso bem legal do Godot. |
− | ** | + | |
+ | Abaixo temos a estrutura de Cenas e nós. | ||
+ | * Sky (Node2D) | ||
+ | ** TextureRect | ||
+ | * Ship (CharacterBody2D) [''script''] | ||
+ | ** Sprite2D | ||
** CollisionShape2D | ** CollisionShape2D | ||
− | * Enemy ( | + | * Enemy (CharacterBody2D) [''script''] |
− | ** | + | ** Sprite2D |
** CollisionShape2D | ** CollisionShape2D | ||
− | * Bomb ( | + | * Bomb (CharacterBody2D) [''script''] |
− | ** | + | ** Sprite2D |
** CollisionShape2D | ** CollisionShape2D | ||
− | ** SoundBomb ( | + | ** SoundBomb (AudioStreamPlayer2D) |
− | * Explosion ( | + | * Explosion (CharacterBody2D) [''script''] |
− | ** | + | ** AnimatedSprite2D - Quando estiver no editor de SpriteFrames, desabilitar a repetição em um botãozinho ao lado de <code>5 FPS</code>, conforme imagem abaixo. |
− | * | + | *** Nó (sinal) - ''_on_animated_sprite_2d_animation_finished()'' |
− | + | ** CollisionShape2D - Inspector... CollisionShape2D... disabled Ativo. | |
− | ** Nó (sinal) - '' | + | ** SoundExplosion (AudioStreamPlayer2D) |
− | * World (Node2D) | + | * World (Node2D) [''script''] |
− | ** | + | ** Sky (instancia) |
** Ship (instancia) | ** Ship (instancia) | ||
− | ** EnemiTimer (Timer) | + | ** EnemiTimer (Timer) - com Autostart ativo. |
− | ** Music ( | + | *** Nó (sinal) - ''_on_enemy_timer_timeout()'' |
− | ** GameOver (TextureRect) | + | ** Music (AudioStreamPlayer2D) - lembre de deixar em Autoplaying e em loop (na importação). |
− | ** Label (Label) | + | ** GameOver (TextureRect) - Em inspector... CanvasItem... Visibility... desabilitar Visible |
− | ** Score (Label) | + | ** Label (Label) - Alterar a cor do texto em Inspector... Control... Themes Overrides... Colors... selecionar '''Font Color'''. pode deixar preto mesmo ou alterar a cor. Podemos alterar '''Font Sizes''' para 30px. |
+ | ** Score (Label) - Altere a cor também e o tamanho da fonte. | ||
+ | [[Arquivo:Godot skyfire explosion desabilitar repeticao.png|centro|miniaturadaimagem|328x328px|Desabilitando repetição na animação da explosão.]] | ||
− | = | + | == Cena Sky == |
+ | Vamos iniciar criando uma cena com um nó raiz do tipo <code>Node2D</code>. Renomeie esse nó para Sky e crie um nó filho do tipo <code>TextureRect</code>. | ||
+ | [[Arquivo:Godot skyfire cena sky.png|centro|commoldura|Cena Sky.]] | ||
+ | Clique no nó <code>TextureRect</code> e arraste a imagem <code>background.png</code> (dos nossos recursos do game) para a aba Inspector... RextureRect... Texture... | ||
+ | [[Arquivo:Godot skyfire arrastar textura.png|centro|miniaturadaimagem|279x279px|Arrastando a textura.]] | ||
+ | Ainda na aba Inspector, vamos em CamvasItem e então clique em Material, expandindo ele. Dentro tem um item chamado '''Material''' (também), clique do lado onde está '''[vazio]''' e adicione um Novo '''Shader Material'''. | ||
+ | [[Arquivo:Godot skyfire material.png|centro|commoldura|Adicionando Material.]] | ||
+ | Vai aparecer uma esfera cinza. Clique nela e vai expandir. Em baixo vai aparecer um elemento chamado '''Shader''' que está como '''[vazio]''', clique ali e adicione um '''Novo Shader'''. | ||
+ | [[Arquivo:Godot skyfire material shader.png|centro|commoldura|Novo Shader]] | ||
+ | Veja que ele vai pedir pra criar um ''script'' especial para esse shader. Como já renomeamos nosso nó raiz como '''Sky''', ele vai salvar como <code>sky.gdshader</code>. Salve esse ''script'' e clique no shader criado (em baixo da esfera). | ||
+ | [[Arquivo:Godot skyfire novo shader.png|centro|miniaturadaimagem|347x347px|Novo Shader.]] | ||
+ | Ele vai abrir uma ferramenta (imagem abaixo) para editar o ''script'' do shader. Vamos alterar o ''script''. | ||
+ | [[Arquivo:Godot skyfire shader editor.png|centro|miniaturadaimagem|623x623px|Editor de script do shader.]] | ||
+ | Não vamos precisar das funções ''<code>fragment</code>'' e ''<code>ligth</code>''. Então vamos excluí-las.<syntaxhighlight lang="python"> | ||
+ | shader_type canvas_item; | ||
− | = | + | void vertex() { |
+ | UV.y -= TIME * 0.1; | ||
+ | } | ||
+ | </syntaxhighlight>Veja que na função ''<code>vertex</code>'', usamos o atributo UV.y e diminuímos ele conforme o TIME. Só que como fica muito rápido, multiplicamos por 0.1. | ||
− | + | Por fim, para que a textura fique se repetindo, precisamos ir, ainda em CanvaItem, em Texture, selecionar Repeat e colocar '''Enabled'''. | |
− | + | [[Arquivo:Godot skyfire texture repeat.png|centro|commoldura|Deixando a textura em loop.]] | |
+ | E pronto! Temos nosso fundo que fica se em modo ''scrolling''. | ||
− | + | = Scripts = | |
− | + | Nesse exemplo, nos ''scripts'' criamos uma constante chamada <code>type</code>. Isso se deve ao motivo de que estamos criando nossos inimigos em tempo de execução e eles surgem com nomes doidos que o Godot dá. Dessa forma, na hora de detectar o tipo de objeto que colidimos, não testaremos mais o <code>name</code>, mas sim o <code>type</code>. | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | </ | ||
== Ship == | == Ship == | ||
− | <syntaxhighlight lang=gdscript> | + | <syntaxhighlight lang="gdscript"> |
− | extends | + | extends CharacterBody2D |
− | var speed = 500 | + | const type : String = "ship" |
− | var screensize | + | var speed : int = 500 # velocidade para os lados |
− | var | + | var screensize : Vector2 # tamanho da tela |
− | var half_h | + | var h : int # altura da imagem |
+ | var half_w : int # metade da largura da imagem | ||
+ | var half_h : int # metade da altura da imagem | ||
− | func _ready(): | + | # Quando o personagem eh criado, ele eh colocado no |
+ | # meio da tela, so que temos um deslocamento para a esquerda | ||
+ | # contando metade da textura da nave, senao a nave fica meio | ||
+ | # para a direita, por isso precisamos do half_w | ||
+ | func _ready() -> void: | ||
screensize = get_viewport_rect().size | screensize = get_viewport_rect().size | ||
− | + | h = $Sprite2D.texture.get_height() | |
− | + | half_w = $Sprite2D.texture.get_width() / 2 | |
− | + | half_h = int(h / 2.0) | |
− | func _process(delta): | + | func _process(delta) -> void: |
var vec = Vector2() | var vec = Vector2() | ||
− | if Input.is_action_pressed("ui_right"): | + | if Input.is_action_pressed("ui_left"): |
+ | vec.x -= speed | ||
+ | elif Input.is_action_pressed("ui_right"): | ||
vec.x += speed | vec.x += speed | ||
− | + | # Quando pressiomamos a barra de espaco, damos um tiro. Isso | |
− | + | # na verdade cria um objeto bomba em cima do personagem. | |
if Input.is_action_just_pressed("ui_select"): | if Input.is_action_just_pressed("ui_select"): | ||
get_parent().new_bomb(Vector2(position.x, position.y - half_h - 20)) | get_parent().new_bomb(Vector2(position.x, position.y - half_h - 20)) | ||
− | var | + | var _info = move_and_collide(vec * delta) |
− | + | # Se vai sair da tela, volta | |
− | + | position.x = clamp(position.x, half_w, screensize.x - half_w) | |
− | + | # Se pontuacao negativa, morre | |
− | + | if get_parent().score < 0: | |
− | + | kill() | |
− | + | ||
− | + | func kill() -> void: | |
− | + | queue_free() | |
− | + | get_parent().new_explosion(position) | |
+ | get_parent().game_over() | ||
</syntaxhighlight> | </syntaxhighlight> | ||
== Enemy == | == Enemy == | ||
− | <syntaxhighlight lang=gdscript> | + | <syntaxhighlight lang="gdscript"> |
− | extends | + | extends CharacterBody2D |
− | const LEFT = 0 | + | const type : String = "enemy" |
− | const RIGHT = 1 | + | const LEFT : int = 0 |
+ | const RIGHT : int = 1 | ||
+ | var screensize : Vector2 # tamanho da tela | ||
+ | var direction : int # direcao do inimigo [esquerda|direita] | ||
+ | var w : int # largura da imagem | ||
+ | var h : int # altura da imagem | ||
+ | var half_w : int # metade da largura da imagem | ||
+ | var half_h : int # metade da altura da imagem | ||
+ | var speed_y : int = 200 # velocidade para baixo | ||
+ | var speed_x : int = 300 # velocidade para os lados | ||
− | + | func _ready() -> void: | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | func _ready(): | ||
randomize() | randomize() | ||
screensize = get_viewport_rect().size | screensize = get_viewport_rect().size | ||
− | w = $ | + | w = $Sprite2D.texture.get_width() |
− | h = $ | + | h = $Sprite2D .texture.get_height() |
− | half_w = w / 2 | + | half_w = int(w / 2.0) |
− | half_h = h / 2 | + | half_h = int(h / 2.0) |
+ | # Criamos nosso inimigo acima da area visivel do jogo | ||
+ | # com posicao e direcao aleatorias | ||
position.x = (randi() % int(screensize.x - w)) + half_w | position.x = (randi() % int(screensize.x - w)) + half_w | ||
position.y = -100 | position.y = -100 | ||
− | + | direction = randi() % 2 | |
− | + | ||
− | func _process(delta): | + | func _process(delta) -> void: |
− | var | + | var vec = Vector2() |
+ | vec.y += speed_y | ||
+ | # Se o inimigo encosta dos lados da tela, ele muda a | ||
+ | # o movimento para a direcao oposta | ||
if direction == RIGHT: | if direction == RIGHT: | ||
− | + | vec.x += speed_x | |
if position.x + half_w > screensize.x: | if position.x + half_w > screensize.x: | ||
direction = LEFT | direction = LEFT | ||
elif direction == LEFT: | elif direction == LEFT: | ||
− | + | vec.x -= speed_x | |
if position.x - half_w < 0: | if position.x - half_w < 0: | ||
direction = RIGHT | direction = RIGHT | ||
− | + | var collision = move_and_collide(vec * delta) | |
− | + | if collision: | |
− | + | var entity = collision.get_collider() | |
− | + | if 'ship' in entity.type: | |
− | + | kill() | |
− | + | entity.kill() | |
− | + | # Se sair da tela, passou da tela pra baixo, | |
− | + | # o jogador perde ponto | |
− | |||
− | |||
if position.y - half_h > screensize.y: | if position.y - half_h > screensize.y: | ||
+ | get_parent().change_score(-1) | ||
+ | queue_free() | ||
+ | # Se o jogo acabou, libera o inimigo | ||
+ | if get_parent().is_game_over: | ||
queue_free() | queue_free() | ||
+ | |||
+ | # Se o inimigo morrer, cria uma explosao nessa mesma posicao | ||
+ | # e retira o inimigo do jogo com queue_free. | ||
+ | func kill() -> void: | ||
+ | get_parent().new_explosion(position) | ||
+ | queue_free() | ||
</syntaxhighlight> | </syntaxhighlight> | ||
== Bomb == | == Bomb == | ||
− | <syntaxhighlight lang=gdscript> | + | <syntaxhighlight lang="gdscript"> |
− | extends | + | extends CharacterBody2D |
− | |||
− | |||
+ | const type : String = "bomb" | ||
+ | var speed : int = 200 | ||
− | func _ready(): | + | func _ready() -> void: |
$SoundBomb.play() | $SoundBomb.play() | ||
− | + | func _physics_process(delta: float) -> void: | |
− | func | + | var collision = move_and_collide(Vector2(0, -speed) * delta) |
− | var | + | if collision: |
− | if | + | var entity = collision.get_collider() |
− | var | + | if 'enemy' in entity.type: |
− | if ' | + | entity.kill() |
− | |||
queue_free() | queue_free() | ||
− | get_parent(). | + | get_parent().change_score(1) |
− | + | if position.y < 0: | |
− | if | ||
queue_free() | queue_free() | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Linha 178: | Linha 209: | ||
== Explosion == | == Explosion == | ||
− | <syntaxhighlight lang=gdscript> | + | <syntaxhighlight lang="gdscript"> |
− | extends | + | extends CharacterBody2D |
− | func _ready(): | + | func _ready() -> void: |
− | $ | + | $AnimatedSprite2D.play() |
$SoundExplosion.play() | $SoundExplosion.play() | ||
− | func | + | # Quando termina a animacao cai fora do jogo |
+ | func _on_animated_sprite_2d_animation_finished() -> void: | ||
queue_free() | queue_free() | ||
</syntaxhighlight> | </syntaxhighlight> | ||
Linha 191: | Linha 223: | ||
== World == | == World == | ||
− | <syntaxhighlight lang=gdscript> | + | <syntaxhighlight lang="gdscript"> |
extends Node2D | extends Node2D | ||
− | + | # Vamos precisar conhecer os objetos porque vamos | |
− | + | # cria-los dinamicamente. Para isso, fazemos um | |
− | + | # preload deles. | |
+ | const Ship = preload("res://ship.tscn") | ||
+ | const Bomb = preload("res://bomb.tscn") | ||
+ | const Enemy = preload("res://enemy.tscn") | ||
+ | const Explosion = preload("res://explosion.tscn") | ||
+ | var score: int = 0 # pontuacao | ||
+ | var is_game_over: bool = false # se o jogo terminou | ||
+ | var screensize: Vector2 # tamanho da tela | ||
+ | |||
+ | func _ready() -> void: | ||
+ | screensize = get_viewport_rect().size | ||
+ | |||
+ | func _physics_process(_delta: float) -> void: | ||
+ | # Se for gameover e pressionar esc, o jogo reinicia | ||
+ | if is_game_over: | ||
+ | if Input.is_action_just_pressed("ui_cancel"): | ||
+ | _reset() | ||
− | # | + | # Reinicializacao do jogo |
− | + | func _reset(): | |
+ | is_game_over = false # setamos gameover para falso | ||
+ | score = 0 # zeramos a pontuacao | ||
+ | $Score.text = str(score) # atualizamos o label | ||
+ | var ship = Ship.instantiate() # instanciamos uma nova nave | ||
+ | # e colocamos ela em uma posicao espedifica | ||
+ | ship.position = Vector2(screensize.y - 10, screensize.x / 2) | ||
+ | add_child(ship) # adicionamos ela ao world | ||
+ | $GameOver.visible = false # imagem de gameover invisivel | ||
+ | $Music.play() # tocamos a musica | ||
+ | $EnemyTimer.start() # iniciamos o timer para criar inimigos | ||
− | func | + | # Alteramos a pontuacao |
+ | func change_score(pts: int): | ||
+ | score += pts | ||
$Score.text = str(score) | $Score.text = str(score) | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | func | + | func game_over() -> void: |
− | var | + | is_game_over = true # gameover true |
− | + | $GameOver.visible = true # mostramos a imagem gameover | |
− | add_child( | + | $Music.stop() # paramos a musica |
+ | $EnemyTimer.stop() # paramos o timer de criar inimigos | ||
+ | |||
+ | # Funcao usada para criar uma explosao em uma posicao (pos) especifica | ||
+ | # normamente vai ser a propria posicao do elemento que morreu | ||
+ | func new_explosion(pos: Vector2): | ||
+ | var explosion = Explosion.instantiate() # instanciamos um objeto Explosion | ||
+ | explosion.position = pos # colocamos ele na posicao | ||
+ | add_child(explosion) # adicionamos ao jogo | ||
− | func | + | # Cria uma bombinha. Normalmente feito pela nave Ship |
− | var | + | # Entao usamos a posicao relativa a posicao x do personagem |
− | + | # mais a metada da largura da imagem de Ship e menos a metade | |
− | add_child( | + | # da largura da imagem da bomba. Esse calculo fazemos la no |
+ | # script de Ship. Isso para que a bombinha fique centralizada | ||
+ | # tambem colocamos, y acima da nave. | ||
+ | func new_bomb(pos: Vector2) -> void: | ||
+ | var bomb = Bomb.instantiate() # Instanciamos um objeto bomba | ||
+ | bomb.position = pos # colocamos ele na posicao | ||
+ | add_child(bomb) # adicinamos ela ao jogo | ||
− | func | + | # Quando o timer de criacao do inimigo der timeout |
− | var enemy = Enemy. | + | func _on_enemy_timer_timeout() -> void: |
− | add_child(enemy) | + | if is_game_over: # se gameover nao cria mais |
− | $EnemyTimer.wait_time = (randi() % | + | return |
− | $EnemyTimer.start() | + | var enemy = Enemy.instantiate() # instancia um novo inimigo |
+ | add_child(enemy) # adiciona ele ao jogo | ||
+ | $EnemyTimer.wait_time = (randi() % 2) + 1 # altera o wait_time aleatoriamente | ||
+ | $EnemyTimer.start() # reinicia o timer | ||
</syntaxhighlight> | </syntaxhighlight> |
Edição atual tal como às 18h15min de 24 de setembro de 2024
Afluentes : Jogos Digitais, Usabilidade, desenvolvimento web, mobile e jogos
Screenshot
Já vimos alguns games passo a passo, agora vamos tentar um desafio. A partir dos assets, da estrutura do projeto, os scripts e algumas dicas, vamos tentar criar o game SkyFire. Abaixo temos um screenshot.
Assets
Estrutura
Veja que:
- todas as Cenas que possuem scripts coloquei uma tag [script] no nó que ele deve ser criado.
- Depois de criado os scripts, em alguns nós da cena devemos criar sinais.
- Crie os sinais só depois da estrutura e do script criados.
- A Cena
Sky
vou explicar um pouco melhor porque vamos precisar fazer um scrolling na imagem de fundo e vamos usar um recurso bem legal do Godot.
Abaixo temos a estrutura de Cenas e nós.
- Sky (Node2D)
- TextureRect
- Ship (CharacterBody2D) [script]
- Sprite2D
- CollisionShape2D
- Enemy (CharacterBody2D) [script]
- Sprite2D
- CollisionShape2D
- Bomb (CharacterBody2D) [script]
- Sprite2D
- CollisionShape2D
- SoundBomb (AudioStreamPlayer2D)
- Explosion (CharacterBody2D) [script]
- AnimatedSprite2D - Quando estiver no editor de SpriteFrames, desabilitar a repetição em um botãozinho ao lado de
5 FPS
, conforme imagem abaixo.- Nó (sinal) - _on_animated_sprite_2d_animation_finished()
- CollisionShape2D - Inspector... CollisionShape2D... disabled Ativo.
- SoundExplosion (AudioStreamPlayer2D)
- AnimatedSprite2D - Quando estiver no editor de SpriteFrames, desabilitar a repetição em um botãozinho ao lado de
- World (Node2D) [script]
- Sky (instancia)
- Ship (instancia)
- EnemiTimer (Timer) - com Autostart ativo.
- Nó (sinal) - _on_enemy_timer_timeout()
- Music (AudioStreamPlayer2D) - lembre de deixar em Autoplaying e em loop (na importação).
- GameOver (TextureRect) - Em inspector... CanvasItem... Visibility... desabilitar Visible
- Label (Label) - Alterar a cor do texto em Inspector... Control... Themes Overrides... Colors... selecionar Font Color. pode deixar preto mesmo ou alterar a cor. Podemos alterar Font Sizes para 30px.
- Score (Label) - Altere a cor também e o tamanho da fonte.
Cena Sky
Vamos iniciar criando uma cena com um nó raiz do tipo Node2D
. Renomeie esse nó para Sky e crie um nó filho do tipo TextureRect
.
Clique no nó TextureRect
e arraste a imagem background.png
(dos nossos recursos do game) para a aba Inspector... RextureRect... Texture...
Ainda na aba Inspector, vamos em CamvasItem e então clique em Material, expandindo ele. Dentro tem um item chamado Material (também), clique do lado onde está [vazio] e adicione um Novo Shader Material.
Vai aparecer uma esfera cinza. Clique nela e vai expandir. Em baixo vai aparecer um elemento chamado Shader que está como [vazio], clique ali e adicione um Novo Shader.
Veja que ele vai pedir pra criar um script especial para esse shader. Como já renomeamos nosso nó raiz como Sky, ele vai salvar como sky.gdshader
. Salve esse script e clique no shader criado (em baixo da esfera).
Ele vai abrir uma ferramenta (imagem abaixo) para editar o script do shader. Vamos alterar o script.
Não vamos precisar das funções fragment
e ligth
. Então vamos excluí-las.
shader_type canvas_item;
void vertex() {
UV.y -= TIME * 0.1;
}
Veja que na função vertex
, usamos o atributo UV.y e diminuímos ele conforme o TIME. Só que como fica muito rápido, multiplicamos por 0.1.
Por fim, para que a textura fique se repetindo, precisamos ir, ainda em CanvaItem, em Texture, selecionar Repeat e colocar Enabled.
E pronto! Temos nosso fundo que fica se em modo scrolling.
Scripts
Nesse exemplo, nos scripts criamos uma constante chamada type
. Isso se deve ao motivo de que estamos criando nossos inimigos em tempo de execução e eles surgem com nomes doidos que o Godot dá. Dessa forma, na hora de detectar o tipo de objeto que colidimos, não testaremos mais o name
, mas sim o type
.
Ship
extends CharacterBody2D
const type : String = "ship"
var speed : int = 500 # velocidade para os lados
var screensize : Vector2 # tamanho da tela
var h : int # altura da imagem
var half_w : int # metade da largura da imagem
var half_h : int # metade da altura da imagem
# Quando o personagem eh criado, ele eh colocado no
# meio da tela, so que temos um deslocamento para a esquerda
# contando metade da textura da nave, senao a nave fica meio
# para a direita, por isso precisamos do half_w
func _ready() -> void:
screensize = get_viewport_rect().size
h = $Sprite2D.texture.get_height()
half_w = $Sprite2D.texture.get_width() / 2
half_h = int(h / 2.0)
func _process(delta) -> void:
var vec = Vector2()
if Input.is_action_pressed("ui_left"):
vec.x -= speed
elif Input.is_action_pressed("ui_right"):
vec.x += speed
# Quando pressiomamos a barra de espaco, damos um tiro. Isso
# na verdade cria um objeto bomba em cima do personagem.
if Input.is_action_just_pressed("ui_select"):
get_parent().new_bomb(Vector2(position.x, position.y - half_h - 20))
var _info = move_and_collide(vec * delta)
# Se vai sair da tela, volta
position.x = clamp(position.x, half_w, screensize.x - half_w)
# Se pontuacao negativa, morre
if get_parent().score < 0:
kill()
func kill() -> void:
queue_free()
get_parent().new_explosion(position)
get_parent().game_over()
Enemy
extends CharacterBody2D
const type : String = "enemy"
const LEFT : int = 0
const RIGHT : int = 1
var screensize : Vector2 # tamanho da tela
var direction : int # direcao do inimigo [esquerda|direita]
var w : int # largura da imagem
var h : int # altura da imagem
var half_w : int # metade da largura da imagem
var half_h : int # metade da altura da imagem
var speed_y : int = 200 # velocidade para baixo
var speed_x : int = 300 # velocidade para os lados
func _ready() -> void:
randomize()
screensize = get_viewport_rect().size
w = $Sprite2D.texture.get_width()
h = $Sprite2D .texture.get_height()
half_w = int(w / 2.0)
half_h = int(h / 2.0)
# Criamos nosso inimigo acima da area visivel do jogo
# com posicao e direcao aleatorias
position.x = (randi() % int(screensize.x - w)) + half_w
position.y = -100
direction = randi() % 2
func _process(delta) -> void:
var vec = Vector2()
vec.y += speed_y
# Se o inimigo encosta dos lados da tela, ele muda a
# o movimento para a direcao oposta
if direction == RIGHT:
vec.x += speed_x
if position.x + half_w > screensize.x:
direction = LEFT
elif direction == LEFT:
vec.x -= speed_x
if position.x - half_w < 0:
direction = RIGHT
var collision = move_and_collide(vec * delta)
if collision:
var entity = collision.get_collider()
if 'ship' in entity.type:
kill()
entity.kill()
# Se sair da tela, passou da tela pra baixo,
# o jogador perde ponto
if position.y - half_h > screensize.y:
get_parent().change_score(-1)
queue_free()
# Se o jogo acabou, libera o inimigo
if get_parent().is_game_over:
queue_free()
# Se o inimigo morrer, cria uma explosao nessa mesma posicao
# e retira o inimigo do jogo com queue_free.
func kill() -> void:
get_parent().new_explosion(position)
queue_free()
Bomb
extends CharacterBody2D
const type : String = "bomb"
var speed : int = 200
func _ready() -> void:
$SoundBomb.play()
func _physics_process(delta: float) -> void:
var collision = move_and_collide(Vector2(0, -speed) * delta)
if collision:
var entity = collision.get_collider()
if 'enemy' in entity.type:
entity.kill()
queue_free()
get_parent().change_score(1)
if position.y < 0:
queue_free()
Explosion
extends CharacterBody2D
func _ready() -> void:
$AnimatedSprite2D.play()
$SoundExplosion.play()
# Quando termina a animacao cai fora do jogo
func _on_animated_sprite_2d_animation_finished() -> void:
queue_free()
World
extends Node2D
# Vamos precisar conhecer os objetos porque vamos
# cria-los dinamicamente. Para isso, fazemos um
# preload deles.
const Ship = preload("res://ship.tscn")
const Bomb = preload("res://bomb.tscn")
const Enemy = preload("res://enemy.tscn")
const Explosion = preload("res://explosion.tscn")
var score: int = 0 # pontuacao
var is_game_over: bool = false # se o jogo terminou
var screensize: Vector2 # tamanho da tela
func _ready() -> void:
screensize = get_viewport_rect().size
func _physics_process(_delta: float) -> void:
# Se for gameover e pressionar esc, o jogo reinicia
if is_game_over:
if Input.is_action_just_pressed("ui_cancel"):
_reset()
# Reinicializacao do jogo
func _reset():
is_game_over = false # setamos gameover para falso
score = 0 # zeramos a pontuacao
$Score.text = str(score) # atualizamos o label
var ship = Ship.instantiate() # instanciamos uma nova nave
# e colocamos ela em uma posicao espedifica
ship.position = Vector2(screensize.y - 10, screensize.x / 2)
add_child(ship) # adicionamos ela ao world
$GameOver.visible = false # imagem de gameover invisivel
$Music.play() # tocamos a musica
$EnemyTimer.start() # iniciamos o timer para criar inimigos
# Alteramos a pontuacao
func change_score(pts: int):
score += pts
$Score.text = str(score)
func game_over() -> void:
is_game_over = true # gameover true
$GameOver.visible = true # mostramos a imagem gameover
$Music.stop() # paramos a musica
$EnemyTimer.stop() # paramos o timer de criar inimigos
# Funcao usada para criar uma explosao em uma posicao (pos) especifica
# normamente vai ser a propria posicao do elemento que morreu
func new_explosion(pos: Vector2):
var explosion = Explosion.instantiate() # instanciamos um objeto Explosion
explosion.position = pos # colocamos ele na posicao
add_child(explosion) # adicionamos ao jogo
# Cria uma bombinha. Normalmente feito pela nave Ship
# Entao usamos a posicao relativa a posicao x do personagem
# mais a metada da largura da imagem de Ship e menos a metade
# da largura da imagem da bomba. Esse calculo fazemos la no
# script de Ship. Isso para que a bombinha fique centralizada
# tambem colocamos, y acima da nave.
func new_bomb(pos: Vector2) -> void:
var bomb = Bomb.instantiate() # Instanciamos um objeto bomba
bomb.position = pos # colocamos ele na posicao
add_child(bomb) # adicinamos ela ao jogo
# Quando o timer de criacao do inimigo der timeout
func _on_enemy_timer_timeout() -> void:
if is_game_over: # se gameover nao cria mais
return
var enemy = Enemy.instantiate() # instancia um novo inimigo
add_child(enemy) # adiciona ele ao jogo
$EnemyTimer.wait_time = (randi() % 2) + 1 # altera o wait_time aleatoriamente
$EnemyTimer.start() # reinicia o timer