CameraUI sprites positions configurable via cameras.json

This commit is contained in:
2026-03-31 23:02:52 -03:00
parent 44acbe0e32
commit ffa3dc076b
35 changed files with 396 additions and 100 deletions

View File

@@ -9,7 +9,7 @@ import io.github.eldek0.asset.group.*;
public class GameAssetManager {
private static final String DATA = "data/";
private static final String DATA = "data/sprites/";
private final AssetManager manager = new AssetManager();
private final Json json = new Json();

View File

@@ -2,15 +2,11 @@ package io.github.eldek0.asset.group;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.graphics.Texture;
import io.github.eldek0.config.FrameRange;
public abstract class AssetBase {
public abstract static class Data {}
public static class FrameRange {
public String base;
public int start, end;
}
protected void queueRange(AssetManager m, FrameRange r) {
for (int i = r.start; i <= r.end; i++) m.load(r.base + i + ".png", Texture.class);
}

View File

@@ -2,6 +2,7 @@ package io.github.eldek0.asset.group;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.graphics.Texture;
import io.github.eldek0.config.FrameRange;
public class BatteryAssets extends AssetBase implements AssetBundle<BatteryAssets.Data> {

View File

@@ -3,6 +3,7 @@ package io.github.eldek0.asset.group;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.graphics.Texture;
import com.badlogic.gdx.utils.Array;
import io.github.eldek0.config.FrameRange;
import java.util.LinkedHashMap;
import java.util.Map;

View File

@@ -2,6 +2,7 @@ package io.github.eldek0.asset.group;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.graphics.Texture;
import io.github.eldek0.config.FrameRange;
public class CustomNightAssets extends AssetBase implements AssetBundle<CustomNightAssets.Data>{

View File

@@ -2,6 +2,7 @@ package io.github.eldek0.asset.group;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.graphics.Texture;
import io.github.eldek0.config.FrameRange;
public class CutsceneAssets extends AssetBase implements AssetBundle<CutsceneAssets.Data> {

View File

@@ -2,6 +2,7 @@ package io.github.eldek0.asset.group;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.graphics.Texture;
import io.github.eldek0.config.FrameRange;
public class JumpscareAssets extends AssetBase implements AssetBundle<JumpscareAssets.Data> {

View File

@@ -2,6 +2,7 @@ package io.github.eldek0.asset.group;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.graphics.Texture;
import io.github.eldek0.config.FrameRange;
public class MaskAssets extends AssetBase implements AssetBundle<MaskAssets.Data> {

View File

@@ -2,6 +2,7 @@ package io.github.eldek0.asset.group;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.graphics.Texture;
import io.github.eldek0.config.FrameRange;
public class MenuAssets extends AssetBase implements AssetBundle<MenuAssets.Data> {

View File

@@ -2,6 +2,7 @@ package io.github.eldek0.asset.group;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.graphics.Texture;
import io.github.eldek0.config.FrameRange;
public class MinigameAssets extends AssetBase implements AssetBundle<MinigameAssets.Data> {

View File

@@ -2,6 +2,7 @@ package io.github.eldek0.asset.group;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.graphics.Texture;
import io.github.eldek0.config.FrameRange;
public class MonitorAssets extends AssetBase implements AssetBundle<MonitorAssets.Data> {

View File

@@ -2,6 +2,7 @@ package io.github.eldek0.asset.group;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.graphics.Texture;
import io.github.eldek0.config.FrameRange;
public class NumbersAssets extends AssetBase implements AssetBundle<NumbersAssets.Data> {

View File

@@ -2,6 +2,7 @@ package io.github.eldek0.asset.group;
import com.badlogic.gdx.assets.AssetManager;
import com.badlogic.gdx.graphics.Texture;
import io.github.eldek0.config.FrameRange;
public class OfficeAssets extends AssetBase implements AssetBundle<OfficeAssets.Data> {

View File

@@ -0,0 +1,10 @@
package io.github.eldek0.config;
public class CameraConfig {
public int id;
public String name;
public int labelPathId;
public float buttonX;
public float buttonY;
public boolean wide;
}

View File

@@ -0,0 +1,8 @@
package io.github.eldek0.config;
public class CameraConfigFile {
public int inCameraId;
public RangeLong wideRandomMov;
public SpritePositionData[] sprites;
public CameraConfig[] cameras;
}

View File

@@ -0,0 +1,9 @@
package io.github.eldek0.config;
public enum CameraSpriteType {
BORDER,
MAP,
LABEL,
REC,
SIGNAL_INTERRUPTED
}

View File

@@ -0,0 +1,6 @@
package io.github.eldek0.config;
public class FrameRange {
public String base;
public int start, end;
}

View File

@@ -0,0 +1,6 @@
package io.github.eldek0.config;
public class RangeLong {
public long min;
public long max;
}

View File

@@ -0,0 +1,11 @@
package io.github.eldek0.config;
public record SpriteConfig<T extends Enum<T>>(
T type,
float x,
float y,
boolean active,
boolean fromTop,
boolean centerX,
int zIndex
) {}

View File

@@ -0,0 +1,11 @@
package io.github.eldek0.config;
public class SpritePositionData {
public String type;
public int x, y;
public boolean active = true;
public boolean fromTop = false;
public boolean centerX = false;
public int zIndex = 0;
}

View File

@@ -7,65 +7,88 @@ 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 com.badlogic.gdx.utils.Json;
import io.github.eldek0.App;
import io.github.eldek0.config.*;
import io.github.eldek0.screen.GameScene;
import io.github.eldek0.ui.SpriteLayout;
import java.util.*;
import static io.github.eldek0.App.assets;
import static io.github.eldek0.asset.GameAssetManager.*;
public class Camera {
private static final int CAM_COUNT = 12;
private static final float CAMERA_SPEED = 5f * 60f; // px/s
private static final String CONFIG = "data/config/cameras.json";
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 }
};;
private CameraConfig[] cameraConfigs;
/** 1-based room index currently shown on the monitor. */
private int inCameraRoom = 10;
private int inCameraRoom;
private final Rectangle[] buttonScreenRects = new Rectangle[CAM_COUNT];
private Rectangle[] buttonScreenRects;
// 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];
// Wide-camera panning
private float[] camerasXPosition;
private int[] wideCameraMovDirection;
private long[] timerCheckpoints;
private long[] timerWaitTimeMs;
private RangeLong wideRandomMov;
private final boolean[] occupiedCamera = new boolean[CAM_COUNT];
private boolean[] occupiedCamera;
private float recBlinkTimer = 0f;
private boolean recVisible = true;
SpriteLayout<CameraSpriteType> layout;
private GameScene gameScene;
public Camera(GameScene gameScene) {
this.gameScene = gameScene;
loadCameraConfig();
initializeArrays();
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
for (int i = 0; i < cameraConfigs.length; i++) {
timerWaitTimeMs[i] = wideRandomMov.min + (long) rng.nextInt((int) (wideRandomMov.max - wideRandomMov.min));
}
this.initButtonScreenRects();
}
private void initializeArrays() {
int camCount = cameraConfigs.length;
buttonScreenRects = new Rectangle[camCount];
camerasXPosition = new float[camCount];
wideCameraMovDirection = new int[camCount];
timerCheckpoints = new long[camCount];
timerWaitTimeMs = new long[camCount];
occupiedCamera = new boolean[camCount];
}
private void initButtonScreenRects(){
Texture btnUnsel = assets.cameras.roomButtonUnselected;
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());
for (int i = 0; i < cameraConfigs.length; i++) {
CameraConfig cameraConfig = cameraConfigs[i];
float worldY = App.SCREEN_HEIGHT - cameraConfig.buttonY - btnUnsel.getHeight();
buttonScreenRects[i] = new Rectangle(
cameraConfig.buttonX,
worldY,
btnUnsel.getWidth(),
btnUnsel.getHeight());
}
}
public void update(float delta) {
for (int i = 6; i < CAM_COUNT; i++) {
updateCameraTimer(i, delta);
for (int i = 0; i < cameraConfigs.length; i++) {
if (cameraConfigs[i].wide) {
updateCameraTimer(i, delta);
}
}
recBlinkTimer += delta;
@@ -73,6 +96,7 @@ public class Camera {
recBlinkTimer -= 1f;
recVisible = !recVisible;
}
this.onTouchDown();
}
@@ -83,7 +107,7 @@ public class Camera {
public void onTouchDown() {
Vector2 position = App.convertPosToWorldPos(new Vector2(Gdx.input.getX(), Gdx.input.getY()));
for (int i = 0; i < CAM_COUNT; i++) {
for (int i = 0; i < this.cameraConfigs.length; i++) {
if (buttonScreenRects[i].contains(position)) {
if (Gdx.input.isButtonJustPressed(Input.Buttons.LEFT)) {
inCameraRoom = i + 1;
@@ -105,57 +129,71 @@ public class Camera {
}
}
private void loadCameraConfig(){
Json json = new Json();
String raw = Gdx.files.internal(CONFIG).readString();
CameraConfigFile configFile = json.fromJson(CameraConfigFile.class, raw);
this.inCameraRoom = configFile.inCameraId;
this.cameraConfigs = configFile.cameras;
this.wideRandomMov = configFile.wideRandomMov;
this.layout = new SpriteLayout<>(
configFile.sprites, // 👈 directo
CameraSpriteType::valueOf
);
}
private Texture getFrame(int room, int index) {
Texture[] textures = switch (room) {
case 1 -> assets.cameras.getLocationFrames("PartyRoom1");
case 2 -> assets.cameras.getLocationFrames("PartyRoom2");
case 3 -> assets.cameras.getLocationFrames("PartyRoom3");
case 4 -> assets.cameras.getLocationFrames("PartyRoom4");
case 5 -> assets.cameras.getLocationFrames("LeftAirVent");
case 6 -> assets.cameras.getLocationFrames("RightAirVent");
case 7 -> assets.cameras.getLocationFrames("MainHall");
case 8 -> assets.cameras.getLocationFrames("PartsnService");
case 9 -> assets.cameras.getLocationFrames("ShowStage");
case 10 -> assets.cameras.getLocationFrames("GameArea");
case 11 -> assets.cameras.getLocationFrames("PrizeCorner");
case 12 -> assets.cameras.getLocationFrames("KidsCove");
default -> null;
};
assert textures != null;
CameraConfig config = cameraConfigs[room - 1];
Texture[] textures = assets.cameras.getLocationFrames(config.name);
if (textures == null || index < 0 || index >= textures.length) {
return null;
}
return textures[index];
}
private Texture getRoomLabel(int room) {
CameraConfig config = cameraConfigs[room - 1];
return assets.cameras.locationLabels.get(config.name);
}
public void renderUI(SpriteBatch batch) {
if (!gameScene.hud.isInsideCamera()) {return;}
if (!gameScene.hud.isInsideCamera()) return;
// Border overlay
Texture border = assets.cameras.borderline;
batch.draw(border, 0, 0);
for (SpriteConfig<CameraSpriteType> config : layout.getSortedSprites()) {
if (!config.active()) continue;
// Map
Texture map = assets.cameras.map;
batch.draw(map, 550, App.SCREEN_HEIGHT - 310 - map.getHeight());
Texture texture = getTextureForSprite(config.type());
if (config.type() == CameraSpriteType.LABEL) {
texture = getRoomLabel(inCameraRoom);
}
// Room label
Texture label = getRoomLabel(inCameraRoom);
if (label != null) {
batch.draw(label, 550, App.SCREEN_HEIGHT - 280 - label.getHeight());
if (texture == null) continue;
if (config.type() == CameraSpriteType.REC && !recVisible) continue;
if (config.type() == CameraSpriteType.SIGNAL_INTERRUPTED && !occupiedCamera[inCameraRoom - 1]) continue;
batch.draw(
texture,
layout.resolveX(config, texture),
layout.resolveY(config, texture)
);
}
drawCameraButtons(batch);
}
// REC sprite
if (recVisible) {
Texture rec = assets.cameras.recordSprite;
batch.draw(rec, 40, App.SCREEN_HEIGHT - 40 - rec.getHeight());
}
// Signal interrupted banner
if (occupiedCamera[inCameraRoom - 1]) {
Texture sig = assets.cameras.signalInterrupted;
float sx = Gdx.graphics.getWidth() / 2f - sig.getWidth() / 2f;
batch.draw(sig, sx, App.SCREEN_HEIGHT - 80 - sig.getHeight());
}
private Texture getTextureForSprite(CameraSpriteType type) {
return switch (type) {
case BORDER -> assets.cameras.borderline;
case MAP -> assets.cameras.map;
case REC -> assets.cameras.recordSprite;
case SIGNAL_INTERRUPTED -> assets.cameras.signalInterrupted;
case LABEL -> null;
};
}
public void renderHitboxes(ShapeRenderer shapeRenderer) {
@@ -167,40 +205,19 @@ public class Camera {
private void drawCameraButtons(SpriteBatch batch) {
Texture btnUnsel = assets.cameras.roomButtonUnselected;
Texture btnSel = assets.cameras.roomButtonSelected;
Texture btnSel = assets.cameras.roomButtonSelected;
for (int i = 0; i < CAM_COUNT; i++) {
Texture btn = (i + 1 == inCameraRoom) ? btnSel : btnUnsel;
for (CameraConfig config : cameraConfigs) {
Texture btn = (config.id == inCameraRoom) ? btnSel : btnUnsel;
float pyX = BTN_POS[i][0];
float pyY = BTN_POS[i][1];
float worldY = App.SCREEN_HEIGHT - config.buttonY - btnUnsel.getHeight();
batch.draw(btn, config.buttonX, worldY);
float worldY = App.SCREEN_HEIGHT - pyY - btnUnsel.getHeight();
batch.draw(btn, pyX, worldY);
Texture lbl = assets.cameras.roomLabels[i];
batch.draw(lbl, pyX + 5, worldY + 7);
Texture lbl = assets.cameras.roomLabels[config.labelPathId];
batch.draw(lbl, config.buttonX + 5, worldY + 7);
}
}
private Texture getRoomLabel(int room) {
return switch (room) {
case 1 -> assets.cameras.getLocationLabel("PartyRoom1/label.png");
case 2 -> assets.cameras.getLocationLabel("PartyRoom2/label.png");
case 3 -> assets.cameras.getLocationLabel("PartyRoom3/label.png");
case 4 -> assets.cameras.getLocationLabel("PartyRoom4/label.png");
case 5 -> assets.cameras.getLocationLabel("LeftAirVent/label.png");
case 6 -> assets.cameras.getLocationLabel("RightAirVent/label.png");
case 7 -> assets.cameras.getLocationLabel("MainHall/label.png");
case 8 -> assets.cameras.getLocationLabel("PartsnService/label.png");
case 9 -> assets.cameras.getLocationLabel("ShowStage/label.png");
case 10 -> assets.cameras.getLocationLabel("GameArea/label.png");
case 11 -> assets.cameras.getLocationLabel("PrizeCorner/label.png");
case 12 -> assets.cameras.getLocationLabel("KidsCove/label.png");
default -> null;
};
}
private void updateCameraTimer(int index, float delta) {
if (index < 6) return;

View File

@@ -0,0 +1,85 @@
package io.github.eldek0.ui;
import com.badlogic.gdx.graphics.Texture;
import io.github.eldek0.App;
import io.github.eldek0.config.SpriteConfig;
import io.github.eldek0.config.SpritePositionData;
import java.util.*;
import java.util.function.Function;
public class SpriteLayout<T extends Enum<T>> {
private final Map<T, SpriteConfig[]> spritesMap;
private final SpriteConfig[] sortedSprites;
public SpriteLayout(SpritePositionData[] rawSprites, Function<String, T> enumResolver) {
if (rawSprites == null) {
throw new IllegalArgumentException("rawSprites no puede ser null");
}
if (enumResolver == null) {
throw new IllegalArgumentException("enumResolver no puede ser null");
}
List<SpriteConfig> parsed = new ArrayList<>();
for (SpritePositionData raw : rawSprites) {
if (raw == null || raw.type == null) continue;
T enumType = enumResolver.apply(raw.type);
SpriteConfig config = new SpriteConfig(
enumType,
raw.x,
raw.y,
raw.active,
raw.fromTop,
raw.centerX,
raw.zIndex
);
parsed.add(config);
}
Map<T, List<SpriteConfig>> temp = new HashMap<>();
for (SpriteConfig sprite : parsed) {
temp.computeIfAbsent((T) sprite.type(), k -> new ArrayList<>()).add(sprite);
}
Map<T, SpriteConfig[]> builtMap = new HashMap<>();
for (Map.Entry<T, List<SpriteConfig>> entry : temp.entrySet()) {
builtMap.put(entry.getKey(), entry.getValue().toArray(new SpriteConfig[0]));
}
this.spritesMap = builtMap;
SpriteConfig[] ordered = parsed.toArray(new SpriteConfig[0]);
Arrays.sort(ordered, Comparator.comparingInt(SpriteConfig::zIndex));
this.sortedSprites = ordered;
}
public SpriteConfig[] getSprites(T type) {
return spritesMap.getOrDefault(type, new SpriteConfig[0]);
}
public SpriteConfig[] getSortedSprites() {
return sortedSprites;
}
public float resolveX(SpriteConfig config, Texture texture) {
float x = config.x();
if (config.centerX()) {
x += App.SCREEN_WIDTH / 2f - texture.getWidth() / 2f;
}
return x;
}
public float resolveY(SpriteConfig config, Texture texture) {
if (config.fromTop()) {
return App.SCREEN_HEIGHT - config.y() - texture.getHeight();
}
return config.y();
}
}