Tuesday, December 23, 2008

MonolithAndroid needs you!

That is, I need beta testers to try the game out on a real hardware device like the T-Mobile G1. Yesterday, I published MonolithAndroid version 1.0.0 on the android market. Because I live in Greece, I cannot get hold of an actual G1 Phone, so I only tested my application on the emulator. And it seems that testing on the emulator is not enough. Hours after monolithandroid was published on android market, I got a couple of emails saying that the program presented them with a black screen and they only heard some music but they saw no graphics. So I unpublished the application. And here's where I need your help.
I would like you to download the installation package from http://code.google.com/p/monolithandroid and try it out on your TMobile G1. And here's what I would like to know:

1. Does the application start?
2. Do you hear music?
3. Can you see the letters MonolithAndroid going on and off?
4. You only see a black screen?
5. If the game seems to run, does it run smoothly, or it feels sluggish?

Tuesday, September 23, 2008

Some thoughts on google android as a gaming platform

With the launch of the first android device (T-Mobile G1) just around the corner, I would like to share with you a few thoughts regarding the viability of the android platform for gaming purposes.
Android has rich multimedia and graphics api's:
1.Support for openGL/ES and 2D drawing.
2.Media api's.
3.Support for sensors (GPS and accelerometer).
4.Networking.
5.Optional touch interface.

So in theory, it should be ideal for gaming, right?

Well yes, but there are some caveats:
1. Not all devices will support touch interfaces and sensors.
2. Not all devices will have the same screen size.
3. For gaming, some sort of joystick or D-PAD device is essential. A D-PAD was standard in the various emulator images, since the first SDK, but the first device (The T-Mobile G1) comes without a D-PAD! Instead, we get a tiny trackball, not the best IO device for gaming!
So the problem is that as it seems, we will have a lot of different devices, with different configurations. Some will have touch interfaces, some will not, some will have a D-PAD, some will have trackballs, some will have acceleration sensors and so on. So, a game developer that develops for android will have the same kind of problems that a PC game programmer has. Different screen sizes and IO devices and so on... That increases the amount of time and resources a developer has to use in order to make his game playable on most phones. The same problems that drive a lot of PC game developers to game consoles.

So how can this be remedied? Well I guess that one possible solution is the introduction of a limited set of device profiles. Something like basic, gaming and advanced profiles, that will provide the developer with the same set of IO features.
Otherwise, I see a lot of developers fleeing to the apple iPhone, with it's game console simplicity, rather than the android's PC like capabilities.

I'd love to see your opinion on those thoughts, so shoot!

Tuesday, August 26, 2008

Yet another SDK update

I just returned from my summer holidays and found a brand new android SDK (0.9beta) available for download. I downloaded it, and found that it introduces a lot of changes. Packages were renamed and openGL/ES programming has changed quite a lot. Even menus are now different. So a lot of work is required to update monolithandroid in order to make it work with this SDK. Maybe I will be able to pump out a new, compatible version till the end of the week. Since this SDK is a 0.9 beta version, not a lot of things will change in the final 1.0 release and that means that the next releases will require a lot less work. And since the first android devices are scheduled for release in Q4, maybe we will be able to get our hands on a device just in time for Christmas. Now I must leave you and return to my project. I have a lot of work to do and correct some nasty bugs. Stay tuned!

Monday, August 4, 2008

Summer <> development

Summer (especially in Greece) is really counterproductive. While the android engineers are working hard to put the finishing touches on our beloved operating system, I am going to the beach every day, drinking beer and coffee, and enjoying the summer vacations. No time for android programming. Tomorrow, my girlfriend and I are going to Crete for a twenty day vacation. We are going to visit our friends in Chania, and go to the archeological sites of Knossos and Faistos. We will end up camping in Gaidouronisi and relax next to the sea. When we return I hope that a new android SDK will be available for download and that one or more devices will be scheduled for release before the end of the year. Next summer I hope that I will be travelling with my android phone.
For those who are lucky enough to be on vacation, enjoy the sea and the sun. For those of you who are still working, take care guys, and don't burnout. Take a break, too.

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

Search the web