## Monday, 26 May 2014

### The Legend of Edgar 1.16

The Legend of Edgar has been updated to 1.16

* Updated Brazilian Portuguese, Dutch, Italian and Russian translations

Help translate the game into your native language

Original sound effects and music

## Tuesday, 13 May 2014

### Android OpenGL 2D Skeleton app

When I first started working on Blob Wars : Attrition, I was working Android's canvas view. However, I soon found that this wasn't up to the task; it was far too slow. I therefore decided to investigate switching to OpenGL. This has given a lot of people grief in the past, but after a lot of investigation, I managed to get it working to my satisfaction - i.e: 2d ortho mode, with the y coordinate being at the top of the screen.

I thought I would share the code for this, to help others along the way. At the bottom of this page, you can find a link to both an APK and the source code for this app. You can use it for a template for your own apps, as you desire. However, you MUST credit me (Stephen J Sweeney).

The app will basically fling 400 OpenGL rectangles around the screen, and print the frames per second. It might not sound like anything special, but it will prove a big help for anyone looking for a template to get started. On my Galaxy S3, this app runs at 60fps.

We'll start with the Rectangle class, which will do the main work for pushing the vertex and coord data to the GPU. This class might look complex, but it's rather simple. I won't go into the whole vertex alignment stuff, mostly because I've forgotten. It works, and that's what matters to me.

```public class Rectangle
{
private final FloatBuffer vertexBuffer;
private final float   vertices[]  = new float[12];

private final FloatBuffer textureBuffer;
private final float   textureCords[] = { 0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f };

private final short[]  indices   = { 0, 1, 2, 0, 2, 3 };

private final ShortBuffer indexBuffer;

public Texture    texture;

public Rectangle(Texture texture)
{
this(texture.width, texture.height);

this.texture = texture;
}

public Rectangle(int w, int h)
{
// bottom left
vertices[0] = 0;
vertices[1] = h;

// bottom right
vertices[3] = w;
vertices[4] = h;

// top left
vertices[6] = 0;
vertices[7] = 0;

// top right
vertices[9] = w;
vertices[10] = 0;

ByteBuffer byteBuffer = ByteBuffer.allocateDirect(vertices.length * 4);
byteBuffer.order(ByteOrder.nativeOrder());
vertexBuffer = byteBuffer.asFloatBuffer();
vertexBuffer.put(vertices);
vertexBuffer.position(0);

byteBuffer = ByteBuffer.allocateDirect(indices.length * 2);
byteBuffer.order(ByteOrder.nativeOrder());
indexBuffer = byteBuffer.asShortBuffer();
indexBuffer.put(indices);
indexBuffer.position(0);

byteBuffer = ByteBuffer.allocateDirect(textureCords.length * 4);
byteBuffer.order(ByteOrder.nativeOrder());
textureBuffer = byteBuffer.asFloatBuffer();
textureBuffer.put(textureCords);
textureBuffer.position(0);
}

public void set(int w, int h)
{
vertices[1] = h;
vertices[3] = w;
vertices[4] = h;
vertices[9] = w;
vertexBuffer.put(vertices);
vertexBuffer.position(0);
}

public void setCoords(float x1, float y1, float x2, float y2)
{
textureCords[0] = x1;
textureCords[1] = y2;

textureCords[2] = x2;
textureCords[3] = y2;

textureCords[4] = x1;
textureCords[5] = y1;

textureCords[6] = x2;
textureCords[7] = y1;

textureBuffer.put(textureCords);
textureBuffer.position(0);
}

public void draw(GL10 gl)
{
if (texture != null)
{
gl.glBindTexture(GL10.GL_TEXTURE_2D, texture.id);
}

gl.glFrontFace(GL10.GL_CW);

gl.glVertexPointer(3, GL10.GL_FLOAT, 0, vertexBuffer);

if (texture != null)
{
gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, textureBuffer);
}

gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, vertices.length / 3);
}

public static void begin(GL10 gl)
{
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
}

public static void end(GL10 gl)
{
gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
}
}
```

The class takes either a Texture class (see later) or can be constructed by supplying the width and height. The later is useful if you're simply drawing a square, such as a status bar. The set() and setCoords() methods can be used to change the rectangle's size and texture coords, useful when performing operations such as drawing text.

Two of the most important methods in this class are the begin() and end() calls, which start all the drawing operations. Technically, we don't have to put these here; we could quite easily most them some place else. They're here just to keep things tidy and keep related functionality together.

Next, we'll look at the Texture class.

```public class Texture
{
public final int id;
public final int width;
public final int height;

public Texture(int id, int width, int height)
{
this.id = id;
this.width = width;
this.height = height;
}
}
```

That's it. It's a bean holding the width, height and OpenGL texture pointer int. Nothing special. We feed this into the Rectangle class. Now that we've got those two classes set up, we can look at loading some textures:

```public class TextureLoader
{
public static GL10   gl;

private static final int textureIds[] = new int[1024];
private static int   n;

{
}

public static Texture loadTexture(final InputStream in)
{
final Bitmap bitmap = BitmapFactory.decodeStream(in);

return generateTexture(bitmap);
}

public static Texture generateTexture(final Bitmap bitmap)
{
final int texture[] = new int[1];

gl.glGenTextures(1, texture, 0);

gl.glBindTexture(GL11.GL_TEXTURE_2D, texture[0]);

gl.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_NEAREST);
gl.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
gl.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP_TO_EDGE);
gl.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP_TO_EDGE);

GLUtils.texImage2D(GL11.GL_TEXTURE_2D, 0, bitmap, 0);

bitmap.recycle();

textureIds[n++] = texture[0];

return new Texture(texture[0], bitmap.getWidth(), bitmap.getHeight());
}

public void free()
{
gl.glDeleteTextures(n, textureIds, 0);
}
}
```

Again, what's going on here should be quite obvious. This class provides some static methods that take either a Bitmap or an InputStream and creates a texture. A couple of notes - we set GL_CLAMP_TO_EDGE to prevent artefacts from showing at the edges of our textures when they're displayed. We also store the texture id in a static array. We do this so that we can delete them when we're done with them (using free()). While the Android OS will delete textures itself when the app finishes, we might want to delete them ourselves at certain points.

We can now move on to getting things to display:

```public class OpenGLView extends GLSurfaceView
{
private final GLRenderer renderer;

private final float   scaleX;
private final float   scaleY;

private final int   SCREEN_WIDTH = 480;
private final int   SCREEN_HEIGHT = 800;

public OpenGLView(final Context context)
{
super(context);

final DisplayMetrics metrics = context.getResources().getDisplayMetrics();
scaleX = ((float) metrics.widthPixels) / SCREEN_WIDTH;
scaleY = ((float) metrics.heightPixels) / SCREEN_HEIGHT;

System.out.format("Target dimensions: %dx%d\n", SCREEN_WIDTH, SCREEN_HEIGHT);
System.out.format("Device dimensions: %dx%d\n", metrics.widthPixels, metrics.heightPixels);
System.out.format("Ratios: %.2fx%.2f\n", scaleX, scaleY);

renderer = new GLRenderer(context, scaleX, scaleY);

setRenderer(renderer);
}
}
```

OpenGLView sets up our renderer. We target a screen size of 480x800, and grab the screen resolution of the device we're running on, so that we can scale thing later. If we don't do this, then we could send up with a small portion on the screen being used on some devices, or the display being too large on others.

Now we come to the big one - GLRenderer, where all the work is done. Again, but class might look complex, but it's very straightforward.

```public class GLRenderer implements GLSurfaceView.Renderer
{
private final Rectangle rect[];

private long   then;
private int    frames;
private final float  scaleX;
private final float  scaleY;
private final Entity entities[];
private int    fps;
private final Context context;

public GLRenderer(final Context context, float scaleX, float scaleY)
{
this.context = context;
this.scaleX = scaleX;
this.scaleY = scaleY;

rect = new Rectangle[9];

entities = new Entity[400];
for (int i = 0; i < entities.length; i++)
{
entities[i] = new Entity();
}
}

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config)
{
gl.glClearColor(0.1f, 0.1f, 0.1f, 0.0f);
gl.glClearDepthf(1.0f);
gl.glEnable(GL10.GL_DEPTH_TEST);
gl.glDepthFunc(GL10.GL_LEQUAL);
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);
}

@Override
public void onDrawFrame(GL10 gl)
{
for (int i = 0; i < entities.length; i++)
{
entities[i].move();
}

gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);

gl.glScalef(scaleX, scaleY, 0.0f);

gl.glEnable(GL11.GL_TEXTURE_2D);
gl.glEnable(GL11.GL_BLEND);
gl.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA);

Rectangle.begin(gl);

for (Entity e : entities)
{
gl.glPushMatrix();
{
gl.glTranslatef((float) e.x, (float) e.y, 0);
gl.glColor4f(e.r, e.g, e.b, 1.0f);
e.rect.draw(gl);
}
gl.glPopMatrix();
}

gl.glPushMatrix();
{
gl.glTranslatef(12, 12, 0);

gl.glColor4f(0.0f, 0, 0, 0.0f);
gl.glTranslatef(4, 4, 0);
FontManager.INSTANCE.drawText(35, "FPS: " + fps, gl);

gl.glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
gl.glTranslatef(-4, -4, 0);
FontManager.INSTANCE.drawText(35, "FPS: " + fps, gl);
}
gl.glPopMatrix();

Rectangle.end(gl);

gl.glDisable(GL11.GL_TEXTURE_2D);
gl.glDisable(GL11.GL_BLEND);

frames++;

if (System.currentTimeMillis() - then >= 1000)
{
fps = frames;
frames = 0;
then = System.currentTimeMillis();
}

long now = System.currentTimeMillis();
long diff = now - then;

sleep(diff);
}

private void sleep(long diff)
{
if (diff < 16)
{
try
{
}
catch (InterruptedException e)
{
}
}
}

public int getFPS()
{
return fps;
}

@Override
public void onSurfaceChanged(GL10 gl, int width, int height)
{
gl.glViewport(0, 0, width, height);

gl.glMatrixMode(GL10.GL_PROJECTION);

gl.glOrthof(0.0f, width, height, 0.0f, 1.0f, -1.0f);

gl.glMatrixMode(GL10.GL_MODELVIEW);

for (int i = 0; i < entities.length; i++)
{
entities[i].rect = rect[i % 9];
}
}
}
```

In the constructor, we create 400 entities, 9 rectangles (to hold the sprites), and grab the scale of the display for later scaling.

onSurfaceChanged() is where we set up our ortho mode, and where we load the textures that we wish to use. We then randomly set a rectangle (with a texture) to each of our entities.

onDrawFrame() is where all the grunt work is done. We first move our entities (do the logic), and then prepare to draw. After calling glLoadIdentity() we set the glScale to get the appropriate ratios. We then call Rectangle.begin() to start the drawing process, and loop through all our entities, calling draw on each of the entity's rectangles. Finally, we calculate and print the frames per second.

That should be enough to get most people up and running. You can download the source code below, as well as an APK that can be run on your device.

Source code: www.stephenjsweeney.com/games/AndroidSkeletonGL.zip
APK: www.stephenjsweeney.com/games/AndroidSkeletonGL-1.0.apk

## Sunday, 11 May 2014

### Blob Wars : Attrition now available for Android!

There's a new Blob Wars game available! Huzzah! Blob Wars : Attrition is available for Android devices, and is set between the events of Metal Blob Solid and Blob and Conquer.

The gameplay is very much like Metal Blob Solid - 2D platforming action, with multiple objectives and missions. It also features a non-linear mission structure, so that the player can (mostly) choose the order in which their should tackle the missions.

The aqua lung and jetpack make a return, and all the familiar weapons are there to enjoy: pistol, plasma rifle, spread gun, grenades, and laser. Assimilated Blobs and Eye Droids are also present in healthy doses, and naturally there are boss fights!

The game works very well on mobile devices, if I do say so myself, much better than I was originally expecting. The on-screen controls are responsive and the game is easy to play.

The full game is now available from Google Play, as a free download. This is a time-limited trial, after which the full game can be purchased using an in-app purchase.