How to implement a Unity-style GetComponent<TYPE>() function in JavaFX?

Viewed 317

I am trying to make a game with an entity-component-system style. The entity class has a list of different components all extending the Component class. How can I make a function to return the component of a specified class? (Transform, Physics, etc). Example from Unity: Transform tf = player.GetComponent();

I have this function in the entity class:

@SuppressWarnings("unchecked")
public <T> T GetComponent(Class<T> T)
{
    for (Component component : components)
    {
        if (component.getClass() == T)
        {
            System.out.println("Returned a component.");
            return (T)component;
        }
    }
    
    return null;
}

And in the PhysicsComponent I am trying to get a reference to the TransformComponent like this:

public PhysicsComponent(Entity player)
{
    this.tf = new TransformComponent(0, 0, 0, 0);
    this.tf = player.GetComponent(tf.getClass());
}

The getComponent function always returns null because I have to pass in a component as an argument to the player constructor to show what class of component I want the player to have, so I can't reference the player in any of the component constructors, because I would need to make dummy components before the player.

How can I implement this like Unity does, with a simple GetComponent(); function?

1 Answers

There are many ways to do more or less what you're looking for .... I'm not familiar with the "entity-component-system" or Unity, but I built an example that may fit your needs.

It starts with an Component type, I made an empty interface for that, but you can make it an abstract class or whatever fits your needs:

    public interface Component {

    }

The next class I added is a ComponentFactory. An ComponentFactory creates Component-Objects of a fixed Type, given some parameter.

    public interface ComponentFactory<IN, T extends Component> {

        Class<T> getComponentClass();
        T createComponent(IN input);
    }

The next class is Lazy, which simply adds support for Lazy but only-once initialization of anything. I will later use it.

    public static class Lazy<T> {

        private volatile T value;
        private final Supplier<? extends T> supplier;

        public Lazy(Supplier<? extends T> supplier) {
            this.supplier = supplier;
        }

        public T getValue() {
            if (this.value == null) {
                synchronized (this) {
                    if (this.value == null) {
                        this.value = this.supplier.get();
                    }
                }
            }

            return this.value;
        }
    }

The next class is Entity. Entity is one of the classes you seem to already have, but you struggled because an Entity would need it's components passed to the constructor, but the Components would also need the Entity passed in the constructor. The Entity now won't receive the Components passed in its constructor, but factories which it may use to construct actual Components at a later point in time.

    public static class Entity {

        private final Map<Class<? extends Component>, Lazy<Component>> components;

        public Entity(List<ComponentFactory<? super Entity, ?>> componentFactories) {
            // do all of the basic construction

            final Map<Class<? extends Component>, Lazy<Component>> components = new HashMap<>();

            for (ComponentFactory<? super Entity, ?> factory : componentFactories) {
                components.put(factory.getComponentClass(), new Lazy<>(() -> factory.createComponent(this)));
            }

            this.components = Collections.unmodifiableMap(components);
        }

        public <T extends Component> T getComponent(Class<T> componentClass) {
            final Lazy<Component> lazy = this.components.get(componentClass);
            final T component;

            if (lazy != null) {
                component = componentClass.cast(lazy.getValue());
            } else {
                component = null;
            }

            return component;
        }
    }

Example

As an example I created the 2 classes "TransformComponent" which takes an Entity and "PhysicsComponent" which takes an entity and uses that entity to receive the TransformComponent of the given entity.

    public static class PhysicsComponent implements Component {
        
        public static final ComponentFactory<Entity, PhysicsComponent> FACTORY = new ComponentFactory<>() {
            @Override
            public Class<PhysicsComponent> getComponentClass() {
                return PhysicsComponent.class;
            }

            @Override
            public PhysicsComponent createComponent(Entity input) {
                return new PhysicsComponent(input);
            }
        };

        private final TransformComponent tf;

        public PhysicsComponent(Entity player) {
            this.tf = player.getComponent(TransformComponent.class);
        }
    }

    public static class TransformComponent implements Component {

        public static final ComponentFactory<Entity, TransformComponent> FACTORY = new ComponentFactory<>() {
            @Override
            public Class<TransformComponent> getComponentClass() {
                return TransformComponent.class;
            }

            @Override
            public TransformComponent createComponent(Entity input) {
                return new TransformComponent(input);
            }
        };

        public TransformComponent(Entity player) {

        }
    }

Note that both Component-Implementation have an public static final Variable for their own Factory.

And finally, we can create our player Entity:

    public static void example() {
        final Entity player = new Entity(List.of(TransformComponent.FACTORY, PhysicsComponent.FACTORY));
        final PhysicsComponent physicsComponent = player.getComponent(PhysicsComponent.class);
    }
Related