Saturday, March 23, 2013

Player Movement and User Input (Level 3)

The goal of this post is to expand to add the following features:
  • Players select an entity by clicking on it
  • Once an entity is selected, the tiles it can reach are highlighted
  • If a player selects one of the highlighted cells, the entity smoothly follows a path from the source tile to the target.  Afterwords* the player is deselected
  • If a player clicks anywhere not highlighted, the entity is deselected and the highlighted cells go away
*The idea is that it happens afterwords.  There is currently a (probably not very bad) bug where before the entity has reached its goal, the player can select that target cell causing the entity to be reselected before reaching it.  This has problems if the player then clicks another cell to move to before the entity has finished its first path, and while the entity will move there, its position will still be the original target cell (I know why it happens, and will discuss it in the following code.)

I'll start by talking about how I handled the smooth movement.  We have two components related to motion:
  • Movable
  • Movement
Movement is a temporary component that actually holds the  path the entity is following. 
package com.blogspot.javagamexyz.gamexyz.components;

import com.artemis.Component;
import com.blogspot.javagamexyz.gamexyz.pathfinding.Path;

public class Movement extends Component {
 
 public Path path;
 public float elapsedTime;
 
 public Movement(Path path) {
  this.path = path;
  elapsedTime = 0;
 }

}

It also has a field called elapsedTime which will be used to pace the smooth movement animation.

Movable is the more general component which entities will have if they can be moved.  It stores information such as what kinds of tiles the unit can move across, how far it can move, and how quickly it completes the movement animation.
package com.blogspot.javagamexyz.gamexyz.components;

import com.artemis.Component;

public class Movable extends Component {
 
 public float energy;  // Roughly how far can you go?
 public float slowness;  // How many seconds it takes to slide from 1 tile to an adjacent
 
 public boolean[] terrainBlocked;
 public float[] terrainCost;
 
 public Movable(float energy, float slowness) {
  this.energy = energy;
  this.slowness = slowness;
  
  terrainBlocked = new boolean[9];
  terrainCost = new float[9];
  
  for (int i = 0; i < terrainBlocked.length; i++) {
   terrainBlocked[i] = false;
   terrainCost[i] = 1.5f*Math.abs(i-4)+1;
  }
 }

}


Line 7 holds the total accumulated cost this entity can move across (sum of all tile costs for a given path).  Line 8 holds how many seconds it takes for the animation to move the entity by one tile.  For each terrain type, lines 10-11 tell the entity if it can move to that terrain, and how much energy it costs to do so.  For now in the constructor I say that units can move on all cells, and have a cost given by the function on line 22 (high cost for the extremes like deep water or mountain peaks, low cost for midlands like grass).

I created an "animation" system called MovementSystem to handle the smooth animation.
package com.blogspot.javagamexyz.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.blogspot.javagamexyz.gamexyz.components.MapPosition;
import com.blogspot.javagamexyz.gamexyz.components.Movable;
import com.blogspot.javagamexyz.gamexyz.components.Movement;
import com.blogspot.javagamexyz.gamexyz.maps.GameMap;
import com.blogspot.javagamexyz.gamexyz.pathfinding.Path;

public class MovementSystem extends EntityProcessingSystem {
 @Mapper ComponentMapper<Movement> mm;
 @Mapper ComponentMapper<MapPosition> pm;
 @Mapper ComponentMapper<Movable> movem;
 
 GameMap gameMap;
 
 @SuppressWarnings("unchecked")
 public MovementSystem(GameMap gameMap) {
  super(Aspect.getAspectForAll(Movement.class, MapPosition.class, Movable.class));
  this.gameMap = gameMap;
 }

 @Override
 protected void inserted(Entity e) {
  Path path = mm.get(e).path;
  
  // If the path was null (somehow) remove the movable component and get out of here!
  if (path == null) {
   e.removeComponent(mm.get(e));
   e.changedInWorld();
  }
  
  // As far as the gameMap is concerned, move the entity there right away
  // (The animation is just for show)
  else gameMap.moveEntity(e.getId(), path.getX(0), path.getY(0));
  
  // Here we can also change the NPC's animation to a walking one 
 }
 
 @Override
 protected void process(Entity e) {
  Movement movement = mm.get(e);
  MapPosition pos = pm.get(e);
  
  // Get the speed with which we move
  float slowness = movem.get(e).slowness;
  
  // Read the path and get it's length
  Path path = movement.path;
  int size = path.getLength();
  
  // Calculate what step we are on (e.g. cell_0 to cell_1, cell_1 to cell_2, etc...)
  int step = (int)(movement.elapsedTime/slowness);
  
  // Check to see if they've reached the end / gone beyond)
  if (size - 2 - step < 0) {
   // At the end of the day, no matter what, make sure the entity ended up where
   // it belonged.
   pos.x = path.getX(0);
   pos.y = path.getY(0);
   
   // Remove the movement component and let them be on their way
   e.removeComponent(movement);
   e.changedInWorld();
   return;
  }
  
  // Otherwise we must still be on the way
  // Get the coordinates of cell_i and cell_(i+1)
  int x0 = path.getX(size - 1 - step);
  int y0 = path.getY(size - 1 - step);
  
  int x1 = path.getX(size - 2 - step);
  int y1 = path.getY(size - 2 - step);
  
  // Determine how close we are to reaching the next step
  float t = movement.elapsedTime/slowness - step;
  
  // Set position to be a linear interpolation between these too coordinates
  pos.x = x0 + t * (x1-x0);
  pos.y = y0 + t * (y1-y0);
  
  // Increase the time animation has been running
  movement.elapsedTime += Gdx.graphics.getDeltaTime(); 
 }
 
 @Override
 protected void removed(Entity e) {
  // Here we can reset the entity's animation to the default one
 }
 
 @SuppressWarnings("unused")
 private void changeStep(int x0, int y0, int x1, int y1) {
  // Here we can maybe change the animation based on which direction the npc
  // is moving.  Call MapTools.getDirectionVector(x0,y0,x1,y1) to see
  // which direction entity is moving.
 }
 
}

Notice it processes entities that have the Movement (which is a temporary component), Movable, and MapPosition components.  It also has a reference to the GameMap so that it can update the entity's position in the map (this part is related to the bug mentioned at the top).

Upon insertion, the GameMap is immediately updated to believe that the entity has reached its destination on line 40.  Entities are "inserted" to the system as soon as they have a combination of all 3 required components.  Usually the entity has completes its path and has the Movement component removed naturally at the end.  If they got another Movement component before they finish the first path, the 2nd one won't trigger the "inserted" method, and so the GameMap won't be updated for the 2nd path.  Everything else will work - the entity will follow a path and look like it has moved.  It just won't have according to the map, which is probably more important.

The idea of the process method is that we compare elapsedTime to slowness to determine how many steps we have gone through.  Say we are on the 1st step (or step = 0).  That means we want to move from path(size-1) to path(size-2) (remember, the path is stored in reverse so path(0) is the target, path(size-1) is the starting cell).  On line 58 we figure out which step we are on.

We'll skip lines 61-71 for now, but below that we get the coordinates of the tiles we are moving from and to for this particular step.  On line 82 we figure out how far along that path we are (scaled from 0 inclusive to 1 exclusive).  Remember step was just movement.elapsedTime/slowness, but rounded down to an integer.  So by subtracting here, we get just the decimal part that was truncated.  In lines 85-86 we just do a linear interpolation between the two tiles.  Note that when t=0, we just go to (x0,y0).  When t=1, it would be (x1,y1).

After that we just increase elapsedTime and call it good!  Lines 61-71 sort of form a failsafe.  I remember when I was working on Spaceship Warrior, I played a lot with the particle explosions.  If there were too many, things would get laggy, and a lot of time would elapse between steps.  Consequently the particles sometimes jumped WAY farther than they should have ever gone before fading away.  This wasn't so bad - they didn't do anything and disappeared soon anyway.  But in case that happens here, we'd be accessing path(-1), (-2) or beyond!  No WAY do we want to do that!  So I included that just to make sure we didn't overshoot out goal.  It's also a nice place to go ahead and remove the Movement component, because it's a guarantee that the animation is done.

I noted in the inserted method that they would be a good spot to switch the entity to a different animation, perhaps a walking one, and then in removed that it would be a good place to switch back the default one.  I also put a shell of a method changeStep() which might be called whenever we go from step 0 to 1, 1 to 2, or so on.  In it, I reference a bit of code I wrote in MapTools which can get a vector (actually a FloatPair) pointing in the direction from one tile to another, so you can potentially switch animations from an entity moving down/right to moving up/right, or so on.
 public static FloatPair getDirectionVector(int x1, int y1, int x2, int y2) {
  FloatPair cell1 = world2window(x1, y1);
  FloatPair cell2 = world2window(x2, y2);
  return new FloatPair(cell2.x-cell1.x, cell2.y-cell1.y);
 }

Notice in lines 85-86 we have the chance of getting a decimal value for the position.  In fact, we really want that for a smooth animation.  I had started with MapPosition holding floats for position, changed it to ints, and have now changed it back to floats.  To handle that though, I had to update the world2window method to permit honest to goodness floating point world positions.
 public static FloatPair world2window(float x, float y) {
  int x0 = (int)x;
  float dx = x - x0; // purely the decimal part
  
  float posX = 5.5f + (x + 0.5f)*col_multiple;
  float posY = row_multiple*(y + 0.5f + (x0%2)*(0.5f - dx/2f) + (x0+1)%2 * dx/2f);
  
  return new FloatPair(posX, posY);
 }

Okay, so that's all good and well, but we need to let the user control stuff now.  I did a LOT of thinking about what I wanted the user experience to be, and what I want to code to be, and how to reconcile to two.  Ultimately, I want the user to click on an entity, get a little menu, select "Move" or whatever, and get options based on that.

I still think the finite state machine method I implemented last update is a good way to think of it, but now I think its a crappy way to implement it.  The control class will have to be riddled with crap like if (state == whatever) {} else {} else {} else{} blah blah blah.

I ultimately decided to split it into multiple controller files, like OverworldDefaultControl, OverworldMovingControl, etc...  My initial problem with this was that so much code will have to be copied - like click-dragging - into dozens of control systems, which would TOTALLY suck!

I ended up using the libgdx InputMultiplexer to stack controls, and it was way easier than I had imagined!  The documentation on that class was pretty sparse I thought, and there were no good tutorials.  It just kind of confused me all around...

That is, until I actually looked at its code.  It's WAY simple in hindsight!  No wonder nobody felt the need to document it!  It's so cool, it literally just is an InputProcessor that doesn't do any input processing on its own.  Instead, it has a list of InputProcessors, and it asks each one in turn: "Can you handle this?"  "Can you handle it?"  "Can you?"  As soon as the first one does, it says "Alright, it's handled!  Done!"  It goes in the order you put them in, but if you manage that you'll be okay!

Before we get to that code though, there's more we need to discuss.  First, I don't like the PlayerSelected component.  We're only ever going to have a single entity selected at a time, and it doesn't mean they get any special "processing".  Also, the MapRenderSystem I have now isn't really rendering any entities, or doing any Artemis related stuff, so why am I adding it to the Artemis World?  I even have to process it manually, so what the heck?  So there were a few changes that have been made.
  • MapRenderSystem is now just a regular old class held within OverworldScreen.
  • To handle that, OverworldScreen now must have the SpriteBatch (in fact I just gave it to AbstractScreen).
  • In the future, rendering that doesn't render entities will be handled similarly.
  • OverworldScreen just has an int to hold the ID of whatever player is selected.  No components, no nonsense.  Just an integer to say which one is the focus.
  • OverworldScreen has an Array<Pair> to hold the coordinates for all cells which the selected entity can reach, given its energy and terrain functions.
  • OverworldScreen doesn't just have an InputProcesor, but an InputMultiplexer.
  • There are presently two input systems, OverworldDefaultController and OverworldMovingController.  When the player selects an entity to move, it adds the OverworldMovingController to the InputMultiplexer so that they can move entities.
Here's what OverworldScreen looks like now:
package com.blogspot.javagamexyz.gamexyz.screens;

import com.artemis.World;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.InputMultiplexer;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.utils.Array;
import com.blogspot.javagamexyz.gamexyz.EntityFactory;
import com.blogspot.javagamexyz.gamexyz.GameXYZ;
import com.blogspot.javagamexyz.gamexyz.custom.Pair;
import com.blogspot.javagamexyz.gamexyz.maps.GameMap;
import com.blogspot.javagamexyz.gamexyz.maps.MapTools;
import com.blogspot.javagamexyz.gamexyz.renderers.MapHighlighter;
import com.blogspot.javagamexyz.gamexyz.renderers.MapRenderer;
import com.blogspot.javagamexyz.gamexyz.screens.control.overworld.OverworldDefaultController;
import com.blogspot.javagamexyz.gamexyz.systems.HudRenderSystem;
import com.blogspot.javagamexyz.gamexyz.systems.MovementSystem;
import com.blogspot.javagamexyz.gamexyz.systems.SpriteRenderSystem;

public class OverworldScreen extends AbstractScreen {
 
 public static GameMap gameMap;
 private OrthographicCamera hudCam;
 
 private SpriteRenderSystem spriteRenderSystem;
 private HudRenderSystem hudRenderSystem;
 
 private MapRenderer mapRenderer;
 private MapHighlighter mapHighlighter;
 
 public int selectedEntity;
 public Array reachableCells;
 public boolean renderMap;
 public boolean renderMovementRange;
 
 public InputMultiplexer inputSystem;
 
 
 public OverworldScreen(GameXYZ game, SpriteBatch batch, World world) {
  super(game,world,batch);

     gameMap  = new GameMap();
     hudCam = new OrthographicCamera();
     
     mapRenderer = new MapRenderer(camera,batch,gameMap.map);
     mapHighlighter = new MapHighlighter(camera, batch);
     
     world.setSystem(new MovementSystem(gameMap));
     spriteRenderSystem = world.setSystem(new SpriteRenderSystem(camera,batch), true);
     hudRenderSystem = world.setSystem(new HudRenderSystem(hudCam, batch),true);
     world.initialize();
     
     this.inputSystem = new InputMultiplexer(new OverworldDefaultController(camera,world,gameMap,this));
     Gdx.input.setInputProcessor(inputSystem);
     
     int x, y;
     for (int i=0; i<100; i++) {
      do {
       x = MathUtils.random(MapTools.width()-1);
       y = MathUtils.random(MapTools.height()-1);
      } while (gameMap.cellOccupied(x, y));
      EntityFactory.createNPC(world,x,y,gameMap).addToWorld();
     }
     
     selectedEntity = -1;
     renderMap = true;
     renderMovementRange = false;
     
 }
 
 @Override
 public void render(float delta) {
  super.render(delta);
  
  if (renderMap) {
   mapRenderer.render();
   spriteRenderSystem.process();
  }
  
  if (renderMovementRange) {
   mapHighlighter.render(reachableCells,0.2f,0.2f,0.8f,0.3f);
  }
  hudRenderSystem.process();
 }

 @Override
 public void show() {
  // TODO Auto-generated method stub
  
 }
 
 @Override
 public void resize(int width, int height) {
  super.resize(width, height);
  hudCam.setToOrtho(false, width, height);
 }

 @Override
 public void hide() {
  // TODO Auto-generated method stub
  
 }

 @Override
 public void pause() {
  // TODO Auto-generated method stub
  
 }

 @Override
 public void resume() {
  // TODO Auto-generated method stub
  
 }

 @Override
 public void dispose() {
  // TODO Auto-generated method stub
  world.deleteSystem(hudRenderSystem);
  world.deleteSystem(spriteRenderSystem);
  world.deleteSystem(world.getSystem(MovementSystem.class));
 }
}


On lines 42-69 I just initialize all the systems, variables, etc.  Notice on line 50 I've added MovementSystem to the world here instead of up in Game.  It strikes me that I don't want the MovementSystem processing while the map screen isn't showing.  On line 55 I create the InputMultiplexer.  Initially I pass an argument for the actual InputProcessors I want it focusing on, and at first, I only want the default processor.

In render() I used boolean flags to mark whether certain renderers should run.  mapRenderer.render() draws the tiles, and mapHighlighter.render() is used to highlight the reachable cells (which are set in OverworldDefaultProcessor when the player clicks on an entity).  I also pass the color, so this is a highly transparent blue shade.  Before we go to the controllers, we'll go over these two renderers:
package com.blogspot.javagamexyz.gamexyz.renderers;

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.math.MathUtils;
import com.badlogic.gdx.utils.Array;
import com.blogspot.javagamexyz.gamexyz.custom.FloatPair;
import com.blogspot.javagamexyz.gamexyz.maps.MapTools;

public class MapRenderer extends AbstractRenderer {

 private TextureAtlas atlas;
 private Array<AtlasRegion> textures;
 private int[][] map;

 public MapRenderer(OrthographicCamera camera, SpriteBatch batch, int[][] map) {
  super(camera, batch);
  this.map = map;
  
  atlas = new TextureAtlas(Gdx.files.internal("textures/maptiles.atlas"),Gdx.files.internal("textures"));
  textures = atlas.findRegions(MapTools.name);
 }
 
 public void render() {
  begin();
  
  TextureRegion reg;
  
  // 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) + 1;
  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 > map.length) x1 = map.length;
  if (y0 < 0) y0 = 0;
  if (y1 > map[0].length) y1 = map[0].length; 
  
  // Loop over everything in the window to draw
  for (int row = y0; row < y1; row++) {
   for (int col = x0; col < x1; col++) {
    reg = textures.get(map[col][row]);
    FloatPair position = MapTools.world2window(col,row);
    batch.draw(reg, position.x-reg.getRegionWidth()/2, position.y-reg.getRegionHeight()/2);
   }
  }
   
  // This line can draw a small image of the whole map
  //batch.draw(gameMap.texture,0,0);
  
  end();
 }
 
}
package com.blogspot.javagamexyz.gamexyz.renderers;

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.math.MathUtils;
import com.badlogic.gdx.utils.Array;
import com.blogspot.javagamexyz.gamexyz.custom.FloatPair;
import com.blogspot.javagamexyz.gamexyz.maps.MapTools;

public class MapRenderer extends AbstractRenderer {

 private TextureAtlas atlas;
 private Array&lt;AtlasRegion&gt; textures;
 private int[][] map;

 public MapRenderer(OrthographicCamera camera, SpriteBatch batch, int[][] map) {
  super(camera, batch);
  this.map = map;
  
  atlas = new TextureAtlas(Gdx.files.internal(&quot;textures/maptiles.atlas&quot;),Gdx.files.internal(&quot;textures&quot;));
  textures = atlas.findRegions(MapTools.name);
 }
 
 public void render() {
  begin();
  
  TextureRegion reg;
  
  // 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) + 1;
  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 &lt; 0) x0 = 0;
  if (x1 &gt; map.length) x1 = map.length;
  if (y0 &lt; 0) y0 = 0;
  if (y1 &gt; map[0].length) y1 = map[0].length; 
  
  // Loop over everything in the window to draw
  for (int row = y0; row &lt; y1; row++) {
   for (int col = x0; col &lt; x1; col++) {
    reg = textures.get(map[col][row]);
    FloatPair position = MapTools.world2window(col,row);
    batch.draw(reg, position.x-reg.getRegionWidth()/2, position.y-reg.getRegionHeight()/2);
   }
  }
   
  // This line can draw a small image of the whole map
  //batch.draw(gameMap.texture,0,0);
  
  end();
 }
 
}

Also, to make things a little easier, I created an AbstractRenderer class to start and end the batch, plus deal with color and the projection matrix.
package com.blogspot.javagamexyz.gamexyz.renderers;

import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;

public abstract class AbstractRenderer {
 
 protected OrthographicCamera camera;
 protected SpriteBatch batch;
 
 public AbstractRenderer(OrthographicCamera camera, SpriteBatch batch) {
  this.camera = camera;
  this.batch = batch;
 }
 
 protected void begin() {
  batch.setProjectionMatrix(camera.combined);
  batch.begin();
 }
 
 protected void end() {
  batch.end();
  batch.setColor(1f,1f,1f,1f);
 }
 
}

In HighlightRenderer.java I load in hex_blank.png, which is just a totally white hex cell I made:
Since white has all colors, it's perfect to filter using setColor() to make it look like whatever color you want, in our current case, blue.

OverworldDefaultController looks like this:
package com.blogspot.javagamexyz.gamexyz.screens.control.overworld;

import com.artemis.Entity;
import com.artemis.World;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.math.Vector2;
import com.blogspot.javagamexyz.gamexyz.EntityFactory;
import com.blogspot.javagamexyz.gamexyz.components.Movable;
import com.blogspot.javagamexyz.gamexyz.custom.Pair;
import com.blogspot.javagamexyz.gamexyz.maps.GameMap;
import com.blogspot.javagamexyz.gamexyz.maps.MapTools;
import com.blogspot.javagamexyz.gamexyz.screens.OverworldScreen;

public class OverworldDefaultController implements InputProcessor {
 
 private OrthographicCamera camera;
 private World world;
 private GameMap gameMap;
 
 // We need a copy of the screen implementing this controller (which has a copy of
 // the Game delegating to it) so we can change screens based on users making selections
 //private GameXYZ game;
 private OverworldScreen screen;
 
 private boolean dragging;
 
 public OverworldDefaultController(OrthographicCamera camera, World world, GameMap gameMap, OverworldScreen screen) {
  this.camera = camera;
  this.world = world;
  this.gameMap = gameMap;
  this.screen = screen;
  
  dragging = false;
 }

 @Override
 public boolean keyDown(int keycode) {
  // TODO Auto-generated method stub
  return false;
 }

 @Override
 public boolean keyUp(int keycode) {
  // TODO Auto-generated method stub
  return false;
 }

 @Override
 public boolean keyTyped(char character) {
  // TODO Auto-generated method stub
  return false;
 }

 @Override
 public boolean touchDown(int screenX, int screenY, int pointer, int button) {
  // TODO Auto-generated method stub
  return false;
 }

 @Override
 public boolean touchUp(int screenX, int screenY, int pointer, int button) {
  if (dragging) {
   dragging = false;
   return true;
  }
  
  // Otherwise, get the coordinates they clicked on
  Pair coords = MapTools.window2world(Gdx.input.getX(), Gdx.input.getY(), camera);
   
  // Check the entityID of the cell they click on
  int entityId = gameMap.getEntityAt(coords.x, coords.y);
  
  // If it's an actual entity (not empty) then "select" it (unless it's already selected)  
  if ((entityId > -1) && (entityId != screen.selectedEntity)) {
   
   // Now select the current entity
   screen.selectedEntity = entityId;
   EntityFactory.createClick(world, coords.x, coords.y, 0.1f, 4f).addToWorld();
   
   // For now let's just assume they are selecting the entity to move it
   // make sure they can really move!
   Entity e = world.getEntity(entityId);
   Movable movable = e.getComponent(Movable.class);
   screen.reachableCells = gameMap.pathFinder.getReachableCells(coords.x, coords.y, movable);
   screen.renderMovementRange = true;
   screen.inputSystem.addProcessor(new OverworldMovingController(camera,world,gameMap,screen));
 
   return true;
  }
  
  // If they didn't click on someone, we didn't process it
  return false;
 }

 @Override
 public boolean mouseMoved(int screenX, int screenY) {
  // TODO Auto-generated method stub
  return false;
 }
 
 @Override
 public boolean touchDragged(int screenX, int screenY, int pointer) {
  if (!dragging) dragging = true;
  Vector2 delta = new Vector2(-camera.zoom*Gdx.input.getDeltaX(), camera.zoom*Gdx.input.getDeltaY());
  camera.translate(delta);
  
  return true;
 }

 @Override
 public boolean scrolled(int amount) {
  if ((camera.zoom > 0.2f || amount == 1) && (camera.zoom < 8 || amount == -1)) camera.zoom += amount*0.1;
  return true;
 }
}

I went back to using a boolean flag to see if the player is click-dragging the screen.  touchDragged() and scrolled() look the same, and notice that they return true.  That means that it will tell the InputMultiplexer that they did handle the input.  If they returned false, the InputMultiplexer would continue asking the other InputProcessers if they handled it.

In touchUp() we first make sure they're not just releasing a drag (and if they are, we return true saying that we got it).  Otherwise we get the coordinates of the cell they clicked on.  If they are selecting a new entity, call screen.selectedEntity = entityId to tell the OverworldScreen who we're looking at.  Also, for now, we're assuming that clicking on an entity means you want to move it, though in the future we'll probably implement a menu system instead.

On line 86 it asks our AStarPathFinder to find a set of tiles that this entity can reach.  To make sure it's specific to this entity, we have to pass the entity's Movable component along.  Up till now, we would have gotten it using an @Mapper ComponentMapper<Movable> style command, but we can't use that now.  That method is only valid in classes that extend EntitySystem (or some derivative of it).  Since I don't really think this controller ought to extend EntitySystem (because I don't want it processing things every game cycle, just adding components when the user clicks) I had to use the slower e.getComponent(Movable.class).  It shouldn't be a big problem - this call should happen infrequently enough that the speed will be unnoticeable.

On line 87 it tells the OverworldScreen to start rendering the highlighted range, and then on 88 it adds the OverworldMovingController to the multiplexer.  Because it is added 2nd (after the default controller) it will be asked to process things only when OverworldDefaultController returns false on something.  Since here we've selected an entity, and I'm sure that's what we want to do for now if we click on someone, we return true.

Outside this loop, if they didn't select a new entity, we return false because this controller doesn't handle anything else.  That way OverworldMovingController has a chance to handle that input.
package com.blogspot.javagamexyz.gamexyz.screens.control.overworld;

import com.artemis.Entity;
import com.artemis.World;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.InputProcessor;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.blogspot.javagamexyz.gamexyz.components.Movable;
import com.blogspot.javagamexyz.gamexyz.components.Movement;
import com.blogspot.javagamexyz.gamexyz.custom.Pair;
import com.blogspot.javagamexyz.gamexyz.maps.GameMap;
import com.blogspot.javagamexyz.gamexyz.maps.MapTools;
import com.blogspot.javagamexyz.gamexyz.screens.OverworldScreen;

public class OverworldMovingController implements InputProcessor {
 private OrthographicCamera camera;
 private World world;
 private GameMap gameMap;
 private OverworldScreen screen;

 public OverworldMovingController(OrthographicCamera camera, World world, GameMap gameMap, OverworldScreen screen) {
  this.camera = camera;
  this.world = world;
  this.gameMap = gameMap;
  this.screen = screen;
 }
 
 @Override
 public boolean keyDown(int keycode) {
  // TODO Auto-generated method stub
  return false;
 }

 @Override
 public boolean keyUp(int keycode) {
  // TODO Auto-generated method stub
  return false;
 }

 @Override
 public boolean keyTyped(char character) {
  // TODO Auto-generated method stub
  return false;
 }

 @Override
 public boolean touchDown(int screenX, int screenY, int pointer, int button) {
  // TODO Auto-generated method stub
  return false;
 }

 @Override
 public boolean touchUp(int screenX, int screenY, int pointer, int button) {
  Pair coords = MapTools.window2world(Gdx.input.getX(), Gdx.input.getY(), camera);
  
  // Did they click within the movable range?
  if (screen.reachableCells.contains(coords, false)) {
   Entity e = world.getEntity(screen.selectedEntity);
   Movable movable = e.getComponent(Movable.class);
   Pair p = gameMap.getCoordinatesFor(screen.selectedEntity);
   e.addComponent(new Movement(gameMap.pathFinder.findPath(p.x, p.y, coords.x, coords.y, movable)));
   e.changedInWorld();
  }

  // Wherever they clicked, they are now done with the "moving" aspect of things
  screen.renderMovementRange = false;
  screen.selectedEntity = -1;
  screen.inputSystem.removeProcessor(this);
  return true;
 }

 @Override
 public boolean mouseMoved(int screenX, int screenY) {
  // TODO Auto-generated method stub
  return false;
 }

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

 @Override
 public boolean scrolled(int amount) {
  return false;
 }
 
}

If we reach touchUp() here, it by default means that we weren't dragging, and we weren't selecting a new entity.  Also, this system is only active if an entity has been selected to move, so we know the screen is also currently highlighting a set of cells as potential move targets.  If they click within that set (lines 57-63) we want to add a Movement component containing a path from their current location to the clicked target.  To find that path, we actually need to know the entity's current location, so I had a choice between calling e.getComponent(Position.class) or storing all entities' locations somewhere else.  The GameMap had a map to go from coordinates to entity, so I decided to also have one to go from entity to coordinates.

On lines 66-68 I say that no matter where they click (within the range or not) we should deselect the entity and stop rendering the movement range.  Remember, this can only be called if the default returned false, so if they click on another entity here (within the movement range or not) we will have never seen this code, so players can click on one entity, and then decide they'd rather click on another and it will just be selected automatically.  If we did get here though, we consider this deselecting to be the final meaning of the touchUp(), so we return true.

There are a few more helper functions that we need to go over.  In GameMap.java we added a new field and these methods
private ObjectMap<Integer,Pair> coordByEntity;
 public Pair getCoordinatesFor(int entityId) {
  if (coordByEntity.containsKey(entityId)) return coordByEntity.get(entityId);
  return null;
 }
 public boolean cellOccupied(int x, int y) {
  return (entityByCoord[x][y] > -1);
 }
 
 public void addEntity(int id, int x, int y) {
  entityByCoord[x][y] = id;
  coordByEntity.put(id, new Pair(x,y));
 }
 
 public void moveEntity(int id, int x, int y) {
  Pair old = coordByEntity.put(id, new Pair(x,y));
  entityByCoord[old.x][old.y] = -1;
  entityByCoord[x][y] = id;
 }

In AStarPathFinder I had to create a new method called getReachableCells.  To accomplish this, I did a breadth first search where I stop after cells exceed the mover's energy.  This required a special queue class which had the ability to remove arbitrary elements (in addition to reading off/removing the first in line).  I also updated the findPath method to use the mover's terrain costs and blocks.  Here's AStarPathFinder
package com.blogspot.javagamexyz.gamexyz.pathfinding;

import java.util.ArrayList;
import java.util.Collections;

import com.badlogic.gdx.utils.Array;
import com.blogspot.javagamexyz.gamexyz.components.Movable;
import com.blogspot.javagamexyz.gamexyz.custom.MyQueue;
import com.blogspot.javagamexyz.gamexyz.custom.Pair;
import com.blogspot.javagamexyz.gamexyz.maps.GameMap;
import com.blogspot.javagamexyz.gamexyz.maps.MapTools;


/**
 * A modified path finder implementation starting with Kevin Glass' AStar algorithm
 * at http://old.cokeandcode.com/pathfinding.  Original header:
 * 
 * A path finder implementation that uses the AStar heuristic based algorithm
 * to determine a path. 
 * 
 * @author Kevin Glass
 */
public class AStarPathFinder {
 /** The set of nodes that have been searched through */
 private Array<Node> closed = new Array<Node>();
 /** The set of nodes that we do not yet consider fully searched */
 private SortedList open = new SortedList();
 
 /** The map being searched */
 private GameMap gameMap;
 private int[][] map;
 /** The maximum depth of search we're willing to accept before giving up */
 private int maxSearchDistance;
 
 /** The complete set of nodes across the map */
 private Node[][] nodes;

 /**
  * Create a path finder 
  * 
  * @param gameMap The map to be searched
  * @param maxSearchDistance The maximum depth we'll search before giving up
  */
 public AStarPathFinder(GameMap gameMap, int maxSearchDistance) {
  this.gameMap = gameMap;
  this.map = gameMap.map;
  this.maxSearchDistance = maxSearchDistance;
  
  nodes = new Node[map.length][map[0].length];
  for (int x=0;x<map.length;x++) {
   for (int y=0;y<map[0].length;y++) {
    nodes[x][y] = new Node(x,y);
   }
  }
 }
 
 /**
  * @see PathFinder#findPath(int, int, int, int, Movable)
  */
 public Path findPath(int sx, int sy, int tx, int ty, Movable mover) {
  // easy first check, if the destination is blocked, we can't get there
  if (isCellBlocked(tx,ty,mover)) {
   return null;
  }
  
  // initial state for A*. The closed group is empty. Only the starting
  // tile is in the open list and it's cost is zero, i.e. we're already there
  nodes[sx][sy].cost = 0;
  nodes[sx][sy].depth = 0;
  closed.clear();
  open.clear();
  open.add(nodes[sx][sy]);
  
  nodes[tx][ty].parent = null;
  
  // While we still have more nodes to search and haven't exceeded our max search depth
  int maxDepth = 0;
  while ((maxDepth < maxSearchDistance) && (open.size() != 0)) {
   // pull out the first node in our open list, this is determined to 
   // be the most likely to be the next step based on our heuristic
   Node current = getFirstInOpen();
   if (current == nodes[tx][ty]) {
    break;
   }
   
   removeFromOpen(current);
   addToClosed(current);
   
   Array<Pair> neighbors = MapTools.getNeighbors(current.x, current.y);
   // search through all the neighbors of the current node evaluating
   // them as next steps
   for (Pair n : neighbors) {
    int xp = n.x;
    int yp = n.y;
    float nextStepCost = current.cost + mover.terrainCost[map[xp][yp]];//getMovementCost(current.x,current.y,xp,yp);
    Node neighbor = nodes[xp][yp];
    
    // If this step exceeds the movers energy, don't even bother with it
    if (nextStepCost > mover.energy) continue;
    
    // Check to see if we have found a new shortest route to this neighbor
    if (nextStepCost < neighbor.cost) {
     if (inOpenList(neighbor)) removeFromOpen(neighbor);
     if (inClosedList(neighbor)) removeFromClosed(neighbor);
    }
    
    // If it was a new shor
    if (!inOpenList(neighbor) && !inClosedList(neighbor)) {
     neighbor.cost = nextStepCost;
     neighbor.heuristic = (float)MapTools.distance(xp, yp, tx, ty);
     maxDepth = Math.max(maxDepth, neighbor.setParent(current));
     addToOpen(neighbor);
    }
   }
  }

  // since we've got an empty open list or we've run out of search 
  // there was no path. Just return null
  if (nodes[tx][ty].parent == null) {
   return null;
  }
  
  // At this point we've definitely found a path so we can uses the parent
  // references of the nodes to find out way from the target location back
  // to the start recording the nodes on the way.
  Path path = new Path();
  Node target = nodes[tx][ty];
  while (target != nodes[sx][sy]) {
   path.prependStep(target.x, target.y);
   target = target.parent;
  }
  path.prependStep(sx,sy);
  
  // thats it, we have our path 
  return path;
 }
 
 
 /**
  * 
  * @param x The x coordinate of the mover
  * @param y The y coordinate of the mover
  * @return An Array<Pair> containing the coordinates for all cells the mover can reach
  */
 public Array<Pair> getReachableCells(int x, int y, Movable mover) {
  Array<Pair> reachableCells = new Array<Pair>();
  MyQueue<Node> open = new MyQueue<Node>();
  closed.clear();
  Node start = nodes[x][y];
  start.depth = 0;
  start.cost = 0;
  open.push(start);
  while (open.size() > 0) {
   // poll() the open queue
   Node current = open.poll();
   
   for (Pair n : MapTools.getNeighbors(current.x,current.y)) {
    Node neighbor = nodes[n.x][n.y];
    float nextStepCost = current.cost + mover.terrainCost[map[n.x][n.y]];
    
    // If the cell is beyond our reach, or otherwise blocked, ignore it
    if (nextStepCost > mover.energy || isCellBlocked(n.x,n.y,mover)) continue;
    
    // Check to see if we have found a new shortest route to this neighbor, in
    // which case it must be totally reconsidered
    if (nextStepCost < neighbor.cost) {
     if (inClosedList(neighbor)) removeFromClosed(neighbor);
     if (open.contains(neighbor, false)) open.remove(neighbor,false);
    }

    if (!open.contains(neighbor, false) && !inClosedList(neighbor)) {
     neighbor.cost = nextStepCost;
     open.push(neighbor);
    }
   }
   addToClosed(current);
  }
  
  for (Node n : closed) {
   if (n.x != x || n.y != y) reachableCells.add(new Pair(n.x,n.y));
  }
  
  return reachableCells;

 }

 /**
  * Get the first element from the open list. This is the next
  * one to be searched.
  * 
  * @return The first element in the open list
  */
 protected Node getFirstInOpen() {
  return (Node) open.first();
 }
 
 /**
  * Add a node to the open list
  * 
  * @param node The node to be added to the open list
  */
 protected void addToOpen(Node node) {
  open.add(node);
 }
 
 /**
  * Check if a node is in the open list
  * 
  * @param node The node to check for
  * @return True if the node given is in the open list
  */
 protected boolean inOpenList(Node node) {
  return open.contains(node);
 }
 
 /**
  * Remove a node from the open list
  * 
  * @param node The node to remove from the open list
  */
 protected void removeFromOpen(Node node) {
  open.remove(node);
 }
 
 /**
  * Add a node to the closed list
  * 
  * @param node The node to add to the closed list
  */
 protected void addToClosed(Node node) {
  closed.add(node);
 }
 
 /**
  * Check if the node supplied is in the closed list
  * 
  * @param node The node to search for
  * @return True if the node specified is in the closed list
  */
 protected boolean inClosedList(Node node) {
  return closed.contains(node,false);
 }
 
 /**
  * Remove a node from the closed list
  * 
  * @param node The node to remove from the closed list
  */
 protected void removeFromClosed(Node node) {
  closed.removeValue(node,false);
 }
 
 /**
  * Check if a given location is valid for the supplied mover
  * 
  * @param mover The mover that would hold a given location
  * @param sx The starting x coordinate
  * @param sy The starting y coordinate
  * @param x The x coordinate of the location to check
  * @param y The y coordinate of the location to check
  * @return True if the location is valid for the given mover
  */
 protected boolean isValidLocation(int sx, int sy, int x, int y) {
  boolean invalid = (x < 0) || (y < 0) || (x >= map.length) || (y >= map[0].length);
  
  if ((!invalid) && ((sx != x) || (sy != y))) {
   //invalid = map.blocked(mover, x, y);
  }
  
  return !invalid;
 }
 
 /**
  * Get the cost to move through a given location
  * 
  * @param mover The entity that is being moved
  * @param sx The x coordinate of the tile whose cost is being determined
  * @param sy The y coordiante of the tile whose cost is being determined
  * @param tx The x coordinate of the target location
  * @param ty The y coordinate of the target location
  * @return The cost of movement through the given tile
  */
 public float getMovementCost(int sx, int sy, int tx, int ty) {
  return (float)map[tx][ty]*3f + 1;
 }

 private boolean isCellBlocked(int x, int y, Movable mover) {
  return ((mover.terrainBlocked[map[x][y]]) || gameMap.cellOccupied(x, y));
 }
 
 /**
  * Get the heuristic cost for the given location. This determines in which 
  * order the locations are processed.
  * 
  * @param mover The entity that is being moved
  * @param x The x coordinate of the tile whose cost is being determined
  * @param y The y coordiante of the tile whose cost is being determined
  * @param tx The x coordinate of the target location
  * @param ty The y coordinate of the target location
  * @return The heuristic cost assigned to the tile
  */
 public float getHeuristicCost(int x, int y, int tx, int ty) {
  return MapTools.distance(x, y, tx, ty);
  //return heuristic.getCost(map, mover, x, y, tx, ty);
 }
 
 /**
  * A simple sorted list
  *
  * @author kevin
  */
 private class SortedList {
  /** The list of elements */
  private ArrayList<Node> list = new ArrayList<Node>();
  
  /**
   * Retrieve the first element from the list
   *  
   * @return The first element from the list
   */
  public Object first() {
   return list.get(0);
  }
  
  /**
   * Empty the list
   */
  public void clear() {
   list.clear();
  }
  
  /**
   * Add an element to the list - causes sorting
   * 
   * @param o The element to add
   */
  public void add(Node o) {
   list.add(o);
   Collections.sort(list);
  }
  
  /**
   * Remove an element from the list
   * 
   * @param o The element to remove
   */
  public void remove(Object o) {
   list.remove(o);
  }
 
  /**
   * Get the number of elements in the list
   * 
   * @return The number of element in the list
    */
  public int size() {
   return list.size();
  }
  
  /**
   * Check if an element is in the list
   * 
   * @param o The element to search for
   * @return True if the element is in the list
   */
  public boolean contains(Object o) {
   return list.contains(o);
  }
 }
 
 /**
  * A single node in the search graph
  */
 private class Node implements Comparable<Node> {
  /** The x coordinate of the node */
  private int x;
  /** The y coordinate of the node */
  private int y;
  /** The path cost for this node */
  private float cost;
  /** The parent of this node, how we reached it in the search */
  private Node parent;
  /** The heuristic cost of this node */
  private float heuristic;
  /** The search depth of this node */
  private int depth;
  
  /**
   * Create a new node
   * 
   * @param x The x coordinate of the node
   * @param y The y coordinate of the node
   */
  public Node(int x, int y) {
   this.x = x;
   this.y = y;
  }
  
  /**
   * Set the parent of this node
   * 
   * @param parent The parent node which lead us to this node
   * @return The depth we have no reached in searching
   */
  public int setParent(Node parent) {
   depth = parent.depth + 1;
   this.parent = parent;
   
   return depth;
  }
  
  /**
   * @see Comparable#compareTo(Object)
   */
  @Override
  public int compareTo(Node o) {
   //Node o = (Node) other;
   
   float f = heuristic + cost;
   float of = o.heuristic + o.cost;
   
   if (f < of) {
    return -1;
   } else if (f > of) {
    return 1;
   } else {
    return 0;
   }
  }
  
  /**
   * @see Object#equals(Object)
   */
  public boolean equals(Object other) {
   if (other instanceof Node) {
    Node o = (Node) other;
    
    return (o.x == x) && (o.y == y);
   }
   
   return false;
  }
  
  public String toString() {
   return "("+x+","+y+")";
  }
 }
}

And the special queue I created is
package com.blogspot.javagamexyz.gamexyz.pathfinding;

import java.util.ArrayList;
import java.util.Collections;

import com.badlogic.gdx.utils.Array;
import com.blogspot.javagamexyz.gamexyz.components.Movable;
import com.blogspot.javagamexyz.gamexyz.custom.MyQueue;
import com.blogspot.javagamexyz.gamexyz.custom.Pair;
import com.blogspot.javagamexyz.gamexyz.maps.GameMap;
import com.blogspot.javagamexyz.gamexyz.maps.MapTools;


/**
 * A modified path finder implementation starting with Kevin Glass' AStar algorithm
 * at http://old.cokeandcode.com/pathfinding.  Original header:
 * 
 * A path finder implementation that uses the AStar heuristic based algorithm
 * to determine a path. 
 * 
 * @author Kevin Glass
 */
public class AStarPathFinder {
 /** The set of nodes that have been searched through */
 private Array&lt;Node&gt; closed = new Array&lt;Node&gt;();
 /** The set of nodes that we do not yet consider fully searched */
 private SortedList open = new SortedList();
 
 /** The map being searched */
 private GameMap gameMap;
 private int[][] map;
 /** The maximum depth of search we're willing to accept before giving up */
 private int maxSearchDistance;
 
 /** The complete set of nodes across the map */
 private Node[][] nodes;

 /**
  * Create a path finder 
  * 
  * @param gameMap The map to be searched
  * @param maxSearchDistance The maximum depth we'll search before giving up
  */
 public AStarPathFinder(GameMap gameMap, int maxSearchDistance) {
  this.gameMap = gameMap;
  this.map = gameMap.map;
  this.maxSearchDistance = maxSearchDistance;
  
  nodes = new Node[map.length][map[0].length];
  for (int x=0;x&lt;map.length;x++) {
   for (int y=0;y&lt;map[0].length;y++) {
    nodes[x][y] = new Node(x,y);
   }
  }
 }
 
 /**
  * @see PathFinder#findPath(int, int, int, int, Movable)
  */
 public Path findPath(int sx, int sy, int tx, int ty, Movable mover) {
  // easy first check, if the destination is blocked, we can't get there
  if (isCellBlocked(tx,ty,mover)) {
   return null;
  }
  
  // initial state for A*. The closed group is empty. Only the starting
  // tile is in the open list and it's cost is zero, i.e. we're already there
  nodes[sx][sy].cost = 0;
  nodes[sx][sy].depth = 0;
  closed.clear();
  open.clear();
  open.add(nodes[sx][sy]);
  
  nodes[tx][ty].parent = null;
  
  // While we still have more nodes to search and haven't exceeded our max search depth
  int maxDepth = 0;
  while ((maxDepth &lt; maxSearchDistance) &amp;&amp; (open.size() != 0)) {
   // pull out the first node in our open list, this is determined to 
   // be the most likely to be the next step based on our heuristic
   Node current = getFirstInOpen();
   if (current == nodes[tx][ty]) {
    break;
   }
   
   removeFromOpen(current);
   addToClosed(current);
   
   Array&lt;Pair&gt; neighbors = MapTools.getNeighbors(current.x, current.y);
   // search through all the neighbors of the current node evaluating
   // them as next steps
   for (Pair n : neighbors) {
    int xp = n.x;
    int yp = n.y;
    float nextStepCost = current.cost + mover.terrainCost[map[xp][yp]];//getMovementCost(current.x,current.y,xp,yp);
    Node neighbor = nodes[xp][yp];
    
    // If this step exceeds the movers energy, don't even bother with it
    if (nextStepCost &gt; mover.energy) continue;
    
    // Check to see if we have found a new shortest route to this neighbor
    if (nextStepCost &lt; neighbor.cost) {
     if (inOpenList(neighbor)) removeFromOpen(neighbor);
     if (inClosedList(neighbor)) removeFromClosed(neighbor);
    }
    
    // If it was a new shor
    if (!inOpenList(neighbor) &amp;&amp; !inClosedList(neighbor)) {
     neighbor.cost = nextStepCost;
     neighbor.heuristic = (float)MapTools.distance(xp, yp, tx, ty);
     maxDepth = Math.max(maxDepth, neighbor.setParent(current));
     addToOpen(neighbor);
    }
   }
  }

  // since we've got an empty open list or we've run out of search 
  // there was no path. Just return null
  if (nodes[tx][ty].parent == null) {
   return null;
  }
  
  // At this point we've definitely found a path so we can uses the parent
  // references of the nodes to find out way from the target location back
  // to the start recording the nodes on the way.
  Path path = new Path();
  Node target = nodes[tx][ty];
  while (target != nodes[sx][sy]) {
   path.prependStep(target.x, target.y);
   target = target.parent;
  }
  path.prependStep(sx,sy);
  
  // thats it, we have our path 
  return path;
 }
 
 
 /**
  * 
  * @param x The x coordinate of the mover
  * @param y The y coordinate of the mover
  * @return An Array&lt;Pair&gt; containing the coordinates for all cells the mover can reach
  */
 public Array&lt;Pair&gt; getReachableCells(int x, int y, Movable mover) {
  Array&lt;Pair&gt; reachableCells = new Array&lt;Pair&gt;();
  MyQueue&lt;Node&gt; open = new MyQueue&lt;Node&gt;();
  closed.clear();
  Node start = nodes[x][y];
  start.depth = 0;
  start.cost = 0;
  open.push(start);
  while (open.size() &gt; 0) {
   // poll() the open queue
   Node current = open.poll();
   
   for (Pair n : MapTools.getNeighbors(current.x,current.y)) {
    Node neighbor = nodes[n.x][n.y];
    float nextStepCost = current.cost + mover.terrainCost[map[n.x][n.y]];
    
    // If the cell is beyond our reach, or otherwise blocked, ignore it
    if (nextStepCost &gt; mover.energy || isCellBlocked(n.x,n.y,mover)) continue;
    
    // Check to see if we have found a new shortest route to this neighbor, in
    // which case it must be totally reconsidered
    if (nextStepCost &lt; neighbor.cost) {
     if (inClosedList(neighbor)) removeFromClosed(neighbor);
     if (open.contains(neighbor, false)) open.remove(neighbor,false);
    }

    if (!open.contains(neighbor, false) &amp;&amp; !inClosedList(neighbor)) {
     neighbor.cost = nextStepCost;
     open.push(neighbor);
    }
   }
   addToClosed(current);
  }
  
  for (Node n : closed) {
   if (n.x != x || n.y != y) reachableCells.add(new Pair(n.x,n.y));
  }
  
  return reachableCells;

 }

 /**
  * Get the first element from the open list. This is the next
  * one to be searched.
  * 
  * @return The first element in the open list
  */
 protected Node getFirstInOpen() {
  return (Node) open.first();
 }
 
 /**
  * Add a node to the open list
  * 
  * @param node The node to be added to the open list
  */
 protected void addToOpen(Node node) {
  open.add(node);
 }
 
 /**
  * Check if a node is in the open list
  * 
  * @param node The node to check for
  * @return True if the node given is in the open list
  */
 protected boolean inOpenList(Node node) {
  return open.contains(node);
 }
 
 /**
  * Remove a node from the open list
  * 
  * @param node The node to remove from the open list
  */
 protected void removeFromOpen(Node node) {
  open.remove(node);
 }
 
 /**
  * Add a node to the closed list
  * 
  * @param node The node to add to the closed list
  */
 protected void addToClosed(Node node) {
  closed.add(node);
 }
 
 /**
  * Check if the node supplied is in the closed list
  * 
  * @param node The node to search for
  * @return True if the node specified is in the closed list
  */
 protected boolean inClosedList(Node node) {
  return closed.contains(node,false);
 }
 
 /**
  * Remove a node from the closed list
  * 
  * @param node The node to remove from the closed list
  */
 protected void removeFromClosed(Node node) {
  closed.removeValue(node,false);
 }
 
 /**
  * Check if a given location is valid for the supplied mover
  * 
  * @param mover The mover that would hold a given location
  * @param sx The starting x coordinate
  * @param sy The starting y coordinate
  * @param x The x coordinate of the location to check
  * @param y The y coordinate of the location to check
  * @return True if the location is valid for the given mover
  */
 protected boolean isValidLocation(int sx, int sy, int x, int y) {
  boolean invalid = (x &lt; 0) || (y &lt; 0) || (x &gt;= map.length) || (y &gt;= map[0].length);
  
  if ((!invalid) &amp;&amp; ((sx != x) || (sy != y))) {
   //invalid = map.blocked(mover, x, y);
  }
  
  return !invalid;
 }
 
 /**
  * Get the cost to move through a given location
  * 
  * @param mover The entity that is being moved
  * @param sx The x coordinate of the tile whose cost is being determined
  * @param sy The y coordiante of the tile whose cost is being determined
  * @param tx The x coordinate of the target location
  * @param ty The y coordinate of the target location
  * @return The cost of movement through the given tile
  */
 public float getMovementCost(int sx, int sy, int tx, int ty) {
  return (float)map[tx][ty]*3f + 1;
 }

 private boolean isCellBlocked(int x, int y, Movable mover) {
  return ((mover.terrainBlocked[map[x][y]]) || gameMap.cellOccupied(x, y));
 }
 
 /**
  * Get the heuristic cost for the given location. This determines in which 
  * order the locations are processed.
  * 
  * @param mover The entity that is being moved
  * @param x The x coordinate of the tile whose cost is being determined
  * @param y The y coordiante of the tile whose cost is being determined
  * @param tx The x coordinate of the target location
  * @param ty The y coordinate of the target location
  * @return The heuristic cost assigned to the tile
  */
 public float getHeuristicCost(int x, int y, int tx, int ty) {
  return MapTools.distance(x, y, tx, ty);
  //return heuristic.getCost(map, mover, x, y, tx, ty);
 }
 
 /**
  * A simple sorted list
  *
  * @author kevin
  */
 private class SortedList {
  /** The list of elements */
  private ArrayList&lt;Node&gt; list = new ArrayList&lt;Node&gt;();
  
  /**
   * Retrieve the first element from the list
   *  
   * @return The first element from the list
   */
  public Object first() {
   return list.get(0);
  }
  
  /**
   * Empty the list
   */
  public void clear() {
   list.clear();
  }
  
  /**
   * Add an element to the list - causes sorting
   * 
   * @param o The element to add
   */
  public void add(Node o) {
   list.add(o);
   Collections.sort(list);
  }
  
  /**
   * Remove an element from the list
   * 
   * @param o The element to remove
   */
  public void remove(Object o) {
   list.remove(o);
  }
 
  /**
   * Get the number of elements in the list
   * 
   * @return The number of element in the list
    */
  public int size() {
   return list.size();
  }
  
  /**
   * Check if an element is in the list
   * 
   * @param o The element to search for
   * @return True if the element is in the list
   */
  public boolean contains(Object o) {
   return list.contains(o);
  }
 }
 
 /**
  * A single node in the search graph
  */
 private class Node implements Comparable&lt;Node&gt; {
  /** The x coordinate of the node */
  private int x;
  /** The y coordinate of the node */
  private int y;
  /** The path cost for this node */
  private float cost;
  /** The parent of this node, how we reached it in the search */
  private Node parent;
  /** The heuristic cost of this node */
  private float heuristic;
  /** The search depth of this node */
  private int depth;
  
  /**
   * Create a new node
   * 
   * @param x The x coordinate of the node
   * @param y The y coordinate of the node
   */
  public Node(int x, int y) {
   this.x = x;
   this.y = y;
  }
  
  /**
   * Set the parent of this node
   * 
   * @param parent The parent node which lead us to this node
   * @return The depth we have no reached in searching
   */
  public int setParent(Node parent) {
   depth = parent.depth + 1;
   this.parent = parent;
   
   return depth;
  }
  
  /**
   * @see Comparable#compareTo(Object)
   */
  @Override
  public int compareTo(Node o) {
   //Node o = (Node) other;
   
   float f = heuristic + cost;
   float of = o.heuristic + o.cost;
   
   if (f &lt; of) {
    return -1;
   } else if (f &gt; of) {
    return 1;
   } else {
    return 0;
   }
  }
  
  /**
   * @see Object#equals(Object)
   */
  public boolean equals(Object other) {
   if (other instanceof Node) {
    Node o = (Node) other;
    
    return (o.x == x) &amp;&amp; (o.y == y);
   }
   
   return false;
  }
  
  public String toString() {
   return &quot;(&quot;+x+&quot;,&quot;+y+&quot;)&quot;;
  }
 }
}

So this was a LOT, but I think it payed off in the end.  We're now primed to create a richer user experience where they are prompted with menus and can select what action to perform, and I think that's what I'm going to focus on next.  I'm torn between using the Themable Widget Library or doing something more custom, but I like the idea of storing menu structure in an XML or JSON file.  In fact, I like the idea of storing as much as possible in stuff like that so the game is easier to change down the line, or even mod (a la Civ).  We'll see what happens!

You have gained 200 XP.  Progress to Level 4: 200/700

Saturday, March 16, 2013

User Input and Screens (Level 2)

Up to this point, I've tried to implement what I considered "basic tricky" systems like animation and pathfinding.  I still have some work to do on the animation system (see some of the comments on that post), but I feel the fundamental idea is sound enough to continue until I really need to address it.

Clearly, what we have now does NOT constitute a game, but it's starting to take shape.  For this update, I wanted to add more sophisticated controls, so that the player could select one of the little dudes and tell him where to move, and select other dudes and have them decide where to move.  The idea seemed simple, but really got me thinking heavily about how to keep the code modular and managable.

In this update, we will see that done, but I had to redesign the game structure down to its core to make it happen in a way that didn't seem overly forced, and I'm really happy with how it has turned out.

First, in case you haven't read it, Andrew Steigert has a wonderful set of libgdx tutorials where he walks through creating a simple game (not unlike Spaceship Warrior) called Tyrion.  One of the main focuses of his tutorials is using Screens to manage your code.  The idea is that most games have numerous screens, each of which behave quite differently.  For instance, we may end up with a:
  • Logo splash screen
  • Main menu screen
  • Load screen
  • Overworld screen
  • Battle map screen
  • Game menu screen
    • Inventory
    • Character stats
    • Party stats
  • And who knows what the heck else?
Each of these screens out to run very different code: for instance the touchDrag() we have implemented wouldn't really make sense on a logo splash screen, or main menu screen.  You really don't want your users dragging those screens around.  Similarly, the idea of clicking on a cell and selecting an entity doesn't make sense in most contexts.  On the main menu, we really DON'T want to render the GameMap.

One of the cool things about screens is that each one can contain its own code.  For us, this could be really helpful in deciding which rendering systems to process, and setting custom controllers for each screen.  As of now, our Launcher.java file extends Game, and our GameXYZ.java implements Screen.  libgdx gave us these classes so that a Game can run, and delegate to different screens as needed, but we're not using it that way.

The first major change I made was to make Launcher.java just a regular class, and no longer extend Game.  Instead, I made GameXYZ.java extend Game.  The idea here is that GameXYZ.java will now be able to delegate to different screens.

To clarify the difference between Game and Screen, consider the methods that are part of each:
Game
  • create()
  • setScreen()
  • getScreen()
  • render(), resize(), show(), hide(), pause(), etc...
Screen
  • render(), resize(), show(), hide(), pause(), etc...
When Game "render()"s, it checks to see if it currently has a screen, and if so, calls screen.render().  In essence, Game is really just a manager for Screens.  Each screen ought to have a reference to the Game controlling it so that they can call game.setScreen(some_other_screen) - that is, so you can change screens.

I created an Abstract class called AbstractScreen.java which holds some things that I expect to be common to all the screens I use, such as an OrthographicCamera, a reference to the Artemis World (so the screens can interact with Entities and process systems), and a reference to GameXYZ.  As of now, I just implemented a single Screen called OverworldScreen which extends AbstractScreen.  OverworldScreen is more or less a rough copy of the old GameXYZ, because I want it to represent the main Screen I have as of yet.  There are a few differences we'll get to.

Here is the updated and new code for all this:
package com.blogspot.javagamexyz.gamexyz;

import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;
import com.blogspot.javagamexyz.gamexyz.utils.ImagePacker;

public class Launcher {
 
 private static final int WIDTH = 1300;
 private static final int HEIGHT = 720;
 
 public static void main(String[] args) {
  ImagePacker.run();
  
  LwjglApplicationConfiguration cfg = new LwjglApplicationConfiguration();
  cfg.width=WIDTH;
  cfg.height=HEIGHT;
  cfg.useGL20=true;
  cfg.title = "GameXYZ";
  cfg.vSyncEnabled = false;
  cfg.resizable=false;
  new LwjglApplication(new GameXYZ(WIDTH,HEIGHT), cfg);
 }
}

Two major things to discuss here.  First, Launcher no longer extends Game - that's because I'm not using Laucher to control my screens, I'm using GameXYZ to do that.  Consequenty, when I declare a new LwjglApplication I don't pass it "this", I pass it a reference to GameXYZ.java (more like the SimpleApp did).

Second, as a major improvement, I am storing the width and height in the Launcher.java file now.  To let my GameXYZ see this, I have to pass it as an argument, but this is no problem!  This is helpful because if we make an HTML5 or Android launcher, we will want to set their width and height separately from one another.

package com.blogspot.javagamexyz.gamexyz;

import com.artemis.World;
import com.badlogic.gdx.Game;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.blogspot.javagamexyz.gamexyz.screens.OverworldScreen;
import com.blogspot.javagamexyz.gamexyz.systems.ColorAnimationSystem;
import com.blogspot.javagamexyz.gamexyz.systems.ExpiringSystem;
import com.blogspot.javagamexyz.gamexyz.systems.ScaleAnimationSystem;
import com.blogspot.javagamexyz.gamexyz.systems.SpriteAnimationSystem;

public class GameXYZ extends Game {

 public int WINDOW_WIDTH;
 public int WINDOW_HEIGHT;
 
 public World world;
 private SpriteBatch batch;

 public GameXYZ(int width, int height) {
  WINDOW_WIDTH = width;
  WINDOW_HEIGHT = height;
 }
 
 public void create() {
  
     world = new World();
     batch = new SpriteBatch();
     
     world.setSystem(new SpriteAnimationSystem());
     world.setSystem(new ScaleAnimationSystem());
     world.setSystem(new ExpiringSystem());
     world.setSystem(new ColorAnimationSystem());
     world.initialize(); 
     
     setScreen(new OverworldScreen(this, batch, world));
 }
}

Here we can see we cut out a lot of code.  All we have is a constructor, with which we set the width and height, and a method called create(), which is called automatically upon creation.  I'm no expert, and I don't really understand the difference between that method and the constructor.  But I do know that if you try to jam it all into the constructor, it fails.  So I keep it separted and it works like a charm!

Notice I've set the basic processing systems, but none of the rendering systems.  I'm not sure if I want to stick with it this way, but right now each screen will be responsible for its own rendering systems.  One reason for this is that everything used to statically reference GameXYZ.gameMap, but that no longer exists.  Primarily because different screens may want different maps.

Note however that GameXYZ has its own SpriteBatch, even though its not doing any of the rendering.  All the best practices seem to indicate that it's best to have only one instance of SpriteBatch in your whole game because it's a resource hog.  All rendering systems that need it will have it passed to them.

Line 36 calls the setScreen method, which for now just goes to OverworldScreen.  Notice I pass OverworldScreen a reference to this instance of GameXYZ, a reference to the SpriteBatch, and a reference to the World.

All screens I paln on implementing will extend AbstractScreen.java

package com.blogspot.javagamexyz.gamexyz.screens;

import com.artemis.World;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.blogspot.javagamexyz.gamexyz.GameXYZ;

public abstract class AbstractScreen implements Screen {
 
 protected final GameXYZ game;
 protected final World world;
 protected final OrthographicCamera camera;
 
 public AbstractScreen(GameXYZ game, World world) {
  this.game = game;
  this.world = world;
  camera = new OrthographicCamera();
 }
 
 @Override
 public void render(float delta) {
  Gdx.gl.glClearColor(0,0,0,1);
     Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
     
     camera.update();
     
     world.setDelta(delta);
     world.process();
 }
 
 @Override
 public void show() {
 }
 
 @Override
 public void hide() {
 }
 
 @Override
 public void pause() {
 }
 
 @Override
 public void resume() {
 }
 
 @Override
 public void resize(int width, int height) {
     game.WINDOW_WIDTH = width;
     game.WINDOW_HEIGHT = height;
     
     camera.setToOrtho(false, width,height);
 }
 
 @Override
 public void dispose() {
 }
}

Notice it has fields to hold the Game and World passed into it, but it doesn't hold the SpriteBatch.  Each screen will also have its own OrthographicCamera (you don't erally want them all sharing the same camera, or zooming out in one screen could influence the way another screen renders).

The render() method calls some of the basic methods that GameXYZ.java used to.  These are things that I could see being generally useful, though I may change my mind about that later and lose the world.process().  Resize changes WINDOW_WIDTH and WINDOW_HEIGHT back in the Game, so all screens should see the updated value.

package com.blogspot.javagamexyz.gamexyz.screens;

import com.artemis.World;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.OrthographicCamera;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.badlogic.gdx.math.MathUtils;
import com.blogspot.javagamexyz.gamexyz.EntityFactory;
import com.blogspot.javagamexyz.gamexyz.GameXYZ;
import com.blogspot.javagamexyz.gamexyz.input.OverworldControlSystem;
import com.blogspot.javagamexyz.gamexyz.maps.GameMap;
import com.blogspot.javagamexyz.gamexyz.maps.MapTools;
import com.blogspot.javagamexyz.gamexyz.systems.HudRenderSystem;
import com.blogspot.javagamexyz.gamexyz.systems.MapRenderSystem;
import com.blogspot.javagamexyz.gamexyz.systems.PathRenderingSystem;
import com.blogspot.javagamexyz.gamexyz.systems.SpriteRenderSystem;

public class OverworldScreen extends AbstractScreen {
 
 public static GameMap gameMap;
 private OrthographicCamera hudCam;
 
 
 public SpriteRenderSystem spriteRenderSystem;
 public HudRenderSystem hudRenderSystem;
 public MapRenderSystem mapRenderSystem;
 public PathRenderingSystem pathRenderSystem;
 
 private OverworldControlSystem overworldControlSystem;
 
 public OverworldScreen(GameXYZ game, SpriteBatch batch, World world) {
  super(game,world);

     gameMap  = new GameMap();
     hudCam = new OrthographicCamera();
     
     spriteRenderSystem = world.setSystem(new SpriteRenderSystem(camera,batch), true);
     mapRenderSystem = world.setSystem(new MapRenderSystem(camera,batch,gameMap),true);
     hudRenderSystem = world.setSystem(new HudRenderSystem(hudCam, batch),true);
     pathRenderSystem = world.setSystem(new PathRenderingSystem(camera,batch),true);
     
     overworldControlSystem = world.setSystem(new OverworldControlSystem(camera,world,gameMap,game));
     Gdx.input.setInputProcessor(overworldControlSystem);
     
     world.initialize();
     
     int x, y;
     for (int i=0; i<100; i++) {
      do {
       x = MathUtils.random(MapTools.width()-1);
       y = MathUtils.random(MapTools.height()-1);
      } while (gameMap.cellOccupied(x, y));
      EntityFactory.createNPC(world,x,y,gameMap).addToWorld();
     }
 }
 
 @Override
 public void render(float delta) {
  super.render(delta);
  
  mapRenderSystem.process();
  pathRenderSystem.process();
  spriteRenderSystem.process();
  hudRenderSystem.process();
 }

 @Override
 public void show() {
  // TODO Auto-generated method stub
  
 }
 
 @Override
 public void resize(int width, int height) {
  super.resize(width, height);
  hudCam.setToOrtho(false, width, height);
 }

 @Override
 public void hide() {
  // TODO Auto-generated method stub
  
 }

 @Override
 public void pause() {
  // TODO Auto-generated method stub
  
 }

 @Override
 public void resume() {
  // TODO Auto-generated method stub
  
 }

 @Override
 public void dispose() {
  // TODO Auto-generated method stub
  game.world.deleteSystem(hudRenderSystem);
  game.world.deleteSystem(mapRenderSystem);
  game.world.deleteSystem(pathRenderSystem);
  game.world.deleteSystem(spriteRenderSystem); 
 }
}

This has a GameMap which is initialized in the constructor.  That means that as long as we have THIS screen running around, we'll have that same GameMap.  It also has a "hudCam" in addition to the camera defined in AbstractScreen.  Because all RenderingSystems now have to share the same SpriteBatch, you get problems if in one rendering system you call batch.setProjectionMatrix(camera.combined), but you don't want to do that for the next rendering system in line.  Once it's set for the batch once, it holds for the rest.  This runs in to that old problem of zooming out from out hud, and scrolling it away off the screen.  This would be silly, so we need a camera which WON'T be changed so the hud can always render from the perspective of that camera.

All of the RenderingSystems live here, and are initialized in the constructor.  Notice they are all given the camera and SpriteBatch we want them to use.  Furthermore, mapRenderSystem is given a reference to the gameMap (remember, it can no longer statically get GameXYZ.gameMap).

We'll skip lines 42-43 for now, but below that we just add a bunch of characters to the world.  createNPC() is a lot like createWarrior() from before.  Remember, I want to be able to SELECT the character I'm controlling at that moment, so I don't want to automatically assign ONE character to be the player.  The render() method isn't too shocking - first we call render() from AbstractScreen, then draw each of our systems in turn.  resize() not only calls super.resize(), but also deals with the hudCam.

Now for lines 42-43.  Each Screen can be controlled in its own unique way, so I created a class called OverworldControlSystem.  It gets a camera, because it needs to be able to zoom, etc..., it gets a copy of the World because it needs to be able to influence entities, the GameMap because it also had to be able to read what was going on in that.  It also has a reference to GameXYZ so that this control system has the power to change screens.

package com.blogspot.javagamexyz.gamexyz.input;

import com.artemis.Aspect;
import com.artemis.ComponentMapper;
import com.artemis.Entity;
import com.artemis.World;
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.Vector2;
import com.blogspot.javagamexyz.gamexyz.EntityFactory;
import com.blogspot.javagamexyz.gamexyz.GameXYZ;
import com.blogspot.javagamexyz.gamexyz.components.MapPosition;
import com.blogspot.javagamexyz.gamexyz.components.Movement;
import com.blogspot.javagamexyz.gamexyz.components.PlayerSelected;
import com.blogspot.javagamexyz.gamexyz.custom.Pair;
import com.blogspot.javagamexyz.gamexyz.maps.GameMap;
import com.blogspot.javagamexyz.gamexyz.maps.MapTools;

public class OverworldControlSystem extends EntityProcessingSystem implements InputProcessor {
 @Mapper ComponentMapper<MapPosition> pm;
 
 private OrthographicCamera camera;
 private World world;
 private GameMap gameMap;
 
 // We need a copy of the screen implementing this controller (which has a copy of
 // the Game delegating to it) so we can change screens based on users making selections
 private GameXYZ game;
 
 private int selectedEntity;
 private Pair pathTarget;
 private State state, lastState;
 

 @SuppressWarnings("unchecked")
 public OverworldControlSystem(OrthographicCamera camera, World world, GameMap gameMap, GameXYZ game) {
  super(Aspect.getAspectForAll(PlayerSelected.class, MapPosition.class));
  
  this.camera = camera;
  this.world = world;
  this.gameMap = gameMap;
  this.game = game;
  
  state = State.DEFAULT;
  lastState = State.DEFAULT;
  selectedEntity = -1;
 }
 
 @Override
 protected void process(Entity e) {
  
  // We should only get here if the player has selected an entity and asked for a path
  if (state == State.FIND_PATH) {
   state = State.ENTITY_SELECTED;
   lastState = State.FIND_PATH;
   
   // Get the entity's position
   MapPosition pos = pm.getSafe(e);
   
   // Add a Movement component to the entity
   Movement movement = new Movement(pos.x,pos.y,pathTarget.x,pathTarget.y, gameMap);
   e.addComponent(movement);
   e.changedInWorld();
  }
  
 }

 @Override
 public boolean keyDown(int keycode) {
  // TODO Auto-generated method stub
  return false;
 }

 @Override
 public boolean keyUp(int keycode) {
  // TODO Auto-generated method stub
  return false;
 }

 @Override
 public boolean keyTyped(char character) {
  // TODO Auto-generated method stub
  return false;
 }

 @Override
 public boolean touchDown(int screenX, int screenY, int pointer, int button) {
  // TODO Auto-generated method stub
  return false;
 }

 @Override
 public boolean touchUp(int screenX, int screenY, int pointer, int button) {
  
  // Are they releasing from dragging?
  if (state == State.DRAGGING) {
   state = lastState;
   lastState = State.DRAGGING;
   return true;
  }
  
  // Otherwise, get the coordinates they clicked on
  Pair coords = MapTools.window2world(Gdx.input.getX(), Gdx.input.getY(), camera);
   
  // Check the entityID of the cell they click on
  int entityId = gameMap.getEntityAt(coords.x, coords.y);
  
  // If it's an actual entity (not empty) then "select" it (unless it's already selected)  
  if ((entityId > -1) && (entityId != selectedEntity)){
   
   // If there was previously another entity selected, "deselect" it
   if (selectedEntity > -1) {
    Entity old = world.getEntity(selectedEntity);
    old.removeComponent(PlayerSelected.class);
    old.removeComponent(Movement.class);
    old.changedInWorld();
   }
   
   // Now select the current entity
   selectedEntity = entityId;
   Entity e = world.getEntity(selectedEntity);
   e.addComponent(new PlayerSelected());
   e.changedInWorld();
   System.out.println(e.getId());
   
   EntityFactory.createClick(world, coords.x, coords.y, 0.1f, 4f).addToWorld();
   
   lastState = state;
   state = State.ENTITY_SELECTED;
   
   return true;
  }
  
  // Are they clicking to find a new path?
  else if (state == State.ENTITY_SELECTED) {
   lastState = state;
   state = State.FIND_PATH;
   pathTarget = coords;
   return true;
  }
  
  return false;
 }

 @Override
 public boolean touchDragged(int screenX, int screenY, int pointer) {
  // If it hadn't been dragging, set the current state to dragging 
  if (state != State.DRAGGING) {
   lastState = state;
   state = State.DRAGGING;
  }
  Vector2 delta = new Vector2(-camera.zoom*Gdx.input.getDeltaX(), camera.zoom*Gdx.input.getDeltaY());
  camera.translate(delta);
  
  return true;
 }

 @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 true;
 }
 
 private enum State {
  DEFAULT,
  ENTITY_SELECTED,
  DRAGGING,
  FIND_PATH,
 };
}


This should somewhat resemble our old "PlayerInputSystem", but there are some new, neat changes.  First, I don't store information about what the player has done as booleans (boolean moving, boolean dragging, etc...) instead I defined an enum called State (lines 172-177).  The idea is that the control system will run (roughly) as a Finite State Machine, where the state dictates what it can do.

For now I've thought of a tentative list of states we may care about, starting in default (just show stuff, leave it open for most anything).  Here's how it works:
If you are in DEFAULT you can
  • Select an NPC by clicking on one (ENTITY_SELECTED)
  • Drag the screen by click dragging (DRAGGING)
If you are in DRAGGING you can
  • Return to the previous state you had been in by letting up on the button (lastState)
If you are in ENTITY_SELECTED you can
  • Find a path from that entity to any cell by clicking on it (FIND_PATH)
  • Select another entity by clicking on it (ENTITY_SELECTED)
  • Drag the screen by click draggin (DRAGGING)
If you are in FIND_PATH
  • process() will automatically find the path (if possible) and return you to just (ENTITY_SELECTED)
   
I imagine expanding this to include more things such as action menus (Move, Attack, Ability, etc...)

The OverworldControlSystem constantly keeps track of which (if any) entity is selected, which cell you have clicked on (for pathfinding), the current state, and the previous state.

On line 109 it asks the GameMap for the entity ID of what is in a particular cell.  If it's nothing, it gets -1.  If there's something there, it gets the ID.  Whatever that ID is, it adds a component called PlayerSelected to that entity (I just renamed the old "Player" component to be more appropriate).  If there had previously been a selected Entity, it removes PlayerSelected status from it first (and also any path it may have been looking at).

I updated GameMap to hold a 2D integer array to hold the ID of entities, retrievable by cell coordinates using getEntityAt(x,y).  I fear that this will be a pain in the but to maintain, but for now (since nothing is really moving) it's simple enough and works.  To keep it up to date, I pass the GameMap into EntityFactory.createNPC() so that it can store the ID into the correct cell of the array upon creation.  When things start moving, I'll have to be careful to force that to update the array.

package com.blogspot.javagamexyz.gamexyz.maps;

import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
import com.blogspot.javagamexyz.gamexyz.pathfinding.AStarPathFinder;

public class GameMap {
 public int[][] map;
 public int[][] entityLocations;
 public int width, height;
 public Pixmap pixmap;
 public Texture texture;
 public AStarPathFinder pathFinder;
 
 public GameMap() {
  HexMapGenerator hmg = new HexMapGenerator();
  map = hmg.getDiamondSquare();
  width = map.length;
  height = map[0].length;
  
  entityLocations = new int[width][height];
  
  pixmap = new Pixmap(width,height,Pixmap.Format.RGBA8888);
  
  for (int i=0; i<width;i++) {
   for (int j=0;j<height;j++) {
    pixmap.setColor(getColor(map[i][j]));
    pixmap.drawPixel(i, j);
    
    entityLocations[i][j] = -1;
    
   }
  }
  
  texture = new Texture(pixmap);
  pixmap.dispose();
  
  pathFinder = new AStarPathFinder(map, 100);
  
 }
 
 private Color getColor(int color) { //  r    g    b
  if (color == 0)      return myColor(34  ,53  ,230);
  else if (color == 1) return myColor(105 ,179 ,239);
  else if (color == 2) return myColor(216 ,209 ,129);
  else if (color == 3) return myColor(183 ,245 ,99);
  else if (color == 4) return myColor(109 ,194 ,46);
  else if (color == 5) return myColor(87  ,155 ,36);
  else if (color == 6) return myColor(156 ,114 ,35);
  else if (color == 7) return myColor(135 ,48  ,5);
  else return new Color(1,1,1,1);
 }
 
 private static Color myColor(int r, int g, int b) {
  return new Color(r/255f, g/255f, b/255f,1);
 }
 
 public int getEntityAt(int x, int y) {
  return entityLocations[x][y];
 }
 
 public boolean cellOccupied(int x, int y) {
  return (entityLocations[x][y] > -1);
 }
 
}


Other than that the changes were fairly minor.  On line 129 of OverworldControlSystem I add a cute click effect for when players select a new character to control.  One thing frustrating me about project organization is that OverworldControlSystem does extend EntityProcessingSystem, so it is an Artemis system.  But I thought it best to put it in a separate package, com.blogspot.javagamexyz.gamexyz.input.

That's a heck of an update!  I went ahead and posted the full code to the repository, including images.  Check it out using SVN from https://code.google.com/p/javagamexyz/source/browse/#svn%2Ftags%2F2013-03-16, or just browse the code.

You have gained 150 XP.  Progress to Level 3: 600/600
DING!  You have advanced to Level 3, congratulations!
As a Level 3 PC, you have mastered
  • Using animations in a libgdx/Artemis framework
  • Creating, handling, and drawing 2D tile based maps, even with Hex cells.  You can deal with helper functions like getNeighbors() and distance()
You have also gained some proficiency at
  • Basic pathfinding using the A* algorithm
  • Managing Screens using Game to split your code up into manageable chunks
Your game is now on the path to becoming something that can actually be played!



Thursday, March 14, 2013

A Star (A*) Pathfinding

I decided the next thing I wanted to implement was a path finder.  It appears to be a universal standard in game development to use the A* algorithm, so I decided to implement that for my hex map.  The good news is that most of the work was already done for me by Kevin Glass over at Coke And Code who has written an excellent article with sample code in Java.  His code is way more general than I was prepared to implement just yet, so I commented out a bunch of stuff I haven't even started thinking about yet, and changed a few things to work on a hex map, and voila!  In the process, I also read a lot about heuristics here, and found some insights to be quite interesting (e.g. not square-rooting Euclidean distance being a bad idea, adding tie-breakers to get more attractive paths, searching for multiple goals and more).  I'm sure that if I ever need to make a more sophisticated pathfinder, this website will prove invaluable!

I'll let you read Kevin Glass' article for an understanding of how the algorithm works, which is a good idea especially considering how I gutted it to get only the essential features to make it work.  When I cut the "fluff" (AKA, the stuff that makes it powerful and general) out of his algorithm, I was left with two class:
  1. Path.java
  2. AStarPathFinder.java
There were two noteworthy changes I had to make to my code, one little, one huge.  I'll start with the little one:

One of the steps in the algorithm is to find all the neighbors of a node, which was nice because I had already created a getNeighbors() method in MapTools to do exactly that.  Alas, I had to fix a major bug in mine: it had no problem returning negative coordinates for the neighbors.  That's because it was stupid and had no idea where the boundary of the world was, and when asked to find neighbors of cell (0,0), it had no problem going outside the world.  My updated algorithm changes it from a fixed size Pair[] array into the libgdx built in class Array.  This is nicer because it permits a variable length of array members (a tile at the border has fewer neighbors than a tile at the center).

Here's what the updated code looks like:
public static Array<Pair> getNeighbors(int x, int y, int n) {
  Array<Pair> coordinates = new Array<Pair>();
  int min;
  int myrow;
  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 ((col < 0) || (col >= width())) continue;
    if (x==col && y==row) continue;
    else if (x % 2 == 0) myrow = 2*y-row;
    else myrow = row;
    if ((myrow < 0) || (myrow >= height())) continue;
    coordinates.add(new Pair(col,myrow));
   }
  }
  return coordinates;
 }


One lines 8 and 12 I check to make sure the coordinates are within the boundary of the world.

The more major change is that I used to store the GameMap as its own Entity, which was nice because I could make a RenderingSystem which would catch it every cycle, and I was excited about the possibility of making the map dynamic and interact with other entities.

Unfortunately, any time I want an Entity to find a path from one point to another, that entity has to be able to see the map.  In fact, that's a pretty basic requirement for a game... things have to be able to see the things around them.  Duh, right?!  Well, that wasn't very clean when the GameMap was an Entity.

Instead, I remembered the old VoidEntitySystem which was a system which will run every time, but doesn't do so for a list of entities.  In SpaceshipWarrior we used one to spawn enemy ships.  In my game here, I'm going to use one to draw a map.

So I created a new package called com.blogspot.javagamexyz.gamexyz.maps and added a class GameMap.java into com.blogspot.javagamexyz.gamexyz.maps which looks a lot like the component did (with the addition of a pathFinder field), but now is a stand alone class.
package com.blogspot.javagamexyz.gamexyz.maps;

import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.Pixmap;
import com.badlogic.gdx.graphics.Texture;
import com.blogspot.javagamexyz.gamexyz.pathfinding.AStarPathFinder;
import com.blogspot.javagamexyz.gamexyz.utils.HexMapGenerator;

public class GameMap {
 public int[][] map;
 public int width, height;
 public Pixmap pixmap;
 public Texture texture;
 public AStarPathFinder pathFinder;
 
 public GameMap() {
  HexMapGenerator hmg = new HexMapGenerator();
  map = hmg.getDiamondSquare();
  width = map.length;
  height = map[0].length;
  pixmap = new Pixmap(width,height,Pixmap.Format.RGBA8888);
  
  for (int i=0; i<width;i++) {
   for (int j=0;j<height;j++) {
    pixmap.setColor(getColor(map[i][j]));
    pixmap.drawPixel(i, j);
   }
  }
  
  texture = new Texture(pixmap);
  pixmap.dispose();
  
  pathFinder = new AStarPathFinder(map, 100);
  
 }
 
 private Color getColor(int color) { //  r    g    b
  if (color == 0)      return myColor(34  ,53  ,230);
  else if (color == 1) return myColor(105 ,179 ,239);
  else if (color == 2) return myColor(216 ,209 ,129);
  else if (color == 3) return myColor(183 ,245 ,99);
  else if (color == 4) return myColor(109 ,194 ,46);
  else if (color == 5) return myColor(87  ,155 ,36);
  else if (color == 6) return myColor(156 ,114 ,35);
  else if (color == 7) return myColor(135 ,48  ,5);
  else return new Color(1,1,1,1);
 }
 
 private static Color myColor(int r, int g, int b) {
  return new Color(r/255f, g/255f, b/255f,1);
 }
 
}

I also changed MapRenderSystem.java to the VoidEntitySystem, and it looks like this:
package com.blogspot.javagamexyz.gamexyz.systems;

import com.artemis.systems.VoidEntitySystem;
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.math.MathUtils;
import com.badlogic.gdx.utils.Array;
import com.blogspot.javagamexyz.gamexyz.maps.GameMap;
import com.blogspot.javagamexyz.gamexyz.utils.MapTools;

public class MapRenderSystem extends VoidEntitySystem {
 private SpriteBatch batch;
 private TextureAtlas atlas;
 private Array textures;
 private OrthographicCamera camera;
 private GameMap gameMap;
 
 public MapRenderSystem(OrthographicCamera camera, GameMap gameMap) {
  this.camera = camera;
  this.gameMap = gameMap;
 }
 
 @Override
 protected void initialize() {
  batch = new SpriteBatch();
  // Load the map tiles into an Array
  atlas = new TextureAtlas(Gdx.files.internal("textures/maptiles.atlas"),Gdx.files.internal("textures"));
  textures = atlas.findRegions(MapTools.name);
 }

 @Override
 protected boolean checkProcessing() {
  return true;
 }
 
 protected void processSystem() {
  
  TextureRegion reg;
  int x, y;

  
  // 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; 
  
  // Loop over everything in the window to draw.  Draw 2 columns at once
  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);
   }
  // Due to the map generation algorithm I use, there is guaranteed to be an odd number of columns.
  // Since I drew 2 columns at once above, the far right one won't be touched.  This bit is a little
  // silly because it draws the far right column, whether it is in the frustum or not.  Oh well...
   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);
   }
   
  }
  
  // This line can draw a small image of the whole map
  //batch.draw(gameMap.texture,0,0);
 }
 
 @Override
 protected void begin() {
  batch.setProjectionMatrix(camera.combined);
  batch.begin();
 }
 
 @Override
 protected void end() {
  batch.end();
 }
}

In my main screen class, GameXYZ.java, I simply added a field for the GameMap:
public class GameXYZ implements Screen {

 public static int WINDOW_WIDTH = 1300;
 public static int WINDOW_HEIGHT = 720;
 
 OrthographicCamera camera;
 SpriteBatch batch;
 World world;
 Game game;
 
 public static GameMap gameMap;
 
 private SpriteRenderSystem spriteRenderSystem;
 private HudRenderSystem hudRenderSystem;
 private MapRenderSystem mapRenderSystem;

 public GameXYZ(Game game) {
  this.game = game;
  
     batch = new SpriteBatch();
     camera = new OrthographicCamera();
     gameMap  = new GameMap();
     world = new World();
.
.
.

Now whenever anything needs to see the GameMap, it can easily by checking GameXYZ.gameMap.  With these fairly important updates, I was able to make my new pathfinding system.  I make no guarantee that it is a smart way, not the way it will end up, but here goes.

Here is the code I ended up with from Kevin's tutorial.  I put it in a new package com.blogspot.javagamexyz.gamexyz.pathfinding:

AStarPathFinder.java
package com.blogspot.javagamexyz.gamexyz.pathfinding;

import java.util.ArrayList;
import java.util.Collections;

import com.badlogic.gdx.utils.Array;
import com.blogspot.javagamexyz.gamexyz.custom.Pair;
import com.blogspot.javagamexyz.gamexyz.utils.MapTools;


/**
 * A path finder implementation that uses the AStar heuristic based algorithm
 * to determine a path. 
 * 
 * @author Kevin Glass
 */
public class AStarPathFinder {
 /** The set of nodes that have been searched through */
 private Array<Node> closed = new Array<Node>();
 /** The set of nodes that we do not yet consider fully searched */
 private SortedList open = new SortedList();
 
 /** The map being searched */
 private int[][] map;
 /** The maximum depth of search we're willing to accept before giving up */
 private int maxSearchDistance;
 
 /** The complete set of nodes across the map */
 private Node[][] nodes;

 /**
  * Create a path finder 
  * 
  * @param heuristic The heuristic used to determine the search order of the map
  * @param map The map to be searched
  * @param maxSearchDistance The maximum depth we'll search before giving up
  * @param allowDiagMovement True if the search should try diaganol movement
  */
 public AStarPathFinder(int[][] map, int maxSearchDistance) {
  this.map = map;
  this.maxSearchDistance = maxSearchDistance;
  
  nodes = new Node[map.length][map[0].length];
  for (int x=0;x<map.length;x++) {
   for (int y=0;y<map[0].length;y++) {
    nodes[x][y] = new Node(x,y);
   }
  }
 }
 
 /**
  * @see PathFinder#findPath(Mover, int, int, int, int)
  */
 public Path findPath(int sx, int sy, int tx, int ty) {
  // easy first check, if the destination is blocked, we can't get there
//  if (map.blocked(mover, tx, ty)) {
//   return null;
//  }
  
  // initial state for A*. The closed group is empty. Only the starting
  // tile is in the open list and it's cost is zero, i.e. we're already there
  nodes[sx][sy].cost = 0;
  nodes[sx][sy].depth = 0;
  closed.clear();
  open.clear();
  open.add(nodes[sx][sy]);
  
  nodes[tx][ty].parent = null;
  
  // while we haven't found the goal and haven't exceeded our max search depth
  int maxDepth = 0;
  while ((maxDepth < maxSearchDistance) && (open.size() != 0)) {
   // pull out the first node in our open list, this is determined to 
   // be the most likely to be the next step based on our heuristic
   Node current = getFirstInOpen();
   if (current == nodes[tx][ty]) {
    break;
   }
   
   removeFromOpen(current);
   addToClosed(current);
   
   Array<Pair> neighbors = MapTools.getNeighbors(current.x, current.y);
   // search through all the neighbours of the current node evaluating
   // them as next steps
   for (Pair n : neighbors) {
    int xp = n.x;
    int yp = n.y;
    float nextStepCost = current.cost + getMovementCost(current.x,current.y,xp,yp);
    Node neighbor = nodes[xp][yp];
    if (nextStepCost < neighbor.cost) {
     if (inOpenList(neighbor)) removeFromOpen(neighbor);
     if (inClosedList(neighbor)) removeFromClosed(neighbor);
    }
    if (!inOpenList(neighbor) && !inClosedList(neighbor)) {
     neighbor.cost = nextStepCost;
     neighbor.heuristic = (float)MapTools.distance(xp,yp,tx,ty);
     maxDepth = Math.max(maxDepth, neighbor.setParent(current));
     addToOpen(neighbor);
    }
   }
  }

  // since we've got an empty open list or we've run out of search 
  // there was no path. Just return null
  if (nodes[tx][ty].parent == null) {
   return null;
  }
  
  // At this point we've definitely found a path so we can uses the parent
  // references of the nodes to find out way from the target location back
  // to the start recording the nodes on the way.
  Path path = new Path();
  Node target = nodes[tx][ty];
  while (target != nodes[sx][sy]) {
   path.prependStep(target.x, target.y);
   target = target.parent;
  }
  path.prependStep(sx,sy);
  
  // thats it, we have our path 
  return path;
 }

 /**
  * Get the first element from the open list. This is the next
  * one to be searched.
  * 
  * @return The first element in the open list
  */
 protected Node getFirstInOpen() {
  return (Node) open.first();
 }
 
 /**
  * Add a node to the open list
  * 
  * @param node The node to be added to the open list
  */
 protected void addToOpen(Node node) {
  open.add(node);
 }
 
 /**
  * Check if a node is in the open list
  * 
  * @param node The node to check for
  * @return True if the node given is in the open list
  */
 protected boolean inOpenList(Node node) {
  return open.contains(node);
 }
 
 /**
  * Remove a node from the open list
  * 
  * @param node The node to remove from the open list
  */
 protected void removeFromOpen(Node node) {
  open.remove(node);
 }
 
 /**
  * Add a node to the closed list
  * 
  * @param node The node to add to the closed list
  */
 protected void addToClosed(Node node) {
  closed.add(node);
 }
 
 /**
  * Check if the node supplied is in the closed list
  * 
  * @param node The node to search for
  * @return True if the node specified is in the closed list
  */
 protected boolean inClosedList(Node node) {
  return closed.contains(node,false);
 }
 
 /**
  * Remove a node from the closed list
  * 
  * @param node The node to remove from the closed list
  */
 protected void removeFromClosed(Node node) {
  closed.removeValue(node,false);
 }
 
 /**
  * Check if a given location is valid for the supplied mover
  * 
  * @param mover The mover that would hold a given location
  * @param sx The starting x coordinate
  * @param sy The starting y coordinate
  * @param x The x coordinate of the location to check
  * @param y The y coordinate of the location to check
  * @return True if the location is valid for the given mover
  */
 protected boolean isValidLocation(int sx, int sy, int x, int y) {
  boolean invalid = (x < 0) || (y < 0) || (x >= map.length) || (y >= map[0].length);
  
  if ((!invalid) && ((sx != x) || (sy != y))) {
   //invalid = map.blocked(mover, x, y);
  }
  
  return !invalid;
 }
 
 /**
  * Get the cost to move through a given location
  * 
  * @param mover The entity that is being moved
  * @param sx The x coordinate of the tile whose cost is being determined
  * @param sy The y coordiante of the tile whose cost is being determined
  * @param tx The x coordinate of the target location
  * @param ty The y coordinate of the target location
  * @return The cost of movement through the given tile
  */
 public float getMovementCost(int sx, int sy, int tx, int ty) {
  return (float)map[tx][ty];
  //return map.getCost(mover, sx, sy, tx, ty);
 }

 /**
  * Get the heuristic cost for the given location. This determines in which 
  * order the locations are processed.
  * 
  * @param mover The entity that is being moved
  * @param x The x coordinate of the tile whose cost is being determined
  * @param y The y coordiante of the tile whose cost is being determined
  * @param tx The x coordinate of the target location
  * @param ty The y coordinate of the target location
  * @return The heuristic cost assigned to the tile
  */
 public float getHeuristicCost(int x, int y, int tx, int ty) {
  return MapTools.distance(x, y, tx, ty);
  //return heuristic.getCost(map, mover, x, y, tx, ty);
 }
 
 /**
  * A simple sorted list
  *
  * @author kevin
  */
 private class SortedList {
  /** The list of elements */
  private ArrayList<Node> list = new ArrayList<Node>();
  
  /**
   * Retrieve the first element from the list
   *  
   * @return The first element from the list
   */
  public Object first() {
   return list.get(0);
  }
  
  /**
   * Empty the list
   */
  public void clear() {
   list.clear();
  }
  
  /**
   * Add an element to the list - causes sorting
   * 
   * @param o The element to add
   */
  public void add(Node o) {
   list.add(o);
   Collections.sort(list);
  }
  
  /**
   * Remove an element from the list
   * 
   * @param o The element to remove
   */
  public void remove(Object o) {
   list.remove(o);
  }
 
  /**
   * Get the number of elements in the list
   * 
   * @return The number of element in the list
    */
  public int size() {
   return list.size();
  }
  
  /**
   * Check if an element is in the list
   * 
   * @param o The element to search for
   * @return True if the element is in the list
   */
  public boolean contains(Object o) {
   return list.contains(o);
  }
 }
 
 /**
  * A single node in the search graph
  */
 private class Node implements Comparable {
  /** The x coordinate of the node */
  private int x;
  /** The y coordinate of the node */
  private int y;
  /** The path cost for this node */
  private float cost;
  /** The parent of this node, how we reached it in the search */
  private Node parent;
  /** The heuristic cost of this node */
  private float heuristic;
  /** The search depth of this node */
  private int depth;
  
  /**
   * Create a new node
   * 
   * @param x The x coordinate of the node
   * @param y The y coordinate of the node
   */
  public Node(int x, int y) {
   this.x = x;
   this.y = y;
  }
  
  /**
   * Set the parent of this node
   * 
   * @param parent The parent node which lead us to this node
   * @return The depth we have no reached in searching
   */
  public int setParent(Node parent) {
   depth = parent.depth + 1;
   this.parent = parent;
   
   return depth;
  }
  
  /**
   * @see Comparable#compareTo(Object)
   */
  public int compareTo(Object other) {
   Node o = (Node) other;
   
   float f = heuristic + cost;
   float of = o.heuristic + o.cost;
   
   if (f < of) {
    return -1;
   } else if (f > of) {
    return 1;
   } else {
    return 0;
   }
  }
  
  /**
   * @see Object#equals(Object)
   */
  public boolean equals(Object other) {
   if (other instanceof Node) {
    Node o = (Node) other;
    
    return (o.x == x) && (o.y == y);
   }
   
   return false;
  }
 }
}

One thing to note is that Kevin used ArrayList to hold the list of nodes, whereas I changed it to the libgdx class Array.  If you've built the SimpleApp bucket drop game you may remember them saying this:
The Array class is a libgdx utility class to be used instead of standard Java collections like ArrayList. The problem with the later is that they produce garbage in various ways. The Array class tries to minimize garbage as much as possible. Libgdx offers other garbage collector aware collections such as hashmaps or sets as well.
To do that, I had to add a .equals() method to the private class Node.

Note on lines 39-49 I load the map and initialize the node array in the constructor.  Next, the method findPath() is the main piece of the puzzle.  It starts commented out because I'm assuming my unit can move anywhere, but someday I'd like to include information about the mover.  Lines 62-68 initialize everything (again, you can read the details in Kevin's article).  Then we begin our search.

Line 75 gets the current node we will work on.  Line 83 gets an Array of coordinates of its neighbors using my getNeighbors() method.  It loops over those neighbors, checks them out, updates their cost if it has found a shorter path to get there, etc...  On line 89 it updates the cost of the path it's currently searching using getMovementCost().  If we look down at lines 221-224 we see I was just lazy and made each cell's cost equal to its value: deep ocean = 0, shallow water = 1, desert = 2, etc...  This means that for now, paths will really like to go through deep ocean, and will really hate going through mountains.  Clearly this will be something to update when the game becomes more involved.

After searching as long as it can/needs to, if it never finds a path it returns null.  Otherwise it returns our path!

On line 228 in the getHeuristicCost() method, I got rid of the Heuristic classes Kevin wrote.  As of now I can't imagine using another heuristic than the straight up distance method in MapTools, so I just use that.  Everything else is pretty much just helper methods, I didn't change too much from Kevin.  The code refers to a class called Path, which is a separate file which looks like this:

Path.java
package com.blogspot.javagamexyz.gamexyz.pathfinding;

import com.badlogic.gdx.utils.Array;

/**
 * A path determined by some path finding algorithm. A series of steps from
 * the starting location to the target location. This includes a step for the
 * initial location.
 * 
 * @author Kevin Glass
 */
public class Path {
 /** The list of steps building up this path */
 private Array<Step> steps = new Array<Step>();
 
 /**
  * Create an empty path
  */
 public Path() {
  
 }

 /**
  * Get the length of the path, i.e. the number of steps
  * 
  * @return The number of steps in this path
  */
 public int getLength() {
  return steps.size;
 }
 
 /**
  * Get the step at a given index in the path
  * 
  * @param index The index of the step to retrieve. Note this should
  * be >= 0 and < getLength();
  * @return The step information, the position on the map.
  */
 public Step getStep(int index) {
  return (Step) steps.get(index);
 }
 
 /**
  * Get the x coordinate for the step at the given index
  * 
  * @param index The index of the step whose x coordinate should be retrieved
  * @return The x coordinate at the step
  */
 public int getX(int index) {
  return getStep(index).x;
 }

 /**
  * Get the y coordinate for the step at the given index
  * 
  * @param index The index of the step whose y coordinate should be retrieved
  * @return The y coordinate at the step
  */
 public int getY(int index) {
  return getStep(index).y;
 }
 
 /**
  * Append a step to the path.  
  * 
  * @param x The x coordinate of the new step
  * @param y The y coordinate of the new step
  */
 public void appendStep(int x, int y) {
  steps.add(new Step(x,y));
 }

 /**
  * Prepend a step to the path.  
  * 
  * @param x The x coordinate of the new step
  * @param y The y coordinate of the new step
  */
 public void prependStep(int x, int y) {
  steps.add(new Step(x, y));
 }
 
 /**
  * Check if this path contains the given step
  * 
  * @param x The x coordinate of the step to check for
  * @param y The y coordinate of the step to check for
  * @return True if the path contains the given step
  */
 public boolean contains(int x, int y) {
  return steps.contains(new Step(x,y),false);
 }
 
 /**
  * A single step within the path
  * 
  * @author Kevin Glass
  */
 public class Step {
  /** The x coordinate at the given step */
  private int x;
  /** The y coordinate at the given step */
  private int y;
  
  /**
   * Create a new step
   * 
   * @param x The x coordinate of the new step
   * @param y The y coordinate of the new step
   */
  public Step(int x, int y) {
   this.x = x;
   this.y = y;
  }
  
  /**
   * Get the x coordinate of the new step
   * 
   * @return The x coodindate of the new step
   */
  public int getX() {
   return x;
  }

  /**
   * Get the y coordinate of the new step
   * 
   * @return The y coodindate of the new step
   */
  public int getY() {
   return y;
  }
  
  /**
   * @see Object#hashCode()
   */
  public int hashCode() {
   return x*y;
  }

  /**
   * @see Object#equals(Object)
   */
  public boolean equals(Object other) {
   if (other instanceof Step) {
    Step o = (Step) other;
    
    return (o.x == x) && (o.y == y);
   }
   
   return false;
  }
 }
}

The included private class Step makes me feel a little guilty because it's so similar to Node from AStarPathFinder, and also to my class Pair, and I don't want to have 3 classes where one will do, so maybe at some point I'll combine them into a single class.

To integrate it all into our code, I created a Component called Movement which just stores a Path:
package com.blogspot.javagamexyz.gamexyz.components;

import com.artemis.Component;
import com.blogspot.javagamexyz.gamexyz.GameXYZ;
import com.blogspot.javagamexyz.gamexyz.pathfinding.Path;

public class Movement extends Component {
 
 public Path path;
 
 public Movement(int x0, int y0, int tx, int ty) {
  path = GameXYZ.gameMap.pathFinder.findPath(x0, y0, tx, ty);
 }

}


Notice on line 12 where it gets the GameMap by referencing GameXYZ.gameMap, and then references its pathFinder to findPath().  If the pathfinder is unable to find a path, it will return Null.  Clearly this will be extended/changed in the future to include stuff like what kind of unit is moving, how far can it move, how well does it move over mountains, ocean, etc.

To test it out I wanted to make it so whenever you click a cell, it tries to come up with a path from my main dude to that cell.  The place to do this seemed like PlayerInputSystem touchDown() (or touchUp()), but there's a little problem.  Those listener methods can't actually see the Entity being processed, so they can't add a new Movement component to it.

Instead, I created a boolean flag: moving, along with a Pair moveTarget.  In the touchDown/Up() method I set the moving flag to be true, and set moveTarget to the cell that was clicked on.  Then, in the process() method I check to see if moving is true, and if so, I add the Movement component to the Entity (and set moving to false, so I'm not adding this same component every cycle).  Here's what it looks like:

PlayerInputSystem.java
package com.blogspot.javagamexyz.gamexyz.systems;

import com.artemis.Aspect;
import com.artemis.Entity;
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.blogspot.javagamexyz.gamexyz.EntityFactory;
import com.blogspot.javagamexyz.gamexyz.components.Movement;
import com.blogspot.javagamexyz.gamexyz.components.Player;
import com.blogspot.javagamexyz.gamexyz.custom.Pair;
import com.blogspot.javagamexyz.gamexyz.utils.MapTools;

public class PlayerInputSystem extends EntityProcessingSystem implements InputProcessor {
 
 private OrthographicCamera camera;
 private Vector3 mouseVector;
 
 private boolean moving;
 private Pair moveTarget;
 
 @SuppressWarnings("unchecked")
 public PlayerInputSystem(OrthographicCamera camera) {
  super(Aspect.getAspectForAll(Player.class));
  this.camera=camera;
  moving=false;
  moveTarget = new Pair(0,0);
 }
 
 @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);
  
  if (moving) {
   moving = false;
   Movement movement = new Movement(11,14,moveTarget.x,moveTarget.y);
   e.addComponent(movement);
   e.changedInWorld();
   
  }
 }

 @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) {
  return false;
 }

 @Override
 public boolean touchUp(int screenX, int screenY, int pointer, int button) {
  // Get the hex cell being clicked
  Pair coords = MapTools.window2world(Gdx.input.getX(), Gdx.input.getY(), camera);
  moving = true;
  moveTarget = coords;
  if (button == 1) camera.zoom = 1;
  EntityFactory.createClick(world, coords.x, coords.y, 0.2f, 4f).addToWorld();
  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;
 }
 
}

Also in the PlayerInputSystem, note line 46.  Because I added a new Movement component, it overrides the old one, but this doesn't register until you call e.changedInWorld().

Now, this is cute... maybe it works, maybe it doesn't?  How would I know?  I click, and supposedly it makes a path?  Well, let's visualize that path.  I did a quick Google search and came up with this cute little feet.png
I put it in its own new folder, "textures/misc".  Then I made a new RenderingSystem just to draw the path, called PathRenderingSystem:
package com.blogspot.javagamexyz.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.Texture;
import com.badlogic.gdx.graphics.g2d.SpriteBatch;
import com.blogspot.javagamexyz.gamexyz.components.Movement;
import com.blogspot.javagamexyz.gamexyz.custom.FloatPair;
import com.blogspot.javagamexyz.gamexyz.utils.MapTools;

public class PathRenderingSystem extends EntitySystem {
 @Mapper ComponentMapper<Movement> mm;
 
 private OrthographicCamera camera;
 private SpriteBatch batch;
 private Texture feet;
 
 @SuppressWarnings("unchecked")
 public PathRenderingSystem(OrthographicCamera camera) {
  super(Aspect.getAspectForAll(Movement.class));
  this.camera = camera;
 }

 @Override
 protected void initialize() {
  batch = new SpriteBatch();
  feet = new Texture(Gdx.files.internal("textures/misc/feet.png"));
 }
 
 @Override
 protected boolean checkProcessing() {
  // TODO Auto-generated method stub
  return true;
 }

 @Override
 protected void processEntities(ImmutableBag<Entity> entities) {
  for (int i=0; i<entities.size(); i++) {
   process(entities.get(i));
  }
 }
 
 @Override
 protected void begin() {
  batch.setProjectionMatrix(camera.combined);
  batch.begin();
 }
 
 private void process(Entity e) {
  Movement move = mm.get(e);
  if (move.path != null) {
   for (int i=0; i<move.path.getLength(); i++) {
    FloatPair coords = MapTools.world2window(move.path.getX(i), move.path.getY(i));
    batch.draw(feet, coords.x-feet.getWidth()/2, coords.y-feet.getHeight()/2);
   }
  }
 }
 
 @Override
 protected void end() {
  batch.end();
 }
}

On line 26 I grab all entities with the "Movement" component.  On line 33 I just go ahead and hardcode loading feet.png - every path cell will just be rendered with that image, so I don't have to get fancy.

For processing, first on line 57 I make sure that the path isn't null.  On lines 58-61 I loop over the path, getting all the steps, change those to coordinates for where to draw the feet in window space, then draw them.  That's just enough to actually see the path it's finding.

Pretty awesome!  Next I plan on working on the user interface a little bit.  For instance, I want to be able to click on a character to select them, then click on a map tile to move them.  I also want to be able to scroll the camera without counting as a movement click.  I'll probably also add some code to literally move the entities you click to their new location.  This part will have me think a lot more about how I want the game to feel.  I'll be shooting towards a Final Fantasy Tactics, Battle for Wesnoth, Fire Emblem -esque SRPG experience.  Because this is written with libgdx it has the potential to be deployed to desktop, HTML5, or Android, which may be too much to think about right now.  I'll probably start just focusing on desktop, but we'll see!

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