Mudanças entre as edições de "Godot Engine: SkyFire"

De Aulas
 
(3 revisões intermediárias pelo mesmo usuário não estão sendo mostradas)
Linha 1: Linha 1:
 
 
 
 
 
 
 
 
 
 
 
  
 
Afluentes : [[Jogos Digitais]], [[Usabilidade, desenvolvimento web, mobile e jogos]]
 
Afluentes : [[Jogos Digitais]], [[Usabilidade, desenvolvimento web, mobile e jogos]]
Linha 17: Linha 6:
  
 
== Assets ==
 
== Assets ==
* [https://saulo.arisa.com.br/~saulo/aulas/unisul/games/assets/skyfire/skyfire.zip Assets]
+
* [https://saulo.arisa.com.br/aulas/unisul/games/assets/skyfire_godot.zip Assets]
* [https://github.com/saulopz/skyfire_godot3 Projeto SkyFire Godot 3 no GitHub]
+
* [https://github.com/saulopz/skyfire Projeto SkyFire no GitHub]
  
 
= Estrutura =
 
= Estrutura =
Linha 90: Linha 79:
 
extends CharacterBody2D
 
extends CharacterBody2D
  
const type = 'ship'  # tipo do personagem
+
const type : String = "ship"
var speed = 500     # velocidade para os lados
+
var speed : int = 500     # velocidade para os lados
var screensize       # tamanho da tela
+
var screensize : Vector2  # tamanho da tela
var h               # altura da imagem
+
var h : int              # altura da imagem
var half_w           # metade da largura da imagem
+
var half_w : int          # metade da largura da imagem
var half_h           # metade da altura da imagem
+
var half_h : int          # metade da altura da imagem
  
 
# Quando o personagem eh criado, ele eh colocado no
 
# Quando o personagem eh criado, ele eh colocado no
Linha 101: Linha 90:
 
# contando metade da textura da nave, senao a nave fica meio
 
# contando metade da textura da nave, senao a nave fica meio
 
# para a direita, por isso precisamos do half_w
 
# para a direita, por isso precisamos do half_w
func _ready():
+
func _ready() -> void:
 
screensize = get_viewport_rect().size
 
screensize = get_viewport_rect().size
 
h = $Sprite2D.texture.get_height()
 
h = $Sprite2D.texture.get_height()
 
half_w = $Sprite2D.texture.get_width() / 2
 
half_w = $Sprite2D.texture.get_width() / 2
half_h = h / 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_left"):
 
if Input.is_action_pressed("ui_left"):
Linha 124: Linha 113:
 
kill()
 
kill()
  
func kill():
+
func kill() -> void:
 
queue_free()
 
queue_free()
 
get_parent().new_explosion(position)
 
get_parent().new_explosion(position)
Linha 135: Linha 124:
 
extends CharacterBody2D
 
extends CharacterBody2D
  
const type = 'enemy'
+
const type : String = "enemy"
const LEFT = 0
+
const LEFT : int = 0
const RIGHT = 1
+
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
  
var screensize      # tamanho da tela
+
func _ready() -> void:
var direction      # direcao do inimigo [esquerda|direita]
 
var w              # largura da imagem
 
var h              # altura da imagem
 
var half_w          # metade da largura da imagem
 
var half_h          # metade da altura da imagem
 
var speed_y = 200  # velocidade para baixo
 
var speed_x = 300  # velocidade para os lados
 
 
 
func _ready():
 
 
randomize()
 
randomize()
 
screensize = get_viewport_rect().size
 
screensize = get_viewport_rect().size
 
w = $Sprite2D.texture.get_width()
 
w = $Sprite2D.texture.get_width()
 
h = $Sprite2D .texture.get_height()
 
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
 
# Criamos nosso inimigo acima da area visivel do jogo
 
# com posicao e direcao aleatorias
 
# com posicao e direcao aleatorias
Linha 161: Linha 149:
 
direction = randi() % 2
 
direction = randi() % 2
 
 
func _process(delta):
+
func _process(delta) -> void:
 
var vec = Vector2()
 
var vec = Vector2()
 
vec.y += speed_y
 
vec.y += speed_y
Linha 191: Linha 179:
 
# Se o inimigo morrer, cria uma explosao nessa mesma posicao
 
# Se o inimigo morrer, cria uma explosao nessa mesma posicao
 
# e retira o inimigo do jogo com queue_free.
 
# e retira o inimigo do jogo com queue_free.
func kill():
+
func kill() -> void:
 
get_parent().new_explosion(position)
 
get_parent().new_explosion(position)
 
queue_free()
 
queue_free()
Linha 201: Linha 189:
 
extends CharacterBody2D
 
extends CharacterBody2D
  
const type = 'bomb'
+
const type : String = "bomb"
var speed = 200
+
var speed : int = 200
  
func _ready():  
+
func _ready() -> void:  
 
$SoundBomb.play()
 
$SoundBomb.play()
  
func _process(delta):
+
func _physics_process(delta: float) -> void:
 
var collision = move_and_collide(Vector2(0, -speed) * delta)
 
var collision = move_and_collide(Vector2(0, -speed) * delta)
 
if collision:
 
if collision:
Linha 224: Linha 212:
 
extends CharacterBody2D
 
extends CharacterBody2D
  
const type = 'explosion'
+
func _ready() -> void:
 
 
func _ready():
 
 
$AnimatedSprite2D.play()
 
$AnimatedSprite2D.play()
 
$SoundExplosion.play()
 
$SoundExplosion.play()
  
 
# Quando termina a animacao cai fora do jogo
 
# Quando termina a animacao cai fora do jogo
func _on_animated_sprite_2d_animation_finished():
+
func _on_animated_sprite_2d_animation_finished() -> void:
 
queue_free()
 
queue_free()
 
</syntaxhighlight>
 
</syntaxhighlight>
Linha 247: Linha 233:
 
const Enemy = preload("res://enemy.tscn")
 
const Enemy = preload("res://enemy.tscn")
 
const Explosion = preload("res://explosion.tscn")
 
const Explosion = preload("res://explosion.tscn")
var score = 0               # pontuacao
+
var score: int = 0             # pontuacao
var is_game_over = false     # se o jogo terminou
+
var is_game_over: bool = false # se o jogo terminou
var screensize               # tamanho da tela
+
var screensize: Vector2        # tamanho da tela
  
func _ready():
+
func _ready() -> void:
 
screensize = get_viewport_rect().size
 
screensize = get_viewport_rect().size
  
func _process(_delta):
+
func _physics_process(_delta: float) -> void:
 
# Se for gameover e pressionar esc, o jogo reinicia
 
# Se for gameover e pressionar esc, o jogo reinicia
 
if is_game_over:
 
if is_game_over:
Linha 274: Linha 260:
  
 
# Alteramos a pontuacao
 
# Alteramos a pontuacao
func change_score(pts):
+
func change_score(pts: int):
 
score += pts
 
score += pts
 
$Score.text = str(score)
 
$Score.text = str(score)
  
func game_over():
+
func game_over() -> void:
 
is_game_over = true        # gameover true
 
is_game_over = true        # gameover true
 
$GameOver.visible = true    # mostramos a imagem gameover
 
$GameOver.visible = true    # mostramos a imagem gameover
Linha 286: Linha 272:
 
# Funcao usada para criar uma explosao em uma posicao (pos) especifica
 
# Funcao usada para criar uma explosao em uma posicao (pos) especifica
 
# normamente vai ser a propria posicao do elemento que morreu
 
# normamente vai ser a propria posicao do elemento que morreu
func new_explosion(pos):
+
func new_explosion(pos: Vector2):
 
var explosion = Explosion.instantiate() # instanciamos um objeto Explosion
 
var explosion = Explosion.instantiate() # instanciamos um objeto Explosion
 
explosion.position = pos                # colocamos ele na posicao
 
explosion.position = pos                # colocamos ele na posicao
Linha 297: Linha 283:
 
# script de Ship. Isso para que a bombinha fique centralizada
 
# script de Ship. Isso para que a bombinha fique centralizada
 
# tambem colocamos, y acima da nave.
 
# tambem colocamos, y acima da nave.
func new_bomb(pos):
+
func new_bomb(pos: Vector2) -> void:
 
var bomb = Bomb.instantiate()  # Instanciamos um objeto bomba
 
var bomb = Bomb.instantiate()  # Instanciamos um objeto bomba
 
bomb.position = pos            # colocamos ele na posicao
 
bomb.position = pos            # colocamos ele na posicao
Linha 303: Linha 289:
  
 
# Quando o timer de criacao do inimigo der timeout
 
# Quando o timer de criacao do inimigo der timeout
func _on_enemy_timer_timeout():
+
func _on_enemy_timer_timeout() -> void:
 
if is_game_over:                          # se gameover nao cria mais
 
if is_game_over:                          # se gameover nao cria mais
 
return
 
return

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.

Godot skyfire.jpg

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)
  • 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.
Desabilitando repetição na animação da explosão.

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.

Cena Sky.

Clique no nó TextureRect e arraste a imagem background.png (dos nossos recursos do game) para a aba Inspector... RextureRect... Texture...

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.

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.

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).

Novo Shader.

Ele vai abrir uma ferramenta (imagem abaixo) para editar o script do shader. Vamos alterar o script.

Editor de script do shader.

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.

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 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