Tuesday, May 27, 2008

Video for monolithandroid 1.0.3c alpha

I decided to put together a video to show graphics, gameplay and music for my game, monolithandroid. Comments are welcome!

Saturday, May 24, 2008

Working with textures in android's OpenGL/ES.


As you may recall, I use textures to draw the moon backdrop for my android application, monolithandroid. Originally, I only used one texture and I used some code from Ed Burnette's forthcoming book "Hello, Android". However, I decided to add more textures in order to improve the game's graphics. So I created a class named GLTextures, that can be used to make working with multiple textures easier.
Here's the code:

package org.teacake.monolith.apk;
import javax.microedition.khronos.opengles.*;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import java.lang.Integer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
public class GLTextures {
public GLTextures(GL10 gl,Context context)
{
this.gl = gl;
this.context = context;
this.textureMap = new java.util.HashMap ();
}

public void loadTextures()
{
int[] tmp_tex = new int[textureFiles.length];
gl.glGenTextures(textureFiles.length, tmp_tex, 0);
textures = tmp_tex;
for(int i=0;i {
Bitmap bmp = BitmapFactory.decodeResource(context.getResources(), textureFiles[i]);
ByteBuffer bb = extract(bmp);
// Get a new texture name
// Load it up
this.textureMap.put(new Integer(textureFiles[i]),new Integer(i));
int tex = tmp_tex[i];
int width = bmp.getWidth();
int height = bmp.getHeight();
gl.glBindTexture(GL10.GL_TEXTURE_2D, tex);
gl.glTexImage2D(GL10.GL_TEXTURE_2D, 0, GL10.GL_RGBA,width, height, 0, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, bb);
gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_LINEAR);
gl.glTexParameterx(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);

}
}
public void setTexture(int id)
{
try
{
int textureid = this.textureMap.get(new Integer(id)).intValue();
gl.glBindTexture(GL10.GL_TEXTURE_2D, this.textures[textureid]);

}
catch(Exception e)
{
return;
}
}
private static ByteBuffer extract(Bitmap bmp)
{
ByteBuffer bb = ByteBuffer.allocateDirect(bmp.height() * bmp.width() * 4);
bb.order(ByteOrder.BIG_ENDIAN);
IntBuffer ib = bb.asIntBuffer();
// Convert ARGB -> RGBA
for (int y = bmp.height() - 1; y > -1; y--)
{

for (int x = 0; x < bmp.width(); x++)
{
int pix = bmp.getPixel(x, bmp.getHeight() - y - 1);
int alpha = ((pix >> 24) & 0xFF);
int red = ((pix >> 16) & 0xFF);
int green = ((pix >> 8) & 0xFF);
int blue = ((pix) & 0xFF);

// Make up alpha for interesting effect

//ib.put(red << 24 | green << 16 | blue << 8 | ((red + blue + green) / 3));
ib.put(red << 24 | green << 16 | blue << 8 | alpha);
}
}
bb.position(0);
return bb;
}



public void add(int resource)
{
if(textureFiles==null)
{
textureFiles = new int[1];
textureFiles[0]=resource;
}
else
{
int[] newarray = new int[textureFiles.length+1];
for(int i=0;i {
newarray[i]=textureFiles[i];
}
newarray[textureFiles.length]=resource;
textureFiles = newarray;
}
}
private java.util.HashMap textureMap;
private int[] textureFiles;
private GL10 gl;
private Context context;
private int[] textures;
}


So, what can you use this code for?
This code enables you to load resources as textures.
At first you create a GLTextures object like this:

GLTextures textures = new GLTextures(gl, context);

Then you add the resource images you want to use as textures:

this.textures.add(R.drawable.moon);
this.textures.add(R.drawable.earth);

And then you loadup the textures:

textures.loadTextures();

When you want to use the texture in your OpenGL code, you can use:

textures.setTexture(R.drawable.moon);
.
.
//OpenGL drawing code
.
.

Actually the setTexture() method calls glBindTexture() which sets the current texture to the correct one.
Of course, you have to enable textures in your OpenGL code in order to do that! If you want more details and examples study the classes GLThread, Square and GLTextures found at http://code.google.com/p/monolithandroid
As you may find, loading pictures and converting them to textures can take a lot of time, so one improvement that you can make is to create a caching scheme. You can, for example, store the textures in a file, after converting them from a picture for the first time, so the next time the application is run, it will load the textures without having to do a conversion. Happy hacking!

Sunday, May 18, 2008

A simple class for playing music and sound effects for android

I think that android has a few problems in the area of sound/music playback. After much digging, the only way that I have found to play back music and sound effects is android.media.MediaPlayer. I wanted to add music and sound effects to my game monolithandroid. So I came up with the following class, aptly named SoundSystem. This class uses a separate thread for playback, because we don't want our sound to slowdown our game rendering.


package org.teacake.monolith;
import android.media.MediaPlayer;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
public class SoundSystem extends Thread
{
public SoundSystem(android.content.Context context)
{
action = SOUND_DO_NOTHING;
lastEventTime= SystemClock.uptimeMillis();
this.context = context;
try
{
musicPlayer = android.media.MediaPlayer.create(context, R.raw.intro);
soundPlayerRotateBlock = android.media.MediaPlayer.create(context, R.raw.rotate);
soundPlayerExplosion = android.media.MediaPlayer.create(context, R.raw.explosion2);
soundPlayerPlaceBlock = android.media.MediaPlayer.create(context, R.raw.place);
musicPlayer.prepare();
musicPlayer.setLooping(1);
musicPlayer.seekTo(0);
soundPlayerRotateBlock.prepare();
soundPlayerRotateBlock.seekTo(0);
soundPlayerExplosion.prepare();
soundPlayerExplosion.seekTo(0);
}
catch(Exception e)
{
}
}
private void startMusic()
{
if(musicPlayer==null)
{
return;
}
try
{
if(musicPlayer.isPlaying())
{
return;
}
else
{
musicPlayer.start();
}
}
catch(Exception e)
{
}
}
private void stopMusic()
{
if(musicPlayer==null)
{
return;
}
try
{
musicPlayer.stop();
}
catch(Exception e)
{
}
}
private void playRotateBlock()
{
int duration=0;
int currentPosition=0;
if(soundPlayerRotateBlock==null)
{
return;
}
try
{
duration=soundPlayerRotateBlock.getDuration();
currentPosition=soundPlayerRotateBlock.getCurrentPosition();
//if(currentPosition==duration-1)
{
soundPlayerRotateBlock.seekTo(0);
soundPlayerRotateBlock.start();
}
}
catch(Exception e)
{
}
}
private void playExplosion()
{
int duration=0;
int currentPosition=0;
if(soundPlayerExplosion==null)
{
return;
}
try
{
duration=soundPlayerExplosion.getDuration();
currentPosition=soundPlayerExplosion.getCurrentPosition();
//if(currentPosition==duration-1)
{
soundPlayerExplosion.seekTo(0);
soundPlayerExplosion.start();
}
}
catch(Exception e)
{
}
}
private void playPlaceBlock()
{
int duration=0;
int currentPosition=0;
if(soundPlayerPlaceBlock==null)
{
return;
}
try
{
duration=soundPlayerPlaceBlock.getDuration();
currentPosition=soundPlayerPlaceBlock.getCurrentPosition();
//if(currentPosition==duration-1)
{
soundPlayerPlaceBlock.seekTo(0);
soundPlayerPlaceBlock.start();
}
}
catch(Exception e)
{

}
}
@Override
public void run()
{
while(!done)
{
switch(action)
{
case SOUND_START_MUSIC:
startMusic();
break;
case SOUND_STOP_MUSIC:
stopMusic();
break;
case SOUND_PLAY_ROTATE_BLOCK:
playRotateBlock();
break;
case SOUND_PLAY_EXPLOSION:
playExplosion();
break;
case SOUND_PLAY_PLACE_BLOCK:
playPlaceBlock();
break;
}
action=SOUND_DO_NOTHING;
try
{
java.lang.Thread.currentThread().sleep(100);
}
catch(Exception e)
{
}
}
}
public boolean done;
public final Handler messageHandler = new Handler() {
@Override
public void handleMessage(Message msg)
{
try
{
action=msg.what;
}
catch(Exception e)
{
}
}
};
private android.content.Context context;
public long lastEventTime;
public int action;
public static final int SOUND_DO_NOTHING=-1;
public static final int SOUND_START_MUSIC=0;
public static final int SOUND_STOP_MUSIC=1;
public static final int SOUND_PLAY_ROTATE_BLOCK=2;
public static final int SOUND_PLAY_EXPLOSION=3;
public static final int SOUND_PLAY_PLACE_BLOCK=4;
private android.media.MediaPlayer musicPlayer;
private android.media.MediaPlayer soundPlayerRotateBlock;
private android.media.MediaPlayer soundPlayerExplosion;
private android.media.MediaPlayer soundPlayerPlaceBlock;
}


As you can see the run() method calls the appropriate method for playing each sound, depending on the message received. How do we send a message to the thread? By using the following code:


android.os.Message message = android.os.Message.obtain(soundSystem.messageHandler, SoundSystem.SOUND_START_MUSIC);
message.sendToTarget();


Notice that we use a MediaPlayer object for each sound. I don't know how heavyweight the MediaPlayer object is, and if it is suitable for use for playing 10's or even 100's of sounds. So that could be a problem if you use a lot of sounds for your game. Also, we use synchronous playback, and that could be a problem, too, if our sounds are long. But since it is only version 1.0, it will be improved

Thursday, May 15, 2008

Porting monolithandroid from m3 to m5


The application that I am developing, monolithandroid, currently does not run under m5 series SDK. Why? My app uses OpenGL/ES and there have been significant changes to the way OpenGL/ES works under android from m3 to m5. In a nutshell, in m3 you can use a plain view and intermix OpenGL and Canvas calls. In m5 this is not currently allowed. This was a major problem because the onPaint method of the View Object was used to draw text such as score and level information, as well as the application backdrop (a picture of the moon). There are ways to circumvent these problems and fix the application so that it works on m5. How? You can use a SurfaceView to draw the OpenGL objects, and you can use an overlay to draw text on top of the SurfaceView. And the backdrop? Actually you can use a textured image (an OpenGL object) to draw the backdrop, with the added bonus that you can easily rotate and scale the image. To sum up, monolithandroid under m3 used a class named GLView to draw everything on screen. For m5, 4 new classes were introduced in place of GLView:
  1. GameOverlay (used for drawing status text and scores)
  2. GameSurfaceView (used for holding a surface for OpenGL drawing)
  3. GLThread (used for actually drawing OpenGL objects)
  4. Square (used to load a textured square for the backdrop image of the moon)
In order to create an overlay, you can use the following code


public void onCreate(Bundle icicle) {
super.onCreate(icicle);
overlay = new GameOverlay(this);
optionsView = new OptionsView(getApplication());
android.content.AssetManager mgr = getApplication().getAssets();
gsf = new GameSurfaceView(this,overlay);
gsf.setViewType(GLThread.VIEW_GAME);
gsf.setGameType(Monolith.GAME_MONOLITH);
setContentView(gsf);
gsf.setVisibility(View.VISIBLE);
this.addContentView(overlay,new android.view.ViewGroup.LayoutParams(android.view.ViewGroup.LayoutParams.FILL_PARENT,android.view.ViewGroup.LayoutParams.FILL_PARENT));
}
You can browse the full code at http://code.google.com/p/monolithandroid/source/browse

It is under svn->trunk->monolithandroid->src->org->teacake->monolith
So after I made these changes, monolithandroid sort of works. However, there are some problems to be addressed, like the time it takes on startup to load the moon image and convert it into a texture.
I would like to take the opportunity to thank Ed Burnette for sending me a preview PDF of his forthcoming android book, "HelloAndroid", and I must say that it looks very, very promising. Way to go, Ed! You can find out more about it at: http://www.pragprog.com/categories/upcoming
I admit that I shamelessly copied some code (the code for loading textures) from it.
Also I would like to thank plusminus and all the guys at http://www.anddev.org where you can find many interesting tutorials, like the following: http://www.anddev.org/textured_cube_opengl_code_sample-t813.html
Excellent work, guys.

Friday, May 9, 2008

I did not win Android Developer Challenge but it's OK

A couple of hours ago I received an email from google that informed me that I am not one of the winner of Android Developer Challenge. To tell you the truth I was not dissapointed. I did not put a lot of hours behind my application, it did not exploit a lot of the platform features and more importantly, it was unoriginal. I am sure that Google's intent behind Android Developer Challenge is to promote original applications, that showcase their platforms strengths. MonolithAndroid (my app) on the contrary only shows OpenGL/ES and touch screen functionality.
To sum up (monolithandroid versus the winning apps):

So from the above, it is easy to see why my poor tiny app did not win ADC, and why I am not whining about it. The question is, can this meek app be transformed into a winner? I don't know, but I will do my best to make it better. And since it is open source, you can take a shot at it!
On Monday we will probably have more information about the winning applications, and see how good they are. Until then, congratulations to the winners (for winning!) and the loosers (for trying). Cheers, guys.

Search the web