Site icon Gamadu

APOLLO – Generic Game Entity

Apollo is a generic game entity/component framework written in Java by Arni Arent. It is inspired by dozens of articles on Entity Component frameworks for games found on the internets and in books such as Game Gems. Apollo is a coherent and streamlined thought-out Entity Framework that is meant to ease game development, for those wanting to use the entity/component approach.

Although Apollo has really only been used for 2D games, it is generic in design so you can use for whatever graphics libraries, 2D or 3D. So far it’s been used for Slick and libgdx games. It also has no external dependencies, and can be used as a (tiny) standalone jar in your projects.

JAR, REPOSITORY AND EXAMPLE PROJECT

JAR

Download JAR here: apollo-63da1c012b61.jar (mercurial changeset hash in the name).

Mercurial

You can check out Apollo using mercurial.

Project home: Apollo-Entity-Framework on Google Project.

Example project

You can check out an example project called Apollo-Warrior. It’s a simple game project using Apollo and Slick to demonstrate how to create a game. You can build on that to get started.

Example project: Apollo-Warrior example project on Google Project.

OVERVIEW

The framework provides you with a set of tools and organization to help you build up a much more complex game project.

To understand these tools better you need to familiarize yourself with the following concepts.

World

This is what runs everything in your game. Everything is added here, entities, managers, entitybuilders, and you can also retrieve everything from your world instance. It is the encapsulation for your Apollo instance. You can create new instances quite easily and run multiple worlds in the same game, or simply create a new instance in case of a game restart.

Entity

The Entity class provides you with a way of encapsulating a game entity as a single thing within your game. An Entity is usually something that the player sees on-screen, like a a monster, missiles, terrain, explosions or some HUD interface. It can also be invisible things such as triggers, AI or scripts.

Component

A component is really the heart and brain of an entity, an entity is the sum of its components. An Entity with no components does nothing and is pointless.

Components contain all the logic and data required for that entity to operate within the game world. Components can be independent of other components, but usually you’ll need to communicate with other components in the same entity, or get access to managers and other things, the world instance provides you with an easy access to everything you need.

Spatial

The Spatial is a component that is bundled with the framework. Its purpose is to provide you with a way of rendering entities on screen. A entity can exist without the Spatial component, then it’s simply invisible. But once you add a Spatial component the framework will render the entity.

A Node component is provided as well, in cases where you need to attach multiple spatials to an entity. Node is a subclass of Spatial, so you can attach nodes or multiple spatials to it. The framework will render the spatials as it were an scenegraph.

Node

The Node is a subclass of Spatial. Usually the spatials of entities are comprised of composite things, either for reuse purposes or because of layering.

A case where you’ll need Node because of reuse, is where you might have a entity like a tank that you can click on in a RTS game and then you’ll display some sorts of selection rectangle. You can separate this into a TankSpatial and SelectionSpatial and put together into a TankNode.

Another case is where you’ll need Node because of layering. Imagine that same tank entity with the selection in a top-down game. Now, you might have dozens of tanks in your game but you don’t want the selection spatial to be rendered until all the tank spatials have been rendered. Here you can define a layer called Actors for the tank spatials and layer called Overlay for selection spatial. That way you ensure all selections are above tanks.

Render manager

The bundled RenderManager takes care of rendering all entity spatial. It renders the spatial using the Painter’s algorithm. It’s simple and efficient, each spatial is put into a layering bucket and then the buckets are rendered.

Layering

The most important aspect of rendering spatials is the layering. Spatials are not rendered in the order they are iterated, but according to their layering order. The RenderManager uses the layer defined in each spatial to put it into a bucket. Each spatial must return a Layer.

The framework defines a tag interface called Layer, which is supposed to be implemented by you with an enum containing the layers you want to have in your game. The order of the enum keys is used for rendering order, so all you need to do to re-arrange layers is to change their order in the enum.

Managers

Managers are something that are external to your entities. They are meant to assist you in managing your entities, either they are used for some sort of book-keeping tasks, operate on your entities or act as some sort of agent within your game to do some tasks. There’s no absolute rule what managers are supposed to be, but generally they are something you wouldn’t want to have as an entity.

The framework comes with a set of managers to enable you to manage your entities, like for rendering purposes, tagging, grouping, teaming, etc. You can of course create your own managers and use throughout your game, and that’s what you’re encouraged to do.

EntityBuilder (templates)

To make it simple for you to create new entities you can create entity builders that create new instances of entities of certain type. The EntityBuilder contains all the logic needed to put together an entity and all the components it should have. So all you need to create a new entity is asking for a entity of certain type and you have a new entity, which you can add to the world right away or put in some pool or queue for later uses.

Please note that EntityBuilders are not the only way you can create entities. Other methods exist, such as creating your own factory class with methods with all initialization arguments appropriate for the entity you’re creating, such as position, removing the need to initialize the entities yourself after they’ve been created by an entitybilder. The framework does not discourage you from doing that, EntityBuilder is a simple way of providing a generic way of instancing entities in the framework.

Event handlers

Entities can intercept events fired into them and activate a registered EventHandler instance for the type of event that occurred.

Usually you’ll want to use events where you don’t want hard-dependencies between components. An example would be for the Health component to fire an “KILLED” event into it’s owner entity and then, if you wish, intercept when the entity is killed and add some sorts of blood splatter graphics on the terrain or add to the score of the player.

MINI TUTORIALS

Below are simple examples of how to use the framework, consider it as pseudo-code as it’s not actual code. The entities here or code doesn’t do much other than showing how to use the framework.

Creating an entity

Let’s create an entity by using the Entity and Component classes.

Entity spaceship = new Entity(world);
spaceship.setComponent(new Transform(100,400));
spaceship.setComponent(new SpaceshipSpatial());
world.addEntity(spaceship); // add it to the world

Creating an entity using EntityBuilder

If you don’t like manually creating and putting an entity together everywhere in your game code, simply use EntityBuilders.

// Define the builder
public class SpaceshipBuilder implements EntityBuilder {
  @Override
  public Entity buildEntity(final World world) {
    Entity spaceship = new Entity(world);
    spaceship.setComponent(new Transform());
    spaceship.setComponent(new SpaceshipSpatial());
    return spaceship;
 }
}

// First we need to add the entity builder to the world
world.setEntityBuilder(“Spaceship”, new SpaceshipBuilder());

// Create a new entity from builder
Entity spaceship = world.createEntity(“Spaceship”);
spaceship.getComponent(Transform.class).setLocation(100,400);
world.addEntity(spaceship);

The gameloop

So, how does this all fit into a game loop? Here’s an example showing you how it would typically be set up.

public class MyGame {
  private World world;
  private RenderManager renderManager;
  
  public void init() {
    world = new World();
    renderManager = new RenderManager(myGraphics);
    world.setManager(renderManager);
    world.setEntityBuilder(“Spaceship”, new SpaceshipBuilder());

    Entity spaceship = world.createEntity(“Spaceship”);
    spaceship.getComponent(Transform.class).setLocation(100,400);
    world.addEntity(spaceship);
  }

  public void update(int delta) {
    world.update(delta);
  }

  public void render(Graphics g) {
    renderManager.render(g);
  }
}

Using EntityManager

EntityManager is where all your entities reside in and are organized. The entity manager resides within your world instance.

EntityManager em = world.getEntityManager();
Bag allEntities = em.getEntities();
Bag entitiesHavingTransform = em.getEntitiesByComponentType(Transform.class);

Tip: You can also use @InjectManager for EntityManager inside your components or other managers.

Spatials

If you want your entity to appear on the screen you’ll need to add a spatial to it. Spatials are automatically picked up by the RenderManager. You can use whatever graphics context you provide.

public class SpaceshipSpatial extends Spatial<Graphics> {
  @InjectComponent
  private Transform transform;

  private Image spaceshipImage;

  public SpaceshipSpatial(Image spaceshipImage) {
    this.spaceshipImage = spaceshipImage;
  }

  @Override
  public Layer getLayer() {
    return Layers.Actors; // you must create a enum with your layers.
  }

  @Override
  public void render(Graphics g) {
    g.drawImage(spaceshipImage, transform.getX(), transform.getY());
  }
}

Nodes

Spatials are only useful when doing simple graphics, but when you need to create an entity that is rather complex in look, you’ll need Node.

Node is a subclass of spatial, but allows you to add multiple spatials to itself, thus you can create branches and leafs.

Below is a complex spaceship that has been separated into multiple spatials.

public class SpaceshipNode extends Node<Graphics> {
  private Image spaceshipImage;

  public SpaceshipNode(Image spaceshipImage) {
    this.spaceshipImage = spaceshipImage;
  }

  @Override
  protected void attachChildren() {
    // All child spatials must be attached here.
    addChild(new SpaceshipSpatial(spaceshipImage));
    addChild(new RocketThrustEffectSpatial());
    addChild(new HealthbarIndicatorSpatial());
    addChild(new EnergyRemainingIndicatorSpatial());
    // etc…
    // or even additional nodes!
    addChild(new DebuggingWithLotsOfSpatialsNode());
  }

  @Override
  public Layer getLayer() {
    return Layers.Actors; // this is not inherited by child spatials.
  }

  @Override
  public void render(Graphics g) {
    // You can do rendering here as well.
  }
}

INJECTIONS USING ANNOTATIONS

Thanks to Joakim Johnson (jojoh) I’ve included a way of automagically injecting dependencies without doing much code. This is done at runtime.

There are three types of injections possible: injecting a manager, injecting a component from owner entity and injecting a tagged entity from the world into a component.

Injecting a manager

There are cases you’ll need access to a manager from either a component or another manager. You can simply add a field to your class that will automagically resolve that for you.

public class MyComponent extends Component {
  @InjectManager
  private MyManager myManager;

  @Override
  public void update(int delta) {
    myManager.coolThisWorks();
  }
}

Injecting a component from owner

But in most cases you’ll want to have access to components from the same entity.

public class MyComponent extends Component {
  @InjectComponent
  private Transform transform;

  @Override
  public void update(int delta) {
    System.out.println(“I am located here: ” + transform.getX() + “,” + transform.getY());
  }
}

Injecting a tagged entity from world into a component

If you need to use a tagged entity from the TagManager inside your components, like a main player entity, you can just use @InjectTaggedEntity.

public class MyComponent extends Component {
  @InjectTaggedEntity(“MainPlayerEntity”)
  private Entity playerEntity;

  @Override
  public void update(int delta) {
    System.out.println(“I have player entity: ” + playerEntity);
  }
}

You can inject managers, tagged entities and components inside components, but only managers inside managers.

EVENT HANDLING

The framework allows you to define EventHandlers for entities, so your components can notify the entity of some change and the registered event handler is triggered.

Example

Example of a Health component triggering an event to it’s owner entity, letting it know it got destroyed.

public class Health extends Component {
  private int health;
  @Override
  public void update(int delta) {
    if(health == 0) {
      owner.fireEvent(“KILLED”);
      world.deleteEntity(owner);
    }
  }
}

Entity spaceship = new Entity(world);
spaceship.setComponent(new Transform(100,400));
spaceship.setComponent(new Health());
spaceship.setComponent(new SpaceshipSpatial());
spaceship.addEventHandler(“KILLED”, new EventHandler() {
  @Override
  public void handleEvent() {
    // Create an explosion effect entity here
    // if the spaceship is killed.
  }
});
world.addEntity(spaceship);

This way you can use your Health component for all your entities in the game, regardless if it’s a spaceship or monster, and still have their own unique “KILLED” effect.

COMPONENT FAMILIES

One aspect that is important to understand with components is that every component has a type. By default the framework uses the class type of the concrete component, but you can specify your own type enabling component polymorphism, which gives you what’s called “family” of components.

Example

// By default, getType() returns Health.class.
public class Health extends Component {
  protected float health;
  
  public float getHealth() {
    return health;
  }

  public float addHealth(float health) {
    this.health += health;
  }

  public float setHealth(float health) {
    this.health = health;
  }

  @Override
  public void update(int delta) {
    if(health == 0) {
      owner.fireEvent(“KILLED”);
      world.deleteEntity(owner);
    }
  }
}

// This belongs to the family of “Health”, so we override getType() here.
public class SlowlyHealHealth extends Health {
  @Override
  public Class<? extends Component> getType() {
    return Health.class; // would otherwise be SlowlyHealHealth.class
  }

  @Override
  public void update(int delta) {
    super.update(delta);

    health += delta*0.001f; // heal
  }
}

Now we can simply always ask for Health.class from all entities, not caring for the concrete implementation.

EntityManager em = world.getEntityManager();
Bag healthEntities = em.getEntitiesByComponentType(Health.class);

// Let’s give all entities extra health.
for(int i = 0; healthEntities.size() > i; i++) {
  Entity e = healthEntities.get(i);
  e.getComponent(Health.class).addHealth(10); // We care not of implementation.
}

THE BUNDLED MANAGERS

Apollo comes with a few bundled managers to make your task easier.

RenderManager

We have already covered the RenderManager, but it’s task is to render your spatials using the Painters’ algorithm. You could create your own rendering manager, but for most games the bundled RenderManager suffices.

EntityManager

All the entities in your world are stored here. The EntityManager is the core manager of the framework. Its’ task is simple, just store the entities and allow easy access to them and their components.

TagManager

Whenever you have a entity that’s rather unique to your world you can tag it so you look it up quickly wherever. Most typical entities are “MainPlayer” entity or “Terrain” entity, but you can tag any entity.

GroupManager

If you need to group together similar entities you can use the GroupManager. Typical cases are where you need to group together entities like “bullets” or “spaceships”, then you could make use of the groups in a collision manager that compares the two groups for collision, or if you just want to look up the group “spaceships” to apply a health power-up to them all.

Currently the framework is designed so that you have to implement Group with a enum.

PlayerManager and TeamManager

These two managers allow you to organize your entities by a Player, and then organize your Players into Teams.

Typical case is a multiplayer RTS game, where you can have a 4v4 team game. Entities are then organized by their Player, and each Player is put into one of the Teams.

Provided are Player and Team interfaces, which need to be implemented by enums.

DISCLAIMER

This framework is still under development. If you want to use it beware that it can change a lot, use at your own risk.

LICENSE

This framework uses the New BSD license.

Copyright (c) 2012, GAMADU.COM
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

    Redistributions of source code must retain the above copyright notice, this list of conditions, and the following disclaimer.
    Redistributions in binary form must reproduce the above copyright notice, this list of conditions, and the following disclaimer in the documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS “AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Exit mobile version