Professional Documents
Culture Documents
Fonte: http://www.tutoriandroid.com/2012/05/como-criar-um-jogo-estilo-
plataforma.html
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,
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:
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 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 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.
}
}
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:
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;
super(context);
@Override
gold_head.png
pink.png
Para testar o projeto, basta ir na sua Activity principal e mudar o setContentView para:
setContentView(new MainGameView(this));
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
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;
Bitmap
final
(background
montado).
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);
}
}
Preenche
alguns
espaos
com
aleatrios
plantas.
Preenche
alguns
espaos
com
aleatrios
flores.
@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
...
protected ArrayList<Pink> pinks;
protected ArrayList<Gold> golds;
protected Background background;
...
...
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.
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.
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:
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;
@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)));
@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:
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;
@Override
@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
<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:
@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...
Perceba que precisamos usar a funo pink.isDead(), que no fizemos anteriormente. Ento
vamos adiciona-la agora na classe Pink:
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);
newStage();
}
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);
}
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.
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
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
...
private int alivePinks;
public MediaPlayer musica;
public SoundPool sound;
private int soundIdPinkHit, soundIdGoldHit, soundIdWin;
public MainGameView(Context context) {
...
...
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);
...
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.
Atendendo
pedidos
irei
explicar
aqui
base
de como
fazer um jogo
estilo
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.
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:
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:
Agora na nossa activity principal no precisamos carregar um layout por xml, vamos apenas
instanciar o nosso GameView:
setContentView(new PlataformaGameView(this));
}
}
Ai est nossa classe com as contantes e atributos que iremos utilizar. Note que chamamos o
construtor pai
parmetro
pois s
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))));
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:
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:
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.
@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);
}
Possveis movimentos
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);
}
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.