Skip to content

Using EntityAnno

Chime Tian edited this page Sep 27, 2026 · 9 revisions

Important

This page assumes you have setup a Java mod with EntityAnno integration using my template, and possess some minimal degree of knowledge of Java modding. If you don't, refer to these articles:

If you so choose to use EntityAnno outside of my template, adjust the information from this Wiki according to your needs. You would most likely already have some degree of knowledge anyway if you managed to get EntityAnno working in your own project.

Preamble

To make this walkthrough more approachable, this Wiki will explain things by giving examples. In particular, the main objective for us today is to create a simple custom unit entity, with legs, that will exit the game once it dies. Yes, it's silly, but it'll certainly be memorable enough to be useful.


Blueprints of Entity Components

Note

This section talks about how entity source files are generated, what the process is, and what the input files and parameters are. Skip to this section if you just want to go ahead and get to coding straight away.

Let's take a quick look at the mindustry.entities.comp package. You'll find that every single class names are suffixed with *Comp. These are our blueprint component classes—they're only used during compile-time, but are effectively gone at runtime. They might as well not exist (and they in fact don't) once the project is bundled into a final .jar file. They do, however, generate interfaces with this naming pattern:

*Comp blueprint class *c component interface
class UnitComp interface Unitc
class MechComp interface Mechc
class LegsComp interface Legsc
class BuildingComp interface Buildingc

The blueprints, which will be gone at runtime, are used to generate component interfaces which do exist at runtime. These are the same interfaces used by the Composition by Inheritance pattern described previously. The interface files only contain body-less functions that exist in the blueprints.

These interfaces are also the ones used by the @EntityDef annotation, which when e.g. used like

//mech
... @EntityDef({Unitc.class, Mechc.class}) ...

will take Unitc and Mechc and generate MechUnit. Specifically, MechUnit is a concrete (non-abstract) class that implements Unitc and Mechc interfaces. However, that is not all; the sources from both MechComp and UnitComp blueprints are merged as well. For example, the void approach(Vec2) method, which in UnitComp is defined as

//UnitComp.java
public void approach(Vec2 vector){
    vel.approachDelta(vector, type.accel * speed());
}

and in MechComp is defined as

//MechComp.java
@Override
public void approach(Vec2 vector){
    //mark walking state when moving in a controlled manner
    if(!vector.isZero(0.001f)){
        walked = true;
    }
}

will be merged in the generated MechUnit source like so

//MechUnit.java, generated
@Override
public void approach(Vec2 vector){
    mech: {
        if(!vector.isZero(0.001f)){
            walked = true;
        }
    }

    unit: {
        vel.approachDelta(vector, type.accel * speed());
    }

    // potentially some more components that we didn't explicitly use, called "component dependencies"
    // more on this later
}

And that's exactly how entities with unit + mech components are defined.

Note

The usage of the @Override annotation isn't critically important, but it does have its uses here (aside from idiomatic Java OOP), which is basically saying that the actual method is defined by some component dependency. More on this in the next section.

Our Own Blueprints

Important

This subsection assumes you use my template, and assumes these placeholder names in the gradle.properties files:

Property Value
modFetch mod.fetched
modGenSrc mod.entities.comp
modGen mod.gen

Referring back to the preamble,

[...] the main objective for us today is to create a simple custom unit entity, with legs, that will exit the game once it dies.

the unit and legs components are already defined in Vanilla, by the Unitc and Legsc interfaces respectively. The exit component is the one that needs to be made from scratch. Do just that—create a file src/mod/entities/comp/ExitComp.java with the contents

package mod.entities.comp;

import arc.*;
import ent.anno.Annotations.*;
import mindustry.gen.*;

@EntityComponent
abstract class ExitComp implements Healthc{
    @Override
    public void killed(){
        Core.app.exit();
    }
}

and try building the project. If everything went smoothly, your project should compile successfully and there should now be a Exitc interface in the package mod.gen. You can confirm this by navigating to the Exitc file through your IDE, or manually by navigating to the build/generated/sources/annotationProcessor/java/main/mod/gen directory.

There are some important things to note, namely

  • the @EntityComponent, which tells EntityAnno that this class is a blueprint and generate Exitc;
  • implements Healthc, which tells EntityAnno that HealthComp is a component dependency; and
  • @Override public void killed(), which adds our own behavior hooked to the killed() method.

The component dependencies part is especially important. This tells the composition pattern that "has exit component" implies "has health component." In inheritance terms, implements Exitc will imply implements Healthc. This is important because oftentimes components only want to add to existing behaviors instead of defining a new behavior; in our case, we want to add a new logic—exiting Mindustry—into an existing "when killed" behavior defined by Healthc.

The @Override annotation makes this distinction clearer, although its meaning changes from the usual "override superclass method" into "merge new logic with existing method from another component."

Note

EntityAnno uses @EntityComponent for blueprints, as opposed to Vanilla's @Component.

Now, let's leverage @EntityDef to generate ExitMechUnit from unit + mech + exit components. You can do this in a lot of ways—the only things that matter are

  • a public static @EntityDef({Unitc.class, Mechc.class, Unitc.class}) UnitType field;
  • setting that field to a new UnitType instance at content-loading phase;
  • calling EntityRegistry.registerUnits().

For example, you can apply these changes (denoted by //new: comments) to your mod class from my template like

package mod;

import ent.anno.Annotations.*; //new: import EntityAnno annotations
import mindutsry.gen.*;        //new: import Unitc, Mechc
import mindustry.mod.*;
import mindustry.type.*;       //new: import UnitType
import mod.gen.*;

public class YourMod extends Mod{
    @EntityDef({Unitc.class, Mechc.class, Exitc.class}) //new: generate ExitMechUnit
    public static UnitType exitingUnit;                 //new: assign to this unit type

    @Override
    public void loadContent(){
        // Call this before loading any content!
        EntityRegistry.register();

        exitingUnit = new UnitType("exiting-unit");     //new: instantiate the unit type

        // Call this *after* loading `UnitType`s!
        EntityRegistry.registerUnits();
    }
}

Important

What EntityRegistry.registerUnits() does in this case is basically equivalent to YourMod.exitingUnit.constructor = ExitMechUnit::create, therefore it is very important to call that method after all units are newly created, otherwise your mod will crash due to null-pointer exceptions.

This is also why there is no constructor-setting code in Vanilla; because the code that does it is generated.

Now, try compiling and installing the mod. My template makes this easy by simply running gradlew install, which will compile the mod and automatically install the mod's .jar file into Mindustry's mods folder. Using a utility mod of your choice, or using the console directly, try spawning the unit and kill it. It should make the game close.

If you've gotten this far, congratulations, you now have all the tools necessary to create custom unit behaviors. However, this is only the surface of what EntityAnno offers to you. The next page is a very technical, in-depth usage guide on every single annotations that EntityAnno defines for you.

Clone this wiki locally