Friday, March 8, 2013

Google Code Repository

I have opened up a Google Code repository where you can go and checkout the source code as we work through it. Check it out at http://code.google.com/p/javagamexyz/.

The code is licensed under the New BSD (BSD 3-clause) which more or less says you may do whatever you want with the code, but I won't assume any liability for anything about it.  The code is hosted using Subversion, for which you can download an eclipse plugin.

To check out a particular version of the code
  1. Install the plugin
  2. In Eclipse say File->Import
  3. Find the option "Project from SVN" and click it
  4. Say "Create a new repository location"
  5. Under URL, say "http://javagamexyz.googlecode.com/svn/tags/2013-03-08/GameXYZ"
    • Or replace the date with whichever version you want
  6. Go with the Head Revision (I don't really know what this means...)
  7. Check out as a project configured using the New Project Wizard
  8. Give the project whatever name
  9. Do the same for /GameXYZ-desktop
  10. You still have to put the .jars in the buildpath and set project dependencies, but then it should work!

To check out the most recent
Use this URL: "http://javagamexyz.googlecode.com/svn/trunk/GameXYZ"

As time goes on, that most recent version may become outdated.  When that happens, right click on the project, go to Team->Update.

There are a few changes to this code from my last blog update that need to be mentioned:
  • MapTools can calculate the "raw" distance between two cells, that is, how many cells they are apart from each other.  This was confusing to do for a hex map, and probably isn't very optimal, but it works!
  • The resource "images" file structure is a little updated
  • I created my own sprite character animation that fits well into a grid cell (32x32 image) - it was fun to do, and I may post a tutorial on how I did it later (note - I suck as an artist, so even if you have no artistic talent you may be able to do something like these)
  • n, wmult, and hmult have been moved to from MidpointDisplacement to HexMapGenerator.  I will probably someday move smoothness and all the thresholds there as well
  • The GameMap component now has a Texture which builds itself upon instantiation, holding an image of the overall map where each cell takes up a single pixel.  This was done using Pixmap from libgdx.  The code is pretty straightforward except for using Pixmap.Format.RBGA8888 - I'm not totally sure what this format means and why I had to use it.  But when I used the other formats, it looked terrible.  My getColor() method hardcodes which color to use for each terrain type.  MapRenderSystem has a line commented out which draws this map at (0,0).
  • I added a FloatPair class, so I can have a Pair not just with ints.  I wanted to redo Pair as a generic class (i.e. Pair<Class1,Class2>) but when I did that  Java wouldn't let me make an array of Pair<Integer,Integer>, which I use to return a list coordinates of neighbors.  It's supposedly possible to do it by using raw types, and not telling the compiler that they are supposed to be Integers, but that seems dirty and may not be available forever.  So I just made a separate class for floats.
  • MapTools has a method which window2world() can figure out what hex cell you're clicking on, depending on where the cursor is in the window.  This replaced the hardcode in PlayerInputSystem which did the same thing.  I want to implement the reverse, world2window(), but haven't gotten around to it yet.
Here's a screenshot of its current incarnation:

You have gained 50 XP.  Progress to Level 3: 350/600

Wednesday, March 6, 2013

Terrain Generation

Okay, time to talk about the random terrain generation algorithm, so that you too can have some cool maps!

A little research brought me to two common random terrain algorithms, Perlin Noise and Diamond-Square.  I liked the look of Diamond-Square a little more, and it seemed easier to implement than Perlin Noise anyway.  My guiding light in this part was an article from Gameprogrammer.com on fractal terrain generation.  It breaks the algorithm down into tiny bitsized steps, and is awesome!

It does, however, have some unfortunate limitations.  First and foremost, it can only build maps which have dimenions 2^n+1.  I don't like the idea of going from n=9 (513 cells) to n=10 (1025 cells) with no middle ground!  I didn't like it one bit.

Also, and perhaps even worse, while setting n to a larger number creates a larger map, it's not really that it builds a larger world... it just builds the same world at a finer and finer scale.  To get an idea of what I mean, consider a few iterations of the diamond square algorithm from the gameprogrammer article:
You don't really get any new features, you just get refinements on features already there.  Thus, whereas a grid cell in the top image may cover 10 square miles, a grid cell in the bottom image may only by 10 feet by 10 feet.  In game, then, you will have to walk over a LOT of tiles to cover much distance, which kind of sucks.  There is room for finer and finer details, but ultimately your map is limited to the features the first few steps came up with.  Especially when you are transforming it to a 2D map anyway, you will lost most of the fine details later iterations created, and you just get a HUGE boring map.

This really limits the diversity of maps you can generate, and just won't do.

To get around this, I modified the initialization step a bit.  Whereas in the original algorithm you just initialize the four corners, either all to the same value (boring) or to some random value (a little less boring), I modified it to allow you to initialize an arbitrary sized grid.
The standard algorithm lets you initialize 4 corners, then works its way inward.

Modified algorithm lets you initialize a grid, and works its way in through each of the regions, which all share borders and can see into their neighbors when appropriate.


Now there is a tradeoff here!  If you initialize too many points in your gird, you end up with maps which don't have much coherent structure:

The tectonic forces that gave rise to this geography were a little drunk at the time...
If you have too few grid points, and rely on making decent size maps by increasing n, you get too boring of maps
I know it's more realistic... but WHYYYYY do I have to cross 12,000 tiles just to make it across the mountain range on the bottom right?
You need to tweak things to strike a balance you like.  Many things about this algorithm are customizable.
Boy, doesn't that look fun!  I can't wait to spend my money to support the people who made this game!
Here's the rough idea of how to use the Diamond Square (also known as midpoint displacement) algorithm to make our 2D map:
  • Generate the fractal terrain (note, this is really a 3D terrain being created)
  • Normalize all the heights to lie between 0 and 1 (inclusive)
  • Have a set of threshold parameters such that all points below the DeepWaterThreshold become deep water, otherwise if they are between that threshold and the ShalowWaterThreshold, they become shallow water, etc...
  • Smile at your pretty map
It's not the best, there are lots of ways it could be improved.  For instance, instead of having strict thresholds, perhaps the height generated by the algorithm sets the probability that certain terrains might be picked.  That way, instead of going from solid grassland to solid darker green grassland to solid forest, there could be smoother transitions and more engaging maps.  But this is a start.

Without further adieu, here is my MidpointDisplacement.java (you could call it DiamondSquare.java):
package com.gamexyz.utils;

import com.badlogic.gdx.math.MathUtils;

public class MidpointDisplacement {
 public float deepWaterThreshold, 
     shallowWaterThreshold,
     desertThreshold,
     plainsThreshold,
     grasslandThreshold,
     forestThreshold,
     hillsThreshold,
     mountainsThreshold;

 public int n;
 public int wmult, hmult;
 
 public float smoothness;

 public MidpointDisplacement() {
  
  // the thresholds which determine cutoffs for different terrain types
  deepWaterThreshold = 0.5f;
  shallowWaterThreshold = 0.55f;
  desertThreshold = 0.58f;
  plainsThreshold = 0.62f;
  grasslandThreshold = 0.7f;
  forestThreshold = 0.8f;
  hillsThreshold = 0.88f;
  mountainsThreshold = 0.95f;
  
  // n partly controls the size of the map, but mostly controls the level of detail available
  n = 7;
  
  // wmult and hmult are the width and height multipliers.  They set how separate regions there are
  wmult=6;
  hmult=4;
  
  // Smoothness controls how smooth the resultant terain is.  Higher = more smooth
  smoothness = 2f;
 }
 
 public int[][] getMap() {
  
  // get the dimensions of the map
  int power = MyMath.pow(2,n);
  int width = wmult*power + 1;
  int height = hmult*power + 1;
  
  // initialize arrays to hold values 
  float[][] map = new float[width][height];
  int[][] returnMap = new int[width][height];
  
  
  int step = power/2;
  float sum;
  int count;
  
  // h determines the fineness of the scale it is working on.  After every step, h
  // is decreased by a factor of "smoothness"
  float h = 1;
  
  // Initialize the grid points
  for (int i=0; i<width; i+=2*step) {
   for (int j=0; j<height; j+=2*step) {
    map[i][j] = MathUtils.random(2*h);
   }
  }

  // Do the rest of the magic
  while (step > 0) {   
   // Diamond step
   for (int x = step; x < width; x+=2*step) {
    for (int y = step; y < height; y+=2*step) {
     sum = map[x-step][y-step] + //down-left
        map[x-step][y+step] + //up-left
        map[x+step][y-step] + //down-right
        map[x+step][y+step];  //up-right
     map[x][y] = sum/4 + MathUtils.random(-h,h);
    }
   }
   
   // Square step
   for (int x = 0; x < width; x+=step) {
    for (int y = step*(1-(x/step)%2); y<height; y+=2*step) {
     sum = 0;
     count = 0;
     if (x-step >= 0) {
      sum+=map[x-step][y];
      count++;
     }
     if (x+step < width) {
      sum+=map[x+step][y];
      count++;
     }
     if (y-step >= 0) {
      sum+=map[x][y-step];
      count++;
     }
     if (y+step < height) {
      sum+=map[x][y+step];
      count++;
     }
     if (count > 0) map[x][y] = sum/count + MathUtils.random(-h,h);
     else map[x][y] = 0;
    }
    
   }
   h /= smoothness;
   step /= 2;
  }
  
  // Normalize the map
  float max = Float.MIN_VALUE;
  float min = Float.MAX_VALUE;
  for (float[] row : map) {
   for (float d : row) {
    if (d > max) max = d;
    if (d < min) min = d;
   }
  }
  
  // Use the thresholds to fill in the return map
  for(int row = 0; row < map.length; row++){
   for(int col = 0; col < map[row].length; col++){
    map[row][col] = (map[row][col]-min)/(max-min);
    if (map[row][col] < deepWaterThreshold) returnMap[row][col] = 0;
    else if (map[row][col] < shallowWaterThreshold) returnMap[row][col] = 1;
    else if (map[row][col] < desertThreshold) returnMap[row][col] = 2;
    else if (map[row][col] < plainsThreshold) returnMap[row][col] = 3;
    else if (map[row][col] < grasslandThreshold) returnMap[row][col] = 4;
    else if (map[row][col] < forestThreshold) returnMap[row][col] = 5;
    else if (map[row][col] < hillsThreshold) returnMap[row][col] = 6;
    else if (map[row][col] < mountainsThreshold) returnMap[row][col] = 7;
    else returnMap[row][col] = 8;
   }
  }

  return returnMap;
 }
}
int n controls the level of detail (and hence the size of your map).  int wmult and int hmult kind of control how many (mostly) independent regions there are (and hence also control the size of your map).  The thresholds all control cutoff points for the different terrain types.  I don't want to explain how the actual algorithm itself works, check out the Gameprogrammer article if you are more curious.

I also created a class HexMapGenerator.java which calls my MidpointDisplacement algorithm (and for now that's all it does, but I hope to expand to make cooler maps, maybe place towns or resources, who knows?)
package com.gamexyz.utils;

public class HexMapGenerator {
 
 public HexMapGenerator() {
 }

 public int[][] getDiamondSquare() {
  MidpointDisplacement md = new MidpointDisplacement();
  return md.getMap();
 }
}

I also updated my GameMap component to load a random map (which is remarkably fast) from the HexMapGenerator
HexMapGenerator hmg = new HexMapGenerator();
  map = hmg.getDiamondSquare();
  width = map.length;
  height = map[0].length;
With that, you can now make some awesome, playable looking maps!  We already know how to scroll around, zoom in and out, etc.  Notice as you scroll out that FPS goes down, that's because we already implemented the frustum culling, but when we zoom out more and more tiles are in the frustum, so it runs slower.  Play around and have some fun!

You have gained 50 XP.  Progress to Level 3: 300/600

The Game Map Pt 2 (Level 2)

This will be a pretty short update, but kind of fun.  The goal is to be able to click-drag to move around the world, tell which cell is being clicked on, scroll to zoom in/out, and draw only the map tiles within our camera's view.

I'm going to implement the first three pieces in PlayerInputSystem.java, and the last part in MapRenderSystem.java.

First let's look at PlayerInputSystem.java
package com.gamexyz.systems;

import com.artemis.Aspect;
import com.artemis.ComponentMapper;
import com.artemis.Entity;
import com.artemis.annotations.Mapper;
import com.artemis.systems.EntityProcessingSystem;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.math.Vector3;
import com.gamexyz.components.Player;
import com.gamexyz.components.Position;
import com.gamexyz.utils.MapTools;

public class PlayerInputSystem extends EntityProcessingSystem implements InputProcessor {
 @Mapper ComponentMapper<Position> pm;
 
 private OrthographicCamera camera;
 private Vector3 mouseVector;
 
 @SuppressWarnings("unchecked")
 public PlayerInputSystem(OrthographicCamera camera) {
  super(Aspect.getAspectForAll(Player.class));
  this.camera=camera;
 }
 
 @Override
 protected void initialize() {
  Gdx.input.setInputProcessor(this);
 }

 @Override
 protected void process(Entity e) {
  mouseVector = new Vector3(Gdx.input.getX(),Gdx.input.getY(),0);
  camera.unproject(mouseVector);
  
 }

 @Override
 public boolean keyDown(int keycode) {
  return false;
 }

 @Override
 public boolean keyUp(int keycode) { 
  return false;
 }

 @Override
 public boolean keyTyped(char character) {
  return false;
 }

 @Override
 public boolean touchDown(int screenX, int screenY, int pointer, int button) {
  int x = (int)((mouseVector.x - 6f) / (float)MapTools.col_multiple);
  int y = (int)((mouseVector.y - (float)MapTools.row_multiple*(x%2)/2) / (float)MapTools.row_multiple);
  return false;
 }

 @Override
 public boolean touchUp(int screenX, int screenY, int pointer, int button) {
  return false;
 }

 @Override
 public boolean touchDragged(int screenX, int screenY, int pointer) {
  Vector3 delta = new Vector3(-camera.zoom*Gdx.input.getDeltaX(), camera.zoom*Gdx.input.getDeltaY(),0);
  camera.translate(delta);
  
  return false;
 }

 @Override
 public boolean mouseMoved(int screenX, int screenY) {
  return false;
 }

 @Override
 public boolean scrolled(int amount) {
  if ((camera.zoom > 0.2f || amount == 1) && (camera.zoom < 8 || amount == -1)) camera.zoom += amount*0.1;
  return false;
 } 
}
The touchDown() method computes which cell is being clicked, and stores the coordinates in x,y.  It looks a little hideous because you have to be careful whether you are in an even or odd column.  Remember, because it's a hex map, if you are in an odd column all the cells are drawn down a little lower.

touchDragged() handles clicking and dragging.  It's kind of awesome that libgdx just comes with built in methods for Gdx.input.getDeltaX() and Y.  I multiply them both by camera.zoom, because when we are zoomed very far in or very far out, the distance the camera moves should change accordingly.  camera.translate() just moves the x,y, and z coordinate of the camera, but we're not touching z, so that component is 0.

scrolled() handles scrolling, and if you scroll up it zooms in (up to 0.2) whereas if you scroll down it scrolls out (up to 8).  You can adjust those points, but be wary of what can happen if you get a negative zoom!  Also, the fact that zoom changes by amount*0.1 sets how fine tuned you can adjust the zoom.  If it were 0.01, you would have finer control.

If you implement these straight away, you might notice something funny... I sure did!  The HUD which displays FPS and so on stays at a fixed point on the MAP, not on the screen.  As you zoom out, it gets smaller and smaller.  As you zoom in it does the same.  That is silly, so let's fix it!

The problem in HudRenderSystem.java is that we run batch.setProjectionMatrix(camera.combined).  This lets things move as you move your camera, exactly what we want to avoid.  Get rid of this line.  In fact, you don't really need anything to do with a camera, so this is what my new HudRenderSystem looks like (I know I left the camera as an argument, but I was just too lazy to change the main code where I initialize it).
package com.gamexyz.systems;

import com.artemis.systems.VoidEntitySystem;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.graphics.Texture.TextureFilter;
import com.badlogic.gdx.graphics.g2d.BitmapFont;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.gamexyz.GameXYZ;

public class HudRenderSystem extends VoidEntitySystem {

 private SpriteBatch batch;
 private BitmapFont font;

 public HudRenderSystem(OrthographicCamera camera) {
 }

 @Override
 protected void initialize() {

  batch = new SpriteBatch();

  Texture fontTexture = new Texture(Gdx.files.internal("fonts/normal_0.png"));
  fontTexture.setFilter(TextureFilter.Linear, TextureFilter.MipMapLinearLinear);
  TextureRegion fontRegion = new TextureRegion(fontTexture);
  font = new BitmapFont(Gdx.files.internal("fonts/normal.fnt"), fontRegion, false);
  font.setUseIntegerPositions(false);
 }

 @Override
 protected void begin() {
  batch.begin();
 }

 @Override
 protected void processSystem() {
  batch.setColor(1, 1, 1, 1);
  font.draw(batch, "FPS: " + Gdx.graphics.getFramesPerSecond(), 20, GameXYZ.WINDOW_HEIGHT - 20);
  font.draw(batch, "Active entities: " + world.getEntityManager().getActiveEntityCount(), 20, GameXYZ.WINDOW_HEIGHT - 40);
  font.draw(batch, "Total created: " + world.getEntityManager().getTotalCreated(), 20, GameXYZ.WINDOW_HEIGHT - 60);
  font.draw(batch, "Total deleted: " + world.getEntityManager().getTotalDeleted(), 20, GameXYZ.WINDOW_HEIGHT - 80);
 }
 
 @Override
 protected void end() {
  batch.end();
 }
}

With that, our HUD should stay up where it belongs... silly HUD...

The last bit we want right now is frustum culling: to only draw the tiles in our camera's view.  Libgdx makes this pretty easy too, but to give you super clear idea of what a frustum is, here's wikipedia.
What we're going to do is get the coordinates of the corners of the far plane, and use them to limit what we render.  In MapRenderSystem, where we defined int x0, x1, y0, and y1, change the code to look like this:
  // Get bottom left and top right coordinates of camera viewport and convert
  // into grid coordinates for the map
  int x0 = MathUtils.floor(camera.frustum.planePoints[0].x / (float)MapTools.col_multiple) - 1;
  int y0 = MathUtils.floor(camera.frustum.planePoints[0].y / (float)MapTools.row_multiple) - 1;
  int x1 = MathUtils.floor(camera.frustum.planePoints[2].x / (float)MapTools.col_multiple) + 2;
  int y1 = MathUtils.floor(camera.frustum.planePoints[2].y / (float)MapTools.row_multiple) + 1;
  
  // Restrict the grid coordinates to realistic values
  if (x0 % 2 == 1) x0 -= 1;
  if (x0 < 0) x0 = 0;
  if (x1 > gameMap.width) x1 = gameMap.width;
  if (y0 < 0) y0 = 0;
  if (y1 > gameMap.height) y1 = gameMap.height; 
And voila!  We probably don't notice a huge difference yet, but when we start playing with HUGE maps later on, this will mean a world of difference.  It handles zooming in and out as well.

This is almost starting to look like it could become a game.  Almost...

You have gained 50 XP.  Progress to Level 3: 250/600

The Game Map (Level 2)

While what we have so far is kind of cute, it doesn't look like much of a game just yet.  As with any decent top down, tile based, 2D RPG, we need some awesome looking maps.  For right now, I don't have a perfect idea of what it should look like, but I've decided to expand myself a bit and make it a hex based grid, instead of square tiles.

The first thing I wanted was a set of terrain tile sprites, which I threw together in Paint.NET.  I based my tiles on a 46 x 39 pixel square, because I wanted my hexagons to be as close to regular hexagons as possible, which requires some rounding on a computer (pesky sqrt(3))!

Anyway, I chose to make tiles for deep water, shallow water, beach/desert, plains, light forest, forest, hills, mountains, and tall mountain peaks.  I named the files hex_0.png through hex_8.png to make them load into a single object with the ImagePacker.  Because I actually drew these, I suppose I should mention something about a license.  I like the creative commons attribution license, so use them however you like as long as you give me credit!  As for my code, use it however you like too.

Creative Commons License
This work is licensed under a Creative Commons Attribution 3.0 Unported License.


I put these images in a folder textures/maptiles in GameXYZ-desktop.  They were designed with partially transparent edges so they could overlap and show some natural looking grid lines.  Here's an example of a random map I generated and rendered with these tiles (click for full size):

Actually, that's just a small portion of the whole, which looks like:

This is really just a first stab at the terrain generation, but I think it's okay for now.  We'll talk about how to make it in a later terrain generation post.  For now though let's focus on how we store and render it.

The data for the map is stored in a 2D array of ints, where each int corresponds to a terrain type (0=deep water, 1=shallow water, etc...).  Unlike a typical 2D tile map, which is just about perfectly represented by a 2D array, the hexmap needs a little tweaking.  Whereas a typical square tile map might look like this:

To turn it into a hexmap, we can shift every other column up by half a cell to look like this:

Of course we won't want to render our tiles to look like squares, but as far as the 2D int array is concerned, this is how it will be rendered.

Notice a frustrating problem with this though, whereas in the square array it's very easy to find a cells neighbors, it is less trivial in the hex map.  Consider cell (1,1).  It has a neighbor of (2,2), or (1+1, 1+1).  However, (4,3) does NOT have a neighbor at (4+1,3+1)=(5,4).  The problem comes with the upward shift of every other column.  So when we are navigating the map, or getting cell neighbors, we'll have to be very careful how we do it.

UPDATE:
The following no longer reflects how I implemented it.  Very shortly after writing this, I realized it stunk as I tried to implement pathfinding.  The general concept is the same, but it is not a Component and Entity thing, it is just a stand alone class called GameMap.java which has a field declared in GameXYZ.java.  To see how it is done more recently, check out the pathfinding post here.
 
To actually implement this, I made a new Component called GameMap, and a new EntitySystem called MapRenderSystem.  At the start of the game I'll create an entity with the GameMap component, which will store the 2D array, plus whatever other crap I can think of as necessary, and every cycle the MapRenderSystem will render it (much like SpriteRenderSystem).  I'll specifically call MapRenderSystem first, so that sprites get rendered on top of it.  I also create a few other helper classes.  Here's what they look like:
package com.gamexyz.components;

import com.artemis.Component;

public class GameMap extends Component {
 public int[][] map;
 public int width, height;
 
 public GameMap() {
  map = new int[][] {
    { 0, 1, 2, 3, 4, 5, 6, 7, 8 },
    { 0, 0, 0, 0, 0, 0, 0, 0, 0 },
    { 0, 0, 0, 0, 0, 0, 0, 0, 0 },
    { 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { 1, 1, 1, 1, 1, 1, 1, 1, 1 },
    { 2, 2, 2, 2, 2, 2, 2, 2, 2 },
    { 2, 2, 2, 2, 2, 2, 2, 2, 2 },
    { 3, 3, 3, 3, 3, 3, 3, 3, 3 },
    { 3, 3, 3, 3, 3, 3, 3, 3, 3 }
  };
  width = map.length;
  height = map[0].length;
  
 }
}
Here we just have some crappy predefined map, plus info on the width and height.  The RenderSystem looks like this:
package com.gamexyz.systems;

import com.artemis.Aspect;
import com.artemis.ComponentMapper;
import com.artemis.Entity;
import com.artemis.EntitySystem;
import com.artemis.annotations.Mapper;
import com.artemis.utils.ImmutableBag;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.graphics.g2d.TextureAtlas.AtlasRegion;
import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.utils.Array;
import com.gamexyz.components.GameMap;
import com.gamexyz.utils.MapTools;

public class MapRenderSystem extends EntitySystem {
 @Mapper ComponentMapper<GameMap> gm;
 private SpriteBatch batch;
 private TextureAtlas atlas;
 private Array<AtlasRegion> textures;

 private OrthographicCamera camera;
 
 @SuppressWarnings("unchecked")
 public MapRenderSystem(OrthographicCamera camera) {
  super(Aspect.getAspectForAll(GameMap.class));
  this.camera = camera;
 }
 
 @Override
 protected void initialize() {
  batch = new SpriteBatch();
  
  atlas = new TextureAtlas(Gdx.files.internal("textures/maptiles.atlas"),Gdx.files.internal("textures"));
  textures = atlas.findRegions(MapTools.name); 
 }

 @Override
 protected boolean checkProcessing() {
  return true;
 }

 @Override
 protected void processEntities(ImmutableBag<Entity> entities) {
  for (int i = 0; i < entities.size(); i++) process(entities.get(i));
 }
 
 private void process(Entity e) {
  GameMap gameMap = gm.get(e);
  TextureRegion reg;
  int x, y;

  int x0 = 0;
  int x1 = gameMap.width;
  
  int y0 = 0;
  int y1 = gameMap.height;
  
  // Loop over everything in the window to draw
  // Because I am drawing a hexmap tile, I chose to 
  // do 2 columns at once. As such, I had to
  // stop shy of the far right column, because
  // col+1 would break for it.  Thus we do that
  // final column separately.
  for (int row = y0; row < y1; row++) {
   for (int col = x0; col < x1-1; col+=2) {
    x = col*MapTools.col_multiple;
    y = row*MapTools.row_multiple;
    reg = textures.get(gameMap.map[col][row]);
    batch.draw(reg, x, y, 0, 0, reg.getRegionWidth(), reg.getRegionHeight(), 1, 1, 0);
    x += MapTools.col_multiple;
    y += MapTools.row_multiple/2;
    reg = textures.get(gameMap.map[col+1][row]);
    batch.draw(reg, x, y, 0, 0, reg.getRegionWidth(), reg.getRegionHeight(), 1, 1, 0);
   }
   if (x1 >= gameMap.width) {
    int col = gameMap.width-1;
    x = col*MapTools.col_multiple;
    y = row*MapTools.row_multiple;
    reg = textures.get(gameMap.map[col][row]);
    batch.draw(reg, x, y, 0, 0, reg.getRegionWidth(), reg.getRegionHeight(), 1, 1, 0);
   }
   
  }
 }
 
 @Override
 protected void begin() {
  batch.setProjectionMatrix(camera.combined);
  batch.begin();
 }
 
 @Override
 protected void end() {
  batch.end();
 }
}
Here, when it's initialized, it stores the map textures in an Array called textures (lines 37-38).  Because every 2nd column has to be shifted up by half a cell, I had a choice between rendering two columns at once, and manually putting the 2nd one up a bit, or rendering each column individually and checking to see if we were on an even or odd column every time.  I chose the first option (lines 70-77).  My random map generation method ends up with an odd number of columns overall, however, so I had to render the last one separately (lines 79-85).

I call MapTools.col_multiple and MapTools.row_multiple to get the row and column offsets that each hex cell should be drawn at.  Because they are hex cells, the images have to overlap a bit to line up properly, and those constants store that information.  MapTools.name is just a string "hex", because I wanted to potentially have the flexibility to someday also do square tiles.  The MapTools class went into com.gamexyz.utils
package com.gamexyz.utils;

import com.gamexyz.custom.Pair;

public class MapTools {
 
 public static final int col_multiple = 34;
 public static final int row_multiple = 38;
 public static final String name = "hex";
 
 

 public static Pair[] getNeighbors(int x, int y, int n) {
  Pair[] coordinates = new Pair[3*(n*n + n)];
  int i = 0;
  int min;
  for (int row = y-n; row<y+n+1; row++) {
   min = MyMath.min(2*(row-y+n), n, -2*(row-y-n)+1);
   for (int col = x-min; col < x+min+1; col++) {
    if (x==col && y==row) continue;
    else if (x % 2 == 0) coordinates[i]=new Pair(col,2*y-row);
    else coordinates[i] = new Pair(col,row);
    i++;
   }
  }
  return coordinates;
 }
 
 public static Pair[] getNeighbors(int x, int y) {
  return getNeighbors(x,y,1);
 }
}
That getNeighbors method was a nightmare to create.  I'm certainly not sure that it's the best way to go about it, but I've tested it and it works.  It returns the neighbors as an array of Pairs, which I had to define in com.gamexyz.custom
package com.gamexyz.custom;

public class Pair {
 public Pair(int x, int y) {
  this.x = x;
  this.y = y;
 }
 public int x, y;
}

Also, a few of these classes reference something called MyMath, which is just a collection of a few math methods I put together in com.gamexyz.utils.  They're not the most general, but they get the job done quickly.
package com.gamexyz.utils;

public class MyMath {
 public static int min(int a, int b) {
  if (a < b) return a;
  return b;
 }
 
 public static int min(int a, int b, int c) {
  if (min(a,b) < c) return min(a,b);
  return c;
 }
 
 public static int pow(int a, int b) {
  if (b > 1) return a*pow(a,b-1);
  else return a;
 }
}

Remember to register the MapRenderSystem and create an Entity with the component for GameMap, and also remember to manually process MapRenderSystem. With that, you are now drawing a game map in hex tiles! These methods should be easy to customize if you want square tiles instead.

There are a couple of crappy things going on here though.  First, this code always renders the ENTIRE map... not just the portion it needs.  That process is called Frustum Culling, and is actually super easy!  For small maps, this isn't so important, but for larger maps it will make a difference.  We'll talk about it in the next article.

Also, it would be super nice to be able to click/drag and move the map, and maybe even use a scroll wheel to zoom in and out.  These are also pretty easy and will be discussed next time.

Also, you can design your own maps by hand, but that is PAINSTAKINGLY slow I think.  I implemented the Diamond-Square fractal terrain generation algorithm, it's super fast, and I'll post that along with its inner workings coming soon.

I will probably also start a Google Code repository soon so people can come and download the code in case something doesn't work so well because I forgot some minor thing I tweaked in another file.

You have gained 100 XP.  Progress to Level 3: 200/600

Saturday, February 9, 2013

Sprite Animation with libgdx and Artemis (Level 2)

After finishing Spaceship warrior, I felt like there were a few more concepts I had to get down.  The first piece I decided to tackle was implementing a SpriteAnimationSystem.  There are a few useful tools in libgdx to accomplish this.

First, when TextuerPacker2 creates a TextureAtlas, it creates a file mapping the file-name to the appropriate region.  If you have a series of images named imageName_0.png, imageName_1.png, ..., imageName_100.png it interprets the _x as an index, and refers to them all by the name imageName.  When there is no number, the atlas just assigns a default index of -1.  But with the numbers, you get indices corresponding to each file.

When you call
atlas.findRegion("imageName");
it returns only the last image of the series.  But if you call
atlas.findRegions("imageName");
it returns an array of all of them, indexed appropriately..  This array can be passed into libgdx's Animation class to create an object that will cycle through all the images in the set.  My idea was to create a new Component called SpriteAnimation which would hold this animation, and SpriteAnimationSystem which would update the Sprite component based on the status of SpriteAnimation.

This way, the SpriteRenderSystem could be used to render everything with just a single static Sprite, and things with SpriteAnimation because all SpriteRenderSystem would ever call was the Sprite class.

First, I did a Google search to find a sprite sheet I could use for the animation, and I came across a game in development called Hero of Allacrost, staring the young knight Claudius.  I found a spritesheet for Claudius and lifted a few frames, naming them warrior_2 through warrior_6 (I started off of 0 just to see what would happen - it worked fine!)


 
Each image is 32x64, and they line up to look like continuous walking.

To understand my implementation, I want to first focus on the TextureRegion class.  TextureRegions have a reference to a Texture, along with rectangle coordinates specifying which part of that Texture to use.  These coordinates are stored in 2 different formats: float u, u2 refer to the x coordinates as a percent of overall texture width.  That is, if a Texture is 100 pixels wide, and the TextureRegion runs horizontally through pixel 35 and pixel 73, u would be 0.35f and u2 would be 0.73f.  float v, v2 do likewise for the vertical component.

On the other hand, int x, y, width, height store the same information in raw pixel counts.  In the example above, x would be 35 and width would be 73-35 = 38. 

The first major change I had to make was to SpriteRenderSystem and Sprite.  In Spaceship Warrior, Sprites only had a name, but SpriteRenderSystem had a bag which assigned a TextureRegion to each entity.  This wouldn't work anymore because the Sprite class itself had to hold its own TextureRegion so the AnimatedSpriteSystem could update it.

Even worse, the Sprites would now only hold a refenece to the actual TextureRegion created from the actual TextureAtlas.  That means when one Sprite updated "its" TextureRegion, it would actually mess with all of them.

To get around this, I used the coordinate information talked about above, and each Sprite had it's own int x, y, width, and height.  Instead of touching the TextureRegion itself, AnimatedSpriteSystem would instead change the Sprite's coordinate information, and the Sprite would update the TextureRegion right before it was drawn.

Here is my implementation:
public class Sprite extends Component {
 
 public Sprite(String name) {
  this.name = name;
 }
 
 
 public TextureRegion region;
 public String name;
 public float r,g,b,a,scaleX,scaleY,rotation;

 public int x, y, width, height;
}

package com.gamexyz.components;
public class SpriteAnimation extends Component {
 
 public Animation animation;
 public float stateTime;
 public float frameDuration;
 public int playMode;
 
 public TextureRegion getFrame() {
  return animation.getKeyFrame(stateTime);
 }
 
}

public class SpriteAnimationSystem extends EntityProcessingSystem {
 @Mapper ComponentMapper<Sprite> sm;
 @Mapper ComponentMapper<SpriteAnimation> sam;
 
 @SuppressWarnings("unchecked")
 public SpriteAnimationSystem() {
  super(Aspect.getAspectForAll(Sprite.class, SpriteAnimation.class));
 }

 @Override
 protected void process(Entity e) {

  Sprite sprite = sm.get(e);
  SpriteAnimation anim = sam.get(e);
  
  anim.stateTime += world.getDelta();

  TextureRegion region = anim.getFrame();
  sprite.x = region.getRegionX();
  sprite.y = region.getRegionY();
  sprite.width = region.getRegionWidth();
  sprite.height = region.getRegionHeight();
 }

 @Override
 protected boolean checkProcessing() {
  return true;
 }
}

public class SpriteRenderSystem extends EntitySystem {
 @Mapper ComponentMapper<position> pm;
 @Mapper ComponentMapper<sprite> sm;
 @Mapper ComponentMapper<spriteanimation> sam;
 
 private OrthographicCamera camera;
 private SpriteBatch batch;
 
 private TextureAtlas atlas;
 
 private List<entity> sortedEntities;
 
 @SuppressWarnings("unchecked")
 public SpriteRenderSystem(OrthographicCamera camera) {
  super(Aspect.getAspectForAll(Position.class, Sprite.class));
  this.camera = camera;
 }
 
 @Override
 protected void initialize() {
  batch = new SpriteBatch();
  
  atlas = new TextureAtlas(Gdx.files.internal("textures/pack.atlas"),Gdx.files.internal("textures"));
  sortedEntities = new ArrayList<entity>();
 }

 @Override
 protected boolean checkProcessing() {
  return true;
 }

 @Override
 protected void processEntities(ImmutableBag<entity> entities) {
  for (Entity e : sortedEntities) {
   process(e);
  }
 }
 
 @Override
 protected void begin() {
  batch.setProjectionMatrix(camera.combined);
  batch.begin();
 }
 
 protected void process(Entity e) {
  if (pm.has(e)) {
   Position position = pm.getSafe(e);
   Sprite sprite = sm.get(e);
   
   TextureRegion spriteRegion = sprite.region;
   batch.setColor(sprite.r, sprite.g, sprite.b, sprite.a);

   int width = spriteRegion.getRegionWidth();
   int height = spriteRegion.getRegionHeight();
   
   sprite.region.setRegion(sprite.x, sprite.y, width, height);
   
   float posX = position.x - (spriteRegion.getRegionWidth() / 2 * sprite.scaleX);
   float posY = position.y - (spriteRegion.getRegionHeight() / 2 * sprite.scaleX);
   batch.draw(spriteRegion, posX, posY, 0, 0, spriteRegion.getRegionWidth(), spriteRegion.getRegionHeight(), sprite.scaleX, sprite.scaleY, sprite.rotation);
  }
 }
 
 @Override
 protected void end() {
  batch.end();
 }
 
 @Override
 protected void inserted(Entity e) {
  Sprite sprite = sm.get(e);
  sortedEntities.add(e);
  TextureRegion reg = atlas.findRegion(sprite.name);
  sprite.region = reg;
  sprite.x = reg.getRegionX();
  sprite.y = reg.getRegionY();
  sprite.width = reg.getRegionWidth();
  sprite.height = reg.getRegionHeight();
  if (sam.has(e)) {
   SpriteAnimation anim = sam.getSafe(e);
   anim.animation = new Animation( anim.frameDuration, atlas.findRegions(sprite.name), anim.playMode);
  }
  
  Collections.sort(sortedEntities, new Comparator<entity>() {
   @Override
   public int compare(Entity e1, Entity e2) {
    Sprite s1 = sm.get(e1);
    Sprite s2 = sm.get(e2);
    return s1.layer.compareTo(s2.layer);
   }
  });
 }
 
 @Override
 protected void removed(Entity e) {
  sortedEntities.remove(e);
 }
}
I completely abandoned the regionsByName bag, and even the regions HashMap which mapped "names" to regions, instead I rely entirely on atlas.getRegion(...).  This is potentially slow, and if I ever need to create a lot of entities at once it may be helpful to recreate the HashMap - especially for the static sprites.  For instance, if I created a particle explosion effect, I may do that down the line.  But for now this seems fine.

My least favorite part comes in lines 79-82.  The SpriteRenderSystem doesn't necessarily require its entities have SpriteAnimation, so I had to carefully check before referencing it.  The reason I didn't include this in a different system, which would have been preferable, is that this is where my TextureAtlas lives.  Without having that to point to, I can't instantiate my Animation.

From that, SpriteAnimationSystem updates the animated sprites x, y, etc, and it gets drawn properly to the screen.  I created an EntityFactory that will make a character with the Claudius animation, and I added a bunch of them.  Copying over my HudRenderingSystem from the previous project let me see that FPS did okay even with thousands of such entities being processed and rendered onscreen.  Furthermore, I made them all start out of step by giving them a random stateTime.  stateTime is the thing that Animation uses to calculate which frame to work with.  For fun, I also added a few static Sprites to verify that they would alongside the animated ones.

int playMode in SpriteAnimation refers to the playMode in libgdx's Animation class.  I like to use Animation.LOOP_PINGPONG for the Claudius animation, which means that once it reaches the last frame, it turns around and loops through them in reverse, back and forth.  This is particularly convenient for a walking animation.  Unfortunately, PINGPONG has a quirk that the endpoint frames last for twice as long.  This ruined our aesthetic in my opinion, and seems extremely silly!  I downloaded the raw code from their github repository here to see what was going on.

Unsatisfied with the way they did things, I created my own Animation class by totally stealing theirs and changing one tiny piece of the code.  I put it in a new package called com.gamexyz.custom, and here is what it looks like:
/*******************************************************************************
 * Copyright 2011 See AUTHORS file.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/

package com.gamexyz.custom;

import com.badlogic.gdx.graphics.g2d.TextureRegion;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.utils.Array;

/** * An Animation stores a list of {@link TextureRegion}s representing an animated sequence, e.g. for running or jumping. Each
 * region of an Animation is called a key frame, multiple key frames make up the animation.
 * 


 * 
 * @author mzechner */
public class Animation {
 public static final int NORMAL = 0;
 public static final int REVERSED = 1;
 public static final int LOOP = 2;
 public static final int LOOP_REVERSED = 3;
 public static final int LOOP_PINGPONG = 4;
 public static final int LOOP_RANDOM = 5;

 final TextureRegion[] keyFrames;
 public final float frameDuration;
 public final float animationDuration;

 private int playMode = NORMAL;

 /** Constructor, storing the frame duration and key frames.
  * 
  * @param frameDuration the time between frames in seconds.
  * @param keyFrames the {@link TextureRegion}s representing the frames. */
 public Animation (float frameDuration, Array keyFrames) {
  this.frameDuration = frameDuration;
  this.animationDuration = keyFrames.size * frameDuration;
  this.keyFrames = new TextureRegion[keyFrames.size];
  for (int i = 0, n = keyFrames.size; i < n; i++) {
   this.keyFrames[i] = keyFrames.get(i);
  }

  this.playMode = NORMAL;
 }

 /** Constructor, storing the frame duration, key frames and play type.
  * 
  * @param frameDuration the time between frames in seconds.
  * @param keyFrames the {@link TextureRegion}s representing the frames.
  * @param playType the type of animation play (NORMAL, REVERSED, LOOP, LOOP_REVERSED, LOOP_PINGPONG, LOOP_RANDOM) */
 public Animation (float frameDuration, Array keyFrames, int playType) {

  this.frameDuration = frameDuration;
  this.animationDuration = keyFrames.size * frameDuration;
  this.keyFrames = new TextureRegion[keyFrames.size];
  for (int i = 0, n = keyFrames.size; i < n; i++) {
   this.keyFrames[i] = keyFrames.get(i);
  }

  this.playMode = playType;
 }

 /** Constructor, storing the frame duration and key frames.
  * 
  * @param frameDuration the time between frames in seconds.
  * @param keyFrames the {@link TextureRegion}s representing the frames. */
 public Animation (float frameDuration, TextureRegion... keyFrames) {
  this.frameDuration = frameDuration;
  this.animationDuration = keyFrames.length * frameDuration;
  this.keyFrames = keyFrames;
  this.playMode = NORMAL;
 }

 /** Returns a {@link TextureRegion} based on the so called state time. This is the amount of seconds an object has spent in the
  * state this Animation instance represents, e.g. running, jumping and so on. The mode specifies whether the animation is
  * looping or not.
  * 
  * @param stateTime the time spent in the state represented by this animation.
  * @param looping whether the animation is looping or not.
  * @return the TextureRegion representing the frame of animation for the given state time. */
 public TextureRegion getKeyFrame (float stateTime, boolean looping) {
  // we set the play mode by overriding the previous mode based on looping
  // parameter value
  if (looping && (playMode == NORMAL || playMode == REVERSED)) {
   if (playMode == NORMAL)
    playMode = LOOP;
   else
    playMode = LOOP_REVERSED;
  } else if (!looping && !(playMode == NORMAL || playMode == REVERSED)) {
   if (playMode == LOOP_REVERSED)
    playMode = REVERSED;
   else
    playMode = LOOP;
  }

  return getKeyFrame(stateTime);
 }

 /** Returns a {@link TextureRegion} based on the so called state time. This is the amount of seconds an object has spent in the
  * state this Animation instance represents, e.g. running, jumping and so on using the mode specified by
  * {@link #setPlayMode(int)} method.
  * 
  * @param stateTime
  * @return the TextureRegion representing the frame of animation for the given state time. */
 public TextureRegion getKeyFrame (float stateTime) {
  int frameNumber = getKeyFrameIndex (stateTime);
  return keyFrames[frameNumber];
 }
 
 /** Returns the current frame number.
  * @param stateTime
  * @return current frame number */
 public int getKeyFrameIndex (float stateTime) {
  int frameNumber = (int)(stateTime / frameDuration);

  if(keyFrames.length == 1)
         return 0;
  
  switch (playMode) {
  case NORMAL:
   frameNumber = Math.min(keyFrames.length - 1, frameNumber);
   break;
  case LOOP:
   frameNumber = frameNumber % keyFrames.length;
   break;
  case LOOP_PINGPONG:
   frameNumber = frameNumber % ((keyFrames.length * 2) - 2);
   //if (frameNumber >= keyFrames.length)
   frameNumber = keyFrames.length -1 - Math.abs(frameNumber - keyFrames.length + 1);//keyFrames.length - 2 - (frameNumber - keyFrames.length);
   break;
  case LOOP_RANDOM:
   frameNumber = MathUtils.random(keyFrames.length - 1);
   break;
  case REVERSED:
   frameNumber = Math.max(keyFrames.length - frameNumber - 1, 0);
   break;
  case LOOP_REVERSED:
   frameNumber = frameNumber % keyFrames.length;
   frameNumber = keyFrames.length - frameNumber - 1;
   break;

  default:
   // play normal otherwise
   frameNumber = Math.min(keyFrames.length - 1, frameNumber);
   break;
  }
  
  return frameNumber;
 }

 /** Sets the animation play mode.
  * 
  * @param playMode can be one of the following: Animation.NORMAL, Animation.REVERSED, Animation.LOOP, Animation.LOOP_REVERSED,
  *           Animation.LOOP_PINGPONG, Animation.LOOP_RANDOM */
 public void setPlayMode (int playMode) {
  this.playMode = playMode;
 }

 /** Whether the animation would be finished if played without looping (PlayMode Animation#NORMAL), given the state time.
  * @param stateTime
  * @return whether the animation is finished. */
 public boolean isAnimationFinished (float stateTime) {
  if(playMode != NORMAL && playMode != REVERSED) return false;
  int frameNumber = (int)(stateTime / frameDuration);
  return keyFrames.length - 1 < frameNumber;
 }
}

All that is different is that on PINGPONG mode, it doesn't linger extra long at the ends.  I had to update all my import commands to import mine, instead of libgdx's.

So there you go, we created an animation system using entities and components.

To  bring you up to speed, my project currently has the following structure
  • GameXYZ
  • EntityFactory
  • Components
    • Expires
    • Player
    • Position
    • Sprite
    • SpriteAnimation
  • Systems
    • ExpiringSystem
    • HudRenderSystem
    • SpriteAnimationSystem
    • SpriteRenderSystem
  • Custom
    • Animation
  • Utils
    • ImagePacker
Then, of course, in the Desktop project I have a Launcher.  I know I'm not using Expiring or Player yet, but they seem pretty important for down the line, and I probably won't change them a bit

You have gained 100 XP.  Progress to Level 3: 100/600

Thursday, February 7, 2013

Spaceship Warrior Pt 6 (Level 1)

This is the last post we will have to do in Spaceship Warrior - the end is in sight!  And all things considered, nothing coming up is very hard given what we have learned.  There are some surprises though.

First, we're going to add the particle explosion effect.  Notice in the demo that whenever your bullets hit an enemy ship, there's a small explosion with a shower of particles flying out in all directions.  And if you shoot for a long enough time, you'll notice that most of the particles fade away really quickly, but sometimes there is a particle explosion that's extra bright and seems to last longer / travel farther out.  How is all this handled?

Well, that last part is a glitch, albeit a really cool one!  But we'll get to it in due time.  First let's look at where the particles are created.  In CollisionSystem.java, when a collision is detected, we run this code:
for (int i = 0; i < 50; i++) EntityFactory.createParticle(world, bp.x, bp.y).addToWorld();

To run this we have to update our EntityFactory with createParticle()
 public static Entity createParticle(World world, float x, float y) {
  Entity e = world.createEntity();
  
  Position position = new Position();
  position.x = x;
  position.y = y;
  e.addComponent(position);
  
  Sprite sprite = new Sprite();
  sprite.name = "particle";
  sprite.scaleX = sprite.scaleY = MathUtils.random(0.3f, 0.6f);
  sprite.r = 1;
  sprite.g = 216/255f;
  sprite.b = 0;
  sprite.a = 0.5f;
  sprite.layer = Sprite.Layer.PARTICLES;
  e.addComponent(sprite);
  
  Velocity velocity = new Velocity(MathUtils.random(-400,400), MathUtils.random(-400,400));
  e.addComponent(velocity);
  
  Expires expires = new Expires();
  expires.delay = 1;
  e.addComponent(expires);

  return e;
 }

As makes sense, the particles should all have a random velocity.  But it's not perfect.  If you pay close attention to the particle explosions (and it helps to decrease the velocity range from -100 to 100), you'll notice that they don't explode outward in a circle, but rather they explode into a kind of rectangle.  It's not wrong per se... but it certainly violates my sense of explosion aesthetics.  The reason is that the velocity vector can point anywhere in the entire rectangle (-400,-400) to (400,400)  In other words, the maximum velocity achievable if the particle is moving strictly left or right (or up or down) is 400.  But the maximum velocity along the diagonals is sqrt(400^2 + 400^2) = 400sqrt(2) = 566.  That's why particles on the diagonal go farther - they can go faster!

Instead of generating random velocities from -400 to 400 in both dimensions, instead we can generate a random angle from 0 to 2*pi, and a random magnitude from 0 to 400, then use sin() and cos() to grab the components.  It's a little more time consuming of an operation, but not terrible.  If you really need to nickel and dime your performance, maybe go back to the rectangle, but in my code I'm going with this instead.
  
  float radians = MathUtils.random(2*MathUtils.PI);
  float magnitude = MathUtils.random(400f);
  
  Velocity velocity = new Velocity(magnitude * MathUtils.cos(radians),magnitude * MathUtils.sin(radians));
  e.addComponent(velocity);

Note that MathUtils.sin() and MathUtils.cos() take radians for the angle (not degrees), and are precalculated at the beginning so you aren't running a full sin() or cos() method every time.  It's just a lookup table optimized for games using libgdx, but if they drag you down too much, you can download the libgdx source code, decrease the precision of those functions, and recompile it into a .jar your own dang self (which is actually pretty easy to do if you look at their code).

Our particles don't really fade away like the demo's yet, they just disperse until they vanish.  We'll change that by introducing the ColorAnimation component and corresponding ColorAnimationSystem.  The Component is really easy to understand, you can just steal it straight away, but well look at the System:
public class ColorAnimationSystem extends EntityProcessingSystem {
 @Mapper ComponentMapper<ColorAnimation> cam;
 @Mapper ComponentMapper<Sprite> sm;

 public ColorAnimationSystem() {
  super(Aspect.getAspectForAll(ColorAnimation.class, Sprite.class));
 }

 @Override
 protected void process(Entity e) {
  ColorAnimation c = cam.get(e);
  Sprite sprite = sm.get(e);
  
  if(c.alphaAnimate) {
   sprite.a += c.alphaSpeed * world.delta;
   
   if(sprite.a > c.alphaMax || sprite.a < c.alphaMin) {
    if(c.repeat) {
     c.alphaSpeed = -c.alphaSpeed;
    } else {
     c.alphaAnimate = false;
    }
   }
  }
 }
}

I'm starting with the demo's code, which didn't really complete the ColorAnimationSystem, but built the part they needed.  You see they just update the sprites alpha level, or transparency, decreasing it steadily from 1 to 0.  When it reaches the end, if repeat is set to true, it reverses direction and heads back to 1.  Otherwise it sets alphaAnimate to false, which stops it from happening.  I thought about replacing that with e.removeComponent(c), but that would cause problems if the alpha was done, but the others: r, g, and b, were not.

Add this system to your world, and update EntityFactory to add a ColorAnimation to new particles like this:
ColorAnimation colorAnimation = new ColorAnimation();
colorAnimation.alphaAnimate = true;
colorAnimation.alphaSpeed = -1f;
colorAnimation.alphaMin = 0f;
colorAnimation.alphaMax = 1f;
colorAnimation.repeat = false;
e.addComponent(colorAnimation);

If we run it, we see particles fade away as expected.  But note that you will sometimes also see an abnormally large particle explosion with extra bright particles!  This is a neat effect, but where on Earth did it come from?  The problem is in line 15.

This allows the alpha to be decreased to something below zero.  It turns out that if you do that, libgdx folds it back up to be between 0 and 1, and just below 0 maps to just below 1.  That means that near the end of its life, your particles went from transparent to completely opaque!  More opaque than they began!  While I like the effect, it doesn't look so good in other cases.  Imagine your ship obtained a cloaking device, and to represent that you created an alpha animation from 1 to 0 and back, on repeat.  If you let it go below 0, it will fade out gently, suddenly pop into full brightness, then disappear and gradually fade back in.  Then, when it's up to full alpha, there's a good chance it will overshoot alpha=1, and become almost transparent again!  In this case, things aren't so good.  I fixed it by changing it as follows:
 protected void process(Entity e) {
  ColorAnimation c = cam.get(e);
  Sprite sprite = sm.get(e);
  
  if(c.alphaAnimate) {
   sprite.a += c.alphaSpeed * world.delta;
   
   if(sprite.a > c.alphaMax) {
    sprite.a = c.alphaMax;
    if(c.repeat) {
     c.alphaSpeed = -c.alphaSpeed;
    } else {
     c.alphaAnimate = false;
    }
   }
   
   else if(sprite.a < c.alphaMin) {
    sprite.a = c.alphaMin;
    if(c.repeat) {
     c.alphaSpeed = -c.alphaSpeed;
    } else {
     c.alphaAnimate = false;
    }
   }
  }
  
  if(c.redAnimate) {
   sprite.r += c.redSpeed * world.delta;
   
   if(sprite.r > c.redMax) {
    sprite.r = c.redMax;
    if(c.repeat) {
     c.redSpeed = -c.redSpeed;
    } else {
     c.redAnimate = false;
    }
   }
   
   else if(sprite.r < c.redMin) {
    sprite.r = c.redMin;
    if(c.repeat) {
     c.redSpeed = -c.redSpeed;
    } else {
     c.redAnimate = false;
    }
   }
  }
  
  if(c.greenAnimate) {
   sprite.g += c.greenSpeed * world.delta;
   
   if(sprite.g > c.greenMax) {
    sprite.g = c.greenMax;
    if(c.repeat) {
     c.greenSpeed = -c.greenSpeed;
    } else {
     c.greenAnimate = false;
    }
   }
   
   else if(sprite.g < c.greenMin) {
    sprite.g = c.greenMin;
    if(c.repeat) {
     c.greenSpeed = -c.greenSpeed;
    } else {
     c.greenAnimate = false;
    }
   }
  }
  
  if(c.blueAnimate) {
   sprite.b += c.blueSpeed * world.delta;
   
   if(sprite.b > c.blueMax) {
    sprite.b = c.blueMax;
    if(c.repeat) {
     c.blueSpeed = -c.blueSpeed;
    } else {
     c.blueAnimate = false;
    }
   }
   
   else if(sprite.b < c.blueMin) {
    sprite.b = c.blueMin;
    if(c.repeat) {
     c.blueSpeed = -c.blueSpeed;
    } else {
     c.blueAnimate = false;
    }
   }
  }
}

Now we have particles that just gradually fade out.  We lost the big, cool particle explosions, but I guess that's life.

The demo also adds a tiny explosion graphic for every hit, and a big one when the ship dies.  To do this, update CollisionSystem and EntityFactory to add the explosions:
   public void handleCollision(Entity bullet, Entity ship) {
    Health health = hm.get(ship);
    Position bp = pm.get(bullet);
    health.health -= 10;
    
    EntityFactory.createExplosion(world, bp.x, bp.y, 0.1f).addToWorld();
    
    for (int i = 0; i < 50; i++) EntityFactory.createParticle(world, bp.x, bp.y).addToWorld();   
    if (health.health <= 0) {
     ship.deleteFromWorld();
     EntityFactory.createExplosion(world, bp.x, bp.y, 0.5f).addToWorld();
    }
    
    bullet.deleteFromWorld();
   }

Update your EntityFactory like the Demo's, and grab ScaleAnimation and ScaleAnimationSystem from the demo, they work as prescribed, though they do force the x and y components be scaled identically.  Add the ScaleAnimationSystem to your world, and you have yourself some bona fide explosions!

To finish up our special effects, the only piece we're really missing are the background stars.  They again work as prescribed, so they can be stolen straight from the demo.  The only thing to remember is that you have to change the width and height variables manually to 1280 and 900, because our program doesn't reference some universal static field with that information.  It probably should... if you want to update it, make sure you update the camera.setToOrtho, the enemy spawning system, and the Launcher.  When I updated mine, I included two variables called public static int WINDOW_HEIGHT and WINDOW_WIDTH in GameXYZ.java.  That way it was highly visible.  I also updated them in the resize() method in GameXYZ.java.

Anyway, to set the stars up you need to update EntityFactory, create an empty (placeholder) component ParallaxStar, and implement ParallaxStarRepeatingSystem.  The actually create the stars, make a loop in GameXYZ.java which creates a bunch of them.  Notice in EntityFactory that they also have a random velocity, but it's all in the negative y direction, and they have a random scale and random alpha.  Once you've got your stars up and running, you are very near the completed product.

The only bit remaining is to add in the HudRenderingSystem and HealthRenderingSystem.  To make them work, you must copy the resources/fonts folder from the demo into your own project.  Remember, Eclipse won't update the explorer automatically, you have to right click the project and refresh.  These systems again work about as prescribed, with no surprises.  HudRenderingSystem for some reason loads your texture-atlas - it doesn't actually use that information, so I got ride of all those lines in mine.  Remember, rendering systems should be updated manually, so you need fields in GameXYZ.java to hold them, just like you did SpriteRenderSystem, and when you add them to the world you need to pass the "true" argument.  You wouldn't want to inadvertently render in the middle of processing your other systems, and you'd hate to render the HUD or HEALTH systems beneath your main sprites.  So deal with them manually, and under spriteRenderSystem.process(), include both of these.

After that, you are officially pretty much done.  Give it a whirl and be amazed - you have recreated Starship Warrior!  Except for one, tiny little problem.  The demo cruises along and hundreds of FPS... on my medium laptop.  My program at this point only ran at 60.  ... ... WTF?!?!

Hunting this bugger down brought me back to Launcher.java.  In the demo's launcher, they set vsynch to false, something I hadn't done.  Making just that one little change made me skyrocket up into the hundreds right past the demo even!

You may have noticed that I was a lot less specific in this page.  That's partly because I think it was all pretty easy by this point, but mostly because I'm TIRED of Spaceship Warrior.  I want to move on to something COOLER!  If you managed to make it here, congratulations!  You just defeated the Spaceship Warrior and completed your quest.

You have gained 150 XP.  Progress to Level 2: 400/400
DING!  You have advanced to Level 2, congratulations!
As a Level 2 PC, you have mastered
  • The art of setting up Artemis systems and components within the libgdx game loop
  • Basic use of the GroupManager, with a specific application to collision detection
  • The ability to handle multiple rendering systems, along with sprite manipulation animations
You have also started down the path of understanding how to read open source game code, but you are by no means a master.  More of... a Novice's Apprentice.