diff options
| author | Elizabeth Alexander Hunt <me@liz.coffee> | 2026-03-01 12:54:31 -0800 |
|---|---|---|
| committer | Elizabeth Alexander Hunt <me@liz.coffee> | 2026-03-01 12:54:47 -0800 |
| commit | 242b32050feff1d7f047e52d46daadd3ec682c14 (patch) | |
| tree | 6207a3f350946ae3b1c2764b2bb9321473f6bfaa /core/src/main/java/coffee/liz/dyl | |
| parent | c491b5cb08972ffc041fa0b968810373b9ed79a3 (diff) | |
| download | dyl-242b32050feff1d7f047e52d46daadd3ec682c14.tar.gz dyl-242b32050feff1d7f047e52d46daadd3ec682c14.zip | |
Adding animations n stuff.
Diffstat (limited to 'core/src/main/java/coffee/liz/dyl')
29 files changed, 750 insertions, 84 deletions
diff --git a/core/src/main/java/coffee/liz/dyl/DylGame.java b/core/src/main/java/coffee/liz/dyl/DylGame.java index 1166d42..aff4066 100644 --- a/core/src/main/java/coffee/liz/dyl/DylGame.java +++ b/core/src/main/java/coffee/liz/dyl/DylGame.java @@ -15,8 +15,8 @@ import lombok.Getter; @Getter public class DylGame extends Game { - public static final float WORLD_WIDTH = 10f; - public static final float WORLD_HEIGHT = 10f; + public static final float WORLD_WIDTH = 20f; + public static final float WORLD_HEIGHT = 20f; private InputMultiplexer inputMultiplexer; private Batch batch; diff --git a/core/src/main/java/coffee/liz/dyl/components/AnimationState.java b/core/src/main/java/coffee/liz/dyl/components/AnimationState.java new file mode 100644 index 0000000..04505c5 --- /dev/null +++ b/core/src/main/java/coffee/liz/dyl/components/AnimationState.java @@ -0,0 +1,36 @@ +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<State, AnimationGraphic> 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/DamageGraceTime.java b/core/src/main/java/coffee/liz/dyl/components/DamageGraceTime.java new file mode 100644 index 0000000..8424639 --- /dev/null +++ b/core/src/main/java/coffee/liz/dyl/components/DamageGraceTime.java @@ -0,0 +1,17 @@ +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 new file mode 100644 index 0000000..d517fc1 --- /dev/null +++ b/core/src/main/java/coffee/liz/dyl/components/FacingDirection.java @@ -0,0 +1,12 @@ +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<Float> unitDirection; +} diff --git a/core/src/main/java/coffee/liz/dyl/components/graphic/AnimationGraphic.java b/core/src/main/java/coffee/liz/dyl/components/graphic/AnimationGraphic.java new file mode 100644 index 0000000..43242b3 --- /dev/null +++ b/core/src/main/java/coffee/liz/dyl/components/graphic/AnimationGraphic.java @@ -0,0 +1,47 @@ +package coffee.liz.dyl.components.graphic; + +import coffee.liz.dyl.components.physics.BoundingBox; +import coffee.liz.ecs.math.Vec2; +import coffee.liz.ecs.math.Vec2f; +import com.badlogic.gdx.graphics.Color; +import com.badlogic.gdx.graphics.g2d.Animation; +import com.badlogic.gdx.graphics.g2d.Batch; +import com.badlogic.gdx.graphics.g2d.TextureRegion; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.time.Duration; +import java.time.Instant; + +@Getter +@RequiredArgsConstructor +public class AnimationGraphic implements Graphic { + private final Color color; + private final Animation<TextureRegion> animation; + private final int z; + private Instant lastUpdate = null; + private Vec2<Float> unitDirection = Vec2f.ZERO; + + @Override + public void setFacing(final Vec2<Float> unitDirection) { + this.unitDirection = unitDirection; + } + + @Override + public void draw(final Batch batch, final BoundingBox boundingBox) { + final Instant now = Instant.now(); + if (lastUpdate == null) lastUpdate = now; + + final float stateDuration = (float) (Duration.between(lastUpdate, now).toMillis()) / 1000f; + final float x = boundingBox.getPosition().getX(); + final float y = boundingBox.getPosition().getY(); + final float w = boundingBox.getSize().getX(); + final float h = boundingBox.getSize().getY(); + batch.setColor(color); + if (unitDirection.getX() < 0) { + batch.draw(animation.getKeyFrame(stateDuration, true), x + w, y, -w, h); + } else { + batch.draw(animation.getKeyFrame(stateDuration, true), x, y, w, h); + } + } +} diff --git a/core/src/main/java/coffee/liz/dyl/components/graphic/Graphic.java b/core/src/main/java/coffee/liz/dyl/components/graphic/Graphic.java index 7075755..3e12083 100644 --- a/core/src/main/java/coffee/liz/dyl/components/graphic/Graphic.java +++ b/core/src/main/java/coffee/liz/dyl/components/graphic/Graphic.java @@ -1,6 +1,7 @@ package coffee.liz.dyl.components.graphic; -import coffee.liz.ecs.common.components.physics.BoundingBox; +import coffee.liz.dyl.components.physics.BoundingBox; +import coffee.liz.ecs.math.Vec2; import coffee.liz.ecs.model.Component; import com.badlogic.gdx.graphics.g2d.Batch; @@ -12,4 +13,6 @@ public interface Graphic extends Component { int getZ(); void draw(final Batch batch, final BoundingBox boundingBox); + + default void setFacing(final Vec2<Float> unitDirection) {} } diff --git a/core/src/main/java/coffee/liz/dyl/components/graphic/TextureGraphic.java b/core/src/main/java/coffee/liz/dyl/components/graphic/TextureGraphic.java index c2718b5..8cca8ac 100644 --- a/core/src/main/java/coffee/liz/dyl/components/graphic/TextureGraphic.java +++ b/core/src/main/java/coffee/liz/dyl/components/graphic/TextureGraphic.java @@ -1,25 +1,37 @@ package coffee.liz.dyl.components.graphic; -import coffee.liz.ecs.common.components.physics.BoundingBox; +import coffee.liz.dyl.components.physics.BoundingBox; +import coffee.liz.ecs.math.Vec2; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.Batch; -import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.RequiredArgsConstructor; @Getter -@AllArgsConstructor +@RequiredArgsConstructor public class TextureGraphic implements Graphic { - private int z; - private Color color; - private Texture texture; + private final int z; + private final Color color; + private final Texture texture; + private boolean facingLeft = false; + + @Override + public void setFacing(final Vec2<Float> unitDirection) { + this.facingLeft = unitDirection.getX() < 0; + } @Override public void draw(final Batch batch, final BoundingBox boundingBox) { + final float x = boundingBox.getPosition().getX(); + final float y = boundingBox.getPosition().getY(); + final float w = boundingBox.getSize().getX(); + final float h = boundingBox.getSize().getY(); batch.setColor(color); - batch.draw(texture, - boundingBox.getPosition().getX(), boundingBox.getPosition().getY(), - boundingBox.getSize().getX(), boundingBox.getSize().getY() - ); + if (facingLeft) { + batch.draw(texture, x + w, y, -w, h); + } else { + batch.draw(texture, x, y, w, h); + } } } diff --git a/core/src/main/java/coffee/liz/dyl/components/physics/BoundingBox.java b/core/src/main/java/coffee/liz/dyl/components/physics/BoundingBox.java new file mode 100644 index 0000000..db484db --- /dev/null +++ b/core/src/main/java/coffee/liz/dyl/components/physics/BoundingBox.java @@ -0,0 +1,34 @@ +package coffee.liz.dyl.components.physics; + +import coffee.liz.ecs.math.Vec2; +import coffee.liz.ecs.model.Component; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@AllArgsConstructor +@Getter +@Setter +public class BoundingBox implements Component { + private Vec2<Float> position; + private Vec2<Float> size; + + public float getTop() { + return position.getY() + size.getY(); + } + + public float getRight() { + return position.getX() + size.getX(); + } + + public boolean isCollidingWith(final BoundingBox other) { + return getRight() > other.getPosition().getX() + && position.getX() < other.getRight() + && getTop() > other.getPosition().getY() + && position.getY() < other.getTop(); + } + + public boolean isAbove(final BoundingBox other) { + return position.getY() >= other.getTop(); + } +} 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 new file mode 100644 index 0000000..b709751 --- /dev/null +++ b/core/src/main/java/coffee/liz/dyl/components/physics/CollisionContacts.java @@ -0,0 +1,20 @@ +package coffee.liz.dyl.components.physics; + +import coffee.liz.ecs.model.Component; +import coffee.liz.ecs.model.Entity; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class CollisionContacts implements Component { + private final List<Entity> contacts = new ArrayList<>(); + + public void add(final Entity entity) { + contacts.add(entity); + } + + public List<Entity> getAll() { + return Collections.unmodifiableList(contacts); + } +} diff --git a/core/src/main/java/coffee/liz/dyl/components/physics/Force.java b/core/src/main/java/coffee/liz/dyl/components/physics/Force.java new file mode 100644 index 0000000..de8b003 --- /dev/null +++ b/core/src/main/java/coffee/liz/dyl/components/physics/Force.java @@ -0,0 +1,11 @@ +package coffee.liz.dyl.components.physics; + +import coffee.liz.ecs.math.Vec2; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class Force { + private final Vec2<Float> force; +} diff --git a/core/src/main/java/coffee/liz/dyl/components/physics/Forces.java b/core/src/main/java/coffee/liz/dyl/components/physics/Forces.java new file mode 100644 index 0000000..e90cd7f --- /dev/null +++ b/core/src/main/java/coffee/liz/dyl/components/physics/Forces.java @@ -0,0 +1,26 @@ +package coffee.liz.dyl.components.physics; + +import coffee.liz.ecs.math.Vec2; +import coffee.liz.ecs.math.Vec2f; +import coffee.liz.ecs.model.Component; +import lombok.Getter; + +import java.util.ArrayList; +import java.util.List; + +@Getter +public class Forces implements Component { + private final List<Force> forces = new ArrayList<>(); + + public void add(final Force force) { + forces.add(force); + } + + public Vec2<Float> sum() { + return forces.stream().map(Force::getForce).reduce(Vec2::plus).orElse(Vec2f.ZERO); + } + + public void clear() { + forces.clear(); + } +} diff --git a/core/src/main/java/coffee/liz/dyl/components/physics/Gravity.java b/core/src/main/java/coffee/liz/dyl/components/physics/Gravity.java new file mode 100644 index 0000000..a6e4c14 --- /dev/null +++ b/core/src/main/java/coffee/liz/dyl/components/physics/Gravity.java @@ -0,0 +1,11 @@ +package coffee.liz.dyl.components.physics; + +import coffee.liz.ecs.model.Component; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class Gravity implements Component { + private final float terminalVelocity; +} 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 new file mode 100644 index 0000000..c49d6e1 --- /dev/null +++ b/core/src/main/java/coffee/liz/dyl/components/physics/Jump.java @@ -0,0 +1,14 @@ +package coffee.liz.dyl.components.physics; + +import coffee.liz.ecs.model.Component; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@AllArgsConstructor +@Getter +@Setter +public class Jump implements Component { + private boolean canJump; + private long jumpStartMs; +} diff --git a/core/src/main/java/coffee/liz/dyl/components/physics/Mass.java b/core/src/main/java/coffee/liz/dyl/components/physics/Mass.java new file mode 100644 index 0000000..19425f5 --- /dev/null +++ b/core/src/main/java/coffee/liz/dyl/components/physics/Mass.java @@ -0,0 +1,11 @@ +package coffee.liz.dyl.components.physics; + +import coffee.liz.ecs.model.Component; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public class Mass implements Component { + private final float mass; +} 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 new file mode 100644 index 0000000..57af1e3 --- /dev/null +++ b/core/src/main/java/coffee/liz/dyl/components/physics/Solid.java @@ -0,0 +1,5 @@ +package coffee.liz.dyl.components.physics; + +import coffee.liz.ecs.model.Component; + +public class Solid implements Component {} diff --git a/core/src/main/java/coffee/liz/dyl/components/physics/Velocity.java b/core/src/main/java/coffee/liz/dyl/components/physics/Velocity.java new file mode 100644 index 0000000..b42d3a6 --- /dev/null +++ b/core/src/main/java/coffee/liz/dyl/components/physics/Velocity.java @@ -0,0 +1,14 @@ +package coffee.liz.dyl.components.physics; + +import coffee.liz.ecs.math.Vec2; +import coffee.liz.ecs.model.Component; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; + +@AllArgsConstructor +@Getter +@Setter +public class Velocity implements Component { + private Vec2<Float> velocity; +} diff --git a/core/src/main/java/coffee/liz/dyl/config/KeyBinds.java b/core/src/main/java/coffee/liz/dyl/config/KeyBinds.java index 0b33b26..620faad 100644 --- a/core/src/main/java/coffee/liz/dyl/config/KeyBinds.java +++ b/core/src/main/java/coffee/liz/dyl/config/KeyBinds.java @@ -2,7 +2,6 @@ package coffee.liz.dyl.config; import com.badlogic.gdx.Input; import lombok.AllArgsConstructor; -import lombok.Builder; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -11,17 +10,13 @@ import java.util.Map; import java.util.Set; import java.util.function.Predicate; -@Builder @AllArgsConstructor @RequiredArgsConstructor @Getter public class KeyBinds { - @Builder.Default private Set<Integer> moveLeftKeys = Set.of(Input.Keys.H, Input.Keys.LEFT, Input.Keys.A); - @Builder.Default private Set<Integer> moveRightKeys = Set.of(Input.Keys.L, Input.Keys.RIGHT, Input.Keys.D); - @Builder.Default - private Set<Integer> jumpKeys = Set.of(Input.Keys.W, Input.Keys.UP, Input.Keys.SPACE); + private Set<Integer> jumpKeys = Set.of(Input.Keys.K, Input.Keys.W, Input.Keys.UP, Input.Keys.SPACE); public Set<Action> filterActiveActions(final Predicate<Integer> isDown) { final Set<Action> actions = new HashSet<>(); 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 b0b1a31..6339a34 100644 --- a/core/src/main/java/coffee/liz/dyl/config/PhysicsConstants.java +++ b/core/src/main/java/coffee/liz/dyl/config/PhysicsConstants.java @@ -1,12 +1,12 @@ package coffee.liz.dyl.config; public final class PhysicsConstants { - public static final float GRAVITY = 9.8f; - public static final float MOVE_SPEED = 5.0f; - public static final float JUMP_INITIAL_VEL = 8.0f; - public static final float JUMP_ACC = 15.0f; - public static final long MAX_JUMP_MS = 150L; + public static final float GRAVITY = 40f; + public static final float MOVE_SPEED = 8.0f; + public static final float JUMP_INITIAL_VEL = 7.0f; + public static final float JUMP_ACC = 30.0f; + public static final long MAX_JUMP_MS = 200L; + public static final float DAMAGE_TIME = 0.25f; - private PhysicsConstants() { - } + private PhysicsConstants() { } } diff --git a/core/src/main/java/coffee/liz/dyl/config/Settings.java b/core/src/main/java/coffee/liz/dyl/config/Settings.java index 13fbb3c..73f39f9 100644 --- a/core/src/main/java/coffee/liz/dyl/config/Settings.java +++ b/core/src/main/java/coffee/liz/dyl/config/Settings.java @@ -12,5 +12,5 @@ public class Settings { @Builder.Default private boolean playMusic = true; @Builder.Default - private KeyBinds keyBinds = KeyBinds.builder().build(); + private KeyBinds keyBinds = new KeyBinds(); } 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 a982bb9..c857d02 100644 --- a/core/src/main/java/coffee/liz/dyl/entities/PlayerFactory.java +++ b/core/src/main/java/coffee/liz/dyl/entities/PlayerFactory.java @@ -1,44 +1,88 @@ package coffee.liz.dyl.entities; +import coffee.liz.dyl.components.AnimationState; import coffee.liz.dyl.components.Controllable; -import coffee.liz.dyl.components.graphic.TextureGraphic; -import coffee.liz.ecs.common.components.physics.BoundingBox; -import coffee.liz.ecs.common.components.physics.Collidable; -import coffee.liz.ecs.common.components.physics.Forces; -import coffee.liz.ecs.common.components.physics.Gravity; -import coffee.liz.ecs.common.components.physics.Jump; -import coffee.liz.ecs.common.components.physics.Mass; -import coffee.liz.ecs.common.components.physics.Velocity; +import coffee.liz.dyl.components.FacingDirection; +import coffee.liz.dyl.components.graphic.AnimationGraphic; +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.model.Entity; +import coffee.liz.ecs.math.Vec2i; import coffee.liz.ecs.model.World; import com.badlogic.gdx.Gdx; import com.badlogic.gdx.files.FileHandle; import com.badlogic.gdx.graphics.Color; import com.badlogic.gdx.graphics.Texture; +import com.badlogic.gdx.graphics.g2d.Animation; import com.badlogic.gdx.graphics.g2d.TextureRegion; +import java.util.Map; + public class PlayerFactory { - private static final int PENGUIN_FRAMES_Y = 4; - private static final int PENGUIN_FRAMES_X = 8; - private static final FileHandle FILE = Gdx.files.internal("player.png"); - private static Texture texture = new Texture(FILE); - - public static Entity addTo(final World world) { - final TextureRegion[][] tmp = TextureRegion.split(texture, texture.getWidth() / PENGUIN_FRAMES_X, - texture.getHeight() / PENGUIN_FRAMES_Y); - return world.createEntity() - .add(new TextureGraphic(0, Color.PINK, tmp[0][0].getTexture())) - .add(new Controllable()) - .add(new BoundingBox(new Vec2f(2f, 2f), new Vec2f(1f, 1f))) - .add(new Velocity(Vec2f.ZERO)) - .add(new Mass(1f)) - .add(new Forces()) - .add(new Gravity(20f)) - .add(new Jump(false, 0L)) - .add(new Collidable()); + private static final PlayerAssets ASSETS = new PlayerAssets(); + + public static void addTo(final World world) { + final var animationState = new AnimationState( + Map.of( + AnimationState.State.JUMP, new AnimationGraphic(Color.WHITE, ASSETS.jumping, 0), + AnimationState.State.DAMAGE, new AnimationGraphic(Color.valueOf("#0000000a"), ASSETS.damaged, 0), + AnimationState.State.IDLE, new AnimationGraphic(Color.WHITE, ASSETS.idling, 0), + AnimationState.State.RUN, new AnimationGraphic(Color.WHITE, ASSETS.running, 0))); + world.createEntity() + .add(animationState) + .add(animationState.getAnimationStates().get(AnimationState.State.IDLE)) + .add(new Controllable()) + .add(new BoundingBox(new Vec2f(1f, 1f), new Vec2f(1f, 1f))) + .add(new Velocity(Vec2f.ZERO)) + .add(new Mass(0.8f)) + .add(new Forces()) + .add(new FacingDirection(Vec2f.ZERO)) + .add(new Gravity(20f)) + .add(new Jump(false, 0L)); } - private PlayerFactory() { + private static class PlayerAssets { + private static final Vec2<Integer> DIMS = new Vec2i(14, 8); + private static final FileHandle FILE = Gdx.files.internal("noir/hero.png"); + final Texture texture; + final TextureRegion[][] region; + final Animation<TextureRegion> idling; + final Animation<TextureRegion> damaged; + final Animation<TextureRegion> jumping; + final Animation<TextureRegion> running; + public PlayerAssets() { + texture = new Texture(FILE); + region = TextureRegion.split(texture, + texture.getWidth() / DIMS.getX(), + texture.getHeight() / DIMS.getY() + ); + + final TextureRegion[] idlingTextureRegion = new TextureRegion[2]; + idlingTextureRegion[0] = region[5][0]; + idlingTextureRegion[1] = region[5][1]; + + final TextureRegion[] runningTextureRegion = new TextureRegion[2]; + runningTextureRegion[0] = region[0][0]; + runningTextureRegion[1] = region[0][1]; + + final TextureRegion[] damagedTextureRegion = new TextureRegion[1]; + damagedTextureRegion[0] = region[6][0]; + + final TextureRegion[] jump = new TextureRegion[1]; + jump[0] = region[1][0]; + + running = new Animation<>(0.20f, runningTextureRegion); + idling = new Animation<>(0.75f, idlingTextureRegion); + damaged = new Animation<>(PhysicsConstants.DAMAGE_TIME, damagedTextureRegion); + jumping = new Animation<>(0.75f, jump); + } } + + private PlayerFactory() { } } diff --git a/core/src/main/java/coffee/liz/dyl/systems/AnimationSystem.java b/core/src/main/java/coffee/liz/dyl/systems/AnimationSystem.java new file mode 100644 index 0000000..8c0d595 --- /dev/null +++ b/core/src/main/java/coffee/liz/dyl/systems/AnimationSystem.java @@ -0,0 +1,56 @@ +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<Class<? extends System>> 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); + final Jump jump = entity.get(Jump.class); + + if (!jump.isCanJump()) { + 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 new file mode 100644 index 0000000..60db760 --- /dev/null +++ b/core/src/main/java/coffee/liz/dyl/systems/DamageSystem.java @@ -0,0 +1,23 @@ +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<Class<? extends System>> 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 index d5acbaf..1938a50 100644 --- a/core/src/main/java/coffee/liz/dyl/systems/InputSystem.java +++ b/core/src/main/java/coffee/liz/dyl/systems/InputSystem.java @@ -1,12 +1,13 @@ package coffee.liz.dyl.systems; import coffee.liz.dyl.components.Controllable; +import coffee.liz.dyl.components.FacingDirection; import coffee.liz.dyl.config.KeyBinds; -import coffee.liz.ecs.common.components.physics.Force; -import coffee.liz.ecs.common.components.physics.Forces; -import coffee.liz.ecs.common.components.physics.Jump; -import coffee.liz.ecs.common.components.physics.Mass; -import coffee.liz.ecs.common.components.physics.Velocity; +import coffee.liz.dyl.components.physics.Force; +import coffee.liz.dyl.components.physics.Forces; +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.Vec2f; import coffee.liz.ecs.model.System; import coffee.liz.ecs.model.World; @@ -35,27 +36,29 @@ public class InputSystem implements System { 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 -> { + world.queryable().allOf(Forces.class, 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 (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))); + } float newDy = velocity.getVelocity().getY(); - if (entity.has(Jump.class) && entity.has(Mass.class) && entity.has(Forces.class)) { - final Jump jump = entity.get(Jump.class); - final Mass mass = entity.get(Mass.class); + final Jump jump = entity.get(Jump.class); + final Mass mass = entity.get(Mass.class); - if (actions.contains(KeyBinds.Action.JUMP)) { - if (jump.isCanJump()) { - newDy = JUMP_INITIAL_VEL; - jump.setCanJump(false); - jump.setJumpStartMs(Instant.now().toEpochMilli()); - } else if (Instant.now().toEpochMilli() - jump.getJumpStartMs() < MAX_JUMP_MS) { - entity.get(Forces.class).add(new Force(new Vec2f(0f, mass.getMass() * JUMP_ACC))); - } + if (actions.contains(KeyBinds.Action.JUMP)) { + final boolean applyForce = Instant.now().toEpochMilli() - jump.getJumpStartMs() < MAX_JUMP_MS; + if (jump.isCanJump()) { + newDy = JUMP_INITIAL_VEL; + jump.setCanJump(false); + jump.setJumpStartMs(Instant.now().toEpochMilli()); + } else if (applyForce) { + entity.get(Forces.class).add(new Force(new Vec2f(0f, mass.getMass() * JUMP_ACC))); } } diff --git a/core/src/main/java/coffee/liz/dyl/systems/RenderSystem.java b/core/src/main/java/coffee/liz/dyl/systems/RenderSystem.java index 15e1f30..1624d0f 100644 --- a/core/src/main/java/coffee/liz/dyl/systems/RenderSystem.java +++ b/core/src/main/java/coffee/liz/dyl/systems/RenderSystem.java @@ -1,8 +1,7 @@ package coffee.liz.dyl.systems; import coffee.liz.dyl.components.graphic.Graphic; -import coffee.liz.ecs.common.components.physics.BoundingBox; -import coffee.liz.ecs.common.systems.physics.CollisionSystem; +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; @@ -27,7 +26,7 @@ public class RenderSystem implements System { @Override public Collection<Class<? extends System>> getDependencies() { - return Set.of(CollisionSystem.class); + return Set.of(AnimationSystem.class); } @Override 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 new file mode 100644 index 0000000..fb14b70 --- /dev/null +++ b/core/src/main/java/coffee/liz/dyl/systems/physics/CollisionGrid.java @@ -0,0 +1,63 @@ +package coffee.liz.dyl.systems.physics; + +import coffee.liz.dyl.components.physics.BoundingBox; +import coffee.liz.ecs.math.Vec2; +import coffee.liz.ecs.math.Vec2i; +import coffee.liz.ecs.model.Entity; +import lombok.Getter; +import lombok.Setter; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +@Setter +@Getter +public class CollisionGrid { + private final Map<Vec2i, Set<Entity>> cells = new HashMap<>(); + private Vec2<Float> origin; + private Vec2<Float> cellSize; + + public void clear() { + cells.clear(); + } + + public void insert(final Entity entity) { + final BoundingBox bb = entity.get(BoundingBox.class); + final int minCx = cellX(bb.getPosition().getX()); + final int minCy = cellY(bb.getPosition().getY()); + final int maxCx = cellX(bb.getRight()); + final int maxCy = cellY(bb.getTop()); + for (int cx = minCx; cx <= maxCx; cx++) { + for (int cy = minCy; cy <= maxCy; cy++) { + cells.computeIfAbsent(new Vec2i(cx, cy), _ -> new HashSet<>()).add(entity); + } + } + } + + public Set<Entity> getNeighbors(final BoundingBox bb) { + final Set<Entity> neighbors = new HashSet<>(); + final int minCx = cellX(bb.getPosition().getX()); + final int minCy = cellY(bb.getPosition().getY()); + final int maxCx = cellX(bb.getRight()); + final int maxCy = cellY(bb.getTop()); + for (int cx = minCx; cx <= maxCx; cx++) { + for (int cy = minCy; cy <= maxCy; cy++) { + final Set<Entity> cell = cells.get(new Vec2i(cx, cy)); + if (cell != null) { + neighbors.addAll(cell); + } + } + } + return neighbors; + } + + private int cellX(final float x) { + return (int) Math.floor((x - origin.getX()) / cellSize.getX()); + } + + private int cellY(final float y) { + return (int) Math.floor((y - origin.getY()) / cellSize.getY()); + } +} 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 new file mode 100644 index 0000000..8cf7a59 --- /dev/null +++ b/core/src/main/java/coffee/liz/dyl/systems/physics/CollisionSystem.java @@ -0,0 +1,125 @@ +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.Jump; +import coffee.liz.dyl.components.physics.Solid; +import coffee.liz.dyl.components.physics.Velocity; +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 lombok.RequiredArgsConstructor; + +import java.util.Collection; +import java.util.Set; + +@RequiredArgsConstructor +public class CollisionSystem implements System { + private float cellSize = 3f; + private final CollisionGrid grid = new CollisionGrid(); + + @Override + public Collection<Class<? extends System>> getDependencies() { + return Set.of(IntegrationSystem.class); + } + + @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; + } + + grid.clear(); + final Vec2<Float> origin = getOrigin(allBoxEntities.stream() + .map(e -> e.get(BoundingBox.class).getPosition()) + .toList()); + grid.setOrigin(origin); + grid.setCellSize(new Vec2f(cellSize, cellSize)); + for (final Entity entity : allBoxEntities) { + grid.insert(entity); + } + + for (final Entity me : world.queryable().allOf(BoundingBox.class, Velocity.class)) { + final BoundingBox meBox = me.get(BoundingBox.class); + for (final Entity them : grid.getNeighbors(meBox)) { + if (me.equals(them)) { + continue; + } + if (!meBox.isCollidingWith(them.get(BoundingBox.class))) { + continue; + } + + contacts(me).add(them); + contacts(them).add(me); + + if (them.has(Solid.class)) { + resolveCollision(me, them); + } + } + } + } + + 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); + 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 + )); + } + + // Landing on top of a solid restores jump ability + if (me.has(Jump.class) && ny > 0f) { + me.get(Jump.class).setCanJump(true); + } + } + + private Vec2<Float> getPenetrationVector(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); + } + + 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); + } +} diff --git a/core/src/main/java/coffee/liz/dyl/systems/physics/ForceReductionSystem.java b/core/src/main/java/coffee/liz/dyl/systems/physics/ForceReductionSystem.java new file mode 100644 index 0000000..944ad2b --- /dev/null +++ b/core/src/main/java/coffee/liz/dyl/systems/physics/ForceReductionSystem.java @@ -0,0 +1,56 @@ +package coffee.liz.dyl.systems.physics; + +import coffee.liz.dyl.components.physics.Force; +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.dyl.systems.InputSystem; +import coffee.liz.ecs.math.Vec2; +import coffee.liz.ecs.math.Vec2f; +import coffee.liz.ecs.model.System; +import coffee.liz.ecs.model.World; +import lombok.RequiredArgsConstructor; + +import java.util.Collection; +import java.util.Set; + +@RequiredArgsConstructor +public class ForceReductionSystem implements System { + private final float gravity; + + @Override + public Collection<Class<? extends System>> getDependencies() { + return Set.of(InputSystem.class); + } + + @Override + public void update(final World world, final float deltaSeconds) { + world.queryable().allOf(Forces.class, Mass.class, Velocity.class).forEach(entity -> { + final Forces forces = entity.get(Forces.class); + final Mass mass = entity.get(Mass.class); + final Velocity velocity = entity.get(Velocity.class); + + if (entity.has(Gravity.class)) { + final Gravity gravityComponent = entity.get(Gravity.class); + if (velocity.getVelocity().getY() > -gravityComponent.getTerminalVelocity()) { + forces.add(new Force(new Vec2f(0f, -mass.getMass() * gravity))); + } + } + + Vec2<Float> netForce = Vec2f.ZERO; + for (final Force f : forces.getForces()) { + netForce = netForce.plus(f.getForce()); + } + forces.clear(); + + final Vec2<Float> acceleration = netForce.scale(1f / mass.getMass(), 1f / mass.getMass()); + velocity.setVelocity(velocity.getVelocity().plus(acceleration.scale(deltaSeconds, deltaSeconds))); + + if (entity.has(Jump.class) && acceleration.getY() < 0) { + entity.get(Jump.class).setCanJump(false); + } + }); + } +} diff --git a/core/src/main/java/coffee/liz/dyl/systems/physics/IntegrationSystem.java b/core/src/main/java/coffee/liz/dyl/systems/physics/IntegrationSystem.java new file mode 100644 index 0000000..4de4dd7 --- /dev/null +++ b/core/src/main/java/coffee/liz/dyl/systems/physics/IntegrationSystem.java @@ -0,0 +1,25 @@ +package coffee.liz.dyl.systems.physics; + +import coffee.liz.dyl.components.physics.BoundingBox; +import coffee.liz.dyl.components.physics.Velocity; +import coffee.liz.ecs.model.System; +import coffee.liz.ecs.model.World; + +import java.util.Collection; +import java.util.Set; + +public class IntegrationSystem implements System { + @Override + public Collection<Class<? extends System>> getDependencies() { + return Set.of(ForceReductionSystem.class); + } + + @Override + public void update(final World world, final float deltaSeconds) { + world.queryable().allOf(Velocity.class, BoundingBox.class).forEach(entity -> { + final BoundingBox bb = entity.get(BoundingBox.class); + final Velocity velocity = entity.get(Velocity.class); + bb.setPosition(bb.getPosition().plus(velocity.getVelocity().scale(deltaSeconds, deltaSeconds))); + }); + } +} 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 7f1cb62..b1376df 100644 --- a/core/src/main/java/coffee/liz/dyl/world/DylGameWorld.java +++ b/core/src/main/java/coffee/liz/dyl/world/DylGameWorld.java @@ -3,14 +3,16 @@ package coffee.liz.dyl.world; import coffee.liz.dyl.DylGame; import coffee.liz.dyl.config.PhysicsConstants; 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.RenderSystem; import coffee.liz.ecs.DAGWorld; -import coffee.liz.ecs.common.components.physics.BoundingBox; -import coffee.liz.ecs.common.components.physics.TopCollidable; -import coffee.liz.ecs.common.systems.physics.CollisionSystem; -import coffee.liz.ecs.common.systems.physics.ForceReductionSystem; -import coffee.liz.ecs.common.systems.physics.IntegrationSystem; +import coffee.liz.dyl.components.physics.BoundingBox; +import coffee.liz.dyl.components.physics.Solid; +import coffee.liz.dyl.systems.physics.CollisionSystem; +import coffee.liz.dyl.systems.physics.ForceReductionSystem; +import coffee.liz.dyl.systems.physics.IntegrationSystem; import coffee.liz.ecs.math.Vec2f; import com.badlogic.gdx.Gdx; @@ -20,12 +22,14 @@ public class DylGameWorld extends DAGWorld { new InputSystem(() -> game.getSettings().getKeyBinds().filterActiveActions(Gdx.input::isKeyPressed)), new ForceReductionSystem(PhysicsConstants.GRAVITY), new IntegrationSystem(), - new CollisionSystem(PhysicsConstants.GRAVITY), + new CollisionSystem(), + new DamageSystem(), + new AnimationSystem(), new RenderSystem(game.getBatch(), game.getViewport()) ); PlayerFactory.addTo(this); createEntity() .add(new BoundingBox(new Vec2f(-50f, -1f), new Vec2f(200f, 1f))) - .add(new TopCollidable()); + .add(new Solid()); } } |
