Day 25: Sprite as an Entity

By now we have the foundation we need to implement our entity-based framework. Today we are going to go through an example of how we can adapt our previous logic to this system. We begin with sprites not only because they constitute one of the key components of Artenus, but also because they are entities that have most of the behaviors we discussed on day 24. What we do today is then basically defining Sprite as a combination of these behaviors rather than a simple object.

Basics

In order to turn it into an entity, we need first to declare the implementation of the Entity interface. So, open the Sprite class in the Artenus module, and make the following changes:

public abstract class Sprite implements Entity {
    . . .

    @Override
    public void onAttach(Scene scene) {
    }

    @Override
    public void onDetach(Scene scene) {
    }
}

As you can see, Sprite does not have any particular attach or detach logic. However, we implement them both with empty body so subclass won’t need to do that.

Behaviors

The next question we need to ask about Sprite, or any other entity for that matter, is which behaviors it has. We described and implemented different behavior interfaces last day. We now go through them to see which ones apply here.

Transformable

Is sprite transformable? To answer this question, we can have a look at its methods. With a quick scan we can find the following methods:

  • getPosition
  • setPosition
  • getScale
  • setScale
  • getRotation
  • setRotation
  • rotate
  • move

All these methods are meant to transform the sprite. So, in fact, Sprite is transformable. These methods also exactly match those in the Transformable interface. Therefore, we can safely declare the implementation of said interface with no extra effort (perhaps apart from adding the @Override annotation to them).

Renderable

Sprite is definitely renderable, as graphical representation is its main purpose! But does it implement all required methods? We need the following methods:

  • render
  • setAlpha
  • getAlpha

Fortunately (or rather intentionally) Sprite already implements all these methods. So, it fits the definition of a renderable within the framework.

Animatable

Whether it refers to simple animations that only transform sprites, or those like image animations that work on atlas-based image sprites, animation is an essential part of a sprite. Whenever we see the following three methods in a class, it can potentially be an animatable:

  • getAnimation
  • setAnimation
  • advance

Sprite already has all these methods and, by nature, it is animatable. We currently have advanceAnimation instead of advance, which we can refactor in order to unify the framework:

@Override
public final void advance(float elapsedTime) {
    if (anim == null)
        return;
    anim.advance(this, elapsedTime);
}

Touchable

Sprite is not touchable in Artenus. It is only a graphical representation with no input behavior. User input will be described later on in this framework, and it will consist of separate entities which operate on sprites and other entities.

Declaring the behaviors

The next step is to declare behaviors offered by sprite. Last day we discussed the two steps required to do this. First we need to implement the associated interfaces for the three behaviors of Sprite:

public abstract class Sprite implements Entity, Animatable, Renderable, Transformable {
    . . .
}

Then we need to explicitly declare the behaviors we support. We do this by implementing the hasBehavior method:

@Override
public boolean hasBehavior(Behaviors behavior) {
    return behavior == Behaviors.ANIMATABLE
            || behavior == Behaviors.RENDERABLE
            || behavior == Behaviors.TRANSFORMABLE;
}

Note that the only behavior left out here is TOUCHABLE. With the changes we have made, nothing should be changed in any of the sub-classes of the Sprite class, namely ImageSprite, and TextSprite. They will simply be entities in the system by inheritance.

One further step

Today we have not made substantial modifications to the framework so far. In fact, we didn’t implement or change any logic. We merely added some declarations and @Override annotations. But how is this going to empower the framework in any way? On day 23 we outlined some advantages of the entity-based system. In this section we see a little example of how this can make the framework stronger.

Open the AnimationHandler interface definition. Currently, the advance method takes a Sprite object to animate. But is that really necessary? An animation handler may probably not care if the animated object is a Swiss knife a Sprite is. Maybe a mere transformable would be enough for it; or maybe it animates a self-defined property or entity behavior which is not part of the framework at all. But one thing is certain when assigning an animation handler to an entity: it must be animatable! In order to enforce this, we can change the interface to the following:

public interface AnimationHandler {
    void advance(Animatable animatable, float elapsedTime);
}

Obviously we would now need some casting to animate the animatable for its specific behaviors. But we already do this in image animation, even without this change:

((ImageSprite)sprite).gotoFrame(frames[currentFrame]);

The new code for the advance method in ImageAnimation will be like this:

public void advance(Animatable sprite, float elapsedTime) {
    if(System.currentTimeMillis() - lastFrame >= frameDelay)
        lastFrame = System.currentTimeMillis();
    else return;

    if(trend == TREND_LOOP)
        currentFrame = (currentFrame + 1) % frames.length;
    else if(trend == TREND_PINGPONG) {
        currentFrame += delta;

        if(currentFrame == 0 || currentFrame == frames.length - 1)
            delta = -delta;
    }
    else if(currentFrame < frames.length - 1)
        currentFrame++;

    ((ImageSprite)sprite).gotoFrame(frames[currentFrame]);
}

In the code above we have to cast the animatable to an ImageSprite, since we animate a specific property of this class. However, we wouldn't need to do this if we only wanted to translate the animatable; we could just cast it to Transformable, because that is the behavior we want to animate:

public void advance(Animatable animatable, float elapsedTime) {
    ((Transformable)animatable).move(elapsedTime * 2, 0);
}

This makes our code more readable as we know exactly what an animation handler deals with. Obviously we do not know whether the animatable is in fact transformable too (it doesn't have to be). But generally, animation handlers in Artenus are designed to work on specific kinds of entities, and they are allowed to make presumptions. A safer code would look like this:

public void advance(Animatable animatable, float elapsedTime) {
    if (!(animatable instanceof Transformable))
        return;

    ((Transformable)animatable).move(elapsedTime * 2, 0);
}

But that wouldn't be necessary as long as the game developer does not assign a translation or rotation animation to a non-transformable entity. Such a scenario is actually a misuse of the framework, and the correct outcome should be an exception that helps the game developer better organize their code. So, the latter approach is not preferred in our implementations.

We can even more elegantly define a generic version of the animation handler to avoid casting in some scenarios:

public interface AnimationHandler {
    void advance(T animatable, float elapsedTime);
}

However, as always, we would like to keep things simple, and limit implementation to what just works. In conclusion, entities and behaviors help the framework to be less dependent on specific classes and implementations, and enable the logic to focus on general features instead. But this is not the only advantage of this system!

Next steps

Today we applied sprites to the new entity-based system. After this we will go through more complicated structures we can build using this system, and pave the way to completely replace the sprite-based logic in scenes. Below you may download files we modified today.

If there is anything not clear about this article or any other game development day, please leave a comment and send me an email, and I will be more than glad to help.