2
\$\begingroup\$

I made some refactorings in my classes. The original code is in this question.

In that question I receive the suggestion to create a Factory for the class BallEntity. I myself forgot that BallEntity is just a kind o 'factory' or 'builder' for a "Ball Entity". Just because Entities in an Entity/Component/System framework is distinguishable only by its components and the creation of these entities is painful and verbose e decide to create this kind of builder class (that is represented by UserEntity and their subclasses).

When I went to use this BallEntity I created I found some problems. Here is a example of use of BallEntity in traditional way, first creating it and then using it in a 'System'.

public class GamePlayScreen{
    public void createBall() {
        Entity ballEntity = new Entity();
        ballEntity.add(BallContextComponent.newInstance());
        /* ...
        ADD others components
        ...
        */

        /* add it to the engine */
        engine.addEntity(ballEntity);

    }
}

public class TeamResetSystem extends EntitySystem {
    private Entity ball;

    @Override
    public void addedToEngine(Engine engine) {
        ball = engine.getEntitiesFor(Family.all(BallContextComponent.class).get()).first();
}

As you see, whenever I want to retrieve the entity I have to remember components that entity have. In this example it's not difficult, but sometimes I have to pass some components to Family.all. And sometimes I add more components. I want I way to centralize the info about which components an entity have. At the end of the code looks like this:

As suggested I created a Factory:

public class BallEntityFactory {

    private final TextureAtlas atlas;
    private final RubeSceneHelper rubeSceneHelper;

    public BallEntityFactory(TextureAtlas atlas, RubeSceneHelper rubeSceneHelper) {
        this.atlas = atlas;
        this.rubeSceneHelper = rubeSceneHelper;
    }

    public BallEntity createBallEntity(String ballImageName, float scaleFactor, Camera camera) {
        Sprite ballSprite = new Sprite(atlas.findRegion(ballImageName));
        Body ballBody = rubeSceneHelper.getBody("ball");
        ScaledSprite scaledSprite = ScaledSprite.createUsingHeight(ballSprite, scaleFactor);
        Fixture fixture = rubeSceneHelper.getFixture(ballBody, "ball");
        return new BallEntity(scaledSprite, ballBody, fixture, camera);
    }
}

I do some refactoring in base class, the 'buildable' argument is because I used BallEntity first to construct the entity and after to retrieve it in the entity system object. When buildable is false, I don't create another entity, since I have interest only in method getDistinctFamily ().

public abstract class UserEntity {

    private final boolean buildable;
    private Entity entity;
    private boolean wasBuilt = false;

    protected UserEntity(boolean buildable) {
        this.buildable = buildable;
        if (buildable) {
            this.entity = new Entity();
        }
    }

    public final Entity getEntity() {
        if (!buildable) {
            throw new IllegalArgumentException("it's not a buildable Entity");
        }
        if (!wasBuilt) {
            final Component[] components = getComponents();
            for (Component c : components) {
                entity.add(c);
            }
            afterConstruct(entity);
        }
        return entity;
    }

    protected abstract Component[] getComponents();

    /* Override when need to init some components */
    public void afterConstruct(Entity entity) {

    }

    public ImmutableArray<Entity> getAllInEngine(Engine engine) {
        return engine.getEntitiesFor(getDistinctFamily());
    }

    protected abstract Family getDistinctFamily();

}



public class BallEntity extends UserEntity {
    private Body ballBody;
    private Camera camera;
    private ScaledSprite ballSprite;
    private Fixture ballFixture;

    /* this constructor is used only to retrieve others BallEntity objects */
    public BallEntity() {
        super(false);
    }

    /* this constructor is used for build a BallEntity*/
    public BallEntity(ScaledSprite ballSprite, Body ballBody, Fixture ballFixture, Camera camera) {
        super(true);
        this.ballSprite = ballSprite;
        this.ballBody = ballBody;
        this.ballFixture = ballFixture;
        this.camera = camera;
    }

    @Override
    public Component[] getComponents() {
        return new Component[]{
                PositionComponent.newInstance(),
                CameraFollowerComponent.newInstance(camera),
                SpriteComponent.newInstance(ballSprite.getSprite()),
                BodyComponent.newInstance(ballBody),
                BallContextComponent.newInstance()};
    }

    @Override
    public void afterConstruct(Entity entity) {
        BodyComponent bodyComponent = entity.getComponent(BodyComponent.class);
        bodyComponent.setPosition(Vector2.Zero);
        ballFixture.setUserData(new FixtureUserData(FixtureType.BALL, entity));
    }

    /**
     * Return the family using the only one component that distinguish this entity from others
     *
     * @return
     */
    @Override
    protected Family getDistinctFamily() {
        return Family.one(BallContextComponent.class).get();
    }

}

GamePlayScreen class:

public void createBall() {
    final BallEntity ballEntity = new BallEntityFactory(atlas, rubeSceneHelper)
            .createBallEntity("ball", 0.78f, camera);
    engine.addEntity(ballEntity.getEntity());
}

TeamResetSystem class:

    @Override
    public void addedToEngine(Engine engine) {
        players = engine.getEntitiesFor(Family.all(PlayerInfoComponent.class, PlayerMatchContextComponent.class).get());
        ball = new BallEntity().getAllInEngine(engine).first();
    }

How to improve this code?

\$\endgroup\$

1 Answer 1

1
+50
\$\begingroup\$

Are you asking how to improve the Entity/Component/System implementation?

For my understanding of Entity/Component/System the Entity should only be a uuid and the engine should map the components of an Entity to that Entity uuid. Mybe the Entity can have some global attributes like "active" or somthing but should not have any other dependencies.

The builder should be closely bounded to the engine so that the engine knows about newly created Entities and can register the Components and the Family behind the scene.


Okay, after a closer look it seems that your UserEntity implementations now are a mix of a type and some kind of post initialisation builder. Sometimes when the empty constructor is used it acts as a type and if the other constructor is used, it acts as a post factory initialisation and wrapper for the Entity. But is this wrapper really needed? Should not do a Factory all the creation work and you need just a EntityType that defines the distinctFamily to specify Entities of the same type? If this is the case, I would recommend to get rid of the UserEntity and to separate this two aspects, type and factory, into own objects/classes. You have already a factory but it seems that it does only the half of the work. You should think about what of the data in your UserEntity is type-specific data and witch data is instance-specific data. The type-specific data should go to a EntityType that can be instantiated per type once and used everywhere. I think the most important data for that type is the distinctFamily and the type of factory that is used to build an instance of that type.

public class EntityType {

     private final Class<? extends EntityFactory> factoryType;
     private final Family distinctFamily;

     public EntityType ( 
         Class<? extends EntityBuilder> factoryType,
         Family distinctFamily
     ) {
          this.factoryType = factoryType;
          this.distinctFamily = distinctFamily;
     }

     public Class<? extends EntityFactory> getFactoryType() {
          return factoryType;
     }

     public Family getDistinctFamily() {
          return distinctFamily;
     }

     public int hashCode() { ... }
     public boolean equals( Object obj ) { ... }

}

If we want to get rid of the UserEntity we have to provide the hirarchie and all the building code that is still in the UserEntity to the Factory. So we create first a abstract factory as an equivalent to the abstract UserEntity. And we should introduce a class that deals with the EntityFactory’s per type and also deals with the dependencies the factories needs do to its work. So we can forget about them, once we have that "provider" class instance properly initialised. We can also bound the abstract factory within the new provider so that the factory is able to link the newly created Entity with the provider and the engine behind the scene.

public class EntityProvider {

    // All needed dependencies to build new entities are here 
    // and get injected (in this case within constructor injection)
    private final Engine engine;
    private final TextureAtlas atlas; 
    private final RubeSceneHelper rubeSceneHelper;

    // And we have some kind of registry for builders of spcific EntityType
    private final Map<EntityType, EntityFactory> entityFactories = new HashMap<EntityType, EntityFactory>();

    public EntityProvider( 
        Engine engine,
        TextureAtlas atlas,
        RubeSceneHelper rubeSceneHelper
    ) {
        this.engine = engine;
        this.atlas = atlas;
        this.rubeSceneHelper = rubeSceneHelper;
    }

    // creates and register a EntityFactory for specific EntityType
    public void registerEntityType( EntityType entityType ) throws Exception {
        if ( builders.containsKey( entityType.getBuilderType() ) {
            return;
        }
        EntityFactory factory = entityType.getBuilderType().newInstance();
        factory.setTextureAtlas( atlas )
        factory.setRubeSceneHelper( rubeSceneHelper );
        entityFactories.put( entityType, factory );
    }

    // gets the EntityFactory implementation for specified EntityType
    public <T extends EntityFactory> T getEntityFactory( EntityType entityType ) {
        Class<?> factoryType = entityType.getFactoryType();
        return factoryType.cast( entityFactories.get( factoryType ) );      
    }

    // Get a single (the first) Entity from the engine with specified EntityType
    public Entity getSingleEntity( EntityType entityType ) {
        return engine.getEntitiesFor(entityType.getDistinctFamily()).first();
    }

    // Get all Entity of a specified EntityType form the engine
    public ImmutableArray<Entity> getAllEntities( EntityType entityType ) {
        return engine.getEntitiesFor(entityType.getDistinctFamily())
    }

    public abstract class EntityFactory {
        // Here are all the dependencies that the EntityFactory needs to create an Entity
        // and this are injected by the EntityFactoryProvider when the EntityFactory is created
        protected TextureAtlas atlas;
        protected RubeSceneHelper rubeSceneHelper;

        protected BallEntityFactory() {}

        void setTextureAtlas( TextureAtlas atlas ) {
            this.atlas = atlas;
        }

        void setRubeSceneHelper( RubeSceneHelper rubeSceneHelper) {
            this.rubeSceneHelper= rubeSceneHelper;
        }

        protected void registerEntity( Entity entity ) {
            engine.add( entity );
        }
    }
}

If you need to map more specific data to the an Entity or EntityType you can do all that in the Provider by introducing a mapping of your need. If you want to reuse old Entity’s instead creating new one’s you can add a map of <EntityType, Stack<Entity>> inactiveEntitiesOfType and in the factory instead of creating a new one you can get a unused one from that map and just update the components and re-add it to the engine.

But next is the BallEntityFactory example implementation

public class BallEntityFactory extends EntityFactory {

    public BallEntityFactory () {
        super();
    }

    // The factory does all the specific creation work at once
    public Entity createEntity( String ballImageName, float scaleFactor, Camera camera ) {
        Sprite ballSprite = new Sprite(atlas.findRegion(ballImageName));
        Body ballBody = rubeSceneHelper.getBody("ball");
        ScaledSprite scaledSprite = ScaledSprite.createUsingHeight(ballSprite, scaleFactor);
        Fixture fixture = rubeSceneHelper.getFixture(ballBody, "ball");

        Entity ballEntity = new Entity();

         ballEntity.add(PositionComponent.newInstance())
         ballEntity.add(CameraFollowerComponent.newInstance(camera))
         ballEntity.add(SpriteComponent.newInstance(ballSprite.getSprite()))
         ballEntity.add(BodyComponent.newInstance(ballBody))
         ballEntity.add(BallContextComponent.newInstance());

         BodyComponent bodyComponent = entity.getComponent(BodyComponent.class);
         bodyComponent.setPosition(Vector2.Zero);
         ballFixture.setUserData(new FixtureUserData(FixtureType.BALL, entity));

         super.registerEntity( entity );
         return entity;
     }
}

Now in the Game you can define your EntityType's once and register it to a registry or even a enum, as you want I just defined it here as constants to make it easy to show it all at once. And you can create and initialise the EntityFactoryProvider also once at the beginning.

public class Game {

    public static final EntityType BALL_ENTITY_TYPE = new EntityType( 
        BallEntityFactory.class,
        Family.one(BallContextComponent.class).get()
    );

    public static final EntityType PLAYER_ENTITY_TYPE = new EntityType( 
        PlayerEntityFactory.class,
        Family.all(PlayerInfoComponent.class, PlayerMatchContextComponent.class).get()
    );

     public static EntityProvider entityProvider;


     public void init() {
         entityProvider = new EntityProvider( ... );
         entityProvider.registerEntityType( BALL_ENTITY_TYPE );
         entityProvider.registerEntityType( PLAYER_ENTITY_TYPE );
     }

     public void createBall() {
         BallEntityFactory factory = entityProvider getEntityFactory( BALL_ENTITY_TYPE );
         Entity ball = factory.createEntity( "ball", 0.78f, camera );
     }


     public void addedToEngine(Engine engine) {
         players = entityProvider.getAllEntities( PLAYER_ENTITY_TYPE );
         ball = entityProvider getSingleEntity( BALL_ENTITY_TYPE );
     }
 }

This are just thought and mybe you can use the one or the other idea and it helps you dealing with your Entities.

\$\endgroup\$
1
  • \$\begingroup\$ @Andreas Thanks for your reply. This code is not part of the implementation of an engine (like your project) but It uses an ECS system to implement a Game. The "BallEntity" I create is an encapsulation of an existing Entity that is part of the engine. I create it to facilitate the instantiation and latter recovery of that entity.. My goal is to improve this code to use in another several similar components I have in my game. \$\endgroup\$
    – alexpfx
    Jun 29, 2015 at 23:15

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.

Not the answer you're looking for? Browse other questions tagged or ask your own question.