Wednesday, January 30, 2013

Spaceship Warrior Pt 2 (Level 1)

Quest Log:

Sweat drips from your brow, the stifling heat threatening to derail your concentration.  You have glanced through the code, but it is frustratingly unclear where to start.  You decide to go to your roots.  Start with the smallest pieces.

Set up a two Java Projects as we discussed before, GameXYZ and GameXYZ-desktop.  In GameXYZ's buildpath, add gdx.jar and artemis-xxxxxxx.jar.  Don't forget to hit the "Order and Export" tab in check gdx.jar.  In GameXYZ-desktop's add gdx-natives, gdx-backend-lwjgl, and gdx-backend-lwjgl-natives.  Also, make sure you hit the Build Path -> Projects tab and reference GameXYZ.  Spaceship Warrior uses more .jar's than we have included, but we'll build up to them.

In GameXYZ-desktop, create a Launcher.java class in new package com.gamexyz.
package com.gamexyz;

import com.badlogic.gdx.Game;
import com.badlogic.gdx.backends.lwjgl.LwjglApplication;
import com.badlogic.gdx.backends.lwjgl.LwjglApplicationConfiguration;

public class Launcher extends Game {
 public static final int FRAME_WIDTH = 1280;
 public static final int FRAME_HEIGHT = 900;
 
 @Override
 public void create() {
  setScreen(new GameXYZ(this));
 }
 
 public static void main(String[] args) {
  LwjglApplicationConfiguration cfg = new LwjglApplicationConfiguration();
  cfg.width=FRAME_WIDTH;
  cfg.height=FRAME_HEIGHT;
  cfg.useGL20=true;
  cfg.title = "GameXYZ";
  new LwjglApplication(new Launcher(), cfg);
 }
}


In GameXYZ create a GameXYZ.java class in new package com.gamexyz.
package com.gamexyz;

import com.badlogic.gdx.Game;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Screen;
import com.badlogic.gdx.graphics.GL10;
import com.badlogic.gdx.graphics.OrthographicCamera;

public class GameXYZ implements Screen {

 OrthographicCamera camera;

 public GameXYZ(Game game) {

     camera = new OrthographicCamera();
     camera.setToOrtho(false, 1280,900);
 }
 
 @Override
    public void render (float delta) {
     
     Gdx.gl.glClearColor(0,0,0.2f,1);
     Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
     
     camera.update();         
 }
}
Eclipse Tip:  You don't have to put in your own import commands.  Once you have typed the code without them, you can press ctrl+shift+o, and they will automatically get added.  If there is any ambiguity, you have to select which package you meant to import from.


Eclipse will now warn you that because class GameXYZ implements Screen, it must implement a series of methods.  It can automatically add them, leaving them empty.  I like to add reporters to them, so my list looks like this:

 @Override
    public void resize (int width, int height) {
     System.out.println("Resize");
    }

 @Override
    public void pause () {
     System.out.println("Pause");
    }

 @Override
    public void resume () {
     System.out.println("Resume");
    }

 @Override
    public void dispose () {
     System.out.println("Dispose");
    }

 @Override
 public void show() {
  System.out.println("Show");
 }

 @Override
 public void hide() {
  System.out.println("Hide");
 }

Cold dread washes over you as you realize just how different things seem.  Now our Launcher extends something called Game.  And our GameXYZ implements something called Screen.  You check the Javadocs for them, and see that Game is an ApplicationListener, like what you used in Drop, but that it can delegate to screens, allowing multiple screens to be created.  This strikes you as a powerful tool, because someday you plan on having splash screens, menus, inventory screens, and more.  Perhaps this Game/Screen feature will be useful.

You decide that it's time to start pursuing Artemis, you want to create a character for your Spaceship Warrior.  You check the demo code and decide that you should start by making Components for Position and Sprite.  In GameXYZ, you create a new package called com.gamexyz.components and add these two classes to it:

package com.gamexyz.components;

import com.artemis.Component;

public class Position extends Component {
 
 public Position(float x, float y) {
  this.x = x;
  this.y = y;
 }
 
 public Position() {
  this(0,0);
 }
 
 public float x, y;
}
package com.gamexyz.components;

import com.artemis.Component;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Texture;

public class Sprite extends Component {
 
 public Sprite(String path) {
  sprite = new Texture(Gdx.files.internal(path));
 }
 
 public Sprite() {
  this("textures-original/fighter.png");
 }
 
 public Texture sprite;
 public float r = 1;
 public float g = 1;
 public float b = 1;
 public float a = 1;
 public float scaleX = 1;
 public float scaleY = 1;
 public float rotation;

}
Your Sprite class is a shadow of the target, but it should be enough to start with.  You also chose to copy the entire "textures-original" folder from Spaceship Warrior, and put it in your GameXYZ-desktop package.  A quick run of Launcher reveals that your code is not broken... yet.  Clearly though, these components alone are not enough to draw your ship to the screen.

Artemis is based on Components and Systems.  Components only hold data, but no logic.  Systems grab all the entities that have the relevant components and process the logic of them.  For instance, a SpriteRenderSystem may be used to draw entities that have both Position and Sprite classes.

Create a new package com.gamexyz.systems and a new class SpriteRenderSystem.java.
public class SpriteRenderSystem extends EntitySystem {
 @Mapper ComponentMapper<Position> pm;
 @Mapper ComponentMapper<Sprite> sm;
 
 private OrthographicCamera camera;
 private SpriteBatch batch;
 
 @SuppressWarnings("unchecked")
 public SpriteRenderSystem(OrthographicCamera camera) {
  super(Aspect.getAspectForAll(Position.class, Sprite.class));
  this.camera = camera;
 }
 
 @Override
 protected void initialize() {
  batch = new SpriteBatch();
 }

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

 @Override
 protected void processEntities(ImmutableBag<Entity> entities) {
  
  for (int i = 0; i < entities.size(); i++) {
   process(entities.get(i));
  }
 }
 
 @Override
 protected void begin() {
  batch.setProjectionMatrix(camera.combined);
  batch.begin();
 }
 
 protected void process(Entity e) {
  if (pm.has(e)) {
   Position position = pm.getSafe(e);
   Sprite sprite = sm.get(e);
   
   batch.setColor(sprite.r, sprite.g, sprite.b, sprite.a);
   float posx = position.x;
   float posy = position.y;
   
   batch.draw(sprite.sprite, posx, posy);
  }
 }
 
 @Override
 protected void end() {
  batch.end();
 }
}

As with Sprite.java, you decided to start smaller, with more familiar tools.  You notice that SpriteRenderingSystem extends EntitySystem, and you look up its code in the Google code repository.  You notice that it has a process() method that looks like this:
public final void process() {
  if(checkProcessing()) {
    begin();
    processEntities(actives);
    end();
  }
}

As such you learned that by switching checkProcessing() to false, you can disable the system.  You also learned the order our child methods are called in.

Using @Mapper instead of getComponent speeds execution up, apparently.  getAspectForAll() gets all the entities which have EVERY one of the argument Components.  You can also use getAspectForOne to get entities that only have at least one of the listed Components.  process() is where most of the magic happens, but begin() and end() call batch.begin() and batch.end(), which we know is important from the drop demo.

You review the demo code to see how to start processing your new system.  In GameScreen.java, you see they have defined a "World", and they registered their systems to this world.  They also have a special object for SpriteRenderSystem, though not all of their systems got this same treatment.  Also, when they add the SpriteRenderSystem, it gets a second argument of true.  Reading more documentation you learn that this is to stop SpriteRenderSystem from running automatically, you must call it manually.

You study the code that shows initializing the world, creating a new Entity, and adding Components to it.  You also look down in the render() method, and see the use of world.setDelta(), world.process(), and spriteRenderSystem.process().  You include all these in your new GameXYZ.java:
public class GameXYZ implements Screen {

 private OrthographicCamera camera;
 private Game game;
 private World world;
 
 private SpriteRenderSystem spriteRenderSystem;

 public GameXYZ(Game game) {
  
     camera = new OrthographicCamera();
     camera.setToOrtho(false, 1280,900);
     
     this.game = game;
     
     world = new World();
     spriteRenderSystem = world.setSystem(new SpriteRenderSystem(camera),true);
     
     world.initialize();
     
     Entity e = world.createEntity();
     e.addComponent(new Position(150,150));
     e.addComponent(new Sprite());
     e.addToWorld();
 }
 
 @Override
    public void render (float delta) {
     
     Gdx.gl.glClearColor(0,0,0.2f,1);
     Gdx.gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
     
     camera.update();
     
     world.setDelta(delta);
     world.process();
     spriteRenderSystem.process();
 }
    (...)
}

You expertly use ctrl+shift+o to clean up any needed imports, and congratulate yourself on a job well done.  You have a ship.

You gain 50 XP.  Progress to Level 2: 100/400

4 comments:

  1. Awesome stuff! I found this when I searched for LibGDX Artemis tutorial, there isn't much out there.

    ReplyDelete
  2. Thanks Nicholas! I know how little there is, when I was first searching around I couldn't find anything. I'm glad you're finding this helpful!

    ReplyDelete
  3. a very well explained tutorial, can't wait to read the next one, good job sir :D

    ReplyDelete