Pular para o conteúdo principal

Requiem of Blessings — Guia Unity

12. Passo a Passo de Implementação (Unity + PixelLab + Play Store)​

Roteiro prático, em ordem, para sair do zero até uma versão publicável na Play Store. Cada fase pressupõe que a anterior está funcional (nem perfeita — funcional).

Fase 0 — Setup do projeto​

  1. Criar projeto Unity 2D (URP) — o URP facilita efeitos de luz/pós-processamento leves (ex: brilho do escudo, aura do monstro guardião) sem pesar no mobile.
  2. Configurar Player Settings desde já pensando em Android: Package Name definitivo (ex: com.suaempresa.requiemofblessings — não muda depois sem virar um app novo na Play Store), Minimum API Level (checar o mínimo atual exigido pela Play Store — validar na Play Console, isso muda com frequência), orientação de tela (retrato, provavelmente, para um RPG mobile de menu).
  3. Criar a estrutura de pastas do projeto (_Project/Scripts, _Project/Art, _Project/Audio, _Project/Prefabs, _Project/ScriptableObjects, etc.) — convenção clara desde o início evita bagunça quando os assets do PixelLab começarem a entrar.
  4. Instalar pacotes: Input System (para os botões dedicados de Esquiva/Parry com timing preciso), TextMeshPro, e deixar reservado o pacote do Firebase Unity SDK (Auth + Firestore) para a fase de save.

Fase 1 — Protótipo cinza (sem arte final)​

Antes de gerar qualquer asset no PixelLab, valide as mecânicas com placeholders (quadrados coloridos, texto):

  1. Implementar o Battle State Machine (seção 9.1): PlayerTurn → EnemyTurn → CheckBattleEnd.
  2. Implementar Esquiva/Parry com janelas de tempo (usar Time.time ou uma coroutine com janela configurável por ScriptableObject de inimigo).
  3. Implementar o Sistema de Escudos (contador por inimigo, resolução de hits excedentes).
  4. Implementar Benção Divina (contador 1→5) e 1 habilidade especial de teste (a barra de precisão, mais simples).
  5. Implementar Jogar Pedra e os 2 itens (Elixir de HP/Bênção).
  6. Validar o "game feel" — isso é o mais importante da fase e o que mais vale iterar antes de gastar em arte.

Fase 2 — Pipeline de assets com PixelLab​

Com o combate validado, comece a gerar os assets:

  1. Herói: usar o gerador de personagem do PixelLab com 4 ou 8 direções, base size recomendada (32x32 ou 64x64, dependendo do nível de detalhe desejado) e fundo transparente.
  2. Animações: usar animação por esqueleto (skeleton-based) do PixelLab para gerar ataque normal, esquiva, parry, golpes especiais e idle — ele gera sprite sheets prontos.
  3. Inimigos: repetir o processo por tipo de monstro (comum e boss), incluindo uma variação visual para o "monstro guardião de Almas" (brilho/aura, conforme decidido na seção 5.1).
  4. Ambientes/masmorras: usar a geração de cenas e tilesets do PixelLab para os fundos de batalha e do Acampamento.
  5. Ícones: usar o preset de ícones de habilidade para os 4 tipos de interação especial, gemas, itens (Elixir de HP/Bênção) e recursos (Almas, Conhecimento das Almas, Tocha).
  6. Importação no Unity: para cada sprite sheet gerado, configurar no import settings: Filter Mode = Point (no filter), Compression = None (ou baixa, testar qualidade), Pixels Per Unit consistente entre todos os assets do jogo (definir um valor único no início, ex: 16 ou 32, e manter em todos os sprites).
  7. Manter uma pasta/planilha de créditos separada para os assets do PixelLab e para os efeitos sonoros de biblioteca gratuita (seção 8) — útil tanto para organização quanto para eventual exigência de atribuição na publicação.

Fase 3 — Sistemas de Batalha completos​

  1. Multi-Enemy Targeting + telégrafos sequenciais (seção 3.1).
  2. Sistema de Habilidades Especiais completo: os 4 tipos de interação (barra de precisão, sequência de gestos, hold/release, ritmo) via a interface comum ISpecialAttackInteraction.
  3. Categorias de dano (Elevado, Área, Aleatório) e a lógica de múltiplos hits contra o sistema de escudo.
  4. Loadout de 3 habilidades (mesmo que a árvore ainda não exista — pode usar uma lista fixa de habilidades de teste no início).
  5. Bosses com 2 fases (seção 3.6) — recomenda-se implementar isso só depois que a luta de 1 fase estiver sólida; é essencialmente trocar o "conjunto de dados" de ataque do boss no meio da luta.
  6. Sistema de Quebra (seção 3.7) — contador de hits + limiar, incapacita a entidade por uma rodada ao encher.
  7. Sistema de Recuperação (seção 3.8) — checagem de dano/hits pós-ação que pula a próxima ação de quem atacou forte demais.
  8. Sistema de Queima (seção 3.9) — contador de ícones de fogo, resolvido no início de cada turno, independente dos sistemas de Escudo e Quebra.

Sugestão de ordem: implemente Escudo primeiro (já é essencial pro combate básico), depois Quebra, depois Queima, e Recuperação por último — Recuperação é o que mais interage com o "sentimento" de ritmo do combate, então é mais fácil ajustar depois que as outras peças já estiverem no lugar e você conseguir sentir o jogo rodando.

Fase 4 — Sistemas do Acampamento​

  1. Progressão de atributos (Força, Vitalidade, Conhecimento) com as fórmulas da seção 4.
  2. Árvore de Conhecimento das Almas (estrutura de nós com bifurcações — um grafo simples de ScriptableObjects costuma resolver bem em Unity).
  3. Forja de Gemas (sorteio aleatório de um pool, custo crescente).
  4. Sistema de Tochas — importante: calcular a recarga baseado em timestamp real (salvo localmente e idealmente validado contra um servidor/Firebase), nunca em um timer que reseta ao fechar o app, e nunca confiando cegamente no relógio do dispositivo (jogadores podem adiantar o relógio pra "trapacear" a recarga — mitigar comparando com hora do servidor quando possível).
  5. Missões Diárias — mesma lógica de timestamp de servidor pro reset diário; o tracking de progresso (bosses derrotados, parries, dano recebido etc.) precisa "escutar" eventos já existentes do sistema de batalha, então faz sentido implementar isso depois que os eventos de combate (fim de batalha, parry bem-sucedido, dano recebido) já existirem de forma consistente.

Fase 5 — Monetização (anúncios)​

  1. Integrar Google AdMob (ou Unity Ads/mediação) para os dois pontos de anúncio recompensado: ganhar Tocha extra e "Visão Divina" (triplicar recompensa).
  2. Implementar o Google UMP (User Messaging Platform) para consentimento de anúncios personalizados — obrigatório para contas AdMob que atendem usuários no EEA/Reino Unido/Suíça, e boa prática global.
  3. Testar com IDs de anúncio de teste do AdMob durante todo o desenvolvimento — só trocar para os IDs reais de produção no build final, para evitar banimento da conta por clique acidental em ads reais durante testes.

Fase 6 — Save System​

  1. Configurar projeto no Firebase Console, adicionar o app Android, baixar google-services.json.
  2. Implementar Firebase Auth (anônimo é suficiente para começar — permite depois migrar pra login de verdade sem perder progresso).
  3. Implementar Firestore (ou Realtime Database) para os dados de progressão (atributos, árvore, gemas, tochas, masmorra atual).
  4. Manter uma cópia local (PlayerPrefs ou arquivo JSON) como cache/fallback offline, sincronizando com a nuvem quando houver conexão.

Fase 7 — Polimento​

  1. Feedback de áudio para parry/esquiva (crítico para o "feel" do timing — mesmo com efeitos de biblioteca gratuita, a sincronia importa mais que a qualidade do som em si).
  2. Efeitos visuais simples (screen shake leve, flash de hit, partículas de quebra de escudo) — ajudam demais a leitura do combate num RPG ativo.
  3. Tutorial básico explicando esquiva/parry/pedra antes da primeira masmorra.

Fase 8 — Checklist de publicação na Play Store​

  1. Gerar build assinado (AAB) com uma keystore própria — guardar essa chave com segurança, ela é necessária pra toda atualização futura do jogo.
  2. Preencher o Data Safety form da Play Console — como o jogo usa Firebase (dados salvos) e anúncios (AdMob coleta dados para anúncios), isso precisa ser declarado corretamente.
  3. Preencher a classificação de conteúdo (IARC) — o tema é dark fantasy com violência estilizada (pixel art), então é importante responder ao questionário com atenção a esse ponto para a faixa etária correta.
  4. Escrever a política de privacidade (obrigatória por causa dos anúncios e do Firebase) e publicá-la em uma URL acessível.
  5. Preparar screenshots, ícone e texto da loja — os assets gerados pelo PixelLab servem bem de base pra artes promocionais também.
  6. Publicar primeiro em teste interno/fechado na Play Console antes de ir para produção — permite pegar crashes e problemas de anúncio/save antes do público geral.
  7. Validar em dispositivos reais de gama baixa/média (comum no público Android) — pixel art costuma rodar bem, mas o sistema de anúncios e save em nuvem merecem teste em conexão instável.

Confirmado: lançamento inicial só para Android. iOS pode ser considerado depois, caso o jogo valide bem — mas isso fica fora do escopo do checklist acima (a App Store tem processo de revisão e exigências próprias, como App Tracking Transparency para anúncios).


13. Guia Detalhado de Tarefas — Começando do Zero (Fase 0 e Fase 1)​

Esta seção expande as Fases 0 e 1 da seção 12 em tarefas bem pequenas, cada uma com objetivo, explicação do conceito de Unity envolvido, e passo a passo. A ideia é você conseguir seguir isso mesmo estudando Unity há pouco tempo — cada tarefa ensina um conceito enquanto entrega uma peça pequena e testável do jogo.

Como usar este guia: faça uma tarefa por vez, teste (aperte o botão Play), veja funcionando, e só então vá pra próxima. Resistir à tentação de pular etapas é a parte mais importante de aprender Unity sem se perder.


Bloco A — Setup do projeto​

Tarefa 1: Criar o projeto

  • Objetivo: ter um projeto Unity 2D configurado e rodando.
  • Passo a passo: Abra o Unity Hub → New Project → template 2D (URP) → nomeie o projeto (ex: RequiemOfBlessings) → Create.
  • Conceito: URP (Universal Render Pipeline) é um dos "motores gráficos" do Unity, mais leve e adequado para mobile. Você não precisa entender profundamente agora — só saiba que escolher isso desde o início evita ter que migrar depois.

Tarefa 2: Organizar as pastas

  • Objetivo: ter uma estrutura de pastas limpa dentro de Assets.
  • Passo a passo: Na janela Project, clique com o botão direito em Assets → Create → Folder. Crie: Scripts, Art, Audio, Prefabs, ScriptableObjects, Scenes.
  • Conceito: No Unity, tudo que você usa (scripts, imagens, sons, cenas) é um "Asset" e vive dentro da pasta Assets. Organizar cedo evita uma bagunça gigante depois que você tiver 200+ arquivos.

Tarefa 3: Criar a primeira cena

  • Objetivo: ter uma cena chamada Battle (onde vamos prototipar o combate).
  • Passo a passo: Dentro de Scenes, botão direito → Create → Scene → nomeie Battle. Dê 2 cliques para abrir.
  • Conceito: uma "Scene" no Unity é como uma fase/tela do jogo — cada tela do seu jogo (Batalha, Acampamento) vai virar uma Scene separada (ou você pode carregar UI por cima da mesma cena — isso é uma decisão que pode vir mais tarde).

Tarefa 4: Instalar o pacote Input System

  • Objetivo: ter o sistema de input moderno do Unity disponível (importante pra timing preciso de parry/esquiva).
  • Passo a passo: Window → Package Manager → mude o dropdown pra "Unity Registry" → procure "Input System" → Install. O Unity vai perguntar se quer trocar o backend de input — aceite.
  • Conceito: existem 2 formas de capturar toques/cliques no Unity: o sistema antigo (Input.GetKeyDown) e o Input System novo, baseado em "Actions". Vamos usar o novo porque ele lida melhor com toques de tela (mobile) e é mais fácil de configurar depois pra Esquiva/Parry.

Bloco B — Estruturas de dados básicas (ScriptableObjects)​

Tarefa 5: Entender e criar seu primeiro ScriptableObject

  • Objetivo: ter um arquivo EnemyData que guarda os dados de um inimigo (nome, HP, dano) sem precisar de código repetido.
  • Conceito (importante, leia com calma): um ScriptableObject é tipo uma "ficha de personagem" que existe como arquivo no seu projeto, não como algo que só existe durante o jogo rodando. Diferente de uma classe comum, você cria um EnemyData para o Goblin, outro pra o Esqueleto, etc., cada um com seus próprios números — sem duplicar código, só duplicando dados. É a ferramenta perfeita pra tudo que você já documentou no GDD com "dados por item" (inimigos, habilidades, gemas, itens).
  • Passo a passo:
    1. Na pasta Scripts, crie um script chamado EnemyData.cs.
    2. Escreva:
      using UnityEngine;

      // POR QUE: o jogo vai ter dezenas de inimigos (42 masmorras) que só diferem nos
      // NÚMEROS (nome, HP, dano, escudos). Sem esta classe, você teria que escrever um
      // script por inimigo ou digitar os valores em cada objeto da cena, um por um.
      // Quem usa: o EnemyController (Tarefa 8) lê esta "ficha" para saber com quanto
      // HP o inimigo começa.
      // ESTRATÉGIA: é um ScriptableObject, não um MonoBehaviour. ScriptableObject é um
      // script que vira um ARQUIVO (.asset) na pasta do projeto, em vez de viver preso
      // a um objeto da cena. Assim a ficha "Goblin" existe uma vez só e qualquer cena
      // pode apontar pra ela. Esta classe só GUARDA dados — não tem lógica nenhuma
      // (quem age é o EnemyController). Separar "dados" de "comportamento" é o padrão
      // que você vai repetir em AbilityData, GemData, ItemData etc.
      // [CreateAssetMenu] é um "atributo" (a coisa entre colchetes) que adiciona uma
      // entrada no menu Create do Unity (botão direito na Project window), para você
      // criar novos arquivos desta ficha sem código. fileName = nome padrão do arquivo
      // novo; menuName = caminho do item no menu.
      [CreateAssetMenu(fileName = "NewEnemy", menuName = "RequiemOfBlessings/Enemy Data")]
      public class EnemyData : ScriptableObject
      {
      // Campos "public" aparecem no Inspector, onde você preenche os valores de
      // cada inimigo sem abrir o código.
      public string enemyName; // nome usado nos logs e, depois, na UI
      public int maxHP; // HP com que o inimigo começa cada batalha
      public int attackDamage; // dano base de um golpe do inimigo
      public int maxShields; // limite de escudo deste inimigo (até 10, conforme o GDD)
      }
    3. Volte pro Unity, clique com botão direito na pasta ScriptableObjects → Create → RequiemOfBlessings → Enemy Data. Isso cria um arquivo NewEnemy.asset.
    4. Clique nesse arquivo e preencha os campos no Inspector (nome, HP, dano) — por exemplo, um "Goblin" com 30 HP e 5 de dano.
  • Por que isso importa: esse padrão (ScriptableObject + CreateAssetMenu) é a base de praticamente todo sistema de dados do seu jogo — você vai repetir esse mesmo padrão pra AbilityData, GemData, ItemData, etc.

Tarefa 6: Criar HeroData

  • Objetivo: o mesmo conceito da Tarefa 5, mas para o herói (HP base, Ataque base, Defesa base — os valores "Base" das fórmulas da seção 4 do GDD).
  • Passo a passo: repita o processo da Tarefa 5, criando HeroData.cs com campos como baseHP, baseAttack, baseDefense.

Bloco C — O primeiro inimigo em cena (sem arte, com um quadrado)​

Tarefa 7: Criar um "boneco de teste" pro inimigo

  • Objetivo: ter um quadrado vermelho na tela representando o inimigo.
  • Passo a passo: Na cena Battle, botão direito na Hierarchy → 2D Object → Sprite → Square. Renomeie pra Enemy_Test. No Inspector, mude a cor (componente Sprite Renderer → Color) pra vermelho.
  • Conceito: um GameObject é qualquer "coisa" que existe na sua cena (o quadrado, a câmera, um botão de UI). Ele ganha comportamento através de Components grudados nele (o Sprite Renderer é um component que faz ele aparecer visualmente).

Tarefa 8: Criar o script EnemyController

  • Objetivo: o Enemy_Test ter HP, escudo, e conseguir "tomar dano".
  • Passo a passo:
    1. Crie EnemyController.cs em Scripts.
    2. Escreva:
      using UnityEngine;

      // POR QUE: o EnemyData (Tarefa 5) é só a ficha, e uma ficha não muda durante a
      // luta. Alguém precisa guardar o HP e os escudos ATUAIS deste inimigo específico
      // (dois Goblins na mesma luta usam a mesma ficha, mas cada um tem seu próprio HP).
      // Quem usa: por enquanto o TestBattleTrigger (Tarefa 9); depois, o BattleManager.
      // ESTRATÉGIA: é um MonoBehaviour, ou seja, um "componente" preso a um GameObject
      // da cena (o quadrado vermelho). Ele lê os valores iniciais da ficha (data) e
      // guarda o estado que muda (currentHP, currentShields) em campos privados. Quem
      // quiser causar dano não mexe no HP direto: chama ReceiveHits, que é o único
      // lugar onde fica a regra do escudo (seção 3.3 do GDD). Com a regra num lugar
      // só, não tem como alguém "esquecer" o escudo.
      public class EnemyController : MonoBehaviour
      {
      public EnemyData data; // arraste o EnemyData no Inspector
      // "private" = só este script enxerga e muda. Evita que outro script altere
      // o HP por fora da regra do escudo.
      private int currentHP; // HP atual nesta batalha (começa em data.maxHP)
      private int currentShields; // escudos ativos agora (0 até data.maxShields)

      // Start() é chamado automaticamente pelo Unity UMA vez, no primeiro frame em
      // que o objeto está ativo, antes do primeiro Update(). É o lugar certo para
      // preparar valores iniciais. (Existe também o Awake(), que roda ainda antes,
      // assim que o objeto é criado; a regra prática é: Awake para configurar a si
      // mesmo, Start quando você depende de outros objetos já estarem prontos.)
      // Aqui: copia o HP máximo da ficha e começa sem escudo.
      void Start()
      {
      currentHP = data.maxHP;
      currentShields = 0;
      }

      // Chamado quando o jogador acerta N hits nesse inimigo.
      // POR QUE recebe "quantidade de hits" e não "dano total": pelo GDD cada hit
      // remove 1 escudo, então precisamos processar golpe por golpe (o loop for).
      public void ReceiveHits(int hitCount, int damagePerHit)
      {
      // Correção de revisão: se o inimigo já morreu, ignora novos hits (antes
      // o log "foi derrotado!" se repetia a cada ataque em um inimigo morto).
      if (currentHP <= 0) return;

      for (int i = 0; i < hitCount; i++)
      {
      if (currentShields > 0)
      {
      currentShields--; // 1 hit remove 1 escudo (seção 3.3 do GDD)
      }
      else
      {
      currentHP -= damagePerHit;
      // Debug.Log escreve uma mensagem na janela Console do Unity; é
      // a forma mais simples de "ver" o que o código está fazendo.
      // O $"..." permite colocar variáveis dentro do texto com {}.
      Debug.Log($"{data.enemyName} tomou {damagePerHit} de dano. HP restante: {currentHP}");
      }
      }

      if (currentHP <= 0)
      {
      Debug.Log($"{data.enemyName} foi derrotado!");
      }
      }
      }
    3. Arraste esse script pro Enemy_Test (arraste o arquivo .cs de dentro da pasta Scripts pra cima do objeto na Hierarchy).
    4. No Inspector do Enemy_Test, arraste o NewEnemy.asset (da Tarefa 5) pro campo Data.
  • Conceito: um MonoBehaviour é a classe base de todo script que vive "dentro" de um GameObject na cena. Start() roda uma vez quando o objeto é criado; é onde você inicializa valores. Esse script já implementa, em código de verdade, a regra de escudo que vocês definiram no GDD (seção 3.3): hit remove escudo primeiro, dano só depois.

Tarefa 9: Testar via um botão temporário

  • Objetivo: conseguir clicar em algo e ver o inimigo tomando dano no Console, validando que a lógica funciona antes de ter qualquer UI real.
  • Passo a passo:
    1. Crie um script TestBattleTrigger.cs:
      using UnityEngine;
      using UnityEngine.InputSystem; // Input System novo (Tarefa 4)

      // POR QUE: ainda não existe UI nem botão de ataque, mas precisamos provar que a
      // regra de dano/escudo do EnemyController funciona. Este script é um "gatilho de
      // teste": aperte Espaço, ele chama ReceiveHits, e você confere no Console.
      // Quem usa: ninguém — ele é temporário e pode ser apagado quando a UI existir.
      // ESTRATÉGIA: MonoBehaviour num objeto vazio (TestManager) que guarda uma
      // referência ao inimigo, preenchida arrastando no Inspector. Ele não sabe
      // NADA da regra de escudo; só pede "leve 2 hits de 10". Assim, quando o botão de
      // verdade chegar, ele faz a mesma chamada e nada no inimigo muda.
      public class TestBattleTrigger : MonoBehaviour
      {
      // Referência a OUTRO componente da cena. Você liga os dois arrastando o
      // Enemy_Test para este campo no Inspector (Tarefa 9, passo 3).
      public EnemyController targetEnemy;

      // Update() é chamado pelo Unity UMA vez por frame (dezenas de vezes por
      // segundo). É onde se verifica input, porque "apertou a tecla" só é
      // verdade no frame exato em que aconteceu.
      void Update()
      {
      // Correção de revisão: antes usava Input.GetKeyDown(KeyCode.Space), que é
      // do sistema de input ANTIGO. Na Tarefa 4 você trocou o backend para o
      // Input System novo, e aí o Input antigo gera erro no Console. A forma
      // nova: Keyboard.current é o teclado conectado (null se não houver) e
      // wasPressedThisFrame é true só no frame em que a tecla foi apertada.
      if (Keyboard.current != null && Keyboard.current.spaceKey.wasPressedThisFrame)
      {
      // Argumentos nomeados (hitCount:, damagePerHit:) só deixam a
      // chamada mais legível; o efeito é o mesmo sem os nomes.
      targetEnemy.ReceiveHits(hitCount: 2, damagePerHit: 10);
      }
      }
      }
    2. Crie um GameObject vazio (botão direito na Hierarchy → Create Empty), chame de TestManager, e arraste esse script nele.
    3. Arraste o Enemy_Test pro campo Target Enemy no Inspector do TestManager.
    4. Aperte Play e pressione a barra de espaço — observe o Console (Window → General → Console) mostrando o inimigo tomando dano.
  • Por que isso importa: esse é seu primeiro loop de teste completo: dado → lógica → resultado observável. É assim que se prototipa qualquer sistema no Unity antes de gastar tempo com UI e arte. Depois trocamos "apertar espaço" por um botão de ataque de verdade.

Bloco D — Estrutura de turno (o "esqueleto" da batalha)​

Tarefa 10: Criar o enum de estados de batalha

  • Objetivo: ter uma forma clara de saber "de quem é a vez" no combate.
  • Passo a passo: crie BattleState.cs:
    // POR QUE: a batalha tem momentos em que só certas ações valem (ex.: jogar pedra só
    // no pré-ataque). Precisamos de um jeito de saber "em que momento estamos".
    // Quem usa: o BattleManager (Tarefa 11), que guarda o estado atual.
    // ESTRATÉGIA: um enum em arquivo próprio, sem "using UnityEngine" porque não usa
    // nada do Unity — é C# puro. Cada valor é uma fase do turno da seção 3.1 do GDD.
    // Usar nomes em vez de números (0, 1, 2) evita bugs do tipo "o 2 era o quê mesmo?".
    public enum BattleState
    {
    PlayerPreAttack, // jogador pode jogar pedra e/ou usar item
    PlayerAttack, // ação final do turno do jogador
    EnemyTurn, // inimigo ataca ou invoca escudo
    BattleOver
    }
  • Conceito: um enum é uma lista fechada de opções nomeadas (em vez de usar números soltos tipo 0, 1, 2 pra representar estados, você usa nomes que fazem sentido). Isso deixa o código muito mais fácil de ler.

Tarefa 11: Criar o BattleManager

  • Objetivo: ter um "maestro" controlando a ordem do turno, seguindo exatamente a estrutura da seção 3.1 do GDD.
  • Passo a passo: crie BattleManager.cs:
    using UnityEngine;

    // POR QUE: cada sistema (escudo, quebra, queima, recuperação) sabe fazer a SUA parte,
    // mas alguém precisa decidir a ORDEM: quem age agora, o que acontece no início do
    // turno, quando passar a vez. Sem um "maestro", cada script tentaria controlar o
    // turno sozinho e a ordem viraria bagunça.
    // Quem usa: por enquanto o teste da barra de espaço; depois, os botões da UI
    // (Bloco G) chamam os métodos públicos daqui.
    // ESTRATÉGIA: é uma máquina de estados. Um MonoBehaviour (no TestManager) guarda o
    // BattleState atual, e cada método público primeiro confere se a ação é permitida
    // no estado atual e só então avança para o próximo estado. Esta versão é só o
    // esqueleto: ela passa pelos estados em sequência, só com logs, sem lógica real
    // de inimigo. Nas Tarefas 12-14 ela ganha as chamadas de Quebra/Queima/Recuperação.
    public class BattleManager : MonoBehaviour
    {
    // Em que fase do turno estamos. É public para você acompanhar no Inspector
    // durante o Play (útil para depurar).
    public BattleState currentState;

    // Start (ver EnemyController): a batalha começa no pré-ataque do jogador.
    void Start()
    {
    currentState = BattleState.PlayerPreAttack;
    Debug.Log("Turno do jogador começou. Pode jogar pedra ou usar item.");
    }

    // Chamado quando o jogador decide atacar (ação final do turno).
    // POR QUE existe: é a única porta de entrada para "encerrar o turno do jogador".
    // A primeira linha é uma "guarda": se não for o momento certo (ex.: turno do
    // inimigo), ignora o clique. É assim que a máquina de estados impede ações
    // fora de hora.
    public void PlayerAttacks()
    {
    if (currentState != BattleState.PlayerPreAttack) return;

    currentState = BattleState.PlayerAttack;
    Debug.Log("Jogador atacou. Turno encerrado, passando pro inimigo.");

    currentState = BattleState.EnemyTurn;
    Debug.Log("Turno do inimigo.");

    // (por enquanto, sem lógica de inimigo real — só de volta pro jogador)
    currentState = BattleState.PlayerPreAttack;
    Debug.Log("Novo turno do jogador.");
    }
    }
    Adicione esse script no TestManager também, e ligue o botão de espaço (Tarefa 9) pra chamar PlayerAttacks() em vez de ReceiveHits diretamente — depois PlayerAttacks() que vai chamar ReceiveHits no inimigo certo.
  • Conceito: isso é uma máquina de estados (state machine) simplificada — o conceito mais importante de todo o sistema de batalha do GDD. Toda a complexidade de "jogador pode fazer isso, mas só nesse momento" nasce de controlar bem esse currentState.

Bloco E — Sistemas de Status: Quebra, Recuperação e Queima​

Esses três sistemas (seções 3.7, 3.8 e 3.9 do GDD) são todos parecidos em estrutura: um contador que enche por algum evento e, ao atingir um limiar, dispara um efeito. Vamos implementar os três reaproveitando o mesmo tipo de raciocínio — depois de fazer o primeiro, os outros dois ficam bem mais rápidos de entender.

Tarefa 12: Criar o BreakCounter (Sistema de Quebra)

  • Objetivo: o inimigo (e depois o herói) ter um contador de hits que, ao atingir um limiar, o deixa "quebrado" por uma rodada.
  • Passo a passo:
    1. Crie BreakCounter.cs:
      using UnityEngine;

      // POR QUE: o Sistema de Quebra (seção 3.7) vale para herói E monstros. Se a
      // contagem ficasse dentro do EnemyController, teríamos que reescrevê-la no
      // herói. Num componente separado, basta adicioná-lo em quem precisa quebrar.
      // Quem usa: o EnemyController chama RegisterHit ao receber um hit que conta;
      // o BattleManager lê IsBroken para pular o turno e chama ResolveBrokenState.
      // ESTRATÉGIA: MonoBehaviour pequeno, com um só trabalho: contador + limiar. Ele
      // NÃO pula turno sozinho; só "levanta a bandeira" IsBroken. Quem decide pular o
      // turno é o BattleManager (o maestro). Isso mantém a regra de turno num lugar
      // só. Ele também não sabe QUAIS hits contam (no monstro, só Ataque Especial de
      // Dano Elevado): quem filtra é quem chama (passo 4 abaixo).
      public class BreakCounter : MonoBehaviour
      {
      // Hits necessários para quebrar. Começa em 3; no jogo final vem da Força
      // (herói) ou do número da masmorra (monstro), seção 3.7.
      public int breakThreshold = 3; // limiar inicial (seção 3.7 do GDD)
      private int currentHits = 0; // hits acumulados desde a última quebra
      // Propriedade com "get" público e "private set": qualquer script pode LER
      // se está quebrado, mas só este script pode MUDAR. É uma forma de proteger
      // o estado, parecida com o campo private do EnemyController.
      public bool IsBroken { get; private set; }

      // Registra 1 hit que conta para a quebra e dispara a quebra ao atingir o
      // limiar. Chamado a cada hit válido (não recebe um total, porque o GDD
      // conta hits, não dano).
      public void RegisterHit()
      {
      if (IsBroken) return; // já quebrado, não acumula mais até resolver

      currentHits++;
      Debug.Log($"Hits acumulados: {currentHits}/{breakThreshold}");

      if (currentHits >= breakThreshold)
      {
      TriggerBreak();
      }
      }

      // Aplica a quebra. É private e separado de RegisterHit só para organização:
      // quando entrar o feedback visual (barra, animação, +50% de dano no monstro
      // vulnerável), é aqui que ele vai, sem poluir a contagem.
      private void TriggerBreak()
      {
      IsBroken = true;
      currentHits = 0; // reseta pra poder quebrar de novo depois (seção 3.7)
      // gameObject.name = nome do objeto na Hierarchy ao qual este script
      // está preso (ex.: "Enemy_Test"). Todo MonoBehaviour tem acesso a ele.
      Debug.Log($"{gameObject.name} QUEBROU! Perde a próxima rodada.");
      }

      // Chamado pelo BattleManager depois que a rodada "perdida" passou.
      // POR QUE não se desliga sozinho: só o BattleManager sabe quando a rodada
      // realmente passou.
      public void ResolveBrokenState()
      {
      IsBroken = false;
      }
      }
    2. Adicione esse script no Enemy_Test (Tarefa 7-8).
    3. No EnemyController (Tarefa 8), adicione uma referência: public BreakCounter breakCounter; e arraste o component no Inspector.
    4. Dentro de ReceiveHits, adicione uma chamada: dentro do loop for, sempre que um hit causar dano de verdade (não quando remove escudo), chame breakCounter.RegisterHit(). Atenção: pelo GDD, só hits de Ataque Especial de Dano Elevado contam pra quebra do monstro (Área/Aleatório, ataque normal e pedra não contam — seção 3.7) — então essa chamada só deve acontecer quando o método ReceiveHits for invocado a partir de uma habilidade especial de Dano Elevado. Uma forma simples de resolver isso: adicione um parâmetro bool countsForBreak no método ReceiveHits, e só chame RegisterHit() quando ele for true.
  • Conceito: isso é o padrão de contador + limiar que você vai repetir nas próximas duas tarefas — só muda o que incrementa o contador e o que acontece quando ele estoura.

Tarefa 13: Criar o BurnStatus (Sistema de Queima)

  • Objetivo: herói e monstro acumularem ícones de fogo que causam dano no início do turno.
  • Passo a passo:
    1. Crie BurnStatus.cs:
      using UnityEngine;

      // POR QUE: a Queima (seção 3.9) é mais um status que herói E monstros podem ter,
      // então segue a mesma ideia do BreakCounter: um componente reaproveitável.
      // Quem usa: quem aplica o golpe de fogo chama AddBurnIcon; o BattleManager chama
      // ResolveBurnDamage no início do turno de quem tem o componente.
      // ESTRATÉGIA: mesmo padrão "contador + gatilho", com uma diferença importante:
      // ResolveBurnDamage só CALCULA e DEVOLVE o dano, não tira HP de ninguém. Quem
      // aplica é o BattleManager, direto no HP e SEM passar pela regra de escudo
      // (pelo GDD, Queima ignora escudo e não conta para a Quebra). Por isso este
      // script não conhece o EnemyController, o escudo nem o BreakCounter.
      public class BurnStatus : MonoBehaviour
      {
      private int burnIcons = 0; // ícones acumulados, sem teto (seção 3.9)
      public float damagePercentPerIcon = 0.03f; // 3% do HP máximo por ícone (seção 3.9)

      // Soma 1 ícone. Chame só quando o golpe de fogo ACERTOU: se houve esquiva ou
      // parry com sucesso, não se ganha ícone (seção 3.9).
      public void AddBurnIcon()
      {
      burnIcons++;
      Debug.Log($"Ícones de queima acumulados: {burnIcons}");
      }

      // Chamado no início do turno de quem tem esse componente.
      // Recebe o HP máximo como parâmetro (em vez de buscar sozinho) para que o
      // mesmo script sirva para herói e monstro, cada um passando o seu HP.
      // Devolve o dano (int) e zera os ícones; aplicar o dano fica com quem chamou.
      public int ResolveBurnDamage(int maxHP)
      {
      if (burnIcons == 0) return 0;

      // Mathf.RoundToInt arredonda o float para o inteiro mais próximo
      // (HP é int). Ex.: 4 ícones * 0.03 * 100 de HP = 12.
      int burnDamage = Mathf.RoundToInt(burnIcons * damagePercentPerIcon * maxHP);
      Debug.Log($"Queima causou {burnDamage} de dano ({burnIcons} ícones consumidos).");
      burnIcons = 0; // consome tudo de uma vez (seção 3.9)
      return burnDamage;
      }
      }
    2. Adicione esse script tanto no Enemy_Test quanto em um futuro objeto do herói.
    3. No BattleManager (Tarefa 11), no início do estado PlayerPreAttack, adicione a checagem: busque o BurnStatus do herói e chame ResolveBurnDamage, aplicando o resultado como dano antes de qualquer outra ação.
  • Conceito: repare que esse sistema não interage com escudo nem quebra — ele é resolvido de forma completamente independente, então nem precisa "conversar" com o BreakCounter ou com a lógica de escudo do EnemyController.

Tarefa 14: Criar o RecoveryStatus (Sistema de Recuperação)

  • Objetivo: depois de um ataque forte (dano alto ou muitos hits), quem atacou perde a próxima ação.
  • Passo a passo:
    1. Crie RecoveryStatus.cs:
      using UnityEngine;

      // POR QUE: a Recuperação (seção 3.8) pune QUEM ATACOU forte (ao contrário da
      // Quebra, que pune quem apanhou). Vale para herói e monstro, então é mais um
      // componente reaproveitável.
      // Quem usa: o BattleManager, que chama CheckRecoveryTrigger depois de cada
      // ataque, confere IsRecovering antes da próxima ação e chama ClearRecovery.
      // ESTRATÉGIA: mesmo formato de "bandeira" do BreakCounter (ver BreakCounter), mas
      // em script SEPARADO de propósito: o GDD diz que Quebra e Recuperação são
      // independentes. Com arquivos separados, mudar uma regra não quebra a outra.
      // Os limiares são campos públicos para você ajustar no Inspector durante o
      // playtest, sem recompilar.
      public class RecoveryStatus : MonoBehaviour
      {
      public int damageThreshold = 25; // valor de exemplo, balancear depois
      public int hitCountThreshold = 3; // valor de exemplo, balancear depois
      // Mesma técnica de "get público / set privado" do BreakCounter.
      public bool IsRecovering { get; private set; }

      // Chamado depois que uma ação de ataque é resolvida.
      // Usa || ("OU"): basta UM dos limiares ser atingido, o que vier primeiro
      // (seção 3.8).
      public void CheckRecoveryTrigger(int damageDealt, int hitCount)
      {
      if (damageDealt >= damageThreshold || hitCount >= hitCountThreshold)
      {
      IsRecovering = true;
      Debug.Log($"{gameObject.name} entrou em Recuperação! Vai perder a próxima ação.");
      }
      }

      // Chamado pelo BattleManager quando a ação "perdida" já foi pulada
      public void ClearRecovery()
      {
      IsRecovering = false;
      }
      }
    2. No BattleManager, depois de resolver qualquer ataque (do herói ou do inimigo), chame CheckRecoveryTrigger passando o dano total causado e a quantidade de hits daquela ação.
    3. Antes de permitir uma nova ação de quem atacou, verifique IsRecovering — se true, pule a ação dele e chame ClearRecovery() em seguida.
  • Conceito: note que esse sistema é independente do Break System (confirmado no GDD) — mesmo que os dois "pareçam" fazer algo parecido (incapacitar por uma rodada), eles têm gatilhos e contadores completamente separados. É comum em design de jogos ter sistemas com efeito visualmente parecido mas mecanicamente distintos — o importante é não deixar o código de um "vazar" pro outro.

Dica de teste: para os três sistemas, use o mesmo truque da Tarefa 9 — um script de teste que força os eventos (ex: tecla 1 = registra hit de quebra, tecla 2 = adiciona ícone de queima, tecla 3 = simula ataque forte) e observe os logs no Console. Só implemente a UI de verdade (barras, ícones) depois que a lógica estiver validada.


Próximos blocos (visão geral, sem detalhar ainda)​

Depois que os Blocos A-E estiverem funcionando e testados, os próximos passos naturais são:

  • Bloco F: Esquiva/Parry com janela de tempo real (usando o Input System da Tarefa 4 + uma Coroutine, outro conceito de Unity pra "esperar" um tempo sem travar o jogo).
  • Bloco G: UI real (Canvas, botões) substituindo a barra de espaço de teste.
  • Bloco H: Benção Divina + a primeira habilidade especial (barra de precisão).

Se quiser, posso detalhar o Bloco F (Esquiva/Parry) e os seguintes no mesmo nível de detalhe assim que você terminar e testar os Blocos A-E — geralmente é mais fácil aprender um bloco de cada vez do que receber tudo de uma vez.


Este documento deve ser tratado como referência viva. Recomenda-se revisar e preencher as seções marcadas com ❓ antes de iniciar a implementação de cada sistema correspondente em Unity.