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; 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.api.screen.Screen;
import com.bartlomiejpluta.base.internal.object.Placeable; import com.bartlomiejpluta.base.internal.object.Placeable;
import com.bartlomiejpluta.base.internal.render.ShaderManager; 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(float x, float y, float radius);
boolean insideFrustum(Context context, float x, float y);
void render(Screen screen, ShaderManager shaderManager); void render(Screen screen, ShaderManager shaderManager);
} }

View File

@@ -1,5 +1,7 @@
package com.bartlomiejpluta.base.util.world; 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.entity.Entity;
import com.bartlomiejpluta.base.api.event.Event; import com.bartlomiejpluta.base.api.event.Event;
import com.bartlomiejpluta.base.api.event.EventType; 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<Entity> spawnedEntities = new LinkedList<>();
private final List<Supplier<Entity>> spawners = new ArrayList<>(); private final List<Supplier<Entity>> spawners = new ArrayList<>();
private final Vector2ic origin; private final Vector2ic origin;
private final Context context;
private final Camera camera;
private final GameMap map; private final GameMap map;
private final ObjectLayer layer; private final ObjectLayer layer;
private float maxTime = 10000f; private DiceRoller interval = DiceRoller.of("90d2");
private int range = 4; private int range = 4;
private int spawnChance = 50; private float spawnChance = 50;
private DiceRoller countRoller = DiceRoller.of("1d4"); private DiceRoller countRoller = DiceRoller.of("1d4");
private Entity awayEntity;
private EventType<? extends Event> entityRemoveEvent; private EventType<? extends Event> entityRemoveEvent;
private float accumulator = 10000f; private float accumulator = 10000f;
private boolean spawnOutsideViewport = false;
private float threshold; 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.origin = new Vector2i(x, y);
this.context = context;
this.camera = context.getCamera();
this.map = map; this.map = map;
this.layer = layer; this.layer = layer;
drawThreshold(); drawThreshold();
} }
public EntitySpawner maxTime(float maxTime) { public EntitySpawner interval(@NonNull String interval) {
this.maxTime = maxTime; this.interval = DiceRoller.of(interval);
this.accumulator = maxTime;
drawThreshold(); drawThreshold();
return this; return this;
} }
@@ -57,8 +61,8 @@ public class EntitySpawner implements Updatable {
return this; return this;
} }
public EntitySpawner spawnChance(int percent) { public EntitySpawner spawnChance(float change) {
this.spawnChance = percent; this.spawnChance = change;
return this; return this;
} }
@@ -67,13 +71,18 @@ public class EntitySpawner implements Updatable {
return this; return this;
} }
public EntitySpawner entityAway(@NonNull Entity awayEntity) { public EntitySpawner trackEntities(@NonNull EventType<? extends Event> entityRemoveEvent) {
this.awayEntity = awayEntity; this.entityRemoveEvent = entityRemoveEvent;
return this; return this;
} }
public EntitySpawner trackEntities(@NonNull EventType<? extends Event> entityRemoveEvent) { public EntitySpawner spawnOutsideViewport() {
this.entityRemoveEvent = entityRemoveEvent; this.spawnOutsideViewport = true;
return this;
}
public EntitySpawner spawnOnCreate() {
this.threshold = 0;
return this; return this;
} }
@@ -83,15 +92,11 @@ public class EntitySpawner implements Updatable {
} }
private void drawThreshold() { private void drawThreshold() {
threshold = random.nextFloat(maxTime); threshold = interval.roll() * 1000f;
} }
@Override @Override
public void update(float dt) { public void update(float dt) {
if (awayEntity != null && awayEntity.manhattanDistance(origin) <= range) {
return;
}
accumulator += dt * 1000; accumulator += dt * 1000;
if (accumulator >= threshold) { if (accumulator >= threshold) {
spawn(); spawn();
@@ -101,36 +106,61 @@ public class EntitySpawner implements Updatable {
} }
private void spawn() { private void spawn() {
if (random.nextInt(100) < spawnChance) { if (random.nextFloat() > spawnChance) {
return; 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) { for (int i = 0; i < count; ++i) {
var attempts = 0; var attempts = 0;
var coordinates = new Vector2i(); 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.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); 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)) { if (!layer.isTileReachable(coordinates)) {
++attempts; ++attempts;
continue; continue;
} }
if (attempts > MAX_REPOSITION_ATTEMPTS) { // If we want to keep spawning entities outside the camera view
return; // 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 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); entity.setCoordinates(coordinates);
layer.addEntity(entity); 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) { if (entityRemoveEvent != null) {
spawnedEntities.add(entity); spawnedEntities.add(entity);
entity.addEventListener(entityRemoveEvent, e -> spawnedEntities.remove(entity)); entity.addEventListener(entityRemoveEvent, e -> spawnedEntities.remove(entity));

View File

@@ -1,6 +1,7 @@
package com.bartlomiejpluta.base.engine.world.camera; package com.bartlomiejpluta.base.engine.world.camera;
import com.bartlomiejpluta.base.api.camera.Camera; import com.bartlomiejpluta.base.api.camera.Camera;
import com.bartlomiejpluta.base.api.context.Context;
import com.bartlomiejpluta.base.api.screen.Screen; import com.bartlomiejpluta.base.api.screen.Screen;
import com.bartlomiejpluta.base.engine.core.gl.shader.constant.UniformName; import com.bartlomiejpluta.base.engine.core.gl.shader.constant.UniformName;
import com.bartlomiejpluta.base.engine.world.object.Model; import com.bartlomiejpluta.base.engine.world.object.Model;
@@ -25,6 +26,13 @@ public class DefaultCamera extends Model implements Camera {
return frustum.testSphere(x, y, 0.0f, radius); return frustum.testSphere(x, y, 0.0f, radius);
} }
@Override
public boolean insideFrustum(Context context, float x, float y) {
var map = context.getMap();
var stepSize = map.getStepSize();
return insideFrustum(stepSize.x() * x, stepSize.y() * y, stepSize.get(stepSize.maxComponent()));
}
@Override @Override
public void render(Screen screen, ShaderManager shaderManager) { public void render(Screen screen, ShaderManager shaderManager) {
// Update matrices // Update matrices