aboutsummaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/src/main/java/coffee/liz/abstractionengine/app/AbstractionEngineGame.java11
-rw-r--r--core/src/main/java/coffee/liz/abstractionengine/app/Theme.java28
-rw-r--r--core/src/main/java/coffee/liz/abstractionengine/app/actor/BlockyButton.java110
-rw-r--r--core/src/main/java/coffee/liz/abstractionengine/app/actor/LifeGridActor.java102
-rw-r--r--core/src/main/java/coffee/liz/abstractionengine/app/actor/Logo.java29
-rw-r--r--core/src/main/java/coffee/liz/abstractionengine/app/actor/Penguin.java81
-rw-r--r--core/src/main/java/coffee/liz/abstractionengine/app/life/CellState.java8
-rw-r--r--core/src/main/java/coffee/liz/abstractionengine/app/life/LifeSystem.java64
-rw-r--r--core/src/main/java/coffee/liz/abstractionengine/app/screen/GameScreen.java45
-rw-r--r--core/src/main/java/coffee/liz/abstractionengine/app/screen/Logo.java53
-rw-r--r--core/src/main/java/coffee/liz/abstractionengine/app/screen/MainMenu.java165
-rw-r--r--core/src/main/java/coffee/liz/abstractionengine/app/screen/ScrollLogo.java118
-rw-r--r--core/src/main/java/coffee/liz/abstractionengine/app/utils/TimerUtils.java17
-rw-r--r--core/src/main/java/coffee/liz/ecs/math/Vec2.java23
-rw-r--r--core/src/main/java/coffee/liz/ecs/math/Vec2f.java7
-rw-r--r--core/src/main/java/coffee/liz/ecs/math/Vec2i.java8
16 files changed, 677 insertions, 192 deletions
diff --git a/core/src/main/java/coffee/liz/abstractionengine/app/AbstractionEngineGame.java b/core/src/main/java/coffee/liz/abstractionengine/app/AbstractionEngineGame.java
index a6cd6e9..e379031 100644
--- a/core/src/main/java/coffee/liz/abstractionengine/app/AbstractionEngineGame.java
+++ b/core/src/main/java/coffee/liz/abstractionengine/app/AbstractionEngineGame.java
@@ -1,6 +1,6 @@
package coffee.liz.abstractionengine.app;
-import coffee.liz.abstractionengine.app.screen.Logo;
+import coffee.liz.abstractionengine.app.screen.ScrollLogo;
import coffee.liz.ecs.math.Vec2;
import coffee.liz.ecs.math.Vec2f;
import com.badlogic.gdx.Game;
@@ -25,9 +25,9 @@ public class AbstractionEngineGame extends Game {
viewport = new FitViewport(WORLD_SIZE.getX(), WORLD_SIZE.getY());
batch = new SpriteBatch();
shapeRenderer = new ShapeRenderer();
- font = initFont(24);
+ font = initFont(24, "fonts/JetBrainsMonoNerdFont-Regular.ttf");
- this.setScreen(new Logo(this));
+ this.setScreen(new ScrollLogo(this));
viewport.update(Gdx.graphics.getWidth(), Gdx.graphics.getHeight(), true);
}
@@ -42,9 +42,8 @@ public class AbstractionEngineGame extends Game {
shapeRenderer.dispose();
}
- private BitmapFont initFont(final int size) {
- final FreeTypeFontGenerator gen = new FreeTypeFontGenerator(
- Gdx.files.internal("fonts/JetBrainsMonoNerdFont-Regular.ttf"));
+ private BitmapFont initFont(final int size, final String path) {
+ final FreeTypeFontGenerator gen = new FreeTypeFontGenerator(Gdx.files.internal(path));
final FreeTypeFontGenerator.FreeTypeFontParameter params = new FreeTypeFontGenerator.FreeTypeFontParameter();
params.characters = RENDERABLE_CHARS;
params.size = size;
diff --git a/core/src/main/java/coffee/liz/abstractionengine/app/Theme.java b/core/src/main/java/coffee/liz/abstractionengine/app/Theme.java
new file mode 100644
index 0000000..ae2a159
--- /dev/null
+++ b/core/src/main/java/coffee/liz/abstractionengine/app/Theme.java
@@ -0,0 +1,28 @@
+package coffee.liz.abstractionengine.app;
+
+import com.badlogic.gdx.graphics.Color;
+
+public final class Theme {
+ public static final Color BG = Color.valueOf("2d2523");
+ public static final Color BG_PATTERN = Color.valueOf("241e1c");
+ public static final Color SURFACE = Color.valueOf("3b312f");
+ public static final Color SURFACE_ALT = Color.valueOf("463a37");
+
+ public static final Color FG = Color.valueOf("f9efea");
+ public static final Color MUTED = Color.valueOf("cbb8b1");
+
+ public static final Color BORDER = Color.valueOf("f1e6e0");
+ public static final Color BORDER_LIGHT = Color.valueOf("6e5b57");
+ public static final Color BORDER_DARK = Color.valueOf("120d0c");
+
+ public static final Color PRIMARY = Color.valueOf("f06aa6");
+ public static final Color PRIMARY_LIGHT = Color.valueOf("ff97c8");
+ public static final Color PRIMARY_DARK = Color.valueOf("d94b8e");
+
+ public static final Color SECONDARY = Color.valueOf("b69cff");
+ public static final Color SECONDARY_LIGHT = Color.valueOf("d7c8ff");
+ public static final Color SECONDARY_DARK = Color.valueOf("8f78d6");
+
+ private Theme() {
+ }
+}
diff --git a/core/src/main/java/coffee/liz/abstractionengine/app/actor/BlockyButton.java b/core/src/main/java/coffee/liz/abstractionengine/app/actor/BlockyButton.java
new file mode 100644
index 0000000..7fdc9ae
--- /dev/null
+++ b/core/src/main/java/coffee/liz/abstractionengine/app/actor/BlockyButton.java
@@ -0,0 +1,110 @@
+package coffee.liz.abstractionengine.app.actor;
+
+import coffee.liz.abstractionengine.app.Theme;
+import com.badlogic.gdx.graphics.Color;
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.g2d.BitmapFont;
+import com.badlogic.gdx.graphics.g2d.GlyphLayout;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+import com.badlogic.gdx.scenes.scene2d.Actor;
+import com.badlogic.gdx.scenes.scene2d.InputEvent;
+import com.badlogic.gdx.scenes.scene2d.InputListener;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+
+@RequiredArgsConstructor
+public class BlockyButton extends Actor {
+ private static final float ALPHA = 0.85f;
+ private static final float BORDER_WIDTH = 1f;
+
+ private final ShapeRenderer shapeRenderer;
+ private final BitmapFont font;
+ private final String text;
+ @Setter
+ private Runnable onClick;
+
+ @Getter
+ private boolean hovered = false;
+ @Getter
+ private boolean pressed = false;
+
+ private final GlyphLayout layout = new GlyphLayout();
+ private final Color bgColor = new Color();
+ private final Color borderColor = new Color();
+
+ @Override
+ public void draw(final Batch batch, final float parentAlpha) {
+ batch.end();
+
+ shapeRenderer.setProjectionMatrix(batch.getProjectionMatrix());
+ shapeRenderer.setTransformMatrix(batch.getTransformMatrix());
+
+ final float effectiveAlpha = parentAlpha * ALPHA * getColor().a;
+ computeColors(effectiveAlpha);
+
+ shapeRenderer.begin(ShapeRenderer.ShapeType.Filled);
+ shapeRenderer.setColor(borderColor);
+ shapeRenderer.rect(getX(), getY(), getWidth(), getHeight());
+ shapeRenderer.setColor(bgColor);
+ shapeRenderer.rect(getX() + BORDER_WIDTH, getY() + BORDER_WIDTH, getWidth() - BORDER_WIDTH * 2,
+ getHeight() - BORDER_WIDTH * 2);
+ shapeRenderer.end();
+
+ batch.begin();
+
+ layout.setText(font, text);
+ final float textX = getX() + (getWidth() - layout.width) / 2f;
+ final float textY = getY() + (getHeight() + layout.height) / 2f;
+
+ final Color textColor = hovered ? Theme.FG : Theme.MUTED;
+ font.setColor(textColor.r, textColor.g, textColor.b, effectiveAlpha);
+ font.draw(batch, text, textX, textY);
+ }
+
+ private void computeColors(final float alpha) {
+ if (pressed) {
+ bgColor.set(Theme.SURFACE_ALT).a = alpha;
+ borderColor.set(Theme.PRIMARY_DARK).a = alpha;
+ } else if (hovered) {
+ bgColor.set(Theme.SURFACE_ALT).a = alpha;
+ borderColor.set(Theme.PRIMARY).a = alpha;
+ } else {
+ bgColor.set(Theme.SURFACE).a = alpha;
+ borderColor.set(Theme.BORDER_LIGHT).a = alpha;
+ }
+ }
+
+ public void addButtonListener() {
+ addListener(new InputListener() {
+ @Override
+ public boolean touchDown(final InputEvent event, final float x, final float y, final int pointer,
+ final int button) {
+ pressed = true;
+ return true;
+ }
+
+ @Override
+ public void touchUp(final InputEvent event, final float x, final float y, final int pointer,
+ final int button) {
+ if (pressed && hovered && onClick != null) {
+ onClick.run();
+ }
+ pressed = false;
+ }
+
+ @Override
+ public void enter(final InputEvent event, final float x, final float y, final int pointer,
+ final Actor fromActor) {
+ hovered = true;
+ }
+
+ @Override
+ public void exit(final InputEvent event, final float x, final float y, final int pointer,
+ final Actor toActor) {
+ hovered = false;
+ pressed = false;
+ }
+ });
+ }
+}
diff --git a/core/src/main/java/coffee/liz/abstractionengine/app/actor/LifeGridActor.java b/core/src/main/java/coffee/liz/abstractionengine/app/actor/LifeGridActor.java
new file mode 100644
index 0000000..d61ce5e
--- /dev/null
+++ b/core/src/main/java/coffee/liz/abstractionengine/app/actor/LifeGridActor.java
@@ -0,0 +1,102 @@
+package coffee.liz.abstractionengine.app.actor;
+
+import coffee.liz.abstractionengine.app.Theme;
+import coffee.liz.abstractionengine.app.life.CellState;
+import coffee.liz.abstractionengine.app.life.LifeInput;
+import coffee.liz.abstractionengine.grid.component.GridPosition;
+import coffee.liz.ecs.math.Vec2;
+import coffee.liz.ecs.math.Vec2f;
+import coffee.liz.ecs.math.Vec2i;
+import coffee.liz.ecs.model.World;
+import com.badlogic.gdx.Gdx;
+import com.badlogic.gdx.graphics.Color;
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
+import com.badlogic.gdx.math.Vector3;
+import com.badlogic.gdx.scenes.scene2d.Actor;
+import com.badlogic.gdx.utils.viewport.Viewport;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+
+import java.time.Duration;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static com.badlogic.gdx.math.MathUtils.clamp;
+
+@RequiredArgsConstructor
+public class LifeGridActor extends Actor {
+ private final ShapeRenderer shapeRenderer;
+ private final World<LifeInput> world;
+ private final Vec2<Integer> gridDimensions;
+ private final Color cellColor = new Color();
+ @Setter
+ private Viewport viewport;
+
+ @Override
+ public void act(final float delta) {
+ super.act(delta);
+ if (viewport == null) {
+ return;
+ }
+ final Vec2<Float> cellSize = computeCellSize();
+ final Set<Vec2<Integer>> forcedAlive = computeForcedAlive(cellSize);
+ world.update(new LifeInput(forcedAlive), Duration.ofMillis((int) (delta * 1000)));
+ }
+
+ @Override
+ public void draw(final Batch batch, final float parentAlpha) {
+ if (viewport == null) {
+ return;
+ }
+ batch.end();
+
+ shapeRenderer.setProjectionMatrix(batch.getProjectionMatrix());
+ shapeRenderer.setTransformMatrix(batch.getTransformMatrix());
+ shapeRenderer.begin(ShapeRenderer.ShapeType.Filled);
+ drawCells();
+ shapeRenderer.end();
+
+ batch.begin();
+ }
+
+ private Vec2<Float> computeCellSize() {
+ return Vec2f.builder().x(viewport.getWorldWidth() / gridDimensions.getX())
+ .y(viewport.getWorldHeight() / gridDimensions.getY()).build();
+ }
+
+ private Set<Vec2<Integer>> computeForcedAlive(final Vec2<Float> cellSize) {
+ final Vector3 cursorPosition = viewport.unproject(new Vector3(Gdx.input.getX(), Gdx.input.getY(), 0));
+ final Vec2<Integer> gridPosition = Vec2f.builder().x(cursorPosition.x).y(cursorPosition.y).build()
+ .transform(x -> clamp(x, 0.0f, viewport.getWorldWidth() - 0.001f),
+ y -> clamp(y, 0.0f, viewport.getWorldHeight() - 0.001f))
+ .scale(cellSize.transform(x -> 1 / x, y -> 1 / y)).intValue();
+ return Stream.of(gridPosition)
+ .flatMap(pos -> Stream.of(Vec2i.ZERO, Vec2i.EAST, Vec2i.WEST, Vec2i.NORTH, Vec2i.SOUTH).map(pos::plus))
+ .collect(Collectors.toSet());
+ }
+
+ private void drawCells() {
+ final Vec2<Float> cellSize = computeCellSize();
+ world.query(Set.of(GridPosition.class, CellState.class)).forEach(entity -> {
+ final CellState state = entity.get(CellState.class);
+ final Vec2<Integer> gridPos = entity.get(GridPosition.class).getPosition();
+ final Vec2<Float> worldPos = Vec2f.builder().x(gridPos.getX() * cellSize.getX())
+ .y(gridPos.getY() * cellSize.getY()).build();
+
+ final float alivePercentage = state.getAlivePercentage();
+ if (alivePercentage <= 0.0f) {
+ return;
+ }
+ shapeRenderer.setColor(interpolateColor(alivePercentage));
+ shapeRenderer.rect(worldPos.getX(), worldPos.getY(), cellSize.getX(), cellSize.getY());
+ });
+ }
+
+ private Color interpolateColor(final float alivePercentage) {
+ final float interpolation = alivePercentage * alivePercentage * alivePercentage;
+ return cellColor.set(Theme.BG).lerp(Theme.FG, interpolation);
+ }
+}
diff --git a/core/src/main/java/coffee/liz/abstractionengine/app/actor/Logo.java b/core/src/main/java/coffee/liz/abstractionengine/app/actor/Logo.java
new file mode 100644
index 0000000..41847b5
--- /dev/null
+++ b/core/src/main/java/coffee/liz/abstractionengine/app/actor/Logo.java
@@ -0,0 +1,29 @@
+package coffee.liz.abstractionengine.app.actor;
+
+import com.badlogic.gdx.Gdx;
+import com.badlogic.gdx.graphics.Color;
+import com.badlogic.gdx.graphics.Texture;
+import com.badlogic.gdx.graphics.g2d.Batch;
+import com.badlogic.gdx.scenes.scene2d.Actor;
+import com.badlogic.gdx.utils.Disposable;
+
+public class Logo extends Actor implements Disposable {
+ private final Texture texture;
+
+ public Logo() {
+ this.texture = new Texture(Gdx.files.internal("sprites/logo.png"));
+ setSize(texture.getWidth(), texture.getHeight());
+ }
+
+ @Override
+ public void draw(final Batch batch, final float parentAlpha) {
+ final Color c = getColor();
+ batch.setColor(c.r, c.g, c.b, c.a * parentAlpha);
+ batch.draw(texture, getX(), getY(), getWidth(), getHeight());
+ }
+
+ @Override
+ public void dispose() {
+ texture.dispose();
+ }
+}
diff --git a/core/src/main/java/coffee/liz/abstractionengine/app/actor/Penguin.java b/core/src/main/java/coffee/liz/abstractionengine/app/actor/Penguin.java
new file mode 100644
index 0000000..09b3d5b
--- /dev/null
+++ b/core/src/main/java/coffee/liz/abstractionengine/app/actor/Penguin.java
@@ -0,0 +1,81 @@
+package coffee.liz.abstractionengine.app.actor;
+
+import coffee.liz.ecs.math.Vec2;
+import coffee.liz.ecs.math.Vec2i;
+import com.badlogic.gdx.Gdx;
+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.Batch;
+import com.badlogic.gdx.graphics.g2d.TextureRegion;
+import com.badlogic.gdx.scenes.scene2d.Actor;
+import com.badlogic.gdx.utils.Disposable;
+import lombok.Getter;
+
+public class Penguin extends Actor implements Disposable {
+ private static final Vec2<Integer> PENGUIN_FRAMES = Vec2i.builder().x(8).y(4).build();
+
+ private final Texture texture;
+ @Getter
+ private final Animation<TextureRegion> tobaganning;
+ @Getter
+ private final Animation<TextureRegion> eepy;
+ @Getter
+ private final Animation<TextureRegion> eeping;
+ private Animation<TextureRegion> currentAnimation;
+ private float stateTime = 0f;
+
+ public Penguin() {
+ this.texture = new Texture(Gdx.files.internal("sprites/penguins.png"));
+ final TextureRegion[][] tmp = TextureRegion.split(texture, texture.getWidth() / PENGUIN_FRAMES.getX(),
+ texture.getHeight() / PENGUIN_FRAMES.getY());
+
+ final TextureRegion[] tobaggonTextureRegion = new TextureRegion[2];
+ tobaggonTextureRegion[0] = tmp[2][7];
+ tobaggonTextureRegion[1] = tmp[3][6];
+
+ final TextureRegion[] eepyTextureRegion = new TextureRegion[2];
+ eepyTextureRegion[0] = tmp[2][3];
+ eepyTextureRegion[1] = tmp[3][3];
+
+ final TextureRegion[] eepingTextureRegion = new TextureRegion[2];
+ eepingTextureRegion[0] = tmp[0][2];
+ eepingTextureRegion[1] = tmp[1][2];
+
+ this.tobaganning = new Animation<>(0.25f, tobaggonTextureRegion);
+ this.eepy = new Animation<>(0.45f, eepyTextureRegion);
+ this.eeping = new Animation<>(0.45f, eepingTextureRegion);
+ this.currentAnimation = tobaganning;
+ }
+
+ @Override
+ public void act(final float delta) {
+ super.act(delta);
+ stateTime += delta;
+ }
+
+ @Override
+ public void draw(final Batch batch, final float parentAlpha) {
+ final TextureRegion frame = currentAnimation.getKeyFrame(stateTime, true);
+ final Color c = getColor();
+ batch.setColor(c.r, c.g, c.b, c.a * parentAlpha);
+ batch.draw(frame, getX(), getY(), getWidth(), getHeight());
+ }
+
+ public void setState(final State state) {
+ this.currentAnimation = switch (state) {
+ case EEPY -> eepy;
+ case EEPING -> eeping;
+ case TOBAGGON -> tobaganning;
+ };
+ }
+
+ @Override
+ public void dispose() {
+ texture.dispose();
+ }
+
+ public enum State {
+ EEPY, EEPING, TOBAGGON
+ }
+}
diff --git a/core/src/main/java/coffee/liz/abstractionengine/app/life/CellState.java b/core/src/main/java/coffee/liz/abstractionengine/app/life/CellState.java
index 94cfdd8..f25ad7c 100644
--- a/core/src/main/java/coffee/liz/abstractionengine/app/life/CellState.java
+++ b/core/src/main/java/coffee/liz/abstractionengine/app/life/CellState.java
@@ -10,14 +10,14 @@ public class CellState implements Component {
public static final CellState LIVE = new CellState(1.0f);
public static final CellState DEAD = new CellState(0.0f);
- private final float decay;
+ private final float alivePercentage;
- public CellState(final float decay) {
- this.decay = clamp(decay);
+ public CellState(final float alivePercentage) {
+ this.alivePercentage = clamp(alivePercentage);
}
public boolean isAlive() {
- return decay >= (1.0f - EPS);
+ return alivePercentage >= (1.0f - EPS);
}
private static float clamp(final float value) {
diff --git a/core/src/main/java/coffee/liz/abstractionengine/app/life/LifeSystem.java b/core/src/main/java/coffee/liz/abstractionengine/app/life/LifeSystem.java
index 803fe58..82aa9c6 100644
--- a/core/src/main/java/coffee/liz/abstractionengine/app/life/LifeSystem.java
+++ b/core/src/main/java/coffee/liz/abstractionengine/app/life/LifeSystem.java
@@ -1,6 +1,5 @@
package coffee.liz.abstractionengine.app.life;
-import coffee.liz.abstractionengine.grid.component.GridPosition;
import coffee.liz.abstractionengine.grid.system.BaseGridIndexSystem;
import coffee.liz.ecs.math.Mat2;
import coffee.liz.ecs.math.Vec2;
@@ -28,6 +27,37 @@ public class LifeSystem extends BaseGridIndexSystem<LifeInput> {
@Override
public void update(final World<LifeInput> world, final LifeInput state, final Duration dt) {
super.update(world, state, dt);
+
+ sinceUpdate = sinceUpdate.plus(dt);
+ if (BETWEEN_UPDATES.compareTo(sinceUpdate) <= 0) {
+ Mat2.convolve(this.rows, CONVOLUTION_RADIUS, () -> 0, (entities, rel, prev) -> {
+ if (rel.equals(Vec2i.ZERO)) {
+ return prev;
+ }
+ return entities.stream().findFirst().map(entity -> entity.get(CellState.class))
+ .map(cellState -> prev + (cellState.isAlive() ? 1 : 0)).orElse(prev);
+ }, (entities, neighboringAliveCells) -> entities.stream().findFirst().map(entity -> {
+ final CellState cellState = entity.get(CellState.class);
+ final float decay = cellState.getAlivePercentage();
+
+ final boolean alive = cellState.isAlive();
+ final boolean diesNow = alive && (neighboringAliveCells < 2 || neighboringAliveCells > 3);
+ final boolean spawnsNow = !alive && neighboringAliveCells == 3;
+ final boolean stillAlive = alive && !diesNow;
+
+ final CellState nextState;
+ if (spawnsNow || stillAlive) {
+ nextState = CellState.LIVE;
+ } else {
+ nextState = new CellState(decay - DECAY_STEP);
+ }
+
+ entity.add(nextState);
+ return entity;
+ }));
+ sinceUpdate = Duration.ZERO;
+ }
+
state.getForceAliveCells().forEach(cell -> {
if (!inBounds(cell)) {
return;
@@ -35,37 +65,5 @@ public class LifeSystem extends BaseGridIndexSystem<LifeInput> {
final Set<Entity> entities = rows.get(cell.getY()).get(cell.getX());
entities.forEach(e -> e.add(CellState.LIVE));
});
-
- sinceUpdate = sinceUpdate.plus(dt);
- if (sinceUpdate.compareTo(BETWEEN_UPDATES) < 0) {
- return;
- }
- sinceUpdate = Duration.ZERO;
-
- Mat2.convolve(this.rows, CONVOLUTION_RADIUS, () -> 0, (entities, rel, prev) -> {
- if (rel.equals(Vec2i.ZERO)) {
- return prev;
- }
- return entities.stream().findFirst().map(entity -> entity.get(CellState.class))
- .map(cellState -> prev + (cellState.isAlive() ? 1 : 0)).orElse(prev);
- }, (entities, neighboringAliveCells) -> entities.stream().findFirst().map(entity -> {
- final CellState cellState = entity.get(CellState.class);
- final float decay = cellState.getDecay();
-
- final boolean alive = cellState.isAlive();
- final boolean diesNow = alive && (neighboringAliveCells < 2 || neighboringAliveCells > 3);
- final boolean spawnsNow = !alive && neighboringAliveCells == 3;
- final boolean stillAlive = alive && !diesNow;
-
- final CellState nextState;
- if (spawnsNow || stillAlive) {
- nextState = CellState.LIVE;
- } else {
- nextState = new CellState(decay - DECAY_STEP);
- }
-
- entity.add(nextState);
- return entity;
- }));
}
}
diff --git a/core/src/main/java/coffee/liz/abstractionengine/app/screen/GameScreen.java b/core/src/main/java/coffee/liz/abstractionengine/app/screen/GameScreen.java
new file mode 100644
index 0000000..2a19a1a
--- /dev/null
+++ b/core/src/main/java/coffee/liz/abstractionengine/app/screen/GameScreen.java
@@ -0,0 +1,45 @@
+package coffee.liz.abstractionengine.app.screen;
+
+import coffee.liz.abstractionengine.app.AbstractionEngineGame;
+import com.badlogic.gdx.Screen;
+import lombok.RequiredArgsConstructor;
+
+@RequiredArgsConstructor
+public class GameScreen implements Screen {
+ private final AbstractionEngineGame game;
+
+ @Override
+ public void show() {
+
+ }
+
+ @Override
+ public void render(float delta) {
+
+ }
+
+ @Override
+ public void resize(int width, int height) {
+
+ }
+
+ @Override
+ public void pause() {
+
+ }
+
+ @Override
+ public void resume() {
+
+ }
+
+ @Override
+ public void hide() {
+
+ }
+
+ @Override
+ public void dispose() {
+
+ }
+}
diff --git a/core/src/main/java/coffee/liz/abstractionengine/app/screen/Logo.java b/core/src/main/java/coffee/liz/abstractionengine/app/screen/Logo.java
deleted file mode 100644
index 82b637a..0000000
--- a/core/src/main/java/coffee/liz/abstractionengine/app/screen/Logo.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package coffee.liz.abstractionengine.app.screen;
-
-import coffee.liz.abstractionengine.app.AbstractionEngineGame;
-import com.badlogic.gdx.Screen;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.log4j.Log4j2;
-
-@RequiredArgsConstructor
-@Log4j2
-public class Logo implements Screen {
- private final AbstractionEngineGame game;
- float secondsShown = 0;
-
- @Override
- public void show() {
- }
-
- @Override
- public void render(final float delta) {
- secondsShown += delta;
- if (secondsShown < 2f) {
- return;
- }
-
- log.info("Transition to main menu after {}", secondsShown);
- game.setScreen(new MainMenu(game));
- dispose();
- }
-
- @Override
- public void resize(int width, int height) {
-
- }
-
- @Override
- public void pause() {
-
- }
-
- @Override
- public void resume() {
- }
-
- @Override
- public void hide() {
-
- }
-
- @Override
- public void dispose() {
-
- }
-}
diff --git a/core/src/main/java/coffee/liz/abstractionengine/app/screen/MainMenu.java b/core/src/main/java/coffee/liz/abstractionengine/app/screen/MainMenu.java
index e02acf4..4f6923a 100644
--- a/core/src/main/java/coffee/liz/abstractionengine/app/screen/MainMenu.java
+++ b/core/src/main/java/coffee/liz/abstractionengine/app/screen/MainMenu.java
@@ -1,6 +1,9 @@
package coffee.liz.abstractionengine.app.screen;
import coffee.liz.abstractionengine.app.AbstractionEngineGame;
+import coffee.liz.abstractionengine.app.Theme;
+import coffee.liz.abstractionengine.app.actor.BlockyButton;
+import coffee.liz.abstractionengine.app.actor.LifeGridActor;
import coffee.liz.abstractionengine.app.life.CellState;
import coffee.liz.abstractionengine.app.life.LifeInput;
import coffee.liz.abstractionengine.app.life.LifeSystem;
@@ -8,125 +11,95 @@ import coffee.liz.abstractionengine.grid.component.GridPosition;
import coffee.liz.ecs.DAGWorld;
import coffee.liz.ecs.math.Mat2;
import coffee.liz.ecs.math.Vec2;
-import coffee.liz.ecs.math.Vec2f;
import coffee.liz.ecs.math.Vec2i;
import coffee.liz.ecs.model.World;
import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.Screen;
-import com.badlogic.gdx.graphics.Color;
-import com.badlogic.gdx.graphics.glutils.ShapeRenderer;
-import com.badlogic.gdx.math.Vector3;
+import com.badlogic.gdx.scenes.scene2d.Stage;
import com.badlogic.gdx.utils.ScreenUtils;
import lombok.RequiredArgsConstructor;
-import lombok.extern.log4j.Log4j2;
-import java.time.Duration;
-import java.util.HashSet;
import java.util.Map;
import java.util.Set;
-import java.util.stream.Stream;
-import static com.badlogic.gdx.math.MathUtils.clamp;
-
-@Log4j2
@RequiredArgsConstructor
public class MainMenu implements Screen {
- private static final Vec2<Integer> DIMENSIONS = Vec2i.builder().x(50).y(50).build();
- private final World<LifeInput> world = new DAGWorld<>(Map.of(LifeSystem.class, new LifeSystem(DIMENSIONS)));
- private final Color cellColor = new Color();
+ public static final Vec2<Integer> GRID_DIMENSIONS = Vec2i.builder().x(50).y(50).build();
+
+ private static final float BUTTON_WIDTH = 50f;
+ private static final float BUTTON_HEIGHT = 12f;
+ private static final float BUTTON_SPACING = 4f;
+ private final World<LifeInput> world = new DAGWorld<>(Map.of(LifeSystem.class, new LifeSystem(GRID_DIMENSIONS)));
private final AbstractionEngineGame game;
+ private final Set<Vec2<Integer>> initialAlive = createGliderPattern();
+
+ private Stage stage;
+ private LifeGridActor lifeGridActor;
+
@Override
public void show() {
- final Set<Vec2<Integer>> glider = Set.of(Vec2i.builder().x(1).y(0).build(), Vec2i.builder().x(2).y(1).build(),
- Vec2i.builder().x(0).y(2).build(), Vec2i.builder().x(1).y(2).build(),
- Vec2i.builder().x(2).y(2).build());
- Mat2.init(DIMENSIONS, pos -> world.createEntity().add(new GridPosition(pos))
- .add(glider.contains(pos) ? CellState.LIVE : CellState.DEAD));
+ Mat2.init(GRID_DIMENSIONS, pos -> world.createEntity().add(new GridPosition(pos))
+ .add(initialAlive.contains(pos) ? CellState.LIVE : CellState.DEAD));
+
+ stage = new Stage(game.viewport, game.batch);
+ lifeGridActor = new LifeGridActor(game.shapeRenderer, world, GRID_DIMENSIONS);
+ lifeGridActor.setViewport(game.viewport);
+ stage.addActor(lifeGridActor);
+
+ createButtons();
+
+ Gdx.input.setInputProcessor(stage);
}
- @Override
- public void render(final float delta) {
- game.viewport.apply();
+ private void createButtons() {
+ final float worldWidth = game.viewport.getWorldWidth();
+ final float worldHeight = game.viewport.getWorldHeight();
+ final float centerX = (worldWidth - BUTTON_WIDTH) / 2f;
+ final float startY = worldHeight / 2f + BUTTON_HEIGHT;
+
+ final BlockyButton playButton = createButton("Play", centerX, startY);
+ playButton.setOnClick(() -> {
+ game.setScreen(new GameScreen(game));
+ });
- final Vec2<Float> cellSize = Vec2f.builder().x(game.viewport.getWorldWidth() / DIMENSIONS.getX())
- .y(game.viewport.getWorldHeight() / DIMENSIONS.getY()).build();
- final Set<Vec2<Integer>> forcedAlive = computeForcedAlive(cellSize);
-
- world.update(new LifeInput(forcedAlive), Duration.ofMillis((int) (delta * 1000)));
- ScreenUtils.clear(Color.CLEAR);
-
- drawCells(cellSize);
-
- // game.batch.setProjectionMatrix(game.viewport.getCamera().combined);
- //
- // for (int i = 0; i < 200; i++) {
- // game.shapeRenderer.rect(i, i, 1, 1);
- // }
- // game.shapeRenderer.end();
- //
- //
- // game.batch.begin();
- // game.font.setColor(Color.WHITE);
- // game.font.draw(game.batch, "Welcome to Drop!!! ", 1, 50);
- // game.font.draw(game.batch, "λλλTap anywhere to begin!", 1, 100);
- // game.batch.end();
- //
- // // world.update(new LifeInput(List.of()), Duration.ofMillis((long)(delta *
- // // 1_000)));
- // //
- // // world.query(Set.of(GridPosition.class,
- // StepsSinceLive.class)).forEach(entity
- // // -> {
- // // final GridPosition gridPosition = entity.get(GridPosition.class);
- // // final StepsSinceLive stepsSinceLive = entity.get(StepsSinceLive.class);
- // //
- // //
- // // });
+ final BlockyButton optionsButton = createButton("Options", centerX, startY - BUTTON_HEIGHT - BUTTON_SPACING);
+ optionsButton.setOnClick(() -> {
+ });
+
+ final BlockyButton quitButton = createButton("Quit", centerX, startY - (BUTTON_HEIGHT + BUTTON_SPACING) * 2);
+ quitButton.setOnClick(Gdx.app::exit);
+
+ stage.addActor(playButton);
+ stage.addActor(optionsButton);
+ stage.addActor(quitButton);
}
- private Set<Vec2<Integer>> computeForcedAlive(final Vec2<Float> cellSize) {
- final Vector3 cursorPosition = game.viewport.unproject(new Vector3(Gdx.input.getX(), Gdx.input.getY(), 0));
- final Vec2<Float> cursorWorld = Vec2f.builder().x(cursorPosition.x).y(cursorPosition.y).build();
- final float clampedX = clamp(cursorWorld.getX(), 0.0f, game.viewport.getWorldWidth() - 0.001f);
- final float clampedY = clamp(cursorWorld.getY(), 0.0f, game.viewport.getWorldHeight() - 0.001f);
- final Vec2<Integer> gridPosition = Vec2i.builder().x((int) (clampedX / cellSize.getX()))
- .y((int) (clampedY / cellSize.getY())).build();
- final Set<Vec2<Integer>> forcedAlive = new HashSet<>();
- if (Gdx.input.isTouched()) {
- Stream.of(gridPosition).flatMap(
- pos -> Stream.of(Vec2i.ZERO, Vec2i.EAST, Vec2i.WEST, Vec2i.NORTH, Vec2i.SOUTH).map(pos::plus))
- .forEach(forcedAlive::add);
- }
- return forcedAlive;
+ private BlockyButton createButton(final String text, final float x, final float y) {
+ final BlockyButton button = new BlockyButton(game.shapeRenderer, game.font, text);
+ button.setPosition(x, y);
+ button.setSize(BUTTON_WIDTH, BUTTON_HEIGHT);
+ button.addButtonListener();
+ return button;
}
- private void drawCells(final Vec2<Float> cellSize) {
- game.shapeRenderer.begin(ShapeRenderer.ShapeType.Filled);
- game.shapeRenderer.setProjectionMatrix(game.viewport.getCamera().combined);
- world.query(Set.of(GridPosition.class, CellState.class)).forEach(entity -> {
- final CellState state = entity.get(CellState.class);
- final Vec2<Integer> gridPos = entity.get(GridPosition.class).getPosition();
- final Vec2<Float> worldPos = Vec2f.builder().x(gridPos.getX() * cellSize.getX())
- .y(gridPos.getY() * cellSize.getY()).build();
-
- final float decay = state.getDecay();
- if (decay <= 0.0f) {
- return;
- }
- game.shapeRenderer.setColor(interpolateColor(decay));
- game.shapeRenderer.rect(worldPos.getX(), worldPos.getY(), cellSize.getX(), cellSize.getY());
- });
- game.shapeRenderer.end();
+ private static Set<Vec2<Integer>> createGliderPattern() {
+ final int offsetX = 5;
+ final int offsetY = 40;
+ return Set.of(Vec2i.builder().x(offsetX + 1).y(offsetY + 2).build(),
+ Vec2i.builder().x(offsetX + 2).y(offsetY + 1).build(),
+ Vec2i.builder().x(offsetX + 0).y(offsetY + 0).build(),
+ Vec2i.builder().x(offsetX + 1).y(offsetY + 0).build(),
+ Vec2i.builder().x(offsetX + 2).y(offsetY + 0).build());
}
- private Color interpolateColor(final float decay) {
- final float bright = decay * decay * decay;
- final float r = 0.02f + (0.98f * bright);
- final float g = 0.02f + (0.98f * bright);
- final float b = 0.02f + (0.98f * bright);
- return cellColor.set(r, g, b, 1.0f);
+ @Override
+ public void render(final float delta) {
+ game.viewport.apply();
+ ScreenUtils.clear(Theme.BG);
+ stage.act(delta);
+ stage.draw();
}
@Override
@@ -136,21 +109,21 @@ public class MainMenu implements Screen {
@Override
public void pause() {
-
}
@Override
public void resume() {
-
}
@Override
public void hide() {
-
}
@Override
public void dispose() {
world.query(Set.of()).forEach(world::removeEntity);
+ if (stage != null) {
+ stage.dispose();
+ }
}
}
diff --git a/core/src/main/java/coffee/liz/abstractionengine/app/screen/ScrollLogo.java b/core/src/main/java/coffee/liz/abstractionengine/app/screen/ScrollLogo.java
new file mode 100644
index 0000000..cc54af3
--- /dev/null
+++ b/core/src/main/java/coffee/liz/abstractionengine/app/screen/ScrollLogo.java
@@ -0,0 +1,118 @@
+package coffee.liz.abstractionengine.app.screen;
+
+import coffee.liz.abstractionengine.app.AbstractionEngineGame;
+import coffee.liz.abstractionengine.app.Theme;
+import coffee.liz.abstractionengine.app.actor.Logo;
+import coffee.liz.abstractionengine.app.actor.Penguin;
+import com.badlogic.gdx.Screen;
+import com.badlogic.gdx.scenes.scene2d.Group;
+import com.badlogic.gdx.scenes.scene2d.Stage;
+import com.badlogic.gdx.scenes.scene2d.actions.Actions;
+import com.badlogic.gdx.utils.ScreenUtils;
+import lombok.RequiredArgsConstructor;
+
+import java.time.Duration;
+
+@RequiredArgsConstructor
+public class ScrollLogo implements Screen {
+ private static final Duration SHOW_FOR = Duration.ofSeconds(8);
+ private static final float LOGO_HEIGHT = 70f;
+ private static final float PENGUIN_SIZE = 20f;
+ private static final float SCROLL_PERCENT = 0.4f;
+ private static final float EEPY_PERCENT = 0.3f;
+ private static final float EEP_PERCENT = 0.10f;
+ private static final float FADE_PERCENT = 0.2f;
+
+ private final AbstractionEngineGame game;
+
+ private Stage stage;
+ private Logo logo;
+ private Penguin penguin;
+ private Group animationGroup;
+
+ @Override
+ public void show() {
+ stage = new Stage(game.viewport, game.batch);
+ logo = new Logo();
+ penguin = new Penguin();
+ animationGroup = new Group();
+
+ final float worldWidth = game.viewport.getWorldWidth();
+ final float worldHeight = game.viewport.getWorldHeight();
+ final float logoWidth = LOGO_HEIGHT * ((float) logo.getWidth() / logo.getHeight());
+
+ logo.setSize(logoWidth, LOGO_HEIGHT);
+ logo.setPosition(0, 0);
+
+ penguin.setSize(PENGUIN_SIZE, PENGUIN_SIZE);
+ penguin.setPosition((logoWidth - PENGUIN_SIZE) / 2f, -PENGUIN_SIZE - 6f);
+ penguin.setState(Penguin.State.TOBAGGON);
+
+ animationGroup.addActor(logo);
+ animationGroup.addActor(penguin);
+ animationGroup.setPosition((worldWidth - logoWidth) / 2f, worldHeight);
+
+ stage.addActor(animationGroup);
+
+ setupAnimations(worldWidth, worldHeight, logoWidth);
+ }
+
+ private void setupAnimations(final float worldWidth, final float worldHeight, final float logoWidth) {
+ final float totalSeconds = SHOW_FOR.toMillis() / 1000f;
+ final float scrollDuration = totalSeconds * SCROLL_PERCENT;
+ final float eepyDuration = totalSeconds * EEPY_PERCENT;
+ final float eepDuration = totalSeconds * EEP_PERCENT;
+ final float fadeDuration = totalSeconds * FADE_PERCENT;
+
+ final float targetY = (worldHeight - LOGO_HEIGHT) / 2f;
+
+ animationGroup
+ .addAction(Actions.sequence(Actions.moveTo((worldWidth - logoWidth) / 2f, targetY, scrollDuration),
+ Actions.run(() -> penguin.setState(Penguin.State.EEPY)), Actions.delay(eepyDuration),
+ Actions.run(() -> penguin.setState(Penguin.State.EEPING)), Actions.delay(eepDuration),
+ Actions.fadeOut(fadeDuration), Actions.run(this::requestTransition)));
+ }
+
+ @Override
+ public void render(final float delta) {
+ game.viewport.apply();
+ ScreenUtils.clear(Theme.BG);
+ stage.act(delta);
+ stage.draw();
+ }
+
+ @Override
+ public void resize(final int width, final int height) {
+ game.viewport.update(width, height, true);
+ }
+
+ @Override
+ public void pause() {
+ }
+
+ @Override
+ public void resume() {
+ }
+
+ @Override
+ public void hide() {
+ dispose();
+ }
+
+ @Override
+ public void dispose() {
+ if (logo != null) {
+ logo.dispose();
+ }
+ if (penguin != null) {
+ penguin.dispose();
+ }
+ if (stage != null) {
+ stage.dispose();
+ }
+ }
+
+ private void requestTransition() {
+ game.setScreen(new MainMenu(game));
+ }
+}
diff --git a/core/src/main/java/coffee/liz/abstractionengine/app/utils/TimerUtils.java b/core/src/main/java/coffee/liz/abstractionengine/app/utils/TimerUtils.java
new file mode 100644
index 0000000..d411ddc
--- /dev/null
+++ b/core/src/main/java/coffee/liz/abstractionengine/app/utils/TimerUtils.java
@@ -0,0 +1,17 @@
+package coffee.liz.abstractionengine.app.utils;
+
+import com.badlogic.gdx.utils.Timer;
+
+public final class TimerUtils {
+ private TimerUtils() {
+ }
+
+ public static Timer.Task sideEffectTask(final Runnable r) {
+ return new Timer.Task() {
+ @Override
+ public void run() {
+ r.run();
+ }
+ };
+ }
+}
diff --git a/core/src/main/java/coffee/liz/ecs/math/Vec2.java b/core/src/main/java/coffee/liz/ecs/math/Vec2.java
index ec7e531..7620395 100644
--- a/core/src/main/java/coffee/liz/ecs/math/Vec2.java
+++ b/core/src/main/java/coffee/liz/ecs/math/Vec2.java
@@ -1,5 +1,7 @@
package coffee.liz.ecs.math;
+import java.util.function.Function;
+
/**
* Cartesian vectors.
*
@@ -47,6 +49,17 @@ public interface Vec2<T> {
Vec2<T> scale(final T scaleX, final T scaleY);
/**
+ * Scales this vector by the given vector.
+ *
+ * @param scale
+ * scale vec.
+ * @return a new scaled vector
+ */
+ default Vec2<T> scale(final Vec2<T> scale) {
+ return scale(scale.getX(), scale.getY());
+ }
+
+ /**
* Length of the vector.
*
* @return length.
@@ -62,4 +75,14 @@ public interface Vec2<T> {
* @return Vec2<Float> components of {@link Vec2<T>}
*/
Vec2<Float> floatValue();
+
+ /**
+ * @param xTransform
+ * transform of x component.
+ * @param yTransform
+ * transform of y component.
+ * @return transformed vec applying {@param xTransform} to x component,
+ * {@param yTransform} to y component.
+ */
+ Vec2<T> transform(final Function<T, T> xTransform, final Function<T, T> yTransform);
}
diff --git a/core/src/main/java/coffee/liz/ecs/math/Vec2f.java b/core/src/main/java/coffee/liz/ecs/math/Vec2f.java
index 46f3fb8..5b91a2d 100644
--- a/core/src/main/java/coffee/liz/ecs/math/Vec2f.java
+++ b/core/src/main/java/coffee/liz/ecs/math/Vec2f.java
@@ -7,6 +7,8 @@ import lombok.Data;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
+import java.util.function.Function;
+
/** Float impl of {@link Vec2}. */
@Getter
@RequiredArgsConstructor
@@ -45,6 +47,11 @@ public final class Vec2f implements Vec2<Float> {
}
@Override
+ public Vec2<Float> transform(final Function<Float, Float> xTransform, final Function<Float, Float> yTransform) {
+ return new Vec2f(xTransform.apply(getX()), yTransform.apply(getY()));
+ }
+
+ @Override
public Vec2<Integer> intValue() {
return Vec2i.builder().x(this.x.intValue()).y(this.y.intValue()).build();
}
diff --git a/core/src/main/java/coffee/liz/ecs/math/Vec2i.java b/core/src/main/java/coffee/liz/ecs/math/Vec2i.java
index dbe246e..c7f9c83 100644
--- a/core/src/main/java/coffee/liz/ecs/math/Vec2i.java
+++ b/core/src/main/java/coffee/liz/ecs/math/Vec2i.java
@@ -7,6 +7,8 @@ import lombok.Data;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
+import java.util.function.Function;
+
/** Integer impl of {@link Vec2}. */
@Getter
@RequiredArgsConstructor
@@ -40,6 +42,12 @@ public final class Vec2i implements Vec2<Integer> {
}
@Override
+ public Vec2<Integer> transform(final Function<Integer, Integer> xTransform,
+ final Function<Integer, Integer> yTransform) {
+ return new Vec2i(xTransform.apply(getX()), yTransform.apply(getY()));
+ }
+
+ @Override
public Vec2<Integer> intValue() {
return this;
}