terça-feira, 10 de abril de 2012

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

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"
    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();
      ((Activityctx).finish();
      Intent intent new Intent(ctxMainGameActivity.class)//Inicia o jogo
      ctx.startActivity(intent);
    }
  }

  @Override
  protected void onLoad({
    Resources res getResources();
    Sprite title
    mSprites.add(title new Sprite(BitmapFactory.decodeResource(resR.drawable.title)));
    title.getWidth()/title.width/2;
    title.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)50getHeight()*0.6fpaintText);
  }
}

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.png
E 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;
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();
      ((Activityctx).finish();
      Intent intent new Intent(ctxMainGameActivity.class)//Inicia novamente o jogo
      ctx.startActivity(intent);
    }
  }

  @Override
  protected void onLoad({
    Resources res getResources();
    Sprite gameover
    mSprites.add(gameover new Sprite(BitmapFactory.decodeResource(resR.drawable.gameover)));
    gameover.getWidth()/gameover.width/2;
    gameover.(int(getHeight()*0.2f);
    paintText new Paint();
    paintText.setColor(Color.WHITE);
    paintText.setTextSize(25);
    score ((Activitycontext).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" " score50getHeight()*0.6fpaintText);
    canvas.drawText(context.getString(R.string.iniciar_jogo)50getHeight()*0.8fpaintText);
  }

}

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.png
E a seguinte string em res/values/strings.xml:

<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 event.getX()event.getY();
        for(Gold gold golds// Para cada gold...
          if(gold.&& gold.&& gold.gold.width && gold.gold.height)// Verifica de clicou nele...
            ((Activitycontext).finish()// Se clicou termina a activity...
            Intent intent new Intent(contextGameOverActivity.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(&& pink.&& pink.&& pink.pink.width && pink.pink.height)// Se o pink estiver vivo e tocar nele...
            pink.kill()// Mata o pink...
            int add;
            score += add (intMath.max(100 level*(System.currentTimeMillis(startTime)/5001)// 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();
      }
    }
  }

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(resR.drawable.gold_head);
    int limitGoldX getWidth()-bitmapGold.getWidth()/8,
      limitGoldY getHeight()-bitmapGold.getHeight();
    for(int 010i++)
      golds.add(new Gold(random.nextInt(limitGoldX)random.nextInt(limitGoldY)randombitmapGold18));
    background new Background(randomgetWidth()getHeight()BitmapFactory.decodeResource(resR.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(resR.drawable.nextlevel)));
    nextLevelSprite.getWidth()/nextLevelSprite.width/2;
    nextLevelSprite.(int(getHeight()*0.2f);
    nextLevelSprite.visible false;
    
    newStage();

  }

Adicione a seguinte imagem em res/drawable-hdpi/:
nextlevel.png
Precisamos 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 0pinks.size()i++){
      pink pinks.get(i);
      if(pink.dead){ // Se ta morto, retira das listas
        mSprites.remove(pink);
        pinks.remove(pink);
        i--;
      }else if(!pink.isDead()){
        if(pink.&& pink.direction >= && pink.direction <= 7)
          pink.changeDirection((byte(pink.direction 4));
        else if(pink.getWidth()-pink.width && pink.direction >= && pink.direction <= 3)
          pink.changeDirection((byte(pink.direction));
        else if(pink.&& pink.direction >= && pink.direction <= 5)
          pink.changeDirection((byte((12 pink.direction8));
        else if(pink.getHeight()-pink.height && (pink.direction >= || pink.direction <= 1))
          pink.changeDirection((byte((pink.direction+18));
      }
    }

    for(Gold gold golds)
      if(gold.|| gold.getWidth()-gold.width)
        gold.speedX *= -1;
      else if(gold.|| gold.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" " scorescoreXscoreYpaintText);
    if(nextLevelSprite.visible)
      canvas.drawText(context.getString(R.string.nextlevel_msg)50getHeight()*0.7fpaintText);
  }

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(resR.drawable.pink);
    Bitmap bitmapGold BitmapFactory.decodeResource(resR.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 0alivePinksi++)// Cria Pinks
      pinks.add(pink new Pink(random.nextInt(limitPinkX)random.nextInt(limitPinkY)randombitmapPink56));
      pink.speed level// Velocidade de acordo com o level
    }
    golds.add(gold new Gold(random.nextInt(limitGoldX)random.nextInt(limitGoldY)randombitmapGold18))// Cria mais um Gold
    gold.speedX *= 1+level/3// Gold criado pode ser mais rapido que o normal
    gold.speedY *= 1+level/3;
    mSprites.addAll(1pinks)//Adiciona os Sprites criado se preocupado com a ordem da lista pois influencia na ordem de desenho
    mSprites.add(mSprites.size()-1gold);
    
  }

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

Até mais.

5 comentários:

Diego - Aprendendo Ruby on rails at 16 de outubro de 2012 11:51 disse...

Tutorial é ótimo! só senti falta de mais detalhamento sobre algumas funções e onde certas coisas acontece. se possível poderia disponibilizar uma versão comentada.
Fiquei com dúvida sobre como posso alterar as imagens pq usar certa função e assim por diante!

E você pode indicar algum livro? e quais são suas fontes de estudos?

Muito obrigado pela tutorial.

Gustavo Carvalho at 18 de outubro de 2012 15:20 disse...

@Diego: Obrigado! Para alterar as imagens você tem que alterar o resource passado pro bitmap em:

Bitmap bitmapPink = BitmapFactory.decodeResource(res, R.drawable.pink);
  Bitmap bitmapGold = BitmapFactory.decodeResource(res, R.drawable.gold_head);

Esse trecho do código esta na parte um do tutorial.

Sobre o livro, eu acho esses bons: http://www.livroandroid.com.br/

Tenho os dois e são bons. O problema com livros é que a tecnologia Android evolui muito rápido então provavelmente você nunca terá um livro que cubra as ultimas versões por mais de 3 meses.

Fora isso, muito Google, Stackoverflow e a documentação oficial em http://d.android.com

Alisson Monteiro at 22 de junho de 2013 00:27 disse...

Gustavo muito bom saber q alguem programa p/ android. Acho muito fácil trabalhar com unity 3d e ate da a cara de um joguim só p/ teste.

Agora oque eu não conseguir foi os scripts de movimentos touch, de um personagem ex.: p/ frente ,p/ os lados e etc. VC poderia me ajudar?

E um conselho acho, q vc se daria bem com o unity, a licença Android está grátis agora.

Aguardo resposta vlw.abraço.

João gabriel at 16 de janeiro de 2015 09:20 disse...

Parabéns, o melhor tutorial que ja vi na net.
pratico e funcional, excelente.

Robson at 28 de agosto de 2015 08:33 disse...

Olá,

Excelente tutorial! Parabéns!
Estava usando seu material para estudos e terminei esta parte, mas trava quando tento jogá-lo (encerra a aplicação). Pode me ajudar por favor? Obrigado!

Postar um comentário

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