-
Notifications
You must be signed in to change notification settings - Fork 8
The game objects
Important
This page assumes you possess some minimal degree of knowledge of Java modding. If you don't, refer to these articles:
Let's say that you want to create your own UnitTypes in Mindustry with custom logic/behavior. Take a look at Mindustry's UnitTypes, which is where all unit types are defined. You will find something quite interesting:
//mech public static @EntityDef({Unitc.class, Mechc.class}) UnitType mace, dagger, crawler, fortress, scepter, reign, vela; //legs public static @EntityDef({Unitc.class, Legsc.class}) UnitType corvus, atrax, merui, cleroi, anthicus, tecta, collaris;
If you've ever done (H)JSON modding, most likely you've written type: mech and type: legs (more definitions here) to make mech and legs units.
Similarly in JS modding, this is done by setting constructor field to () => MechUnit.create() and () => LegsUnit.create(), or some EntityMapping shenanigans to create custom unit classes.
Notice that, however, none of these are present in Vanilla code's UnitTypes.java source file. There is no setting type nor constructor. If you don't believe me, be my guest and Ctrl+F "constructor" in the code and ponder about the "0 matches" shown in your search bar. Instead, the only things remotely close to the notion of "mech units" and "legs units" is this funny-looking code:
//mech ... @EntityDef({Unitc.class, Mechc.class}) ... //legs ... @EntityDef({Unitc.class, Legsc.class}) ...
This @EntityDef thing is exactly that—it defines the entity components used by the UnitTypes in the game. Yes, these are some big words especially if you've just started Java modding or even programming Java in general; please bear with me. We will walk these through and hopefully by the end of this guide you will have some knowledge of
- general Mindustry code structures;
- how Mindustry's block/unit/etc. type and entity classes work; and
- the general idea of how you can apply this for your mod.
Tip
This section is not exclusive to Java—in fact, there is an entire paradigm often used in game development called Entity Component System, or ECS for short.
Mindustry does not actually do "true" ECS, but it uses a design that is loosely based on it. Hence, many concepts from ECS will be brought over here. If you're an aspiring game developer, give ECS a good read!
Note
Skip this section if you're already knowledgeable about "entity" vs "type" (e.g., Building vs Block, Unit vs UnitType).
First and foremost, we need to clarify what an "entity" even is.
Consider
. You place one near your core, another behind your walls, and maybe another one in the launch zone waiting for imminent instant destruction. Notice that each
has its own "mind;" they can rotate independently with each other, shoot at their own targets, have their own individual health and ammo, and so on.
That is to say, each
that you placed is its own "entity," i.e. a stateful construct that live in the game map. When you place a
, a new Building entity is created to hold the state of the block that you just placed. When you break the
, or when it gets destroyed by enemies, the entity of that block is erased from the game map. This ensures that all
blocks are unique and different from each other...
...or are they really? Notice that, even though they're separate entities with their own states, they're all
! Same maximum health, same ammo kinds, same tile size, so on and so forth. This is what a "type" is; a stateless construct that doesn't directly live in the world of Mindustry, but is often used as a common property for multiple entities.
This is the difference between "entity" and "type," and they're related in the sense that types are common properties shared by entities that otherwise have independent states. Think of types as common immutable blueprints/designs shared by entities of those types.
Some non-exhaustive class examples:
| Type | Entity |
|---|---|
Block |
Building |
ItemTurret |
ItemTurretBuild |
UnitType |
UnitEntity, MechUnit, LegsUnit, etc. |
BasicBulletType, LaserBulletType, etc. |
Bullet |
Weather |
WeatherState |
Effect |
EffectState |
Tip
It is completely okay to not be instantly able to tell the difference between entities and types right away. It will start making sense as you code your mod more and more.
Note
This section marks where things start getting more lengthy and technical. Even though "component" is an integral part of ECS, Mindustry diverges from traditional ECS approach significantly. This article deliberately steers in the direction of how things are done in Mindustry.
Minimal familiarity with Java syntax and the concept of classes is required.
The first half of this section is a prerequisite which is about inheritance; if you're already comfortable with that and are knowledgeable about how type and entity classes are written (at least in Blocks), skip to this section.
Now that we know what entities are, let's inspect them more.
Imagine a simple map, with your
protected by some
s and
s defending against some
s and
s in the distance. By the time the enemy approaches, your turrets and the enemy units shoot bullets at each other. All the buildings, units, and bullets you're witnessing are entities. They have their own individual states and behaviors. However, while they are different individual entities, upon closer inspection we can notice that some entities are quite similar in behavior with each other; particularly,
-
,
, and
are stationary building entities;
-
and
can shoot bullets and be controlled by the player;
-
-
and
are mobile combat entities (albeit they maneuver differently; one walks while the other flies); and - the bullets are ephemeral entities that damage other entities in opposing teams.
If you thought
Well, this just means the buildings share the same type, just like the units and bullets as listed above.
then you would be correct, to some degree. To visualize and walk you through what Mindustry precisely does, let us
In terms of type and entity classes, how would you program the behavior of our hypothetical scenario described earlier? For our purposes, the following code blocks are pseudocode, i.e., they're just imaginary code that we assume "just works" to get the point across.
Firstly, let's do the most basic and naïve approach of implementing entities by
This would look similarly to
//CoreBlock.java
public class CoreBlock{
//some common properties like tile size, max health, max item capacity, etc.
public class CoreEntity{
//some individual properties like tile position, current health, current item amount, etc.
}
}and the same for the turret blocks, i.e. class ItemTurretBlock and class ItemTurretEntity. Same goes for mech units and flying units. Same goes for bullets, and you've most likely seen just how many bullet variants there are.
You can probably imagine how tedious this would get. It's not exactly efficient to write duplicated code for multiple things whose types share some common fields; e.g., all blocks have size and health, all units have weapons, all bullets have damage, so on and so forth. Is this really the best way of doing it?
Thankfully, no! Since we're programming with Java, we refer to the paradigm of Object-Oriented Programming, or OOP for short, and use one core concept you must familiarize yourself with known as
In layperson terms, inheritance allows you to create a sub-class that has all the properties of a super-class. Again, please bear with me. Think of inheritance as an "is a" relationship. For starters, let's think of how some contents can be described using inheritance. An item turret (e.g.
) can be described by listing its properties from the most niche to the most common as following:
-
ItemTurret, a turret block that consumes items to shoot bullets; -
Turret, a block which can target other entities; -
Block, one of the most fundamental content type in the game, i.e. the things you build and destroy in the map; and -
UnlockableContent, common ancestorclassthat is inherited by all contents that are shown in your Core Database.
This means an ItemTurret "is a" Turret, which itself "is a" Block, which itself "is a" UnlockableContent.
Note
Many classes are skipped for simplicity, so you will find a different inheritance chain if you inspect Mindustry's code yourself.
In terms of code, we can describe each item in the list as a class that extends the next item in the list. Let's cross-reference this by looking at a (trimmed) snippet of the ItemTurret source code:
//ItemTurret.java
public class ItemTurret extends Turret{
public ObjectMap<Item, BulletType> ammoTypes = new OrderedMap<>();
//(trimmed) some methods that affect turrets globally
// e.g., how database stats and UI bars are displayed
public class ItemTurretBuild extends TurretBuild{
//(trimmed) some methods that affect turret entities individually
// e.g., how items are accepted by the entity and dispensed as bullets
}
}Notice how ItemTurret is the subclass inheriting the superclass Turret. This means all item turrets share common properties with generic turrets and, by extension, generic blocks. But, not all turrets are item turrets; subclasses like power turrets, liquid turrets, etc. also exist. To further strengthen this visualization, let's inspect some real properties in some of these classes:
| Class | Field |
|---|---|
ItemTurret |
ammoTypes |
Turret |
shoot |
Block |
size, health
|
UnlockableContent |
description, techNode
|
Following the same inheritance chain listed earlier, we can conclude that an ItemTurret is just a specialized form—a subclass—of Turret, and so on for Block and UnlockableContent. An ItemTurret has
- an
ammoTypesfield because it itself defines that field; - a
shootfield because it inherits them fromTurret; -
sizeandhealthfields because it inherits them fromBlock; as well as -
descriptionandtechNodefields because it inherits them fromUnlockableContent.
However, the reverse isn't true:
- an
UnlockableContentisn't necessarily aBlock(it might be anItem, or aLiquid, etc.); - a
Blockisn't necessarily anTurret(it might be some other completely different block like power generators or walls); and - a
Turretisn't necessarily anItemTurret(it might be a power turret, a liquid turret, etc.).
Up until this point, this specific usage of inheritance is, in fact, exactly how every block types and block entities are defined in Mindustry. With this knowledge, you can definitely just go ahead and make any kind of block types and entities (referred to as Buildings) to your liking.
And the same goes for units and bullets, yes?
No, unfortunately not. Even less so for bullets. Remember, this section is titled The "Component," yet we haven't talked about this at all. This gets us to the last step; the actual general approach that Mindustry uses, which is
Inheritance is already covered, i.e., an "is a" relation. Think of composition as a "has a" relation. Mindustry actually uses composition everywhere. The pattern isn't really obvious in block entities, but it is in unit entities. More specifically, every unit entity (and by extension, all entities) actually store their states and behave according to their "components" list.
| Unit type | Unit entity components |
|---|---|
|
|
Has unit component |
|
|
Has unit + mech components |
|
|
Has unit + legs compoents |
As previously stated, entities are stateful constructs, and these states and behaviors are defined by the list of "components" they have. Notice that all entities listed in the table above have unit components; this is simply because unit entities are just entities that have the unit component. Similarly, block entities are just entities that have the building component, and bullet entities are just entities that have the bullet component.
Mindustry does some mixes and matches with a fairly large amount of component definitions, combining many of them to generate entity classes to the game's liking. This brings us back to the preamble:
//mech ... @EntityDef({Unitc.class, Mechc.class}) ... //legs ... @EntityDef({Unitc.class, Legsc.class}) ...
What @EntityDef does is generate these entity classes
//MechUnit.java, generated from @EntityDef({Unitc.class, Mechc.class})
public class MechUnit extends Unit implements Unitc, Mechc, ... { /* ... */ }
//LegsUnit.java, generated from @EntityDef({Unitc.class, Legsc.class})
public class LegsUnit extends Unit implements Unitc, Legsc, ... { /* ... */ }and assign them automatically to the specific UnitType fields that requested to generate the entity classes in the first place—specifically, it sets their constructor field to MechUnit::create and LegsUnit::create respectively.
Note
"Generate" here refers to source files generation. Yes, the MechUnit and LegsUnit (and others) classes are generated via compile-time. You won't find the source for these classes anywhere in Mindustry's GitHub—you will, however, be able to find UnitComp, MechComp, and LegsComp. This will be explained in the next article about using EntityAnno.
This is also where inheritance comes into play. Notice that
-
MechUnithas- the unit component, because it implements
Unitc; - the mech component, because it implements
Mechc; and
- the unit component, because it implements
-
LegsUnithas- the unit component, because it implements
Unitc; - the legs component, because it implements
Legsc.
- the unit component, because it implements
Effectively, this means Mindustry uses a composition model for the entities that is powered with inheritance. Yes, those are also fairly big words, but you don't really need to worry about this.
Tip
The phrase "implements [interface]" is basically the same with "inherits [class]" for our purposes. The only difference is "inherit" is used for classes, while "implement" is used for interfaces.
It is beneficial to understand how Java interfaces work.
Now that we know how entity composition classes work, surely you can go straight to defining your own custom Unit classes...
...not! The issue is the @EntityDef annotation (among others) is not present at all outside of Vanilla's source code. You can't simply drop it in as a dependency either, because everything is hard-coded. Fortunately for you, EntityAnno exists to bridge this gap—it brings all of entity-related annotations from Vanilla into your mod, and more! This Wiki will walk you through all about using EntityAnno in the next page.