Friday, 10 October 2014

The Legend of Edgar 1.18

The Legend of Edgar has been updated to 1.18

* Updated German translation

Download 1.18 from Sourceforge

Help translate the game into your native language

Original sound effects and music

Monday, 1 September 2014

Summation

Summation is a match 3 game, but with a twist. You must match tiles in order to reach a target number. To aid you in this task, you can use mathematical operators to affect how your matched tiles will behave, so you can add, subtract, multiply and divide your way to victory.

On some stages, you will have unlimited operators, but on others, you will need to match them first. In addition to this, some stages will have tiles that change the target number, shuffle the board or randomly change their value.

There are 60 stages and thousands of randomly generated ones to choose from. The randomly generated stages can be rated (provided you complete it) so you can let other players know what fun levels there are to play.

Click here to download it from the Google Play store.

Sunday, 22 June 2014

Eclipse crashing in KDE

I upgraded to Kubuntu 14.04 yesterday, and my Eclipse would constantly crash. Fortunately, I found a solution.

Go to your System Settings -> Application Appearance and select the GTK section.

Change the GTK2 Theme to anything apart from oxygen-gtk (personally, I downloaded the gtk2-engines and applied Clearlooks).


Your Eclipse should work happily again after this.

Sunday, 1 June 2014

The Great Race

The Great Race is a casual endless runner for Android. You can choose from 8 different characters and compete against other players across the to world to see if you can run further than anyone else that day.


You can download the game by following the link below

https://play.google.com/store/apps/details?id=com.parallelrealities.thegreatrace

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

Download 1.16 from Sourceforge

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;

 private TextureLoader()
 {
 }

 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.glShadeModel(GL10.GL_SMOOTH);
  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.glLoadIdentity();
  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
   {
    Thread.sleep(16 - (diff));
   }
   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.glLoadIdentity();

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

  gl.glMatrixMode(GL10.GL_MODELVIEW);
  gl.glLoadIdentity();

  TextureLoader.gl = gl;

  rect[0] = new Rectangle(TextureLoader.loadTexture(context.getResources().openRawResource(R.raw.aquabob)));
  rect[1] = new Rectangle(TextureLoader.loadTexture(context.getResources().openRawResource(R.raw.bob)));
  rect[2] = new Rectangle(TextureLoader.loadTexture(context.getResources().openRawResource(R.raw.bobjp)));
  rect[3] = new Rectangle(TextureLoader.loadTexture(context.getResources().openRawResource(R.raw.cherry)));
  rect[4] = new Rectangle(TextureLoader.loadTexture(context.getResources().openRawResource(R.raw.crate)));
  rect[5] = new Rectangle(TextureLoader.loadTexture(context.getResources().openRawResource(R.raw.pistolblob)));
  rect[6] = new Rectangle(TextureLoader.loadTexture(context.getResources().openRawResource(R.raw.pistoldroid)));
  rect[7] = new Rectangle(TextureLoader.loadTexture(context.getResources().openRawResource(R.raw.plasmarifle)));
  rect[8] = new Rectangle(TextureLoader.loadTexture(context.getResources().openRawResource(R.raw.straightbullet)));

  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