diff options
Diffstat (limited to 'core/src/main/java')
32 files changed, 413 insertions, 261 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); diff --git a/core/src/main/java/coffee/liz/ecs/DAGWorld.java b/core/src/main/java/coffee/liz/ecs/DAGWorld.java index f941dba..f9801c2 100644 --- a/core/src/main/java/coffee/liz/ecs/DAGWorld.java +++ b/core/src/main/java/coffee/liz/ecs/DAGWorld.java @@ -34,6 +34,7 @@ public class DAGWorld implements World { protected final Map<Class<? extends System>, System> systems; private final List<System> systemExecutionOrder; private final QueryBuilder queryBuilder = new QueryBuilder(this); + private float elapsedTime = 0f; public DAGWorld(final System... systems) { this.systems = singletonClazzMap(systems); @@ -74,9 +75,15 @@ public class DAGWorld implements World { @Override public void update(final float deltaSeconds) { + elapsedTime += deltaSeconds; systemExecutionOrder.forEach(system -> system.update(this, deltaSeconds)); } + @Override + public float getTime() { + return elapsedTime; + } + @SuppressWarnings("unchecked") @Override public <S extends System> S getSystem(final Class<S> system) { diff --git a/core/src/main/java/coffee/liz/ecs/events/EventBus.java b/core/src/main/java/coffee/liz/ecs/events/EventBus.java index d814281..1ed04c5 100644 --- a/core/src/main/java/coffee/liz/ecs/events/EventBus.java +++ b/core/src/main/java/coffee/liz/ecs/events/EventBus.java @@ -28,13 +28,10 @@ public class EventBus<E> { /** * Remove a previously registered hook. * - * @param hook - * callback to remove - * @return true - * if the hook was removed. + * @param hook callback to remove */ - public boolean unsubscribe(final Consumer<E> hook) { - return subscriptions.remove(hook); + public void unsubscribe(final Consumer<E> hook) { + subscriptions.remove(hook); } /** diff --git a/core/src/main/java/coffee/liz/ecs/model/Query.java b/core/src/main/java/coffee/liz/ecs/model/Query.java index 679ea49..8b52d42 100644 --- a/core/src/main/java/coffee/liz/ecs/model/Query.java +++ b/core/src/main/java/coffee/liz/ecs/model/Query.java @@ -5,10 +5,6 @@ import java.util.Set; import java.util.stream.Collectors; public record Query(Set<Class<? extends Component>> queryingComponents, QueryFilter filter) { - public Query { - queryingComponents = Set.copyOf(queryingComponents); - } - @SafeVarargs public static Query allOf(final Class<? extends Component>... components) { return new Query(Arrays.stream(components).collect(Collectors.toSet()), QueryFilter.ALL_OF); diff --git a/core/src/main/java/coffee/liz/ecs/model/World.java b/core/src/main/java/coffee/liz/ecs/model/World.java index 63335c8..88602fc 100644 --- a/core/src/main/java/coffee/liz/ecs/model/World.java +++ b/core/src/main/java/coffee/liz/ecs/model/World.java @@ -15,6 +15,8 @@ public interface World { void update(float deltaSeconds); + float getTime(); + <S extends System> S getSystem(Class<S> system); default void dispose() { |
