Godot Engine: SkyFire

De Aulas
Revisão de 09h31min de 21 de maio de 2024 por Admin (discussão | contribs) (→‎Estrutura)




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 (StaticBody2D) [script]
    • AnimatedSprite2D
      • Nó (sinal) - _on_animated_sprite_2d_animation_finished()
    • CollisionShape2D
    • SoundExplosion (AudioStreamPlayer2D)
  • World (Node2D) [script]
    • Background (instancia)
    • Ship (instancia)
    • EnemiTimer (Timer)
      • Nó (sinal) - _on_enemy_timer_timeout()
    • Music (AudioStreamPlayer2D)
    • GameOver (TextureRect)
    • Label (Label)
    • Score (Label)

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

Background

extends Node2D

var speed = 100
var screensize
var h
var half_h

func _ready():
	screensize = get_viewport_rect().size
	h = $A.texture.get_height()
	half_h = h / 2

func _process(delta):
	$A.position.y += speed * delta
	$B.position.y += speed * delta
	if $A.position.y - half_h > screensize.y:
		$A.position.y -= h * 2
	if $B.position.y - half_h > screensize.y:
		$B.position.y -= h * 2

Ship

extends CharacterBody2D

const type = 'ship'
var speed = 500
var screensize
var h
var half_w
var half_h

func _ready():
	screensize = get_viewport_rect().size
	h = $Sprite2D.texture.get_height()
	half_w = $Sprite2D.texture.get_width() / 2
	half_h = h / 2

func _process(delta):
	var vec = Vector2()
	if Input.is_action_pressed("ui_left"):
		vec.x -= speed
	elif Input.is_action_pressed("ui_right"):
		vec.x += speed
	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)
	position.x = clamp(position.x, half_w, screensize.x - half_w)
	if get_parent().score < 0:
		kill()

func kill():
	queue_free()
	get_parent().new_explosion(position)
	get_parent().game_over()

Enemy

extends CharacterBody2D

const type = 'enemy'
const LEFT = 0
const RIGHT = 1

var screensize
var direction
var w
var h
var half_w
var half_h
var speed_y = 200
var speed_x = 300

func _ready():
	randomize()
	screensize = get_viewport_rect().size
	w = $Sprite2D.texture.get_width()
	h = $Sprite2D .texture.get_height()
	half_w = w / 2
	half_h = h / 2
	position.x = (randi() % int(screensize.x - w)) + half_w
	position.y = -100
	direction = randi() % 2
	
func _process(delta):
	var vec = Vector2()
	vec.y += speed_y
	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()
	if position.y - half_h > screensize.y:
		get_parent().change_score(-1)
		queue_free()
	if get_parent().is_game_over:
		queue_free()

func kill():
	get_parent().new_explosion(position)
	queue_free()

Bomb

extends CharacterBody2D

const type = 'bomb'
var speed = 200

func _ready(): 
	$SoundBomb.play()

func _process(delta):
	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 StaticBody2D

const type = 'explosion'

func _ready():
	$AnimatedSprite2D.play()
	$SoundExplosion.play()

func _on_animated_sprite_2d_animation_finished():
	queue_free()

World

extends Node2D

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 = 0
var is_game_over = false
var screensize

func _ready():
	screensize = get_viewport_rect().size

func _process(_delta):
	if is_game_over:
		if Input.is_action_just_pressed("ui_accept"):
			_reset()

func _reset():
	is_game_over = false
	score = 0
	$Score.text = str(score)
	var ship = Ship.instantiate()
	ship.position = Vector2(screensize.y - 10, screensize.x / 2)
	add_child(ship)
	$GameOver.visible = false
	$Music.play()
	$EnemyTimer.start()

func change_score(pts):
	score += pts
	$Score.text = str(score)

func game_over():
	is_game_over = true
	$GameOver.visible = true
	$Music.stop()
	$EnemyTimer.stop()

func new_explosion(pos):
	var explosion = Explosion.instantiate()
	explosion.position = pos
	add_child(explosion)

func new_bomb(pos):
	var bomb = Bomb.instantiate()
	bomb.position = pos
	add_child(bomb)

func _on_enemy_timer_timeout():
	if is_game_over:
		return
	var enemy = Enemy.instantiate()
	add_child(enemy)
	$EnemyTimer.wait_time = (randi() % 2) + 1
	$EnemyTimer.start()