Caça aos Wumpus ou experimente escrever um jogo clássico do Android

imagem

Você já ouviu falar de Vampus? Independentemente da resposta - bem-vindo ao seu mandato!

Neste artigo, quero contar a minha história de criação de um jogo para Android. Dependendo da competência do leitor, a experiência, pensamentos e decisões transmitidas por mim serão mais ou menos úteis. No entanto, espero que minha história, pelo menos, seja interessante.

Conteúdo


1. Introdução
2. Escolha dos fundos
3. A ideia do projeto
4. CHEIRO DE WUMPUS
5. A base do básico - a estrutura do projeto
6. Geração do labirinto de Vampus e trabalho com ele
7. Armazenamento de mensagens do jogo e sua saída para o jogador
8. O primeiro resultado
9. Até jogos pequenos são dignos de história.
10. A Transformação Vampus e o resultado final
11. Conclusão

1. Introdução


Conteúdo

Primeiro, algumas palavras sobre você. Infelizmente, programar não é minha atividade principal, mas estou feliz em dedicar meu tempo livre a ela.

Por alguma razão, escolhi a maneira de criar jogos para celular. Meus projetos anteriores para dispositivos móveis foram criados no ambiente Qt em conjunto com as linguagens QML e C ++. Apreciei muito esse trio, porém, analisando minhas idéias, percebi que, no futuro, levaria muito tempo e esforço para resolver alguns problemas com os meios conhecidos por mim. Portanto, pensando no próximo projeto, decidi encontrar novas ferramentas de desenvolvimento mais adequadas e obter experiência com elas.

2. Escolha dos fundos


Conteúdo

Antes, prestei atenção apenas ao Android e, no novo projeto, decidi me concentrar neste sistema operacional, familiarize-me com o Android Studio, que é nativo a ele, experimente o novo Java para mim (e no futuro, se você quiser, também prometerá o Kotlin).

Eu não tinha usado o AS ou Java antes, e enfrentei uma frente gigantesca de trabalho com muitas tarefas novas, e estava pronto para correr para a batalha, restava apenas o projeto.

3. A ideia do projeto


Conteúdo

Sabe-se que o melhor de tudo é que o treinamento é realizado em tarefas reais e, principalmente, nas que são de interesse. Para mim, esse projeto de treinamento seria um jogo para o qual formei vários requisitos:

  1. Repetir valor.
  2. A simplicidade da mecânica do jogo.
  3. Uso mínimo de gráficos.
  4. A jogabilidade deve forçar o jogador a pensar.
  5. A festa do jogo não deve demorar.
  6. Rápida implementação do projeto (2 meses).
  7. Simplicidade e facilidade de interface do usuário.

Tendo analisado muitas opções e avaliado objetivamente meus pontos fortes, lembrando que, independentemente de quanto tempo e recursos fossem investidos no início, levaria muito mais, cheguei à conclusão de que é melhor usar os clássicos testados como base do que inventar algo próprio. Eu absolutamente não queria criar uma cobra condicional e comecei a estudar jogos antigos. Então eu descobri o curioso Hunt the Wumpus.

4. CHEIRO DE WUMPUS


Conteúdo
imagem

Vampus Hunting é um jogo de texto clássico inventado por Gregory Yob em 1972. No mesmo ano, ele recebeu uma descrição do jogo e do código-fonte em um artigo de revista.

A essência do jogo é o estudo do jogador sobre o labirinto de dodecaedro, que é o lar do vampiro maligno, e tenta adivinhar, com base nas mensagens indicadoras exibidas no log do jogo, que estão nos quartos superiores. Além do próprio Vampus (produz um odor desagradável), existem morcegos (barulho é ouvido) transportando o jogador para uma sala aleatória e poços (penetram), batida que leva ao final do jogo. O objetivo do jogo é matar o Vampus, para o qual o jogador tem 5 flechas que podem voar de 1 a 5 quartos por vez (o próprio jogador decide que tipo de "força" para dar o tiro). Assim, o jogador tem duas ações: atirar do arco, ir para a sala. Qual será o resultado depende da parte da sorte e do grau de consciência.

Em geral, gostei da mecânica: é simples, mas ao mesmo tempo com elementos de risco. Não havia jogos interessantes no Vampus no Google Play (exceto um novo jogo da época no mundo Lovecraft, no qual, como descobri mais tarde, era baseado na mesma mecânica do Vampus. Mas este artigo é sobre como criar um jogo no Habré), portanto foi aceito a decisão de tomar Vampus como base. Estabeleci o objetivo de manter o jogo clássico, mas atualizo-o levemente e adiciono novos recursos.

5. A base do básico - a estrutura do projeto


Conteúdo

Primeiro de tudo, eu aprendi as regras do jogo clássico e me familiarizei com várias implementações do Vampus. Então eu fiz um diagrama com a lógica do jogo (clicável):


A princípio, o esquema foi útil, porque Permitido analisar a mecânica do jogo, eliminar falhas e criar algo próprio. Além disso, esse esquema foi útil quando mais tarde trabalhei com o artista para explicar a essência do jogo.

Dividi o projeto em 4 partes, em cada uma das quais diferentes tarefas foram resolvidas. Eu darei apenas alguns deles.

1. Mecânica de jogo

  • De que forma as informações sobre a masmorra serão armazenadas?
  • Que tipo e quantas variáveis ​​você precisa?
  • Algoritmos de escrita: formar uma masmorra, vôo da lança, verificar o resultado do disparo, vôo da lança com uma sequência de salas selecionada incorretamente, mover um jogador, verificar uma sala ao mover, mover um jogador com morcegos, etc.
  • Como exibir informações no registro do jogo e em que ordem?
  • Em que ordem verificar o quarto?

2. interface do usuário

  • Quais atividades devem estar no aplicativo? Como eles devem parecer e que elementos devem estar neles?
  • Quais parâmetros podem ser alterados nas configurações?
  • Preciso de imagens no jogo?
  • Qual, em geral, deve ser o estilo do aplicativo (cores, humor, estilo de mensagem)?
  • Quais fontes usar?

3. Outros

  • Conecte-se aos serviços do Google Play
  • Trabalhar com arquivos XML
  • Quais fontes usar?

4. Escrevendo texto para o jogo

  • Mensagens do Jogo
  • As regras
  • Descrição do jogo para o Google Play

É um tanto desnecessário, e não impossível, descrever tudo; portanto, vou me concentrar apenas em alguns pontos, após os quais mostrarei o primeiro resultado obtido.

6. Geração do labirinto de Vampus e trabalho com ele


Conteúdo

O labirinto de Vampus é um dodecaedro, que pode ser representado como uma matriz G de dimensão 20x20. Nós numeramos os vértices de 0 a 19. Se o elemento da matriz for 1, há uma passagem entre os vértices (salas), caso contrário, não.

Também introduzimos uma matriz 20 × 3 N que armazena os índices de vizinhos para cada sala. Essa matriz acelerará o trabalho com G.


As matrizes G e N são costuradas no código do jogo e não mudam (é claro, armazenar G é desnecessário, porque você pode trabalhar apenas com N , mas por enquanto vamos deixar assim). Estes são os índices "verdadeiros" de um determinado dodecaedro de uma vez por todas. Para o jogador, os índices de "jogo" são formados, que são uma espécie de máscara dos "verdadeiros", em um vetor V de dimensão 20, da seguinte maneira:

//  ""    for (byte i = 0; i < 20; i++) { V[i] = i; } //    ""  for (int i = 0; i < 20; i++) { int tmpRand = random.nextInt(20); byte tmpVar = V[i]; V[i] = V[tmpRand]; V[tmpRand] = tmpVar; } 

Assim, é obtida a seguinte imagem:


O vetor V é formado a cada novo jogo, o que dá ao jogador uma "nova" masmorra.

Para estabelecer a correspondência entre o índice da sala "true" e "game", o método de conversão indByNmb é usado:

 public byte indByNmb(int room) { byte ind = -1; for (byte i = 0; i < V.length; i++) { if (V[i] == room) { ind = i; break; } } return ind; } 

Na entrada, o método indByNmb obtém o índice "game" da sala da sala e, na saída, fornece o ind "true".

Depois de gerar a estrutura da masmorra, colocamos: 2 bandos de morcegos, 2 buracos, um Vampus e um jogador:

 byte[] randomRooms = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}; for (int i = 0; i < 20; i++) { int tmpRand = random.nextInt(20); byte tmpVar = randomRooms[i]; randomRooms[i] = randomRooms[tmpRand]; randomRooms[tmpRand] = tmpVar; } P = randomRooms[0]; W = randomRooms[1]; Pits[0] = randomRooms[2]; Pits[1] = randomRooms[3]; Bats[0] = randomRooms[4]; Bats[1] = randomRooms[5]; 

Essa colocação garante que não haverá dois habitantes em uma sala e o jogador não será jogado na sala para o Vampus desde o início.

A geração completa de masmorras é a seguinte:

Código
 byte[] V = new byte[20]; // ""  int P; //  , byte W; //   byte[] Bats = new byte[2]; //    , byte[] Pits = new byte[2]; //    public void generateDungeons() { resetVars(); //      for (int i = 0; i < 20; i++) { int tmpRand = random.nextInt(20); byte tmpVar = V[i]; V[i] = V[tmpRand]; V[tmpRand] = tmpVar; } byte[] randomRooms = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}; for (int i = 0; i < 20; i++) { int tmpRand = random.nextInt(20); byte tmpVar = randomRooms[i]; randomRooms[i] = randomRooms[tmpRand]; randomRooms[tmpRand] = tmpVar; } P = randomRooms[0]; W = randomRooms[1]; Pits[0] = randomRooms[2]; Pits[1] = randomRooms[3]; Bats[0] = randomRooms[4]; Bats[1] = randomRooms[5]; } 


Agora você pode implementar todos os algoritmos da mecânica do jogo. Assim, por exemplo, a saída das salas vizinhas ocorre quando um jogador entra na sala com o índice currentRoom :

 public void printNearRooms(byte currentRoom) { byte ind = indByNmb(currentRoom); appendText(V[N[ind][0]], V[N[ind][1]], V[N[ind][2]]); } 

Na entrada, o método printNearRooms obtém o índice atual de "jogo" da sala currentRoom .

Considere a mecânica de um exemplo. Deixe o jogador ir para uma nova sala e uma mensagem aparecerá: "Agora estou na sala 8". O número 8 é um índice de jogos. O índice de sala “verdadeiro” é 6 (veja as capturas de tela acima). O código está trabalhando especificamente com o índice "true", ou seja, 6. Para 6, os índices dos vizinhos “verdadeiros” são determinados: 2, 5, 7. Os do “jogo”, respectivamente, serão: 10, 0, 7. Mostramos o jogador no log: “Eu posso ir para as salas 10, 0, 7”.

Assim, formando cada novo jogo o vetor V e trabalhando com os índices "verdadeiro" e "jogo" do gráfico de labirinto, é criada a aparência do fato de que cada jogo é único.

Graças à função appendText, as mensagens são exibidas em um intervalo especificado. Nós a encontraremos mais tarde.

Aqui está um exemplo de como verificar a sala quanto à proximidade de ratos:

 public boolean isBatsNear() { boolean answer = false; byte indP = indByNmb(P); for (int i = 0; i < 3; i++) { if ((V[N[indP][i]] == Bats[0]) || (V[N[indP][i]] == Bats[1])) { answer = true; break; } } return answer; } 


7. Armazenamento de mensagens do jogo e sua saída para o jogador


Conteúdo

O jogo clássico foi um processo de interação entre o jogador e o console, ou seja, o jogo era puramente textual. Eu queria salvar esse recurso para o qual dividi todas as mensagens do jogo em blocos, por exemplo:

  • A primeira mensagem do novo jogo.
  • Uma mensagem quando um jogador se move.
  • Mensagem nas proximidades dos poços.
  • Mensagem ao mover o Vampus.
  • Mensagem ao cair em um buraco.

O texto é armazenado em um arquivo XML. Cada bloco tem várias opções de mensagens para uma variedade de jogabilidade.

Um exemplo de bloco de mensagens que é exibido se houver um buraco em uma das salas vizinhas:

 <string name="g_pitsNear_1">—  \n</string> <string name="g_pitsNear_2">—    \n</string> <string name="g_pitsNear_3">—     \n</string> <string name="g_pitsNear_4">—  , \n</string> <string name="g_pitsNear_5">—   \n</string> <string-array name="g_pitsNear"> <item>@string/g_pitsNear_1</item> <item>@string/g_pitsNear_2</item> <item>@string/g_pitsNear_3</item> <item>@string/g_pitsNear_4</item> <item>@string/g_pitsNear_5</item> </string-array> 

Com essa estrutura, você pode editar facilmente os existentes ou adicionar novas mensagens sem afetar o código Java.

Se, por exemplo, ao verificar a sala, o método isBatsNear mostrado anteriormente retornou true , obtemos o bloco de mensagens necessário do XML e, então, assumimos aleatoriamente um como argumento para appendText :

 if (isBatsNear()) { String[] g_batsNear = getResources().getStringArray(R.array.g_batsNear); appendText(g_batsNear[random.nextInt(g_batsNear.length)]); } 

As mensagens do jogo são enviadas para o console, que é um objeto TextView . Vamos dar uma olhada no método appendText .

 public void appendText(final String str) { msgBuffer.add(str); if (!isTimerGameMsgWork) { mTimerGameMsg.run(); isTimerGameMsgWork = true; } } 

Quando se torna necessário exibir uma mensagem do jogo, o método appendText é chamado , o que leva como argumento. No próprio método, uma linha é adicionada primeiro ao buffer msgBuffer . Isso é seguido pela verificação da variável booleana isTimerGameMsgWork . Aceita true nos casos em que o timer mTimerGameMsg é iniciado . Quando esse timer funciona, a partir do buffer msgBuffer, de acordo com o princípio FIFO (primeiro a entrar, primeiro a sair), as mensagens são recebidas em um intervalo especificado mIntervalGameMsg e adicionadas ao log do jogo - txtViewGameLog .

O código completo de saída da mensagem:

Código
 ArrayList<String> msgBuffer = new ArrayList<>(); Handler mHandlerGameMsg; private int mIntervalGameMsg = 1000; boolean isTimerGameMsgWork = false; public void appendText(final String str) { msgBuffer.add(str); if (!isTimerGameMsgWork) { mTimerGameMsg.run(); isTimerGameMsgWork = true; } } final Runnable mTimerGameMsg = new Runnable() { @Override public void run() { if (msgBuffer.size() == 0) { mHandlerGameMsg.removeCallbacks(mTimerGameMsg); isTimerGameMsgWork = false; } else { txtViewGameLog.append(msgBuffer.get(0)); msgBuffer.remove(0); mHandlerGameMsg.postDelayed(mTimerGameMsg, mIntervalGameMsg); } } }; 



8. O primeiro resultado


Conteúdo

Após um mês de desenvolvimento, a primeira versão jogável do jogo com funcionalidade totalmente implementada foi recebida. Anexo capturas de tela:

Capturas de tela da primeira versão do jogo





Nas capturas de tela apresentadas, você pode ver: o menu principal, a janela de regras, a janela de configurações, a janela do jogo.

Obviamente, entendi que o resultado era completamente desinteressante. O principal é que adquiri experiência prática em AS e Java, que foi a primeira tarefa.

O jogador, como no jogo clássico, teve que interagir através do console: digite o número da sala para mover ou a rota para a seta voar. Nas configurações, possibilitei alterar o tamanho da fonte e a transparência do plano de fundo. Supus que, para cada janela do jogo, houvesse sua própria imagem de fundo (para diversificar levemente a jogabilidade, ha!).

Em seguida, planejei substituir as fotos existentes (que sem escrúpulos tirei da Internet) pelas que o artista desenharia. Depois, lançava o jogo no mercado Play e esquecia com segurança, aplicando a experiência adquirida a novos projetos. E então eu não podia imaginar o quanto Vampus poderia mudar ...

9. Até jogos pequenos são dignos de história.


Conteúdo

Quando uma pessoa vem trabalhar com a alma, o projeto apenas se beneficia disso. Eu tive sorte que a artista, Anastasia Frolikova , acabou sendo uma pessoa assim. I.e. em vez de apenas desenhar o que eu precisava, ela se interessou pelo mundo do jogo e queria entender como ele funciona. E de repente descobriu-se que em geral não havia paz! Quem é esse vampus? E por que um jogador deveria matá-lo? Como são os quartos do Vampus? E assim por diante, sobre o que eu não pensava e que não planejava contar ao jogador. Como resultado, concordamos que mesmo um jogo aparentemente tão pequeno deveria ter sua própria história. E ela apareceu.

Segundo a nossa lenda, os vampiros, embora antigos, mas não uma criatura mítica do mal, que gostam de tirar sarro das pessoas. Sim, ele vive em um labirinto, mas esse labirinto não tem a forma de uma masmorra escura clássica, mas a forma de uma casa inimaginável que consiste em uma pilha de salas, cujo conteúdo caracteriza Vampus. Por exemplo, em uma das salas, há um cinema, nas paredes onde há pôsteres de seus filmes favoritos, e na outra, o armário, onde ele prepara suas "brincadeiras".


O jogador de um caçador sem nome se transformou em um regular no fórum criptozoológico que quer provar a existência de Vampus. Substituímos o arco e flecha clássico por uma câmera e um filme, e o objetivo do jogo não era matar Vampus, mas tirar sua fotografia. A propósito, o menu principal foi refeito em um fórum no qual as pessoas discutem o Vampus e, a partir da discussão, o jogador pode aprender sobre isso.

A primeira versão do menu principal, feita na forma de um fórum

Quanto aos outros aspectos, eles permaneceram quase inalterados: os ratos agem da mesma maneira, e entrar no buraco começou a levar a um acidente, a quebra da câmera e o fim do jogo (e não a morte do jogador).


Outro momento sobre a câmera. No jogo clássico, o jogador podia atirar uma flecha no intervalo de 1 a 5 quartos e parecia lógico. Temos uma câmera em vez de um arco (tirando fotos de 1 a 3 quartos por vez, mas trabalhando como uma flecha clássica que atinge Vampus). E isso ... parece estranho, não é? Havia uma ideia de reduzir o alcance da câmera para 1 sala, para que você pudesse tirar fotos apenas da vizinha, mas isso primeiro complica o jogo e, em segundo lugar, podem ser obtidas situações em que o jogo não pode ser vencido, o que está errado. Em geral, esse é o momento que pessoalmente ainda me assombra, mas ainda não encontrei uma solução.

No que diz respeito ao estilo e humor do jogo. Quase todos os jogos sobre Vampus são feitos em tons de cinza chatos, e as ações ocorrem em locais escuros das masmorras. Portanto, decidimos que nosso jogo deveria ser diferente de tudo isso e ser feito com humor e cores brilhantes.




10. A Transformação Vampus e o resultado final


Conteúdo

Então, mais 2 meses de trabalho no jogo estavam esperando por nós. Desde Vampus tem 20 quartos, cada um tem seu próprio interior. Além disso, foram sorteados ícones de conquistas, ícones no jogo, decisões sobre design em geral e UI. Todo o texto do jogo também foi adicionado, o código foi complementado e otimizado, novas funções foram adicionadas (por exemplo, um bloco de notas apareceu para registrar informações durante o jogo). Em geral, o Vampus passou por grandes mudanças.

As salas, por exemplo, foram criadas da seguinte maneira:

imagem
Existem 20 quartos, todos eles únicos, e o jogador recebe um labirinto "novo" a cada jogo. Como criar todas as novas fotos de jogos anexadas a novas salas? O mais simples é usar a mesma abordagem de índices "verdadeiros" e "jogos":

 public void changeImgOfRoom() { ImageView img = findViewById(R.id.imgRoom); int ind = indByNmb(P); String imgName = "room_" + ind; int id = getResources().getIdentifier(imgName, "drawable", this.getPackageName()); Glide.with(this) .load(id) .transition(DrawableTransitionOptions.withCrossFade()) .into(img); } 

Imagens de um formato quadrado (para reduzir a distorção ao visualizar em telas diferentes) são armazenadas em recursos com os nomes [room_0; room_1; ..., sala_19]. E eles estão, de fato, associados aos índices "verdadeiros" de dodecaedro, mas para o jogador, cada novo jogo para a mesma sala terá imagens diferentes. Por que isso é necessário? Para permitir que um grupo de jogo específico correlacione informações textuais com a imagem de uma sala específica ("sim, eu lembro que na sala X, que era a sala de estar, havia um rascunho") e que não deu certo que "por que é sempre X é a mesma foto? Tudo para variar e ajudar o jogador (no entanto, como a experiência demonstrou, não há ajuda na memorização visual, é mais eficiente trabalhar com texto).

Por fim, temos uma nova versão do jogo. E você sabe o que? O vampus se tornou muito atraente e, o mais importante, ainda é o mesmo Vampus clássico, mas em uma nova casa aconchegante!

Capturas de tela da segunda versão do jogo





Nas capturas de tela: o menu principal, a janela de regras, a janela de configurações, a janela do jogo.

No que diz respeito à mecânica, ela é apenas ligeiramente modificada e renomeada (bem, de fato, há uma diferença: uma câmera ou um arco, se você precisar fazer a mesma coisa?). A mudança mais notável na mecânica é simplificar o processo de interação entre o jogador e o jogo - a entrada clássica de números de quarto usando o teclado foi removida. Agora, para alternar entre salas, você precisa selecionar 1 dentre 3 números na janela pop-up e, para formar a “rota” para fotografar, basta rolar as rodas no tambor:


No vídeo abaixo, você pode ver o resultado final:



11. Conclusão


Conteúdo

A primeira versão do jogo foi recebida por mim em um mês de desenvolvimento, alocando 1-2 horas após o trabalho. Ao mesmo tempo, nem o AS nem o Java eram familiares para mim. A segunda versão do jogo levou mais 2 meses. Assim, apenas 3 meses de trabalho de lazer.

É claro que, desta forma, o jogo não é para uma ampla variedade, porque pode parecer complicado e não emocionante para um jogador moderno, mas mais importante, provavelmente, mantendo o espírito dos jogos clássicos, você não acha?

Estou feliz com o resultado? Definitivamente sim. Eu ganhei muita experiência tanto em programação quanto em trabalho em equipe. Gosto tanto da mecânica resultante do jogo quanto de seu componente visual. Gostaria de mudar alguma coisa? Obviamente, não há limites para a perfeição e você sempre pode adicionar / melhorar algo, mas não pode fazer isso para sempre!

Este jogo é interessante? Bem, não cabe a mim decidir. Mas deixe Vampus ficar confortável na casa que construímos para ele com muito amor e atenção.

Desejo-lhe sucesso!

Obrigado e cuidado com o Vampus!

PS Tarefas que surgem constantemente e a alegria de resolvê-las é exatamente o que eu amo programar. Espero que você não tenha menos prazer.

Source: https://habr.com/ru/post/pt462943/


All Articles