terça-feira, 27 de março de 2012

Desenvolvendo jogos para Android - Parte 1 - Criando elementos gráficos

Jogo que iremos desenvolver: Smash!
Hoje começaremos com uma série de tutoriais sobre desenvolvimento de jogos/games para Android. Para isso iremos construir juntos, passo a passo, o jogo que estou chamando de Smash! Nessa primeira parte aprenderemos como fazer os personagens, e o background utilizando a classe Sprite.

O jogo é bastante simples: há os personagens rosas e os dourados. O objetivo do jogador é derrotar todos os rosas (clicando neles) sem clicar nos dourados, no mínimo de tempo possível. A medida que se avança nas fases os personagens ficam mais rápidos.


Iremos utilizar como base para o desenvolvimento o nosso Android Game Engine que estamos desenvolvendo aqui no blog. Iremos usar a 1ª versão dele, que tem pouca coisa mas o suficiente para criar esse jogo. Se você quer saber como funciona essa engine, quer entender como o jogo roda, acesse os tutoriais sobre ele aqui.

Acompanhe o desenvolvimento por aqui ou baixe o código usado aqui ou o .apk aqui.


Então vamos começar. Crie um novo projeto com as suas preferências. A primeira coisa a se fazer é adicionar o Android Game Engine no projeto. Isso é simples. Primeiro crie uma pasta "libs" na raiz do seu projeto (junto com src, res...) e nessa pasta coloque esse aquivo. Agora vá no eclipse, abra essa pasta (se ainda não apareceu no Package Explorer do eclipse de um F5 que irá aparecer), dê um clique direito no arquivo que adicionamos (AndroidGameEngine.jar) e vá em Build Path -> Add to Build Path como mostra a figura:

Adicionando a biblioteca do Android Game Engine ao projeto.
Feito isso vamos começar a codificação do jogo de fato. Vamos começar com o sprite do personagem dourado, chamado de Gold, por ser bastante simples. Ele apenas vai ficar rodando o cenário, e não há alteração na animação. Ele irá estender a classe Sprite:


package com.tutoriandroid.games.smash;

import java.util.Random;

import android.graphics.Bitmap;

import com.gdacarv.engine.androidgame.Sprite;

public class Gold extends Sprite {
  
  public int speedXspeedY;

  public Gold(int xint yRandom randomBitmap bmpint bmp_rowsint bmp_columns{
    super(bmpbmp_rowsbmp_columns);
    setAnimation(ANIM_GO)// Seta a animação apenas como "ida" (ciclíca).
    speedX random.nextInt(73//Velocidade horizontal de -3 a 3.
    speedY random.nextInt(73//Velocidade vertical de -3 a 3.
    this.x;
    this.y;
  }

  @Override
  public void update(// Anda.
    super.update();
    += speedX;
    += speedY;
  }
}


Bem simples não? O construtor recebe como parâmetros a posição (x, y), uma instância para gerar números randômicos (poderia criar uma nova dentro do construtor, mas acho melhor utilizar a mesma para todas as instâncias), a imagem em Bitmap, e a quantidade de linhas e colunas de frames dessa imagem.


Vamos então partir para o sprite do personagem rosa, chamado de Pink, que é um pouco mais complexo por envolver diferentes animações e tipo de movimentação:

package com.tutoriandroid.games.smash;

import java.util.Random;

import android.graphics.Bitmap;
import android.graphics.Paint;

import com.gdacarv.engine.androidgame.Sprite;

public class Pink extends Sprite {
  
  public int speed 1// Velocidade da movimentação/animação.
  public byte direction 0// Direção da movimentação/animação.
  private boolean dying falsedead false;
  protected byte animationSpeedControl 0;

  public Pink(int xint yRandom randomBitmap bmpint bmp_rowsint bmp_columns{
    super(bmpbmp_rowsbmp_columns);
    direction (byterandom.nextInt(8)// Direção aleatória.
    int frame direction*3// Seta o frame inicial para a direção
    setAnimation(frameframeframe+3ANIM_GOBACK);
    this.x;
    this.y;
  }

  @Override
  public void update({
    animationSpeedControl++// Esta versão do Android Game Engine não
    if(animationSpeedControl >= speed)// suporta mudança na 
      super.update()// velocidade da animação, então foi feito
      animationSpeedControl 0// esse hack para diminuir a 
    // velocidade, que estava muito rápida.
      
    if(!dying)// Se não está morrendo, anda na direção.
      += direction direction -speed speed 0;
      += Math.abs((direction-24direction || direction speed -speed 0;
    }
    else// Se está morrendo, aumenta a transparência até ficar  invisível.
      int alpha mPaint.getAlpha() 
      if(alpha <= 25)
        dead true;
      else
        mPaint.setAlpha(alpha-10);
    }
  }
  
  public void changeDirection(byte direct)// Muda a direção e muda a  animação de acordo.
    direction direct
    int frame direction*3;
    setAnimation(frameframeframe+3ANIM_GOBACK);
  }
  
  public void kill()// Função que será chamada quando o jogador clicar no Pink.
    dying true;
    setAnimation(001ANIM_STOP);
    mPaint new Paint();
  }
}

Nota para o hack que fiz para poder alterar a velocidade da animação, pois tava muito rápida. Diferente do Gold, esse sprite não usa speedX e speedY, ele usa um speed geral e define sua direção entre as oito possíveis. Cada valor de 0 a 7, que a variável direction pode assumir tem o seguinte significado:

Direções assumidas por Pink

Ainda falta o background mas isso mostrarei depois. Vamos agora fazer nossa GameView para, pelo menos, mostrar nossas Sprites andando pelo cenário:

package com.tutoriandroid.games.smash;


import java.util.ArrayList;

import java.util.Random;



import android.content.Context;

import android.content.res.Resources;

import android.graphics.Bitmap;

import android.graphics.BitmapFactory;

import android.graphics.Canvas;
import android.view.MotionEvent;



import com.gdacarv.engine.androidgame.GameView;



public class MainGameView extends GameView {

  

  protected int level 0score 0;

  

  protected ArrayList<Pinkpinks; //Lista dos Pinks.
  protected ArrayList<Goldgolds; //Lista dos Golds.



  public MainGameView(Context context{

    super(context);

  }



  @Override

  protected void onLoad({

    pinks new ArrayList<Pink>();

    golds new ArrayList<Gold>();

    Random random new Random();
    Resources res getResources();
    Bitmap bitmapPink BitmapFactory.decodeResource(resR.drawable.pink);
    Bitmap bitmapGold BitmapFactory.decodeResource(resR.drawable.gold_head);
    int limitPinkX getWidth()-bitmapPink.getWidth()/6, // Define os limites da tela
      limitPinkY getHeight()-bitmapPink.getHeight()/5, // que os pinks e golds podem
      limitGoldX getWidth()-bitmapGold.getWidth()/8, // aparecer inicialmente.
      limitGoldY getHeight()-bitmapGold.getHeight();
    for(int 010i++)
      pinks.add(new Pink(random.nextInt(limitPinkX)random.nextInt(limitPinkY)randombitmapPink56));
    for(int 010i++)
      golds.add(new Gold(random.nextInt(limitGoldX)random.nextInt(limitGoldY)randombitmapGold18));
    mSprites.addAll(pinks); // Adiciona Pinks e Golds a lista de Sprites para serem 
    mSprites.addAll(golds); // desenhados e atualizados automaticamente.
  }
  @Override
  public void update({
    super.update();
    for(Pink pink pinks) // Para cada Pink, verifica se ele chegou nas bordas da tela e
      if(pink.0) // muda de direção caso ocorra.
        pink.changeDirection((byte(pink.direction 4));
      else if(pink.getWidth()-pink.width)
        pink.changeDirection((byte(pink.direction));
      else if(pink.0)
        pink.changeDirection((byte((12 pink.direction8));
      else if(pink.getHeight()-pink.height)
        pink.changeDirection((byte((pink.direction+18));
    for(Gold gold golds) // Para cada Gold, verifica se ele chegou nas bordas da tela e 
      if(gold.|| gold.getWidth()-gold.width) // muda de direção caso ocorra. 
        gold.speedX *= -1;
      else if(gold.|| gold.getHeight()-gold.height)
        gold.speedY *= -1;
  }
}


Se tudo estiver certo, dará erro apenas acusando a falta de R.drawable.pink e R.drawable.gold_head. Basta adicionar essas duas imagens na sua pasta res/drawable-hdpi:
gold_head.png
    
pink.png
Para testar o projeto, basta ir na sua Activity principal e mudar o setContentView para:

setContentView(new MainGameView(this));

Adicionalmente, se quiser pode deletar o res/layout/main.xml pois ele não será usado. Se tudo der certo você poderá executar e ver seus Pinks e Golds rodando na tela de maneira satisfatória, como na imagem:
Sprites andando

Agora vamos então colocar um background. Uma solução bem simples é pegar uma imagem grande e desenhar no fundo da tela, mas ai você pode ter problema por o tamanho da tela não ser fixo, e sempre terá o mesmo background. Então escolhi usar uma imagem pequena, com 4 tiles, e construir o background dinamicamente aleatoriamente usando o tamanho da tela para isso.

background.png
E imagem a ser utilizada é essa:




E a minha classe Background é essa:

package com.tutoriandroid.games.smash;

import java.util.Random;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;

import com.gdacarv.engine.androidgame.Sprite;

public class Background extends Sprite {
  
  private Bitmap mBitmap// O Bitmap final (background montado).

  public Background(Random randomint stageWidthint stageHeigthBitmap bmp{
    super(bmp);
    mBitmap Bitmap.createBitmap(stageWidthstageHeigthBitmap.Config.ARGB_8888);
    Canvas canvas new Canvas(mBitmap)// Usa-se um canvas para desenhar no Bitmap.
    
    Rect source new Rect(003232)// Preenche o fundo todo com o primeiro tile
    Rect destiny new Rect();
    for(int 0canvas.getWidth()+= 32){
      destiny.right (destiny.left i32;
      for(int 0canvas.getHeight()+= 32){
        destiny.bottom (destiny.top j32;
        canvas.drawBitmap(bmpsourcedestinynull);
      }
    }
    
    int tilesX stageWidth/32 1// Preenche alguns espaços aleatórios com grama alta.
      tilesY stageHeigth/32 1,
      qts;
    qts (int(random.nextInt((int(tilesX*tilesY*0.3f))+tilesX*tilesY*0.1f);
    source.right (source.left 3232;
    for(int 0qtsi++){
      destiny.right (destiny.left random.nextInt(tilesX)*3232;
      destiny.bottom (destiny.top random.nextInt(tilesY)*3232;
      canvas.drawBitmap(bmpsourcedestinynull);
    }
    
    qts (int(random.nextInt((int(tilesX*tilesY*0.1f))+tilesX*tilesY*0.05f);
    source.right (source.left 6432;// Preenche alguns espaços aleatórios com plantas.
    for(int 0qtsi++){
      destiny.right (destiny.left random.nextInt(tilesX)*3232;
      destiny.bottom (destiny.top random.nextInt(tilesY)*3232;
      canvas.drawBitmap(bmpsourcedestinynull);
    }  
    
    qts (int(random.nextInt((int(tilesX*tilesY*0.1f))+tilesX*tilesY*0.05f);
    source.right (source.left 9632;// Preenche alguns espaços aleatórios com flores.
    for(int 0qtsi++){
      destiny.right (destiny.left random.nextInt(tilesX)*3232;
      destiny.bottom (destiny.top random.nextInt(tilesY)*3232;
      canvas.drawBitmap(bmpsourcedestinynull);
    }
  }
  
  @Override
  public void onDraw(Canvas canvas{
    canvas.drawBitmap(mBitmap00null)// Substitui o onDraw original para desenhar o Bitmap montado.
  }
}

Não explicarei alguns detalhes pois não acho importante para o contexto, mas se alguém tiver alguma dúvida basta entrar em contato, deixar um comentário, que eu respondo com maior prazer.

Vamos fazer umas pequenas modificações no MainGameView para mostrar o background. Primeiro adicione a variável ao objeto e ficará assim:

... protected ArrayList<Pink> pinks;
protected ArrayList<Gold> golds;
protected Background background; ...


Depois temos que instanciar o Background e coloca-lo na lista de Sprites:

... for(int i = 0; i < 10; i++)
      golds.add(new Gold(random.nextInt(limitGoldX), random.nextInt(limitGoldY), random, bitmapGold, 1, 8));
background = new Background(random, getWidth(), getHeight(), BitmapFactory.decodeResource(res, R.drawable.background));
mSprites.add(background);
mSprites.addAll(pinks);
mSprites.addAll(golds); ...


Fique atento para a ordem a qual se adiciona os Sprites na lista de Sprites pois isso define quem será desenhado primeiro. No nosso caso, primeiro o background, depois os Pinks, e por ultimo os Golds (gods devem ser desenhados em cima dos pinks para atrapalhar o jogador de clickar nos pinks).

Pronto, agora o jogo está como a primeira imagem, com o background bonitinho (designers podem discordar) e os personagens andando pela tela.

Nos próximos tutoriais faremos o jogo responder ao toque na tela, e adicionaremos pontos, levels e menus.

Parte 2 >>

Até mais.

37 comentários:

Leonardo at 25 de abril de 2012 02:06 disse...

O melhor tutorial que já vi na net!

Queria saber sobre o Adobe AIR for android e iOS, será se compensa mais?

Gustavo Carvalho at 25 de abril de 2012 06:46 disse...

Talvez se seu jogo for bastante complexo, você queira desenvolver para ambas as plataformas, e não quer aprender exatamente como funciona em cada uma, então compensa.

Willian Desing at 19 de junho de 2012 18:55 disse...

Tem como eu fazer a Sprite com imagens em GIF ?

Gustavo Carvalho at 19 de junho de 2012 21:21 disse...

Não do jeito que foi explicado e implementado. Até tem como usar gif no Android, mas não é nem um pouco fácil.

Willian Desing at 20 de junho de 2012 11:08 disse...

Segui todo o tutorial, deu alguns erros estranhos, dai eu consegui arruma tipo no Pink, eu acrescentei um @SuppressWarnings("unused") em cima do public class Pink extends Sprite {
fiz isso em outros tbm, dai não deu mais erro, só que quando eu vou testa o jogo ele abre normalmente, e quando eu toco na tela pra jogar, monstra os Golds rodando na tela, e quando eu vou clicar em algun, dá um erro ! e o jogo fecha, tirei algumas prints olha:
http://i49.tinypic.com/1zdvvcg.png
http://i49.tinypic.com/14w3y8g.png
http://i45.tinypic.com/6nvz92.png
http://i49.tinypic.com/200fqzk.png

repare que eu troquei todas as Variáveis de Pink pink pinks e Gold gold golds para Zumbi zumbi zumbis e Humano humano humanos.

Gustavo Carvalho at 20 de junho de 2012 11:12 disse...

Pelo que vi o erro ocorre no onDraw. Verifique se você não está tentando desenhar algo nulo. Mais do que isso só posso afirmar se você me passar o código fonte.

Willian Desing at 20 de junho de 2012 12:31 disse...
Este comentário foi removido pelo autor.
Willian Desing at 24 de junho de 2012 21:00 disse...

Iae conseguiu identificar algum erro ?

Gustavo Carvalho at 26 de junho de 2012 18:47 disse...

Ainda não identifiquei o erro. Se você me mandar zipado o código fonte completo poderei testar aqui. gdacarv@gmail.com

Natan F. at 22 de julho de 2012 15:02 disse...

Ola amigo gostei muito do seu trabalho, sou muito novo em programaçao pra jogos e(pra vc ver meu nivel) nao consegui identificar o software que usa pra programar pode me dar algumas informaçoes basicas pra começar??

Gustavo Carvalho at 22 de julho de 2012 18:41 disse...

Olá Natan, tudo sobre isso pode ser encontrado aqui: http://tutoriandroid.blogspot.com.br/2012/02/configurando-o-ambiente-de.html

Waldeck at 3 de agosto de 2012 13:53 disse...

Rapaz, que tutorial interessante... Estou desenvolvendo um jogo de plataforma e o seu tutorial me ajudou muito a enteder conceitos básicos!!! E com certeza te pertubarei mais! rsrsrs Valeu !!!

Gustavo Carvalho at 7 de agosto de 2012 05:33 disse...

Que bom que está sendo útil! =]

Eduardo Vitor at 17 de setembro de 2012 22:40 disse...

Valeu me ajudou bastante vc poderia depois ensinar a fazer jogos 3D mais completo como:FRONTILLINE COMAND e BLOOD & GLORY

Gustavo Carvalho at 19 de setembro de 2012 13:47 disse...

@Eduardo: Que bom que foi útil! Agora, para fazer jogos que nem os citados estude bastaaaaante e use uma engine (www.unity3d.com)

Unknown at 21 de novembro de 2012 11:07 disse...

Ótimo post, o ADT tem limite de imagens que podem ser adicionadas no resouces? Porque quando eu adiciono até umas 6 imagens para usar no game fica tudo bem. quando adiciono mais de seis (independente c é selecionada de uma vez ou tudo junto) o ADT não reconhece as imagens. Isso impossibilita de usa-las no game, pois dá erro

victor89 at 22 de novembro de 2012 08:40 disse...

Dae cara, gostei do tutorial bem legal.
Tentei fazer ele aqui só que ta dando um erro. Abro ele normal e os personagem ficam caminhando, mas quando fecho o jogo ou a activity perde o foco da um NullPointerException no onDraw da GameView... sabe o que pode ser?
Abraço.

Gustavo Carvalho at 23 de novembro de 2012 13:54 disse...

Unknown: Que eu saiba não existe esse limite, e com certeza não seria de 6 imagens. Talvez se suas imagens for muiiiito grande podem estar estourando a memória. Talvez seja algum problema no formato das imagens ou algo assim. Cheque se os aquivos de imagem não contém espaços ou outros caracteres proibidos no nome.

@Victor89: Aparentemente é algum problema na Game Engine, mas agora não sei exatamente qual o problema. Se quiser investigar: http://code.google.com/p/gdacarv-android-game-engine/

Hilter Frazão at 19 de fevereiro de 2013 23:51 disse...

Quero muito aprender a fazer games, mas sou completamente novo nesse mundo. O que devo estudar?
que linguagens? onde as estudo? Por onde começar? por favor me ajude.

Gustavo Carvalho at 21 de fevereiro de 2013 15:00 disse...

@Hilter: Comece estudando lógica de programação e game design. Depois você vai se especializando, estuda uma linguagem de programação utilizada em jogos como C++, pearl ou, no caso de Android, Java. Depois estude a plataforma ou engine que irá desenvolver, Android, Unity, Unreal...

Tudo isso você acha na Internet, mas pode procurar também livros, cursos, academia...

Luis Henrique at 18 de março de 2013 00:27 disse...

Gustavo, me tira uma dúvida, por favor!
O bitmap passado por parametro para a Classe "Gold" (filha de Sprites) é apenas uma referencia/ponteiro para um bitmap pré carregado, correto?
Ele não vai alocar o espaço do bitmap todo para cada objeto Gold que criar, certo?

Valeu ! Parabéns pelo tutorial, tá show de bola. Venho seguindo desde a Engine !

Gustavo Carvalho at 19 de março de 2013 11:32 disse...

@Luis: Correto, a instancia do objeto é a mesma, logo só um bitmap é carregado.

Thiago at 24 de março de 2013 02:30 disse...

Tenho uma duvida: tenho uma Classe que é uma View que criei, que extende LinearLayout e é gerada a partir um modelo em um XML pelo getLayoutInflater().inflate();

Como exporto essa Classe como jar de biblioteca para que eu posso importar essa class e utilizar essa View em outros projetos?
Obg

Anônimo at 24 de março de 2013 20:34 disse...

oq é "ECLIPSE" ???
AJUDA ???

Renata Lumi Shin-Ike at 16 de maio de 2013 00:27 disse...

Oioi! Obrigada pelo tutorial! Bem fácil de entender!

Tenho uma dúvida... rodei essa primeira parte do jogo no meu celular e em outros, e notei que para os celulares com resolução 320x480, a imagem de background fica errada e não consigo resolver isto! Vc poderia me ajudar?

Segue a imagem de como fica o background:
(só troquei os golds por kirbys azuis haha)
http://sphotos-c.ak.fbcdn.net/hphotos-ak-prn2/970266_500306836684406_1479162916_n.jpg

Michael Robles at 6 de junho de 2013 14:36 disse...

Ao cara que perguntou o que é eclipse sugiro que estude um pouco mais antes de tentar desenvolver esse tutoria.
Você pode até conseguir, mas se não sabe nem ao menos o que é eclipse é muito provável que não compreenda nada dos comandos que serão usados.
Eclipse é o ambiente de desenvolvimento que ele utiliza para a plataforma android!
http://www.eclipse.org/

Anônimo at 9 de julho de 2013 15:45 disse...

Um eclipse é um evento astronômico que acontece quando um objeto celeste se move para a sombra de outro. O termo é derivado do termo grego antigo ἔκλειψις (ékleipsis), do verbo ἐκλείπω (ekleípō), "deixar para trás", uma combinação do prefixo ἐκ- (ek-), das preposições ἐκ, ἐξ (ek, ex), "fora", e o verbo λείπω (leípō), "deixar" .1 Quando acontece um eclipse dentro de um sistema estelar, como o Sistema Solar, ele forma um tipo de sizígia, o alinhamento de três ou mais corpos celestes do mesmo sistema gravitacional em uma linha reta .2
O termo eclipse é usado com mais frequência para descrever um eclipse solar, quando a sombra da Lua cruza a superfície da Terra, ou um eclipse lunar, quando a Lua se move na sombra da Terra. Entretanto, ele pode se referir a eventos além do sistema Terra-Lua: por exemplo, um planeta entrando na sombra de uma de suas luas, uma lua entrando na sombra do planeta que orbita, ou uma lua cruzando a sombra de outra lua. Um sistema estelar binário também pode produzir eclipses se o plano de suas órbitas intersecta a posição do observador.

ronaldos22ss2 at 22 de agosto de 2013 00:13 disse...

Alguém sabe me dizer o por que desse erro "Cannot make a static reference to the non-static field Pink.x" isto esta ocorrendo nos comando Pink, pink, Gold, gold, já adicionei as imagens na pasta certa por que continua o erro

Anônimo at 20 de outubro de 2013 15:08 disse...

...

Anônimo at 10 de janeiro de 2014 10:22 disse...

cara, posta mais imagens de onde ficam os arquivos ae

luiz fonseca at 15 de janeiro de 2014 10:17 disse...

O meu nao esta achando a figura Bitmap bitmapPink = BitmapFactory.decodeResource(res, R.drawable.pink);
Bitmap bitmapGold = BitmapFactory.decodeResource(res, R.drawable.gold_head);

e esta na pasta drawable que foi pedido pra colocar

Anônimo at 6 de fevereiro de 2014 10:02 disse...

Alguem pode me ajudar estou fazendo um jogo diferente desse estou tentando colocar um progress bar em uma surface View e nao estou conseguindo me mande um email: wesleybezerra7@hotmail.com

Tiago Cantalice at 3 de maio de 2014 13:07 disse...

Olá, estou fazendo o tutorial.
Mas aconteceu um erro no fundo.
Segue em anexo a imagem no jogo
http://www.casimages.com.br/i/140503060704304447.png.html
Alguém pode me dizer como conserto isso?

Luciano Fronza at 18 de maio de 2014 22:41 disse...

Olá, muito bom o tutorial.
Tentei fazer um exemplo aqui. Abro ele normal, consigo movimentar a imagem do fundo através de toque em tela, mas quando fecho o jogo ou a activity perde o foco da um NullPointerException no onDraw da GameView... sabe o que pode ser?
Abraço.

Anônimo at 29 de maio de 2014 23:37 disse...

Olá Gustavo, peguei todo o código e colei exatamente como está e utilizei a ultima versão do Eclipse com ADT e emulei com AVD usando as configurações para android <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="18" / >. Ocorre os seguintes erros: 1- cada gold é gerado no canto superior - esquerdo da tela, mesmo quando passa de uma fase para outra ele adicina um novo gold na mesma posição. O background é gerado como se fossem faixas horizontais de 16px e deixando um espaço de 16px entre uma linha e outra. Teria alguma relação com as versões do android?

Anônimo at 8 de julho de 2014 12:15 disse...

Cara aqui não está funcinando não, aparece Unfortunately, Smash has Stopped.
Oq pode ser??

Anônimo at 8 de julho de 2014 12:20 disse...

O jogo funciona com o emulador do android do eclipse no windons com o mouse, ou apenas no celular com touch screen?

Postar um comentário

 
© 2011 Tutoriandroid | Recode by Ardhiansyam | Based on Android Developers Blog