Feel free to join the next Helmholtz Hacky Hour #26 on Wednesday, April 21, 2021 from 2PM to 3PM!

Commit 1232ed93 authored by Martin Lange's avatar Martin Lange

renamed Heading to Movement, initialize entities without Movement

parent 3272d9c3
Pipeline #12190 passed with stage
in 37 seconds
......@@ -19,7 +19,7 @@ The complete model code is presented along with the descriptions. ECS-specific r
* [References](#references)
* [Appendix](#appendix)
<img title="Screenshot of the model" src="https://git.ufz.de/oesa/ecs-tutorial/uploads/aca134ea4e37bada52effa167172d51a/image.png" alt="Screenshot of the model" width="400">
<img title="Screenshot of the model" src="https://git.ufz.de/oesa/ecs-tutorial/uploads/78a0b08e3e5d4f64383337735f133b43/image.png" alt="Screenshot of the model" width="400">
*Screenshot of the model*
......@@ -62,7 +62,7 @@ The purpose of this model is to demonstrate the use of the ECS approach for indi
**Components**
In this grassing model, the only entities are grassers. All grassers possess the components `Position`, and `Energy`. An optional component `Heading` determines the current activity of each grasser.
In this grassing model, the only entities are grassers. All grassers possess the components `Position`, and `Energy`. An optional component `Movement` determines the current activity of each grasser.
> Components must extend `com.artemis.Component`.
......@@ -84,36 +84,36 @@ public class Position extends com.artemis.Component {
}
```
Component `Heading` contains the angle the entity is heading towards, in radians.
Entities have components `Heading` only when searching, while there is no defined heading when they are grassing. The presence or absence of the component thus defines an entity's behaviour.
Component `Energy` contains the entity's current energy budget.
```java
/// file:src/main/java/grassing/comp/Heading.java
/// file:src/main/java/grassing/comp/Energy.java
package grassing.comp;
public class Heading extends com.artemis.Component {
public float angle;
public class Energy extends com.artemis.Component {
public float value;
public Heading() {}
public Heading(float angle) {
this.angle = angle;
public Energy() {}
public Energy(float value) {
this.value = value;
}
}
```
Component `Energy` contains the entity's current energy budget.
Component `Movement` contains the direction the entity is moving, in radians.
Entities have components `Movement` only when searching, but not when grassing. The presence or absence of the component thus defines an entity's behaviour.
```java
/// file:src/main/java/grassing/comp/Energy.java
/// file:src/main/java/grassing/comp/Movement.java
package grassing.comp;
public class Energy extends com.artemis.Component {
public float value;
public class Movement extends com.artemis.Component {
public float direction;
public Energy() {}
public Energy(float value) {
this.value = value;
public Movement() {}
public Movement(float direction) {
this.direction = direction;
}
}
```
......@@ -222,9 +222,9 @@ Grassers have two different behaviours: grassing and searching. Behaviour change
## Initialization
The model is initialized with `NUM_GRASSERS` grassers. All grassers start with components `Position`, `Heading` and `Energy`.
The model is initialized with `NUM_GRASSERS` grassers. All grassers start with components `Position` and `Energy`. They start with the grassing behaviour, as they initially have no component `Movement`.
Position and heading are initialized randomly. Energy is initialized with 1.0.
Position is initialized randomly. Energy is initialized with 1.0.
```java
/// Create entities
......@@ -233,7 +233,6 @@ private static void createEntities(World world, Random rng) {
int entity = world.create();
world.edit(entity)
.add(new Position(rng.nextFloat() * WORLD_WIDTH, rng.nextFloat() * WORLD_HEIGHT))
.add(new Heading(rng.nextFloat() * 2 * (float) Math.PI))
.add(new Energy(1f));
}
}
......@@ -336,7 +335,7 @@ The grasser reproduction works on all entities that have the components `Positio
In each iteration, each grasser reproduces with a fixed probability.
When reproducing, energy is split in half between the grasser and it's offspring. The offspring starts with the grassing behaviour (i.e. with a `Heading`).
When reproducing, energy is split in half between the grasser and it's offspring. The offspring starts with the grassing behaviour (i.e. without `Movement`).
> We extend `IteratingSystem` again. This time we wrote `@All({Position.class, Energy.class})`, which means that we are interested only in entities that have all listed components.
>
......@@ -377,7 +376,6 @@ public class Reproduction extends IteratingSystem {
int entity = world.create();
world.edit(entity)
.add(new Position(pos.x, pos.y))
.add(new Heading(random.rng.nextFloat() * 2 * (float) Math.PI))
.add(new Energy(e.value));
}
......@@ -387,13 +385,13 @@ public class Reproduction extends IteratingSystem {
### Grassing
The grassing behaviour works on all entities that have the components `Position`, `Energy` but no `Heading`. Thus, it ignores grassers that are currently searching (and thus have a `Heading`).
The grassing behaviour works on all entities that have the components `Position`, `Energy` but no `Movement`. Thus, it ignores grassers that are currently searching (and thus have `Movement`).
If the grasser's energy is 1.0 or above, it does nothing. Otherwise, the grasser decides if there is enough grass in it's grid cell. If this is the case, the grasser consumes grass and converts it to energy. Otherwise, the grasser switches to the searching behaviour.
> The behaviour is switched by adding component `Heading`.
> The behaviour is switched by adding component `Movement`.
>
> We extend `IteratingSystem` again, and use `@All(...)` as before, but with an additional `@Exclude(...)`. This time, we want only entities that have no `Heading`, which is optional according to our model logic. Thus, grassers that are searching instead of grassing will not be processed by this system. In the `else` branch at the end of the code, we add a component `Heading`, which means that the respective entity will not be processed by this system in the next step, but by the `SearchBehaviour`.
> We extend `IteratingSystem` again, and use `@All(...)` as before, but with an additional `@Exclude(...)`. This time, we want only entities that have no `Movement`, which is optional according to our model logic. Thus, grassers that are searching instead of grassing will not be processed by this system. In the `else` branch at the end of the code, we add a component `Movement`, which means that the respective entity will not be processed by this system in the next step, but by the `SearchBehaviour`.
```java
/// file:src/main/java/grassing/sys/GrassingBehaviour.java
......@@ -408,7 +406,7 @@ import grassing.comp.*;
import grassing.res.*;
@All({Position.class, Energy.class})
@Exclude(Heading.class)
@Exclude(Movement.class)
public class GrassingBehaviour extends IteratingSystem {
ComponentMapper<Position> position;
......@@ -439,7 +437,7 @@ public class GrassingBehaviour extends IteratingSystem {
grass.grass.set((int) pos.x, (int) pos.y, g - consumption);
} else {
world.edit(id)
.add(new Heading(random.rng.nextFloat() * 2 * (float) Math.PI));
.add(new Movement(random.rng.nextFloat() * 2 * (float) Math.PI));
}
}
}
......@@ -447,11 +445,11 @@ public class GrassingBehaviour extends IteratingSystem {
### Search
The grassing behaviour system works on all entities that have the components `Position` and `Heading`. Thus, it ignores grassers that are currently grassing (and thus have no `Heading`).
The grassing behaviour system works on all entities that have the components `Position` and `Movement`. Thus, it ignores grassers that are currently grassing (and thus have no `Movement`).
The grasser decides if there is enough grass in it's grid cell. If this is the case, the grasser switches to the searching behaviour. Otherwise, it continues searching by a random walk. When reaching a world borders, the grasser is turned by 180掳.
> The behaviour is switched by removing component `Heading`.
> The behaviour is switched by removing component `Movement`.
```java
/// file:src/main/java/grassing/sys/SearchBehaviour.java
......@@ -466,11 +464,11 @@ import grassing.res.Grass;
import grassing.res.Randomness;
import grassing.util.MathUtil;
@All({Position.class, Heading.class})
@All({Position.class, Movement.class})
public class SearchBehaviour extends IteratingSystem {
ComponentMapper<Position> position;
ComponentMapper<Heading> heading;
ComponentMapper<Movement> movement;
@Wire Grass grass;
@Wire Randomness random;
......@@ -491,7 +489,7 @@ public class SearchBehaviour extends IteratingSystem {
float g = grass.grass.get((int) pos.x, (int) pos.y);
if( g >= minGrass ) {
heading.remove(id);
movement.remove(id);
} else {
randomWalk(id);
}
......@@ -499,18 +497,18 @@ public class SearchBehaviour extends IteratingSystem {
private void randomWalk(int id) {
var pos = position.get(id);
var head = heading.get(id);
var mov = movement.get(id);
head.angle += (float) random.rng.nextGaussian() * maxAngle;
mov.direction += (float) random.rng.nextGaussian() * maxAngle;
var h = MathUtil.heading(head.angle);
var h = MathUtil.heading(mov.direction);
float xNew = pos.x + h.x * speed;
float yNew = pos.y + h.y * speed;
if(grass.grass.contains((int) xNew, (int) yNew)) {
pos.x = xNew;
pos.y = yNew;
} else {
head.angle = (head.angle + (float) Math.PI) % (2 * (float) Math.PI);
mov.direction = (mov.direction + (float) Math.PI) % (2 * (float) Math.PI);
}
}
}
......
......@@ -35,14 +35,14 @@ import java.awt.*;
public class Graphics extends BaseEntitySystem {
protected ComponentMapper<Position> position;
protected ComponentMapper<Heading> heading;
protected ComponentMapper<Movement> movement;
@Wire Grass grass;
private JPanel canvas;
final private int CELL_SIZE = 6;
public Graphics() {
super();
}
......@@ -53,7 +53,7 @@ public class Graphics extends BaseEntitySystem {
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
canvas = new JPanel() {
@Override
@Override
public void paint(java.awt.Graphics g) {
Graphics.this.paint((Graphics2D)g);
}
......@@ -72,7 +72,7 @@ public class Graphics extends BaseEntitySystem {
protected void processSystem() {
canvas.paintImmediately(0, 0, grass.grass.width * CELL_SIZE, grass.grass.height * CELL_SIZE);
}
void paint(Graphics2D g2d) {
FloatGrid grass = this.grass.grass;
int s = CELL_SIZE;
......@@ -92,14 +92,14 @@ public class Graphics extends BaseEntitySystem {
for (int i = 0; i < grassers.size(); i++) {
int entity = grassers.get(i);
var pos = position.get(entity);
var head = heading.get(entity);
if(head == null) {
var mov = movement.get(entity);
if(mov == null) {
g2d.setPaint(Color.WHITE);
g2d.fillRect((int) ((pos.x - 0.3f) * s), (int) ((pos.y - 0.3f) * s), (int) (0.6f * s), (int) (0.6f * s));
} else {
g2d.setPaint(Color.ORANGE);
var f = MathUtil.heading(head.angle);
var r = MathUtil.heading(head.angle + 0.5f * (float) Math.PI);
var f = MathUtil.heading(mov.direction);
var r = MathUtil.heading(mov.direction + 0.5f * (float) Math.PI);
x[0] = (int) ((pos.x + f.x) * s);
y[0] = (int) ((pos.y + f.y) * s);
x[1] = (int) ((pos.x + 0.4f * r.x) * s);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment