Libgdx - Scene2dui and entity component systems

I am a big fan of computer graphics programming (or any kind of visuals really), including videogames. In 2010, I came across a post by Mario Zechner(@badlogicgames), creator of the libgdx library/framework. I am unable to find it, but i do remember it being an opengles tutorial for android, it was a pretty neat tutorial, so i started following his blog, and saw the beginnings of what would be one of the most important open source game development frameworks around.
I am by no means a "real" game developer, I have tried, but because of the art assets I always get stuck...anyways. Last year I started this little math game, using the libgdx framework, it was meant to be done fast, in order to have my first game published.
Once I had this decent progress, I tried to get some art assets, but had no luck (I even posted about it in opengameart.org which was my best chance since i had no money to pay to for the assets to be made, some time later I tried with money! but still was unsuccessful). Anyways, project was abandoned, bla bla bla.

Earlier this year, out of curiosity I decided to try to redo the game using one of the entity component systems lying around (and I have even pushed myself as far as attemping to create assets myself!), I considered using Ashley, but at the time I started working on the game it had no support for libgdx's GWT backend, so I opted out for the artemis-odb library.
Since there doesn't seem to be much information on how to create a game using entity component systems (most of them seem to be based on scene graphs), I thought about documenting some of the functionality of the game.

First off, I have never seen any kind of information on how you can use libgdx's scene2dui along with your entities. Since the ui is my only kind of input that the game have, I needed to have my entities listen to ui widgets.

The game is about little archers that shoot vikings for whatever reason by solving some simple math equations, the math equations are represented by a Label and 2 TextButton.
Here's how an archer entity looks in the game:
Funny looking archer huh?

In the above image, when the user clicks on button "8" the archer should shoot.
Here are all the components which the archer is composed of:

  • Id -> a unique identifier for each entity created in the game
  • Tag -> this tells me what kind of entity this one is, for the archer, the tag is simply "archer"
  • Facing -> the direction which the archer if facing towards
  • Equation -> contains information about the equation that the archer listens to
  • Spatial -> position, size, etc etc
  • Animator -> chooses a frame out of an animation for rendering the archer

The component we are interested in is the equation, since this will allow the systems to alert the archer to shoot. Here's what it looks like:

public class Equation extends Component{  
    public int n1, n2, prevN1, prevN2;
    public char operator, prevOperator = '*';
    public float result, wrongResult, timeTillNextAnswer;
    public boolean isAnswerGiven, isWaitingDelay, isAnswerCorrect;

    public StringBuilder stringValue;
    public Equation(){
        this.stringValue = new StringBuilder();
    }
}

And a small explanation of what the data is for!:

  • n1 and n2 are the number involved in the equation
  • prevN1 and prevN2 are, as they suggest, the previous value of n1 and n2, they are used to avoid repeating the same operation twice in a row to make it less boring, since you know...math is not exactly popular
  • operator and prevOperator should be pretty straight forward to understan
  • result and wrongResult are...i suppose you guessed already
  • timeTillNextAnswer is a small counter to give each player's response a delay
  • isAnswerGiven, isWaitingDelay and isAnswerCorrect are just flags to control the above points
  • stringValue is what I use to print the equation

The processing of the equation could be split on 2 different steps:

  • Processing the input -> check if the user is able to shoot (there is a small delay once the user solves an equation) and see if he solved the equation correctly
  • Processing the equation refresh -> keep the delay for a certain ammount of time, provide user with a visual feedback, wait for the archer "aiming" animation, and spawn a new arrow entity.

Since I want to explain the interaction with scene2dui, I will just cover the input processing.

First, I needed to keep some kind of track of what buttons / label is bound to which equation, since the buttons only live within the scene2d Stage. I decided for a simple solution: create my own stage.
Now, the stage I use in the game does some other stuff unrelated to equations, but here's what it uses to work with equations:

public class GameStage extends Stage {  
    public ObjectMap<TextButton, Equation> buttonForEquation;
    private ObjectMap<Equation, EquationWidget> equationWidgets;

        // ...more fields

    public GameStage(OrthographicCamera camera) {
         super(new FitViewport(MathGame.VIRTUAL_WIDTH, MathGame.VIRTUAL_HEIGHT, camera), MathGame.BATCH);
         buttonForEquation = new ObjectMap<TextButton, Equation>();
         equationWidgets = new ObjectMap<Equation, EquationWidget>();
               //more initialization code
    }

    public void createWidgetsFor(Equation eq, float x, float y){
         Skin skin = Assets.getUISkin();
       EquationWidget tbl = new EquationWidget(skin, eq);
       tbl.setPosition( x, y + 15 );
       equationWidgets.put(eq, tbl);
       for(TextButton tb : tbl.getButtons())
            buttonForEquation.put(tb, eq);
       addActor(tbl);
     }

  public Array<TextButton> getButtonsFor(Equation eq){
       return equationWidgets.get(eq).getButtons();
  }

  public Label getLabelFor(Equation eq){
       return equationWidgets.get(eq).getEquationLabel();
  }

  //methods below have nothing to do with input, but they are still related to equations, so I will leave them there        

  public void check(Equation eq){
      equationWidgets.get(eq).mark(check);
  }

  public void cross(Equation eq){
      equationWidgets.get(eq).mark(cross);
  }

  public void clear(Equation eq){
      equationWidgets.get(eq).mark(null);
  }

  public void refreshUI(Equation eq){
      equationWidgets.get(eq).invalidate();
  }
}

So that's it, the stage only contains maps to look for equation stuff, and a method to create a so called EquationWidget, which just creates two buttons and a label, and puts them in a table. The input system is what will request the stage to create the equation actors when an entity with an equation component spawns into the game.

First, let's see the members of the system, as well as how it actually inserts the actors into the stage:

public class EquationInputSystem extends EntityProcessingSystem{  
     ClickListener listener;
     TileMap map;
     @Wire ComponentMapper<Equation> em;
     @Wire ComponentMapper<Spatial> sm;
     @Wire ComponentMapper<Animator> am;
     GameStage stage;
     TweenManager tweenManager;

     @SuppressWarnings("unchecked")
    public EquationInputSystem(GameStage stage, TileMap map) {
        super(Aspect.getAspectForAll(Equation.class));
        this.stage = stage;
        this.map = map;
        listener = new ClickListener(){
            // let's not worry about this now...
        };
    }

     @Override
     protected void inserted(Entity e) {
         Equation eq = em.get(e);
          Spatial sp = sm.get(e);
        Vector2 pixelPositions = sp.coords.cpy().scl(Constants.PIXELS_PER_METER);
           stage.createWidgetsFor(eq, pixelPositions.x, pixelPositions.y);
           for( TextButton tb : stage.getButtonsFor(eq))
            tb.addListener(listener);
        }
     }

So, the input system contains a ClickListener, the reason is that within that click, we contain the logic to process the input (which is basically, should I shoot or not?).
Notice that once an entity with an Equation is added to the world, we get its position( Spatial ) and tell the stage to create the widgets for us, at a certain location.
Once we created the actors, we just need to add our listener to the buttons!

Next, see what the listener does in order to allow archer to shoot:

     listener = new ClickListener(){
        @Override
        public void clicked(InputEvent event, float x, float y) {
            TextButton clickedButton = (TextButton) event.getListenerActor();
            GameStage gameStage = EquationInputSystem.this.stage; 
            Equation eq = gameStage.buttonForEquation.get( clickedButton );
            if ( eq.timeTillNextAnswer > 0){
                event.cancel();
          return;
        }
      float value = Float.parseFloat(clickedButton.getText().toString());
      eq.isAnswerCorrect = value == eq.result;
      eq.isAnswerGiven = true;
      if ( eq.isAnswerCorrect ){
          eq.timeTillNextAnswer = Constants.CORRECT_ANSWER_DELAY;
          gameStage.check(eq);   // add a green check on top of our answer, we solved it right!
          //play ok sound
      }else{
          eq.timeTillNextAnswer = Constants.INCORRECT_ANSWER_DELAY;
          gameStage.cross(eq);
          //play incorrect sound
          EquationInputSystem.this.map.score.incorrect(eq.operator); // show a red X on top of our chosen answer, since we failed the equation
      }
      eq.isWaitingDelay = true;
      dirty = true;
      }
  };

So, listener will just take the equation, see if enough time has passed for it to be clicked, if it has, flag the equation as appropiate, and add to it a new delay so player can't click it again for some time. Then add some eye candy (cross or check, depending on correct or incorrect answer).

There it is, now we have our components and game ui interacting with each other!.
That should give you a good example on how to make your ui and entities interact among them in your games, regardless of the entity component system engine you are using.

Now in case you are curious, here's the rest of the code to process the archer entity it has solved the equation correctly:

    TweenCallback shootArrow = new TweenCallback() {
      @Override
      public void onEvent(int type, BaseTween<?> source) {
          Entity e=((Entity)source.getUserData());                    // changes archer status to idle ( since it already shot!), and adds a ProjectileEmmision, which is then processed by another system
          Animator a = am.get(e);
          Equation eq = em.get(e);
          a.action = Constants.STATUS_ARCHER_IDLE;
          a.stateTime = 0;
          ProjectileEmission pe = e.createComponent(ProjectileEmission.class);
          pe.attachedOperator = eq.operator;
          e.changedInWorld();
      }
    };

     @Override
     protected void process(Entity e) {
        Equation eq = em.get(e);
        if ( eq.isAnswerGiven && eq.isAnswerCorrect ){
            Animator a = am.get(e);
            a.action = Constants.STATUS_ARCHER_SHOOT;
            eq.isAnswerGiven = false;
            a.stateTime = 0;
            Tween                               // basically this is just a timer to call  shoot arrow callback once the archer's aiming animation is finished
                .call(shootArrow)
                .setUserData(e)
                .delay(Constants.ARCHER_SHOOT_DURATION)
                .start(tweenManager);
    }
}
@Override
protected void end() {  
    dirty = false;
}

@Override
protected boolean checkProcessing() {  
    return dirty;
}
comments powered by Disqus