Skip to main content
update
Source Link

--

By saying Your systems should be getting components in packs (grouped by entity) from common pool (instead of creating it each for it self) I meant to say there is no composition relation between systems and components. Your systems should not have components directly attached to it.

Here is minimal example of ECS with the position, velocity and 'rendering' components. It is not optimized, lacks encapsulation, manager misses few methods (at the very least deleteEntity), but I hope it captures the spirit in least lines of code possible.

public class Component {
    
    public final static int POS = 1 << 0,
                            VEL = 1 << 1,
                            RENDER = 1 << 2;
    
    public static class Velocity {
        float velx, vely;
    }
    
    public static class Position {
        float x, y;
    }
    
    public static class Rendering {
        String name; // for sake of having at least one field inside.
    }
}

--

public class Systems {
    
    public static class Velocity {
        public void update(EntityManager manager) {
            int required_components = Component.POS | Component.VEL;
            for (int i = 0; i < manager.size; i++) {
                if ((manager.flag[i] & required_components) == required_components){
                    manager.pos[i].x += manager.vel[i].velx;
                    manager.pos[i].y += manager.vel[i].vely;
                }
            }
        }
    }
    
    public static class Render {
        public void update(EntityManager manager) {
            int required_components = Component.POS | Component.RENDER;
            for (int i = 0; i < manager.size; i++) {
                if ((manager.flag[i] & required_components) == required_components) {
                    System.out.println(String.format("%s: (%f x, %f y)", manager.rendering[i].name, manager.pos[i].y, manager.pos[i].y));
                }
            }
        }
    }
    
}

--

public class EntityManager {
    public int flag[];
    public Component.Position pos[];
    public Component.Velocity vel[];
    public Component.Rendering rendering[];
    public final int size;
    
    public EntityManager(int size) {
        this.size = size;
        pos = new Component.Position[size];
        vel = new Component.Velocity[size];
        rendering = new Component.Rendering[size];
        flag = new int[size];
    }
    
    public int createEntity(int flag) {
        for (int i = 0; i < size; i++){
            if (this.flag[i] == 0) {
                this.flag[i] = flag;
                if ((flag & Component.POS) > 0) pos[i] = new Component.Position();
                if ((flag & Component.VEL) > 0) vel[i] = new Component.Velocity();
                if ((flag & Component.RENDER) > 0) rendering[i] = new Component.Rendering();
                return i;
            }
        }
        return -1;
    }
}

--

public class GameContainer {
    
    public static void main(String[] args) {
        new GameContainer();
    }
    
    EntityManager manager;
    Systems.Render renderingSystem;
    Systems.Velocity velocitySystem;
    
    GameContainer() {
        manager = new EntityManager(5);
        renderingSystem = new Systems.Render();
        velocitySystem = new Systems.Velocity();
        
        int id = manager.createEntity(Component.POS | Component.VEL | Component.RENDER);
        if (id > -1) {
            manager.pos[id].x = 10;
            manager.pos[id].y = 10;
            manager.vel[id].velx = 1;
            manager.vel[id].vely = 1;
            manager.rendering[id].name = "player";
        }
        
        id = manager.createEntity(Component.POS | Component.RENDER);
        if (id > -1) {
            manager.pos[id].x = 0;
            manager.pos[id].y = 0;
            manager.rendering[id].name = "tree";
        }
        startGameLoop();
    }
    
    void startGameLoop() {
        while(true) {
            velocitySystem.update(manager);
            renderingSystem.update(manager);
        }
    }
    
}

--

By saying Your systems should be getting components in packs (grouped by entity) from common pool (instead of creating it each for it self) I meant to say there is no composition relation between systems and components. Your systems should not have components directly attached to it.

Here is minimal example of ECS with the position, velocity and 'rendering' components. It is not optimized, lacks encapsulation, manager misses few methods (at the very least deleteEntity), but I hope it captures the spirit in least lines of code possible.

public class Component {
    
    public final static int POS = 1 << 0,
                            VEL = 1 << 1,
                            RENDER = 1 << 2;
    
    public static class Velocity {
        float velx, vely;
    }
    
    public static class Position {
        float x, y;
    }
    
    public static class Rendering {
        String name; // for sake of having at least one field inside.
    }
}

--

public class Systems {
    
    public static class Velocity {
        public void update(EntityManager manager) {
            int required_components = Component.POS | Component.VEL;
            for (int i = 0; i < manager.size; i++) {
                if ((manager.flag[i] & required_components) == required_components){
                    manager.pos[i].x += manager.vel[i].velx;
                    manager.pos[i].y += manager.vel[i].vely;
                }
            }
        }
    }
    
    public static class Render {
        public void update(EntityManager manager) {
            int required_components = Component.POS | Component.RENDER;
            for (int i = 0; i < manager.size; i++) {
                if ((manager.flag[i] & required_components) == required_components) {
                    System.out.println(String.format("%s: (%f x, %f y)", manager.rendering[i].name, manager.pos[i].y, manager.pos[i].y));
                }
            }
        }
    }
    
}

--

public class EntityManager {
    public int flag[];
    public Component.Position pos[];
    public Component.Velocity vel[];
    public Component.Rendering rendering[];
    public final int size;
    
    public EntityManager(int size) {
        this.size = size;
        pos = new Component.Position[size];
        vel = new Component.Velocity[size];
        rendering = new Component.Rendering[size];
        flag = new int[size];
    }
    
    public int createEntity(int flag) {
        for (int i = 0; i < size; i++){
            if (this.flag[i] == 0) {
                this.flag[i] = flag;
                if ((flag & Component.POS) > 0) pos[i] = new Component.Position();
                if ((flag & Component.VEL) > 0) vel[i] = new Component.Velocity();
                if ((flag & Component.RENDER) > 0) rendering[i] = new Component.Rendering();
                return i;
            }
        }
        return -1;
    }
}

--

public class GameContainer {
    
    public static void main(String[] args) {
        new GameContainer();
    }
    
    EntityManager manager;
    Systems.Render renderingSystem;
    Systems.Velocity velocitySystem;
    
    GameContainer() {
        manager = new EntityManager(5);
        renderingSystem = new Systems.Render();
        velocitySystem = new Systems.Velocity();
        
        int id = manager.createEntity(Component.POS | Component.VEL | Component.RENDER);
        if (id > -1) {
            manager.pos[id].x = 10;
            manager.pos[id].y = 10;
            manager.vel[id].velx = 1;
            manager.vel[id].vely = 1;
            manager.rendering[id].name = "player";
        }
        
        id = manager.createEntity(Component.POS | Component.RENDER);
        if (id > -1) {
            manager.pos[id].x = 0;
            manager.pos[id].y = 0;
            manager.rendering[id].name = "tree";
        }
        startGameLoop();
    }
    
    void startGameLoop() {
        while(true) {
            velocitySystem.update(manager);
            renderingSystem.update(manager);
        }
    }
    
}
Source Link

Components should hold information, that is / can be different from one instance to another; in your example hardcoded ExitData values with conjunction of creating new instances on each get call make it little different to just using a global field. Frankly I am not sure if Exit should even be a system as such.

Your systems should be getting components in packs (grouped by entity) from common pool (instead of creating it each for it self), and then depending on the kind of components there are in said pack, do something with them or not.

I don't understand what your entities list is supposed to do, you don't use it. Also you put values by value, but retrieve by index, which smells like a bug.

I think your problem is that you chose a bad testing case. Something more appropriate would be:

  • Make Position (with x and y fields) and Velocity (with xvel and yvel) components.

  • Make Velocity System, which in update loop for entities with Position and Velocity components does x += xvel; y += yvel;

  • Make 'Render' System, which simply prints Position values.

  • Add entities, which all have Position component, optionally have Velocity component, and the starting values of the components is different.

This way you would test against systems interacting on components from same pool, systems choosing to work against component set that matches criteria, grouping components by their entity.