You are on page 1of 39

Desenvolvendo jogos para Android - Parte 1 Criando elementos grficos

Fonte: http://www.tutoriandroid.com/2012/05/como-criar-um-jogo-estilo-

plataforma.html

Jogo que iremos desenvolver: Smash!

Hoje comearemos com uma srie 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,

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 mnimo de tempo
possvel. A medida que se avana nas fases os personagens ficam mais rpidos.

Iremos utilizar como base para o desenvolvimento o nosso Android Game Engine que estamos
desenvolvendo aqui no blog. Iremos usar a 1 verso 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 eleaqui.

Acompanhe o desenvolvimento por aqui ou baixe o cdigo usado aqui ou o .apk aqui.

Ento vamos comear. Crie um novo projeto com as suas preferncias. 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 no apareceu no Package Explorer doeclipse 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 comear a codificao do jogo de fato. Vamos comear com o sprite do
personagem dourado, chamado de Gold, por ser bastante simples. Ele apenas vai ficar
rodando o cenrio, e no h alterao na animao. 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_column

s) {
super(bmp, bmp_rows, bmp_columns);
setAnimation(ANIM_GO); // Seta a animao apenas como "ida" (ciclca).
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 no? O construtor recebe como parmetros a posio (x, y), uma instncia para
gerar nmeros randmicos (poderia criar uma nova dentro do construtor, mas acho melhor
utilizar a mesma para todas as instncias), a imagem em Bitmap, e a quantidade de linhas e
colunas de frames dessa imagem.

Vamos ento partir para o sprite do personagem rosa, chamado de Pink, que um pouco mais
complexo por envolver diferentes animaes e tipo de movimentao:

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 movimentao/animao.


public byte direction = 0; // Direo da movimentao/animao.
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_column
s) {
super(bmp, bmp_rows, bmp_columns);
direction = (byte) random.nextInt(8); // Direo aleatria.
int frame = 1 + direction*3; // Seta o frame inicial para a direo
setAnimation(frame, frame, frame+3, ANIM_GOBACK);
this.x = x;
this.y = y;
}

@Override
public void update() {
animationSpeedControl++; // Esta verso do Android Game Engine no
if(animationSpeedControl >= 6 - speed){ // suporta mudana na
super.update(); // velocidade da animao, ento foi feito
animationSpeedControl = 0; // esse hack para diminuir a
} // velocidade, que estava muito rpida.

if(!dying){ // Se no est morrendo, anda na direo.


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 transparncia at ficar invisvel.
int alpha = mPaint.getAlpha();
if(alpha <= 25)
dead = true;
else
mPaint.setAlpha(alpha-10);

}
}

public void changeDirection(byte direct){ // Muda a direo e muda a animao de


acordo.
direction = direct;
int frame = 1 + direction*3;
setAnimation(frame, frame, frame+3, ANIM_GOBACK);
}

public void kill(){ // Funo 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 animao, pois tava muito
rpida. Diferente do Gold, esse sprite no usa speedX e speedY, ele usa um speed geral e
define sua direo entre as oito possveis. Cada valor de 0 a 7, que a varivel direction pode
assumir tem o seguinte significado:

Direes assumidas por Pink

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

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.

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), ra
ndom, bitmapPink, 5, 6));
for(int i = 0; i < 10; i++)
golds.add(new Gold(random.nextInt(limitGoldX), random.nextInt(limitGoldY), ra
ndom, 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 direo 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 direo 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

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 no ser usado. Se


tudo der certo voc poder executar e ver seus Pinks e Golds rodando na tela de maneira
satisfatria, como na imagem:

Sprites andando

Agora vamos ento colocar um background. Uma soluo bem simples pegar uma imagem
grande e desenhar no fundo da tela, mas ai voc pode ter problema por o tamanho da tela
no ser fixo, e sempre ter o mesmo background. Ento 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; //

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

espaos

com

aleatrios
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

espaos

com

aleatrios
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); //
desenhar

Substitui
Bitmap

onDraw

original

para

montado.

}
}

No explicarei alguns detalhes pois no acho importante para o contexto, mas se algum tiver
alguma dvida basta entrar em contato, deixar um comentrio, que eu respondo com maior
prazer.

Vamos

fazer

umas

pequenas

modificaes

no MainGameView para

mostrar

o background. Primeiro adicione a varivel 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 prximos tutoriais faremos o jogo responder ao toque na tela, e adicionaremos pontos,
levels e menus.

Desenvolvendo jogos para Android - Parte 2 Interao

Continuando a srie de desenvolvimento de jogos para Android, vamos completar nosso jogo
e deixa-lo jogvel. Vamos criar as telas iniciais e de game over, criar a interao e computar
scores e levels.

Veja a primeira parte aqui.

Acompanhe o desenvolvimento por aqui ou baixe o cdigo 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 referncia a ele no AndroidManifest, como toda Activity, e seta-la
como Activity inicial da aplicao:

<?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();
((Activity) ctx).finish();
Intent intent = new Intent(ctx, MainGameActivity.class); //Inicia

jogo

ctx.startActivity(intent);
}
}

@Override
protected void onLoad() {
Resources res = getResources();
Sprite title;
mSprites.add(title = new Sprite(BitmapFactory.decodeResource(res, R.drawable.ti
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, ento 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 anlogo: crie umaGameOverActivity setando o contentView para um new
GameOverView(this), adicione a referncia 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();
((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.png

E a seguinte string em res/values/strings.xml:

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

Vamos ento modificar nosso MainGameView para responder ao toque, passar de nvel e dar
Game Over. Primeiro vamos modificar e acrescentar variveis:

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 referncia do contexto
protected Sprite nextLevelSprite; // Sprite que mostra imagem NextLevel
private Paint paintText;
float scoreX, scoreY; // Posio 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 + go
ld.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 parmetro
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 no tem mais nenhum vivo...
nextLevelSprite.visible = true; // Exibe imagem de proximo nvel
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 funo pink.isDead(), que no fizemos anteriormente. Ento
vamos adiciona-la agora na classe Pink:

public boolean isDead() {


return dying || dead;
}

Ela apenas retorna de o Pink esta morto ou morrendo. Outra mudana a criao
do mtodo newStage() que cria novos Pinks e um novoGold para o jogo. Com isso, vamos
alterar o onLoad para que ele no crie os Pinks (deixe isso para o newStage) e carregue a
imagem do prximo 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), ra
ndom, bitmapGold, 1, 8));
background = new Background(random, getWidth(), getHeight(), BitmapFactory.deco
deResource(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.d


rawable.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.png

Precisamos tambm alterar o update do MainGameView para que os Pinks mortos sejam
limpados de nossas listas, e adicionar uma verificaes para evitar bugs como clicar
exatamente no momento de troca de direo:

@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);
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.direc
tion <= 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.di
rection <= 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 tambm modificar o onDraw para mostrar o score e a mensagem de prximo 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 mtodo 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(limitPin
kY), 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;
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 prximo tutorial faremos o jogo salvar o high score, adicionaremos algumas animaes e
efeitos sonoros.

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 msica ambiente, sons de
interao com os Pinks e Golds, sons de vitria ao passar de nvel e som de derrota ao dar
Game Over. Tambm iremos salvar o High Score, ou seja, a maior pontuao 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 cdigo usado aqui ou o


.apk aqui.

Primeiro

vamos

adicionar

os

sons

msicas.

Baixe-os aqui,

extraia

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 informaes

sobre msicas

sons

no

Android

acesse

tutorial: Manipulando msica e sons no Android

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:

...

esse

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, no? Esse cdigo apenas instancia (e manda executar no caso da msica) e carrega os
sons. Note que se a gente no mandar explicitamente a msica parar ela ira executar " para
sempre", mesmo aps o aplicativo ser fechado. Para evitar isso vamos fazer uma mudana 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) {
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 aplicao for pausada a msica ser, e quando a activity for destruda a
rea de memria 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();
}
}
}

Agora toda vez que um Pink for morto ouvir um som, quando acertar um Gold ouvir outro e
quando passar de nvel 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 no precisa se preocupar em fazer a msica para pois ela bastante
curta e no possui loop, ento 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 configuraes usando SharedPreferences. Primeiramente queremos que o high score
seja exibido na tela inicial, assim o jogador sabe o desafio que o aguarda. Ento 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);
}

...

Com apenas uma linha de cdigo 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 vrios 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.

Como criar um jogo estilo plataforma

Atendendo

pedidos

irei

explicar

aqui

base

de como

fazer um jogo

estilo

plataforma. Apesar de parecerem simples so um pouco mais complexo do que se pensa.


Neste tutorial irei explicar os conceitos mais bsicos, que so a gravidade, o pulo e a
movimentao sobre as plataformas.

Claro ainda h muitas outras coisas que compe esse tipo de jogo, como inimigos, itens,
movimentaes

realistas,

colises

com

paredes

outros

objetos,

movimentao

da cmera pelo cenrio, animao dos personagens, aes, entre muitos outros.

O projeto final voc pode baixar aqui.

Para comear vamos criar um projeto, o qual coloquei o nome de "Plataforma Game". Em
seguida crie uma pasta no seu projeto, junto com as pastas res, src, etc... E adicione
esse aquivo. Esse o jar da nossa Game Engine. Agora, pelo eclipse, clique com boto direito
nesse arquivo recm adicionado (se a pasta libs ainda no tiver aparecido d um F5 para
atualizar), v em Build Path -> Add to Build Path, similar a figura:

Game Engine adicionado ao projeto Smash

Pronto, agora nosso projeto j conta com a biblioteca de desenvolvimento de games para
Android criada por mim. Vamos comear o desenvolvimento do jogo em si. Primeiro vamos
criar o grfico que representa nosso heri. Geralmente utilizado uma sequncia em Sprite
para representar o heri e suas mais variadas animaes, mas aqui vamos apenas utilizar
uma forma oval azul. Crie uma pasta em res com o nome de drawable e insira o seguinte
arquivo XML:

<?xml version="1.0" encoding="utf-8"?>


<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<size android:width="30dp" android:height="50dp"/>
<solid android:color="#ff0000ff"/>
</shape>

Agora na nossa activity principal no precisamos carregar um layout por xml, vamos apenas
instanciar o nosso GameView:

public class PlataformaGameActivity extends Activity {


/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(new PlataformaGameView(this));
}
}

Acusar um erro pois PlataformaGameView no existe, ento vamos cria-lo:

public class PlataformaGameView extends GameView {

private static final float GRAVITY = 0.4f;


private static final float VELOCIDADE_ANDAR = 4;
private static final float ALTURA_PULO = 10;

protected Sprite mHero;


protected float heroVelocidadeVertical = 0;
protected boolean[][] esqueletoFase;
protected Paint chaoPaint;

public PlataformaGameView(Context context) {


super(context, true);
}
}

Ai est nossa classe com as contantes e atributos que iremos utilizar. Note que chamamos o
construtor pai

passando true como

parmetro

pois s

assim poderemos utilizar

mtodo TouchEvents que veremos mais a frente. Com a classe criada, vamos definir
o onLoad que ser executado automaticamente para fazer os carregamentos necessrios:

@Override
protected void onLoad() {
super.onLoad();
mSprites.add(mHero = new Sprite(50, 10,
drawableToBitmap(getResources().getDrawable(R.drawable.hero))));

esqueletoFase = new boolean[getWidth()/20 + 1][getHeight()/20


+ 1];
chaoPaint = new Paint();
chaoPaint.setARGB(255, 255, 255, 255); //Branco
montarFase();
}

O que fazemos a carregar o Sprite mHero com o grfico que definimos anteriormente e
adiciona-lo a lista mSprites, pois assim ele ser desenhado automaticamente. Utilizamos um
mtodo para carregar o bitmap atravs do drawable que fizemos no xml, e esse mtodo
tambm precisa ser adicionado a classe:

public static Bitmap drawableToBitmap (Drawable drawable) {


if (drawable instanceof BitmapDrawable) {
return ((BitmapDrawable)drawable).getBitmap();
}

Bitmap bitmap =
Bitmap.createBitmap(drawable.getIntrinsicWidth(),
drawable.getIntrinsicHeight(), Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(),
canvas.getHeight());
drawable.draw(canvas);

return bitmap;
}

Agora usaremos uma tcnica para estabelecer onde h ou no cho no cenrio. O que faremos
pegar toda a tela e dividir em uma matriz de 20 pixels cada, e isso que o atributo
esqueletoFase significa. Por exemplo, se esqueletoFase[4][6] for true, significa que do pixel
4*20 = 80x a 100x e 6*20 = 120y a 140y h um cho e o heri deve poder ficar em cima dele.
Chamamos o mtodo montarFase() para definir esses espaos de cho:

private void montarFase() {


int alturaMax = esqueletoFase[0].length-2;
for(int i = 0; i < esqueletoFase.length; i++){
esqueletoFase[i][alturaMax] = true;
if(i > 7)
esqueletoFase[i][alturaMax-4] = true;
if(i > 15)
esqueletoFase[i][alturaMax-9] = true;
if(i > 20)
esqueletoFase[i][alturaMax-14] = true;
}
}

Nesse caso estou definindo uma plataforma que completa a parte de baixo toda da tela e
algumas intermedirias. Agora vamos definir a gravidade e o que acontecer quando o heri
estiver em cima de uma dessas plataformas. J que isso tem que ser verificado a todo tempo,
utilizaremos o mtodo update que chamado automaticamente a cada frame do jogo:

@Override
public void update() {
int alturaPlataforma = (mHero.y+mHero.height)/20;
if(esqueletoFase[(mHero.x+mHero.width/2)/20]
[alturaPlataforma]){
mHero.y = alturaPlataforma*20 - mHero.height;
if(heroVelocidadeVertical > 0)
heroVelocidadeVertical = 0;
}else if(heroVelocidadeVertical + GRAVITY < 20)
heroVelocidadeVertical += GRAVITY;

mHero.y += heroVelocidadeVertical;
super.update();

O que esse mtodo faz verificar se no "p" do heri (no caso a nossa bolinha azul) h uma
plataforma, atravs do esqueletoFase. Se houver ele reposicionar o heri em cima dessa
plataforma (isso necessrio pois se o heri casse a mais de 1 pixel por segundo, ele poderia
parar um pouco dentro da plataforma), e setar a velocidade vertical para zero, caso seja
maior que zero (estiver caindo). Se no houver uma plataforma nos ps do heri a velocidade
vertical ser acrescida da gravidade. Aps tudo isso somado a posio y do heri a
velocidade vertical. Geralmente nos jogos, toda essa lgica de velocidade inserida dentro
da classe que representa o heri, mas como aqui no possumos uma classe especifica para
isso utilizaremos dessa maneira mesmo.

Para pegar a movimentao do heri usaremos o mtodo TouchEvents:

@Override
protected void TouchEvents(int x, int y, int action) {
if(y <= getHeight()/2)
pular();
else if(x <= getWidth()/2)
andar(false);
else
andar(true);
super.TouchEvents(x, y, action);
}

Os comandos so simples: se o jogador tocar na metade de cima da tela o heri ir pular, se


tocar na parte de baixo ele ir andar para esquerda ou direita a depender da metade vertical
que ele toque, como mostra a figura:

Possveis movimentos

necessrio definir os mtodos andar e pular:

private void andar(boolean praDireita) {


mHero.x += praDireita ? VELOCIDADE_ANDAR : -VELOCIDADE_ANDAR;
}

private void pular() {


if(heroVelocidadeVertical == 0)
heroVelocidadeVertical = -ALTURA_PULO;
}

So mtodos simples: andar apenas se movimenta para direita ou esquerda na quantidade de


pixels definido pela constanteVELOCIDADE_ANDAR e o pular verifica se a velocidade vertical
0, ou seja, se o heri est no cho e define sua velocidade vertical de acordo com a
constante ALTURA_PULO.

Agora falta apenas alterar nosso onDraw para que seja desenhado as plataformas de acordo
com esqueletoFase:

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
drawEsqueleto(canvas);
}

private void drawEsqueleto(Canvas canvas) {


int xInicio = -1;
for(int h = 0; h < esqueletoFase[0].length; h++){
for(int w = 0; w < esqueletoFase.length; w++)
if(esqueletoFase[w][h]){
if(xInicio == -1)
xInicio = w*20;
}else{
if(xInicio > -1){
canvas.drawRect(xInicio, h*20, w*20, (h+1)*20,
chaoPaint);
xInicio = -1;
}
}
if(xInicio > -1){
canvas.drawRect(xInicio, h*20,
esqueletoFase.length*20, (h+1)*20, chaoPaint);
xInicio = -1;
}
}
}

H vrias formas de fazer esse desenho, inclusive num jogo de verdade utilizaria sprites do
cenrio, mas aqui fiz dessa forma que pega automaticamente nosso esqueletoFase e
desenha, onde for true, um quadrado branco. Esse cdigo foi otimizado para plataformas
horizontais, no sendo indicado para desenhar paredes ("plataformas verticais").

Nosso exemplo est pronto e ao ser executado ter um resultado satisfatrio: o heri
comear caindo para ultima plataforma e voc poder move-lo e subir em outras
plataformas. Claro que ainda h muita coisa a se fazer, por exemplo: nosso jogo apenas
verifica se o heri tocou no cho e o coloca em cima desse cho, ento possvel ele passar
dentro da plataforma de baixo para cima. Tambm se o heri tentar sair da tela produzido
um erro.

O cdigo completo e o apk est disponvel aqui!

Por enquanto isso, qualquer coisa entrem em contato. At mais.

You might also like