Bom dia,
Após passar praticamente 1 semana estudando o mundo Android, resolvi iniciar o desenvolvimento do jogo DroidBasket exposto no 6º dia.
Explicação do Jogo:
- Cairá 100 robôs do céu em velocidade constante, porém em posições diferentes;
- Usando o touch o usuário deverá juntar o máximo de robôs possíveis;
Objetivo do Jogo:
- Conseguir coletar o máximo de robôs possíveis;
Resultado esperado:
- Se coletar 50 robôs, significa ter um bom reflexo e uma boa visão;
- Se conseguir coletar mais de 50 já é uma grande conquista;
- Se coletar um valor abaixo de 50, o usuário terá que começar a praticar um pouco mais a cada dia, com o intuito de acelerar seus reflexos;
Como a ideia é a diversão, irei disponibilizar o fonte e explicar o que fiz até agora.
Antes de começarmos, gostaria de agradecer o portal pontoV http://pontov.com.br/ e meu amigo Marky que desenvolveu esse artigo http://pontov.com.br/site/java/69-android/247-android-desenhando-na-tela?q=%2Fjava%2F69-android%2F247-android-desenhando-na-tela&start=5 no qual vamos utiliza-lo para a construção deste game.
Então, antes de mais nada, é obrigatório realizar o download dos fontes do artigo:
https://github.com/MarkyVasconcelos/AndroidTechsGames
Vou mostrar agora quais foram as modificações realizadas em cada classe:
JPongActivity.java -> DroidBasketActivity.java
package br.techs.jpong;
//
//Importa as bibliotecas DroidBasket.
//
import br.techs.R;
//
//Importa as bibliotecas Android.
//
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
public class DroidBasketActivity
extends Activity
{
/**
Variaveis de trabalho.
*/
private static final int
REFRESH = 1;
private Handler
io_gui_refresher;
private ComplexGameView
io_game_view;
/**
Primeiro método a ser chamado após criar a Activity.
*/
public
void onCreate
(
Bundle savedInstanceState
)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.droidbasket);
//
// Recebe a VIEW do jogo.
//
io_game_view =
(ComplexGameView)findViewById(R.id.gameView);
//
// Atualiza a tela do jogo.
//
io_gui_refresher = new Handler()
{
public
void handleMessage
(
Message msg
)
{
if (
msg.what == REFRESH
)
{
io_game_view.invalidate();
}
super.handleMessage(msg);
}
};
io_game_view.setCallbackHandler(io_gui_refresher);
}
}
- A principal modificação desta classe, foi retirar a chamada da Thread que faz referência a classe ComplexGameView.java
Motivo: Apenas Por preferencia e visualização, optei por criar uma Thread interna na classe ComplexGameView.
Modificações na Classe ComplexGameView:
package br.techs.jpong;
//
//Importa as bibliotecas DroidBasket.
//
import br.techs.PiecesManager;
import br.techs.R;
import br.techs.hud.ScoreFinalboard;
import br.techs.hud.Scoreboard;
import br.techs.math.Vector2D;
import br.techs.pieces.Robot;
import br.techs.pieces.Basket;
import br.techs.pieces.Basket.Axis;
import br.techs.pieces.Wall;
import java.util.Random;
//
//Importa as bibliotecas Android.
//
import android.content.Context;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
public class ComplexGameView
extends View
implements Runnable
{
/**
Variaveis de trabalho.
*/
//
// Tamanho da tela.
//
private int
width = 480,
height = 800;
private Paint
background;
private PiecesManager
manager;
private Basket
basket;
private Wall
wall;
private int
in_robot_count = 0;
private Handler
io_gui_refresher;
/**
Construtores.
*/
public ComplexGameView
(
Context context
)
{
super(context);
init();
}
public ComplexGameView
(
Context context,
AttributeSet attrs
)
{
super(context, attrs);
init();
}
public ComplexGameView
(
Context context,
AttributeSet attrs,
int defStyle
)
{
super(context, attrs, defStyle);
init();
}
/**
Cria os componentes para a view.
*/
public
void init()
{
//
// Pinta o fundo.
//
background = new Paint();
background.setColor(Color.BLACK);
//
// Armazena o gerenciador dos compoentes.
//
manager = new PiecesManager();
//
// Cria as 3 paredes.
//
// Se for a parede esquerda, esse vetor é (1, 0);
// Se for a direita (-1, 0);
// Se for a de cima (0, -1);
// E se for a de baixo (0, 1);
//
manager.addPiece(new Wall(new Rect(0, 0, 10, height),new Vector2D(1,
0))); // esquerda
manager.addPiece(new Wall(new Rect(width - 10, 0, width, height),new
Vector2D(-1, 0))); // direita
wall = new Wall(new Rect(0, height - 10, width,
height),new Vector2D(0, 1));
manager.addPiece(wall); // de baixo
//
// Cria o componente de captura para as bolinhas.
//
basket = new Basket(new Vector2D(0, 1),
Axis.X_AXIS, 700);
manager.addPiece(basket);
manager.addPiece(new Scoreboard(basket));
//
// Cria a tela responsável por criar as 100 bolinhas na tela.
//
new Thread()
{
//
// Define as prioridades da thread.
//
{
setDaemon(true);
}
@Override
public
void run()
{
//
// Faz o procedimento até a thread ser interrompida.
//
while (
basket.notifyHitGet()+wall.notifyHitGet()
<= 100
)
{
if (
in_robot_count %
100
== 0
)
{
//
// Cria o robo.
//
createRobot();
}
//
// Processa as informações.
//
manager.processAI();
//
// Atualiza o jogo.
//
Message
lo_message = new Message();
lo_message.what = 1;
io_gui_refresher.sendMessage(lo_message);
try
{
//
// Aguarda um segundo para criar outro.
//
Thread.sleep(15);
}
catch (Exception ex)
{
// Aconteceu alguma coisa.
}
//
// Conta o robot.
//
in_robot_count++;
}
}
}.start();
}
/**
Pinta os componentes.
*/
public
void onDraw
(
Canvas canvas
)
{
canvas.save();
canvas.drawRect(0, 0, getWidth(), getHeight(), background);
manager.draw(canvas);
canvas.restore();
}
/**
Cria o robozinho.
*/
private
void createRobot()
{
//
// Cria um random para alterar a posição dos robos.
//
Random
rdm = new Random();
//
// Cria um novo vector com uma outra direção.
//
Vector2D
vec = new Vector2D(10 + rdm.nextInt(550), 20);
//
// Cria o novo robo.
//
Robot
lo_robot = new Robot
(
vec,
new Vector2D(0, 1),
15,
manager,
Color.rgb(50 + rdm.nextInt(206),
50 + rdm.nextInt(206), 50 + rdm.nextInt(206)),
BitmapFactory.decodeResource(getResources(), R.drawable.ball)
);
manager.addPiece(lo_robot);
}
/**
Evento de TOQUE.
*/
public
boolean onTouchEvent(MotionEvent evt)
{
for (
int
i = 0
;
i < evt.getPointerCount()
;
i++
)
{
float
y = evt.getY(i);
//
// Movimenta o basket.
//
basket.notifyMotionEvent(evt.getX(i), evt.getY(i));
}
return true;
}
public
void setCallbackHandler
(
Handler ao_gui_refresher
)
{
io_gui_refresher = ao_gui_refresher;
}
@Override
public
void run()
{
}
}
- Retirada a parede (Wall) na parte de cima do Jogo;
- Alterado o nome da variavel PAD para Basket;
- Removido o uso de um dos Baskets disponiveis no jogo principal;
- Alterado o nome do método createBall() para createRobot();
- Alterando a direção das bolinhas no método createRobot() para cairem constantemente sem mudar a direção.
- Modificação no método onTouchEvent() para trabalhar apenas com 1 Basket;
- Criação da Thread responsável por criar as bolinhas do jogo e atualizar a view principal.
Mostrando a Thread antiga:
@Override
public void run() {
while (true) {
try {
if (loops % 500 == 0)
createBall();
manager.processAI();
Message msg = new Message();
msg.what = 1;
handler.sendMessage(msg);
Thread.sleep(10);
loops++;
} catch (Exception e) {
}
}
}
Apresentando a Thread nova:
//
// Cria a tela responsável por criar as 100 bolinhas na tela.
//
new Thread()
{
//
// Define as prioridades da thread.
//
{
setDaemon(true);
}
@Override
public
void run()
{
//
// Faz o procedimento até a thread ser interrompida.
//
while (
basket.notifyHitGet()+wall.notifyHitGet()
<= 100
)
{
if (
in_robot_count %
100
== 0
)
{
//
// Cria o robo.
//
createRobot();
}
//
// Processa as informações.
//
manager.processAI();
//
// Atualiza o jogo.
//
Message
lo_message = new Message();
lo_message.what = 1;
io_gui_refresher.sendMessage(lo_message);
try
{
//
// Aguarda um segundo para criar outro.
//
Thread.sleep(15);
}
catch (Exception ex)
{
// Aconteceu alguma coisa.
}
//
// Conta o robot.
//
in_robot_count++;
}
}
}.start();
- A Thread irá parar quando cairem 100 robozinhos. Ou seja, A quantidade capturada pelo Basket somada com a quantidade de robozinhos que bateram na parede.
Pad.java -> Basket.java
package br.techs.pieces;
//
//Importa as bibliotecas DroidBasket.
//
import br.techs.math.Vector2D;
//
//Importa as bibliotecas Android.
//
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
public class Basket
extends BlockableEntity
{
/**
Variaveis de trabalho.
*/
private Paint
io_wall_paint;
private int
io_default_distance_from_axis;
private Axis
io_this_axis;
private int
in_basket_count = 0;
/**
Construtores.
*/
public Basket
(
Vector2D normal,
Axis axis,
int defaultDistance
)
{
super(new Rect(), normal);
io_wall_paint = new Paint();
io_wall_paint.setColor(Color.GRAY);
io_this_axis = axis;
this.io_default_distance_from_axis
= defaultDistance;
}
/**
Atualiza os movimentos dos basket.
@param x
@param y
*/
public
void notifyMotionEvent
(
float x,
float y
)
{
io_this_axis.atualize(this, x, y);
}
/**
Pinta o desenho na "view".
*/
@Override
public
void draw
(
Canvas canvas
)
{
canvas.save();
canvas.drawRect(getBounds(), io_wall_paint);
canvas.restore();
}
public
enum Axis
{
X_AXIS
{
@Override
void atualize
(
Basket pad,
float x,
float y
)
{
pad.setBounds
(
new Rect((int) x - 150,
pad.io_default_distance_from_axis,
(int) x-10,
pad.io_default_distance_from_axis + 10)
);
}
},
Y_AXIS
{
@Override
void atualize
(
Basket pad,
float x,
float y
)
{
pad.setBounds
(
new Rect(pad.io_default_distance_from_axis,
(int) y - 150, pad.io_default_distance_from_axis
+ 10,
(int) y + 150)
);
}
};
abstract
void atualize
(
Basket pad,
float x,
float y
);
}
/**
Conta a quantidade de vezes que o robo bate no basket.
*/
public
void notifyHit()
{
in_basket_count++;
}
/**
@return
Retorna a quantidade de vezes que o robo bateu no basket.
*/
public
int notifyHitGet()
{
return (in_basket_count);
}
@Override
public
void processAI()
{
// Do nothing, position is handled by events
}
}
- Criação dos métodos notifyHit() e notifyHitGet() responsáveis por contar a quantidade de robozinhos em contato com o Basket.
- Diminuindo o tamanho do Basket no método Axis.
Modificações na classe Wall:
package br.techs.pieces;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import br.techs.math.Vector2D;
public class Wall extends BlockableEntity {
private Paint wallPaint;
private int
in_basket_count = 0;
public Wall(Rect bounds, Vector2D normal) {
super(bounds, normal);
wallPaint = new Paint();
wallPaint.setColor(Color.GRAY);
}
@Override
public void draw(Canvas canvas) {
canvas.save();
canvas.drawRect(getBounds(), wallPaint);
canvas.restore();
}
@Override
public void processAI() {
// Do nothing, walls don't think
}
/**
Conta a quantidade de vezes que o robo bate no basket.
*/
public
void notifyHit()
{
in_basket_count++;
}
/**
@return
Retorna a quantidade de vezes que o robo bateu no basket.
*/
public
int notifyHitGet()
{
return (in_basket_count);
}
}
- Remoção dos métodos addHitListener e notifyHit.
- Criação dos métodos notifyHit() e notifyHitGet() responsáveis por contar a quantidade de robozinhos em contato com a parede (Wall).
Ball.java -> Robot.java
package br.techs.pieces;
//
//Importa as bibliotecas DroidBasket.
//
import br.techs.PiecesManager;
import br.techs.math.Vector2D;
//
//Importa as bibliotecas Android.
//
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
public class Robot
extends Entity
{
private Paint
paint;
private Vector2D
pos;
private Vector2D
dir;
private float
speed;
private PiecesManager
pieces;
private Bitmap
image;
public Robot
(
Vector2D pos,
Vector2D dir,
float speed
)
{
super(new Rect());
this.pos = pos;
this.dir = dir;
this.speed = speed;
paint = new Paint();
paint.setColor(Color.RED);
}
public Robot
(
Vector2D pos,
Vector2D dir,
float speed,
PiecesManager manager,
int color,
Bitmap image
)
{
super(new Rect());
this.pos = pos;
this.dir = dir;
this.speed = speed;
paint = new Paint();
paint.setColor(color);
pieces = manager;
this.image = image;
}
public void setPiecesManager(PiecesManager manager) {
pieces = manager;
}
@Override
public void draw(Canvas canvas) {
canvas.save();
canvas.drawBitmap(image, pos.getX(),pos.getY(), paint);
// canvas.drawCircle(pos.getX(), pos.getY(), 10, paint);
canvas.restore();
}
private void move() {
pos.plusMe(dir.multiply(speed));
}
@Override
public
void processAI()
{
Rect
bounds = new Rect((int) pos.getX() - 5, (int) pos.getY() - 5, (int) pos.getX() + 5, (int) pos.getY() + 5);
for (
Entity
ent : pieces.getPieces()
)
{
//
// Se não encontrou nenhum parade bloqueando ...
//
if (
ent == this
)
{
//
// Continua rodando a aplicação.
//
continue;
}
//
// Se encontrou algo blocante ...
//
if (
ent instanceof BlockableEntity
)
{
//
// Se "esbarrou" a bolinha em algo ...
//
if (
bounds.intersect(ent.getBounds())
)
{
//
// MUDA A ROTA DA BOLINHA.
//
Vector2D n = ((BlockableEntity) ent).getNormal();
// r = v-2 * v.dot( n ) * n
dir = dir.minus(n.multiply(2).multiply(dir.dot(n)));
//
// Se bateu numa parede ...
//
if (
ent instanceof Basket
)
{
((Basket)ent).notifyHit();
}
else if (
ent instanceof Wall
)
{
((Wall)ent).notifyHit();
}
break;
}
}
}
move();
}
}
- Modificação no método processAI() para incrementar a contagem da captura do robô ao bater no Basket e na parede (Wall)
Bom, espero não ter esquecido de nada… No mais, retirei alguns código que achei desnecessários (que acredito ter passado despercebido) do tipo: for (int i = 0; i < 1; i++) na classe ComplexGameView.
Estou colocando a disposição o fonte nos links: https://github.com/downloads/runthecode/DroidBasket/LinaFerreira-DroidBasketv1.0.0.rar e http://www.megaupload.com/?d=NJK0ITQE
O próximo passo e fazer o robozinho (ou bolinha) se destruir ao bater no Basket ou na Parede. E também criar uma Acitivity mostrando o resultado e a pontuação.
Tchauzin!