I'm working in a game as a hobbie. I'm using an Entity/Component/System architecture using Libgdx as Engine and Ashley as Entity Framework.
I have a GamePlayScreen in which I initialize my system, creating Entities, Systems and Components. Below is a tiny code snippet of class constructor with the calls to severals 'creational' and 'setup' methods. As example I show below the code of createBall() and createSystems() methods.
public class GamePlayScreen extends BaseScreen {
public GamePlayScreen(SoccerGame game) {
super(game);
engine = new Engine();
setupViewport();
setupInput();
createResourceHelperObjects();
createWorld();
createField();
createTeams();
createBall();
createGoalLines();
createMatch();
createSystems();
}
public void createBall() {
BallFactory.create(atlas, rubeSceneHelper, camera, SCENE_BOUNDS)
.createAndAddToEngine(EmptyObjects.EMPTY_CREATE_ARGUMENTS, engine);
}
public void createSystems() {
engine.addSystem(new TeamCreationSystem(rubeSceneHelper));
engine.addSystem(new TeamResetSystem());
engine.addSystem(new UnprojectInputSystem());
// engine.addSystem(new SelectPlayerByTouchSystem());
engine.addSystem(new InputSystem());
engine.addSystem(new AISystem());
engine.addSystem(new MetersToPixelConvertSystem(PIXEL_TO_METER_FACTOR));
engine.addSystem(new CameraPositionSystem());
engine.addSystem(new RenderSystem(viewport, DEBUG_PHYSICS));
engine.addSystem(new WorldStepSystem());
engine.addSystem(new GameManagmentSystem());
}
}
Entity is a framework class that represent an Object that is composed by components. I have a lot of this entitys, Ball, Player, Team, Input, Match and so forth.
In a first moment I was creating this Entities in GamePlayScreen class. But it start to become huge. So I decide tho create factory classes for my Entities. Below a example for the Ball and Player entities factories.
public class PlayerFactory extends CreateAndAddToEngineEntityFactory {
public static final String PLAYER_NUMBER = "NUMBER";
public static final String PLAYER_POSITION = "PLAYER_POSITION";
public static final String PLAYER_NAME = "PLAYER_NAME";
public static final String UNIFORM = "UNIFORM";
public static final String TEAM = "TEAM";
public static final String INITIAL_POSITION = "INITIAL_POSITION";
private RubeSceneHelper rubeSceneHelper;
private BodyCloner bodyCloner;
private PlayerFactory(RubeSceneHelper rubeSceneHelper) {
this.rubeSceneHelper = rubeSceneHelper;
bodyCloner = BodyCloner.newInstance();
}
public static PlayerFactory newInstance(RubeSceneHelper rubeSceneHelper) {
return new PlayerFactory(rubeSceneHelper);
}
@Override
public Entity create(CreateArguments arguments) {
final ScaledSprite uniform = arguments.get(UNIFORM);
Entity player = new Entity();
final Body bodyModel = rubeSceneHelper.getBody("player");
final Fixture bodyFixture = rubeSceneHelper.getFixture(bodyModel, "player_fixture");
bodyFixture.setUserData(new FixtureUserData(FixtureType.PLAYER, player));
final Body body = bodyCloner.clone(bodyModel);
body.setUserData(EntityUserData.newInstance(player));
Vector2 initialPosition = arguments.get(INITIAL_POSITION);
body.setTransform(initialPosition.x, initialPosition.y, 0);
player.add(BodyComponent.newInstance(body));
final Sprite sprite = new Sprite(uniform.getSprite());
player.add(SpriteComponent.newInstance(sprite));
final Entity team = arguments.get(TEAM);
final PlayerPosition playerPosition = arguments.get(PLAYER_POSITION);
final int number = arguments.get(PLAYER_NUMBER, 0);
player.add(PlayerMatchContextComponent.newInstance(team, playerPosition, number, initialPosition));
player.add(PositionComponent.newInstance());
String playerName = arguments.get(PLAYER_NAME);
player.add(PlayerInfoComponent.newInstance(playerName));
return player;
}
}
public class BallFactory extends CreateAndAddToEngineEntityFactory {
private TextureAtlas atlas;
private RubeSceneHelper rubeSceneHelper;
private Camera camera;
private Rectangle SCENE_BOUNDS;
private BallFactory(TextureAtlas atlas, RubeSceneHelper rubeSceneHelper, Camera camera, Rectangle SCENE_BOUNDS) {
this.atlas = atlas;
this.rubeSceneHelper = rubeSceneHelper;
this.camera = camera;
this.SCENE_BOUNDS = SCENE_BOUNDS;
}
public static BallFactory create(TextureAtlas atlas, RubeSceneHelper rubeSceneHelper, Camera camera, Rectangle SCENE_BOUNDS) {
return new BallFactory(atlas, rubeSceneHelper, camera, SCENE_BOUNDS);
}
@Override
public Entity create(CreateArguments arguments) {
final Entity ballEntity = new Entity();
final Sprite ballSprite = new Sprite(atlas.findRegion("ball"));
final Body ballBody = rubeSceneHelper.getBody("ball");
ballBody.setTransform(0, 0, 0);
final Fixture ball = rubeSceneHelper.getFixture(ballBody, "ball");
ball.setUserData(new FixtureUserData(FixtureType.BALL, ballEntity));
ballEntity.add(SpriteComponent.newInstance(ballSprite));
ballEntity.add(BodyComponent.newInstance(ballBody));
ballEntity.add(BallContextComponent.newInstance());
final PositionComponent positionComponent = PositionComponent.newInstance();
ballEntity.add(positionComponent);
ballEntity.add(CameraFollowerComponent.newInstance(camera));
ballSprite.setScale(0.78f / ballSprite.getHeight());
return ballEntity;
}
}
I also do this way because I need to quickly know which components are part of an entity. Because I recovery the entities later in Systems doing something like:
//get entities that have all those components.
final Entity ballEntity = engine.getEntitiesFor(Family
.all(BallContextComponent.class, SpriteComponent.class, BodyComponent.class, PositionComponent.class, CameraFollowerComponent.class)
.get()).first();
It was was tricky to figure out when I created all entities in one place. So I decided to create Factories for each entity.
But I don't like it to much. So I thought about creating a kind of inheritance for each Entity. I ended up deciding to create a class like this:
public abstract class UserEntity {
private final Entity entity;
private boolean wasBuilt = false;
private Array<Class<? extends Component>> componentClasses;
protected UserEntity() {
this.entity = new Entity();
}
public final Entity getEntity() {
if (!wasBuilt) {
final Array<Component> components = getComponents();
componentClasses = new Array<Class<? extends Component>>();
for (Component c : components) {
entity.add(c);
componentClasses.add(c.getClass());
}
init(entity);
}
return entity;
}
protected abstract Array<Component> getComponents();
public Class<? extends Component>[] getComponentClasses() {
if (!wasBuilt) {
throw new IllegalArgumentException("It needs to build entity before use it.");
}
return componentClasses.toArray();
}
/* Override when need to init some components */
public void init(Entity entity) {
}
}
public class BallEntity extends UserEntity {
private final RubeSceneHelper rubeSceneHelper;
private ScaledSprite ballSprite;
private Body ballBody;
private Camera camera;
public BallEntity(ScaledSprite ballSprite, Body ballBody, Camera camera, RubeSceneHelper rubeSceneHelper) {
this.ballSprite = ballSprite;
this.ballBody = ballBody;
this.camera = camera;
this.rubeSceneHelper = rubeSceneHelper;
}
@Override
public Array<Component> getComponents() {
Array<Component> components = new Array<Component>();
components.add(PositionComponent.newInstance());
components.add(CameraFollowerComponent.newInstance(camera));
components.add(SpriteComponent.newInstance(ballSprite.getSprite()));
components.add(BodyComponent.newInstance(ballBody));
components.add(BallContextComponent.newInstance());
return components;
}
@Override
public void init(Entity entity) {
BodyComponent bodyComponent = entity.getComponent(BodyComponent.class);
bodyComponent.setPosition(Vector2.Zero);
Fixture ballFixture = rubeSceneHelper.getFixture(bodyComponent.getBody(), "ball");
ballFixture.setUserData(new FixtureUserData(FixtureType.BALL, entity));
}
}
but I'm not sure if I'll do it this way. As can be seen I have to pass a lot of data as constructor for the entities. My GamePlayScreen class will end up getting big again. You could evaluate my code or any suggestions for changes or a better way to organize this all?
if anyone is interested in seeing more full code is in github.