Step 1. Add the JitPack repository to your build file
Add it in your root settings.gradle at the end of repositories:
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
}
}
Add it in your settings.gradle.kts at the end of repositories:
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
mavenCentral()
maven { url = uri("https://jitpack.io") }
}
}
Add to pom.xml
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
</repository>
</repositories>
Add it in your build.sbt at the end of resolvers:
resolvers += "jitpack" at "https://jitpack.io"
Add it in your project.clj at the end of repositories:
:repositories [["jitpack" "https://jitpack.io"]]
Step 2. Add the dependency
dependencies {
implementation 'com.github.Darkyenus:retinazer:0.3.0'
}
dependencies {
implementation("com.github.Darkyenus:retinazer:0.3.0")
}
<dependency>
<groupId>com.github.Darkyenus</groupId>
<artifactId>retinazer</artifactId>
<version>0.3.0</version>
</dependency>
libraryDependencies += "com.github.Darkyenus" % "retinazer" % "0.3.0"
:dependencies [[com.github.Darkyenus/retinazer "0.3.0"]]
This is an implementation of the entity-component-system design pattern in Java and a fork of the original implementation by Anton Gustafsson. Since forking, the code in this repository has moved in a slightly different direction than the original, so be sure to check it out as well.
You can obtain a build through JitPack and treat it like any other Java library. The only dependency is the core jar of libGDX for the primitive collections.
All basic building blocks of an ECS are here.
int
- and entity ID
Engine
Component
marker (empty) interface
Component
interface, so component inheritance is not allowedEntitySystem
EntityProcessorSystem
for iterating over all entities which have certain componentsFamilyWatcherSystem
for detecting changes in entity setsEntitySystem
, the EngineService
which is useful for doing non-entity updates in certain parts of Engine
update, or, as name suggests, to provide some service to other systemsAdditionally, there are some concepts specific to this implementation:
ComponentSet
is an immutable set of component types, which you have to create before you start using the Engine
Family
describes a set of entities, based on the component types the entity has or does not haveMapper
provides access to the components and can be obtained from the Engine
EntitySetView
is an immutable set of entities. You can obtain an automatically updated set of entities described by a Family
through the Engine
Engine.wire()
which fills all variables declared with the @Wire
annotation with objects returned by appropriate WireResolver
Mapper
s, EngineService
s and even Engine
into the registered EngineService
sFor more information about the various classes, see the JavaDoc.
The entity management is setup to work in batches, which are triggered by a call to Engine.flush()
.
This happens at the start of Engine.update()
and after each EngineService.update()
called within.
When you mark entity for removal, it won't be removed until the next flush()
. Similarly for component removals.
Entity and component additions are instant, but EntitySetView
membership update happens during the next flush()
.
This is done to prevent errors stemming from using recently removed entities and components,
to make reasoning about change listeners easier and finally to improve performance by batching changes together.
Additionally, entity ID is guaranteed to not be reused during the very next update cycle. In other words, if your update removes an entity, it is guaranteed that an entity with that ID will not exist during the next update. Only during the update after that can a new entity be assigned that ID.
The ID reuse is important, because it allows the IDs to be small, which helps with performance and memory consumption.
/** A simple component given to entities with position */
class Positioned implements Component {
public int x, y;
}
/** Singleton component (tag component) given to entities that should fall. */
class Falling implements Component {
public static final Falling INSTANCE = new Falling();
}
/** A system which moves all entities that should (and can) fall down. */
class GravitySystem extends EntityProcessorSystem {
// This Mapper will be filled in automatically when the system is added to the engine
@Wire
private Mapper<Positioned> positioned;
public GravitySystem() {
super(Main.COMPONENT_DOMAIN.familyWith(Positioned.class, Falling.class));
}
// This is an EntityProcessorSystem, so this method will be called
// once per update per entity which belongs to family specified in constructor
@Override
protected void process(int entity) {
final Positioned positioned = this.positioned.get(entity);
positioned.y -= 1;
}
}
class Main {
// A set of all used components
public static final ComponentSet COMPONENT_DOMAIN = new ComponentSet(Positioned.class, Falling.class);
public static void main() {
// To create an Engine, specify the component domain and all systems/services to be used by the engine.
// The order in which the systems are specified matches the update order.
final Engine engine = new Engine(COMPONENT_DOMAIN, new GravitySystem()/*, ... */);
// Instead of wiring, you can always get the Mappers manually
final Mapper<Positioned> positioned = engine.getMapper(Positioned.class);
final Mapper<Falling> falling = engine.getMapper(Falling.class);
final int fallingEntity = engine.createEntity();
positioned.add(fallingEntity, new Positioned());
falling.add(fallingEntity, Falling.INSTANCE);
final int staticEntity = engine.createEntity();
positioned.add(staticEntity, new Positioned());
for (int i = 0; i < 10; i++) {
engine.update();
}
assertEquals(0, positioned.get(staticEntity).y);
assertEquals(-10, positioned.get(fallingEntity).y);
}
}