Saturday 25 August 2012

First 3D project

As expected, I ended up starting a new project... It's quite a leap up from what was achieved in Race Game.

Laptop video (shows 2D and 3D rendering plus raw map files):


Android video (shows smooth 3D rendering on android device):


Next steps will include:
  • Control scheme to suit android devices
  • Nice 3D models for keys, treasures, etc
  • Animated opening doors (quite how is a mystery!)
  • Cartoony / cutesy graphics and sound
  • Level select screen
  • Level complete screen
  • Game complete screen
  • Efficiency improvements
  • Lots more level maps
I think I'm going to drop the 'score' aspect, instead an achievement based level rating sounds good... Finish level for one star and ability to progress to next level, finish level having collected all treasure awards additional star, finish level in under X seconds awards additional star. Quite easy to progress through the levels, but replays required to get 100% perfect three star level rating.

Interestingly, Race Game has a fair number of android 1.6 users. Considering how few 1.6 devices exist, it's clear that there are people out there with low spec devices that want to play casual fun games. With this in mind, I'm going to keep striving for as near global compatibility as possible.

This game will be an arcade/maze/puzzler/adventure mashup. I'm going to leave out enemies, make it more about getting to the exit quickly, exploring, etc. This simplifies things for me, and should result in a game that appeals to quite a wide market.

A separate game that introduces first person shooter elements (zombies, weapons, and a darker art style) could be built on top of this base later on.

The code is freely available here, and there's a demo up on google play - check it out!


          

Sunday 12 August 2012

Racing Game - Powering on ahead!

I've neglected the blog for a few days, spent my time working on the app rather than writing about it. As a result, quite a lot of progress has been made in the past few days :)

Here's a video of the app as it currently stands:



Wednesday 8 August 2012

Racing Game - limiting scope, wishlist culling, and task prioritisation (or, The battle against hobbyist A.D.D.)

I have a horrible habit of starting a project, having great fun with it, then either losing interest, or heading off in a different direction to what was originally planned, unnecessarily complicating things, and giving up. End result is an unfinished project kicked to the kerb and a new one started. Repeat ad infinitum. It's like hobby ADD. I have so many started but unfinished projects it's not even funny. I know I'm not the only one!

This time it'll be different I said. This time I'll see it through til the end... But I so want to start a new project. I want to make a start on a platform game, Super Mario 3 on the NES is still one of the best games ever in my opinion, I want to make something similar. I want to create an arcade blaster, I loved Spy Hunter as a kid, I want to remake it for android, maybe 2D gameplay with 3D graphics. I want to create a basic 3D engine, maybe start with a first person perspective maze game, and extend to create a first person shooter, or a first/third person driving sim. I have lots of ideas, and I want to start on them all at once. So much ambition, so little attention span...

I'm pleased that I got further than usual this time - I've actually published an early alpha of Race Game on Google Play. I think it's in no small part due to me publishing this worklog and publicly sharing the code on github, there may not be that many visitors (yet?), but I still have a funny feeling of responsibility to get something finished. What's currently on Google Play is very much unfinished, so I'm definitely not off the hook yet (I won't be until I hit v1.0 at least), but it still feels like a mini victory over my hobby ADD :)

I've been reviewing the ideas/todo list and have decided to be pretty ruthless in terms of what stays and what goes. What's staying has been split into 'must haves' and 'would likes', and each list has then been prioritised.

I think it's fair to say that until all the 'musts' have been tackled the game is essentially broken, pretty good definition of must have really... Once all the 'musts' have been taken care of, I can package the app up as v1.0, update on Google Play, and go on to start a new project with a clear conscience.

The 'would like' items can be deferred to a later update. None are crucial to the functioning of the game. Only reason facebook bragging is at the top of the list is because it interests me personally, I think it would be a cool feature to re-use in future games (players post achievements to their wall, friends download the app to compete... a kind of viral promotional platform). Variable difficulty should be easy enough to implement, good bang for buck ratio (players could choose a quick course to kill 30 seconds, or a longer course when they're feeling a bit more hardcore etc, tailor game to the player for relatively small effort on my part), might as well get it done. The rest of the list are items that simply add a little polish to the game. I like to keep reminding myself that atari/spectrum games were ugly as sin but good fun, so I'm not too worried about this stuff really (just as well, I'm no artist!). Learning a little about animation and intro/outro scenes would help me when working on future projects, so I might have a little poke around in those areas, but we'll see.

The 'dropped' items won't be implemented in this project. I would like this project to remain simple and easy to read when finished. I know it'll help me if I ever need to quickly check back to see how I solved a particular problem in the past, maybe it'll help others too. The simpler and clearer the better, so I don't want to pollute it with too many extra features and it ending up a confused mess. There's nothing to stop me (or someone else) coming back and making Race Game Deluxe in the future, but it will be a seperate project, this particular project won't live forever, it will serve its purpose and then development will stop.

So, it looks like I have a plan. Great. As an added bonus, that 'must' list looks quite small and manageable, which is nice. I'm quite confident that I'll be able to get a modest v1.0 published on Google Play soon, and only then start tinkering with 3D mazes and tiled maps and other cool stuff... I guess the question of if/when v1.1 will see the light of day depends on how interesting the next project is, but I'm determined to get v1.0 out there pre-September (I got this far in 10 days of spare time so that should be a very achievable goal)!


          

Tuesday 7 August 2012

Racing Game - Too early to go alpha?

I've still not taken care of my housekeeping tasks, but I do have a fully functional HUD now - this makes me feel a little guilty but a lot happy :)


Monday 6 August 2012

Racing Game - HUD and Timer



As expected, I left the housekeeping for a while and carried on with the fun stuff :) There's a couple of things worth a little mention today:
  1. How to render a heads up display
  2. Introduction of a new timer class

Friday 3 August 2012

Racing Game – Bobo’s end


In my last post I mentioned that having play tested a little I wasn’t really satisfied with how the game played. The levels were too blocky, character movement felt like it was in the wrong direction, and the story/theme was a bit forced and rubbish.
The simplest way to reduce the blockiness of the level would be to simply shink the tile sizes. Creating loads of block objects seemed a bad idea though (taking it to the extreme there could be one per pixel!). Instead I went back to the drawing board to rethink the level concept. Here’s a simple definition of a level:
  • A level row is 10px high and 240px wide
  • The viewable portion of the level is 320px high (32 x rows)
  • An example row could be: 80px wide grass, 80px wide road, 80px wide grass
  • The road sections are the same width in all the levels rows
  • The left grass section can increase/decrease in width by up to 4px per consecutive row
  • The right grass section takes up the remainder of the levelRow width
With these rules defined, I went on to rewrite the level generation code:
 public Level(int screenWidth, int roadWidth, int roadOffset, int levelLength, int rowHeight) {  
  this.screenWidth = screenWidth;  
  this.roadWidth = roadWidth;  
  this.roadOffset = roadOffset;  
  this.levelLength = levelLength;  
  this.rowHeight = rowHeight;  
  this.levelRowRoadOffset = new int[levelLength];  
  offroadAreas = new Array();  
  generateOffsets();  
  generateOffroadAreas();  
  generateFinishLine();  
  car = new Car(new Vector2((screenWidth/2)-(Car.WIDTH/2), 20));  
 }  
 private void generateOffsets() {  
  for (int row=0; row<levelLength; row++) { // for each row in the level  
  levelRowRoadOffset[row] = roadOffset;  
  // generate a random integer (between -4 and +4)  
  int modifier = (randomGenerator.nextInt(9)) - 4;  
  System.out.println("Modifier: "+modifier);  
  // apply modifier offset provided road would remain in bounds  
  if ((roadOffset + modifier >= 1) && (roadOffset + modifier + roadWidth <= screenWidth)) {  
   roadOffset += modifier;  
  }  
  }  
 }  
 private void generateOffroadAreas() {  
  for (int row=0; row<levelLength; row++) { // for each row in the level  
  OffroadArea leftGrass = new OffroadArea(0, row * rowHeight, levelRowRoadOffset[row], rowHeight);  
  offroadAreas.add(leftGrass);  
  OffroadArea rightGrass = new OffroadArea(levelRowRoadOffset[row] + roadWidth, row * rowHeight, screenWidth - (levelRowRoadOffset[row] + roadWidth), rowHeight);  
  offroadAreas.add(rightGrass);  
  }  
 }  
 private void generateFinishLine() {  
  finishLine = new FinishLine(0, levelLength * rowHeight, screenWidth, 40);  
 }  

Bobo became Car, Block became OffroadArea, and there were a few little changes to these classes, nothing really worth mentioning here. The drawing routine in GameScreen changed a little:
 for(OffroadArea offroadArea: level.getOffroadAreas()) {  
  spriteBatch.draw(grassTextureRegion, offroadArea.getBounds().x, offroadArea.getBounds().y, offroadArea.getBounds().width, offroadArea.getBounds().height);  
 }  

If you’d like to see the full extent of the changes made, the complete source can be found here.


What next?

Lots to do…
  • I can think of a couple of of efficiency improvements to do – there’s no sense in checking the whole level for collisions, and the same goes for rendering rows that are offscreen (espcecially those you have passed and won’t possibly see again).
  • I think maybe there should be subclasses of OffroadArea, ice, grass, rocks, finish line, etc, all with different behaviour, but for now I’ll leave things as they are, “it aint broke, why fix it”.
  • The level may need a little tweaking, certainly in later levels the game would need to get tougher. Deceleration when offroad needs tweaking too, it’s too sudden at the moment.
  • There’s a cheat possible where if you leave the screen bounds you race ahead, I’ll need to constrain car to world bounds.
  • The timer needs to be reintroduced, current time should be displayed oncreen, and high scores should be saved to persistent memory.
  • A delay before starting the race would be nice, maybe a traffic light graphic or “3, 2, 1, Go!” message.
  • The finish line could be improved, maybe a nice animation of the car driving offscreen?
  • Sound effects would be nice.
  • Maybe the car should rotate a little when ‘steering’?
  • Maybe some dirt particles should get kicked up when the car is offroad?
  • I’m already thinking about the third dimension. I need to forget about this for now if I ever want to produce a finished game. Maybe this game should be RaceGame2D, and RaceGame3D could be another project for another time!
There’s no doubt loads more to do that I’ve not thought of yet. I think I have plenty to be getting on with though! Don’t really know what I’ll tackle next, so the next update will be a lucky dip! Until then, here’s a bonus video of the basic game as it currently stands :)
          

Thursday 2 August 2012

Run Bobo Run – Basic game mechanic complete



This update was good fun to write, feels like quite a lot was achieved in not a lot of code :) I've now implemented collision detection, tilt control, a level timer, and world forces (gravity and terminal velocity).

Collision Detection


I added a bounding box to both the Bobo and Block classes. Bobo's bounds are updated whenever he is updated, this is not necessary in the case of static Block objects. The main game loop calls checkCollisions() each cycle, this is an inefficient but perfectly functional collision checking method:

private void checkCollisions() {
   int numBlocks = level.getBlocks().size;
   for (int i = 0; i < numBlocks; i++) {
      Block block = level.getBlocks().get(i);
      if (block.getBounds().overlaps(level.getBobo().getBounds())) {
         level.getBobo().hitBlock();
      }
   }
}

If any Block bounding boxes overlap Bobo's bounding box, call bobo's hitBlock() method, which slows him right down:

public void hitBlock() {
   velocity.set(0, -20);
}

The efficiency of the collision checking routine could be massively improved by only checking for collisions with nearby blocks that Bobo could possibly collide with (as opposed to every single block in the level), but I'm lazy and this will do the job for now...

Tilt Control


I made some changes to the handleInput method:

private void handleInput(float deltaTime) {
   if (Gdx.app.getType() == Application.ApplicationType.Android) {
      level.getBobo().moveBobo(deltaTime, Gdx.input.getAccelerometerX() * -50);
   }
   if (Gdx.app.getType() == Application.ApplicationType.Desktop) {
      float moveBobo = 0;
      if(Gdx.input.isKeyPressed(Input.Keys.LEFT)) moveBobo = -100f;
      if(Gdx.input.isKeyPressed(Input.Keys.RIGHT)) moveBobo = 100f;
      level.getBobo().moveBobo(deltaTime, moveBobo);
   }
}

The if statements allow for platform specific controls - on Android use the accelerometer, on the desktop use the cursor keys. The user input results in a call to Bobo's moveBobo() method:

public void moveBobo (float deltaTime, float moveBobo) {
   velocity.x = moveBobo;
   update(deltaTime);
}

Basically, alter Bobo's velocity in accordance with the passed in parameters, then call bobo's update method:

public void update(float deltaTime) {
   if (-velocity.y < TERMINAL_VELOCITY) {
      velocity.add(Level.gravity.x * deltaTime, Level.gravity.y * deltaTime);
   }
   position.add(velocity.x * deltaTime, velocity.y * deltaTime);
   bounds.set(position.x, position.y, WIDTH, HEIGHT);
}

If Bobo has not yet reached his terminal velocity on the y-axis, apply acceleration/gravity (a property of Level - "gravity = new Vector2(0, -30);") to his velocity, then update his position in respect of his velocity (and the time since last update), and update his bounding box to match his new position.

Level Timer


Finally, there's a timer to keep track of how long it takes to get to the end of the level. Currently it just outputs a message to the console, but that's enough to prove it works.

When the level is created, the current time is recorded (startTime = TimeUtils.nanoTime();).
checkGameover() is called on each cycle of the main game loop:
private void checkGameover() {
   if (level.getBobo().getPosition().y <= level.getLevelEnd())
      System.out.println("Finished level in "+(TimeUtils.nanoTime()-level.getStartTime())/1000000000+" seconds!");
}

And here's the Level getLevelEnd() method:
public int getLevelEnd() {
   return -((levelHeight * Block.HEIGHT)-Bobo.HEIGHT);
}


That's it, nothing more to it :)

What Next?


At this point, the basic game mechanic is complete. I could (should??) go on to implement the other screens, the in game heads up display, the high score recording... But, err, I'm not happy with the game. The setting/story is a bit too convoluted, the uniform blocky walls are a bit rubbish, and the tilt controls feel a bit weird when controlling a character that's facing you and running down the screen.
I'm going to revisit the level generation code to get things looking a little less blocky, change the direction of play to up not down, and maybe change the setting from a running clown to something a bit more "normal", a racing car on a road, a speed boat on a river, or the old classic - a helicopter in a cavern.
You can checkout the game in its current state here if you like. It should look quite different come my next update!