Weiter gearbeitet an allem möglichen
This commit is contained in:
BIN
blight-assets/src/main/resources/Textures/hud/bar_bg.png
Normal file
BIN
blight-assets/src/main/resources/Textures/hud/bar_bg.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 74 B |
Binary file not shown.
|
After Width: | Height: | Size: 75 B |
BIN
blight-assets/src/main/resources/Textures/hud/bar_fill_mana.png
Normal file
BIN
blight-assets/src/main/resources/Textures/hud/bar_fill_mana.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 75 B |
Binary file not shown.
|
After Width: | Height: | Size: 75 B |
BIN
blight-assets/src/main/resources/Textures/hud/bar_frame.png
Normal file
BIN
blight-assets/src/main/resources/Textures/hud/bar_frame.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 156 B |
BIN
blight-assets/src/main/resources/audio/ambient/water/wading.ogg
Normal file
BIN
blight-assets/src/main/resources/audio/ambient/water/wading.ogg
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
281
blight-game/src/main/java/de/blight/game/state/DrownState.java
Normal file
281
blight-game/src/main/java/de/blight/game/state/DrownState.java
Normal file
@@ -0,0 +1,281 @@
|
||||
package de.blight.game.state;
|
||||
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.app.state.BaseAppState;
|
||||
import com.jme3.bullet.control.CharacterControl;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.material.RenderState;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.shape.Quad;
|
||||
import de.blight.common.model.MainCharacter;
|
||||
import de.blight.game.control.PlayerInputControl;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Ertrink-Sequenz:
|
||||
* 1. MONITORING – Tiefe überwachen
|
||||
* 2. FADING_OUT – Tiefe > 1,5 m: Eingabe weg, Charakter läuft weiter,
|
||||
* Bild + Ton in 3 s ausblenden
|
||||
* 3. BLACK – 2 s Schwarzbild, dann Teleport + HP/Mana/Stamina=1
|
||||
* 4. WAIT_PHYSICS – 1,5 s warten damit Terrain-Physik lädt
|
||||
* 5. FADING_IN – 3 s Einblenden (REVIVE eingefroren)
|
||||
* 6. REVIVE_PLAYING – REVIVE-Animation läuft durch
|
||||
* → zurück zu MONITORING
|
||||
*/
|
||||
public class DrownState extends BaseAppState {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(DrownState.class);
|
||||
|
||||
/** Tiefe (m) die den Ertrink-Vorgang auslöst. */
|
||||
private static final float TRIGGER_DEPTH = 1.5f;
|
||||
|
||||
private static final float FADE_OUT_DUR = 3.0f;
|
||||
private static final float BLACK_DUR = 2.0f;
|
||||
private static final float WAIT_PHYSICS_DUR = 1.5f;
|
||||
private static final float FADE_IN_DUR = 3.0f;
|
||||
|
||||
// Strand-Suche
|
||||
private static final float MAX_BEACH_H = 5f;
|
||||
private static final float MAX_SLOPE_DEG = 10f;
|
||||
private static final float SCAN_STEP_R = 3f;
|
||||
private static final float SCAN_MAX_R = 300f;
|
||||
private static final int SCAN_DIRS = 16;
|
||||
|
||||
private final TerrainChunkState terrain;
|
||||
private final CharacterControl physicsChar;
|
||||
private final PlayerInputControl playerInput;
|
||||
private final MainCharacter mainCharacter;
|
||||
|
||||
private SimpleApplication app;
|
||||
private Geometry overlay;
|
||||
private Material overlayMat;
|
||||
|
||||
/** Gesetzt von WorldScene nach setupAnimationContext(). */
|
||||
private String reviveClip = null;
|
||||
private float reviveLength = 0f;
|
||||
|
||||
private enum Phase { MONITORING, FADING_OUT, BLACK, WAIT_PHYSICS, FADING_IN, REVIVE_PLAYING }
|
||||
private Phase phase = Phase.MONITORING;
|
||||
private float timer = 0f;
|
||||
|
||||
public DrownState(TerrainChunkState terrain, CharacterControl physicsChar,
|
||||
PlayerInputControl playerInput, MainCharacter mainCharacter) {
|
||||
this.terrain = terrain;
|
||||
this.physicsChar = physicsChar;
|
||||
this.playerInput = playerInput;
|
||||
this.mainCharacter = mainCharacter;
|
||||
}
|
||||
|
||||
public void setReviveInfo(String clip, float length) {
|
||||
this.reviveClip = clip;
|
||||
this.reviveLength = length;
|
||||
log.info("[DrownState] REVIVE-Info: clip='{}' {}s", clip, length);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize(Application application) {
|
||||
app = (SimpleApplication) application;
|
||||
float w = app.getCamera().getWidth();
|
||||
float h = app.getCamera().getHeight();
|
||||
overlay = new Geometry("drown_overlay", new Quad(w, h));
|
||||
overlayMat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
overlayMat.setColor("Color", new ColorRGBA(0f, 0f, 0f, 0f));
|
||||
overlayMat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
||||
overlay.setMaterial(overlayMat);
|
||||
overlay.setQueueBucket(RenderQueue.Bucket.Gui);
|
||||
overlay.setLocalTranslation(0f, 0f, 50f);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cleanup(Application application) {
|
||||
detachOverlay();
|
||||
restoreVolume();
|
||||
}
|
||||
|
||||
@Override protected void onEnable() {}
|
||||
@Override protected void onDisable() {}
|
||||
|
||||
@Override
|
||||
public void update(float tpf) {
|
||||
switch (phase) {
|
||||
case MONITORING -> updateMonitoring();
|
||||
case FADING_OUT -> updateFadingOut(tpf);
|
||||
case BLACK -> updateBlack(tpf);
|
||||
case WAIT_PHYSICS -> updateWaitPhysics(tpf);
|
||||
case FADING_IN -> updateFadingIn(tpf);
|
||||
case REVIVE_PLAYING -> updateRevivePlaying(tpf);
|
||||
}
|
||||
}
|
||||
|
||||
// ── MONITORING ────────────────────────────────────────────────────────────
|
||||
|
||||
private void updateMonitoring() {
|
||||
if (physicsChar == null) return;
|
||||
Vector3f pos = physicsChar.getPhysicsLocation();
|
||||
float depth = Math.max(0f, -terrain.getHeightAt(pos.x, pos.z));
|
||||
if (depth >= TRIGGER_DEPTH) {
|
||||
log.info("[DrownState] Tiefe {}m – Ertrink-Sequenz gestartet", depth);
|
||||
playerInput.blockInputsKeepMoving();
|
||||
attachOverlay();
|
||||
setAlpha(0f);
|
||||
setListenerScale(1f);
|
||||
timer = FADE_OUT_DUR;
|
||||
phase = Phase.FADING_OUT;
|
||||
}
|
||||
}
|
||||
|
||||
// ── FADING_OUT (3 s) ──────────────────────────────────────────────────────
|
||||
|
||||
private void updateFadingOut(float tpf) {
|
||||
timer -= tpf;
|
||||
float alpha = Math.max(0f, 1f - timer / FADE_OUT_DUR);
|
||||
setAlpha(alpha);
|
||||
setListenerScale(1f - alpha);
|
||||
if (timer <= 0f) {
|
||||
setAlpha(1f);
|
||||
setListenerScale(0f);
|
||||
timer = BLACK_DUR;
|
||||
phase = Phase.BLACK;
|
||||
}
|
||||
}
|
||||
|
||||
// ── BLACK (2 s) ───────────────────────────────────────────────────────────
|
||||
|
||||
private void updateBlack(float tpf) {
|
||||
timer -= tpf;
|
||||
if (timer <= 0f) {
|
||||
executeDrown();
|
||||
}
|
||||
}
|
||||
|
||||
private void executeDrown() {
|
||||
// Vollständig blockieren (stoppt auch Laufrichtung)
|
||||
playerInput.blockForIntro();
|
||||
|
||||
if (mainCharacter != null) {
|
||||
mainCharacter.setCurrentHp(1);
|
||||
mainCharacter.setCurrentMana(1);
|
||||
mainCharacter.setCurrentStamina(1);
|
||||
}
|
||||
|
||||
Vector3f pos = physicsChar.getPhysicsLocation();
|
||||
Vector3f beach = findNearestBeach(pos.x, pos.z);
|
||||
physicsChar.setPhysicsLocation(beach);
|
||||
playerInput.setGroundGrace(60);
|
||||
log.info("[DrownState] Teleportiert zu {}", beach);
|
||||
|
||||
timer = WAIT_PHYSICS_DUR;
|
||||
phase = Phase.WAIT_PHYSICS;
|
||||
}
|
||||
|
||||
// ── WAIT_PHYSICS (1,5 s) ──────────────────────────────────────────────────
|
||||
|
||||
private void updateWaitPhysics(float tpf) {
|
||||
timer -= tpf;
|
||||
if (timer <= 0f) {
|
||||
if (reviveClip != null) {
|
||||
playerInput.startFrozenRevive(reviveClip);
|
||||
}
|
||||
timer = FADE_IN_DUR;
|
||||
phase = Phase.FADING_IN;
|
||||
}
|
||||
}
|
||||
|
||||
// ── FADING_IN (3 s) ───────────────────────────────────────────────────────
|
||||
|
||||
private void updateFadingIn(float tpf) {
|
||||
timer -= tpf;
|
||||
float alpha = Math.max(0f, timer / FADE_IN_DUR);
|
||||
setAlpha(alpha);
|
||||
setListenerScale(1f - alpha);
|
||||
if (timer <= 0f) {
|
||||
detachOverlay();
|
||||
restoreVolume();
|
||||
if (reviveClip != null) {
|
||||
playerInput.unfreezeRevive();
|
||||
timer = reviveLength;
|
||||
phase = Phase.REVIVE_PLAYING;
|
||||
} else {
|
||||
playerInput.unblockInputs();
|
||||
phase = Phase.MONITORING;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ── REVIVE_PLAYING ────────────────────────────────────────────────────────
|
||||
|
||||
private void updateRevivePlaying(float tpf) {
|
||||
timer -= tpf;
|
||||
if (timer <= 0f) {
|
||||
playerInput.unblockInputs();
|
||||
phase = Phase.MONITORING;
|
||||
log.info("[DrownState] Sequenz abgeschlossen – Spieler freigegeben");
|
||||
}
|
||||
}
|
||||
|
||||
// ── Hilfsmethoden ─────────────────────────────────────────────────────────
|
||||
|
||||
private void attachOverlay() {
|
||||
if (overlay.getParent() == null) app.getGuiNode().attachChild(overlay);
|
||||
}
|
||||
|
||||
private void detachOverlay() {
|
||||
if (overlay != null && overlay.getParent() != null) app.getGuiNode().detachChild(overlay);
|
||||
}
|
||||
|
||||
private void setAlpha(float alpha) {
|
||||
overlayMat.setColor("Color", new ColorRGBA(0f, 0f, 0f, alpha));
|
||||
}
|
||||
|
||||
private void setListenerScale(float scale) {
|
||||
AudioSettingsState as = app.getStateManager().getState(AudioSettingsState.class);
|
||||
float master = (as != null) ? as.getMaster() : 1f;
|
||||
app.getListener().setVolume(master * Math.max(0f, Math.min(1f, scale)));
|
||||
}
|
||||
|
||||
private void restoreVolume() {
|
||||
AudioSettingsState as = app.getStateManager().getState(AudioSettingsState.class);
|
||||
float master = (as != null) ? as.getMaster() : 1f;
|
||||
app.getListener().setVolume(master);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sucht radial vom Ausgangspunkt aus den nächsten flachen Landpunkt.
|
||||
* Nahe Punkte liegen im selben Terrain-Chunk → Physik ist bereits geladen.
|
||||
*/
|
||||
private Vector3f findNearestBeach(float startX, float startZ) {
|
||||
float maxSlope = (float) Math.tan(Math.toRadians(MAX_SLOPE_DEG));
|
||||
float slopeStep = 4f;
|
||||
double angleStep = 2 * Math.PI / SCAN_DIRS;
|
||||
|
||||
for (float r = SCAN_STEP_R; r <= SCAN_MAX_R; r += SCAN_STEP_R) {
|
||||
for (int d = 0; d < SCAN_DIRS; d++) {
|
||||
float x = startX + r * (float) Math.cos(d * angleStep);
|
||||
float z = startZ + r * (float) Math.sin(d * angleStep);
|
||||
|
||||
float h = terrain.getHeightAt(x, z);
|
||||
if (h < 0.2f || h > MAX_BEACH_H) continue;
|
||||
|
||||
float hxp = terrain.getHeightAt(x + slopeStep, z);
|
||||
float hxn = terrain.getHeightAt(x - slopeStep, z);
|
||||
float hzp = terrain.getHeightAt(x, z + slopeStep);
|
||||
float hzn = terrain.getHeightAt(x, z - slopeStep);
|
||||
float dhdx = (hxp - hxn) / (2f * slopeStep);
|
||||
float dhdz = (hzp - hzn) / (2f * slopeStep);
|
||||
float slope = (float) Math.sqrt(dhdx * dhdx + dhdz * dhdz);
|
||||
if (slope > maxSlope) continue;
|
||||
|
||||
log.info("[DrownState] Strandpunkt: ({}, {}), h={}, r={}m", x, z, h, r);
|
||||
return new Vector3f(x, h + 1.0f, z);
|
||||
}
|
||||
}
|
||||
|
||||
log.warn("[DrownState] Kein Strandpunkt gefunden – Fallback");
|
||||
return new Vector3f(startX, Math.max(terrain.getHeightAt(startX, startZ), 0f) + 3f, startZ);
|
||||
}
|
||||
}
|
||||
286
blight-game/src/main/java/de/blight/game/state/HudState.java
Normal file
286
blight-game/src/main/java/de/blight/game/state/HudState.java
Normal file
@@ -0,0 +1,286 @@
|
||||
package de.blight.game.state;
|
||||
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.app.state.BaseAppState;
|
||||
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.Mesh;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial;
|
||||
import com.jme3.scene.VertexBuffer;
|
||||
import com.jme3.texture.Texture;
|
||||
import com.jme3.ui.Picture;
|
||||
import com.jme3.util.BufferUtils;
|
||||
import de.blight.common.model.MainCharacter;
|
||||
import de.blight.game.animation.AnimationLibrary;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.IOException;
|
||||
import java.nio.FloatBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* HUD-Balken (unten rechts): Health (rot), Stamina (gelb), Mana (blau).
|
||||
* PNG-basiert – Dateien in Textures/hud/ können durch eigene Grafiken ersetzt werden.
|
||||
*/
|
||||
public class HudState extends BaseAppState {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(HudState.class);
|
||||
|
||||
// Dimensionen des Fülbereichs (ohne Rahmen)
|
||||
private static final float BAR_W = 200f;
|
||||
private static final float BAR_H = 18f;
|
||||
private static final float BORDER = 2f;
|
||||
private static final float MARGIN = 12f;
|
||||
private static final float GAP = 5f;
|
||||
|
||||
private static final float SLOT_W = BAR_W + BORDER * 2;
|
||||
private static final float SLOT_H = BAR_H + BORDER * 2;
|
||||
|
||||
private static final String[] FILL_ASSETS = {
|
||||
"Textures/hud/bar_fill_health.png",
|
||||
"Textures/hud/bar_fill_stamina.png",
|
||||
"Textures/hud/bar_fill_mana.png",
|
||||
};
|
||||
|
||||
private static final ColorRGBA[] FILL_COLORS = {
|
||||
new ColorRGBA(0.85f, 0.10f, 0.10f, 1f),
|
||||
new ColorRGBA(0.90f, 0.80f, 0.10f, 1f),
|
||||
new ColorRGBA(0.10f, 0.30f, 0.90f, 1f),
|
||||
};
|
||||
|
||||
private final MainCharacter mc;
|
||||
|
||||
private SimpleApplication app;
|
||||
private Node hudNode;
|
||||
private final Geometry[] fills = new Geometry[3];
|
||||
private final float[] lastRatios = { -1f, -1f, -1f };
|
||||
|
||||
public HudState(MainCharacter mc) {
|
||||
this.mc = mc;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize(Application application) {
|
||||
app = (SimpleApplication) application;
|
||||
ensureAssets();
|
||||
hudNode = new Node("hud_bars");
|
||||
buildBars();
|
||||
app.getGuiNode().attachChild(hudNode);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void cleanup(Application application) {
|
||||
if (hudNode.getParent() != null) {
|
||||
app.getGuiNode().detachChild(hudNode);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEnable() {
|
||||
hudNode.setCullHint(Spatial.CullHint.Inherit);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDisable() {
|
||||
hudNode.setCullHint(Spatial.CullHint.Always);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(float tpf) {
|
||||
if (mc == null) return;
|
||||
int maxHp = mc.getMaxHp();
|
||||
int maxSt = mc.getMaxStamina();
|
||||
int maxMn = mc.getMaxMana();
|
||||
applyRatio(0, maxHp > 0 ? (float) mc.getCurrentHp() / maxHp : 0f);
|
||||
applyRatio(1, maxSt > 0 ? (float) mc.getCurrentStamina() / maxSt : 0f);
|
||||
applyRatio(2, maxMn > 0 ? (float) mc.getCurrentMana() / maxMn : 0f);
|
||||
}
|
||||
|
||||
// ── Aufbau ───────────────────────────────────────────────────────────────────
|
||||
|
||||
private void buildBars() {
|
||||
float screenW = app.getCamera().getWidth();
|
||||
|
||||
// Stapelung von unten: idx=2 (mana) ganz unten, idx=0 (health) ganz oben
|
||||
for (int i = 0; i < 3; i++) {
|
||||
int stackPos = 2 - i; // health → stack 2 (höchste Y), mana → stack 0
|
||||
float x = screenW - MARGIN - SLOT_W;
|
||||
float y = MARGIN + stackPos * (SLOT_H + GAP);
|
||||
buildBar(i, x, y);
|
||||
}
|
||||
}
|
||||
|
||||
private void buildBar(int idx, float x, float y) {
|
||||
// Rahmen (Frame) – transparent innen, Rand sichtbar
|
||||
Picture frame = loadPicture("Textures/hud/bar_frame.png", SLOT_W, SLOT_H,
|
||||
new ColorRGBA(0.65f, 0.65f, 0.65f, 1f));
|
||||
frame.setLocalTranslation(x, y, 1f);
|
||||
hudNode.attachChild(frame);
|
||||
|
||||
// Hintergrund (schwarz)
|
||||
Picture bg = loadPicture("Textures/hud/bar_bg.png", BAR_W, BAR_H, ColorRGBA.Black);
|
||||
bg.setLocalTranslation(x + BORDER, y + BORDER, 2f);
|
||||
hudNode.attachChild(bg);
|
||||
|
||||
// Füllbalken (UV-geclippt, kein Strecken des Textur-Gradienten)
|
||||
Geometry fill = buildFillGeometry(idx, x + BORDER, y + BORDER, 3f);
|
||||
fills[idx] = fill;
|
||||
hudNode.attachChild(fill);
|
||||
}
|
||||
|
||||
private Geometry buildFillGeometry(int idx, float x, float y, float z) {
|
||||
Mesh mesh = new Mesh();
|
||||
fillMesh(mesh, BAR_W, 1f); // initial: voll
|
||||
|
||||
Material mat;
|
||||
try {
|
||||
Texture tex = app.getAssetManager().loadTexture(FILL_ASSETS[idx]);
|
||||
tex.setWrap(Texture.WrapMode.EdgeClamp);
|
||||
mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setTexture("ColorMap", tex);
|
||||
} catch (Exception e) {
|
||||
mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setColor("Color", FILL_COLORS[idx]);
|
||||
}
|
||||
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
||||
|
||||
Geometry g = new Geometry("hud_fill_" + idx, mesh);
|
||||
g.setMaterial(mat);
|
||||
g.setQueueBucket(RenderQueue.Bucket.Gui);
|
||||
g.setLocalTranslation(x, y, z);
|
||||
return g;
|
||||
}
|
||||
|
||||
/** Setzt Breite und UV-Clipping des Fill-Meshes auf den gegebenen Ratio (0–1). */
|
||||
private void applyRatio(int idx, float ratio) {
|
||||
ratio = Math.max(0f, Math.min(1f, ratio));
|
||||
if (Math.abs(ratio - lastRatios[idx]) < 0.001f) return;
|
||||
lastRatios[idx] = ratio;
|
||||
|
||||
Geometry g = fills[idx];
|
||||
float w = BAR_W * ratio;
|
||||
|
||||
Mesh mesh = g.getMesh();
|
||||
FloatBuffer pos = (FloatBuffer) mesh.getBuffer(VertexBuffer.Type.Position).getData();
|
||||
pos.put(3, w); // Vertex 1: x
|
||||
pos.put(6, w); // Vertex 2: x
|
||||
mesh.getBuffer(VertexBuffer.Type.Position).setUpdateNeeded();
|
||||
|
||||
// UV-Clipping: nur ratio-Anteil der Textur sichtbar (kein Strecken)
|
||||
FloatBuffer uv = (FloatBuffer) mesh.getBuffer(VertexBuffer.Type.TexCoord).getData();
|
||||
uv.put(2, ratio); // UV 1: u
|
||||
uv.put(4, ratio); // UV 2: u
|
||||
mesh.getBuffer(VertexBuffer.Type.TexCoord).setUpdateNeeded();
|
||||
|
||||
mesh.updateBound();
|
||||
}
|
||||
|
||||
/** Erstellt ein Quad-Mesh mit UV-Koordinaten die bei maxU enden (für Clipping). */
|
||||
private static void fillMesh(Mesh mesh, float w, float maxU) {
|
||||
float h = BAR_H;
|
||||
mesh.setBuffer(VertexBuffer.Type.Position, 3,
|
||||
BufferUtils.createFloatBuffer(
|
||||
0, 0, 0,
|
||||
w, 0, 0,
|
||||
w, h, 0,
|
||||
0, h, 0
|
||||
));
|
||||
mesh.setBuffer(VertexBuffer.Type.TexCoord, 2,
|
||||
BufferUtils.createFloatBuffer(
|
||||
0f, 0f,
|
||||
maxU, 0f,
|
||||
maxU, 1f,
|
||||
0f, 1f
|
||||
));
|
||||
mesh.setBuffer(VertexBuffer.Type.Index, 3,
|
||||
BufferUtils.createShortBuffer((short)0,(short)1,(short)2,(short)0,(short)2,(short)3));
|
||||
mesh.setMode(Mesh.Mode.Triangles);
|
||||
mesh.updateBound();
|
||||
}
|
||||
|
||||
// ── PNG-Loader ────────────────────────────────────────────────────────────────
|
||||
|
||||
private Picture loadPicture(String assetPath, float w, float h, ColorRGBA fallback) {
|
||||
Picture p = new Picture("hud_" + assetPath);
|
||||
try {
|
||||
p.setImage(app.getAssetManager(), assetPath, true);
|
||||
} catch (Exception e) {
|
||||
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setColor("Color", fallback);
|
||||
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
||||
p.setMaterial(mat);
|
||||
}
|
||||
p.setWidth(w);
|
||||
p.setHeight(h);
|
||||
p.setQueueBucket(RenderQueue.Bucket.Gui);
|
||||
return p;
|
||||
}
|
||||
|
||||
// ── Placeholder-PNGs erzeugen ─────────────────────────────────────────────────
|
||||
|
||||
private void ensureAssets() {
|
||||
Path root = AnimationLibrary.findAssetRoot();
|
||||
Path dir = root.resolve("Textures").resolve("hud");
|
||||
try {
|
||||
Files.createDirectories(dir);
|
||||
} catch (IOException e) {
|
||||
log.warn("[HUD] Verzeichnis nicht erstellbar: {}", dir);
|
||||
return;
|
||||
}
|
||||
|
||||
// Rahmen: grauer Rand, transparente Mitte
|
||||
ensureFrame(dir.resolve("bar_frame.png"), (int) SLOT_W, (int) SLOT_H, (int) BORDER,
|
||||
180, 180, 180);
|
||||
// Hintergrund: schwarz
|
||||
ensureSolid(dir.resolve("bar_bg.png"), 4, 4, 0, 0, 0, 255);
|
||||
// Füllfarben (kleine Kacheln, werden skaliert/geclippt)
|
||||
ensureSolid(dir.resolve("bar_fill_health.png"), 4, 4, 217, 25, 25, 255);
|
||||
ensureSolid(dir.resolve("bar_fill_stamina.png"), 4, 4, 229, 204, 25, 255);
|
||||
ensureSolid(dir.resolve("bar_fill_mana.png"), 4, 4, 25, 76, 229, 255);
|
||||
}
|
||||
|
||||
private static void ensureSolid(Path path, int w, int h,
|
||||
int r, int g, int b, int a) {
|
||||
if (Files.exists(path)) return;
|
||||
try {
|
||||
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
|
||||
int argb = (a << 24) | (r << 16) | (g << 8) | b;
|
||||
for (int py = 0; py < h; py++) {
|
||||
for (int px = 0; px < w; px++) {
|
||||
img.setRGB(px, py, argb);
|
||||
}
|
||||
}
|
||||
ImageIO.write(img, "PNG", path.toFile());
|
||||
} catch (Exception e) {
|
||||
log.warn("[HUD] PNG nicht erstellbar: {}", path);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ensureFrame(Path path, int w, int h, int border,
|
||||
int r, int g, int b) {
|
||||
if (Files.exists(path)) return;
|
||||
try {
|
||||
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
|
||||
int borderArgb = (255 << 24) | (r << 16) | (g << 8) | b;
|
||||
for (int py = 0; py < h; py++) {
|
||||
for (int px = 0; px < w; px++) {
|
||||
boolean isBorder = px < border || py < border
|
||||
|| px >= w - border || py >= h - border;
|
||||
img.setRGB(px, py, isBorder ? borderArgb : 0);
|
||||
}
|
||||
}
|
||||
ImageIO.write(img, "PNG", path.toFile());
|
||||
} catch (Exception e) {
|
||||
log.warn("[HUD] Frame-PNG nicht erstellbar: {}", path);
|
||||
}
|
||||
}
|
||||
}
|
||||
BIN
blight-map/src/main/map/blight_map.blm.tmp
Normal file
BIN
blight-map/src/main/map/blight_map.blm.tmp
Normal file
Binary file not shown.
420
com/jme3/app/SimpleApplication.java
Normal file
420
com/jme3/app/SimpleApplication.java
Normal file
@@ -0,0 +1,420 @@
|
||||
/*
|
||||
* Copyright (c) 2009-2025 jMonkeyEngine
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are
|
||||
* met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
*
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* * Neither the name of 'jMonkeyEngine' nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
package com.jme3.app;
|
||||
|
||||
import com.jme3.app.state.AppState;
|
||||
import com.jme3.app.state.ConstantVerifierState;
|
||||
import com.jme3.audio.AudioListenerState;
|
||||
import com.jme3.font.BitmapFont;
|
||||
import com.jme3.font.BitmapText;
|
||||
import com.jme3.input.FlyByCamera;
|
||||
import com.jme3.input.KeyInput;
|
||||
import com.jme3.input.controls.ActionListener;
|
||||
import com.jme3.input.controls.KeyTrigger;
|
||||
import com.jme3.profile.AppStep;
|
||||
import com.jme3.renderer.RenderManager;
|
||||
import com.jme3.renderer.queue.RenderQueue.Bucket;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.Spatial.CullHint;
|
||||
import com.jme3.scene.threadwarden.SceneGraphThreadWarden;
|
||||
import com.jme3.system.AppSettings;
|
||||
import com.jme3.system.JmeContext.Type;
|
||||
import com.jme3.system.JmeSystem;
|
||||
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* `SimpleApplication` is the foundational base class for all jMonkeyEngine 3 (jME3) applications.
|
||||
* It provides a streamlined setup for common game development tasks, including scene management,
|
||||
* camera controls, and performance monitoring.
|
||||
*
|
||||
* <p>By default, `SimpleApplication` attaches several essential {@link com.jme3.app.state.AppState} instances:
|
||||
* <ul>
|
||||
* <li>{@link com.jme3.app.StatsAppState}: Displays real-time frames-per-second (FPS) and
|
||||
* detailed performance statistics on-screen.</li>
|
||||
* <li>{@link com.jme3.app.FlyCamAppState}: Provides a convenient first-person fly-by camera
|
||||
* controller, allowing easy navigation within the scene.</li>
|
||||
* <li>{@link com.jme3.audio.AudioListenerState}: Manages the audio listener, essential for 3D sound.</li>
|
||||
* <li>{@link com.jme3.app.DebugKeysAppState}: Enables debug functionalities like displaying
|
||||
* camera position and memory usage in the console.</li>
|
||||
* <li>{@link com.jme3.app.state.ConstantVerifierState}: A utility state for verifying constant
|
||||
* values, primarily for internal engine debugging.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p><b>Default Key Bindings:</b></p>
|
||||
* <ul>
|
||||
* <li><b>Esc:</b> Closes and exits the application.</li>
|
||||
* <li><b>F5:</b> Toggles the visibility of the statistics view (FPS and debug stats).</li>
|
||||
* <li><b>C:</b> Prints the current camera position and rotation to the console.</li>
|
||||
* <li><b>M:</b> Prints memory usage statistics to the console.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Applications extending `SimpleApplication` should implement the
|
||||
* {@link #simpleInitApp()} method to set up their initial scene and game logic.
|
||||
*/
|
||||
public abstract class SimpleApplication extends LegacyApplication {
|
||||
|
||||
protected static final Logger logger = Logger.getLogger(SimpleApplication.class.getName());
|
||||
|
||||
public static final String INPUT_MAPPING_EXIT = "SIMPLEAPP_Exit";
|
||||
public static final String INPUT_MAPPING_CAMERA_POS = DebugKeysAppState.INPUT_MAPPING_CAMERA_POS;
|
||||
public static final String INPUT_MAPPING_MEMORY = DebugKeysAppState.INPUT_MAPPING_MEMORY;
|
||||
public static final String INPUT_MAPPING_HIDE_STATS = "SIMPLEAPP_HideStats";
|
||||
|
||||
protected Node rootNode = new Node("Root Node");
|
||||
protected Node guiNode = new Node("Gui Node");
|
||||
protected BitmapText fpsText;
|
||||
protected BitmapFont guiFont;
|
||||
protected FlyByCamera flyCam;
|
||||
protected boolean showSettings = true;
|
||||
private final AppActionListener actionListener = new AppActionListener();
|
||||
|
||||
private class AppActionListener implements ActionListener {
|
||||
|
||||
@Override
|
||||
public void onAction(String name, boolean isPressed, float tpf) {
|
||||
if (!isPressed) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (name.equals(INPUT_MAPPING_EXIT)) {
|
||||
stop();
|
||||
} else if (name.equals(INPUT_MAPPING_HIDE_STATS)) {
|
||||
StatsAppState statsState = stateManager.getState(StatsAppState.class);
|
||||
if (statsState != null) {
|
||||
statsState.toggleStats();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a `SimpleApplication` with a predefined set of default
|
||||
* {@link com.jme3.app.state.AppState} instances.
|
||||
* These states provide common functionalities like statistics display,
|
||||
* fly camera control, audio listener, debug keys, and constant verification.
|
||||
*/
|
||||
public SimpleApplication() {
|
||||
this(new StatsAppState(),
|
||||
new FlyCamAppState(),
|
||||
new AudioListenerState(),
|
||||
new DebugKeysAppState(),
|
||||
new ConstantVerifierState());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a `SimpleApplication` with a custom array of initial
|
||||
* {@link com.jme3.app.state.AppState} instances.
|
||||
*
|
||||
* @param initialStates An array of `AppState` instances to be attached
|
||||
* to the `stateManager` upon initialization.
|
||||
*/
|
||||
public SimpleApplication(AppState... initialStates) {
|
||||
super(initialStates);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
// set some default settings in-case
|
||||
// settings dialog is not shown
|
||||
boolean loadSettings = false;
|
||||
if (settings == null) {
|
||||
logger.log(Level.INFO, "AppSettings not set, creating default settings.");
|
||||
setSettings(new AppSettings(true));
|
||||
loadSettings = true;
|
||||
}
|
||||
|
||||
// show settings dialog
|
||||
if (showSettings) {
|
||||
if (!JmeSystem.showSettingsDialog(settings, loadSettings)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
//re-setting settings they can have been merged from the registry.
|
||||
setSettings(settings);
|
||||
super.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current speed multiplier of the application.
|
||||
* This value affects how quickly the game world updates relative to real time.
|
||||
* A value of 1.0f means normal speed, 0.5f means half speed, 2.0f means double speed.
|
||||
*
|
||||
* @return The current speed of the application.
|
||||
*/
|
||||
public float getSpeed() {
|
||||
return speed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Changes the application's speed multiplier.
|
||||
* A `speed` of 0.0f effectively pauses the application's update cycle.
|
||||
*
|
||||
* @param speed The desired speed multiplier. A value of 1.0f is normal speed.
|
||||
* Must be non-negative.
|
||||
*/
|
||||
public void setSpeed(float speed) {
|
||||
this.speed = speed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the `FlyByCamera` instance associated with this application.
|
||||
* This camera allows free-form navigation within the 3D scene.
|
||||
*
|
||||
* @return The `FlyByCamera` object, or `null` if `FlyCamAppState` is not attached
|
||||
* or has not yet initialized the camera.
|
||||
*/
|
||||
public FlyByCamera getFlyByCamera() {
|
||||
return flyCam;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the `Node` dedicated to 2D graphical user interface (GUI) elements.
|
||||
* Objects attached to this node are rendered on top of the 3D scene,
|
||||
* typically without perspective effects, suitable for HUDs and UI.
|
||||
*
|
||||
* @return The `Node` object representing the GUI root.
|
||||
*/
|
||||
public Node getGuiNode() {
|
||||
return guiNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the root `Node` of the 3D scene graph.
|
||||
* All main 3D spatial objects and models should be attached to this node
|
||||
* to be part of the rendered scene.
|
||||
*
|
||||
* @return The `Node` object representing the 3D scene root.
|
||||
*/
|
||||
public Node getRootNode() {
|
||||
return rootNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the settings dialog is configured to be shown at application startup.
|
||||
*
|
||||
* @return `true` if the settings dialog will be displayed, `false` otherwise.
|
||||
*/
|
||||
public boolean isShowSettings() {
|
||||
return showSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the jME3 settings dialog should be displayed before the application starts.
|
||||
*
|
||||
* @param showSettings `true` to show the settings dialog, `false` to suppress it.
|
||||
*/
|
||||
public void setShowSettings(boolean showSettings) {
|
||||
this.showSettings = showSettings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the font that will be set to the guiFont field
|
||||
* and subsequently set as the font for the stats text.
|
||||
*
|
||||
* @return the loaded BitmapFont
|
||||
*/
|
||||
protected BitmapFont loadGuiFont() {
|
||||
return assetManager.loadFont("Interface/Fonts/Default.fnt");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
super.initialize();
|
||||
|
||||
//noinspection AssertWithSideEffects
|
||||
assert SceneGraphThreadWarden.setup(rootNode);
|
||||
//noinspection AssertWithSideEffects
|
||||
assert SceneGraphThreadWarden.setup(guiNode);
|
||||
|
||||
// Several things rely on having this
|
||||
guiFont = loadGuiFont();
|
||||
|
||||
guiNode.setQueueBucket(Bucket.Gui);
|
||||
guiNode.setCullHint(CullHint.Never);
|
||||
|
||||
viewPort.attachScene(rootNode);
|
||||
guiViewPort.attachScene(guiNode);
|
||||
|
||||
if (inputManager != null) {
|
||||
// Special handling for FlyCamAppState:
|
||||
// Although FlyCamAppState manages the FlyByCamera, SimpleApplication
|
||||
// historically initializes and configures a default FlyByCamera instance
|
||||
// and sets its initial speed. This allows subclasses to directly access
|
||||
// 'flyCam' early in simpleInitApp().
|
||||
|
||||
FlyCamAppState flyCamState = stateManager.getState(FlyCamAppState.class);
|
||||
if (flyCamState != null) {
|
||||
flyCam = new FlyByCamera(cam);
|
||||
flyCam.setMoveSpeed(1f); // Set a default movement speed for the camera
|
||||
flyCamState.setCamera(flyCam); // Link the FlyCamAppState to this camera instance
|
||||
}
|
||||
|
||||
// Register the "Exit" input mapping for the Escape key, but only for Display contexts.
|
||||
if (context.getType() == Type.Display) {
|
||||
inputManager.addMapping(INPUT_MAPPING_EXIT, new KeyTrigger(KeyInput.KEY_ESCAPE));
|
||||
}
|
||||
|
||||
// Register the "Hide Stats" input mapping for the F5 key, if StatsAppState is active.
|
||||
StatsAppState statsState = stateManager.getState(StatsAppState.class);
|
||||
if (statsState != null) {
|
||||
inputManager.addMapping(INPUT_MAPPING_HIDE_STATS, new KeyTrigger(KeyInput.KEY_F5));
|
||||
inputManager.addListener(actionListener, INPUT_MAPPING_HIDE_STATS);
|
||||
}
|
||||
|
||||
// Attach the action listener to the "Exit" mapping.
|
||||
inputManager.addListener(actionListener, INPUT_MAPPING_EXIT);
|
||||
}
|
||||
|
||||
// Configure the StatsAppState if it exists.
|
||||
StatsAppState statsState = stateManager.getState(StatsAppState.class);
|
||||
if (statsState != null) {
|
||||
statsState.setFont(guiFont);
|
||||
fpsText = statsState.getFpsText();
|
||||
}
|
||||
|
||||
// Call the user's application initialization code.
|
||||
simpleInitApp();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop(boolean waitFor) {
|
||||
//noinspection AssertWithSideEffects
|
||||
assert SceneGraphThreadWarden.reset();
|
||||
super.stop(waitFor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update() {
|
||||
if (prof != null) {
|
||||
prof.appStep(AppStep.BeginFrame);
|
||||
}
|
||||
|
||||
// Executes AppTasks from the main thread
|
||||
super.update();
|
||||
|
||||
// Skip updates if paused or speed is zero
|
||||
if (speed == 0 || paused) {
|
||||
return;
|
||||
}
|
||||
|
||||
float tpf = timer.getTimePerFrame() * speed;
|
||||
|
||||
// Update AppStates
|
||||
if (prof != null) {
|
||||
prof.appStep(AppStep.StateManagerUpdate);
|
||||
}
|
||||
stateManager.update(tpf);
|
||||
|
||||
// Call user's per-frame update method
|
||||
simpleUpdate(tpf);
|
||||
|
||||
// Update scene graph nodes (logical and geometric states)
|
||||
if (prof != null) {
|
||||
prof.appStep(AppStep.SpatialUpdate);
|
||||
}
|
||||
rootNode.updateLogicalState(tpf);
|
||||
guiNode.updateLogicalState(tpf);
|
||||
|
||||
rootNode.updateGeometricState();
|
||||
guiNode.updateGeometricState();
|
||||
|
||||
// Render AppStates and the scene
|
||||
if (prof != null) {
|
||||
prof.appStep(AppStep.StateManagerRender);
|
||||
}
|
||||
stateManager.render(renderManager);
|
||||
|
||||
if (prof != null) {
|
||||
prof.appStep(AppStep.RenderFrame);
|
||||
}
|
||||
renderManager.render(tpf, context.isRenderable());
|
||||
// Call user's custom render method
|
||||
simpleRender(renderManager);
|
||||
stateManager.postRender();
|
||||
|
||||
if (prof != null) {
|
||||
prof.appStep(AppStep.EndFrame);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Controls the visibility of the frames-per-second (FPS) display on the screen.
|
||||
*
|
||||
* @param show `true` to display the FPS, `false` to hide it.
|
||||
*/
|
||||
public void setDisplayFps(boolean show) {
|
||||
StatsAppState statsState = stateManager.getState(StatsAppState.class);
|
||||
if (statsState != null) {
|
||||
statsState.setDisplayFps(show);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Controls the visibility of the comprehensive statistics view on the screen.
|
||||
* This view typically includes details about memory, triangles, and other performance metrics.
|
||||
*
|
||||
* @param show `true` to display the statistics view, `false` to hide it.
|
||||
*/
|
||||
public void setDisplayStatView(boolean show) {
|
||||
StatsAppState statsState = stateManager.getState(StatsAppState.class);
|
||||
if (statsState != null) {
|
||||
statsState.setDisplayStatView(show);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void simpleInitApp();
|
||||
|
||||
/**
|
||||
* An optional method that can be overridden by subclasses for per-frame update logic.
|
||||
* This method is called during the application's update loop, after AppStates are updated
|
||||
* and before the scene graph's logical state is updated.
|
||||
*
|
||||
* @param tpf The time per frame (in seconds), adjusted by the application's speed.
|
||||
*/
|
||||
public void simpleUpdate(float tpf) {
|
||||
// Default empty implementation; subclasses can override
|
||||
}
|
||||
|
||||
/**
|
||||
* An optional method that can be overridden by subclasses for custom rendering logic.
|
||||
* This method is called during the application's render loop, after the main scene
|
||||
* has been rendered and before post-rendering for states.
|
||||
* Useful for drawing overlays or specific rendering tasks outside the main scene graph.
|
||||
*
|
||||
* @param rm The `RenderManager` instance, which provides access to rendering functionalities.
|
||||
*/
|
||||
public void simpleRender(RenderManager rm) {
|
||||
// Default empty implementation; subclasses can override
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user