Bienvenidos sean a este post, en el post anterior terminamos con nuestro segundo proyecto y en este post veremos todos los archivos con sus respectivos codigos finales para poder generar el juego.
En caso de necesitar una explicacion del alguno de los codigos les sugiero visitar sus correspondientes posts, aqui el listado de ellos:
- Segundo proyecto
- La clase PongActivity
- La clase PongJuego
- Implementando el bucle de juego
- La clase Pelota
- La clase Bate
- Manejando el bate
- Finalizando a Pong
En estos posts encontraran muchas de las explicaciones de lo realizado en los distintos codigos, a continuacion comenzaremos a detallar cada uno de ellos pero antes veamos cual es el modelo a elegir para diseñarlo:
- Dispositivos: Phone and Tablet
- Actividad: Empty Activity
- Nombre: Pong
- Nombre de paquete: org.example.pong
- API Minimo: API 14 (Android 4.0)
Les dejo el link para descargar los sonidos que utilizamos en la creacion del juego:
PongActivity.java
package org.example.pong;
import android.app.Activity;
import android.graphics.Point;
import android.os.Bundle;
import android.view.Display;
public class PongActivity extends Activity {
private PongJuego mPongJuego;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Display display = getWindowManager().getDefaultDisplay();
Point tamano = new Point();
display.getSize(tamano);
mPongJuego = new PongJuego(this, tamano.x, tamano.y);
setContentView(mPongJuego);
}
@Override
protected void onResume(){
super.onResume();
mPongJuego.resume();
}
@Override
protected void onPause(){
super.onPause();
mPongJuego.pause();
}
}
Nota: Para esta clase deben renombrar a MainActivity como PongActivity por medio de Refactor->Rename
PongJuego.java
package org.example.pong;
import android.content.Context;
import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.SoundPool;
import android.os.Build;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.widget.Toast;
import java.io.IOException;
class PongJuego extends SurfaceView implements Runnable {
private final boolean DEPURANDO = true;
private SurfaceHolder mNuestroCont;
private Canvas mCanvas;
private Paint mPincel;
private long mFPS;
private final int MILES_EN_SEGUNDO = 1000;
private int mScreenX;
private int mScreenY;
private int mFontTam;
private int mFontMargen;
private Bate mBate;
private Pelota mPelota;
private int mPuntaje;
private int mVidas;
private Thread mJuegoThread = null;
private volatile boolean mJugando;
private boolean mPausado = true;
private SoundPool mSP;
private int mBeepID = -1;
private int mBoopID = -1;
private int mBopID = -1;
private int mMissID = -1;
public PongJuego(Context contexto, int x, int y){
super(contexto);
mScreenX = x;
mScreenY = y;
mFontTam = mScreenX / 20;
mFontMargen = mScreenX /75;
mNuestroCont = getHolder();
mPincel = new Paint();
mPelota = new Pelota(mScreenX);
mBate = new Bate(mScreenX,mScreenY);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP){
AudioAttributes atributos = new AudioAttributes.Builder()
.setUsage(AudioAttributes.USAGE_MEDIA)
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
.build();
mSP = new SoundPool.Builder()
.setMaxStreams(5)
.setAudioAttributes(atributos)
.build();
} else {
mSP = new SoundPool(5, AudioManager.STREAM_MUSIC,0);
}
try{
AssetManager assetManager = contexto.getAssets();
AssetFileDescriptor descriptor;
descriptor = assetManager.openFd("beep.wav");
mBeepID = mSP.load(contexto,R.raw.beep,1);
descriptor = assetManager.openFd("boop.wav");
mBoopID = mSP.load(contexto,R.raw.boop,1);
descriptor = assetManager.openFd("bop.wav");
mBopID = mSP.load(contexto,R.raw.bop,1);
descriptor = assetManager.openFd("miss.wav");
mMissID = mSP.load(contexto,R.raw.miss,1);
} catch (IOException e){
Log.e("Error","Fallo la carga de alguno de los archivos.");
}
iniciaNuevoJuego();
}
private void iniciaNuevoJuego(){
mPuntaje = 0;
mVidas = 3;
mPelota.reset(mScreenX,mScreenY);
}
private void dibujar(){
if (mNuestroCont.getSurface().isValid()){
mCanvas = mNuestroCont.lockCanvas();
mCanvas.drawColor(Color.argb(255,26,128,182));
mPincel.setColor(Color.argb(255,255,255,255));
mCanvas.drawRect(mPelota.getmRect(),mPincel);
mCanvas.drawRect(mBate.getmRect(),mPincel);
mPincel.setTextSize(mFontTam);
mCanvas.drawText("Puntaje: " + mPuntaje
+ " Vidas: " + mVidas,
mFontMargen,mFontTam,mPincel);
if (DEPURANDO){
imprimirDepuracion();
}
mNuestroCont.unlockCanvasAndPost(mCanvas);
}
}
private void imprimirDepuracion(){
int debugTam = mFontTam / 2;
int debugComienzo = 150;
mPincel.setTextSize(debugTam);
mCanvas.drawText("FPS: " + mFPS,
10,
debugComienzo + debugTam,
mPincel);
}
@Override
public void run(){
while(mJugando){
long frameInicio = System.currentTimeMillis();
if (!mPausado){
actualizar();
detectarColisiones();
}
dibujar();
long esteFrameTiempo = System.currentTimeMillis() - frameInicio;
if (esteFrameTiempo > 0){
mFPS = MILES_EN_SEGUNDO / esteFrameTiempo;
}
}
}
private void actualizar(){
mPelota.actualizar(mFPS);
mBate.actualizar(mFPS);
}
private void detectarColisiones(){
if(RectF.intersects(mBate.getmRect(),mPelota.getmRect())){
mPelota.reboteBate(mBate.getmRect());
mPelota.incremetarVelocidad();
mPuntaje++;
mSP.play(mBeepID,1,1,0,0,1);
}
if(mPelota.getmRect().bottom >= mScreenY){
mPelota.invertirYVelocidad();
mVidas--;
mSP.play(mMissID,1,1,0,0,1);
if(mVidas==0){
mPausado=true;
iniciaNuevoJuego();
}
}
if (mPelota.getmRect().top <= 0){
mPelota.invertirYVelocidad();
mSP.play(mBoopID,1,1,0,0,1);
}
if (mPelota.getmRect().left <= 0){
mPelota.invertirXVelocidad();
mSP.play(mBopID,1,1,0,0,1);
}
if (mPelota.getmRect().right >= mScreenX){
mPelota.invertirXVelocidad();
mSP.play(mBopID,1,1,0,0,1);
}
}
public void pause(){
mJugando = false;
try{
mJuegoThread.join();
} catch (InterruptedException e){
Log.e("Error","uniendo a thread");
}
}
public void resume(){
mJugando = true;
mJuegoThread = new Thread(this);
mJuegoThread.start();
}
@Override
public boolean onTouchEvent(MotionEvent motionEvent){
switch (motionEvent.getAction()
& MotionEvent.ACTION_MASK){
case MotionEvent.ACTION_DOWN:
mPausado = false;
if(motionEvent.getX() > (mScreenX/2)){
mBate.setEstadoMovimiento(mBate.DERECHA);
} else {
mBate.setEstadoMovimiento(mBate.IZQUIERDA);
}
break;
case MotionEvent.ACTION_UP:
mBate.setEstadoMovimiento(mBate.DETENIDO);
break;
}
return true;
}
}
Bate.java
package org.example.pong;
import android.graphics.RectF;
class Bate {
private RectF mRect;
private float mLongitud;
private float mXCoord;
private float mBateVeloc;
private int mScreenX;
final int DETENIDO = 0;
final int IZQUIERDA = 1;
final int DERECHA = 2;
private int mBateMueve = DETENIDO;
Bate(int sx, int sy){
mScreenX = sx;
mLongitud = mScreenX / 8;
float altura = sy / 40;
mXCoord = mScreenX / 2;
float mYCoord = sy - altura;
mRect = new RectF(mXCoord, mYCoord,
mXCoord + mLongitud,
mYCoord + altura);
mBateVeloc = mScreenX;
}
RectF getmRect(){
return mRect;
}
void setEstadoMovimiento(int state){
mBateMueve = state;
}
void actualizar(long fps){
if(mBateMueve == IZQUIERDA){
mXCoord = mXCoord - (mBateVeloc/fps);
}
if (mBateMueve == DERECHA){
mXCoord = mXCoord + (mBateVeloc/fps);
}
if(mXCoord < 0){
mXCoord = 0;
}
else if (mXCoord + mLongitud > mScreenX){
mXCoord = mScreenX - mLongitud;
}
mRect.left = mXCoord;
mRect.right = mXCoord + mLongitud;
}
}
Pelota.java
package org.example.pong;
import android.graphics.RectF;
class Pelota {
private RectF mRect;
private float mXVelocidad;
private float mYVelocidad;
private float mPelotaAncho;
private float mPelotaAlto;
Pelota(int screenX){
mPelotaAlto = screenX / 100;
mPelotaAncho = screenX / 100;
mRect = new RectF();
}
RectF getmRect(){
return mRect;
}
void actualizar(long fps){
mRect.left = mRect.left + (mXVelocidad / fps);
mRect.top = mRect.top + (mYVelocidad / fps);
mRect.right = mRect.left + mPelotaAncho;
mRect.bottom = mRect.top + mPelotaAlto;
}
void invertirYVelocidad(){
mYVelocidad = -mYVelocidad;
}
void invertirXVelocidad(){
mXVelocidad = -mXVelocidad;
}
void reset(int x, int y){
mRect.left = x / 2;
mRect.top = 0;
mRect.right = (x / 2) + mPelotaAncho;
mRect.bottom = mPelotaAlto;
mYVelocidad = -(y/3);
mXVelocidad = y / 3;
}
void incremetarVelocidad(){
mXVelocidad = mXVelocidad * 1.1f;
mYVelocidad = mYVelocidad * 1.1f;
}
void reboteBate(RectF posicionBate){
float bateCentro = posicionBate.left + (posicionBate.width()/2);
float pelotaCentro = mRect.left + (mPelotaAncho / 2);
float interseccRelativa = (bateCentro - pelotaCentro);
if (interseccRelativa < 0){
mXVelocidad = Math.abs(mXVelocidad);
} else {
mXVelocidad = -Math.abs(mXVelocidad);
}
invertirYVelocidad();
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.example.pong">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".PongActivity"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen"
android:screenOrientation="landscape" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Con todo esto ya podran generar el juego como se ve en el siguiente video
Este es un juego muy util para aprender varios conceptos complementarios con respecto al primero, entre ellos:
- Como implementar un game engine
- Como funciona la deteccion de colisiones
- Como agregar sonidos a nuestro juego
- Como manejar (de manera muy simple) un sprite en pantalla
Espero les haya gustado la realizacion de este segundo juego y como podran observar a medida que avanzamos vamos mejorando nuestras herramientas y conocimientos para poder lograr verdaderos juegos simples pero entretenidos y poder cada dia estar mas cerca de poder, para aquellos que lo sueñan, crear nuestros propios juegos, ya saben que pueden seguirme en tumblr, Twitter o Facebook para recibir una notificacion cada vez que subo un nuevo post en este blog, nos vemos en el proximo post.
Tengo un Patreon donde podes acceder de manera exclusiva a material para este blog antes de ser publicado, sigue los pasos del link para saber como.

Tambien podes donar
Es para mantenimiento del sitio, gracias!
$1.00