Partilhar: WhatsApp
aulify
UC UC02841 · T. Desenv. Software

Ficha 02 · Godot intermédio

Inimigos, áudio, save, export
Versão · Aluno
Tempo · 45 minutos
Aluno(a)
Turma
Data

Exercício 1 · Inimigo com patrulha

Cria scene "Enemy.tscn" (CharacterBody2D) que anda para a frente e para trás entre 2 marcadores. Quando bate numa parede inverte direcção.

Resposta:

enemy.gd:

extends CharacterBody2D

const SPEED = 100
const GRAVITY = 980

var direction = 1   # 1 ou -1

func _physics_process(delta):
    if not is_on_floor():
        velocity.y += GRAVITY * delta

    velocity.x = SPEED * direction

    var collision = move_and_collide(velocity * delta)
    if collision:
        # Se bateu em parede (não chão), inverte
        if abs(collision.get_normal().x) > 0.5:
            direction *= -1
    else:
        move_and_slide()

Ou (simples) usar RayCast2D para detectar parede/borda:

@onready var ray = $RayCast2D

func _physics_process(delta):
    if ray.is_colliding():
        direction *= -1
        ray.target_position.x *= -1
    velocity.x = SPEED * direction
    move_and_slide()

Exercício 2 · Spawn de inimigos com Timer

Cria spawner que gera um inimigo a cada 3 segundos em posição aleatória entre x=100 e x=900.

Resposta:
Spawner (Node2D)
└── Timer

spawner.gd:

extends Node2D

const EnemyScene = preload("res://enemy.tscn")

@onready var timer = $Timer

func _ready():
    timer.wait_time = 3.0
    timer.timeout.connect(_spawn)
    timer.start()

func _spawn():
    var e = EnemyScene.instantiate()
    e.position = Vector2(randf_range(100, 900), 100)
    get_parent().add_child(e)

Exercício 3 · Player com vidas e morte

Adiciona ao Player: - 3 vidas (variável). - Sinal vida_mudou(nova). - Função sofrer_dano() que decrementa. - Quando vidas = 0, mostra "Game Over" na UI.

Resposta:

player.gd:

extends CharacterBody2D

signal vida_mudou(nova)
signal morreu

var vidas = 3

func sofrer_dano(d = 1):
    vidas -= d
    vida_mudou.emit(vidas)
    if vidas <= 0:
        morreu.emit()
        # Desactivar input
        set_physics_process(false)
        $AnimatedSprite2D.play("die")

ui.gd:

extends CanvasLayer

@onready var vidas_label = $VidasLabel
@onready var game_over = $GameOverPanel

func _ready():
    var player = get_node("/root/World/Player")
    player.vida_mudou.connect(_atualizar_vidas)
    player.morreu.connect(_game_over)
    game_over.hide()
    _atualizar_vidas(player.vidas)

func _atualizar_vidas(n):
    vidas_label.text = "Vidas: " + str(n)

func _game_over():
    game_over.show()

Exercício 4 · Coletável dá vida

Cria scene "HealthCoin.tscn" (Area2D) que ao ser apanhada dá +1 vida ao player. Reutiliza scripts anteriores.

Resposta:

health_coin.gd:

extends Area2D

func _ready():
    body_entered.connect(_on_body_entered)

func _on_body_entered(body):
    if body.has_method("sofrer_dano"):    # é um player-like
        body.vidas += 1
        body.vida_mudou.emit(body.vidas)
        queue_free()

Para evitar exceder máximo:

const VIDA_MAX = 5

func _on_body_entered(body):
    if body.vidas < VIDA_MAX:
        body.vidas += 1
        body.vida_mudou.emit(body.vidas)
        queue_free()

Exercício 5 · Áudio (efeitos sonoros)

Adiciona ao player: - Som de pulo (AudioStreamPlayer). - Som de morte.

Toca os sons nos momentos certos.

Resposta:

Estrutura:

Player (CharacterBody2D)
├── AnimatedSprite2D
├── CollisionShape2D
├── PuloSfx (AudioStreamPlayer)   # stream = pulo.ogg
└── MorteSfx (AudioStreamPlayer)  # stream = morte.ogg

player.gd:

@onready var pulo_sfx = $PuloSfx
@onready var morte_sfx = $MorteSfx

func _physics_process(delta):
    # ...
    if Input.is_action_just_pressed("ui_accept") and is_on_floor():
        velocity.y = JUMP_VELOCITY
        pulo_sfx.play()
    # ...

func sofrer_dano(d = 1):
    vidas -= d
    if vidas <= 0:
        morte_sfx.play()
        # ...

Sons gratuitos: freesound.org.

Exercício 6 · Música de fundo com loop

Adiciona AudioStreamPlayer com música .ogg em loop, que toca desde o início e baixa volume com tecla M.

Resposta:
  1. Importar musica.ogg para o projecto.
  2. Selecionar o ficheiro → Import tab → ✓ Loop, depois Reimport.
  3. Adicionar AudioStreamPlayer à cena, arrastar o .ogg para Stream.
  4. ✓ Autoplay.

world.gd:

extends Node2D

@onready var musica = $Musica

var muted = false

func _unhandled_input(event):
    if event.is_action_pressed("mute"):  # tecla M no Input Map
        muted = !muted
        musica.volume_db = -80 if muted else 0

Adicionar action "mute" no Input Map → tecla M.

Exercício 7 · Save / Load com JSON

Cria save com score, vidas e nível actual. Botão "Save" guarda, botão "Load" carrega.

Resposta:

game.gd (autoload):

extends Node

const SAVE_PATH = "user://savegame.json"

var score = 0
var vidas = 3
var nivel = 1

func save_game():
    var data = {
        "score": score,
        "vidas": vidas,
        "nivel": nivel
    }
    var file = FileAccess.open(SAVE_PATH, FileAccess.WRITE)
    file.store_string(JSON.stringify(data))
    file.close()
    print("Guardado!")

func load_game():
    if not FileAccess.file_exists(SAVE_PATH):
        print("Sem save")
        return
    var file = FileAccess.open(SAVE_PATH, FileAccess.READ)
    var data = JSON.parse_string(file.get_as_text())
    file.close()
    if data:
        score = data["score"]
        vidas = data["vidas"]
        nivel = data["nivel"]
        print("Carregado: score=", score, " vidas=", vidas)

UI:

$SaveBtn.pressed.connect(Game.save_game)
$LoadBtn.pressed.connect(Game.load_game)

Exercício 8 · Export para Web (HTML5)

Faz export do projecto para HTML5 e demonstra a correr no browser (pode ser localmente com python3 -m http.server).

Resposta:
  1. Editor → Manage Export Templates → Download (uma vez).

  2. Project → Export → Add… → Web.

  3. Export Path: build/web/index.html.

  4. Click "Export Project" (sem Debug).

  5. Servir localmente (browsers não correm file:// para WASM): bash cd build/web python3 -m http.server 8000

  6. Abrir http://localhost:8000 — o jogo corre no browser!

  7. Para publicar:

  8. itch.io: zip de build/web/, upload como "HTML" project.
  9. GitHub Pages: commit a build/web/ numa branch gh-pages.
  10. Vercel/Netlify: drag-and-drop da pasta.

Limitações HTML5: - Sem alguns formatos de áudio. - Performance pior que nativo. - Tamanho download (~10MB+ comum).

Mas distribuir é trivial e funciona em qualquer dispositivo com browser.