Improve entity spawner functionality and fix some spawner bugs

This commit is contained in:
2022-08-19 14:33:40 +02:00
parent 5c97a014f5
commit dccc8fd5ff
3 changed files with 68 additions and 26 deletions

View File

@@ -1,5 +1,7 @@
package com.bartlomiejpluta.base.api.camera;
import com.bartlomiejpluta.base.api.context.Context;
import com.bartlomiejpluta.base.api.move.Movable;
import com.bartlomiejpluta.base.api.screen.Screen;
import com.bartlomiejpluta.base.internal.object.Placeable;
import com.bartlomiejpluta.base.internal.render.ShaderManager;
@@ -10,5 +12,7 @@ public interface Camera extends Placeable {
boolean insideFrustum(float x, float y, float radius);
boolean insideFrustum(Context context, float x, float y);
void render(Screen screen, ShaderManager shaderManager);
}

View File

@@ -1,5 +1,7 @@
package com.bartlomiejpluta.base.util.world;
import com.bartlomiejpluta.base.api.camera.Camera;
import com.bartlomiejpluta.base.api.context.Context;
import com.bartlomiejpluta.base.api.entity.Entity;
import com.bartlomiejpluta.base.api.event.Event;
import com.bartlomiejpluta.base.api.event.EventType;
@@ -26,28 +28,30 @@ public class EntitySpawner implements Updatable {
private final List<Entity> spawnedEntities = new LinkedList<>();
private final List<Supplier<Entity>> spawners = new ArrayList<>();
private final Vector2ic origin;
private final Context context;
private final Camera camera;
private final GameMap map;
private final ObjectLayer layer;
private float maxTime = 10000f;
private DiceRoller interval = DiceRoller.of("90d2");
private int range = 4;
private int spawnChance = 50;
private float spawnChance = 50;
private DiceRoller countRoller = DiceRoller.of("1d4");
private Entity awayEntity;
private EventType<? extends Event> entityRemoveEvent;
private float accumulator = 10000f;
private boolean spawnOutsideViewport = false;
private float threshold;
public EntitySpawner(int x, int y, @NonNull GameMap map, @NonNull ObjectLayer layer) {
public EntitySpawner(int x, int y, @NonNull Context context, @NonNull GameMap map, @NonNull ObjectLayer layer) {
this.origin = new Vector2i(x, y);
this.context = context;
this.camera = context.getCamera();
this.map = map;
this.layer = layer;
drawThreshold();
}
public EntitySpawner maxTime(float maxTime) {
this.maxTime = maxTime;
this.accumulator = maxTime;
public EntitySpawner interval(@NonNull String interval) {
this.interval = DiceRoller.of(interval);
drawThreshold();
return this;
}
@@ -57,8 +61,8 @@ public class EntitySpawner implements Updatable {
return this;
}
public EntitySpawner spawnChance(int percent) {
this.spawnChance = percent;
public EntitySpawner spawnChance(float change) {
this.spawnChance = change;
return this;
}
@@ -67,13 +71,18 @@ public class EntitySpawner implements Updatable {
return this;
}
public EntitySpawner entityAway(@NonNull Entity awayEntity) {
this.awayEntity = awayEntity;
public EntitySpawner trackEntities(@NonNull EventType<? extends Event> entityRemoveEvent) {
this.entityRemoveEvent = entityRemoveEvent;
return this;
}
public EntitySpawner trackEntities(@NonNull EventType<? extends Event> entityRemoveEvent) {
this.entityRemoveEvent = entityRemoveEvent;
public EntitySpawner spawnOutsideViewport() {
this.spawnOutsideViewport = true;
return this;
}
public EntitySpawner spawnOnCreate() {
this.threshold = 0;
return this;
}
@@ -83,15 +92,11 @@ public class EntitySpawner implements Updatable {
}
private void drawThreshold() {
threshold = random.nextFloat(maxTime);
threshold = interval.roll() * 1000f;
}
@Override
public void update(float dt) {
if (awayEntity != null && awayEntity.manhattanDistance(origin) <= range) {
return;
}
accumulator += dt * 1000;
if (accumulator >= threshold) {
spawn();
@@ -101,36 +106,61 @@ public class EntitySpawner implements Updatable {
}
private void spawn() {
if (random.nextInt(100) < spawnChance) {
if (random.nextFloat() > spawnChance) {
return;
}
var count = Math.max(0, countRoller.roll() - spawnedEntities.size());
// Spawn multiple entities at the time
var roll = countRoller.roll();
var count = Math.max(0, roll - spawnedEntities.size());
for (int i = 0; i < count; ++i) {
var attempts = 0;
var coordinates = new Vector2i();
do {
while (true) {
// Give up if too many fails during drawing the proper coordinates
if (attempts > MAX_REPOSITION_ATTEMPTS) {
return;
}
// Draw the coordinates and make sure they are inside the current map boundaries
coordinates.x = MathUtil.clamp(origin.x() + random.nextInt(2 * range) - range, 0, map.getColumns() - 1);
coordinates.y = MathUtil.clamp(origin.y() + random.nextInt(2 * range) - range, 0, map.getRows() - 1);
// If tile is not reachable, draw the coordinates again
if (!layer.isTileReachable(coordinates)) {
++attempts;
continue;
}
if (attempts > MAX_REPOSITION_ATTEMPTS) {
return;
// If we want to keep spawning entities outside the camera view
// check if the drawn coordinates are inside frustum.
// If so, draw it again.
if (spawnOutsideViewport && camera.insideFrustum(context, coordinates.x, coordinates.y)) {
++attempts;
continue;
}
} while (Distance.manhattan(origin, coordinates) <= range);
// We need also to drop the coordinates that are too far from the spawner origin
if (Distance.manhattan(origin, coordinates) > range) {
++attempts;
continue;
}
// We found the proper coordinates
break;
}
// Draw the entity spawner
var spawner = spawners.get(random.nextInt(spawners.size()));
var entity = spawner.get();
// Create the entity and push it onto the map layer
var entity = spawner.get();
entity.setCoordinates(coordinates);
layer.addEntity(entity);
// If we want to keep the number of spawned entities per spawner almost constant
// we need to know when the entity should be removed (i.e. it has been killed by player).
if (entityRemoveEvent != null) {
spawnedEntities.add(entity);
entity.addEventListener(entityRemoveEvent, e -> spawnedEntities.remove(entity));