From f643f0afb8c7d91a7a39ff96f58b95baac985ce0 Mon Sep 17 00:00:00 2001 From: Elizabeth Hunt Date: Sat, 7 Mar 2026 15:01:14 -0800 Subject: Some really good refactoring happening in here --- .../main/java/coffee/liz/dyl/actions/Action.java | 4 + .../java/coffee/liz/dyl/actions/JumpAction.java | 4 + .../java/coffee/liz/dyl/actions/MoveAction.java | 14 ++++ .../coffee/liz/dyl/components/AnimationState.java | 36 --------- .../coffee/liz/dyl/components/Controllable.java | 6 -- .../coffee/liz/dyl/components/DamageGraceTime.java | 17 ----- .../coffee/liz/dyl/components/FacingDirection.java | 12 --- .../liz/dyl/components/control/ActionQueue.java | 62 ++++++++++++++++ .../liz/dyl/components/control/Controllable.java | 6 ++ .../dyl/components/control/FacingDirection.java | 15 ++++ .../liz/dyl/components/graphic/AnimationState.java | 34 +++++++++ .../liz/dyl/components/health/DamageGraceTime.java | 17 +++++ .../dyl/components/health/RelativeDamageBox.java | 10 +++ .../dyl/components/physics/CollisionContacts.java | 20 +++-- .../coffee/liz/dyl/components/physics/Jump.java | 5 +- .../liz/dyl/components/physics/Jumpable.java | 10 --- .../coffee/liz/dyl/components/physics/Solid.java | 3 +- .../coffee/liz/dyl/config/PhysicsConstants.java | 6 +- .../coffee/liz/dyl/entities/PlayerFactory.java | 19 ++--- .../coffee/liz/dyl/systems/AnimationSystem.java | 55 -------------- .../java/coffee/liz/dyl/systems/DamageSystem.java | 23 ------ .../java/coffee/liz/dyl/systems/InputSystem.java | 79 -------------------- .../java/coffee/liz/dyl/systems/JumpSystem.java | 43 ----------- .../java/coffee/liz/dyl/systems/RenderSystem.java | 52 ------------- .../systems/collision/SolidCollisionSystem.java | 58 +++++++++++++++ .../liz/dyl/systems/control/InputSystem.java | 51 +++++++++++++ .../systems/control/JumpActionConsumerSystem.java | 58 +++++++++++++++ .../control/MovementActionConsumerSystem.java | 48 ++++++++++++ .../liz/dyl/systems/graphics/AnimationSystem.java | 56 ++++++++++++++ .../liz/dyl/systems/graphics/RenderSystem.java | 52 +++++++++++++ .../liz/dyl/systems/health/DamageSystem.java | 23 ++++++ .../dyl/systems/physics/AccelerationSystem.java | 2 +- .../liz/dyl/systems/physics/CollisionGrid.java | 21 ++++-- .../liz/dyl/systems/physics/CollisionSystem.java | 85 ++++++---------------- .../java/coffee/liz/dyl/world/DylGameWorld.java | 20 +++-- core/src/main/java/coffee/liz/ecs/DAGWorld.java | 7 ++ .../main/java/coffee/liz/ecs/events/EventBus.java | 9 +-- core/src/main/java/coffee/liz/ecs/model/Query.java | 4 - core/src/main/java/coffee/liz/ecs/model/World.java | 2 + 39 files changed, 600 insertions(+), 448 deletions(-) create mode 100644 core/src/main/java/coffee/liz/dyl/actions/Action.java create mode 100644 core/src/main/java/coffee/liz/dyl/actions/JumpAction.java create mode 100644 core/src/main/java/coffee/liz/dyl/actions/MoveAction.java delete mode 100644 core/src/main/java/coffee/liz/dyl/components/AnimationState.java delete mode 100644 core/src/main/java/coffee/liz/dyl/components/Controllable.java delete mode 100644 core/src/main/java/coffee/liz/dyl/components/DamageGraceTime.java delete mode 100644 core/src/main/java/coffee/liz/dyl/components/FacingDirection.java create mode 100644 core/src/main/java/coffee/liz/dyl/components/control/ActionQueue.java create mode 100644 core/src/main/java/coffee/liz/dyl/components/control/Controllable.java create mode 100644 core/src/main/java/coffee/liz/dyl/components/control/FacingDirection.java create mode 100644 core/src/main/java/coffee/liz/dyl/components/graphic/AnimationState.java create mode 100644 core/src/main/java/coffee/liz/dyl/components/health/DamageGraceTime.java create mode 100644 core/src/main/java/coffee/liz/dyl/components/health/RelativeDamageBox.java delete mode 100644 core/src/main/java/coffee/liz/dyl/systems/AnimationSystem.java delete mode 100644 core/src/main/java/coffee/liz/dyl/systems/DamageSystem.java delete mode 100644 core/src/main/java/coffee/liz/dyl/systems/InputSystem.java delete mode 100644 core/src/main/java/coffee/liz/dyl/systems/JumpSystem.java delete mode 100644 core/src/main/java/coffee/liz/dyl/systems/RenderSystem.java create mode 100644 core/src/main/java/coffee/liz/dyl/systems/collision/SolidCollisionSystem.java create mode 100644 core/src/main/java/coffee/liz/dyl/systems/control/InputSystem.java create mode 100644 core/src/main/java/coffee/liz/dyl/systems/control/JumpActionConsumerSystem.java create mode 100644 core/src/main/java/coffee/liz/dyl/systems/control/MovementActionConsumerSystem.java create mode 100644 core/src/main/java/coffee/liz/dyl/systems/graphics/AnimationSystem.java create mode 100644 core/src/main/java/coffee/liz/dyl/systems/graphics/RenderSystem.java create mode 100644 core/src/main/java/coffee/liz/dyl/systems/health/DamageSystem.java (limited to 'core/src/main/java') 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/AnimationState.java b/core/src/main/java/coffee/liz/dyl/components/AnimationState.java deleted file mode 100644 index 04505c5..0000000 --- a/core/src/main/java/coffee/liz/dyl/components/AnimationState.java +++ /dev/null @@ -1,36 +0,0 @@ -package coffee.liz.dyl.components; - -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; - -import java.util.Map; - -@Getter -@Setter -@RequiredArgsConstructor -public class AnimationState implements Component { - private State currentState; - private State transition; - - private final Map animationStates; - - @Nullable - public AnimationGraphic getTransitionAnimation() { - if (transition == null) { - return null; - } - return animationStates.get(transition); - } - - public enum State { - IDLE, - RUN, - JUMP, - DAMAGE - } -} diff --git a/core/src/main/java/coffee/liz/dyl/components/Controllable.java b/core/src/main/java/coffee/liz/dyl/components/Controllable.java deleted file mode 100644 index c476c91..0000000 --- a/core/src/main/java/coffee/liz/dyl/components/Controllable.java +++ /dev/null @@ -1,6 +0,0 @@ -package coffee.liz.dyl.components; - -import coffee.liz.ecs.model.Component; - -public class Controllable implements Component { -} diff --git a/core/src/main/java/coffee/liz/dyl/components/DamageGraceTime.java b/core/src/main/java/coffee/liz/dyl/components/DamageGraceTime.java deleted file mode 100644 index 8424639..0000000 --- a/core/src/main/java/coffee/liz/dyl/components/DamageGraceTime.java +++ /dev/null @@ -1,17 +0,0 @@ -package coffee.liz.dyl.components; - -import coffee.liz.ecs.model.Component; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -public class DamageGraceTime implements Component { - private float duration; - - public void decrement(final float deltaTime) { - duration -= deltaTime; - } - - public boolean isOver() { - return duration <= 0; - } -} diff --git a/core/src/main/java/coffee/liz/dyl/components/FacingDirection.java b/core/src/main/java/coffee/liz/dyl/components/FacingDirection.java deleted file mode 100644 index d517fc1..0000000 --- a/core/src/main/java/coffee/liz/dyl/components/FacingDirection.java +++ /dev/null @@ -1,12 +0,0 @@ -package coffee.liz.dyl.components; - -import coffee.liz.ecs.math.Vec2; -import coffee.liz.ecs.model.Component; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -@Getter -public class FacingDirection implements Component { - private final Vec2 unitDirection; -} 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, ExpiringAction> actionQueue = new HashMap<>(); + + public boolean has(final Class 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 consume(final Class actionClass, final float currentTime, final Predicate 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/control/Controllable.java b/core/src/main/java/coffee/liz/dyl/components/control/Controllable.java new file mode 100644 index 0000000..f63a1c2 --- /dev/null +++ b/core/src/main/java/coffee/liz/dyl/components/control/Controllable.java @@ -0,0 +1,6 @@ +package coffee.liz.dyl.components.control; + +import coffee.liz.ecs.model.Component; + +public class Controllable implements Component { +} diff --git a/core/src/main/java/coffee/liz/dyl/components/control/FacingDirection.java b/core/src/main/java/coffee/liz/dyl/components/control/FacingDirection.java new file mode 100644 index 0000000..280bfb0 --- /dev/null +++ b/core/src/main/java/coffee/liz/dyl/components/control/FacingDirection.java @@ -0,0 +1,15 @@ +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; + +@AllArgsConstructor +@Getter +@Setter +public class FacingDirection implements Component { + private Vec2 unitDirection; +} diff --git a/core/src/main/java/coffee/liz/dyl/components/graphic/AnimationState.java b/core/src/main/java/coffee/liz/dyl/components/graphic/AnimationState.java new file mode 100644 index 0000000..ad183da --- /dev/null +++ b/core/src/main/java/coffee/liz/dyl/components/graphic/AnimationState.java @@ -0,0 +1,34 @@ +package coffee.liz.dyl.components.graphic; + +import coffee.liz.ecs.model.Component; +import jakarta.annotation.Nullable; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; + +import java.util.Map; + +@Getter +@Setter +@RequiredArgsConstructor +public class AnimationState implements Component { + private State currentState; + private State transition; + + private final Map animationStates; + + @Nullable + public AnimationGraphic getTransitionAnimation() { + if (transition == null) { + return null; + } + return animationStates.get(transition); + } + + public enum State { + IDLE, + RUN, + JUMP, + DAMAGE + } +} diff --git a/core/src/main/java/coffee/liz/dyl/components/health/DamageGraceTime.java b/core/src/main/java/coffee/liz/dyl/components/health/DamageGraceTime.java new file mode 100644 index 0000000..ca16f63 --- /dev/null +++ b/core/src/main/java/coffee/liz/dyl/components/health/DamageGraceTime.java @@ -0,0 +1,17 @@ +package coffee.liz.dyl.components.health; + +import coffee.liz.ecs.model.Component; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class DamageGraceTime implements Component { + private float duration; + + public void decrement(final float deltaTime) { + duration -= deltaTime; + } + + public boolean isOver() { + return duration <= 0; + } +} 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 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 penetrationVector; + private final List contacts = new ArrayList<>(); + + public void clear() { + contacts.clear(); } - private final List contacts = new ArrayList<>(); + public void add(final Entity entity, final Vec2 separationVector) { + contacts.add(new Contact(entity, separationVector)); + } - public void add(final Entity entity, final Vec2 penetrationVector) { - contacts.add(new Contact(entity, penetrationVector)); + @Getter + @RequiredArgsConstructor + public static class Contact { + private final Entity contactEntity; + private final Vec2 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/AnimationSystem.java b/core/src/main/java/coffee/liz/dyl/systems/AnimationSystem.java deleted file mode 100644 index a932eea..0000000 --- a/core/src/main/java/coffee/liz/dyl/systems/AnimationSystem.java +++ /dev/null @@ -1,55 +0,0 @@ -package coffee.liz.dyl.systems; - -import coffee.liz.dyl.components.AnimationState; -import coffee.liz.dyl.components.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.physics.CollisionSystem; -import coffee.liz.ecs.model.System; -import coffee.liz.ecs.model.World; - -import java.util.Collection; -import java.util.List; - -public class AnimationSystem implements System { - @Override - public Collection> getDependencies() { - return List.of(InputSystem.class, CollisionSystem.class, DamageSystem.class); - } - - @Override - public void update(final World world, final float deltaSeconds) { - world.queryable().allOf(AnimationState.class, Velocity.class).forEach(entity -> { - final AnimationState animationState = entity.get(AnimationState.class); - final Velocity velocity = entity.get(Velocity.class); - - if (entity.has(Jump.class)) { - animationState.setTransition(AnimationState.State.JUMP); - return; - } - if (velocity.getVelocity().getX() != 0) { - animationState.setTransition(AnimationState.State.RUN); - return; - } - animationState.setTransition(AnimationState.State.IDLE); - }); - - world.queryable().allOf(AnimationState.class).forEach(entity -> { - final AnimationState animationState = entity.get(AnimationState.class); - final AnimationGraphic animation = animationState.getTransitionAnimation(); - if (animation == null) { - return; - } - if (entity.has(FacingDirection.class)) { - animation.setFacing(entity.get(FacingDirection.class).getUnitDirection()); - } - if (animationState.getTransition().equals(animationState.getCurrentState())) { - return; - } - - entity.add(animation); - }); - } -} diff --git a/core/src/main/java/coffee/liz/dyl/systems/DamageSystem.java b/core/src/main/java/coffee/liz/dyl/systems/DamageSystem.java deleted file mode 100644 index 60db760..0000000 --- a/core/src/main/java/coffee/liz/dyl/systems/DamageSystem.java +++ /dev/null @@ -1,23 +0,0 @@ -package coffee.liz.dyl.systems; - -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 DamageSystem implements System { - - @Override - public Collection> getDependencies() { - return List.of(CollisionSystem.class); - } - - @Override - public void update(final World world, final float deltaSeconds) { -// world.queryable().allOf(CollisionContacts.class) -// world.queryable().allOf(DamageGraceTime.class).forEach() - // TODO: Set opacity - } -} 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> activeActions; - - @Override - public Collection> getDependencies() { - return Set.of(); - } - - @Override - public void update(final World world, final float deltaSeconds) { - final Set 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> 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/RenderSystem.java b/core/src/main/java/coffee/liz/dyl/systems/RenderSystem.java deleted file mode 100644 index 1624d0f..0000000 --- a/core/src/main/java/coffee/liz/dyl/systems/RenderSystem.java +++ /dev/null @@ -1,52 +0,0 @@ -package coffee.liz.dyl.systems; - -import coffee.liz.dyl.components.graphic.Graphic; -import coffee.liz.dyl.components.physics.BoundingBox; -import coffee.liz.ecs.model.System; -import coffee.liz.ecs.model.World; -import com.badlogic.gdx.graphics.Color; -import com.badlogic.gdx.graphics.OrthographicCamera; -import com.badlogic.gdx.graphics.g2d.Batch; -import com.badlogic.gdx.utils.viewport.Viewport; - -import java.util.Collection; -import java.util.Comparator; -import java.util.Set; - -public class RenderSystem implements System { - private final Batch batch; - private final OrthographicCamera camera; - private final Viewport viewport; - - public RenderSystem(final Batch batch, final Viewport viewport) { - this.batch = batch; - this.viewport = viewport; - this.camera = (OrthographicCamera) viewport.getCamera(); - } - - @Override - public Collection> getDependencies() { - return Set.of(AnimationSystem.class); - } - - @Override - public void update(final World world, final float deltaSeconds) { - viewport.apply(); - camera.update(); - - batch.setProjectionMatrix(camera.combined); - batch.begin(); - batch.setColor(Color.WHITE); - - world.queryable().allOf(BoundingBox.class, Graphic.class).stream() - .sorted(Comparator.comparingDouble(e -> e.get(Graphic.class).getZ())) - .forEach(e -> { - final BoundingBox boundingBox = e.get(BoundingBox.class); - final Graphic graphic = e.get(Graphic.class); - graphic.draw(batch, boundingBox); - }); - - batch.setColor(Color.WHITE); - batch.end(); - } -} 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> 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 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> heldActions; + private final Supplier> justPressedActions; + + @Override + public Collection> getDependencies() { + return Set.of(); + } + + @Override + public void update(final World world, final float deltaSeconds) { + final float time = world.getTime(); + final Set held = heldActions.get(); + final Set 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> 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> 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/graphics/AnimationSystem.java b/core/src/main/java/coffee/liz/dyl/systems/graphics/AnimationSystem.java new file mode 100644 index 0000000..a1e4874 --- /dev/null +++ b/core/src/main/java/coffee/liz/dyl/systems/graphics/AnimationSystem.java @@ -0,0 +1,56 @@ +package coffee.liz.dyl.systems.graphics; + +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.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; + +import java.util.Collection; +import java.util.List; + +public class AnimationSystem implements System { + @Override + public Collection> getDependencies() { + return List.of(InputSystem.class, CollisionSystem.class, DamageSystem.class); + } + + @Override + public void update(final World world, final float deltaSeconds) { + world.queryable().allOf(AnimationState.class, Velocity.class).forEach(entity -> { + final AnimationState animationState = entity.get(AnimationState.class); + final Velocity velocity = entity.get(Velocity.class); + + if (entity.has(Jump.class)) { + animationState.setTransition(AnimationState.State.JUMP); + return; + } + if (velocity.getVelocity().getX() != 0) { + animationState.setTransition(AnimationState.State.RUN); + return; + } + animationState.setTransition(AnimationState.State.IDLE); + }); + + world.queryable().allOf(AnimationState.class).forEach(entity -> { + final AnimationState animationState = entity.get(AnimationState.class); + final AnimationGraphic animation = animationState.getTransitionAnimation(); + if (animation == null) { + return; + } + if (entity.has(FacingDirection.class)) { + animation.setFacing(entity.get(FacingDirection.class).getUnitDirection()); + } + if (animationState.getTransition().equals(animationState.getCurrentState())) { + return; + } + + entity.add(animation); + }); + } +} diff --git a/core/src/main/java/coffee/liz/dyl/systems/graphics/RenderSystem.java b/core/src/main/java/coffee/liz/dyl/systems/graphics/RenderSystem.java new file mode 100644 index 0000000..5897c65 --- /dev/null +++ b/core/src/main/java/coffee/liz/dyl/systems/graphics/RenderSystem.java @@ -0,0 +1,52 @@ +package coffee.liz.dyl.systems.graphics; + +import coffee.liz.dyl.components.graphic.Graphic; +import coffee.liz.dyl.components.physics.BoundingBox; +import coffee.liz.ecs.model.System; +import coffee.liz.ecs.model.World; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.OrthographicCamera; +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.utils.viewport.Viewport; + +import java.util.Collection; +import java.util.Comparator; +import java.util.Set; + +public class RenderSystem implements System { + private final Batch batch; + private final OrthographicCamera camera; + private final Viewport viewport; + + public RenderSystem(final Batch batch, final Viewport viewport) { + this.batch = batch; + this.viewport = viewport; + this.camera = (OrthographicCamera) viewport.getCamera(); + } + + @Override + public Collection> getDependencies() { + return Set.of(AnimationSystem.class); + } + + @Override + public void update(final World world, final float deltaSeconds) { + viewport.apply(); + camera.update(); + + batch.setProjectionMatrix(camera.combined); + batch.begin(); + batch.setColor(Color.WHITE); + + world.queryable().allOf(BoundingBox.class, Graphic.class).stream() + .sorted(Comparator.comparingDouble(e -> e.get(Graphic.class).getZ())) + .forEach(e -> { + final BoundingBox boundingBox = e.get(BoundingBox.class); + final Graphic graphic = e.get(Graphic.class); + graphic.draw(batch, boundingBox); + }); + + batch.setColor(Color.WHITE); + batch.end(); + } +} diff --git a/core/src/main/java/coffee/liz/dyl/systems/health/DamageSystem.java b/core/src/main/java/coffee/liz/dyl/systems/health/DamageSystem.java new file mode 100644 index 0000000..b8ac306 --- /dev/null +++ b/core/src/main/java/coffee/liz/dyl/systems/health/DamageSystem.java @@ -0,0 +1,23 @@ +package coffee.liz.dyl.systems.health; + +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 DamageSystem implements System { + + @Override + public Collection> getDependencies() { + return List.of(CollisionSystem.class); + } + + @Override + public void update(final World world, final float deltaSeconds) { +// world.queryable().allOf(CollisionContacts.class) +// world.queryable().allOf(DamageGraceTime.class).forEach() + // TODO: Set opacity + } +} 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 cellSize; private final Map> cells = new HashMap<>(); private Vec2 origin; - private Vec2 cellSize; + + public void updateOrigin(final Collection> points) { + float minX = Float.MAX_VALUE, minY = Float.MAX_VALUE; + for (final Vec2 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 CELL_SIZE = new Vec2f(3f, 3f); + private final CollisionGrid grid = new CollisionGrid(CELL_SIZE); @Override public Collection> 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 allBoxEntities = world.queryable().allOf(BoundingBox.class); if (allBoxEntities.isEmpty()) { return; } + final List allCollidingEntities = allBoxEntities.stream().filter(e -> e.has(CollisionContacts.class)).toList(); + allCollidingEntities.forEach(e -> e.get(CollisionContacts.class).clear()); grid.clear(); - final Vec2 origin = getOrigin(allBoxEntities.stream() + + final List> 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 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 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 getPenetrationVector(final BoundingBox meBox, final BoundingBox themBox) { + private Vec2 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 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 getOrigin(final Collection> positions) { - float minX = Float.MAX_VALUE, minY = Float.MAX_VALUE; - for (final Vec2 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, System> systems; private final List 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 getSystem(final Class 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 { /** * 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 hook) { - return subscriptions.remove(hook); + public void unsubscribe(final Consumer 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> queryingComponents, QueryFilter filter) { - public Query { - queryingComponents = Set.copyOf(queryingComponents); - } - @SafeVarargs public static Query allOf(final Class... 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 getSystem(Class system); default void dispose() { -- cgit v1.2.3-70-g09d2