Como separar estados ociosos e interativos em um loop de jogo de bichinho virtual
Em um jogo de bichinho virtual, mantenha o comportamento autônomo e as ações do jogador em caminhos de atualização separados, permitindo que ambos modifiquem um único estado compartilhado do pet por meio de transições explícitas. O comportamento ocioso deve avançar com o tempo decorrido; o comportamento interativo deve responder a eventos de entrada ou ao estado de entrada atual. Isso facilita pausar, testar e evitar que uma animação do pet ou um temporizador de necessidades dispute prioridade com um comando do jogador.
Defina o que significam “ocioso” e “interativo”
Trate-os como modos de comportamento, e não necessariamente como cenas diferentes ou loops separados. Em ocioso (idle), o pet segue sua rotina: escolhe uma pequena ação de ambiente, faz avançar um temporizador ou reproduz uma animação não essencial. Em interativo, o jogo está processando uma ação deliberada, como um toque, arrasto ou comando selecionado. O pet ainda pode se animar durante uma interação; a distinção está em qual sistema tem permissão para tomar decisões e alterar o estado do jogo.
Comece com uma pequena lista de ações que o jogador pode realizar e de comportamentos autônomos que o pet pode executar. Marque quais variáveis de estado cada um pode alterar. Por exemplo, uma piscada de ambiente pode afetar apenas a apresentação, enquanto uma ação selecionada de brincar pode alterar a atividade atual do pet e iniciar um temporizador. Esse mapeamento simples de responsabilidades ajuda a expor conflitos antes que o código cresça.
Mantenha o loop externo independente do comportamento do pet
O callback de quadros da engine deve coordenar o trabalho, e não conter todas as regras do pet. Uma sequência útil é: coletar as entradas, aplicar comandos válidos, avançar a simulação e, em seguida, renderizar o estado mais recente. Mantenha a coleta de entradas separada da simulação para que um toque não seja tratado acidentalmente como um tique de temporizador, e mantenha a renderização livre de alterações na lógica do jogo. O Game Programming Patterns descreve essa separação como processar entradas, atualizar o jogo e renderizar em um loop contínuo; atualizações de intervalo fixo são úteis quando a simulação precisa de um intervalo estável. Game Loop, Game Programming Patterns
Para um jogo web, o requestAnimationFrame agenda o trabalho antes da nova pintura na tela e fornece um carimbo de data/hora (timestamp) que você pode usar para medir o tempo decorrido. Em geral, os navegadores pausam esses callbacks em abas ocultas, portanto, não presuma que eles continuem funcionando como um relógio de parede confiável enquanto o jogo estiver em segundo plano. Window.requestAnimationFrame(), MDN
Encaminhe a entrada do jogador por meio de comandos
Converta a entrada bruta em um comando simples, como start_play, stop_play ou pet_tap. Um manipulador de comandos (command handler) verifica se a ação é válida para o modo atual, atualiza o modelo compartilhado uma única vez e emite qualquer sinal de apresentação resultante. Isso evita colocar a lógica de ação em callbacks de botão, callbacks de animação e na rotina ociosa ao mesmo tempo.
Por exemplo, se o jogador tocar enquanto o pet já estiver brincando, defina o resultado deliberadamente: ignore o toque, selecione uma atividade diferente ou interrompa a atividade. Evite permitir que tanto o seletor de ações ociosas quanto o manipulador de toques atribuam uma nova atividade durante a mesma atualização. Em engines com propagação de eventos, processe a entrada de jogabilidade na etapa apropriada e respeite o consumo pela interface do usuário (UI); a documentação de Node da Godot observa que a entrada não tratada (unhandled input) geralmente é mais adequada para o gameplay quando os controles de GUI precisam ter prioridade. Classe Node, documentação da Godot
Faça o tempo ocioso avançar com um relógio claro
Para comportamentos que devem progredir continuamente durante uma sessão visível, acumule o tempo de simulação decorrido e acione uma decisão ociosa quando seu intervalo for atingido. Não conte quadros: a frequência de quadros varia. Na Godot, _process(delta) recebe o tempo desde o quadro ocioso anterior, enquanto _physics_process(delta) segue os tiques de física; a documentação também observa que o delta acumulado pode divergir dos segundos do mundo real sob certas condições. Classe Node, documentação da Godot
Escolha o relógio de acordo com o comportamento. O movimento cosmético geralmente pode usar o tempo decorrido de quadro. O movimento relacionado a colisões pertence à atualização de física da engine. O temporizador de "escolher uma ação de ambiente a cada poucos segundos" do pet pode usar um temporizador de simulação, mas decida o que acontece após um quadro longo ou uma pausa: limite o valor decorrido (cap), execute apenas uma decisão ou compense o atraso em passos delimitados. Uma compensação ilimitada pode fazer o pet pular vários estados repentinamente quando o jogo for retomado.
Torne as transições de modo explícitas
Escreva uma tabela simples de transições antes de adicionar mais estados. Por exemplo, idle → playing inicia uma atividade selecionada pelo jogador; playing → idle ocorre quando ela termina ou o jogador a cancela; idle → resting pode ser escolhido pela rotina autônoma. Defina os efeitos de entrada e saída em um único lugar, como iniciar ou zerar um temporizador e selecionar a animação relevante. Não use apenas a conclusão da animação como prova de que o modo de jogo mudou: a reprodução visual pode ser interrompida ou ignorada.
Uma função de transição deve validar o modo atual, atualizar o modelo e retornar um resultado que o renderizador possa exibir. Isso mantém "o que o pet está fazendo" separado de "como essa ação é desenhada". Isso também oferece um alvo claro para os testes: envie um comando, inspecione o modo resultante e, em seguida, verifique se a rotina ociosa não o sobrescreve mais.
Decida como o tempo em segundo plano deve se comportar
Existem duas abordagens comuns, e a escolha certa depende do design do jogo. Um pet restrito à sessão pode interromper a simulação enquanto estiver suspenso e retomá-la a partir de seu estado anterior. Um jogo que prevê o avanço dos temporizadores enquanto estiver fechado deve registrar uma referência de tempo e reconciliar o tempo decorrido no retorno. Trate isso como um cálculo de retomada separado, em vez de tentar executar o loop de quadros em segundo plano; os callbacks de animação do navegador podem ser pausados em abas ocultas. Window.requestAnimationFrame(), MDN
Para um pet simples, reconcilie apenas as grandezas que realmente devem avançar — talvez apenas um temporizador de atividade — e defina um limite superior razoável para a compensação. Em seguida, teste fechar e reabrir após um intervalo curto e um longo. A escolha do relógio, o comportamento durante a suspensão e o teto de compensação são decisões de design; o callback de quadros da engine não decide isso por você.
Verifique se há atualizações conflitantes e entradas perdidas
Teste o loop com alguns cenários reproduzíveis: deixe o jogo ocioso por vários intervalos; emita um comando logo antes de uma decisão ociosa; mantenha uma entrada pressionada ou a repita; pause e retome; e acione uma nova ação enquanto uma animação ainda estiver sendo reproduzida. Verifique se cada comando altera o modelo apenas uma vez, se a lógica ociosa não substitui um estado selecionado pelo jogador e se o comportamento do tempo decorrido corresponde à política de pausa escolhida.
Se estiver usando uma atualização fixa (fixed update) para a simulação, lembre-se de que ela pode rodar zero, uma ou várias vezes entre os quadros renderizados. A Unity documenta esse comportamento para FixedUpdate e o recomenda para cálculos de física; atualizações visuais e de entrada têm necessidades de temporização diferentes. FixedUpdate(), documentação da Unity O Input System da Unity também enfileira eventos de dispositivos e os processa de acordo com o modo de atualização configurado; portanto, alinhe o tratamento de entradas com o loop que consome esses comandos. Fila de eventos de entrada, Unity Input System
Uma separação prática para manter
Mantenha a arquitetura enxuta: um modelo compartilhado de pet, um caminho de entrada para comando, uma atualização de comportamento ocioso, uma função de transição explícita e um renderizador que lê o modelo. Adicione uma etapa de simulação fixa apenas quando um comportamento sensível ao tempo exigir; não faça com que toda animação ou escolha de ambiente dependa de tiques de física. Quando surgir um bug, verifique quem alterou o modo por último e qual relógio o fez avançar. Isso dá a cada atualização uma função clara e torna os comportamentos ocioso e interativo previsíveis conforme o pet ganha mais ações.
