Last day we introduced entities. Our implementation for them was extremely general and simple: they had only attach and detach methods. If we want to adapt them to the different scenarios they are meant for, we need another mechanism. In some game engines, this mechanism is called “attachments”. For example, each entity can have a motion attachment, a physics attachment, and so on. In Artenus, we take a different approach, and handle different use cases of entities through behaviors. A behavior is a declaration of a feature or a set of functions an entity supports.
Behavior declaration
Simply put, a behavior in Artenus is an interface that defines some functions for an entity. An entity that has a specific behavior needs to declare it, and also implement the corresponding interface. Later on we use decorator objects to apply different behaviors to existing objects. To begin, we create an enumeration that contains the set of all behaviors we currently have. So, create a
enumeration called Behaviors
under the com.annahid.libs.artenus.entities.behavior
package like this:
package com.annahid.libs.artenus.entities.behavior; public enum Behaviors { ANIMATABLE, TRANSFORMABLE, RENDERABLE, TOUCHABLE }
We already know what animatable, renderable, and transformable entities look like. Sprites for example have all these behaviors. They can be assigned an animation (animatable), they have a visual nature and can be rendered (renderable), and they can be moved, rotated and scaled (transformable). However, they are not touchable. This behavior will be discussed later when we get to user input.
Now that we have a list of behaviors, we need a way for entities to declare them. Each of above behaviors corresponds to an interface implemented by the entity that has them. So, the first thought that comes to mind is to use Java’s instanceof
to determine behaviors. The main issue with this approach concerns portability. What if we need to port this framework to C++ where there is no such mechanism? We aim to minimize the use of instanceof
as it is not considered a good practice in object-oriented programming. Instead, we add another method to the Entity interface to help us declare an entity’s interfaces
/** * Indicates whether this entity has the specified behavior. If it does, it can be cast to the * corresponding interface. * * @param behavior Behavior to be checked * @return {@code true} if this entity has the behavior, {@code false} otherwise */ boolean hasBehavior(Behaviors behavior);
It is then the responsibility of each entity to declare behaviors it has using this method by returning true
for them.
Behavior interfaces
As mentioned in the previous section, each behavior corresponds to an interface in Artenus. In this section we will create these interfaces. Most of these interfaces borrow methods from Sprite.
Animatable
The animatable interface is the easiest to implement. It only needs three methods. The most important method it has is the advance method. An animatable entity needs a chance to update itself every frame. The other two methods are for the assignment and retrieval of an external animation handler. So, we create the following interface in the same package as the Behaviors enumeration.
package com.annahid.libs.artenus.entities.behavior; public interface Animatable { /** * Gets the animation handler currently affecting this animatable. * * @return Animation handler */ AnimationHandler getAnimation(); /** * Assigns an animation handler to handle animations for this animatable. * * @param animation Animation handler, or {@code null} to remove the animation handler */ void setAnimation(AnimationHandler animation); /** * Advances the animation for this animatable. This method is called once per animation frame. * * @param elapsedTime the amount of time since last call to this method */ void advance(float elapsedTime); }
You may notice that Sprite
already has all these methods. So, officially it can be an animatable if it declares the implementation of said behavior using both Java’s implements
keyword and out hasBehavior
method. The same goes for the next two interfaces we are going to implement.
Transformable
Entity transformations supported by this framework are rotation, scaling, and translation (position). There are more complicated affine transformations, but they are beyond the scope of this tutorial. Our Transformable interface needs to implement methods required to perform the three mentioned transformations.
package com.annahid.libs.artenus.entities.behavior; import com.annahid.libs.artenus.types.Point2D; public interface Transformable { /** * Gets the current position of this transformable. * * @return Current 2-dimensional position */ Point2D getPosition(); /** * Sets the position of this transformable. * * @param position The new position */ void setPosition(Point2D position); /** * Sets the position of this transformable. * * @param x The x coordinate of the new position * @param y The y coordinate of the new position */ void setPosition(float x, float y); /** * Moves this transformable the given distance. The translation will be relative to * this transformable's current position. * * @param amountX The horizontal translation * @param amountY The vertical translation */ void move(float amountX, float amountY); /** * Gets the current rotational angle of this transformable. * * @return Rotational angle in degrees */ float getRotation(); /** * Sets the rotational angle of this transformable. * * @param angle Rotational angle in degrees */ void setRotation(float angle); /** * Rotates this transformable the given number of degrees. This rotation will be * relative to this entity's current rotational angle. * * @param angle The angle in degrees to rotate */ void rotate(float angle); /** * Gets the 2-dimensional scaling factor for this transformable. * * @return The scaling factor over horizontal and vertical axes */ Point2D getScale(); /** * Sets the scaling factor for this transformable. Horizontal and vertical scaling * factors will be set to the same value. * * @param scaleValue Scaling factor */ void setScale(float scaleValue); /** * Sets the scaling factor for this transformable, specifying different values * horizontally and vertically. * * @param scaleX Horizontal scaling factor * @param scaleY Vertical scaling factor */ void setScale(float scaleX, float scaleY); }
Renderable
The renderable contains all visual capabilities of an entity. We begin by the simplest form of this interface that only contains a render method:
package com.annahid.libs.artenus.entities.behavior; import javax.microedition.khronos.opengles.GL10; /** * Interface for all entities that support rendering of visual content. */ public interface Renderable { void render(GL10 gl); }
Currently we don’t have a lot of visual functions in our framework (there are quite a few in the Artenus project itself). One thing we could add here is alpha transparency, which we have already defined in the Sprite class:
/** * Sets the transparency value for this renderable. An alpha value of 1 indicates a fully opaque * renderable and a value of 0 is an invisible sprite. Any value in between can be specified to * achieve transparency. * * @param alpha The alpha value for transparency */ void setAlpha(float alpha); /** * Gets the transparency value for this renderable. * * @return The alpha value for transparency */ float getAlpha();
If you have a feeling that this does not belong to this interface, note that alpha transparency is a visual feature, and something required and used in the rendering procedure. As we go on, we may add other methods to this interface.
Touchable
Implementation of touchable can be complicated without prior preparation of helper classes and general knowledge of touch handling on Android. Hence we postpone the implementation of this behavior to when we introduce touch events and input handling.
Next steps
Today we created the what we need to start migrating our sprite-based architecture to the entity world. I have tried to document the code as detailed as possible. If there is anything missing or you have any questions, feel free to ask in the comments, or through email. Next few days we will discuss how our current framework can be translated to the new architecture. As always, you may download today’s code below: