Desenvolvendo jogos para Android.docx

30
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 .

Transcript of Desenvolvendo jogos para Android.docx

Page 1: Desenvolvendo jogos para Android.docx

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:

Page 2: Desenvolvendo jogos para Android.docx

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 speedX, speedY;

  public Gold(int x, int y, Random random, Bitmap bmp, int bmp_rows, int bmp_columns) {    super(bmp, bmp_rows, bmp_columns);    setAnimation(ANIM_GO); // Seta a animação apenas como "ida" (ciclíca).    speedX = random.nextInt(7) - 3; //Velocidade horizontal de -3 a 3.    speedY = random.nextInt(7) - 3; //Velocidade vertical de -3 a 3.    this.x = x;    this.y = y;  }

  @Override  public void update() { // Anda.    super.update();    x += speedX;    y += 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:

Page 3: Desenvolvendo jogos para Android.docx

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 = false, dead = false;  protected byte animationSpeedControl = 0;

  public Pink(int x, int y, Random random, Bitmap bmp, int bmp_rows, int bmp_columns) {    super(bmp, bmp_rows, bmp_columns);    direction = (byte) random.nextInt(8); // Direção aleatória.    int frame = 1 + direction*3; // Seta o frame inicial para a direção    setAnimation(frame, frame, frame+3, ANIM_GOBACK);    this.x = x;    this.y = y;  }

  @Override  public void update() {    animationSpeedControl++; // Esta versão do Android Game Engine não    if(animationSpeedControl >= 6 - 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.      x += direction % 4 > 0 ? direction > 4 ? -speed : speed : 0;      y += Math.abs((direction-2) % 4) > 0 ? direction > 6 || direction < 2 ? 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 = 1 + direction*3;    setAnimation(frame, frame, frame+3, ANIM_GOBACK);  }    public void kill(){ // Função que será chamada quando o jogador clicar no Pink.    dying = true;    setAnimation(0, 0, 1, ANIM_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:

Page 4: Desenvolvendo jogos para Android.docx

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 = 0, score = 0;

 

  protected ArrayList<Pink> pinks; //Lista dos Pinks.  protected ArrayList<Gold> golds; //Lista dos Golds.

Page 5: Desenvolvendo jogos para Android.docx

  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(res, R.drawable.pink);    Bitmap bitmapGold = BitmapFactory.decodeResource(res, R.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 i = 0; i < 10; i++)      pinks.add(new Pink(random.nextInt(limitPinkX), random.nextInt(limitPinkY), random, bitmapPink, 5, 6));    for(int i = 0; i < 10; i++)      golds.add(new Gold(random.nextInt(limitGoldX), random.nextInt(limitGoldY), random, bitmapGold, 1, 8));    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.x < 0) // muda de direção caso ocorra.        pink.changeDirection((byte) (4 - pink.direction % 4));      else if(pink.x > getWidth()-pink.width)        pink.changeDirection((byte) (8 - pink.direction));      else if(pink.y < 0)        pink.changeDirection((byte) ((12 - pink.direction) % 8));      else if(pink.y > getHeight()-pink.height)        pink.changeDirection((byte) (5 - (pink.direction+1) % 8));    for(Gold gold : golds) // Para cada Gold, verifica se ele chegou nas bordas da tela e       if(gold.x < 0 || gold.x > getWidth()-gold.width) // muda de direção caso ocorra.         gold.speedX *= -1;      else if(gold.y < 0 || gold.y > 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

Page 6: Desenvolvendo jogos para Android.docx

    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.pngE 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;

Page 7: Desenvolvendo jogos para Android.docx

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 random, int stageWidth, int stageHeigth, Bitmap bmp) {    super(bmp);    mBitmap = Bitmap.createBitmap(stageWidth, stageHeigth, Bitmap.Config.ARGB_8888);    Canvas canvas = new Canvas(mBitmap); // Usa-se um canvas para desenhar no Bitmap.        Rect source = new Rect(0, 0, 32, 32); // Preenche o fundo todo com o primeiro tile    Rect destiny = new Rect();    for(int i = 0; i < canvas.getWidth(); i += 32){      destiny.right = (destiny.left = i) + 32;      for(int j = 0; j < canvas.getHeight(); j += 32){        destiny.bottom = (destiny.top = j) + 32;        canvas.drawBitmap(bmp, source, destiny, null);      }    }        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 = 32) + 32;    for(int i = 0; i < qts; i++){      destiny.right = (destiny.left = random.nextInt(tilesX)*32) + 32;      destiny.bottom = (destiny.top = random.nextInt(tilesY)*32) + 32;      canvas.drawBitmap(bmp, source, destiny, null);    }        qts = (int) (random.nextInt((int) (tilesX*tilesY*0.1f))+tilesX*tilesY*0.05f);    source.right = (source.left = 64) + 32;// Preenche alguns espaços aleatórios com plantas.    for(int i = 0; i < qts; i++){      destiny.right = (destiny.left = random.nextInt(tilesX)*32) + 32;      destiny.bottom = (destiny.top = random.nextInt(tilesY)*32) + 32;      canvas.drawBitmap(bmp, source, destiny, null);    }          qts = (int) (random.nextInt((int) (tilesX*tilesY*0.1f))+tilesX*tilesY*0.05f);    source.right = (source.left = 96) + 32;// Preenche alguns espaços aleatórios com flores.    for(int i = 0; i < qts; i++){      destiny.right = (destiny.left = random.nextInt(tilesX)*32) + 32;      destiny.bottom = (destiny.top = random.nextInt(tilesY)*32) + 32;      canvas.drawBitmap(bmp, source, destiny, null);    }  }    @Override  public void onDraw(Canvas canvas) {    canvas.drawBitmap(mBitmap, 0, 0, null); // 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.

Page 8: Desenvolvendo jogos para Android.docx

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(resmSprites.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 >>

Desenvolvendo jogos para Android - Parte 2 - Interação

Page 9: Desenvolvendo jogos para Android.docx

Continuando a série de desenvolvimento de jogos para Android, vamos completar nosso jogo e deixa-lo jogável. Vamos criar as telas iniciais e de game over, criar a interação e computar scores e levels.

Veja a primeira parte aqui.

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

Vamos criar a tela inicial (que é a da foto). Criaremos uma TitleActivity e apenas setamos o contentView dela para um new TitleGameView(this) que iremos criar em seguida:

package com.tutoriandroid.games.smash;

import android.app.Activity;import android.os.Bundle;

public class TitleActivity extends Activity {    /** Called when the activity is first created. */    @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(new TitleGameView(this));    }}

 Temos que adicionar uma referência a ele no AndroidManifest, como toda Activity, e seta-la como Activity inicial da aplicação:

<?xml version="1.0" encoding="utf-8"?><manifest xmlns:android="http://schemas.android.com/apk/res/android"

Page 10: Desenvolvendo jogos para Android.docx

    package="com.tutoriandroid.games.smash"    android:versionCode="1"    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="7" />

    <application        android:icon="@drawable/ic_launcher"        android:label="@string/app_name" >        <activity            android:name=".TitleActivity"            android:label="@string/app_name" >            <intent-filter>                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />            </intent-filter>        </activity>        <activity android:name=".MainGameActivity"/>    </application>

</manifest>

E vamos criar o TitleGameView, que ira executar tudo da tela inicial:

package com.tutoriandroid.games.smash;

import android.app.Activity;import android.content.Context;import android.content.Intent;import android.content.res.Resources;import android.graphics.BitmapFactory;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.view.MotionEvent;

import com.gdacarv.engine.androidgame.GameView;import com.gdacarv.engine.androidgame.Sprite;

public class TitleGameView extends GameView {    private Paint paintText;  private Context context;

  public TitleGameView(Context context) {    super(context);    this.context = context;  }

  @Override  public void TouchEvents(MotionEvent event) {    if((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN){      Context ctx = getContext();      ((Activity) ctx).finish();      Intent intent = new Intent(ctx, MainGameActivity.class); //Inicia o jogo      ctx.startActivity(intent);    }  }

  @Override  protected void onLoad() {    Resources res = getResources();    Sprite title;     mSprites.add(title = new Sprite(BitmapFactory.decodeResource(res, R.drawable.ti

Page 11: Desenvolvendo jogos para Android.docx

tle)));    title.x = getWidth()/2 - title.width/2;    title.y = 20;    paintText = new Paint();    paintText.setColor(Color.WHITE);    paintText.setTextSize(25);  }

  @Override  protected void onDraw(Canvas canvas) {    super.onDraw(canvas);    canvas.drawText(context.getString(R.string.iniciar_jogo), 50, getHeight()*0.6f, paintText);  }}

O que fazemos aqui é apenas desenhar uma sprite e uma mensagem na tela, então temos que adiciona-los aos recursos. Adicione a seguinte imagem em res/drawable-hdpi/:

title.pngE em res/values/strings.xml adicione:

<string name="iniciar_jogo">Toque na tela para iniciar o jogo</string>

Pronto. Agora nosso jogo tem uma tela inicial bem simples. Vamos adicionar a tela de Game Over. O processo é análogo: crie uma GameOverActivity setando o contentView para um new GameOverView(this), adicione a referência ao AndroidManifest(sem a parte de configurar como Activity principal):

<activity android:name=".GameOverActivity"/>

 E crie o GameOverView:

package com.tutoriandroid.games.smash;

import android.app.Activity;import android.content.Context;import android.content.Intent;import android.content.res.Resources;

Page 12: Desenvolvendo jogos para Android.docx

import android.graphics.BitmapFactory;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.view.MotionEvent;

import com.gdacarv.engine.androidgame.GameView;import com.gdacarv.engine.androidgame.Sprite;

public class GameOverView extends GameView {

  private Paint paintText;  private Context context;  private int score;

  public GameOverView(Context context) {    super(context);    this.context = context;  }

  @Override  public void TouchEvents(MotionEvent event) {    if((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN){      Context ctx = getContext();      ((Activity) ctx).finish();      Intent intent = new Intent(ctx, MainGameActivity.class); //Inicia novamente o jogo      ctx.startActivity(intent);    }  }

  @Override  protected void onLoad() {    Resources res = getResources();    Sprite gameover;     mSprites.add(gameover = new Sprite(BitmapFactory.decodeResource(res, R.drawable.gameover)));    gameover.x = getWidth()/2 - gameover.width/2;    gameover.y = (int) (getHeight()*0.2f);    paintText = new Paint();    paintText.setColor(Color.WHITE);    paintText.setTextSize(25);    score = ((Activity) context).getIntent().getIntExtra("SCORE", 0); //Carrega o score passado pela Intent  }

  @Override  protected void onDraw(Canvas canvas) {    super.onDraw(canvas);    canvas.drawText(context.getString(R.string.score) + " " + score, 50, getHeight()*0.6f, paintText);    canvas.drawText(context.getString(R.string.iniciar_jogo), 50, getHeight()*0.8f, paintText);  }

}

Novamente essa tela apenas exibe uma imagem, o score (que é passado do MainGame pela intent) e uma mensagem. Adicione a seguinte imagem em res/drawable-hdpi/:

gameover.pngE a seguinte string em res/values/strings.xml:

Page 13: Desenvolvendo jogos para Android.docx

<string name="score">Score:</string>

Vamos então modificar nosso MainGameView para responder ao toque, passar de nível e dar Game Over. Primeiro vamos modificar e acrescentar variáveis:

public class MainGameView extends GameView {                protected int level = 1, score = 0; // Mudar level inicial para 1                protected ArrayList<Pink> pinks;        protected ArrayList<Gold> golds;        protected Background background;        private long startTime; // Tempo de inicio do jogo, usado no score        private Context context; // Salvar referência do contexto        protected Sprite nextLevelSprite; // Sprite que mostra imagem NextLevel

        private Paint paintText;        float scoreX, scoreY; // Posição da mensagem do score                private int alivePinks; // Quantidade de pinks vivos

        public MainGameView(Context context) {                super(context);                this.context = context;                        }

Feito isso podemos criar os eventos de toque:

  @Override  public void TouchEvents(MotionEvent event) {    if((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN){      if(alivePinks > 0){ // Se tiver pinks vivos, verifica se clicou em pink ou gold        float x = event.getX(), y = event.getY();        for(Gold gold : golds) // Para cada gold...          if(x > gold.x && y > gold.y && x < gold.x + gold.width && y < gold.y + gold.height){ // Verifica de clicou nele...            ((Activity) context).finish(); // Se clicou termina a activity...            Intent intent = new Intent(context, GameOverActivity.class); // Chama o GameOverActivity...            intent.putExtra("SCORE", score); // Passa o score como parâmetro            context.startActivity(intent);          }        for(Pink pink : pinks) // Para cada pink...          if(!pink.isDead() && x > pink.x && y > pink.y && x < pink.x + pink.width && y < pink.y + pink.height){ // Se o pink estiver vivo e tocar nele...            pink.kill(); // Mata o pink...            int add;            score += add = (int) Math.max(100 - level*3 - (System.currentTimeMillis() - startTime)/500, 1); // Adiciona o score dependendo de qual rapido matou o pink...            alivePinks--; // Subtrai um do contador de pinks vivos...            if(alivePinks == 0) // Se não tem mais nenhum vivo...              nextLevelSprite.visible = true; // Exibe imagem de proximo nível            Log.d("Score", "Valeu: " + add);          }      }else{ // Sem pink vivo, a mensagens de proximo level já está exibida e tocar significa passar de level        newStage();      }    }

  }

Page 14: Desenvolvendo jogos para Android.docx

Perceba que precisamos usar a função pink.isDead(), que não fizemos anteriormente. Então vamos adiciona-la agora na classe Pink:

public boolean isDead() {      return dying || dead;}

Ela apenas retorna de o Pink esta morto ou morrendo. Outra mudança é a criação do método newStage() que cria novos Pinks e um novo Gold para o jogo. Com isso, vamos alterar o onLoad para que ele não crie os Pinks (deixe isso para o newStage) e carregue a imagem do próximo level e o texto que mostra o score:

 @Override  protected void onLoad() {    pinks = new ArrayList<Pink>();    golds = new ArrayList<Gold>();    Random random = new Random();    Resources res = getResources();    Bitmap bitmapGold = BitmapFactory.decodeResource(res, R.drawable.gold_head);    int limitGoldX = getWidth()-bitmapGold.getWidth()/8,      limitGoldY = getHeight()-bitmapGold.getHeight();    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(golds);

    paintText = new Paint();    paintText.setColor(Color.WHITE);    paintText.setTextSize(25);    scoreX = getWidth()*0.05f;    scoreY = getHeight()*0.95f;        mSprites.add(nextLevelSprite = new Sprite(BitmapFactory.decodeResource(res, R.drawable.nextlevel)));    nextLevelSprite.x = getWidth()/2 - nextLevelSprite.width/2;    nextLevelSprite.y = (int) (getHeight()*0.2f);    nextLevelSprite.visible = false;        newStage();

  }

Adicione a seguinte imagem em res/drawable-hdpi/:

nextlevel.pngPrecisamos também alterar o update do MainGameView para que os Pinks mortos sejam limpados de nossas listas, e adicionar uma verificações para evitar bugs como clicar exatamente no momento de troca de direção:

@Override  public void update() {    super.update();    Pink pink;    for(int i = 0; i < pinks.size(); i++){      pink = pinks.get(i);      if(pink.dead){ // Se ta morto, retira das listas        mSprites.remove(pink);        pinks.remove(pink);

Page 15: Desenvolvendo jogos para Android.docx

        i--;      }else if(!pink.isDead()){        if(pink.x < 0 && pink.direction >= 5 && pink.direction <= 7)          pink.changeDirection((byte) (4 - pink.direction % 4));        else if(pink.x > getWidth()-pink.width && pink.direction >= 1 && pink.direction <= 3)          pink.changeDirection((byte) (8 - pink.direction));        else if(pink.y < 0 && pink.direction >= 3 && pink.direction <= 5)          pink.changeDirection((byte) ((12 - pink.direction) % 8));        else if(pink.y > getHeight()-pink.height && (pink.direction >= 7 || pink.direction <= 1))          pink.changeDirection((byte) (5 - (pink.direction+1) % 8));      }    }    for(Gold gold : golds)      if(gold.x < 0 || gold.x > getWidth()-gold.width)        gold.speedX *= -1;      else if(gold.y < 0 || gold.y > getHeight()-gold.height)        gold.speedY *= -1;

  }

Precisamos também modificar o onDraw para mostrar o score e a mensagem de próximo level quando preciso:

@Override  protected void onDraw(Canvas canvas) {    super.onDraw(canvas);    canvas.drawText(context.getString(R.string.score) + " " + score, scoreX, scoreY, paintText);    if(nextLevelSprite.visible)      canvas.drawText(context.getString(R.string.nextlevel_msg), 50, getHeight()*0.7f, paintText);

  }

Claro, precisamos adicionar uma string em res/values/strings.xml:

<string name="nextlevel_msg">Toque na tela para continuar o jogo</string>

E finalmente vamos criar nosso método newStage:

  private void newStage() {    level++; //Passa de level    nextLevelSprite.visible = false; //Deixa a mensagem e imagem de proximo level invisiveis    alivePinks = 5+level; //Define a quantidade de Pinks de acordo com o level    startTime = System.currentTimeMillis(); // Guarda o tempo do inicio do level        Random random = new Random();    Resources res = getResources();    Bitmap bitmapPink = BitmapFactory.decodeResource(res, R.drawable.pink);    Bitmap bitmapGold = BitmapFactory.decodeResource(res, R.drawable.gold_head);    Pink pink;    Gold gold;    int limitPinkX = getWidth()-bitmapPink.getWidth()/6,      limitPinkY = getHeight()-bitmapPink.getHeight()/5,      limitGoldX = getWidth()-bitmapGold.getWidth()/8,      limitGoldY = getHeight()-bitmapGold.getHeight();    for(int i = 0; i < alivePinks; i++){ // Cria Pinks      pinks.add(pink = new Pink(random.nextInt(limitPinkX), random.nextInt(limitPinkY), random, bitmapPink, 5, 6));      pink.speed = level; // Velocidade de acordo com o level    }    golds.add(gold = new Gold(random.nextInt(limitGoldX), random.nextInt(limitGoldY), random, bitmapGold, 1, 8)); // Cria mais um Gold    gold.speedX *= 1+level/3; // Gold criado pode ser mais rapido que o normal    gold.speedY *= 1+level/3;

Page 16: Desenvolvendo jogos para Android.docx

    mSprites.addAll(1, pinks); //Adiciona os Sprites criado se preocupado com a ordem da lista pois influencia na ordem de desenho    mSprites.add(mSprites.size()-1, gold);    

  }

Pronto. Agora nosso jogo já tem tela inicial, game over, levels, score e funciona bem. No próximo tutorial faremos o jogo salvar o high score, adicionaremos algumas animações e efeitos sonoros.

Desenvolvendo jogo para Android - Parte 3 - Sons e High Score >>

Desenvolvendo jogos para Android - Parte 3 - Sons e High Score

Neste tutorial faremos os últimos ajustes no nosso jogo antes de publica-lo no Google Play (irei mostrar como em outro tutorial). Iremos adicionar uma música ambiente, sons de interação com os Pinks e Golds, sons de vitória ao passar de nível e som de derrota ao dar Game Over. Também iremos salvar o High Score, ou seja, a maior pontuação em todo o jogo, assim se cria um desafio a ser passado toda vez que o jogador jogar o jogo.

<< Parte 1<< Parte 2

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

Primeiro vamos adicionar os sons e músicas. Baixe-os aqui, e extraia a pasta raw em res (todo aquivo de som deve ficar na pasta res/raw). Dê um refresh no seu projeto e você já poderá utilizar os sons como recursos. Para maiores informações sobre músicas e sons no Android acesse esse tutorial: Manipulando música e sons no Android

Page 17: Desenvolvendo jogos para Android.docx

Abra o MainGameView e adicione os seguintes atributos (em negrito) a classe:

...

        private int alivePinks;                public MediaPlayer musica;        public SoundPool sound;        private int soundIdPinkHit, soundIdGoldHit, soundIdWin;

        public MainGameView(Context context) {

...

Dado esses atributos precisamos instancia-los e carrega-los. No onLoad adicione isso:

...

              newStage();                                musica = MediaPlayer.create(context, R.raw.background_music);                musica.setLooping(true);                musica.start();                                sound = new SoundPool(4, AudioManager.STREAM_MUSIC, 0);                soundIdPinkHit = sound.load(context, R.raw.pink_hit, 1);                soundIdGoldHit = sound.load(context, R.raw.gold_hit, 1);                soundIdWin = sound.load(context, R.raw.win, 1);        }

...

Simples, não? Esse código apenas instancia (e manda executar no caso da música) e carrega os sons. Note que se a gente não mandar explicitamente a música parar ela ira executar "para sempre", mesmo após o aplicativo ser fechado. Para evitar isso vamos fazer uma mudança no nosso MainGameActivity:

package com.tutoriandroid.games.smash;

import android.app.Activity;import android.os.Bundle;

public class MainGameActivity extends Activity {                private MainGameView gameView;

        @Override    public void onCreate(Bundle savedInstanceState) {

Page 18: Desenvolvendo jogos para Android.docx

        super.onCreate(savedInstanceState);        setContentView(gameView = new MainGameView(this));    }                @Override        protected void onPause() {                super.onPause();                gameView.musica.stop();        }                @Override        protected void onDestroy() {                super.onDestroy();                gameView.sound.release();        }                @Override        protected void onResume() {                super.onResume();                if(gameView.musica != null)                        gameView.musica.start();        }}

Assim sempre que a aplicação for pausada a música será, e quando a activity for destruída a área de memória usada pelos sons deve ser liberada. Vamos fazer o sons serem tocados quando os eventos ocorrerem. Para isso vamos alterar nosso TouchEvents do MainGameView:

  @Override  public void TouchEvents(MotionEvent event) {    if((event.getAction() & MotionEvent.ACTION_MASK) == MotionEvent.ACTION_DOWN){      if(alivePinks > 0){        float x = event.getX(), y = event.getY();        for(Gold gold : golds)         if(x > gold.x && y > gold.y && x < gold.x + gold.width && y < gold.y + gold.height){            sound.play(soundIdGoldHit, 1f, 1f, 0, 0, 1f);            ((Activity) context).finish();            Intent intent = new Intent(context, GameOverActivity.class);            intent.putExtra("SCORE", score);            context.startActivity(intent);          }        for(Pink pink : pinks)          if(!pink.isDead() && x > pink.x && y > pink.y && x < pink.x + pink.width && y < pink.y + pink.height){            sound.play(soundIdPinkHit, 1f, 1f, 0, 0, 1f);            pink.kill();            int add;            score += add = (int) Math.max(100 - level*3 - (System.currentTimeMillis() - startTime)/500, 1);            alivePinks--;            if(alivePinks == 0){              nextLevelSprite.visible = true;              sound.play(soundIdWin, 1f, 1f, 0, 0, 1f);            }            Log.d("Score", "Valeu: " + add);          }      }else{        newStage();      }

Page 19: Desenvolvendo jogos para Android.docx

    }

  }

Agora toda vez que um Pink for morto ouvirá um som, quando acertar um Gold ouvirá outro e quando passar de nível ouvirá um som de incentivo. Vamos colocar um som de Game Over, no GameOverView em onLoad adicione o seguinte:

...

                score = ((Activity) context).getIntent().getIntExtra("SCORE", 0);                                MediaPlayer musica = MediaPlayer.create(context, R.raw.gameover);                musica.start();        }

...

Pronto. Aqui a gente não precisa se preocupar em fazer a música para pois ela é bastante curta e não possui loop, então eventualmente irá parar.

Agora iremos implementar o High Score, que é bastante simples. Para isso usaremos SharedPreferences, se quiser saber mais sobre acesse este tutorial: Criando uma tela de configurações usando SharedPreferences. Primeiramente queremos que o high score seja exibido na tela inicial, assim o jogador sabe o desafio que o aguarda. Então vamos alterar nosso TitleGameView para guardar um inteiro highScore, carrega-lo e exibi-lo na tela:

...int highScore; ...

        @Override        protected void onLoad() {                Resources res = getResources();                Sprite title;                 mSprites.add(title = new Sprite(BitmapFactory.decodeResource(res, R.drawable.title)));                title.x = getWidth()/2 - title.width/2;                title.y = 20;                paintText = new Paint();                paintText.setColor(Color.WHITE);                paintText.setTextSize(25);                                highScore = PreferenceManager.getDefaultSharedPreferences(context).getInt("HIGH_SCORE", 0);        }

        @Override        protected void onDraw(Canvas canvas) {                super.onDraw(canvas);                canvas.drawText(context.getString(R.string.iniciar_jogo), 50, getHeight()*0.6f, paintText);                canvas.drawText(context.getString(R.string.highscore)+" "+highScore, 40, getHeight()*0.8f, paintText);        }

...

Page 20: Desenvolvendo jogos para Android.docx

Com apenas uma linha de código recuperamos nosso high score e com outra linha desenhamos. Agora vamos para a GameOverView salvar (caso o score tenha sido maior) o novo high score e exibir o ultimo para o jogador:

...private int score, highScore; ...

        @Override        protected void onLoad() {                Resources res = getResources();                Sprite gameover;                 mSprites.add(gameover = new Sprite(BitmapFactory.decodeResource(res, R.drawable.gameover)));                gameover.x = getWidth()/2 - gameover.width/2;                gameover.y = (int) (getHeight()*0.2f);                paintText = new Paint();                paintText.setColor(Color.WHITE);                paintText.setTextSize(25);                score = ((Activity) context).getIntent().getIntExtra("SCORE", 0);                                MediaPlayer musica = MediaPlayer.create(context, R.raw.gameover);                musica.start();                                SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);                highScore = prefs.getInt("HIGH_SCORE", 0);                if(score > highScore)                        prefs.edit().putInt("HIGH_SCORE", score).commit();        }

        @Override        protected void onDraw(Canvas canvas) {                super.onDraw(canvas);                canvas.drawText(context.getString(R.string.score) + " " + score, 50, getHeight()*0.6f, paintText);                canvas.drawText(context.getString(R.string.iniciar_jogo), 50, getHeight()*0.82f, paintText);                canvas.drawText(context.getString(R.string.highscore)+" "+highScore, 50, getHeight()*0.68f, paintText);        }

...

Pronto. No onLoad simplesmente pegamos o high score, comparamos com o atual e se for maior salvamos. E mostramos o ultimo high score na tela.

Agora nosso jogo já está "completo". Claro que você pode melhora-lo em vários outros aspectos ainda, mas para fim de tutoriais  ele está finalizado. Irei utiliza-lo para mostrar como adicionar AdMob no seu aplicativo e como publicar no Google Play.

Até mais.