You are here

Chapter 30: Playability Review #1

I finally got to my long awaited playability test, and it went totally great!

At least according to my definition of "great" - I found loads of issues and misfeatures that would have been detected by some poor end-user otherwise ;-)

  1. The images on the Main Menu look pretty bad. They have way too distinct edges to look good.
  2. If I rotate or thrust the ship with very fast "sweeps" on the screen, the ship both moves and fires a missile. I can't really decide if it's good or bad, though.
  3. The missiles appear "on top" of the ship when they are fired. It looks a bit fishy...
  4. The missiles are also quite big, and does not really look like missiles at all.
  5. The missiles are slow. Boringly slow.
  6. The life-cycle (or range) of the missiles feels awkward.
  7. Last but not least: If I fire away a whole bunch of missiles towards an asteroid, only the very last of them seems to actually hit. All other just disappear into thin air (or maybe vacuum, as this is in space).

 

This is actually golden opportunity to get some external feedback on the existing functionality.

Here is a link to the current version of the app: JustRoids_playability_review_1.apk. (If your web browser should rename the .apk-file to .zip, you will have to rename it back to .apk before installing it on your device.)

If you have the possibility, please download it and see what you think of the maneuverability, look-and-feel etc.

But do keep in mind that the game is far from complete. All that we have in the game is:

  • 5 Asteroids, that it should be possible to shoot at.
  • A ship that should turn red if it's hit by an asteroid.
  • Possibility to rotate or accelerate the ship.

Please give it a try and post your findings as comments to this chapter.

 

Meanwhile, let's start fixing the seven issues already identified.

 

Issue 1 - The images on the Main Menu look pretty bad

I did a quick-fix for this using Gimp: Filters --> Decor -> Fuzzy Border. It was a great improvement, but still not quite 100%.

 

Issue 2 - Fast sweeps make the ship fire as well

I can't really decide whether this is good or bad, so I'll leave this issue as is for now.

 

Issue 3 - The missiles appear on top of the ship

Well. The fix for this issue is almost too easy to describe ;-) In GameEngine.draw, we simply draw the missiles before we draw the ship:

public void draw(Canvas canvas) {
  canvas.drawRect(0, 0, canvas.getWidth(), canvas.getHeight(), blackPaint);
  asteroidHandler.draw(canvas);
  missileHandler.draw(canvas);
  ship.draw(canvas);
}

 

Issue 4 - The missiles are too big and look crappy

I really prefer to too keep the missiles circular, as they would have to be rotated when drawn otherwise. 

But a 10x10 pixels copy of missile_20.png, saved as missile_10 and the following change in MissileHandler's contructor:

public MissileHandler(Resources resources) {
  paint = new Paint();
  bitmap = BitmapFactory.decodeResource(resources, R.drawable.missile_10);
  missiles = new ArrayList<Missile>();
}

makes it look a whole lot better.

 

Issue 5 - The missile are too slow

Yes, they are. The missiles are created in MissileHandler.update, if there is a pendingEvent with the value EVENT_FIRE. The current velocity when creating new missiles is "3". A value of "6" is more like it:

public void update(float screenWidth, float screenHeight, int pendingEvent,
   float shipX, float shipY, int shipAngle, List<Asteroid> asteroids) {
  float dX;
  float dY;
  float distance;

  if (pendingEvent == GameEngine.EVENT_FIRE) {
   Missile newMissile = new Missile(bitmap, shipX, shipY, 6, shipAngle);
   missiles.add(newMissile);
  }
  Iterator<Missile> missilesIterator = missiles.iterator();
  Missile missile;
  while (missilesIterator.hasNext()) {
   missile = missilesIterator.next();
   missile.age++;
   if (missile.age > 25 * 3 || missile.collided == true) {
    // TODO: Hardcoded max age. Works better with quadratic game
    // area
    missilesIterator.remove();
   } else {
    missile.move(screenWidth, screenHeight);
    for (Asteroid asteroid : asteroids) {
     asteroid.collided = false;
     dX = Math.abs(missile.x - asteroid.x);
     dY = Math.abs(missile.y - asteroid.y);
     distance = (float) Math.sqrt(dX * dX + dY * dY);
     if (distance <= (missile.bitmap.getWidth() / 2 + asteroid.bitmap
       .getWidth() / 2)) {
      asteroid.collided = true;
      missile.collided = true;
     }
    }
   }
  }
  missiles.trimToSize();
}

 

Issue 6 - The missile's range is not quite perfect.

I guess it would feel a bit more natural if the missiles lived until they the reach the end of the screen instead of a certain amount of time. To accomplish this, we can add a new member variable and a getter function in GfxObject:

private boolean looped = false;
 
public boolean isLooped() {
  return looped;
}

and make a few changes in GfxObject.move:

public void move(float screenWidth, float screenHeight) {
  float minX = 0 - bitmap.getWidth() / 2;
  float maxX = screenWidth + bitmap.getWidth() / 2;
  float minY = 0 - bitmap.getHeight() / 2;
  float maxY = screenHeight + bitmap.getHeight() / 2;

  looped = false;
  x += dX;
  y += dY;
  if (x > maxX) {
   x = minX;
   looped = true;
  }
  if (x < minX) {
   x = maxX;
   looped = true;
  }
  if (y > maxY) {
   y = minY;
   looped = true;
  }
  if (y < minY) {
   y = maxY;
   looped = true;
  }

  angle += rotation;
  if (Math.abs(angle) >= 360)
   angle = 0;
}

The last change is in MissileHandler.update, where we remove the age control and replace it with checking whether the missile was "looped" during the last update:

public void update(float screenWidth, float screenHeight, int pendingEvent,
   float shipX, float shipY, int shipAngle, List<Asteroid> asteroids) {
  float dX;
  float dY;
  float distance;

  if (pendingEvent == GameEngine.EVENT_FIRE) {
   Missile newMissile = new Missile(bitmap, shipX, shipY, 6, shipAngle);
   missiles.add(newMissile);
  }
  Iterator<Missile> missilesIterator = missiles.iterator();
  Missile missile;
  while (missilesIterator.hasNext()) {
   missile = missilesIterator.next();
   // missile.age++;
   if (missile.isLooped() || missile.collided == true) {
    // TODO: Hardcoded max age. Works better with quadratic game
    // area
    missilesIterator.remove();
   } else {
    missile.move(screenWidth, screenHeight);
    for (Asteroid asteroid : asteroids) {
     asteroid.collided = false;
     dX = Math.abs(missile.x - asteroid.x);
     dY = Math.abs(missile.y - asteroid.y);
     distance = (float) Math.sqrt(dX * dX + dY * dY);
     if (distance <= (missile.bitmap.getWidth() / 2 + asteroid.bitmap
       .getWidth() / 2)) {
      asteroid.collided = true;
      missile.collided = true;
     }
    }
   }
  }
  missiles.trimToSize();
}

 

Issue 7 - Only the last missile hit it's target

Time to fix the most disturbing issue of them all in the playability review...

I must have had some kind of black-out when I wrote the collision detection in the MissileHandler. I specifically set collided to false for each asteroid before I check for collisions, which means that only the last missile in the list has any real impact. Just as we observed during the review. So, the very last change in this chapter is also in Missilehandler.update:

public void update(float screenWidth, float screenHeight, int pendingEvent,
   float shipX, float shipY, int shipAngle, List<Asteroid> asteroids) {
  float dX;
  float dY;
  float distance;

  if (pendingEvent == GameEngine.EVENT_FIRE) {
   Missile newMissile = new Missile(bitmap, shipX, shipY, 6, shipAngle);
   missiles.add(newMissile);
  }
  Iterator<Missile> missilesIterator = missiles.iterator();
  Missile missile;
  while (missilesIterator.hasNext()) {
   missile = missilesIterator.next();
   if (missile.isLooped() || missile.collided == true) {
    missilesIterator.remove();
   } else {
    missile.move(screenWidth, screenHeight);
    for (Asteroid asteroid : asteroids) {
     //asteroid.collided = false;
     dX = Math.abs(missile.x - asteroid.x);
     dY = Math.abs(missile.y - asteroid.y);
     distance = (float) Math.sqrt(dX * dX + dY * dY);
     if (distance <= (missile.bitmap.getWidth() / 2 + asteroid.bitmap
       .getWidth() / 2)) {
      asteroid.collided = true;
      missile.collided = true;
     }
    }
   }
  }
  missiles.trimToSize();
}

This last change concludes the actions to solve the issues identified during the first playability review, and it actually feels like we are more or less finished with the central parts of the game!

The upcoming chapters will focus on handling levels, adding status displays and so on.

 

Theme by Danetsoft and Danang Probo Sayekti inspired by Maksimer