Archive for the ‘android’ Category

android game – imagem e som

November 21st, 2009

android show image
Opa ! apos algumas idas e vindas no codigo, as coisas começam a se arrumar hehehehe, nesta versão já esta resolvida a exibição de imagem a partir do nome da imagem, e execução de som no formato ogg, tanto no loop como um som a partir de um evento do usuário.

Apesar da recomendação do SDK seja de acessar os arquivos de midia através do resource ID criado na classe R, eu estou construindo as classes usando o nome do resource para poder expandir a programação do jogo para uma lingaguem de script, e usar o java como uma camada de abstração do jogo.

Vamos ao codigo e alguns comentarios sobre cada um :

GameActivity - ganhou uma nova chamada para determinar que volume o jogo vai usar, setVolumeControlStream(AudioManager.STREAM_MUSIC) assim o jogo fica com o mesmo volume que estiver acertado para a musica, alem disto o metodo onDestroy() chama o novo metodo release() da view que vai se encarregar de liberar recursos e em especial mandar os audios se calarem :)

package com.athanazio.android.showimage;

import android.app.Activity;
import android.media.AudioManager;
import android.os.Bundle;
import android.view.Window;

public class GameActivity extends Activity {
	private GameView view;

	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		setVolumeControlStream(AudioManager.STREAM_MUSIC);

		view = new GameView(this, getResources());
		setContentView(view);
	}

	protected void onDestroy() {
		super.onDestroy();
		view.release();
	}

}

GameView – usa as classes Sprite e Audio para exibir imagens e tocar audio, poucas mudanças comparando com a versão anterior.

package com.athanazio.android.showimage;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;

public class GameView extends View implements Runnable {

	private static final String TAG = "GAME VIEW";

	private static final int INTERVAL = 10;
	private Paint cPaint;

	private boolean running = true;

	FPSCounter fps;

	private Sprite background;
	private Sprite cloud;

	private Audio soundtrack;
	private Audio touch;

	public GameView(Context context, Resources resources) {
		super(context);

		String packageName = getClass().getPackage().getName();

		background = new Sprite(resources, packageName, "background");
		cloud = new Sprite(resources, packageName, "cloud");
		cloud.setCenterAtMiddle();

		soundtrack = new Audio(resources, packageName, "soundtrack");
		touch = new Audio(resources, packageName, "cowbell");

		soundtrack.setLooping(true);
		soundtrack.play();

		cPaint = new Paint();
		setFocusable(true);
		setClickable(true);
		setLongClickable(true);

		fps = new FPSCounter();

		// Set the background
		this.setBackgroundColor(Color.WHITE);
		Log.i(TAG, "game view created");

		Thread monitorThread = new Thread(this);
		monitorThread.setPriority(Thread.MIN_PRIORITY);
		monitorThread.start();
	}

	public void draw(Canvas canvas) {
		super.draw(canvas);

		background.draw(canvas);
		cloud.draw(canvas);

		cPaint.setColor(Color.WHITE);
		canvas.drawText(fps.getFPS(), 20, 20, cPaint);
	}

	public boolean onKeyUp(int keyCode, KeyEvent event) {
		return super.onKeyUp(keyCode, event);
	}

	public boolean onKeyDown(int keyCode, KeyEvent event) {
		boolean handled = false;
		Log.i(TAG, "key down");
		float x = cloud.getX();
		float y = cloud.getY();

		if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
			y = y - 5;
			handled = true;
		} else if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
			y = y + 5;
			handled = true;
		} else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
			x = x - 5;
			handled = true;
		} else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
			x = x + 5;
			handled = true;
		}

		cloud.move(x, y);
		return handled;
	}

	public boolean onTouchEvent(MotionEvent event) {
		cloud.setX(event.getRawX());
		cloud.setY(event.getRawY());
		Log.i(TAG, "on touch");

		touch.play();
		return super.onTouchEvent(event);
	}

	public void run() {
		while (running) {
			try {
				Thread.sleep(INTERVAL);
			} catch (InterruptedException e) {
				Log.e(TAG, "main loop finished");
			}
			update();
			postInvalidate();
		}
	}

	private void update() {
		fps.update();

	}

	public void release() {
		running = false;
		soundtrack.release();
		touch.release();
	}

}

Sprite – esta eh responsavel por exibir imagens na tela, uma atenção especial deve ser dada ao recurso de determinar o centro da imagem, isto eh muito util para responder eventos de mouse, e por exemplo posicionar a imagem centralizada onde o usuário clicou, o método setCenterAtMiddle() calcula a posição para desenhar baseado na largura e altura da imagem. e por default o centro da imagem eh no 0,0.

package com.athanazio.android.showimage;

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.Log;

public class Sprite {

	private static final String TAG = "GAME SPRITE";
	private Bitmap bitmap;
	private float x;
	private float y;
	private float centerX;
	private float centerY;
	private float drawX;
	private float drawY;
	private Paint paint;

	public Sprite(Resources resources, String packageName, String name) {
		String logFileName = packageName + " " + name;
		Log.i(TAG, "loading image : " + logFileName);

		int id = resources.getIdentifier(name, "drawable", packageName);
		bitmap = BitmapFactory.decodeResource(resources, id);
		centerX = 0;
		centerY = 0;

		setX(0);
		setY(0);

		this.paint = new Paint();
	}

	public void draw(Canvas canvas) {
		canvas.drawBitmap(bitmap, drawX, drawY, paint);
	}

	public void move(float x, float y) {
		setX(this.x + x);
		setY(this.y + y);
	}

	public void setCenterAtMiddle(){
		centerX = bitmap.getWidth() / 2F;
		centerY = bitmap.getHeight() / 2F;
	}

	public float getX() {
		return x;
	}

	public void setX(float x) {
		this.x = x;
		drawX = this.x - centerX;
	}

	public float getY() {
		return y;
	}

	public void setY(float y) {
		this.y = y;
		drawY = this.y - centerY;
	}

}

Audio – esta classe esconde a criação de um MediaPlayer para cada som que se deseja tocar, imagino que o melhor cenário seria ter uma classe para som em loop que usaria um MediaPlayer, e outra classe para efeitos sonoros que tocaria no maximo N efeitos ao mesmo tempo, mantendo menos objetos MediaPlayer na memória.

Mas a implementação atual mantém um MediaPlayer para cada som, observar que o resource ID é recuperado pelo nome do arquivo usando o metodo resources.getIdentifier(name, “raw”, packageName) e a partir do resource ID podemos recuperar o AssetFileDescriptor com a chamada resources.openRawResourceFd(id), of file descriptor é usado para inicializar o MediaPlayer com os detalhes necessários a execução do som.

package com.athanazio.android.showimage;

import android.content.res.AssetFileDescriptor;
import android.content.res.Resources;
import android.media.MediaPlayer;
import android.util.Log;

/**
 * Audio resources abstraction
 *
 * @author athanazio
 * @see "http://developer.android.com/intl/fr/guide/topics/media/index.html"
 * @see "http://developer.android.com/intl/fr/reference/android/media/MediaPlayer.html#Valid_and_Invalid_States"
 */
public class Audio {

	private static final String TAG = "GAME AUDIO";
	private MediaPlayer player;
	private boolean ready;
	private int id;
	private AssetFileDescriptor afd;

	/**
	 *
	 * @param resources
	 * @param context
	 * @param packageName
	 * @param name
	 *
	 */
	public Audio(Resources resources, String packageName, String name) {
		String logFileName = packageName + " " + name;
		Log.i(TAG, "loading audio : " + logFileName);

		ready = false;
		player = new MediaPlayer();

		try {
			this.id = resources.getIdentifier(name, "raw", packageName);
			this.afd = resources.openRawResourceFd(id);

			Log.i(TAG, "loading audio : " + logFileName + " id:" + id);

			player.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
			player.setLooping(false);
			player.prepare();

			// reset the player after completion
			player.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
				public void onCompletion(MediaPlayer mp) {
					try {
						player.reset();
						player.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
						player.prepare();
					} catch (Exception e) {
						Log.e(TAG, "error resetting the media player");
						e.printStackTrace();
					}
				}
			});

			ready = true;

		} catch (Exception e) {
			Log.e(TAG, "ERROR loading audio : " + logFileName);
			e.printStackTrace();
		}
	}

	public void play() {
		if (ready) {
			player.start();
		} else {
			Log.i(TAG, "the player is not ready.");
		}
	}

	public void setLooping(boolean b) {
		player.setLooping(b);
	}

	public void release() {
		player.stop();
		player.release();
	}

}

android – mover bolinha

November 21st, 2009

android move ball

Na minha opinão o hello world para desenvolver jogos eh mover um desenho primitivo na tela junto com a informação de FPS (frames por segundo) até porque logo logo perguntamos : tah rodando a quantos FPS ? :) assim sendo já posso dizer que fiz o meu hello world para o android.

Alguns links que ajudaram bastante foram estes :

Uma apresentação da google sobre o desenvolvimento para de jogos para o Android
Writing-Real-Time-Games-for-Android

Os exemplos de cógigo citados na apresentação
C2B 2 GameView

Com certeza este exemplo que escrevi eh extremamente simples, mas exemplifica alguns elementos básicos :

  • main loop
  • chamada de update
  • atualização da area de vizualização
  • input do usuário

O ambiente que tenho eh o Eclipse com o plugin para desenvolvimento do android instalado, para os testes eu prefiro usar o telefone porque o emulador demora muito para inicializar :) e este exemplo tem basicamento 3 classes :

GameActivity – que eh a entrada da aplicação, nela eu solicito o uso fullscreen para o jogo, e determino que a visualização será realizada por uma instancia da classe GameView.

package com.athanazio.android.moveball2;

import android.app.Activity;
import android.os.Bundle;
import android.view.Window;

public class GameActivity extends Activity {
	private GameView view;

	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		requestWindowFeature(Window.FEATURE_NO_TITLE);

		view = new GameView(this);
		setContentView(view);
	}

	protected void onPause() {
		super.onPause();
		view.pause();
	}
}

GameView – nesta classe reside o loop principal do jogo que é iniciado no final do construtor, note que na verdade eh criado um Thread com baixa prioridade, que passará a realizar as atividades de atualização e principalmente executar o método postupdate() notficando a interface que pode ser exibida.

além do loop principal o input direcional é tratado pelo método onKeyDown() e o tocar na tela pelo método onTouchEvent()

package com.athanazio.android.moveball2;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;

public class GameView extends View implements Runnable {

	private static final String TAG = "GAMEVIEW";

	private static final int INTERVAL = 10;
	private Paint cPaint;

	private float x = 50;

	private float y = 50;

	private boolean running = true;

	FPSCounter fps;

	public GameView(Context context) {
		super(context);
		cPaint = new Paint();
		setFocusable(true);
		setClickable(true);
		setLongClickable(true);

		fps = new FPSCounter();

		// Set the background
		this.setBackgroundColor(Color.WHITE);
		Log.i(TAG, "game view created");

		Thread monitorThread = new Thread(this);
		monitorThread.setPriority(Thread.MIN_PRIORITY);
		monitorThread.start();
	}

	public boolean onKeyUp(int keyCode, KeyEvent event) {
		return super.onKeyUp(keyCode, event);
	}

	public boolean onKeyDown(int keyCode, KeyEvent event) {
		boolean handled = false;
		Log.i(TAG, "key down");

		if (keyCode == KeyEvent.KEYCODE_DPAD_UP) {
			y = y - 5;
			handled = true;
		} else if (keyCode == KeyEvent.KEYCODE_DPAD_DOWN) {
			y = y + 5;
			handled = true;
		} else if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
			x = x - 5;
			handled = true;
		} else if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
			x = x + 5;
			handled = true;
		}
		return handled;
	}

	public boolean onTouchEvent(MotionEvent event) {
		x = event.getRawX();
		y = event.getRawY();
		Log.i(TAG, "on touch");

		return super.onTouchEvent(event);
	}

	public void draw(Canvas canvas) {
		super.draw(canvas);

		cPaint.setColor(Color.BLUE);
		canvas.drawCircle(x, y, 15, cPaint);

		cPaint.setColor(Color.BLACK);
		canvas.drawText(fps.getFPS(), 20, 20, cPaint);

	}

	public void run() {
		while (running) {
			try {
				Thread.sleep(INTERVAL);
			} catch (InterruptedException e) {
				Log.e(TAG, "main loop finished");
			}
			update();
			postInvalidate();
		}
	}

	private void update() {
		fps.update();

	}

	public void pause() {
		running = false;
	}

}

FPSCounter – nesta classe é feita uma contagem de frames que o jogo consegue exibir por segundo, vi algumas implementações interessantes disto, mas acabei fazendo o mais simples e mais econômico, contar frames e ver se jah passou o tempo :) note que algumas variáveis eu criei fora dos métodos para evitar que o Garbage collector tivesse que trabalhar, querendo ou não isto economiza alguns milisegundos :)

package com.athanazio.android.moveball2;

import java.text.DecimalFormat;

import android.os.SystemClock;

public class FPSCounter {

	public static final DecimalFormat format = new DecimalFormat("###0.00");

	private float frames;
	private String fps;
	private long start;
	private long current;

	public FPSCounter() {
		start = SystemClock.uptimeMillis();
		frames = 0;
		fps = "???";
	}

	public void update() {
		frames ++;
		current = SystemClock.uptimeMillis();
		if( current - start > 1000 ){
			fps = format.format(frames);
			frames = 0;
			start = current;
		}
	}

	public String getFPS() {
		return fps;
	}
}

a propósito o fps esta em torno de 90 e poucos, vamos ver o que acontece quando tiver de desenhar uma imagem no fundo e varias outras na tela hehehehe.