Como construir um corredor infinito no iOS: Cocos2D, automação e mais

Publicados: 2022-03-11

Desenvolver jogos para iOS pode ser uma experiência enriquecedora em termos de crescimento pessoal e financeiro. No início deste ano, implantei um jogo baseado em Cocos2D, Bee Race, na App Store. Sua jogabilidade é simples: um corredor infinito no qual os jogadores (neste caso, abelhas) acumulam pontos e evitam obstáculos. Veja aqui uma demonstração.

Neste tutorial, explicarei o processo por trás do desenvolvimento de jogos para iOS, desde o Cocos2D até a publicação. Para referência, aqui está um pequeno índice:

  • Sprites e objetos físicos
  • Uma breve introdução ao Cocos2D
  • Usando Cocos2D com storyboards
  • Jogabilidade e (breve) descrição do projeto
  • Automatize trabalhos. Use ferramentas. Fique tranquilo.
  • Faturamento no aplicativo
  • Jogabilidade multiplayer com o Game Center
  • Espaço para melhorias
  • Conclusão

Sprites e objetos físicos

Antes de entrarmos nos detalhes, será útil entender a distinção entre sprites e objetos físicos.

Para qualquer entidade que apareça na tela de um jogo de corrida sem fim, a representação gráfica dessa entidade é chamada de sprite , enquanto a representação poligonal dessa entidade no mecanismo de física é chamada de objeto físico .

Assim, o sprite é desenhado na tela, apoiado por seu objeto físico correspondente, que é então manipulado pelo seu mecanismo de física. Essa configuração pode ser visualizada aqui, onde os sprites são exibidos na tela, com suas contrapartes poligonais físicas destacadas em verde:

Nos jogos de corredor infinito do iOS, sprites e objetos físicos coexistem.

Objetos físicos não são conectados a seus respectivos sprites por padrão, o que significa que você, como desenvolvedor do iOS, pode escolher qual mecanismo de física usar e como conectar sprites e corpos. A maneira mais comum é subclassificar o sprite padrão e adicionar um corpo físico concreto a ele.

Com aquilo em mente…

Um breve tutorial sobre o desenvolvimento de jogos para iOS Cocos2D

Cocos2D-iphone é uma estrutura de código aberto para iOS que usa OpenGL para aceleração de gráficos de hardware e suporta os mecanismos de física Chipmunk e Box2D.

Em primeiro lugar, por que precisamos de tal estrutura? Bem, para começar, os frameworks implementam os componentes mais usados ​​no desenvolvimento de jogos. Por exemplo, o Cocos2D pode carregar sprites (em particular, folhas de sprite (por quê?)), iniciar ou parar um motor de física e lidar com tempo e animação corretamente. E faz tudo isso com código que foi revisado e testado extensivamente - por que dedicar seu próprio tempo para reescrever código provavelmente inferior?

Talvez o mais importante, no entanto, o desenvolvimento de jogos Cocos2D usa aceleração de hardware gráfico . Sem essa aceleração, qualquer jogo de corredor infinito para iOS com um número moderado de sprites será executado com desempenho notavelmente ruim. Se tentarmos fazer uma aplicação mais complicada, provavelmente começaremos a ver um efeito “bullet-time” na tela, ou seja, várias cópias de cada sprite enquanto ele tenta animar.

Finalmente, o Cocos2D otimiza o uso de memória, pois armazena sprites em cache. Assim, quaisquer sprites duplicados requerem memória adicional mínima, o que obviamente é útil para jogos.

Usando Cocos2D com storyboards

Depois de todos os elogios que dei ao Cocos2D, pode parecer ilógico sugerir o uso de Storyboards. Por que não apenas manipular seus objetos com Cocos2D, etc.? Bem, para ser honesto, para janelas estáticas, geralmente é mais conveniente usar o Interface Builder do Xcode e seu mecanismo Storyboard.

Em primeiro lugar, ele me permite arrastar e posicionar todos os meus elementos gráficos para o meu jogo de corrida sem fim com o mouse. Em segundo lugar, a API do Storyboard é muito, muito útil. (E sim, eu sei sobre o Cocos Builder).

Aqui está um rápido vislumbre do meu Storyboard:

Para aprender a fazer um jogo de corrida sem fim, comece com um bom Storyboard.

O controlador de visão principal do jogo contém apenas uma cena Cocos2D com alguns elementos HUD no topo:

O início do nosso tutorial Cocos2D está no controlador de visualização.

Preste atenção ao fundo branco: é uma cena Cocos2D, que carregará todos os elementos gráficos necessários em tempo de execução. Outras visualizações (indicadores ao vivo, dentes de leão, botões, etc.) são todas as visualizações padrão do Cocoa, adicionadas à tela usando o Interface Builder.

Não vou me alongar nos detalhes – se você estiver interessado, exemplos podem ser encontrados no GitHub.

Jogabilidade e (breve) descrição do projeto

(Para fornecer mais motivação, gostaria de descrever meu jogo de corredor sem fim com um pouco mais de detalhes. Sinta-se à vontade para pular esta seção se quiser prosseguir para a discussão técnica.)

Durante o jogo ao vivo, a abelha fica imóvel, e o próprio campo está realmente correndo, trazendo vários perigos (aranhas e flores venenosas) e vantagens (dentes-de-leão e suas sementes).

O Cocos2D possui um objeto de câmera que foi projetado para seguir o personagem; na prática, era menos complicado manipular o CCLayer contendo o mundo do jogo.

Os controles são simples: tocar na tela move a abelha para cima e outro toque a move para baixo.

A própria camada do mundo na verdade tem duas subcamadas. Quando o jogo começa, a primeira subcamada é preenchida de 0 a BUF_LEN e exibida inicialmente. A segunda subcamada é preenchida antecipadamente de BUF_LEN para 2*BUF_LEN. Quando a abelha atinge BUF_LEN, a primeira subcamada é limpa e instantaneamente repovoada de 2*BUF_LEN para 3*BUF_LEN, e a segunda subcamada é apresentada. Dessa forma, alternamos entre camadas, nunca retendo objetos obsoletos, parte importante para evitar vazamentos de memória.

Meu jogo de corredor infinito é composto por um mundo de várias camadas.

Em termos de motores de física, usei o Chipmunk por dois motivos:

  1. Está escrito em puro Objective-C.
  2. Eu já trabalhei com Box2D antes, então eu queria comparar os dois.

O motor de física foi realmente usado apenas para detecção de colisão. Às vezes, me perguntam: “Por que você não escreveu sua própria detecção de colisão?”. Na realidade, não há muito sentido nisso. Motores de física foram projetados para isso mesmo: eles podem detectar colisões entre corpos de formas complicadas e otimizar esse processo. Por exemplo, os mecanismos de física geralmente dividem o mundo em células e realizam verificações de colisão apenas para corpos na mesma célula ou em células adjacentes.

Automatize trabalhos. Use ferramentas. Fique tranquilo.

Um componente-chave do desenvolvimento de jogos de corredor infinito indie é evitar tropeçar em pequenos problemas. O tempo é um recurso crucial no desenvolvimento de um aplicativo, e a automação pode economizar muito tempo.

Mas, às vezes, a automação também pode ser um compromisso entre o perfeccionismo e o cumprimento do prazo. Nesse sentido, o perfeccionismo pode ser um assassino de Angry Birds.

Por exemplo, em outro jogo para iOS que estou desenvolvendo atualmente, construí um framework para criar layouts usando uma ferramenta especial (disponível no GitHub). Esse framework tem suas limitações (por exemplo, não tem boas transições entre cenas), mas usá-lo me permite fazer minhas cenas em um décimo do tempo.

Portanto, embora você não possa construir seu próprio superframework com superferramentas especiais, você ainda pode e deve automatizar o maior número possível dessas pequenas tarefas.

O perfeccionismo pode ser um assassino de Angry Birds. O tempo é um recurso crucial no desenvolvimento de jogos para iOS.
Tweet

Na construção desse corredor infinito, a automação foi a chave mais uma vez. Por exemplo, meu artista me enviava gráficos de alta resolução por meio de uma pasta especial do Dropbox. Para economizar tempo, escrevi alguns scripts para criar automaticamente conjuntos de arquivos para as várias resoluções de destino exigidas pela App Store, adicionando -hd ou @2x também (os scripts são baseados no ImageMagick).

Em termos de ferramentas adicionais, eu achei o TexturePacker muito útil - ele pode empacotar sprites em folhas de sprite para que seu aplicativo consuma menos memória e carregue mais rápido, pois todos os seus sprites serão lidos de um único arquivo. Também pode exportar texturas em quase todos os formatos de frameworks possíveis. (Observe que o TexturePacker não é uma ferramenta gratuita, mas acho que vale o preço. Você também pode conferir alternativas gratuitas como o ShoeBox.)

A principal dificuldade associada à física do jogo é criar polígonos adequados para cada sprite. Em outras palavras, criando uma representação poligonal de alguma abelha ou flor de forma obscura. Nem tente fazer isso manualmente — sempre use aplicativos especiais, que são muitos. Alguns são até bem… exóticos – como criar máscaras vetoriais com o Inkspace e depois importá-las para o jogo.

Para o meu próprio desenvolvimento de jogos de corredor sem fim, criei uma ferramenta para automatizar esse processo, que chamo de Andengine Vertex Helper. Como o nome sugere, ele foi inicialmente projetado para a estrutura Andengine, embora funcione adequadamente com vários formatos atualmente.

No nosso caso, precisamos usar o padrão plist:

 <real>%.5f</real><real>%.5f</real>

Em seguida, criamos um arquivo plist com as descrições dos objetos:

 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>jet_ant</key> <dict> <key>vertices</key> <array> <real>-0.18262</real><real>0.08277</real> <real>-0.14786</real><real>-0.22326</real> <real>0.20242</real><real>-0.55282</real> <real>0.47047</real><real>0.41234</real> <real>0.03823</real><real>0.41234</real> </array> </dict> </dict> </plist>

E um carregador de objetos:

 - (void)createBodyAtLocation:(CGPoint)location{ float mass = 1.0; body = cpBodyNew(mass, cpMomentForBox(mass, self.sprite.contentSize.width*self.sprite.scale, self.sprite.contentSize.height*self.sprite.scale)); body->p = location; cpSpaceAddBody(space, body); NSString *path =[[NSBundle mainBundle] pathForResource:@"obj _descriptions" ofType:@"plist"]; // <- load plist NSDictionary *objConfigs = [[[NSDictionary alloc] initWithContentsOfFile:path] autorelease]; NSArray *vertices = [[objConfigs objectForKey:namePrefix] objectForKey:@"vertices"]; shape = [ChipmunkUtil polyShapeWithVertArray:vertices withBody:body width:self.sprite.contentSize.width height:self.sprite.contentSize.height]; shape->e = 0.7; shape->u = 1.0; shape->collision_type = OBJ_COLLISION_TYPE; cpSpaceAddShape(space, shape); }

Para testar como os sprites correspondem aos seus corpos físicos, veja aqui.

Muito melhor, certo?

Em resumo, sempre automatize quando possível. Mesmo scripts simples podem economizar muito tempo. E o mais importante, esse tempo pode ser usado para programação em vez de clicar com o mouse. (Para motivação adicional, aqui está um token XKCD.)

Faturamento no aplicativo

As bolas de sopro coletadas no jogo funcionam como uma moeda no aplicativo, permitindo que os usuários comprem novas skins para suas abelhas. No entanto, essa moeda também pode ser comprada com dinheiro real. Um ponto importante a ser observado em relação ao faturamento no aplicativo é se você precisa ou não realizar verificações do lado do servidor para validar a compra. Uma vez que todos os bens compráveis ​​são essencialmente iguais em termos de jogabilidade (apenas alterando a aparência da abelha), não há necessidade de realizar uma verificação do servidor para a validade da compra. No entanto, em muitos casos, você definitivamente precisará fazer isso.

Para saber mais, Ray Wenderlich tem o tutorial de cobrança no aplicativo perfeito.

Jogabilidade multiplayer com o Game Center

Nos jogos para celular, socializar é mais do que apenas adicionar um botão 'Curtir' no Facebook ou configurar tabelas de classificação. Para tornar o jogo mais emocionante, implementei uma versão multiplayer.

Como funciona? Primeiro, dois jogadores são conectados usando a criação de partidas em tempo real do iOS Game Center. Como os jogadores estão realmente jogando o mesmo jogo de corredor infinito, é preciso haver apenas um único conjunto de objetos de jogo. Isso significa que a instância de um jogador precisa estar gerando os objetos, e a do outro jogador os lerá. Em outras palavras, se os dispositivos de ambos os jogadores estivessem gerando objetos de jogo, seria difícil sincronizar a experiência.

Com isso em mente, após a conexão ser estabelecida, ambos os jogadores enviam um ao outro um número aleatório. O jogador com o maior número atua como o “servidor”, criando objetos de jogo.

Você se lembra da discussão sobre a geração do mundo porcionado? Onde tínhamos duas subcamadas, uma de 0 a BUF_LEN e outra de BUF_LEN a 2*BUF_LEN? Essa arquitetura não foi usada por acaso - era necessário fornecer gráficos suaves em redes atrasadas. Quando uma porção de objetos é gerada, ela é empacotada em uma plist e enviada para o outro jogador. O buffer é grande o suficiente para permitir que o segundo jogador jogue mesmo com um atraso de rede. Ambos os jogadores enviam um ao outro sua posição atual com um período de meio segundo, também enviando seus movimentos para cima e para baixo imediatamente. Para suavizar a experiência, a posição e a velocidade são corrigidas a cada 0,5 segundo com uma animação suave, então na prática parece que o outro jogador está se movendo ou acelerando gradualmente.

Certamente há mais considerações a serem feitas em relação à jogabilidade interminável multijogador, mas espero que isso lhe dê uma noção dos tipos de desafios envolvidos.

Espaço para melhorias

Os jogos nunca terminam. É certo que existem várias áreas em que gostaria de melhorar a minha, nomeadamente:

  1. Problemas de controle: bater é muitas vezes um gesto pouco intuitivo para jogadores que preferem deslizar.
  2. A camada do mundo é movida usando a ação CCMoveBy. Isso foi bom quando a velocidade da camada mundial era constante, já que a ação CCMoveBy era ciclada com CCRepeatForever:

     -(void) infiniteMove{ id actionBy = [CCMoveBy actionWithDuration: BUFFER_DURATION position: ccp(-BUFFER_LENGTH, 0)]; id actionCallFunc = [CCCallFunc actionWithTarget:self selector:@selector(requestFillingNextBuffer)]; id actionSequence = [CCSequence actions: actionBy, actionCallFunc, nil]; id repeateForever = [CCRepeatForever actionWithAction:actionSequence]; [self.bufferContainer runAction:repeateForever]; }

    Mas depois, adicionei um aumento de velocidade mundial para tornar o jogo mais difícil à medida que avança:

     -(void) infiniteMoveWithAccel { float duration = BUFFER_DURATION-BUFFER_ACCEL*self.lastBufferNumber; duration = max(duration, MIN_BUFFER_DURATION); id actionBy = [CCMoveBy actionWithDuration: duration position: ccp(-BUFFER_LENGTH, 0)]; id restartMove = [CCCallFunc actionWithTarget:self selector:@selector(infiniteMoveWithAccel)]; id fillBuffer = [CCCallFunc actionWithTarget:self selector:@selector(requestFillingNextBuffer)]; id actionSequence = [CCSequence actions: actionBy, restartMove, fillBuffer, nil]; [self.bufferContainer runAction:actionSequence]; }

    Essa alteração fez com que a animação se esfarrapasse a cada reinicialização da ação. Tentei resolver o problema, sem sucesso. No entanto, meus testadores beta não perceberam o comportamento, então adiei a correção.

  3. Por um lado, não houve necessidade de escrever minha própria autorização para multiplayer ao usar o Game Center ou executar meu próprio servidor de jogo. Por outro lado, tornou impossível criar bots, o que é algo que eu gostaria de mudar.

Conclusão

Criar seu próprio jogo de corredor infinito indie pode ser uma ótima experiência. E quando você chega à etapa de publicação do processo, pode ser uma sensação maravilhosa ao liberar sua própria criação na natureza.

O processo de revisão pode variar de vários dias a várias semanas. Para saber mais, há um site útil aqui que usa dados de origem coletiva para estimar os tempos atuais de revisão.

Além disso, recomendo usar o AppAnnie para examinar várias informações sobre todos os aplicativos na App Store, e registrar-se em alguns serviços de análise, como o Flurry Analytics, também pode ser útil.

E se este jogo o intrigou, não deixe de conferir Bee Race na loja.