Artenus 2D Framework
Artenus Getting Started

Creating the Game Scene

This game scene is the most important part of this project, as it helps you understand how entity collections are used, and learn how user input can be used in a game. Before we get into that, let us first write the basic skeleton of this scene.

/**
 * This is the game scene, which is displayed if you click the play button.
 */
public class GameScene extends Scene {

    public GameScene(Stage parentStage) {
        super(parentStage);
    }

    @Override
    public void onLoaded() {
        // TODO: Set up the scene
        super.onLoaded();
    }

    @Override
    public void advance(float elapsedTime) {
        super.advance(elapsedTime);
        // TODO: Handle scene animation
    }

    @Override
    public boolean onBackButton() {
        stage.setScene(new MenuScene(stage));
        return true;
    }
}

There is nothing in the code above that we haven't already discussed. Notice that the back button returns control to the menu scene. Always create a new instance of the next scene when you switch scenes. Keeping static instances of scenes is bad practice in the Artenus framework.

Declaring local resources

As mentioned before, resources can either be declared globally in the onLoadStage method of the stage manager, or locally for individual scenes. This is important for cases where there are many resources each used only by one scene. Keeping resources for all scenes in memory may not be feasible in such cases. Locally loaded resources will be cleared once the scene is switched out.

To demonstrate how local resources work, our example has two local resources: the character, and the directional knob HUD element. These elements are not used in the menu scene, and so they need only to be loaded for the game scene. Local declaration of texture is done using texture manager's addLocal method.

@Override
public void onLocalLoad() {
    TextureManager.addLocal(R.raw.anim_girl, R.raw.arrow);
}

Setting up the scene

Although we can implement this scene in a simpler way, we are going to implement it in a way that is close to a real game scenario. In a real game, there are different layers such as background, game objects (foreground), and HUD. In this tutorial we just use the last two. The entity-based system allows us to add collections of entities to a scene as if they were single entities. So, we need to add two entities to the scene: a game layer, and an HUD. Every other entity will then be added to one of these two. So, add the following two fields to the scene:

/**
 * The Game layer contains all game objects.
 */
private EntityCollection gameLayer = new EntityCollection();

/**
 * The HUD layer contains, well, the HUD!
 */
private EntityCollection hudLayer = new EntityCollection();

Add also the following fields for the character and the HUD. This is required since we need to access these entities later for animation and input handling.

/**
 * Yes, our main and only character is a girl!
 */
private ImageSprite girl;

/**
 * This is the HUD for the direction knob. The knob spawns wherever you first touch the screen.
 */
private ImageSprite arrowSprite;

Now we can write the onLoaded method as follows:

@Override
public void onLoaded() {
    // Setup the atlas cutout
    ImageSprite.Cutout cutout = new ImageSprite.Cutout(150, 120, 6, 2);

    // Create the character
    girl = new ImageSprite(R.raw.anim_girl, cutout);
    girl.setPosition(stage.getLogicalWidth() / 2, stage.getLogicalHeight() / 2);
    gameLayer.add(girl);

    // Create the direction indicator
    arrowSprite = new ImageSprite(R.raw.arrow, new ImageSprite.Cutout(256, 256, 1));
    arrowSprite.setAlpha(0);
    arrowSprite.setPosition(0, 0);
    hudLayer.add(arrowSprite);

    // You could have done this before adding items to them, it wouldn't make a difference.
    // It is only important to add the HUD layer after the game layer, so its contents are
    // displayed above game contents.
    add(gameLayer);
    add(hudLayer);

    super.onLoaded();
}

Arrow sprite's alpha is initially set to 0, which makes it invisible. It will be made visible whenever the user moves the character. Also note the cutout specification for the girl sprites, which cuts out 12 frames (a 6x2 grid), each of which having a width of 150 and a height of 120. These are animation frames used later to make a walking impression.

Handling user input

We have already responded to user actions through touch buttons and the back button. Beside these features, Artenus also provides a more game-oriented user input tool called game input, which represents a game controller with a single direction knob and up to four buttons. The concrete implementation might map this to a real physical controller, or a series of touch gestures. For example, slide input uses slide gestures for the direction knob, and touch buttons for action buttons. This is currently the only game input available in the framework, and it is what we are going to use in this tutorial.

To handle user input as described above, you need two components: a game input instance added to the scene, and a listener that handles events produced by that instance. For simplicity, we let the scene itself handle those events. First thing is to add a slide input instance to the scene. So, add the following to the onLoaded method:

SlideInput controller = new SlideInput();
controller.setEffectiveArea(0, 0, stage.getLogicalWidth(), stage.getLogicalHeight());
controller.setListener(this);
add(controller);

Note that we set the listener to be the scene itself, but we haven't yet implemented the listener interface. Before we do so, we add a field to the scene to keep the directional knob's value. This will be used to animate the character.

/**
 * Holds movement direction of the character.
 */
private Point2D direction = new Point2D(0, 0);

Now, have the scene implement the InputListener interface, and add the following method to it:

@Override
public void inputStatusChanged(GameInput input) {
    SlideInput slideInput = (SlideInput) input;
    direction = input.getDirection();

    if (direction.x == 0 && direction.y == 0) {
        // If there is no direction, freeze everything
        arrowSprite.setAlpha(0);
    } else {
        // Otherwise, show the direction HUD entity, and rotate the character
        arrowSprite.setAlpha(0.5f);
        arrowSprite.setPosition(slideInput.getRefX(), slideInput.getRefY());
        arrowSprite.setRotation((float) Math.toDegrees(Math.atan2(direction.y, direction.x)));
        girl.setRotation(90 + arrowSprite.getRotation());
    }
}

Each time the direction changes to a non-zero number, the direction indicator (arrowSprite) will become visible, and its direction will change to the direction of the knob. The character's direction will also change accordingly. Note that there is still no animation or movement. This is what we are going to do next.

Animating the character

Now that we receive user input, it is time to use it to move the character. All animation in the scene is handled in the advance method. So, we add our movement animation there:

@Override
public void advance(float elapsedTime) {
    super.advance(elapsedTime);
    float dx = direction.x * elapsedTime * 100;
    float dy = direction.y * elapsedTime * 100;
    girl.move(dx, dy);
}

The character will move 100 logical pixels per second in the direction of the knob. The direction vector's length is always 1. If you run the game now, you will see that something is missing. The character will move like a ghost. A real human takes steps when she is moving. Fortunately, we have the animation for walking in our texture cutout. To play this animation, we need to create an image animation and assign it to the sprite whenever walking is taking place. Add the following field to the scene:

/**
* Walking animation handler for our character.
*/
private final ImageAnimation walkingAnimation = new ImageAnimation(
    new int[] { 0, 1, 2, 1, 0, 3, 4, 3 },
    Trends.LOOP,
    0
);

The first argument of the constructor is the list of frames in the animation. The second argument is the trend (whether it should be played once, looped, etc), and the third parameter is the index of the first animation frame to be played. All we need to do now is to assign (and remove) this animation to (from) the character at the appropriate time. The inputStatusChanged seems like the best place for this. So, following is the modified version of this method:

@Override
public void inputStatusChanged(GameInput input) {
    SlideInput slideInput = (SlideInput) input;
    direction = input.getDirection();

    if (direction.x == 0 && direction.y == 0) {
        // If there is no direction, freeze everything
        arrowSprite.setAlpha(0);
        girl.setAnimation(null);
    } else {
        // Otherwise, show the direction HUD entity, and rotate the character
        arrowSprite.setAlpha(0.5f);
        arrowSprite.setPosition(slideInput.getRefX(), slideInput.getRefY());
        arrowSprite.setRotation((float) Math.toDegrees(Math.atan2(direction.y, direction.x)));
        girl.setAnimation(walkingAnimation);
        girl.setRotation(90 + arrowSprite.getRotation());
    }
}

Adding a sound effect

No game is complete without a sound. To have an example of how sound effects are used in Artenus, we add a stepping sound to our character. We have already declared this sound effect in our main activity. We just need to play it at the right time, which is the animation frame where the character's foot touches the ground. We add this convenience function to the class to check for such frames and play the sound.

private int savedFrame = 0;

private void checkStepSound() {
    final int frame = girl.getCurrentFrame();

    if (savedFrame != frame && (frame == 1 || frame == 3)) {
        SoundManager.playSound(R.raw.step);
        savedFrame = frame;
    }
}

The purpose of the savedFrame field is to play the step sound once for the frame. This method will be called in advance and, theoretically, the advance method may be called several times for a single animation frame (depending on animation speed). So, we need to wait until the frame changes before playing the sound again on the next occurrence.

Now that we have this method, we can just call it freely in advance:

@Override
public void advance(float elapsedTime) {
    super.advance(elapsedTime);
    ...
    checkStepSound();
}

We now have a working example of an Artenus project. Although this example does not cover everything in the framework, it is a starting point to help you understand the workflow. For more information, you can see the reference. You may also download the complete code for this tutorial here.

Previous: Creating the First Scene Artenus Framework Reference