2
\$\begingroup\$

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.

\$\endgroup\$
1
  • \$\begingroup\$ your components have logic. is not that wrong? you've got key listeners and so... \$\endgroup\$
    – eduyayo
    Jun 7, 2020 at 23:03

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.