From d954aef0eb1217a03378a781345ed19a33cf53dc Mon Sep 17 00:00:00 2001 From: eldek Date: Tue, 24 Mar 2026 15:38:02 -0300 Subject: [PATCH] Implemented cameras Added basic cameras with panning --- core/src/main/java/io/github/eldek0/App.java | 2 + .../github/eldek0/asset/GameAssetManager.java | 12 +- .../java/io/github/eldek0/game/Camera.java | 234 +++++++++++++++++- .../java/io/github/eldek0/game/Office.java | 2 +- .../io/github/eldek0/screen/GameScene.java | 10 +- .../main/java/io/github/eldek0/ui/Button.java | 8 + .../main/java/io/github/eldek0/ui/HUD.java | 13 + gradle/gradle-daemon-jvm.properties | 21 +- 8 files changed, 281 insertions(+), 21 deletions(-) diff --git a/core/src/main/java/io/github/eldek0/App.java b/core/src/main/java/io/github/eldek0/App.java index 9d99b7c..1fe4783 100644 --- a/core/src/main/java/io/github/eldek0/App.java +++ b/core/src/main/java/io/github/eldek0/App.java @@ -1,6 +1,7 @@ package io.github.eldek0; import com.badlogic.gdx.Game; +import com.badlogic.gdx.Gdx; import com.badlogic.gdx.graphics.OrthographicCamera; import com.badlogic.gdx.math.Vector2; import com.badlogic.gdx.math.Vector3; @@ -41,6 +42,7 @@ public class App extends Game { @Override public void render() { super.render(); + Gdx.graphics.setTitle(Gdx.graphics.getFramesPerSecond() + " FPS"); } diff --git a/core/src/main/java/io/github/eldek0/asset/GameAssetManager.java b/core/src/main/java/io/github/eldek0/asset/GameAssetManager.java index 47f5a61..97e45d8 100644 --- a/core/src/main/java/io/github/eldek0/asset/GameAssetManager.java +++ b/core/src/main/java/io/github/eldek0/asset/GameAssetManager.java @@ -59,12 +59,12 @@ public class GameAssetManager { // ========================================================= // PATHS — Cameras (utils) // ========================================================= - private static final String CAM_PATH = "sprites/cameras/"; - private static final String CAM_UTILS_PATH = CAM_PATH + "utils/"; - private static final String CAM_LABELS_PATH = CAM_PATH + "Labels/"; - private static final String CAM_STATIC_PATH = CAM_PATH + "static/"; - private static final String CAM_LOC_PATH = CAM_PATH + "locations/"; - private static final String CAM_MANGLE_PATH = CAM_PATH + "mangle/"; + public static final String CAM_PATH = "sprites/cameras/"; + public static final String CAM_UTILS_PATH = CAM_PATH + "utils/"; + public static final String CAM_LABELS_PATH = CAM_PATH + "Labels/"; + public static final String CAM_STATIC_PATH = CAM_PATH + "static/"; + public static final String CAM_LOC_PATH = CAM_PATH + "locations/"; + public static final String CAM_MANGLE_PATH = CAM_PATH + "mangle/"; public static final String CAMERA_MAP = CAM_UTILS_PATH + "Map.png"; public static final String CAMERA_BORDERLINE = CAM_UTILS_PATH + "Border.png"; diff --git a/core/src/main/java/io/github/eldek0/game/Camera.java b/core/src/main/java/io/github/eldek0/game/Camera.java index 254a657..500a1e2 100644 --- a/core/src/main/java/io/github/eldek0/game/Camera.java +++ b/core/src/main/java/io/github/eldek0/game/Camera.java @@ -1,11 +1,239 @@ package io.github.eldek0.game; +import com.badlogic.gdx.Gdx; +import com.badlogic.gdx.Input; +import com.badlogic.gdx.graphics.Texture; import com.badlogic.gdx.graphics.g2d.SpriteBatch; +import com.badlogic.gdx.graphics.glutils.ShapeRenderer; +import com.badlogic.gdx.math.Rectangle; +import com.badlogic.gdx.math.Vector2; +import io.github.eldek0.App; +import io.github.eldek0.screen.GameScene; + +import static io.github.eldek0.App.assets; +import static io.github.eldek0.asset.GameAssetManager.*; public class Camera { - public Camera(){} + private static final int CAM_COUNT = 12; + private static final float CAMERA_SPEED = 5f * 60f; // px/s - public void render(SpriteBatch batch){} + private final float[][] BTN_POS = new float[][] { + { 620 - 25, 621 - 125 }, { 735 - 25, 621 - 125 }, + { 620 - 25, 556 - 125 }, { 735 - 25, 556 - 125 }, + { 600 - 15, 700 - 105 }, { 710 - 15, 700 - 105 }, + { 769 - 35, 482 - 115 }, { 612 - 37, 471 - 115 }, + { 920 - 25, 441 - 110 }, { 830 - 10, 570 - 115 }, + { 954 - 25, 515 - 110 }, { 945 - 25, 608 - 105 } + };; - public void update(float delta){} + /** 1-based room index currently shown on the monitor. */ + private int inCameraRoom = 10; + + private final Rectangle[] buttonScreenRects = new Rectangle[CAM_COUNT]; + + // Wide-camera panning (indices 6-11 → rooms 7-12) + private final float[] camerasXPosition = new float[CAM_COUNT]; + private final int[] wideCameraMovDirection = new int[CAM_COUNT]; + private final long[] timerCheckpoints = new long[CAM_COUNT]; + private final long[] timerWaitTimeMs = new long[CAM_COUNT]; + + private final boolean[] occupiedCamera = new boolean[CAM_COUNT]; + + private float recBlinkTimer = 0f; + private boolean recVisible = true; + + private GameScene gameScene; + + public Camera(GameScene gameScene) { + this.gameScene = gameScene; + java.util.Random rng = new java.util.Random(); + for (int i = 0; i < CAM_COUNT; i++) { + timerWaitTimeMs[i] = 5000 + (long) rng.nextInt(2001); // [5000,7000] ms + } + this.initButtonScreenRects(); + } + + private void initButtonScreenRects(){ + Texture btnUnsel = assets.getTexture(CAM_LABELS_PATH + "13.png"); + + for (int i = 0; i < CAM_COUNT; i++) { + float[] btnPos = BTN_POS[i]; + float worldY = App.SCREEN_HEIGHT - btnPos[1] - btnUnsel.getHeight(); + buttonScreenRects[i] = new Rectangle(btnPos[0], worldY, btnUnsel.getWidth() , btnUnsel.getHeight()); + } + } + + public void update(float delta) { + for (int i = 6; i < CAM_COUNT; i++) { + updateCameraTimer(i, delta); + } + + recBlinkTimer += delta; + if (recBlinkTimer >= 1f) { + recBlinkTimer -= 1f; + recVisible = !recVisible; + } + this.onTouchDown(); + } + + public void renderBackground(SpriteBatch batch) { + if (!gameScene.hud.isInsideCamera()){return;} + renderCurrentRoom(batch); + } + + public void onTouchDown() { + Vector2 position = App.convertPosToWorldPos(new Vector2(Gdx.input.getX(), Gdx.input.getY())); + for (int i = 0; i < CAM_COUNT; i++) { + if (buttonScreenRects[i].contains(position)) { + if (Gdx.input.isButtonJustPressed(Input.Buttons.LEFT)) { + inCameraRoom = i + 1; + return; + } + } + } + } + + private void renderCurrentRoom(SpriteBatch batch) { + int idx = inCameraRoom - 1; + float xOff = camerasXPosition[idx]; + + if (!occupiedCamera[idx]) { + Texture frame = getDefaultFrame(inCameraRoom); + if (frame != null) { + batch.draw(frame, xOff, 0); + } + } + } + + private Texture getDefaultFrame(int room) { + return switch (room) { + case 1 -> assets.getTexture(CAM_LOC_PATH + "PartyRoom1/0.png"); + case 2 -> assets.getTexture(CAM_LOC_PATH + "PartyRoom2/0.png"); + case 3 -> assets.getTexture(CAM_LOC_PATH + "PartyRoom3/0.png"); + case 4 -> assets.getTexture(CAM_LOC_PATH + "PartyRoom4/0.png"); + case 5 -> assets.getTexture(CAM_LOC_PATH + "LeftAirVent/0.png"); + case 6 -> assets.getTexture(CAM_LOC_PATH + "RightAirVent/0.png"); + case 7 -> assets.getTexture(CAM_LOC_PATH + "MainHall/0.png"); + case 8 -> assets.getTexture(CAM_LOC_PATH + "PartsnService/0.png"); + case 9 -> assets.getTexture(CAM_LOC_PATH + "ShowStage/0.png"); + case 10 -> assets.getTexture(CAM_LOC_PATH + "GameArea/2.png"); + case 11 -> assets.getTexture(CAM_LOC_PATH + "PrizeCorner/0.png"); + case 12 -> assets.getTexture(CAM_LOC_PATH + "KidsCove/0.png"); + default -> null; + }; + } + + public void renderUI(SpriteBatch batch) { + if (!gameScene.hud.isInsideCamera()) {return;} + + // Border overlay + Texture border = assets.getTexture(CAM_UTILS_PATH + "Border.png"); + batch.draw(border, 0, 0); + + // Map + Texture map = assets.getTexture(CAM_UTILS_PATH + "Map.png"); + batch.draw(map, 550, App.SCREEN_HEIGHT - 310 - map.getHeight()); + + // Room label + Texture label = getRoomLabel(inCameraRoom); + if (label != null) { + batch.draw(label, 550, App.SCREEN_HEIGHT - 280 - label.getHeight()); + } + + drawCameraButtons(batch); + + // REC sprite + if (recVisible) { + Texture rec = assets.getTexture(CAM_UTILS_PATH + "1.png"); + batch.draw(rec, 40, App.SCREEN_HEIGHT - 40 - rec.getHeight()); + } + + // Signal interrupted banner + if (occupiedCamera[inCameraRoom - 1]) { + Texture sig = assets.getTexture(CAM_UTILS_PATH + "2.png"); + float sx = Gdx.graphics.getWidth() / 2f - sig.getWidth() / 2f; + batch.draw(sig, sx, App.SCREEN_HEIGHT - 80 - sig.getHeight()); + } + } + + public void renderHitboxes(ShapeRenderer shapeRenderer) { + shapeRenderer.setColor(1, 0, 0, 1); + for (Rectangle rect : this.buttonScreenRects){ + shapeRenderer.rect(rect.x, rect.y, rect.width, rect.height); + } + } + + private void drawCameraButtons(SpriteBatch batch) { + Texture btnUnsel = assets.getTexture(CAM_LABELS_PATH + "13.png"); + Texture btnSel = assets.getTexture(CAM_LABELS_PATH + "14.png"); + + for (int i = 0; i < CAM_COUNT; i++) { + Texture btn = (i + 1 == inCameraRoom) ? btnSel : btnUnsel; + + float pyX = BTN_POS[i][0]; + float pyY = BTN_POS[i][1]; + + float worldY = App.SCREEN_HEIGHT - pyY - btnUnsel.getHeight(); + batch.draw(btn, pyX, worldY); + + Texture lbl = assets.getTexture(CAM_LABELS_PATH + (i + 1) + ".png"); + batch.draw(lbl, pyX + 5, worldY + 7); + } + } + + private Texture getRoomLabel(int room) { + return switch (room) { + case 1 -> assets.getTexture(CAM_LOC_PATH + "PartyRoom1/label.png"); + case 2 -> assets.getTexture(CAM_LOC_PATH + "PartyRoom2/label.png"); + case 3 -> assets.getTexture(CAM_LOC_PATH + "PartyRoom3/label.png"); + case 4 -> assets.getTexture(CAM_LOC_PATH + "PartyRoom4/label.png"); + case 5 -> assets.getTexture(CAM_LOC_PATH + "LeftAirVent/label.png"); + case 6 -> assets.getTexture(CAM_LOC_PATH + "RightAirVent/label.png"); + case 7 -> assets.getTexture(CAM_LOC_PATH + "MainHall/label.png"); + case 8 -> assets.getTexture(CAM_LOC_PATH + "PartsnService/label.png"); + case 9 -> assets.getTexture(CAM_LOC_PATH + "ShowStage/label.png"); + case 10 -> assets.getTexture(CAM_LOC_PATH + "GameArea/label.png"); + case 11 -> assets.getTexture(CAM_LOC_PATH + "PrizeCorner/label.png"); + case 12 -> assets.getTexture(CAM_LOC_PATH + "KidsCove/label.png"); + default -> null; + }; + } + + private void updateCameraTimer(int index, float delta) { + if (index < 6) return; + + Texture sample = getDefaultFrame(index + 1); + if (sample == null) return; + float maxOffset = -(sample.getWidth() - App.SCREEN_WIDTH); + + long now = System.currentTimeMillis(); + + if (timerCheckpoints[index] != 0) { + if (now - timerCheckpoints[index] > timerWaitTimeMs[index]) { + timerCheckpoints[index] = 0; + } + return; + } + + if (wideCameraMovDirection[index] == 0) { + camerasXPosition[index] -= CAMERA_SPEED * delta; + if (camerasXPosition[index] <= maxOffset) { + camerasXPosition[index] = maxOffset; + timerCheckpoints[index] = now; + wideCameraMovDirection[index] = 1; + } + } else { + camerasXPosition[index] += CAMERA_SPEED * delta; + if (camerasXPosition[index] >= 0) { + camerasXPosition[index] = 0; + timerCheckpoints[index] = now; + wideCameraMovDirection[index] = 0; + } + } + } + + public int getInCameraRoom() { return inCameraRoom; } + public void setInCameraRoom(int room) { this.inCameraRoom = room; } + public boolean isOccupied(int roomIndex) { return occupiedCamera[roomIndex]; } + public void setOccupied(int roomIndex, boolean v) { occupiedCamera[roomIndex] = v; } } diff --git a/core/src/main/java/io/github/eldek0/game/Office.java b/core/src/main/java/io/github/eldek0/game/Office.java index ef9f2fc..819b825 100644 --- a/core/src/main/java/io/github/eldek0/game/Office.java +++ b/core/src/main/java/io/github/eldek0/game/Office.java @@ -167,7 +167,7 @@ public class Office { } public void update(float dt) { - positionX += (int) (SPEED * movement * dt); + positionX += SPEED * movement * dt; if (-positionX < 0) positionX = 0; else if (-positionX > MAX_POS_X) positionX = -MAX_POS_X; } diff --git a/core/src/main/java/io/github/eldek0/screen/GameScene.java b/core/src/main/java/io/github/eldek0/screen/GameScene.java index 2ad3c0a..11b8b22 100644 --- a/core/src/main/java/io/github/eldek0/screen/GameScene.java +++ b/core/src/main/java/io/github/eldek0/screen/GameScene.java @@ -11,6 +11,7 @@ import com.badlogic.gdx.graphics.glutils.FrameBuffer; import com.badlogic.gdx.graphics.glutils.ShaderProgram; import com.badlogic.gdx.graphics.glutils.ShapeRenderer; import io.github.eldek0.App; +import io.github.eldek0.game.Camera; import io.github.eldek0.game.Mask; import io.github.eldek0.game.Office; import io.github.eldek0.ui.HUD; @@ -25,6 +26,7 @@ public class GameScene implements Screen { private final Office office; public final HUD hud; private final Mask mask; + private final Camera camera; FrameBuffer frameBuffer; public GameScene(App app) { @@ -36,6 +38,7 @@ public class GameScene implements Screen { this.office = new Office(this.hud); this.mask = new Mask(this.hud); + this.camera = new Camera(this); frameBuffer = new FrameBuffer(Pixmap.Format.RGBA8888, App.SCREEN_WIDTH, App.SCREEN_HEIGHT, false); @@ -55,11 +58,13 @@ public class GameScene implements Screen { office.update(v); hud.update(v); mask.update(v); + camera.update(v); frameBuffer.begin(); Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT); batch.begin(); office.render(batch); + camera.renderBackground(batch); batch.end(); frameBuffer.end(); @@ -72,6 +77,7 @@ public class GameScene implements Screen { batch.setShader(null); mask.render(batch); + camera.renderUI(batch); hud.render(); batch.end(); @@ -82,6 +88,8 @@ public class GameScene implements Screen { hud.renderHitboxes(shapeRenderer); + camera.renderHitboxes(shapeRenderer); + shapeRenderer.end(); if (Gdx.input.isKeyJustPressed(Input.Keys.F5) && App.DEBUG) { @@ -117,7 +125,7 @@ public class GameScene implements Screen { public void reloadShader() { if (shader != null) shader.dispose(); - String basePath = System.getProperty("user.dir") + "/assets/"; + String basePath = System.getProperty("user.dir") + "/"; String vert = Gdx.files.internal(basePath + VERTEX_PATH).readString(); String frag = Gdx.files.internal(basePath + FRAGMENT_PATH).readString(); diff --git a/core/src/main/java/io/github/eldek0/ui/Button.java b/core/src/main/java/io/github/eldek0/ui/Button.java index a81a2ff..6343639 100644 --- a/core/src/main/java/io/github/eldek0/ui/Button.java +++ b/core/src/main/java/io/github/eldek0/ui/Button.java @@ -24,6 +24,8 @@ public class Button { private final Animation animation; private float stateTime; + private boolean enable = true; + public Button(Texture sprite, float x, float y, Rectangle bounds, Texture[] transitionFrames) { this.buttonTexture = sprite; this.x = x; @@ -35,6 +37,7 @@ public class Button { } public void update(float delta) { + if (!enable){return;} Vector2 position = App.convertPosToWorldPos(new Vector2(Gdx.input.getX(), Gdx.input.getY())); if (bounds.contains(position)) { @@ -57,10 +60,12 @@ public class Button { } public void render(SpriteBatch batch) { + if (!enable){return;} batch.draw(buttonTexture, x, y, buttonTexture.getWidth(), buttonTexture.getHeight()); } public void renderHitbox(ShapeRenderer shapeRenderer) { + if (!enable){return;} shapeRenderer.rect(bounds.x, bounds.y, bounds.width, bounds.height); } @@ -89,6 +94,9 @@ public class Button { } + public boolean isEnable() {return enable;} + public void enable() {this.enable = true;} + public void disable() {this.enable = false;} public boolean isInTransition(){return entering || quitting;} public boolean isInside() { return inside; } public boolean isBeingPressed() { return beingPressed; } diff --git a/core/src/main/java/io/github/eldek0/ui/HUD.java b/core/src/main/java/io/github/eldek0/ui/HUD.java index 266c8d5..bae399c 100644 --- a/core/src/main/java/io/github/eldek0/ui/HUD.java +++ b/core/src/main/java/io/github/eldek0/ui/HUD.java @@ -24,6 +24,19 @@ public class HUD { cameraButton.render(batch); batch.end(); + + if (maskButton.isInTransition() || maskButton.isInside()){ + maskButton.enable(); + cameraButton.disable(); + } + else if (cameraButton.isInTransition() || cameraButton.isInside()) { + maskButton.disable(); + cameraButton.enable(); + } + else { + maskButton.enable(); + cameraButton.enable(); + } } public void renderHitboxes(ShapeRenderer shapeRenderer) { diff --git a/gradle/gradle-daemon-jvm.properties b/gradle/gradle-daemon-jvm.properties index 5d47bf4..ef1b636 100644 --- a/gradle/gradle-daemon-jvm.properties +++ b/gradle/gradle-daemon-jvm.properties @@ -1,12 +1,13 @@ #This file is generated by updateDaemonJvm -toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/df211d3c3eefdc408b462041881bc575/redirect -toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/93aeea858331bd6bb00ba94759830234/redirect -toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/df211d3c3eefdc408b462041881bc575/redirect -toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/b41931cf1e70bc8e08d7dd19c343ef00/redirect -toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/3426ffcaa54c3f62406beb1f1ab8b179/redirect -toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/d6690dfd71c4c91e08577437b5b2beb0/redirect -toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/df211d3c3eefdc408b462041881bc575/redirect -toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/b41931cf1e70bc8e08d7dd19c343ef00/redirect -toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/1e91f45234d88a64dafb961c93ddc75a/redirect -toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/552c7bffe0370c66410a51c55985b511/redirect +toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/d1cdf34033d69f8d4f43c91ee68af29f/redirect +toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/952dbdd3f95dda9dc0709bc891206f1f/redirect +toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/d1cdf34033d69f8d4f43c91ee68af29f/redirect +toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/952dbdd3f95dda9dc0709bc891206f1f/redirect +toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/a278f10e4aa15951649a32f0d45debe2/redirect +toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/46db9db627f8bba1a2822054b93228f9/redirect +toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/d1cdf34033d69f8d4f43c91ee68af29f/redirect +toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/952dbdd3f95dda9dc0709bc891206f1f/redirect +toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/278a765117def83915ff20a22e3aab48/redirect +toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/fe06cac13d0fc1321b8e034d546e8a06/redirect +toolchainVendor=MICROSOFT toolchainVersion=21