summaryrefslogtreecommitdiff
path: root/core/src/main/java/coffee/liz/dyl
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/main/java/coffee/liz/dyl')
-rw-r--r--core/src/main/java/coffee/liz/dyl/actions/Action.java4
-rw-r--r--core/src/main/java/coffee/liz/dyl/actions/JumpAction.java4
-rw-r--r--core/src/main/java/coffee/liz/dyl/actions/MoveAction.java14
-rw-r--r--core/src/main/java/coffee/liz/dyl/components/control/ActionQueue.java62
-rw-r--r--core/src/main/java/coffee/liz/dyl/components/control/Controllable.java (renamed from core/src/main/java/coffee/liz/dyl/components/Controllable.java)2
-rw-r--r--core/src/main/java/coffee/liz/dyl/components/control/FacingDirection.java (renamed from core/src/main/java/coffee/liz/dyl/components/FacingDirection.java)9
-rw-r--r--core/src/main/java/coffee/liz/dyl/components/graphic/AnimationState.java (renamed from core/src/main/java/coffee/liz/dyl/components/AnimationState.java)4
-rw-r--r--core/src/main/java/coffee/liz/dyl/components/health/DamageGraceTime.java (renamed from core/src/main/java/coffee/liz/dyl/components/DamageGraceTime.java)2
-rw-r--r--core/src/main/java/coffee/liz/dyl/components/health/RelativeDamageBox.java10
-rw-r--r--core/src/main/java/coffee/liz/dyl/components/physics/CollisionContacts.java20
-rw-r--r--core/src/main/java/coffee/liz/dyl/components/physics/Jump.java5
-rw-r--r--core/src/main/java/coffee/liz/dyl/components/physics/Jumpable.java10
-rw-r--r--core/src/main/java/coffee/liz/dyl/components/physics/Solid.java3
-rw-r--r--core/src/main/java/coffee/liz/dyl/config/PhysicsConstants.java6
-rw-r--r--core/src/main/java/coffee/liz/dyl/entities/PlayerFactory.java19
-rw-r--r--core/src/main/java/coffee/liz/dyl/systems/InputSystem.java79
-rw-r--r--core/src/main/java/coffee/liz/dyl/systems/JumpSystem.java43
-rw-r--r--core/src/main/java/coffee/liz/dyl/systems/collision/SolidCollisionSystem.java58
-rw-r--r--core/src/main/java/coffee/liz/dyl/systems/control/InputSystem.java51
-rw-r--r--core/src/main/java/coffee/liz/dyl/systems/control/JumpActionConsumerSystem.java58
-rw-r--r--core/src/main/java/coffee/liz/dyl/systems/control/MovementActionConsumerSystem.java48
-rw-r--r--core/src/main/java/coffee/liz/dyl/systems/graphics/AnimationSystem.java (renamed from core/src/main/java/coffee/liz/dyl/systems/AnimationSystem.java)9
-rw-r--r--core/src/main/java/coffee/liz/dyl/systems/graphics/RenderSystem.java (renamed from core/src/main/java/coffee/liz/dyl/systems/RenderSystem.java)2
-rw-r--r--core/src/main/java/coffee/liz/dyl/systems/health/DamageSystem.java (renamed from core/src/main/java/coffee/liz/dyl/systems/DamageSystem.java)2
-rw-r--r--core/src/main/java/coffee/liz/dyl/systems/physics/AccelerationSystem.java2
-rw-r--r--core/src/main/java/coffee/liz/dyl/systems/physics/CollisionGrid.java21
-rw-r--r--core/src/main/java/coffee/liz/dyl/systems/physics/CollisionSystem.java85
-rw-r--r--core/src/main/java/coffee/liz/dyl/world/DylGameWorld.java20
28 files changed, 401 insertions, 251 deletions
diff --git a/core/src/main/java/coffee/liz/dyl/actions/Action.java b/core/src/main/java/coffee/liz/dyl/actions/Action.java
new file mode 100644
index 0000000..224cfc6
--- /dev/null
+++ b/core/src/main/java/coffee/liz/dyl/actions/Action.java
@@ -0,0 +1,4 @@
+package coffee.liz.dyl.actions;
+
+public interface Action {
+}
diff --git a/core/src/main/java/coffee/liz/dyl/actions/JumpAction.java b/core/src/main/java/coffee/liz/dyl/actions/JumpAction.java
new file mode 100644
index 0000000..0d8eda5
--- /dev/null
+++ b/core/src/main/java/coffee/liz/dyl/actions/JumpAction.java
@@ -0,0 +1,4 @@
+package coffee.liz.dyl.actions;
+
+public class JumpAction implements Action {
+}
diff --git a/core/src/main/java/coffee/liz/dyl/actions/MoveAction.java b/core/src/main/java/coffee/liz/dyl/actions/MoveAction.java
new file mode 100644
index 0000000..ad56387
--- /dev/null
+++ b/core/src/main/java/coffee/liz/dyl/actions/MoveAction.java
@@ -0,0 +1,14 @@
+package coffee.liz.dyl.actions;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+@Getter
+public class MoveAction implements Action {
+ private final Direction direction;
+
+ public enum Direction {
+ LEFT, RIGHT
+ }
+}
diff --git a/core/src/main/java/coffee/liz/dyl/components/control/ActionQueue.java b/core/src/main/java/coffee/liz/dyl/components/control/ActionQueue.java
new file mode 100644
index 0000000..35b936c
--- /dev/null
+++ b/core/src/main/java/coffee/liz/dyl/components/control/ActionQueue.java
@@ -0,0 +1,62 @@
+package coffee.liz.dyl.components.control;
+
+import coffee.liz.dyl.actions.Action;
+import coffee.liz.ecs.model.Component;
+import jakarta.annotation.Nullable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Predicate;
+
+public class ActionQueue implements Component {
+ private final Map<Class<? extends Action>, ExpiringAction> actionQueue = new HashMap<>();
+
+ public boolean has(final Class<? extends Action> actionType) {
+ return actionQueue.containsKey(actionType);
+ }
+
+ public void queue(final Action action, final float currentTime) {
+ queue(action, currentTime, -1f);
+ }
+
+ public void queue(final Action action, final float currentTime, final float expiresAfterSeconds) {
+ actionQueue.put(action.getClass(), new ExpiringAction(action, currentTime, expiresAfterSeconds));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Nullable
+ public <T extends Action> T consume(final Class<T> actionClass, final float currentTime, final Predicate<T> consumed) {
+ final ExpiringAction expiringAction = actionQueue.get(actionClass);
+ if (expiringAction == null) {
+ return null;
+ }
+ if (expiringAction.isExpired(currentTime)) {
+ actionQueue.remove(actionClass);
+ return null;
+ }
+
+ final Action action = expiringAction.getAction();
+ if (action != null && actionClass.isAssignableFrom(action.getClass()) && consumed.test((T) action)) {
+ actionQueue.remove(actionClass);
+ return (T) action;
+ }
+ return null;
+ }
+
+ @RequiredArgsConstructor
+ @Getter
+ private static class ExpiringAction {
+ private final Action action;
+ private final float queuedAt;
+ private final float expiresAfterSeconds;
+
+ public boolean isExpired(final float currentTime) {
+ if (expiresAfterSeconds < 0f) {
+ return false;
+ }
+ return (currentTime - queuedAt) > expiresAfterSeconds;
+ }
+ }
+}
diff --git a/core/src/main/java/coffee/liz/dyl/components/Controllable.java b/core/src/main/java/coffee/liz/dyl/components/control/Controllable.java
index c476c91..f63a1c2 100644
--- a/core/src/main/java/coffee/liz/dyl/components/Controllable.java
+++ b/core/src/main/java/coffee/liz/dyl/components/control/Controllable.java
@@ -1,4 +1,4 @@
-package coffee.liz.dyl.components;
+package coffee.liz.dyl.components.control;
import coffee.liz.ecs.model.Component;
diff --git a/core/src/main/java/coffee/liz/dyl/components/FacingDirection.java b/core/src/main/java/coffee/liz/dyl/components/control/FacingDirection.java
index d517fc1..280bfb0 100644
--- a/core/src/main/java/coffee/liz/dyl/components/FacingDirection.java
+++ b/core/src/main/java/coffee/liz/dyl/components/control/FacingDirection.java
@@ -1,12 +1,15 @@
-package coffee.liz.dyl.components;
+package coffee.liz.dyl.components.control;
import coffee.liz.ecs.math.Vec2;
import coffee.liz.ecs.model.Component;
+import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
+import lombok.Setter;
-@RequiredArgsConstructor
+@AllArgsConstructor
@Getter
+@Setter
public class FacingDirection implements Component {
- private final Vec2<Float> unitDirection;
+ private Vec2<Float> unitDirection;
}
diff --git a/core/src/main/java/coffee/liz/dyl/components/AnimationState.java b/core/src/main/java/coffee/liz/dyl/components/graphic/AnimationState.java
index 04505c5..ad183da 100644
--- a/core/src/main/java/coffee/liz/dyl/components/AnimationState.java
+++ b/core/src/main/java/coffee/liz/dyl/components/graphic/AnimationState.java
@@ -1,9 +1,7 @@
-package coffee.liz.dyl.components;
+package coffee.liz.dyl.components.graphic;
-import coffee.liz.dyl.components.graphic.AnimationGraphic;
import coffee.liz.ecs.model.Component;
import jakarta.annotation.Nullable;
-import lombok.Builder;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
diff --git a/core/src/main/java/coffee/liz/dyl/components/DamageGraceTime.java b/core/src/main/java/coffee/liz/dyl/components/health/DamageGraceTime.java
index 8424639..ca16f63 100644
--- a/core/src/main/java/coffee/liz/dyl/components/DamageGraceTime.java
+++ b/core/src/main/java/coffee/liz/dyl/components/health/DamageGraceTime.java
@@ -1,4 +1,4 @@
-package coffee.liz.dyl.components;
+package coffee.liz.dyl.components.health;
import coffee.liz.ecs.model.Component;
import lombok.RequiredArgsConstructor;
diff --git a/core/src/main/java/coffee/liz/dyl/components/health/RelativeDamageBox.java b/core/src/main/java/coffee/liz/dyl/components/health/RelativeDamageBox.java
new file mode 100644
index 0000000..e290d4f
--- /dev/null
+++ b/core/src/main/java/coffee/liz/dyl/components/health/RelativeDamageBox.java
@@ -0,0 +1,10 @@
+package coffee.liz.dyl.components.health;
+
+import coffee.liz.ecs.model.Component;
+
+public class RelativeDamageBox implements Component {
+ // TODO: Bounding box relative to sprite bounding box
+ // A damage system will check that the damaging entity collides with the physics bounding box and then that it
+ // also collides with this component, so we don't have to index the grid twice.
+ // private final Vec2<Float> relativeDimensions; // To fill the center
+}
diff --git a/core/src/main/java/coffee/liz/dyl/components/physics/CollisionContacts.java b/core/src/main/java/coffee/liz/dyl/components/physics/CollisionContacts.java
index 49ab7f6..033c3b6 100644
--- a/core/src/main/java/coffee/liz/dyl/components/physics/CollisionContacts.java
+++ b/core/src/main/java/coffee/liz/dyl/components/physics/CollisionContacts.java
@@ -12,16 +12,20 @@ import java.util.List;
@Getter
public class CollisionContacts implements Component {
- @Getter
- @RequiredArgsConstructor
- public class Contact {
- private final Entity contactEntity;
- private final Vec2<Float> penetrationVector;
+ private final List<Contact> contacts = new ArrayList<>();
+
+ public void clear() {
+ contacts.clear();
}
- private final List<Contact> contacts = new ArrayList<>();
+ public void add(final Entity entity, final Vec2<Float> separationVector) {
+ contacts.add(new Contact(entity, separationVector));
+ }
- public void add(final Entity entity, final Vec2<Float> penetrationVector) {
- contacts.add(new Contact(entity, penetrationVector));
+ @Getter
+ @RequiredArgsConstructor
+ public static class Contact {
+ private final Entity contactEntity;
+ private final Vec2<Float> separationVector;
}
}
diff --git a/core/src/main/java/coffee/liz/dyl/components/physics/Jump.java b/core/src/main/java/coffee/liz/dyl/components/physics/Jump.java
index 79d2f78..c791040 100644
--- a/core/src/main/java/coffee/liz/dyl/components/physics/Jump.java
+++ b/core/src/main/java/coffee/liz/dyl/components/physics/Jump.java
@@ -4,15 +4,12 @@ import coffee.liz.ecs.model.Component;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
-import lombok.RequiredArgsConstructor;
import lombok.Setter;
-import java.time.Instant;
-
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
public class Jump implements Component {
- private Instant jumpStart;
+ private float startTime;
}
diff --git a/core/src/main/java/coffee/liz/dyl/components/physics/Jumpable.java b/core/src/main/java/coffee/liz/dyl/components/physics/Jumpable.java
index d35464a..4ce8e74 100644
--- a/core/src/main/java/coffee/liz/dyl/components/physics/Jumpable.java
+++ b/core/src/main/java/coffee/liz/dyl/components/physics/Jumpable.java
@@ -1,16 +1,6 @@
package coffee.liz.dyl.components.physics;
import coffee.liz.ecs.model.Component;
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-import lombok.Setter;
-import java.time.Instant;
-
-@AllArgsConstructor
-@Getter
-@Setter
public class Jumpable implements Component {
- private boolean canPhysicallyJump = false;
- private Instant jumpRequestTime;
}
diff --git a/core/src/main/java/coffee/liz/dyl/components/physics/Solid.java b/core/src/main/java/coffee/liz/dyl/components/physics/Solid.java
index 57af1e3..c9d4a78 100644
--- a/core/src/main/java/coffee/liz/dyl/components/physics/Solid.java
+++ b/core/src/main/java/coffee/liz/dyl/components/physics/Solid.java
@@ -2,4 +2,5 @@ package coffee.liz.dyl.components.physics;
import coffee.liz.ecs.model.Component;
-public class Solid implements Component {}
+public class Solid implements Component {
+}
diff --git a/core/src/main/java/coffee/liz/dyl/config/PhysicsConstants.java b/core/src/main/java/coffee/liz/dyl/config/PhysicsConstants.java
index 3019160..6414629 100644
--- a/core/src/main/java/coffee/liz/dyl/config/PhysicsConstants.java
+++ b/core/src/main/java/coffee/liz/dyl/config/PhysicsConstants.java
@@ -1,14 +1,12 @@
package coffee.liz.dyl.config;
-import java.time.Duration;
-
public final class PhysicsConstants {
public static final float GRAVITY = 40f;
public static final float ADDITIONAL_JUMP_OVER_GRAVITY = 1.05f;
public static final float MOVE_SPEED = 8.0f;
public static final float JUMP_INITIAL_VEL = 10.0f;
- public static final Duration MAX_JUMP = Duration.ofMillis(100);
- public static final Duration JUMP_BUFFER = Duration.ofMillis(400);
+ public static final float MAX_JUMP_SECONDS = 0.1f;
+ public static final float JUMP_BUFFER_SECONDS = 0.2f;
public static final float JUMP_CUT_FACTOR = 0.5f;
public static final float DAMAGE_TIME = 0.25f;
diff --git a/core/src/main/java/coffee/liz/dyl/entities/PlayerFactory.java b/core/src/main/java/coffee/liz/dyl/entities/PlayerFactory.java
index ffd55f6..e5e8855 100644
--- a/core/src/main/java/coffee/liz/dyl/entities/PlayerFactory.java
+++ b/core/src/main/java/coffee/liz/dyl/entities/PlayerFactory.java
@@ -1,17 +1,12 @@
package coffee.liz.dyl.entities;
-import coffee.liz.dyl.components.AnimationState;
-import coffee.liz.dyl.components.Controllable;
-import coffee.liz.dyl.components.FacingDirection;
+import coffee.liz.dyl.components.control.ActionQueue;
+import coffee.liz.dyl.components.graphic.AnimationState;
+import coffee.liz.dyl.components.control.Controllable;
+import coffee.liz.dyl.components.control.FacingDirection;
import coffee.liz.dyl.components.graphic.AnimationGraphic;
-import coffee.liz.dyl.components.physics.Jumpable;
+import coffee.liz.dyl.components.physics.*;
import coffee.liz.dyl.config.PhysicsConstants;
-import coffee.liz.dyl.components.physics.BoundingBox;
-import coffee.liz.dyl.components.physics.Forces;
-import coffee.liz.dyl.components.physics.Gravity;
-import coffee.liz.dyl.components.physics.Jump;
-import coffee.liz.dyl.components.physics.Mass;
-import coffee.liz.dyl.components.physics.Velocity;
import coffee.liz.ecs.math.Vec2;
import coffee.liz.ecs.math.Vec2f;
import coffee.liz.ecs.math.Vec2i;
@@ -41,12 +36,14 @@ public class PlayerFactory {
.add(animationState.getAnimationStates().get(AnimationState.State.IDLE))
.add(new Controllable())
.add(new BoundingBox(new Vec2f(1f, 1f), PlayerAssets.PLAYER_DIMS.floatValue().scale(1/6f, 1/6f)))
+ .add(new CollisionContacts())
+ .add(new ActionQueue())
.add(new Velocity(Vec2f.ZERO))
.add(new Mass(0.8f))
.add(new Forces())
.add(new FacingDirection(Vec2f.ZERO))
.add(new Gravity(-20f))
- .add(new Jumpable(false, null));
+ .add(new Jumpable());
}
private static class PlayerAssets {
diff --git a/core/src/main/java/coffee/liz/dyl/systems/InputSystem.java b/core/src/main/java/coffee/liz/dyl/systems/InputSystem.java
deleted file mode 100644
index ea0e574..0000000
--- a/core/src/main/java/coffee/liz/dyl/systems/InputSystem.java
+++ /dev/null
@@ -1,79 +0,0 @@
-package coffee.liz.dyl.systems;
-
-import coffee.liz.dyl.components.Controllable;
-import coffee.liz.dyl.components.FacingDirection;
-import coffee.liz.dyl.components.physics.Jumpable;
-import coffee.liz.dyl.config.KeyBinds;
-import coffee.liz.dyl.components.physics.Jump;
-import coffee.liz.dyl.components.physics.Velocity;
-import coffee.liz.ecs.math.Vec2f;
-import coffee.liz.ecs.model.System;
-import coffee.liz.ecs.model.World;
-import lombok.RequiredArgsConstructor;
-
-import java.time.Duration;
-import java.time.Instant;
-import java.util.Collection;
-import java.util.Set;
-import java.util.function.Supplier;
-
-import static coffee.liz.dyl.config.PhysicsConstants.*;
-
-@RequiredArgsConstructor
-public class InputSystem implements System {
- private final Supplier<Set<KeyBinds.Action>> activeActions;
-
- @Override
- public Collection<Class<? extends System>> getDependencies() {
- return Set.of();
- }
-
- @Override
- public void update(final World world, final float deltaSeconds) {
- final Set<KeyBinds.Action> actions = activeActions.get();
-
- world.queryable().allOf(Controllable.class, Velocity.class).forEach(entity -> {
- final Velocity velocity = entity.get(Velocity.class);
-
- float dx = 0f;
- if (actions.contains(KeyBinds.Action.MOVE_LEFT)) dx -= MOVE_SPEED;
- if (actions.contains(KeyBinds.Action.MOVE_RIGHT)) dx += MOVE_SPEED;
- if (dx != 0) {
- entity.add(new FacingDirection(new Vec2f(dx / MOVE_SPEED, 0f)));
- }
-
- final Instant now = Instant.now();
- final Jumpable jumpable = entity.get(Jumpable.class);
- final boolean canRequestJump = jumpable.getJumpRequestTime() == null;
- final boolean jumpRequested = actions.contains(KeyBinds.Action.JUMP);
- final boolean hasCurrentJump = entity.has(Jump.class);
-
- float dy = velocity.getVelocity().getY();
-
- // buffer only fires on a released press — holding never re-triggers
- final boolean withinBuffer = !canRequestJump && !jumpRequested
- && Duration.between(jumpable.getJumpRequestTime(), now).compareTo(JUMP_BUFFER) <= 0;
- if (jumpable.isCanPhysicallyJump() && !hasCurrentJump && (canRequestJump && jumpRequested || withinBuffer)) {
- entity.add(new Jump(now));
- dy = JUMP_INITIAL_VEL;
- jumpable.setJumpRequestTime(now); // mark consumed; keeps canRequestJump false while held
- } else if (!jumpRequested && hasCurrentJump && dy > 0) {
- dy *= JUMP_CUT_FACTOR;
- }
-
- // record an aerial press for buffering (ground presses are recorded above on initiation)
- if (!jumpable.isCanPhysicallyJump() && canRequestJump && jumpRequested && !entity.has(Jump.class)) {
- jumpable.setJumpRequestTime(now);
- } else if (!jumpRequested && !entity.has(Jump.class)) {
- if (jumpable.isCanPhysicallyJump()) {
- jumpable.setJumpRequestTime(null); // released on ground — ready for next press
- } else if (!canRequestJump
- && Duration.between(jumpable.getJumpRequestTime(), now).compareTo(JUMP_BUFFER) > 0) {
- jumpable.setJumpRequestTime(null); // aerial, buffer expired
- }
- }
-
- velocity.setVelocity(new Vec2f(dx, dy));
- });
- }
-}
diff --git a/core/src/main/java/coffee/liz/dyl/systems/JumpSystem.java b/core/src/main/java/coffee/liz/dyl/systems/JumpSystem.java
deleted file mode 100644
index d1552f5..0000000
--- a/core/src/main/java/coffee/liz/dyl/systems/JumpSystem.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package coffee.liz.dyl.systems;
-
-import coffee.liz.dyl.components.physics.CollisionContacts;
-import coffee.liz.dyl.components.physics.Jump;
-import coffee.liz.dyl.components.physics.Jumpable;
-import coffee.liz.dyl.components.physics.Velocity;
-import coffee.liz.dyl.systems.physics.CollisionSystem;
-import coffee.liz.ecs.model.System;
-import coffee.liz.ecs.model.World;
-
-import java.util.Collection;
-import java.util.List;
-
-public class JumpSystem implements System {
- @Override
- public Collection<Class<? extends System>> getDependencies() {
- return List.of(CollisionSystem.class);
- }
-
- @Override
- public void update(final World world, final float deltaSeconds) {
- world.queryable().allOf(Jumpable.class).forEach(entity -> {
- entity.get(Jumpable.class).setCanPhysicallyJump(false);
- });
- world.queryable().allOf(Jumpable.class, CollisionContacts.class).forEach(entity -> {
- final CollisionContacts contacts = entity.get(CollisionContacts.class);
- final Jumpable jumpable = entity.get(Jumpable.class);
- contacts.getContacts().forEach(contact -> {
- final boolean solidUnderUs = contact.getPenetrationVector().getY() <= 0;
- if (!solidUnderUs) {
- return;
- }
- jumpable.setCanPhysicallyJump(true);
-
- final boolean currentJumpOver = entity.has(Jump.class) && entity.has(Velocity.class)
- && entity.get(Velocity.class).getVelocity().getY() <= 0;
- if (currentJumpOver) {
- entity.remove(Jump.class);
- }
- });
- });
- }
-}
diff --git a/core/src/main/java/coffee/liz/dyl/systems/collision/SolidCollisionSystem.java b/core/src/main/java/coffee/liz/dyl/systems/collision/SolidCollisionSystem.java
new file mode 100644
index 0000000..6f3697f
--- /dev/null
+++ b/core/src/main/java/coffee/liz/dyl/systems/collision/SolidCollisionSystem.java
@@ -0,0 +1,58 @@
+package coffee.liz.dyl.systems.collision;
+
+import coffee.liz.dyl.components.physics.BoundingBox;
+import coffee.liz.dyl.components.physics.CollisionContacts;
+import coffee.liz.dyl.components.physics.Solid;
+import coffee.liz.dyl.components.physics.Velocity;
+import coffee.liz.dyl.systems.physics.CollisionSystem;
+import coffee.liz.ecs.math.Vec2;
+import coffee.liz.ecs.math.Vec2f;
+import coffee.liz.ecs.model.Entity;
+import coffee.liz.ecs.model.System;
+import coffee.liz.ecs.model.World;
+
+import java.util.Collection;
+import java.util.List;
+
+public class SolidCollisionSystem implements System {
+ @Override
+ public Collection<Class<? extends System>> getDependencies() {
+ return List.of(CollisionSystem.class);
+ }
+
+ @Override
+ public void update(final World world, final float deltaSeconds) {
+ world.queryable().allOf(CollisionContacts.class, BoundingBox.class, Velocity.class).forEach(me -> {
+ final CollisionContacts contacts = me.get(CollisionContacts.class);
+ contacts.getContacts().forEach(contact -> {
+ final Entity them = contact.getContactEntity();
+ if (!them.has(Solid.class)) {
+ return;
+ }
+
+ applyNormalImpulse(me, contact);
+ });
+ });
+ }
+
+ private void applyNormalImpulse(final Entity me, final CollisionContacts.Contact contact) {
+ final float sLen = contact.getSeparationVector().length();
+ if (sLen < 0.0001f) {
+ return; // Just touching, no separation needed
+ }
+
+ final BoundingBox meBox = me.get(BoundingBox.class);
+ meBox.setPosition(meBox.getPosition().plus(contact.getSeparationVector()));
+ final float nx = contact.getSeparationVector().getX() / sLen;
+ final float ny = contact.getSeparationVector().getY() / sLen;
+
+ final Vec2<Float> vel = me.get(Velocity.class).getVelocity();
+ final float velIntoWall = -(vel.getX() * nx + vel.getY() * ny);
+ if (velIntoWall > 0f) {
+ me.get(Velocity.class).setVelocity(new Vec2f(
+ vel.getX() + nx * velIntoWall,
+ vel.getY() + ny * velIntoWall
+ ));
+ }
+ }
+}
diff --git a/core/src/main/java/coffee/liz/dyl/systems/control/InputSystem.java b/core/src/main/java/coffee/liz/dyl/systems/control/InputSystem.java
new file mode 100644
index 0000000..ca114ee
--- /dev/null
+++ b/core/src/main/java/coffee/liz/dyl/systems/control/InputSystem.java
@@ -0,0 +1,51 @@
+package coffee.liz.dyl.systems.control;
+
+import coffee.liz.dyl.actions.JumpAction;
+import coffee.liz.dyl.actions.MoveAction;
+import coffee.liz.dyl.components.control.ActionQueue;
+import coffee.liz.dyl.components.control.Controllable;
+import coffee.liz.dyl.components.physics.Jumpable;
+import coffee.liz.dyl.config.KeyBinds;
+import coffee.liz.ecs.model.System;
+import coffee.liz.ecs.model.World;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Collection;
+import java.util.Set;
+import java.util.function.Supplier;
+
+import static coffee.liz.dyl.config.PhysicsConstants.*;
+
+@RequiredArgsConstructor
+public class InputSystem implements System {
+ private final Supplier<Set<KeyBinds.Action>> heldActions;
+ private final Supplier<Set<KeyBinds.Action>> justPressedActions;
+
+ @Override
+ public Collection<Class<? extends System>> getDependencies() {
+ return Set.of();
+ }
+
+ @Override
+ public void update(final World world, final float deltaSeconds) {
+ final float time = world.getTime();
+ final Set<KeyBinds.Action> held = heldActions.get();
+ final Set<KeyBinds.Action> justPressed = justPressedActions.get();
+
+ world.queryable().allOf(Controllable.class, ActionQueue.class).forEach(entity -> {
+ final ActionQueue actionQueue = entity.get(ActionQueue.class);
+
+ final boolean moveLeft = held.contains(KeyBinds.Action.MOVE_LEFT);
+ final boolean moveRight = held.contains(KeyBinds.Action.MOVE_RIGHT);
+ if (moveLeft != moveRight) {
+ final MoveAction action = moveLeft ? new MoveAction(MoveAction.Direction.LEFT)
+ : new MoveAction(MoveAction.Direction.RIGHT);
+ actionQueue.queue(action, time);
+ }
+
+ if (entity.has(Jumpable.class) && justPressed.contains(KeyBinds.Action.JUMP)) {
+ actionQueue.queue(new JumpAction(), time, JUMP_BUFFER_SECONDS);
+ }
+ });
+ }
+}
diff --git a/core/src/main/java/coffee/liz/dyl/systems/control/JumpActionConsumerSystem.java b/core/src/main/java/coffee/liz/dyl/systems/control/JumpActionConsumerSystem.java
new file mode 100644
index 0000000..eadc402
--- /dev/null
+++ b/core/src/main/java/coffee/liz/dyl/systems/control/JumpActionConsumerSystem.java
@@ -0,0 +1,58 @@
+package coffee.liz.dyl.systems.control;
+
+import coffee.liz.dyl.actions.JumpAction;
+import coffee.liz.dyl.components.control.ActionQueue;
+import coffee.liz.dyl.components.physics.*;
+import coffee.liz.dyl.config.PhysicsConstants;
+import coffee.liz.dyl.systems.collision.SolidCollisionSystem;
+import coffee.liz.ecs.model.System;
+import coffee.liz.ecs.model.World;
+
+import java.util.Collection;
+import java.util.List;
+
+public class JumpActionConsumerSystem implements System {
+ @Override
+ public Collection<Class<? extends System>> getDependencies() {
+ return List.of(InputSystem.class, SolidCollisionSystem.class);
+ }
+
+ @Override
+ public void update(final World world, final float deltaSeconds) {
+ final float time = world.getTime();
+
+ world.queryable().allOf(Jumpable.class, Velocity.class, CollisionContacts.class, ActionQueue.class).forEach(entity -> {
+ final ActionQueue queue = entity.get(ActionQueue.class);
+ final Velocity velocity = entity.get(Velocity.class);
+ final CollisionContacts contacts = entity.get(CollisionContacts.class);
+ final boolean solidOverUs = contacts.getContacts().stream()
+ .anyMatch(contact -> contact.getSeparationVector().getY() < 0 && contact.getContactEntity().has(Solid.class));
+ final boolean solidUnderUs = contacts.getContacts().stream()
+ .anyMatch(contact -> contact.getSeparationVector().getY() > 0 && contact.getContactEntity().has(Solid.class));
+ final boolean hasCurrentJump = entity.has(Jump.class);
+
+ final boolean bonkedIntoCeiling = solidOverUs && hasCurrentJump;
+ final boolean justHitFloor = solidUnderUs && hasCurrentJump;
+ final boolean notJumping = !hasCurrentJump || bonkedIntoCeiling || justHitFloor;
+ if (notJumping) {
+ entity.remove(Jump.class);
+ }
+
+ queue.consume(JumpAction.class, time, _action -> {
+ if (notJumping && solidUnderUs) {
+ entity.add(new Jump(time));
+ }
+ if (entity.has(Jump.class)) {
+ final Jump jump = entity.get(Jump.class);
+ final boolean inTimeWindowToApplyJump = (time - jump.getStartTime()) <= PhysicsConstants.MAX_JUMP_SECONDS;
+ if (inTimeWindowToApplyJump) {
+ velocity.setVelocity(velocity.getVelocity()
+ .transform(x -> x, y -> PhysicsConstants.JUMP_INITIAL_VEL));
+ return true;
+ }
+ }
+ return false;
+ });
+ });
+ }
+}
diff --git a/core/src/main/java/coffee/liz/dyl/systems/control/MovementActionConsumerSystem.java b/core/src/main/java/coffee/liz/dyl/systems/control/MovementActionConsumerSystem.java
new file mode 100644
index 0000000..9791cfb
--- /dev/null
+++ b/core/src/main/java/coffee/liz/dyl/systems/control/MovementActionConsumerSystem.java
@@ -0,0 +1,48 @@
+package coffee.liz.dyl.systems.control;
+
+import coffee.liz.dyl.actions.MoveAction;
+import coffee.liz.dyl.components.control.ActionQueue;
+import coffee.liz.dyl.components.control.FacingDirection;
+import coffee.liz.dyl.components.physics.Velocity;
+import coffee.liz.dyl.config.PhysicsConstants;
+import coffee.liz.ecs.math.Vec2f;
+import coffee.liz.ecs.model.Entity;
+import coffee.liz.ecs.model.System;
+import coffee.liz.ecs.model.World;
+
+import java.util.Collection;
+import java.util.List;
+
+public class MovementActionConsumerSystem implements System {
+ @Override
+ public Collection<Class<? extends System>> getDependencies() {
+ return List.of(InputSystem.class);
+ }
+
+ @Override
+ public void update(final World world, final float deltaSeconds) {
+ final float time = world.getTime();
+
+ world.queryable().allOf(ActionQueue.class, Velocity.class, FacingDirection.class).forEach(entity -> {
+ final ActionQueue actionQueue = entity.get(ActionQueue.class);
+ if (!actionQueue.has(MoveAction.class)) {
+ final Velocity velocity = entity.get(Velocity.class);
+ velocity.setVelocity(velocity.getVelocity().transform(_x -> 0f, y -> y));
+ return;
+ }
+ actionQueue.consume(MoveAction.class, time, (a) -> consumeAction(entity, a));
+ });
+ }
+
+ private boolean consumeAction(final Entity entity, final MoveAction action) {
+ final Velocity velocity = entity.get(Velocity.class);
+ final FacingDirection facingDirection = entity.get(FacingDirection.class);
+ final float dx = switch(action.getDirection()) {
+ case LEFT -> -PhysicsConstants.MOVE_SPEED;
+ case RIGHT -> PhysicsConstants.MOVE_SPEED;
+ };
+ facingDirection.setUnitDirection(new Vec2f(dx / PhysicsConstants.MOVE_SPEED, 0f));
+ velocity.setVelocity(velocity.getVelocity().transform(_x -> dx, y -> y));
+ return true;
+ }
+}
diff --git a/core/src/main/java/coffee/liz/dyl/systems/AnimationSystem.java b/core/src/main/java/coffee/liz/dyl/systems/graphics/AnimationSystem.java
index a932eea..a1e4874 100644
--- a/core/src/main/java/coffee/liz/dyl/systems/AnimationSystem.java
+++ b/core/src/main/java/coffee/liz/dyl/systems/graphics/AnimationSystem.java
@@ -1,11 +1,12 @@
-package coffee.liz.dyl.systems;
+package coffee.liz.dyl.systems.graphics;
-import coffee.liz.dyl.components.AnimationState;
-import coffee.liz.dyl.components.FacingDirection;
+import coffee.liz.dyl.components.graphic.AnimationState;
+import coffee.liz.dyl.components.control.FacingDirection;
import coffee.liz.dyl.components.graphic.AnimationGraphic;
-import coffee.liz.dyl.components.graphic.Graphic;
import coffee.liz.dyl.components.physics.Jump;
import coffee.liz.dyl.components.physics.Velocity;
+import coffee.liz.dyl.systems.health.DamageSystem;
+import coffee.liz.dyl.systems.control.InputSystem;
import coffee.liz.dyl.systems.physics.CollisionSystem;
import coffee.liz.ecs.model.System;
import coffee.liz.ecs.model.World;
diff --git a/core/src/main/java/coffee/liz/dyl/systems/RenderSystem.java b/core/src/main/java/coffee/liz/dyl/systems/graphics/RenderSystem.java
index 1624d0f..5897c65 100644
--- a/core/src/main/java/coffee/liz/dyl/systems/RenderSystem.java
+++ b/core/src/main/java/coffee/liz/dyl/systems/graphics/RenderSystem.java
@@ -1,4 +1,4 @@
-package coffee.liz.dyl.systems;
+package coffee.liz.dyl.systems.graphics;
import coffee.liz.dyl.components.graphic.Graphic;
import coffee.liz.dyl.components.physics.BoundingBox;
diff --git a/core/src/main/java/coffee/liz/dyl/systems/DamageSystem.java b/core/src/main/java/coffee/liz/dyl/systems/health/DamageSystem.java
index 60db760..b8ac306 100644
--- a/core/src/main/java/coffee/liz/dyl/systems/DamageSystem.java
+++ b/core/src/main/java/coffee/liz/dyl/systems/health/DamageSystem.java
@@ -1,4 +1,4 @@
-package coffee.liz.dyl.systems;
+package coffee.liz.dyl.systems.health;
import coffee.liz.dyl.systems.physics.CollisionSystem;
import coffee.liz.ecs.model.System;
diff --git a/core/src/main/java/coffee/liz/dyl/systems/physics/AccelerationSystem.java b/core/src/main/java/coffee/liz/dyl/systems/physics/AccelerationSystem.java
index 519cc3f..c3c61d0 100644
--- a/core/src/main/java/coffee/liz/dyl/systems/physics/AccelerationSystem.java
+++ b/core/src/main/java/coffee/liz/dyl/systems/physics/AccelerationSystem.java
@@ -7,7 +7,7 @@ import coffee.liz.dyl.components.physics.Jump;
import coffee.liz.dyl.components.physics.Mass;
import coffee.liz.dyl.components.physics.Velocity;
import coffee.liz.dyl.config.PhysicsConstants;
-import coffee.liz.dyl.systems.InputSystem;
+import coffee.liz.dyl.systems.control.InputSystem;
import coffee.liz.ecs.math.Vec2;
import coffee.liz.ecs.math.Vec2f;
import coffee.liz.ecs.model.System;
diff --git a/core/src/main/java/coffee/liz/dyl/systems/physics/CollisionGrid.java b/core/src/main/java/coffee/liz/dyl/systems/physics/CollisionGrid.java
index fb14b70..617fc22 100644
--- a/core/src/main/java/coffee/liz/dyl/systems/physics/CollisionGrid.java
+++ b/core/src/main/java/coffee/liz/dyl/systems/physics/CollisionGrid.java
@@ -2,22 +2,29 @@ package coffee.liz.dyl.systems.physics;
import coffee.liz.dyl.components.physics.BoundingBox;
import coffee.liz.ecs.math.Vec2;
+import coffee.liz.ecs.math.Vec2f;
import coffee.liz.ecs.math.Vec2i;
import coffee.liz.ecs.model.Entity;
import lombok.Getter;
-import lombok.Setter;
+import lombok.RequiredArgsConstructor;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
-@Setter
@Getter
+@RequiredArgsConstructor
public class CollisionGrid {
+ private final Vec2<Float> cellSize;
private final Map<Vec2i, Set<Entity>> cells = new HashMap<>();
private Vec2<Float> origin;
- private Vec2<Float> cellSize;
+
+ public void updateOrigin(final Collection<Vec2<Float>> points) {
+ float minX = Float.MAX_VALUE, minY = Float.MAX_VALUE;
+ for (final Vec2<Float> pos : points) {
+ minX = Math.min(minX, pos.getX());
+ minY = Math.min(minY, pos.getY());
+ }
+ this.origin = new Vec2f(minX, minY);
+ }
public void clear() {
cells.clear();
diff --git a/core/src/main/java/coffee/liz/dyl/systems/physics/CollisionSystem.java b/core/src/main/java/coffee/liz/dyl/systems/physics/CollisionSystem.java
index fb98151..099839e 100644
--- a/core/src/main/java/coffee/liz/dyl/systems/physics/CollisionSystem.java
+++ b/core/src/main/java/coffee/liz/dyl/systems/physics/CollisionSystem.java
@@ -2,9 +2,7 @@ package coffee.liz.dyl.systems.physics;
import coffee.liz.dyl.components.physics.BoundingBox;
import coffee.liz.dyl.components.physics.CollisionContacts;
-import coffee.liz.dyl.components.physics.Solid;
import coffee.liz.dyl.components.physics.Velocity;
-import coffee.liz.dyl.systems.InputSystem;
import coffee.liz.ecs.math.Vec2;
import coffee.liz.ecs.math.Vec2f;
import coffee.liz.ecs.model.Entity;
@@ -13,12 +11,14 @@ import coffee.liz.ecs.model.World;
import lombok.RequiredArgsConstructor;
import java.util.Collection;
+import java.util.List;
import java.util.Set;
+import java.util.stream.Collectors;
@RequiredArgsConstructor
public class CollisionSystem implements System {
- private float cellSize = 3f;
- private final CollisionGrid grid = new CollisionGrid();
+ private static final Vec2<Float> CELL_SIZE = new Vec2f(3f, 3f);
+ private final CollisionGrid grid = new CollisionGrid(CELL_SIZE);
@Override
public Collection<Class<? extends System>> getDependencies() {
@@ -27,20 +27,19 @@ public class CollisionSystem implements System {
@Override
public void update(final World world, final float deltaSeconds) {
- world.queryable().allOf(CollisionContacts.class)
- .forEach(e -> e.remove(CollisionContacts.class));
-
final Set<Entity> allBoxEntities = world.queryable().allOf(BoundingBox.class);
if (allBoxEntities.isEmpty()) {
return;
}
+ final List<Entity> allCollidingEntities = allBoxEntities.stream().filter(e -> e.has(CollisionContacts.class)).toList();
+ allCollidingEntities.forEach(e -> e.get(CollisionContacts.class).clear());
grid.clear();
- final Vec2<Float> origin = getOrigin(allBoxEntities.stream()
+
+ final List<Vec2<Float>> entityPositions = allBoxEntities.stream()
.map(e -> e.get(BoundingBox.class).getPosition())
- .toList());
- grid.setOrigin(origin);
- grid.setCellSize(new Vec2f(cellSize, cellSize));
+ .toList();
+ grid.updateOrigin(entityPositions);
for (final Entity entity : allBoxEntities) {
grid.insert(entity);
}
@@ -51,69 +50,31 @@ public class CollisionSystem implements System {
if (me.equals(them)) {
continue;
}
- if (!meBox.isCollidingWith(them.get(BoundingBox.class))) {
+ final BoundingBox themBox = them.get(BoundingBox.class);
+ if (!meBox.isCollidingWith(themBox)) {
continue;
}
- if (them.has(Solid.class)) {
- resolveCollision(me, them);
+ if (me.has(CollisionContacts.class)) {
+ me.get(CollisionContacts.class).add(them, getSeparationVector(meBox, themBox));
+ }
+ if (them.has(CollisionContacts.class)) {
+ them.get(CollisionContacts.class).add(me, getSeparationVector(themBox, meBox));
}
}
}
}
-
- private void resolveCollision(final Entity me, final Entity them) {
- final BoundingBox meBox = me.get(BoundingBox.class);
- final BoundingBox themBox = them.get(BoundingBox.class);
-
- final Vec2<Float> mtv = getPenetrationVector(meBox, themBox);
- contacts(me).add(them, getPenetrationVector(themBox, meBox));
- contacts(them).add(me, mtv);
- meBox.setPosition(meBox.getPosition().plus(mtv));
-
- // Cancel the velocity component pressing into the solid (normal impulse)
- final float mtvLen = mtv.length();
- final float nx = mtv.getX() / mtvLen;
- final float ny = mtv.getY() / mtvLen;
-
- final Vec2<Float> vel = me.get(Velocity.class).getVelocity();
- final float velIntoWall = -(vel.getX() * nx + vel.getY() * ny);
- if (velIntoWall > 0f) {
- me.get(Velocity.class).setVelocity(new Vec2f(
- vel.getX() + nx * velIntoWall,
- vel.getY() + ny * velIntoWall
- ));
- }
- }
-
- private Vec2<Float> getPenetrationVector(final BoundingBox meBox, final BoundingBox themBox) {
+ private Vec2<Float> getSeparationVector(final BoundingBox meBox, final BoundingBox themBox) {
final float fromLeft = meBox.getRight() - themBox.getPosition().getX();
final float fromRight = themBox.getRight() - meBox.getPosition().getX();
final float fromBottom = meBox.getTop() - themBox.getPosition().getY();
final float fromTop = themBox.getTop() - meBox.getPosition().getY();
- final float penetrationX = fromLeft < fromRight ? -fromLeft : fromRight;
- final float penetrationY = fromBottom < fromTop ? -fromBottom : fromTop;
-
- final Vec2<Float> minimumTranslationVector = Math.abs(penetrationX) <= Math.abs(penetrationY)
- ? new Vec2f(penetrationX, 0f)
- : new Vec2f(0f, penetrationY);
- return minimumTranslationVector;
- }
-
- private CollisionContacts contacts(final Entity entity) {
- if (!entity.has(CollisionContacts.class)) {
- entity.add(new CollisionContacts());
- }
- return entity.get(CollisionContacts.class);
- }
+ final float separationX = fromLeft < fromRight ? -fromLeft : fromRight;
+ final float separationY = fromBottom < fromTop ? -fromBottom : fromTop;
- private Vec2<Float> getOrigin(final Collection<Vec2<Float>> positions) {
- float minX = Float.MAX_VALUE, minY = Float.MAX_VALUE;
- for (final Vec2<Float> pos : positions) {
- minX = Math.min(minX, pos.getX());
- minY = Math.min(minY, pos.getY());
- }
- return new Vec2f(minX, minY);
+ return Math.abs(separationX) <= Math.abs(separationY)
+ ? new Vec2f(separationX, 0f)
+ : new Vec2f(0f, separationY);
}
}
diff --git a/core/src/main/java/coffee/liz/dyl/world/DylGameWorld.java b/core/src/main/java/coffee/liz/dyl/world/DylGameWorld.java
index fd8e282..fb1f847 100644
--- a/core/src/main/java/coffee/liz/dyl/world/DylGameWorld.java
+++ b/core/src/main/java/coffee/liz/dyl/world/DylGameWorld.java
@@ -4,11 +4,13 @@ import coffee.liz.dyl.DylGame;
import coffee.liz.dyl.config.PhysicsConstants;
import coffee.liz.dyl.entities.FloorFactory;
import coffee.liz.dyl.entities.PlayerFactory;
-import coffee.liz.dyl.systems.AnimationSystem;
-import coffee.liz.dyl.systems.DamageSystem;
-import coffee.liz.dyl.systems.InputSystem;
-import coffee.liz.dyl.systems.JumpSystem;
-import coffee.liz.dyl.systems.RenderSystem;
+import coffee.liz.dyl.systems.collision.SolidCollisionSystem;
+import coffee.liz.dyl.systems.control.MovementActionConsumerSystem;
+import coffee.liz.dyl.systems.graphics.AnimationSystem;
+import coffee.liz.dyl.systems.health.DamageSystem;
+import coffee.liz.dyl.systems.control.InputSystem;
+import coffee.liz.dyl.systems.control.JumpActionConsumerSystem;
+import coffee.liz.dyl.systems.graphics.RenderSystem;
import coffee.liz.ecs.DAGWorld;
import coffee.liz.dyl.components.physics.BoundingBox;
import coffee.liz.dyl.components.physics.Solid;
@@ -21,14 +23,18 @@ import com.badlogic.gdx.Gdx;
public class DylGameWorld extends DAGWorld {
public DylGameWorld(final DylGame game) {
super(
- new InputSystem(() -> game.getSettings().getKeyBinds().filterActiveActions(Gdx.input::isKeyPressed)),
+ new InputSystem(
+ () -> game.getSettings().getKeyBinds().filterActiveActions(Gdx.input::isKeyPressed),
+ () -> game.getSettings().getKeyBinds().filterActiveActions(Gdx.input::isKeyJustPressed)),
new AccelerationSystem(PhysicsConstants.GRAVITY),
new MovementSystem(),
new CollisionSystem(),
+ new SolidCollisionSystem(),
new DamageSystem(),
new AnimationSystem(),
new RenderSystem(game.getBatch(), game.getViewport()),
- new JumpSystem()
+ new JumpActionConsumerSystem(),
+ new MovementActionConsumerSystem()
);
PlayerFactory.addTo(this);
FloorFactory.addTo(this);