diff options
Diffstat (limited to 'core/src/main/java/coffee')
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; } |
