Muitos jogos para Android não são grandes o suficiente para justificar salvar / carregar ou opções / preferências, não importa personagens personalizados e similares simplesmente porque eles são jogados de improviso por 10 minutos no trem para casa.
Não cometa o erro que cometi de embarcar em algo com um amplo escopo para um primeiro jogo Android. Faça vários jogos menores e escolha algo maior
Especificidades do Android:
Estrutura:
Eu recomendaria ter quase todo o jogo em uma classe de atividade. O Android trabalha com a ideia de que cada atividade é como um miniaplicativo, semi-independente das outras atividades no aplicativo. O aplicativo é essencialmente uma pilha de atividades, com o usuário vendo o que está no topo.
Quando você abre uma em cima, ela geralmente é destruída e será recriada na próxima vez que o usuário iniciar uma nova atividade (intenção de iniciar a atividade). Isso dificulta a manutenção de estados entre as atividades e leva a uma única arquitetura de atividades
Você pode querer ter uma atividade do tipo Tela inicial com um menu "Novo jogo / Carregar jogo / Opções", e fazer disso a atividade de inicialização. No entanto, você precisará ter um menu no jogo e também na sua atividade de jogo, que terá a maioria das mesmas funções
Em meu próprio aplicativo, criei um "MenuActivity" que possui as funções que iniciam um novo jogo, salvam, carregam, alteram opções. Em seguida, minha HomeActivity estende isso, assim como minha GameActivity.
Como tudo está em uma classe Activity, eu recomendaria fazer uma hierarquia de classes de atividade e usar lotes de escopo privados, protegidos e padrão. Fazendo dessa maneira, você pode pelo menos dividir as coisas em arquivos diferentes para não ter um arquivo de Atividade incontrolável. Por exemplo, para o meu próprio aplicativo:
GraphicsEngine extends MenuActivity
.
PhysicsEngine extends GraphicsEngine
.
GameLogicActivity extends PhysicsEngine
.
UIActivity extends GameLogicActivity
.
Como meu aplicativo é opengl-es 3D, muitas coisas que eu faço não funcionam com atividades cruzadas, então tudo está na mesma atividade, então talvez eu esteja inclinado a usar uma arquitetura de atividade
-
Rosqueamento
Para a atividade do jogo, você tem dois threads (ou 3, se estiver fazendo coisas 3D em 3D). Um segmento é a interface do usuário / segmento principal. Esse é o segmento em que a atividade é iniciada, executada e é fornecida a você pelo android.
Esse thread da interface do usuário é o único no qual você pode atualizar os elementos da interface do usuário (visualizações, layouts etc.). É também aquele em que qualquer ouvinte para entrada do usuário será executado. Você não vê nenhuma mecânica do thread da interface do usuário, apenas que ele roda em um loop em segundo plano em algum lugar.
O segundo segmento, você mesmo (eu recomendaria o uso do AsyncTask , apesar de todas as suas deficiências). Isso faz todas as coisas que não são da interface do usuário que você faz em um loop de jogo normal, como atualização de movimento, cálculo de colisões, cálculos de combate e assim por diante.
Você cria esse segmento / classe AsyncTask como uma classe interna de sua atividade. Dessa forma, você pode ter alguns objetos de escopo de atividade ( Vector<Spaceship>
) que podem ser acessados pelo thread da interface do usuário e pelo loop do jogo.
Como a lógica do jogo está em andamento no segmento do loop do jogo, esse é o único segmento que realmente precisará alterar os valores das variáveis (atualizar a velocidade do tanque, reduzir o HP do jogador). O thread da interface do usuário apenas lê valores, portanto, deve haver problemas mínimos de simultaneidade.
A parte complicada é conseguir que o thread da interface do usuário faça atualizações a pedido do thread do loop do jogo. Há algumas maneiras de fazer isso. Se você ler a documentação do AsyncTask, ela possui o método publishProgress () / onProgressUpdate (). Na verdade, isso está adicionando coisas à fila de tarefas do thread da interface do usuário a ser executada no próximo loop.
O manipulador faz exatamente a mesma coisa, onde você implementa o método handleMessage () e esse método é realmente executado pelo próximo loop do thread da interface do usuário.
Por fim, qualquer entrada do usuário, estando no encadeamento da interface do usuário, você pode atualizar os elementos da interface do usuário imediatamente (assim, dentro da implementação de qualquer ouvinte onClick / onTouch). Se você precisar atualizar os objetos do jogo, poderá usar coisas como sincronizar ou implementar sua própria fila de atualizações no AsyncTask que, como o thread da interface do usuário, será executado na próxima vez em que executar o loop do jogo
Guia de segmentação para Android .
-
UI
Quanto à estrutura real da interface do usuário na atividade única, eu recomendo ter um layout de quadro como layout básico. Os elementos filho em um layout de quadro funcionam como uma fila. O primeiro elemento é desenhado primeiro, o segundo é desenhado em cima do primeiro, o terceiro em cima do segundo.
Dessa forma, você pode ter vários arquivos de layout XML e, gerenciando os filhos do layout de quadro, pode trocar facilmente conjuntos de visualizações dentro e fora.
Se você sempre tiver o SurfaceView na parte inferior (primeiro filho no layout do quadro), poderá usar todas as visualizações / widgets usuais do Android (botões, visualizações de texto, visualizações de rolagem) etc. sobre a parte superior da visualização de superfície, o que acontece a parte gráfica do jogo.
Por exemplo, se algo estiver carregando, você pode pausar o loop do jogo / fazer com que pule tudo, adicione uma tela opaca de 'loading' como o último filho do layout do quadro e o usuário verá 'loading' aparecer na tela , sem saber que outras visões estão por trás disso. Dessa forma, você não precisa remover visualizações que demoram muito para serem configuradas ou causam complicações cada vez que são adicionadas / removidas.
Eu também recomendaria o uso de lotes View.setVisibility. Você pode, por exemplo, adicionar um layout inteiro de 'inventário' ao layout do quadro base e, em seguida, apenas definirVisibility (View.Visible) quando o usuário clicar para visualizar seu inventário e definirVisibility (View.Gone) quando fechá-lo novamente. Dessa forma, você nem gerencia os filhos do layout do quadro, apenas adiciona tudo e torna as coisas visíveis / invisíveis quando e quando o usuário faz coisas diferentes
Isso ajuda a enfiar novamente; à medida que o usuário clica para abrir seu inventário, o onCLickListener é tratado no encadeamento da interface do usuário, o inventário é visível nele e o método updateInventory é chamado, novamente a partir do encadeamento da interface do usuário, com apenas getters em todos os objetos do jogo chamados
Aqui está o diagrama que fiz para uma pergunta anterior sobre a idéia de layout de atividade / quadro único: