gitignore-Fix: Quell-Paket config/ nicht mehr ignorieren
**/config/ hat fälschlicherweise auch de.blight.game.config (9 Java-Dateien) ignoriert. Ersetze durch explizite Regeln nur für Laufzeit-Konfigurationen (root/config/, blight-editor/config/, blight-game/config/). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
6
.gitignore
vendored
6
.gitignore
vendored
@@ -3,7 +3,11 @@
|
||||
|
||||
# Gradle build output
|
||||
**/build/
|
||||
**/config/
|
||||
|
||||
# Laufzeit-Konfigurationen (keine Quell-Pakete!)
|
||||
/config/
|
||||
blight-editor/config/
|
||||
blight-game/config/
|
||||
|
||||
# Eclipse project files
|
||||
.project
|
||||
|
||||
145
blight-game/src/main/java/de/blight/game/config/AudioScreen.java
Normal file
145
blight-game/src/main/java/de/blight/game/config/AudioScreen.java
Normal file
@@ -0,0 +1,145 @@
|
||||
package de.blight.game.config;
|
||||
|
||||
import com.jme3.font.BitmapText;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.scene.Node;
|
||||
import de.blight.lang.TextResolver;
|
||||
|
||||
public class AudioScreen extends MenuScreen {
|
||||
|
||||
private static final int ROW_MASTER = 0;
|
||||
private static final int ROW_MUSIC = 1;
|
||||
private static final int ROW_SPEECH = 2;
|
||||
private static final int ROW_EFFECTS = 3;
|
||||
private static final int ROW_AMBIENT = 4;
|
||||
private static final int ROW_COUNT = 5;
|
||||
private static final int STEP_COUNT = 11;
|
||||
|
||||
private final AudioSettings live;
|
||||
private AudioSettings edit;
|
||||
private final Runnable onClose;
|
||||
|
||||
private final int[] stepIdx = new int[ROW_COUNT];
|
||||
|
||||
private final float arrW = 30f;
|
||||
private final float cellW = 120f;
|
||||
private final float cellH = 36f;
|
||||
private final float[] cellX = new float[ROW_COUNT];
|
||||
private final float[] cellY = new float[ROW_COUNT];
|
||||
private final BitmapText[] valTexts = new BitmapText[ROW_COUNT];
|
||||
|
||||
public AudioScreen(AudioSettings live, Runnable onClose) {
|
||||
this.live = live;
|
||||
this.onClose = onClose;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEnableExtras() {
|
||||
edit = new AudioSettings();
|
||||
edit.master = live.master;
|
||||
edit.music = live.music;
|
||||
edit.speech = live.speech;
|
||||
edit.effects = live.effects;
|
||||
edit.ambient = live.ambient;
|
||||
|
||||
stepIdx[ROW_MASTER] = toStepIdx(edit.master);
|
||||
stepIdx[ROW_MUSIC] = toStepIdx(edit.music);
|
||||
stepIdx[ROW_SPEECH] = toStepIdx(edit.speech);
|
||||
stepIdx[ROW_EFFECTS] = toStepIdx(edit.effects);
|
||||
stepIdx[ROW_AMBIENT] = toStepIdx(edit.ambient);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void buildUI() {
|
||||
initPanel(t("menu.audio.title"));
|
||||
|
||||
String[] rowKeys = {
|
||||
"menu.audio.row.master", "menu.audio.row.music", "menu.audio.row.speech",
|
||||
"menu.audio.row.effects","menu.audio.row.ambient"
|
||||
};
|
||||
|
||||
float lblX = CONT_X + 30f;
|
||||
float vx = CONT_X + CONT_W - 240f;
|
||||
float startY = CONT_Y + CONT_H - HDR_H - 58f;
|
||||
float step = 56f;
|
||||
|
||||
for (int i = 0; i < ROW_COUNT; i++) {
|
||||
final int row = i;
|
||||
float ry = startY - i * step;
|
||||
|
||||
BitmapText lbl = crispText(t(rowKeys[i]), UiTheme.FONT_LABEL, UiTheme.COL_WHITE);
|
||||
lbl.setLocalTranslation(lblX, ry + cellH - 4f, 0f);
|
||||
panel.attachChild(lbl);
|
||||
|
||||
addButton(panel, "<", vx - arrW - 6f, ry, arrW, cellH,
|
||||
BtnStyle.ARROW, () -> cycle(row, -1));
|
||||
|
||||
panel.attachChild(NinePatch.button(assets).build(vx, ry, cellW, cellH, 0f));
|
||||
|
||||
addButton(panel, ">", vx + cellW + 6f, ry, arrW, cellH,
|
||||
BtnStyle.ARROW, () -> cycle(row, +1));
|
||||
|
||||
BitmapText vt = crispText("", UiTheme.FONT_LABEL, UiTheme.COL_GOLD);
|
||||
panel.attachChild(vt);
|
||||
valTexts[i] = vt;
|
||||
|
||||
cellX[i] = vx;
|
||||
cellY[i] = ry;
|
||||
}
|
||||
|
||||
for (int i = 0; i < ROW_COUNT; i++) refreshText(i);
|
||||
|
||||
float bw = 160f, bh = 42f;
|
||||
float btnY = CONT_Y + 22f;
|
||||
float saveX = CONT_X + CONT_W / 2f - bw - 10f;
|
||||
float canX = CONT_X + CONT_W / 2f + 10f;
|
||||
addButton(panel, t("menu.audio.btn.apply"), saveX, btnY, bw, bh, BtnStyle.SAVE, this::applyAndSave);
|
||||
addButton(panel, t("menu.audio.btn.cancel"), canX, btnY, bw, bh, BtnStyle.QUIT, this::close);
|
||||
}
|
||||
|
||||
private void refreshText(int row) {
|
||||
int pct = stepIdx[row] * 10;
|
||||
BitmapText vt = valTexts[row];
|
||||
vt.setText(pct + "%");
|
||||
float tw = vt.getLineWidth() / getUiScale();
|
||||
vt.setLocalTranslation(
|
||||
cellX[row] + (cellW - tw) / 2f,
|
||||
cellY[row] + cellH - 4f,
|
||||
1f
|
||||
);
|
||||
}
|
||||
|
||||
private void cycle(int row, int dir) {
|
||||
stepIdx[row] = Math.max(0, Math.min(STEP_COUNT - 1, stepIdx[row] + dir));
|
||||
float val = stepIdx[row] / 10f;
|
||||
switch (row) {
|
||||
case ROW_MASTER -> edit.master = val;
|
||||
case ROW_MUSIC -> edit.music = val;
|
||||
case ROW_SPEECH -> edit.speech = val;
|
||||
case ROW_EFFECTS -> edit.effects = val;
|
||||
case ROW_AMBIENT -> edit.ambient = val;
|
||||
}
|
||||
refreshText(row);
|
||||
}
|
||||
|
||||
private void applyAndSave() {
|
||||
live.master = edit.master;
|
||||
live.music = edit.music;
|
||||
live.speech = edit.speech;
|
||||
live.effects = edit.effects;
|
||||
live.ambient = edit.ambient;
|
||||
AudioSettingsStore.save(live);
|
||||
close();
|
||||
}
|
||||
|
||||
private void close() {
|
||||
setEnabled(false);
|
||||
if (onClose != null) onClose.run();
|
||||
}
|
||||
|
||||
private static int toStepIdx(float v) {
|
||||
return Math.max(0, Math.min(STEP_COUNT - 1, Math.round(v * 10f)));
|
||||
}
|
||||
|
||||
private static String t(String id) { return TextResolver.get().resolveId(id); }
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package de.blight.game.config;
|
||||
|
||||
public class AudioSettings {
|
||||
public float master = 1.0f;
|
||||
public float music = 1.0f;
|
||||
public float speech = 1.0f;
|
||||
public float effects = 1.0f;
|
||||
public float ambient = 0.5f;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package de.blight.game.config;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import de.blight.common.BlightHome;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.*;
|
||||
|
||||
public class AudioSettingsStore {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(AudioSettingsStore.class);
|
||||
private static final Path FILE = BlightHome.resolve("config", "audio.json");
|
||||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||
|
||||
public static AudioSettings load() {
|
||||
if (Files.exists(FILE)) {
|
||||
try (Reader r = Files.newBufferedReader(FILE)) {
|
||||
AudioSettings s = GSON.fromJson(r, AudioSettings.class);
|
||||
return s != null ? s : new AudioSettings();
|
||||
} catch (IOException e) {
|
||||
log.warn("audio.json konnte nicht geladen werden: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
return new AudioSettings();
|
||||
}
|
||||
|
||||
public static void save(AudioSettings s) {
|
||||
try {
|
||||
Files.createDirectories(FILE.getParent());
|
||||
try (Writer w = Files.newBufferedWriter(FILE)) {
|
||||
GSON.toJson(s, w);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("audio.json konnte nicht gespeichert werden: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package de.blight.game.config;
|
||||
|
||||
import com.jme3.font.BitmapText;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.scene.Node;
|
||||
import de.blight.lang.TextResolver;
|
||||
|
||||
public class MainMenuState extends MenuScreen {
|
||||
|
||||
private static final ColorRGBA COL_TITLE = new ColorRGBA(0.85f, 0.70f, 0.35f, 1.00f);
|
||||
|
||||
private final Runnable onNewGame;
|
||||
private final Runnable onContinue;
|
||||
private final Runnable onLoad;
|
||||
private final Runnable onOptions;
|
||||
private final Runnable onQuit;
|
||||
|
||||
public MainMenuState(Runnable onNewGame, Runnable onContinue, Runnable onLoad,
|
||||
Runnable onOptions, Runnable onQuit) {
|
||||
this.onNewGame = onNewGame;
|
||||
this.onContinue = onContinue;
|
||||
this.onLoad = onLoad;
|
||||
this.onOptions = onOptions;
|
||||
this.onQuit = onQuit;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void buildUI() {
|
||||
initPanel("BLIGHT", COL_TITLE);
|
||||
|
||||
addQuad(panel, CONT_X + 20f, CONT_Y + CONT_H - HDR_H - 10f, CONT_W - 40f, 1f,
|
||||
new ColorRGBA(0.3f, 0.3f, 0.5f, 1f), -17f);
|
||||
|
||||
String[] keys = {
|
||||
"menu.main.btn.new_game", "menu.main.btn.continue", "menu.main.btn.load",
|
||||
"menu.main.btn.options", "menu.main.btn.quit"
|
||||
};
|
||||
Runnable[] acts = { onNewGame, onContinue, onLoad, onOptions, onQuit };
|
||||
|
||||
float bw = 260f, bh = 50f;
|
||||
float bx = CONT_X + (CONT_W - bw) / 2f;
|
||||
float startY = CONT_Y + CONT_H - HDR_H - 68f;
|
||||
float step = 62f;
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
boolean enabled = acts[i] != null;
|
||||
BtnStyle style = !enabled ? BtnStyle.DISABLED : i == 4 ? BtnStyle.QUIT : BtnStyle.DEFAULT;
|
||||
addButton(panel, t(keys[i]), bx, startY - i * step, bw, bh, style, acts[i]);
|
||||
}
|
||||
}
|
||||
|
||||
private static String t(String id) { return TextResolver.get().resolveId(id); }
|
||||
}
|
||||
107
blight-game/src/main/java/de/blight/game/config/MenuCanvas.java
Normal file
107
blight-game/src/main/java/de/blight/game/config/MenuCanvas.java
Normal file
@@ -0,0 +1,107 @@
|
||||
package de.blight.game.config;
|
||||
|
||||
import com.jme3.asset.AssetManager;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.material.RenderState;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.shape.Quad;
|
||||
|
||||
/**
|
||||
* Gemeinsamer Helfer für alle Menü-Screens.
|
||||
*
|
||||
* Koordinatensystem: virtueller Referenzraum 1376 × 768.
|
||||
* createCanvas() liefert einen skalierten Node, dessen Kinder in diesem Referenzraum
|
||||
* positioniert werden und automatisch auf jeden Bildschirm skalieren.
|
||||
* createBgLayer() / createPauseBgLayer() liefern ein dunkles Vollbild-Overlay
|
||||
* im Transparent-Bucket (z=100, JME3 GUI-Kamera schaut in +Z → größere z = dahinter),
|
||||
* sodass Panel-Elemente (z=1 bis 10) stets darüber rendern.
|
||||
*/
|
||||
public final class MenuCanvas {
|
||||
|
||||
public static final float REF_W = 1376f;
|
||||
public static final float REF_H = 768f;
|
||||
|
||||
/** Rand auf jeder Seite als Anteil der Referenzgröße (0.10 = 10 % → 80 % Inhalt). */
|
||||
public static final float MARGIN_FRAC = 0.10f;
|
||||
|
||||
public static final float CONT_X = REF_W * MARGIN_FRAC;
|
||||
public static final float CONT_Y = REF_H * MARGIN_FRAC;
|
||||
public static final float CONT_W = REF_W * (1f - 2f * MARGIN_FRAC);
|
||||
public static final float CONT_H = REF_H * (1f - 2f * MARGIN_FRAC);
|
||||
|
||||
private MenuCanvas() {}
|
||||
|
||||
/**
|
||||
* Canvas-Skalierungsfaktor (min-fit, kein Stretching).
|
||||
* Hilfreich für crispText: Schriftgröße = virtualSize * getScale()
|
||||
*/
|
||||
public static float getScale(Camera cam) {
|
||||
return Math.min(cam.getWidth() / REF_W, cam.getHeight() / REF_H);
|
||||
}
|
||||
|
||||
/**
|
||||
* Skalierter Canvas-Node: Kinder in virtuellen Koordinaten (0..REF_W, 0..REF_H)
|
||||
* erscheinen automatisch korrekt auf dem Bildschirm.
|
||||
* Den Node direkt an guiNode hängen.
|
||||
*/
|
||||
public static Node createCanvas(Camera cam) {
|
||||
float scale = Math.min(cam.getWidth() / REF_W, cam.getHeight() / REF_H);
|
||||
float ox = (cam.getWidth() - REF_W * scale) / 2f;
|
||||
float oy = (cam.getHeight() - REF_H * scale) / 2f;
|
||||
Node n = new Node("menu-canvas");
|
||||
n.setLocalScale(scale, scale, 1f);
|
||||
n.setLocalTranslation(ox, oy, 0f);
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Canvas für das Pause-Menü: keine Skalierung, in Bildschirmmitte zentriert.
|
||||
*/
|
||||
public static Node createFixedCanvas(Camera cam) {
|
||||
float ox = (cam.getWidth() - REF_W) / 2f;
|
||||
float oy = (cam.getHeight() - REF_H) / 2f;
|
||||
Node n = new Node("menu-canvas-fixed");
|
||||
n.setLocalScale(1f, 1f, 1f);
|
||||
n.setLocalTranslation(ox, oy, 0f);
|
||||
return n;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dunkles Vollbild-Overlay (Transparent-Bucket, z=100).
|
||||
* Liegt hinter allen Panel-Elementen (z=1 bis 10).
|
||||
* Direkt an guiNode hängen (nicht an canvasNode).
|
||||
*/
|
||||
public static Node createBgLayer(AssetManager am, Camera cam) {
|
||||
return darkOverlay(am, cam, "menu-bg-layer", 0.03f, 0.03f, 0.06f, 1f);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dunkles Vollbild-Overlay für die Pause-Ansicht (60% schwarz).
|
||||
* Lässt die Spielwelt (gebluurt) durch und liegt hinter dem Panel.
|
||||
* Direkt an guiNode hängen (nicht an canvasNode).
|
||||
*/
|
||||
public static Node createPauseBgLayer(AssetManager am, Camera cam) {
|
||||
return darkOverlay(am, cam, "pause-bg-layer", 0f, 0f, 0f, 0.60f);
|
||||
}
|
||||
|
||||
// ── Privat ────────────────────────────────────────────────────────────────
|
||||
|
||||
private static Node darkOverlay(AssetManager am, Camera cam, String name,
|
||||
float r, float g, float b, float a) {
|
||||
float sw = cam.getWidth(), sh = cam.getHeight();
|
||||
Node layer = new Node(name);
|
||||
Material mat = new Material(am, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setColor("Color", new ColorRGBA(r, g, b, a));
|
||||
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
||||
Geometry geo = new Geometry("bg_overlay", new Quad(sw, sh));
|
||||
geo.setMaterial(mat);
|
||||
geo.setQueueBucket(RenderQueue.Bucket.Gui);
|
||||
geo.setLocalTranslation(0f, 0f, -100f);
|
||||
layer.attachChild(geo);
|
||||
return layer;
|
||||
}
|
||||
}
|
||||
249
blight-game/src/main/java/de/blight/game/config/MenuScreen.java
Normal file
249
blight-game/src/main/java/de/blight/game/config/MenuScreen.java
Normal file
@@ -0,0 +1,249 @@
|
||||
package de.blight.game.config;
|
||||
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.app.state.BaseAppState;
|
||||
import com.jme3.asset.AssetManager;
|
||||
import com.jme3.font.BitmapFont;
|
||||
import com.jme3.font.BitmapText;
|
||||
import com.jme3.input.MouseInput;
|
||||
import com.jme3.input.controls.ActionListener;
|
||||
import com.jme3.input.controls.MouseButtonTrigger;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.material.RenderState;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.post.FilterPostProcessor;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.shape.Quad;
|
||||
import de.blight.game.post.GaussianBlurFilter;
|
||||
import de.blight.game.scene.WorldScene;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Abstrakte Basisklasse für alle Menü-Screens (Hauptmenü, Pause, Grafik, Audio, Tasten).
|
||||
*
|
||||
* Kümmert sich um:
|
||||
* - Canvas + bgLayer Auf-/Abbau
|
||||
* - NinePatch-Panel-Container mit dunklem Header + Titeltext
|
||||
* - CrispText-Rendering (Counter-Scale für scharfe Schrift auf WQHD)
|
||||
* - Button-Factory mit automatischer Klick-Erkennung
|
||||
* - optionaler Blur-Support
|
||||
* - toVirtual() / addQuad() Helfer
|
||||
*/
|
||||
public abstract class MenuScreen extends BaseAppState {
|
||||
|
||||
public static final float HDR_H = 44f;
|
||||
public static final float PAD = 14f;
|
||||
|
||||
/** Abgeleitete Container-Koordinaten – geändert wird nur MenuCanvas.MARGIN_FRAC. */
|
||||
public static final float CONT_X = MenuCanvas.CONT_X;
|
||||
public static final float CONT_Y = MenuCanvas.CONT_Y;
|
||||
public static final float CONT_W = MenuCanvas.CONT_W;
|
||||
public static final float CONT_H = MenuCanvas.CONT_H;
|
||||
|
||||
public enum BtnStyle { DEFAULT, SAVE, QUIT, DISABLED, ARROW }
|
||||
|
||||
protected SimpleApplication app;
|
||||
protected AssetManager assets;
|
||||
protected BitmapFont font;
|
||||
protected Node guiNode;
|
||||
protected Node canvasNode;
|
||||
protected Node panel;
|
||||
|
||||
private Node bgLayer;
|
||||
private GaussianBlurFilter blurFilter;
|
||||
private float uiScale;
|
||||
|
||||
private final String MAP_CLICK = "_MsClick_" + System.identityHashCode(this);
|
||||
|
||||
private record Btn(float x, float y, float w, float h, Runnable action) {}
|
||||
private final List<Btn> buttons = new ArrayList<>();
|
||||
|
||||
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
protected void initialize(Application application) {
|
||||
app = (SimpleApplication) application;
|
||||
assets = app.getAssetManager();
|
||||
guiNode = app.getGuiNode();
|
||||
font = assets.loadFont("Interface/Fonts/Default.fnt");
|
||||
onInitMenu();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEnable() {
|
||||
uiScale = MenuCanvas.getScale(app.getCamera());
|
||||
bgLayer = buildBgLayer();
|
||||
canvasNode = buildCanvas();
|
||||
if (bgLayer != null) guiNode.attachChild(bgLayer);
|
||||
if (canvasNode != null) guiNode.attachChild(canvasNode);
|
||||
|
||||
onEnableExtras();
|
||||
|
||||
buttons.clear();
|
||||
panel = new Node("ms-panel");
|
||||
buildUI();
|
||||
canvasNode.attachChild(panel);
|
||||
|
||||
if (withBlur()) applyBlur();
|
||||
app.getInputManager().setCursorVisible(true);
|
||||
app.getInputManager().addMapping(MAP_CLICK, new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
|
||||
app.getInputManager().addListener(clickListener, MAP_CLICK);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDisable() {
|
||||
onDisableExtras();
|
||||
app.getInputManager().removeListener(clickListener);
|
||||
if (app.getInputManager().hasMapping(MAP_CLICK)) {
|
||||
app.getInputManager().deleteMapping(MAP_CLICK);
|
||||
}
|
||||
app.getInputManager().setCursorVisible(false);
|
||||
if (bgLayer != null) { guiNode.detachChild(bgLayer); bgLayer = null; }
|
||||
if (canvasNode != null) { guiNode.detachChild(canvasNode); canvasNode = null; }
|
||||
panel = null;
|
||||
buttons.clear();
|
||||
if (withBlur()) removeBlur();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cleanup(Application application) {}
|
||||
|
||||
// ── Abstrakt ──────────────────────────────────────────────────────────────
|
||||
|
||||
/** Inhalt aufbauen — addButton(), addQuad(), crispText() stehen bereit. */
|
||||
protected abstract void buildUI();
|
||||
|
||||
// ── Optionale Hooks ───────────────────────────────────────────────────────
|
||||
|
||||
protected void onInitMenu() {}
|
||||
protected void onEnableExtras() {}
|
||||
protected void onDisableExtras() {}
|
||||
protected boolean withBlur() { return false; }
|
||||
|
||||
protected Node buildBgLayer() { return MenuCanvas.createBgLayer(assets, app.getCamera()); }
|
||||
protected Node buildCanvas() { return MenuCanvas.createCanvas(app.getCamera()); }
|
||||
|
||||
// ── Panel-Initialisierung ─────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Baut NinePatch-Panel + dunklen Header + Titeltext auf.
|
||||
* Muss am Anfang von buildUI() aufgerufen werden.
|
||||
*/
|
||||
/** Baut den 80%-Container mit Header für diesen Screen auf. */
|
||||
protected void initPanel(String title) {
|
||||
initPanel(title, UiTheme.COL_WHITE);
|
||||
}
|
||||
|
||||
protected void initPanel(String title, ColorRGBA titleColor) {
|
||||
panel.attachChild(NinePatch.container(assets).build(CONT_X, CONT_Y, CONT_W, CONT_H, -19f));
|
||||
addQuad(panel, CONT_X, CONT_Y + CONT_H - HDR_H, CONT_W, HDR_H, UiTheme.COL_HDR, -18f);
|
||||
BitmapText t = crispText(title, UiTheme.FONT_HEADING, titleColor);
|
||||
t.setLocalTranslation(CONT_X + PAD,
|
||||
CONT_Y + CONT_H - (HDR_H - UiTheme.FONT_HEADING * 0.85f) / 2f, -17f);
|
||||
panel.attachChild(t);
|
||||
}
|
||||
|
||||
// ── Button-Factory ────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Erzeugt einen NinePatch-Button mit zentriertem Label und registriert ihn
|
||||
* für automatische Klick-Erkennung.
|
||||
*/
|
||||
protected void addButton(Node parent, String label,
|
||||
float x, float y, float w, float h,
|
||||
BtnStyle style, Runnable action) {
|
||||
NinePatch patch = switch (style) {
|
||||
case SAVE -> NinePatch.buttonSave(assets);
|
||||
case QUIT -> NinePatch.buttonQuit(assets);
|
||||
case DISABLED -> NinePatch.buttonDisabled(assets);
|
||||
case ARROW -> NinePatch.buttonArrow(assets);
|
||||
default -> NinePatch.button(assets);
|
||||
};
|
||||
parent.attachChild(patch.build(x, y, w, h, 0f));
|
||||
|
||||
ColorRGBA col = (style == BtnStyle.DISABLED) ? UiTheme.COL_MUTED : UiTheme.COL_WHITE;
|
||||
BitmapText lbl = crispText(label, UiTheme.FONT_LABEL, col);
|
||||
float tw = lbl.getLineWidth() / uiScale;
|
||||
lbl.setLocalTranslation(x + (w - tw) / 2f, y + (h + UiTheme.FONT_LABEL * 0.85f) / 2f, 1f);
|
||||
parent.attachChild(lbl);
|
||||
|
||||
if (style != BtnStyle.DISABLED && action != null) {
|
||||
buttons.add(new Btn(x, y, w, h, action));
|
||||
}
|
||||
}
|
||||
|
||||
// ── Klick-Listener ────────────────────────────────────────────────────────
|
||||
|
||||
private final ActionListener clickListener = (name, pressed, tpf) -> {
|
||||
if (!pressed) return;
|
||||
float[] v = toVirtual(app.getInputManager().getCursorPosition());
|
||||
onScreenClick(v);
|
||||
for (Btn b : buttons) {
|
||||
if (v[0] >= b.x() && v[0] <= b.x() + b.w()
|
||||
&& v[1] >= b.y() && v[1] <= b.y() + b.h()) {
|
||||
b.action().run();
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** Hook für Subklassen die auch auf Klicks reagieren (z.B. ConfigScreen Zeilen). */
|
||||
protected void onScreenClick(float[] v) {}
|
||||
|
||||
// ── Blur ──────────────────────────────────────────────────────────────────
|
||||
|
||||
private void applyBlur() {
|
||||
WorldScene ws = app.getStateManager().getState(WorldScene.class);
|
||||
if (ws == null) return;
|
||||
FilterPostProcessor fpp = ws.getSharedFPP();
|
||||
if (fpp == null) return;
|
||||
blurFilter = new GaussianBlurFilter(6f);
|
||||
fpp.addFilter(blurFilter);
|
||||
}
|
||||
|
||||
private void removeBlur() {
|
||||
if (blurFilter == null) return;
|
||||
WorldScene ws = app.getStateManager().getState(WorldScene.class);
|
||||
if (ws != null) {
|
||||
FilterPostProcessor fpp = ws.getSharedFPP();
|
||||
if (fpp != null) fpp.removeFilter(blurFilter);
|
||||
}
|
||||
blurFilter = null;
|
||||
}
|
||||
|
||||
// ── Shared Helpers ────────────────────────────────────────────────────────
|
||||
|
||||
/** Erzeugt BitmapText mit Counter-Scale-Technik für scharfes Rendering auf WQHD. */
|
||||
protected BitmapText crispText(String text, float virtualSize, ColorRGBA color) {
|
||||
return UiTheme.crispText(font, text, virtualSize, color, uiScale);
|
||||
}
|
||||
|
||||
/** Bildschirmkoordinaten → virtuelle Canvas-Koordinaten. */
|
||||
protected float[] toVirtual(Vector2f screen) {
|
||||
float scale = MenuCanvas.getScale(app.getCamera());
|
||||
float ox = (app.getCamera().getWidth() - MenuCanvas.REF_W * scale) / 2f;
|
||||
float oy = (app.getCamera().getHeight() - MenuCanvas.REF_H * scale) / 2f;
|
||||
return new float[]{ (screen.x - ox) / scale, (screen.y - oy) / scale };
|
||||
}
|
||||
|
||||
protected Geometry addQuad(Node parent, float x, float y, float w, float h,
|
||||
ColorRGBA col, float z) {
|
||||
Geometry g = new Geometry("q", new Quad(w, h));
|
||||
Material m = new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
m.setColor("Color", col.clone());
|
||||
m.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
||||
g.setQueueBucket(RenderQueue.Bucket.Gui);
|
||||
g.setMaterial(m);
|
||||
g.setLocalTranslation(x, y, z);
|
||||
parent.attachChild(g);
|
||||
return g;
|
||||
}
|
||||
|
||||
protected float getUiScale() { return uiScale; }
|
||||
}
|
||||
324
blight-game/src/main/java/de/blight/game/config/NinePatch.java
Normal file
324
blight-game/src/main/java/de/blight/game/config/NinePatch.java
Normal file
@@ -0,0 +1,324 @@
|
||||
package de.blight.game.config;
|
||||
|
||||
import com.jme3.asset.AssetInfo;
|
||||
import com.jme3.asset.AssetKey;
|
||||
import com.jme3.asset.AssetManager;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.material.RenderState;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.VertexBuffer;
|
||||
import com.jme3.scene.shape.Quad;
|
||||
import com.jme3.texture.Texture;
|
||||
import com.jme3.texture.Texture2D;
|
||||
import com.jme3.texture.image.ColorSpace;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Rendert eine Textur als 9-Patch: Ecken bleiben unverzerrt,
|
||||
* Kanten strecken in einer Achse, Mitte streckt in beiden.
|
||||
*
|
||||
* Borders werden automatisch aus Guide-Pixeln erkannt (Alpha=35 oder 140).
|
||||
* Die Guide-Pixel werden beim Laden durch die benachbarte Rahmenfarbe ersetzt,
|
||||
* sodass sie im Rendering vollständig unsichtbar sind.
|
||||
*
|
||||
* Verwendung:
|
||||
* Node n = NinePatch.panel(assets).build(px, py, pw, ph, -1f);
|
||||
* parentNode.attachChild(n);
|
||||
*/
|
||||
public class NinePatch {
|
||||
|
||||
private static final Set<Integer> GUIDE_ALPHAS = Set.of(35, 140);
|
||||
|
||||
// ── Vorkonfigurierte Factories ──────────────────────────────────────────
|
||||
|
||||
public static NinePatch container(AssetManager assets) {
|
||||
return fromPng(assets, "Textures/menu/container.png");
|
||||
}
|
||||
|
||||
public static NinePatch panel(AssetManager assets) {
|
||||
return fromPng(assets, "Textures/menu/panel.png");
|
||||
}
|
||||
|
||||
public static NinePatch button(AssetManager assets) {
|
||||
return fromPng(assets, "Textures/menu/button.png");
|
||||
}
|
||||
|
||||
public static NinePatch buttonSave(AssetManager assets) {
|
||||
return fromPng(assets, "Textures/menu/button_save.png");
|
||||
}
|
||||
|
||||
public static NinePatch buttonQuit(AssetManager assets) {
|
||||
return fromPng(assets, "Textures/menu/button_quit.png");
|
||||
}
|
||||
|
||||
public static NinePatch buttonArrow(AssetManager assets) {
|
||||
return fromPng(assets, "Textures/menu/button_arrow.png");
|
||||
}
|
||||
|
||||
public static NinePatch buttonDisabled(AssetManager assets) {
|
||||
return fromPng(assets, "Textures/menu/button_disabled.png");
|
||||
}
|
||||
|
||||
public static NinePatch tabActive(AssetManager assets) {
|
||||
return fromPng(assets, "Textures/menu/tab_active.png");
|
||||
}
|
||||
|
||||
public static NinePatch tabInactive(AssetManager assets) {
|
||||
return fromPng(assets, "Textures/menu/tab_inactive.png");
|
||||
}
|
||||
|
||||
public static NinePatch itemCell(AssetManager assets) {
|
||||
return fromPng(assets, "Textures/menu/item_cell.png");
|
||||
}
|
||||
|
||||
// Scrollbars haben keine Guide-Pixel → explizite Werte + normales Laden
|
||||
public static NinePatch scrollbarTrack(AssetManager assets) {
|
||||
Texture tex = assets.loadTexture("Textures/menu/scrollbar_track.png");
|
||||
return new NinePatch(assets, tex, 16, 32, 4, 4, 4, 4);
|
||||
}
|
||||
|
||||
public static NinePatch scrollbarThumb(AssetManager assets) {
|
||||
Texture tex = assets.loadTexture("Textures/menu/scrollbar_thumb.png");
|
||||
return new NinePatch(assets, tex, 16, 32, 4, 4, 4, 4);
|
||||
}
|
||||
|
||||
// ── Auto-Detection ─────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Lädt PNG, erkennt Borders aus Guide-Pixeln, ersetzt die Guide-Pixel
|
||||
* durch die benachbarte Rahmenfarbe und erzeugt eine aufbereitete Textur.
|
||||
*/
|
||||
private static NinePatch fromPng(AssetManager assets, String path) {
|
||||
AssetInfo info = assets.locateAsset(new AssetKey<>(path));
|
||||
if (info == null) {
|
||||
throw new RuntimeException("NinePatch-Asset nicht gefunden: " + path);
|
||||
}
|
||||
BufferedImage img;
|
||||
try (InputStream is = info.openStream()) {
|
||||
img = ImageIO.read(is);
|
||||
if (img == null) {
|
||||
throw new RuntimeException("ImageIO.read() lieferte null für: " + path);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("NinePatch-Ladefehler: " + path, e);
|
||||
}
|
||||
|
||||
// Sicherstellen, dass wir ARGB-Zugriff haben
|
||||
if (img.getType() != BufferedImage.TYPE_INT_ARGB) {
|
||||
BufferedImage argb = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB);
|
||||
argb.getGraphics().drawImage(img, 0, 0, null);
|
||||
img = argb;
|
||||
}
|
||||
|
||||
int w = img.getWidth();
|
||||
int h = img.getHeight();
|
||||
int[] borders = detectBorders(img, w, h);
|
||||
int l = borders[0], r = borders[1], t = borders[2], b = borders[3];
|
||||
|
||||
// Guide-Pixel durch Rahmen-Nachbarfarbe ersetzen
|
||||
stripGuides(img, w, h, l - 1, w - r, t - 1, h - b);
|
||||
|
||||
Texture2D tex = toJmeTexture(img, w, h);
|
||||
return new NinePatch(assets, tex, w, h, l, r, t, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Erkennt Border-Werte anhand der Guide-Positionen.
|
||||
*
|
||||
* Mittlere Zeile → linke/rechte Guide-X-Position
|
||||
* Mittlere Spalte → obere/untere Guide-Y-Position (PNG-Koordinaten)
|
||||
*
|
||||
* Formel: linke/obere Border = guidePos + 1 (Guide im fixen Strip, kein Stretch).
|
||||
* rechte/untere Border = texDim - guidePos (Guide ist erstes Pixel des Strips).
|
||||
*/
|
||||
private static int[] detectBorders(BufferedImage img, int w, int h) {
|
||||
int midX = w / 2;
|
||||
int midY = h / 2;
|
||||
|
||||
int lx = -1, rx = -1;
|
||||
for (int x = 0; x < w; x++) {
|
||||
int a = (img.getRGB(x, midY) >> 24) & 0xff;
|
||||
if (GUIDE_ALPHAS.contains(a)) {
|
||||
if (lx < 0) { lx = x; }
|
||||
rx = x;
|
||||
}
|
||||
}
|
||||
|
||||
int ty = -1, by = -1;
|
||||
for (int y = 0; y < h; y++) {
|
||||
int a = (img.getRGB(midX, y) >> 24) & 0xff;
|
||||
if (GUIDE_ALPHAS.contains(a)) {
|
||||
if (ty < 0) { ty = y; }
|
||||
by = y;
|
||||
}
|
||||
}
|
||||
|
||||
int l = lx >= 0 ? lx + 1 : w / 4;
|
||||
int r = rx >= 0 ? w - rx : w / 4;
|
||||
int t = ty >= 0 ? ty + 1 : h / 4;
|
||||
int b = by >= 0 ? h - by : h / 4;
|
||||
|
||||
return new int[]{l, r, t, b};
|
||||
}
|
||||
|
||||
/**
|
||||
* Ersetzt Guide-Pixel durch die Farbe des nächsten Nicht-Guide-Pixels
|
||||
* in Richtung des Rahmen-Rands (weg von der Bildmitte).
|
||||
*
|
||||
* lx, rx: Spaltenindizes der linken/rechten Guide-Linie
|
||||
* ty, by: Zeilenindizes der oberen/unteren Guide-Linie (PNG-Koordinaten)
|
||||
*/
|
||||
private static void stripGuides(BufferedImage img, int w, int h, int lx, int rx, int ty, int by) {
|
||||
// Snapshot der Guide-Positionen (vor Modifikation)
|
||||
boolean[][] isGuide = new boolean[h][w];
|
||||
for (int y = 0; y < h; y++) {
|
||||
for (int x = 0; x < w; x++) {
|
||||
int a = (img.getRGB(x, y) >> 24) & 0xff;
|
||||
isGuide[y][x] = GUIDE_ALPHAS.contains(a);
|
||||
}
|
||||
}
|
||||
|
||||
for (int y = 0; y < h; y++) {
|
||||
for (int x = 0; x < w; x++) {
|
||||
if (!isGuide[y][x]) { continue; }
|
||||
|
||||
// Richtung: weg von der Bildmitte, in Richtung des Rahmens
|
||||
boolean col = (x == lx || x == rx);
|
||||
boolean row = (y == ty || y == by);
|
||||
|
||||
int dx = col ? (x == lx ? -1 : +1) : 0;
|
||||
int dy = row ? (y == ty ? -1 : +1) : 0;
|
||||
|
||||
if (dx == 0 && dy == 0) {
|
||||
// Verirrter Guide-Pixel außerhalb der bekannten Linien
|
||||
img.setRGB(x, y, 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
img.setRGB(x, y, walkToNonGuide(img, isGuide, x + dx, y + dy, dx, dy, w, h));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int walkToNonGuide(BufferedImage img, boolean[][] isGuide,
|
||||
int x, int y, int dx, int dy, int w, int h) {
|
||||
while (x >= 0 && x < w && y >= 0 && y < h) {
|
||||
if (!isGuide[y][x]) { return img.getRGB(x, y); }
|
||||
x += dx;
|
||||
y += dy;
|
||||
}
|
||||
return 0; // Fallback: transparent
|
||||
}
|
||||
|
||||
/**
|
||||
* Konvertiert ein BufferedImage in eine JME3-Textur.
|
||||
* Zeilen werden von unten nach oben gelesen (entspricht JME3-Y-Flip beim PNG-Laden).
|
||||
*/
|
||||
private static Texture2D toJmeTexture(BufferedImage img, int w, int h) {
|
||||
ByteBuffer buf = ByteBuffer.allocateDirect(w * h * 4);
|
||||
int[] row = new int[w];
|
||||
for (int y = h - 1; y >= 0; y--) {
|
||||
img.getRGB(0, y, w, 1, row, 0, w);
|
||||
for (int argb : row) {
|
||||
buf.put((byte) ((argb >> 16) & 0xff)); // R
|
||||
buf.put((byte) ((argb >> 8) & 0xff)); // G
|
||||
buf.put((byte) ( argb & 0xff)); // B
|
||||
buf.put((byte) ((argb >> 24) & 0xff)); // A
|
||||
}
|
||||
}
|
||||
buf.flip();
|
||||
com.jme3.texture.Image jmeImg = new com.jme3.texture.Image(
|
||||
com.jme3.texture.Image.Format.RGBA8, w, h, buf, ColorSpace.sRGB);
|
||||
return new Texture2D(jmeImg);
|
||||
}
|
||||
|
||||
// ── Instanz ────────────────────────────────────────────────────────────
|
||||
|
||||
private final AssetManager assets;
|
||||
private final Texture tex;
|
||||
private final float texW, texH;
|
||||
private final float l, r, t, b;
|
||||
|
||||
public NinePatch(AssetManager assets, Texture tex,
|
||||
int texW, int texH, int l, int r, int t, int b) {
|
||||
this.assets = assets;
|
||||
this.tex = tex;
|
||||
this.texW = texW;
|
||||
this.texH = texH;
|
||||
this.l = l;
|
||||
this.r = r;
|
||||
this.t = t;
|
||||
this.b = b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Baut den Node mit 9 Quads. x/y sind Bildschirm-Koordinaten
|
||||
* (Gui-Space, y=0 unten). z bestimmt die Render-Reihenfolge.
|
||||
*/
|
||||
public Node build(float x, float y, float w, float h, float z) {
|
||||
tex.setWrap(Texture.WrapMode.EdgeClamp);
|
||||
tex.setMagFilter(Texture.MagFilter.Nearest);
|
||||
|
||||
Material mat = new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setTexture("ColorMap", tex);
|
||||
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
||||
|
||||
// UV-Koordinaten (V=0 = Textur-Unterkante in JME3)
|
||||
float u1 = l / texW;
|
||||
float u2 = (texW - r) / texW;
|
||||
float v1 = b / texH; // unterer Rand in UV
|
||||
float v2 = (texH - t) / texH; // oberer Rand in UV
|
||||
|
||||
// Bildschirm-Positionen der Nahtlinien
|
||||
float sx1 = x + l, sx2 = x + w - r;
|
||||
float sy1 = y + b, sy2 = y + h - t;
|
||||
|
||||
Node n = new Node("ninepatch");
|
||||
// Reihe unten
|
||||
n.attachChild(q(mat, x, y, l, b, 0, 0, u1, v1, z));
|
||||
n.attachChild(q(mat, sx1, y, sx2-sx1, b, u1, 0, u2, v1, z));
|
||||
n.attachChild(q(mat, sx2, y, r, b, u2, 0, 1, v1, z));
|
||||
// Reihe mitte
|
||||
n.attachChild(q(mat, x, sy1, l, sy2-sy1, 0, v1, u1, v2, z));
|
||||
n.attachChild(q(mat, sx1, sy1, sx2-sx1, sy2-sy1, u1, v1, u2, v2, z));
|
||||
n.attachChild(q(mat, sx2, sy1, r, sy2-sy1, u2, v1, 1, v2, z));
|
||||
// Reihe oben
|
||||
n.attachChild(q(mat, x, sy2, l, t, 0, v2, u1, 1, z));
|
||||
n.attachChild(q(mat, sx1, sy2, sx2-sx1, t, u1, v2, u2, 1, z));
|
||||
n.attachChild(q(mat, sx2, sy2, r, t, u2, v2, 1, 1, z));
|
||||
return n;
|
||||
}
|
||||
|
||||
private static Geometry q(Material mat,
|
||||
float x, float y, float w, float h,
|
||||
float uMin, float vMin, float uMax, float vMax,
|
||||
float z) {
|
||||
if (w <= 0 || h <= 0) { return new Geometry(); }
|
||||
Quad mesh = new Quad(w, h);
|
||||
|
||||
// UV-Buffer überschreiben (JME3-Quad Vertex-Reihenfolge: BL, BR, TR, TL)
|
||||
FloatBuffer uvBuf = mesh.getFloatBuffer(VertexBuffer.Type.TexCoord);
|
||||
uvBuf.rewind();
|
||||
uvBuf.put(uMin).put(vMin) // BL
|
||||
.put(uMax).put(vMin) // BR
|
||||
.put(uMax).put(vMax) // TR
|
||||
.put(uMin).put(vMax); // TL
|
||||
uvBuf.rewind();
|
||||
mesh.getBuffer(VertexBuffer.Type.TexCoord).setUpdateNeeded();
|
||||
|
||||
Geometry g = new Geometry("np", mesh);
|
||||
g.setMaterial(mat);
|
||||
g.setQueueBucket(RenderQueue.Bucket.Gui);
|
||||
g.setLocalTranslation(x, y, z);
|
||||
return g;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,247 @@
|
||||
package de.blight.game.config;
|
||||
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.app.state.BaseAppState;
|
||||
import com.jme3.asset.AssetManager;
|
||||
import com.jme3.font.BitmapFont;
|
||||
import com.jme3.font.BitmapText;
|
||||
import com.jme3.input.KeyInput;
|
||||
import com.jme3.input.controls.ActionListener;
|
||||
import com.jme3.input.controls.KeyTrigger;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.material.RenderState;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.post.FilterPostProcessor;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.shape.Quad;
|
||||
import de.blight.game.post.GaussianBlurFilter;
|
||||
import de.blight.game.scene.WorldScene;
|
||||
|
||||
/**
|
||||
* Abstrakte Basisklasse für alle Overlay-Screens (Inventar, Charakter, Quest).
|
||||
*
|
||||
* Kümmert sich um:
|
||||
* - Mutual exclusion: nur ein Overlay gleichzeitig aktiv
|
||||
* - 90%-Bildschirm-Panel mit dunklem Header
|
||||
* - bgLayer + canvasNode Aufbau/Abbau
|
||||
* - Blur-Filter-Verwaltung
|
||||
* - Welt-Pause/Resume
|
||||
* - ESC-Schließen
|
||||
* - Crisp-Text-Helper (Counter-Scale für scharfe Schrift)
|
||||
* - toVirtual-Helper (Screen → virtuelle Canvas-Koordinaten)
|
||||
*/
|
||||
public abstract class OverlayState extends BaseAppState {
|
||||
|
||||
// ── Mutual Exclusion ──────────────────────────────────────────────────────
|
||||
|
||||
private static OverlayState activeOverlay;
|
||||
|
||||
// ── Panel-Dimensionen (90% des Referenzraums) ─────────────────────────────
|
||||
|
||||
public static final float PW = MenuCanvas.CONT_W;
|
||||
public static final float PH = MenuCanvas.CONT_H;
|
||||
public static final float PX = MenuCanvas.CONT_X;
|
||||
public static final float PY = MenuCanvas.CONT_Y;
|
||||
public static final float HDR_H = 44f;
|
||||
public static final float PAD = 16f;
|
||||
|
||||
// Content-Bereich (innerhalb Header und Padding)
|
||||
public static final float CX = PX + PAD;
|
||||
public static final float CY = PY + PAD;
|
||||
public static final float CW = PW - PAD * 2f;
|
||||
public static final float CH = PH - HDR_H - PAD * 2f;
|
||||
|
||||
// ── Input-Mapping für ESC (pro-Instanz eindeutiger Name) ──────────────────
|
||||
|
||||
private final String MAP_ESC = "_OverlayEsc_" + System.identityHashCode(this);
|
||||
|
||||
// ── Zustand, den Unterklassen nutzen ──────────────────────────────────────
|
||||
|
||||
protected SimpleApplication app;
|
||||
protected AssetManager assets;
|
||||
protected BitmapFont font;
|
||||
protected Node guiNode;
|
||||
protected Node canvasNode;
|
||||
|
||||
// ── Interner Zustand ──────────────────────────────────────────────────────
|
||||
|
||||
private Node bgLayer;
|
||||
private GaussianBlurFilter blurFilter;
|
||||
private float uiScale;
|
||||
|
||||
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
||||
|
||||
@Override
|
||||
protected void initialize(Application application) {
|
||||
app = (SimpleApplication) application;
|
||||
assets = app.getAssetManager();
|
||||
guiNode = app.getGuiNode();
|
||||
font = assets.loadFont("Interface/Fonts/Default.fnt");
|
||||
|
||||
app.getInputManager().addMapping(MAP_ESC, new KeyTrigger(KeyInput.KEY_ESCAPE));
|
||||
app.getInputManager().addListener(escListener, MAP_ESC);
|
||||
|
||||
initOverlayKeys();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEnable() {
|
||||
// Mutual exclusion: aktives Overlay schließen
|
||||
if (activeOverlay != null && activeOverlay != this) {
|
||||
activeOverlay.setEnabled(false);
|
||||
}
|
||||
activeOverlay = this;
|
||||
|
||||
// Canvas + bgLayer aufbauen
|
||||
uiScale = MenuCanvas.getScale(app.getCamera());
|
||||
bgLayer = MenuCanvas.createBgLayer(assets, app.getCamera());
|
||||
canvasNode = MenuCanvas.createCanvas(app.getCamera());
|
||||
guiNode.attachChild(bgLayer);
|
||||
guiNode.attachChild(canvasNode);
|
||||
|
||||
// Äußerer Container: NinePatch-Rahmen (Hintergrund + Border) → Header
|
||||
Node panel = new Node("overlay-panel");
|
||||
panel.attachChild(NinePatch.container(assets).build(PX, PY, PW, PH, -19f));
|
||||
|
||||
// Header-Balken
|
||||
addQuad(panel, PX, PY + PH - HDR_H, PW, HDR_H, UiTheme.COL_HDR, -18f);
|
||||
|
||||
// Titel
|
||||
BitmapText titleTxt = crispText(getTitle(), UiTheme.FONT_TITLE, UiTheme.COL_WHITE);
|
||||
titleTxt.setLocalTranslation(PX + PAD, PY + PH - PAD / 2f, -17f);
|
||||
panel.attachChild(titleTxt);
|
||||
|
||||
// Inhalt vom Subtyp aufbauen
|
||||
buildContent(panel, uiScale);
|
||||
|
||||
canvasNode.attachChild(panel);
|
||||
|
||||
// Blur + Welt pausieren + Cursor sichtbar
|
||||
WorldScene ws = app.getStateManager().getState(WorldScene.class);
|
||||
if (ws != null) {
|
||||
ws.setPaused(true);
|
||||
FilterPostProcessor fpp = ws.getSharedFPP();
|
||||
if (fpp != null) {
|
||||
blurFilter = new GaussianBlurFilter(6f);
|
||||
fpp.addFilter(blurFilter);
|
||||
}
|
||||
}
|
||||
app.getInputManager().setCursorVisible(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDisable() {
|
||||
if (activeOverlay == this) {
|
||||
activeOverlay = null;
|
||||
}
|
||||
|
||||
// Canvas + bgLayer abbauen
|
||||
if (bgLayer != null) { guiNode.detachChild(bgLayer); bgLayer = null; }
|
||||
if (canvasNode != null) { guiNode.detachChild(canvasNode); canvasNode = null; }
|
||||
|
||||
// Blur entfernen + Welt resumieren + Cursor ausblenden
|
||||
WorldScene ws = app.getStateManager().getState(WorldScene.class);
|
||||
if (ws != null) {
|
||||
ws.setPaused(false);
|
||||
if (blurFilter != null) {
|
||||
FilterPostProcessor fpp = ws.getSharedFPP();
|
||||
if (fpp != null) { fpp.removeFilter(blurFilter); }
|
||||
blurFilter = null;
|
||||
}
|
||||
}
|
||||
app.getInputManager().setCursorVisible(false);
|
||||
|
||||
onOverlayClosed();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cleanup(Application application) {
|
||||
app.getInputManager().deleteMapping(MAP_ESC);
|
||||
cleanupOverlayKeys();
|
||||
}
|
||||
|
||||
// ── ESC-Listener ─────────────────────────────────────────────────────────
|
||||
|
||||
private final ActionListener escListener = (name, pressed, tpf) -> {
|
||||
if (pressed && isEnabled()) {
|
||||
setEnabled(false);
|
||||
}
|
||||
};
|
||||
|
||||
// ── Abstrakte Methoden für Unterklassen ───────────────────────────────────
|
||||
|
||||
/** Überschriftstext im Header-Balken. */
|
||||
protected abstract String getTitle();
|
||||
|
||||
/**
|
||||
* Inhalt aufbauen. Der panel-Node ist bereits mit NinePatch + Header versehen.
|
||||
* Unterklassen fügen hier ihre Widgets ein.
|
||||
*
|
||||
* @param panel Empfänger-Node (bereits an canvasNode gehängt)
|
||||
* @param scale Canvas-Skalierungsfaktor (für crispText)
|
||||
*/
|
||||
protected abstract void buildContent(Node panel, float scale);
|
||||
|
||||
/**
|
||||
* Toggle-Taste und weitere Eingaben in initialize() registrieren.
|
||||
* Wird von {@link #initialize} aufgerufen.
|
||||
*/
|
||||
protected abstract void initOverlayKeys();
|
||||
|
||||
/**
|
||||
* Toggle-Taste und weitere Eingaben in cleanup() deregistrieren.
|
||||
* Wird von {@link #cleanup} aufgerufen.
|
||||
*/
|
||||
protected abstract void cleanupOverlayKeys();
|
||||
|
||||
// ── Optionaler Hook ───────────────────────────────────────────────────────
|
||||
|
||||
/** Wird nach onDisable() aufgerufen – für subklassenspezifisches Teardown. */
|
||||
protected void onOverlayClosed() {}
|
||||
|
||||
// ── Shared Helpers ────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Erstellt einen BitmapText mit Counter-Scale-Technik:
|
||||
* - Schrift wird mit virtualSize*uiScale Pixeln gerendert (scharf)
|
||||
* - LocalScale 1/uiScale bringt ihn zurück auf virtualSize virtuelle Einheiten
|
||||
*/
|
||||
protected BitmapText crispText(String text, float virtualSize, ColorRGBA color) {
|
||||
return UiTheme.crispText(font, text, virtualSize, color, uiScale);
|
||||
}
|
||||
|
||||
/**
|
||||
* Übersetzt Bildschirmkoordinaten (JME3 Pixel) in virtuelle Canvas-Koordinaten.
|
||||
*/
|
||||
protected float[] toVirtual(Vector2f screen) {
|
||||
float scale = MenuCanvas.getScale(app.getCamera());
|
||||
float ox = (app.getCamera().getWidth() - MenuCanvas.REF_W * scale) / 2f;
|
||||
float oy = (app.getCamera().getHeight() - MenuCanvas.REF_H * scale) / 2f;
|
||||
return new float[]{ (screen.x - ox) / scale, (screen.y - oy) / scale };
|
||||
}
|
||||
|
||||
/**
|
||||
* Erzeugt ein farbiges Quad, hängt es an parent und gibt die Geometry zurück.
|
||||
*/
|
||||
protected Geometry addQuad(Node parent, float x, float y, float w, float h,
|
||||
ColorRGBA col, float z) {
|
||||
Geometry g = new Geometry("q", new Quad(w, h));
|
||||
Material m = new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
m.setColor("Color", col.clone());
|
||||
m.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
||||
g.setQueueBucket(RenderQueue.Bucket.Gui);
|
||||
g.setMaterial(m);
|
||||
g.setLocalTranslation(x, y, z);
|
||||
parent.attachChild(g);
|
||||
return g;
|
||||
}
|
||||
|
||||
/** Aktueller uiScale-Wert (gesetzt beim letzten onEnable). */
|
||||
protected float getUiScale() {
|
||||
return uiScale;
|
||||
}
|
||||
}
|
||||
228
blight-game/src/main/java/de/blight/game/config/UiTheme.java
Normal file
228
blight-game/src/main/java/de/blight/game/config/UiTheme.java
Normal file
@@ -0,0 +1,228 @@
|
||||
package de.blight.game.config;
|
||||
|
||||
import com.jme3.asset.AssetManager;
|
||||
import com.jme3.font.BitmapFont;
|
||||
import com.jme3.font.BitmapText;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.material.RenderState;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.shape.Quad;
|
||||
|
||||
/**
|
||||
* Zentrale Theme-Konstanten und Widget-Factories für alle Overlay-Screens.
|
||||
* Keine Instanzierung – alles statisch.
|
||||
*/
|
||||
public final class UiTheme {
|
||||
|
||||
private UiTheme() {}
|
||||
|
||||
// ── Farben ────────────────────────────────────────────────────────────────
|
||||
|
||||
public static final ColorRGBA COL_PANEL = new ColorRGBA(0.20f, 0.20f, 0.32f, 0.97f);
|
||||
public static final ColorRGBA COL_HDR = new ColorRGBA(0.10f, 0.10f, 0.20f, 1.00f);
|
||||
public static final ColorRGBA COL_TAB_ON = new ColorRGBA(0.22f, 0.22f, 0.42f, 1.00f);
|
||||
public static final ColorRGBA COL_TAB_OFF = new ColorRGBA(0.12f, 0.12f, 0.20f, 1.00f);
|
||||
public static final ColorRGBA COL_WHITE = ColorRGBA.White.clone();
|
||||
public static final ColorRGBA COL_MUTED = new ColorRGBA(0.55f, 0.55f, 0.60f, 1.00f);
|
||||
public static final ColorRGBA COL_GOLD = new ColorRGBA(1.00f, 0.85f, 0.20f, 1.00f);
|
||||
public static final ColorRGBA COL_CELL = new ColorRGBA(0.14f, 0.14f, 0.22f, 1.00f);
|
||||
public static final ColorRGBA COL_CELL_FRAME = new ColorRGBA(0.22f, 0.22f, 0.35f, 1.00f);
|
||||
public static final ColorRGBA COL_DIVIDER = new ColorRGBA(0.22f, 0.22f, 0.35f, 0.80f);
|
||||
public static final ColorRGBA COL_SUBHDR = new ColorRGBA(0.10f, 0.10f, 0.18f, 0.80f);
|
||||
public static final ColorRGBA COL_THUMB_BG = new ColorRGBA(0.20f, 0.20f, 0.28f, 1.00f);
|
||||
public static final ColorRGBA COL_BADGE = new ColorRGBA(0.04f, 0.04f, 0.07f, 0.92f);
|
||||
public static final ColorRGBA COL_SUBCAT_TXT = new ColorRGBA(0.55f, 0.78f, 1.00f, 1.00f);
|
||||
|
||||
// Character-State-Farben
|
||||
public static final ColorRGBA COL_HP = new ColorRGBA(0.85f, 0.10f, 0.10f, 1.00f);
|
||||
public static final ColorRGBA COL_STAMINA = new ColorRGBA(0.90f, 0.80f, 0.10f, 1.00f);
|
||||
public static final ColorRGBA COL_MANA = new ColorRGBA(0.10f, 0.35f, 0.90f, 1.00f);
|
||||
public static final ColorRGBA COL_BAR_BG = new ColorRGBA(0.10f, 0.10f, 0.14f, 1.00f);
|
||||
public static final ColorRGBA COL_SECTION = new ColorRGBA(0.10f, 0.10f, 0.18f, 0.90f);
|
||||
public static final ColorRGBA COL_CARD = new ColorRGBA(0.12f, 0.12f, 0.20f, 1.00f);
|
||||
public static final ColorRGBA COL_CARD_BRD = new ColorRGBA(0.22f, 0.22f, 0.35f, 1.00f);
|
||||
public static final ColorRGBA COL_UNLOCKED = new ColorRGBA(0.40f, 0.90f, 0.40f, 1.00f);
|
||||
public static final ColorRGBA COL_LOCKED = new ColorRGBA(0.35f, 0.35f, 0.38f, 1.00f);
|
||||
public static final ColorRGBA COL_PIP_ON = COL_GOLD;
|
||||
public static final ColorRGBA COL_PIP_OFF = new ColorRGBA(0.22f, 0.22f, 0.28f, 1.00f);
|
||||
public static final ColorRGBA COL_XP_BAR = new ColorRGBA(0.30f, 0.65f, 1.00f, 1.00f);
|
||||
|
||||
// Quest-State-Farben
|
||||
public static final ColorRGBA COL_SELECTED = new ColorRGBA(0.20f, 0.20f, 0.38f, 1.00f);
|
||||
public static final ColorRGBA COL_ITEM_BG = new ColorRGBA(0.12f, 0.12f, 0.20f, 1.00f);
|
||||
public static final ColorRGBA COL_DETAIL_BG = new ColorRGBA(0.08f, 0.08f, 0.14f, 1.00f);
|
||||
public static final ColorRGBA COL_DIALOG_BG = new ColorRGBA(0.10f, 0.10f, 0.18f, 0.90f);
|
||||
public static final ColorRGBA COL_XP = new ColorRGBA(0.40f, 0.90f, 0.40f, 1.00f);
|
||||
public static final ColorRGBA COL_SUCCESS = new ColorRGBA(0.30f, 0.80f, 0.30f, 1.00f);
|
||||
public static final ColorRGBA COL_COMPLETED = new ColorRGBA(0.40f, 0.65f, 0.40f, 1.00f);
|
||||
public static final ColorRGBA COL_TYPE_TALK = new ColorRGBA(0.40f, 0.75f, 1.00f, 1.00f);
|
||||
public static final ColorRGBA COL_TYPE_ITEM = new ColorRGBA(1.00f, 0.75f, 0.25f, 1.00f);
|
||||
public static final ColorRGBA COL_TYPE_BRING = new ColorRGBA(0.85f, 0.45f, 1.00f, 1.00f);
|
||||
public static final ColorRGBA COL_TYPE_FOLLOW = new ColorRGBA(0.45f, 1.00f, 0.85f, 1.00f);
|
||||
public static final ColorRGBA COL_TYPE_INTERACT= new ColorRGBA(1.00f, 0.55f, 0.40f, 1.00f);
|
||||
|
||||
// Checkbox
|
||||
public static final ColorRGBA COL_CHECK_BOX = new ColorRGBA(0.15f, 0.15f, 0.25f, 1.00f);
|
||||
public static final ColorRGBA COL_CHECK_BORDER = new ColorRGBA(0.30f, 0.30f, 0.50f, 1.00f);
|
||||
public static final ColorRGBA COL_CHECK_TICK = new ColorRGBA(0.40f, 0.90f, 0.40f, 1.00f);
|
||||
|
||||
// ComboBox
|
||||
public static final ColorRGBA COL_COMBO_BG = new ColorRGBA(0.12f, 0.12f, 0.22f, 1.00f);
|
||||
public static final ColorRGBA COL_COMBO_BORDER = new ColorRGBA(0.28f, 0.28f, 0.45f, 1.00f);
|
||||
public static final ColorRGBA COL_COMBO_ARROW = new ColorRGBA(0.70f, 0.70f, 0.80f, 1.00f);
|
||||
|
||||
// ── Schriftgrößen (virtuelle Pixel) ───────────────────────────────────────
|
||||
|
||||
public static final float FONT_TITLE = 20f;
|
||||
public static final float FONT_HEADING = 16f;
|
||||
public static final float FONT_LABEL = 14f;
|
||||
public static final float FONT_BODY = 12f;
|
||||
public static final float FONT_SMALL = 11f;
|
||||
public static final float FONT_TINY = 10f;
|
||||
|
||||
// ── Widget: Checkbox ──────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Erzeugt eine Checkbox-Node in virtuellen Canvas-Koordinaten.
|
||||
* Die Node-UserData "bounds" enthält float[]{x, y, 20, 20} für Hit-Testing.
|
||||
*
|
||||
* @param assets AssetManager
|
||||
* @param font BitmapFont für das Label
|
||||
* @param x linke Kante in virtuellen Koordinaten
|
||||
* @param y untere Kante in virtuellen Koordinaten (JME3 Y-up)
|
||||
* @param label Beschriftung rechts der Box
|
||||
* @param checked initialer Zustand
|
||||
* @param scale Canvas-Skalierungsfaktor (von MenuCanvas.getScale)
|
||||
* @return Node, der an den Panel-Node gehängt werden kann
|
||||
*/
|
||||
public static Node checkbox(AssetManager assets, BitmapFont font,
|
||||
float x, float y, String label,
|
||||
boolean checked, float scale) {
|
||||
final float BOX_SZ = 20f;
|
||||
Node n = new Node("checkbox");
|
||||
|
||||
// Rahmen
|
||||
Geometry border = quad(assets, x, y, BOX_SZ, BOX_SZ, COL_CHECK_BORDER, -15f);
|
||||
n.attachChild(border);
|
||||
// Innen
|
||||
Geometry inner = quad(assets, x + 1, y + 1, BOX_SZ - 2, BOX_SZ - 2, COL_CHECK_BOX, -14f);
|
||||
n.attachChild(inner);
|
||||
|
||||
// Häkchen (zwei Quads als Kreuzbalken)
|
||||
if (checked) {
|
||||
// Diagonale von unten-links nach oben-rechts
|
||||
float thick = 3f;
|
||||
Geometry tick1 = quad(assets, x + 3, y + 7, BOX_SZ - 6, thick, COL_CHECK_TICK, -13f);
|
||||
tick1.rotate(0f, 0f, (float) Math.toRadians(45));
|
||||
n.attachChild(tick1);
|
||||
Geometry tick2 = quad(assets, x + 3, y + 7, BOX_SZ - 6, thick, COL_CHECK_TICK, -13f);
|
||||
tick2.rotate(0f, 0f, (float) Math.toRadians(-45));
|
||||
n.attachChild(tick2);
|
||||
}
|
||||
|
||||
// Label rechts der Box
|
||||
if (label != null && !label.isEmpty()) {
|
||||
BitmapText lbl = crispText(font, label, FONT_BODY, COL_WHITE, scale);
|
||||
lbl.setLocalTranslation(x + BOX_SZ + 6, y + BOX_SZ - 2, -14f);
|
||||
n.attachChild(lbl);
|
||||
}
|
||||
|
||||
// Bounds für Hit-Testing speichern
|
||||
n.setUserData("bounds", new float[]{x, y, BOX_SZ, BOX_SZ});
|
||||
return n;
|
||||
}
|
||||
|
||||
// ── Widget: ComboBox ──────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Erzeugt eine einfache ComboBox-Node in virtuellen Canvas-Koordinaten.
|
||||
* Zeigt den ausgewählten Eintrag + Pfeil nach unten.
|
||||
* Die Node-UserData "bounds" enthält float[]{x, y, w, 24} für Hit-Testing.
|
||||
*
|
||||
* @param assets AssetManager
|
||||
* @param font BitmapFont
|
||||
* @param x linke Kante
|
||||
* @param y untere Kante
|
||||
* @param w Breite
|
||||
* @param options alle Einträge
|
||||
* @param selectedIndex ausgewählter Index
|
||||
* @param scale Canvas-Skalierungsfaktor
|
||||
* @return Node
|
||||
*/
|
||||
public static Node comboBox(AssetManager assets, BitmapFont font,
|
||||
float x, float y, float w,
|
||||
String[] options, int selectedIndex,
|
||||
float scale) {
|
||||
final float H = 24f;
|
||||
Node n = new Node("combobox");
|
||||
|
||||
// Hintergrund
|
||||
Geometry bg = quad(assets, x, y, w, H, COL_COMBO_BG, -15f);
|
||||
n.attachChild(bg);
|
||||
// Rahmen
|
||||
Geometry border = quad(assets, x, y, w, 1f, COL_COMBO_BORDER, -14f);
|
||||
n.attachChild(border);
|
||||
Geometry borderT = quad(assets, x, y + H - 1, w, 1f, COL_COMBO_BORDER, -14f);
|
||||
n.attachChild(borderT);
|
||||
Geometry borderL = quad(assets, x, y, 1f, H, COL_COMBO_BORDER, -14f);
|
||||
n.attachChild(borderL);
|
||||
Geometry borderR = quad(assets, x + w - 1, y, 1f, H, COL_COMBO_BORDER, -14f);
|
||||
n.attachChild(borderR);
|
||||
|
||||
// Pfeil rechts
|
||||
final float ARR_W = 20f;
|
||||
Geometry arrBg = quad(assets, x + w - ARR_W, y, ARR_W, H, COL_COMBO_BORDER, -14f);
|
||||
n.attachChild(arrBg);
|
||||
BitmapText arrow = crispText(font, "v", FONT_TINY, COL_COMBO_ARROW, scale);
|
||||
arrow.setLocalTranslation(x + w - ARR_W + (ARR_W - arrow.getLineWidth()) / 2f,
|
||||
y + H - (H - arrow.getLineHeight()) / 2f, -13f);
|
||||
n.attachChild(arrow);
|
||||
|
||||
// Ausgewählter Text
|
||||
String selText = (options != null && selectedIndex >= 0 && selectedIndex < options.length)
|
||||
? options[selectedIndex] : "";
|
||||
BitmapText selLbl = crispText(font, selText, FONT_BODY, COL_WHITE, scale);
|
||||
selLbl.setLocalTranslation(x + 6, y + H - (H - selLbl.getLineHeight()) / 2f, -13f);
|
||||
n.attachChild(selLbl);
|
||||
|
||||
n.setUserData("bounds", new float[]{x, y, w, H});
|
||||
return n;
|
||||
}
|
||||
|
||||
// ── Interne Hilfsmethoden ─────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Erzeugt einen BitmapText mit Counter-Scale-Technik für scharfes Rendering.
|
||||
* Der Text wird mit virtualSize*scale Pixeln gerendert (scharf), aber erscheint
|
||||
* in virtualSize virtuellen Einheiten auf dem Canvas.
|
||||
*/
|
||||
public static BitmapText crispText(BitmapFont font, String text,
|
||||
float virtualSize, ColorRGBA color, float scale) {
|
||||
BitmapText t = new BitmapText(font);
|
||||
t.setSize(virtualSize * scale);
|
||||
t.setColor(color.clone());
|
||||
t.setText(text);
|
||||
t.setLocalScale(1f / scale, 1f / scale, 1f);
|
||||
return t;
|
||||
}
|
||||
|
||||
private static Geometry quad(AssetManager assets,
|
||||
float x, float y, float w, float h,
|
||||
ColorRGBA col, float z) {
|
||||
Geometry g = new Geometry("q", new Quad(w, h));
|
||||
Material m = new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
m.setColor("Color", col.clone());
|
||||
if (col.a < 1f) {
|
||||
m.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
||||
g.setQueueBucket(RenderQueue.Bucket.Transparent);
|
||||
} else {
|
||||
g.setQueueBucket(RenderQueue.Bucket.Gui);
|
||||
}
|
||||
g.setMaterial(m);
|
||||
g.setLocalTranslation(x, y, z);
|
||||
return g;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user