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 ;-)
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:
Please give it a try and post your findings as comments to this chapter.
Meanwhile, let's start fixing the seven issues already identified.
I did a quick-fix for this using Gimp: Filters --> Decor -> Fuzzy Border. It was a great improvement, but still not quite 100%.
I can't really decide whether this is good or bad, so I'll leave this issue as is for now.
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); }
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.
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(); }
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 // areamissilesIterator.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(); }
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