Initaler Commit
This commit is contained in:
92
blight-game/src/main/java/de/blight/game/BlightApp.java
Normal file
92
blight-game/src/main/java/de/blight/game/BlightApp.java
Normal file
@@ -0,0 +1,92 @@
|
||||
package de.blight.game;
|
||||
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.input.KeyInput;
|
||||
import com.jme3.input.controls.ActionListener;
|
||||
import com.jme3.input.controls.KeyTrigger;
|
||||
import com.jme3.system.AppSettings;
|
||||
import de.blight.game.config.*;
|
||||
import de.blight.game.scene.WorldScene;
|
||||
|
||||
public class BlightApp extends SimpleApplication {
|
||||
|
||||
private KeyBindings keyBindings;
|
||||
private GraphicsSettings graphicsSettings;
|
||||
private WorldScene worldScene;
|
||||
private ConfigScreen configScreen;
|
||||
private GraphicsScreen graphicsScreen;
|
||||
private PauseMenu pauseMenu;
|
||||
|
||||
public static void main(String[] args) {
|
||||
BlightApp app = new BlightApp();
|
||||
|
||||
GraphicsSettings gs = GraphicsStore.load();
|
||||
AppSettings settings = new AppSettings(true);
|
||||
settings.setTitle("Blight");
|
||||
settings.setResolution(gs.width, gs.height);
|
||||
settings.setFullscreen(gs.fullscreen);
|
||||
settings.setVSync(gs.vsync);
|
||||
settings.setSamples(gs.samples);
|
||||
|
||||
app.setSettings(settings);
|
||||
app.setShowSettings(false);
|
||||
app.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void simpleInitApp() {
|
||||
flyCam.setEnabled(false);
|
||||
inputManager.deleteMapping(INPUT_MAPPING_EXIT);
|
||||
|
||||
keyBindings = KeyBindingStore.load();
|
||||
graphicsSettings = GraphicsStore.load();
|
||||
|
||||
worldScene = new WorldScene(keyBindings);
|
||||
stateManager.attach(worldScene);
|
||||
|
||||
configScreen = new ConfigScreen(keyBindings, () -> worldScene.reloadBindings(keyBindings));
|
||||
configScreen.setOnClose(() -> pauseMenu.setEnabled(true));
|
||||
stateManager.attach(configScreen);
|
||||
configScreen.setEnabled(false);
|
||||
|
||||
graphicsScreen = new GraphicsScreen(graphicsSettings, () -> pauseMenu.setEnabled(true));
|
||||
stateManager.attach(graphicsScreen);
|
||||
graphicsScreen.setEnabled(false);
|
||||
|
||||
pauseMenu = new PauseMenu(
|
||||
() -> { pauseMenu.setEnabled(false); graphicsScreen.setEnabled(true); },
|
||||
() -> { pauseMenu.setEnabled(false); configScreen.setEnabled(true); }
|
||||
);
|
||||
stateManager.attach(pauseMenu);
|
||||
pauseMenu.setEnabled(false);
|
||||
|
||||
inputManager.addMapping("ToggleMenu", new KeyTrigger(KeyInput.KEY_ESCAPE));
|
||||
inputManager.addListener((ActionListener) (name, isPressed, tpf) -> {
|
||||
if (!isPressed) return;
|
||||
|
||||
if (graphicsScreen.isEnabled()) {
|
||||
// GraphicsScreen wird nur über seine eigenen Buttons geschlossen
|
||||
return;
|
||||
}
|
||||
if (configScreen.isEnabled()) {
|
||||
if (configScreen.isWaiting()) {
|
||||
configScreen.cancelWaiting();
|
||||
} else {
|
||||
configScreen.setEnabled(false);
|
||||
pauseMenu.setEnabled(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (pauseMenu.isEnabled()) {
|
||||
pauseMenu.setEnabled(false);
|
||||
worldScene.setPaused(false);
|
||||
return;
|
||||
}
|
||||
pauseMenu.setEnabled(true);
|
||||
worldScene.setPaused(true);
|
||||
}, "ToggleMenu");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void simpleUpdate(float tpf) {}
|
||||
}
|
||||
@@ -0,0 +1,309 @@
|
||||
package de.blight.game.config;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.app.state.BaseAppState;
|
||||
import com.jme3.font.BitmapFont;
|
||||
import com.jme3.font.BitmapText;
|
||||
import com.jme3.input.KeyInput;
|
||||
import com.jme3.input.MouseInput;
|
||||
import com.jme3.input.RawInputListener;
|
||||
import com.jme3.input.controls.ActionListener;
|
||||
import com.jme3.input.controls.MouseButtonTrigger;
|
||||
import com.jme3.input.event.JoyAxisEvent;
|
||||
import com.jme3.input.event.JoyButtonEvent;
|
||||
import com.jme3.input.event.KeyInputEvent;
|
||||
import com.jme3.input.event.MouseButtonEvent;
|
||||
import com.jme3.input.event.MouseMotionEvent;
|
||||
import com.jme3.input.event.TouchEvent;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.material.RenderState;
|
||||
import com.jme3.math.ColorRGBA;
|
||||
import com.jme3.math.Vector2f;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.shape.Quad;
|
||||
|
||||
/**
|
||||
* Overlay-AppState der die Tastenbelegungs-Maske anzeigt.
|
||||
*
|
||||
* ESC → Schließen (ohne Speichern)
|
||||
* Klick auf Row → wartet auf neue Taste
|
||||
* ESC während Warten → bricht nur die Zuweisung ab
|
||||
* Speichern → schreibt JSON, ruft onSave-Callback
|
||||
*/
|
||||
public class ConfigScreen extends BaseAppState implements RawInputListener {
|
||||
|
||||
// Farben
|
||||
private static final ColorRGBA COL_BG = new ColorRGBA(0.05f, 0.05f, 0.08f, 0.88f);
|
||||
private static final ColorRGBA COL_PANEL = new ColorRGBA(0.10f, 0.10f, 0.16f, 1.00f);
|
||||
private static final ColorRGBA COL_ROW = new ColorRGBA(0.18f, 0.18f, 0.28f, 1.00f);
|
||||
private static final ColorRGBA COL_ROW_HOVER = new ColorRGBA(0.25f, 0.25f, 0.40f, 1.00f);
|
||||
private static final ColorRGBA COL_ROW_WAIT = new ColorRGBA(0.50f, 0.30f, 0.10f, 1.00f);
|
||||
private static final ColorRGBA COL_BTN_SAVE = new ColorRGBA(0.15f, 0.40f, 0.15f, 1.00f);
|
||||
private static final ColorRGBA COL_BTN_CANCEL = new ColorRGBA(0.40f, 0.15f, 0.15f, 1.00f);
|
||||
private static final ColorRGBA COL_TEXT = ColorRGBA.White;
|
||||
private static final ColorRGBA COL_TEXT_KEY = new ColorRGBA(0.85f, 0.85f, 0.50f, 1.00f);
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private SimpleApplication app;
|
||||
private Node guiNode;
|
||||
private BitmapFont font;
|
||||
|
||||
private KeyBindings liveBindings; // geteilt mit der ganzen App
|
||||
private KeyBindings editCopy; // wird beim Öffnen geklont
|
||||
|
||||
private Runnable onSave; // Callback → PlayerInputControl.reloadBindings
|
||||
private Runnable onClose; // Callback → PauseMenu wiederherstellen
|
||||
|
||||
private Node panel;
|
||||
private List<Row> rows = new ArrayList<>();
|
||||
private int waitingRow = -1; // -1 = keine Zuweisung aktiv
|
||||
|
||||
// UI-Elemente für Buttons (Bounds in Screen-Koordinaten)
|
||||
private float saveBtnX, saveBtnY, saveBtnW, saveBtnH;
|
||||
private float cancelBtnX, cancelBtnY;
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private static class Row {
|
||||
String field;
|
||||
String label;
|
||||
BitmapText keyText;
|
||||
Geometry bg;
|
||||
float x, y, w, h; // Button-Bounds
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
public ConfigScreen(KeyBindings liveBindings, Runnable onSave) {
|
||||
this.liveBindings = liveBindings;
|
||||
this.onSave = onSave;
|
||||
}
|
||||
|
||||
public boolean isWaiting() { return waitingRow >= 0; }
|
||||
|
||||
public void setOnClose(Runnable onClose) { this.onClose = onClose; }
|
||||
|
||||
public void cancelWaiting() {
|
||||
if (waitingRow >= 0) { resetRowColor(waitingRow); waitingRow = -1; }
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Lifecycle
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void initialize(Application app) {
|
||||
this.app = (SimpleApplication) app;
|
||||
this.guiNode = this.app.getGuiNode();
|
||||
this.font = app.getAssetManager().loadFont("Interface/Fonts/Default.fnt");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEnable() {
|
||||
editCopy = liveBindings.copy();
|
||||
waitingRow = -1;
|
||||
buildUI();
|
||||
app.getInputManager().setCursorVisible(true);
|
||||
app.getInputManager().addRawInputListener(this);
|
||||
app.getInputManager().addMapping("_CfgClick", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
|
||||
app.getInputManager().addListener(clickListener, "_CfgClick");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDisable() {
|
||||
if (panel != null) { guiNode.detachChild(panel); panel = null; }
|
||||
rows.clear();
|
||||
waitingRow = -1;
|
||||
app.getInputManager().removeRawInputListener(this);
|
||||
app.getInputManager().deleteMapping("_CfgClick");
|
||||
app.getInputManager().setCursorVisible(false);
|
||||
}
|
||||
|
||||
@Override protected void cleanup(Application app) {}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// UI aufbauen
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private void buildUI() {
|
||||
float sw = app.getCamera().getWidth();
|
||||
float sh = app.getCamera().getHeight();
|
||||
|
||||
panel = new Node("cfg-panel");
|
||||
|
||||
// Halbdurchsichtiger Overlay über dem Spiel
|
||||
addQuad(panel, 0, 0, sw, sh, COL_BG, -2);
|
||||
|
||||
float pw = 720, ph = 440;
|
||||
float px = (sw - pw) / 2f, py = (sh - ph) / 2f;
|
||||
addQuad(panel, px, py, pw, ph, COL_PANEL, -1);
|
||||
|
||||
// Titel
|
||||
BitmapText title = text("TASTENBELEGUNG", 20, COL_TEXT);
|
||||
centerText(title, px, py + ph - 40, pw);
|
||||
panel.attachChild(title);
|
||||
|
||||
BitmapText hint = text("Klicke eine Taste um sie neu zu belegen", 14, new ColorRGBA(0.7f, 0.7f, 0.7f, 1f));
|
||||
centerText(hint, px, py + ph - 70, pw);
|
||||
panel.attachChild(hint);
|
||||
|
||||
// Reihen
|
||||
float rowX = px + 30;
|
||||
float keyX = px + pw - 220;
|
||||
float rowW = 180;
|
||||
float rowH = 36;
|
||||
float startY = py + ph - 110;
|
||||
float stepY = 48;
|
||||
|
||||
for (int i = 0; i < KeyBindings.ENTRIES.length; i++) {
|
||||
String[] entry = KeyBindings.ENTRIES[i];
|
||||
float ry = startY - i * stepY;
|
||||
|
||||
BitmapText lbl = text(entry[1], 16, COL_TEXT);
|
||||
lbl.setLocalTranslation(rowX, ry + rowH - 8, 0);
|
||||
panel.attachChild(lbl);
|
||||
|
||||
Geometry bg = addQuad(panel, keyX, ry, rowW, rowH, COL_ROW, 0);
|
||||
|
||||
BitmapText kt = text(KeyNames.of(editCopy.get(entry[0])), 16, COL_TEXT_KEY);
|
||||
kt.setLocalTranslation(keyX + 10, ry + rowH - 8, 1);
|
||||
panel.attachChild(kt);
|
||||
|
||||
Row row = new Row();
|
||||
row.field = entry[0];
|
||||
row.label = entry[1];
|
||||
row.keyText = kt;
|
||||
row.bg = bg;
|
||||
row.x = keyX; row.y = ry; row.w = rowW; row.h = rowH;
|
||||
rows.add(row);
|
||||
}
|
||||
|
||||
// Buttons
|
||||
float btnW = 160, btnH = 42;
|
||||
float btnY = py + 25;
|
||||
saveBtnX = px + pw / 2f - btnW - 15;
|
||||
saveBtnY = btnY;
|
||||
saveBtnW = btnW;
|
||||
saveBtnH = btnH;
|
||||
cancelBtnX = px + pw / 2f + 15;
|
||||
cancelBtnY = btnY;
|
||||
|
||||
addQuad(panel, saveBtnX, saveBtnY, btnW, btnH, COL_BTN_SAVE, 0);
|
||||
BitmapText saveLabel = text("Speichern", 16, COL_TEXT);
|
||||
centerText(saveLabel, saveBtnX, saveBtnY + btnH - 10, btnW);
|
||||
panel.attachChild(saveLabel);
|
||||
|
||||
addQuad(panel, cancelBtnX, cancelBtnY, btnW, btnH, COL_BTN_CANCEL, 0);
|
||||
BitmapText cancelLabel = text("Abbrechen", 16, COL_TEXT);
|
||||
centerText(cancelLabel, cancelBtnX, cancelBtnY + btnH - 10, btnW);
|
||||
panel.attachChild(cancelLabel);
|
||||
|
||||
guiNode.attachChild(panel);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Mausklick
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private final ActionListener clickListener = (name, isPressed, tpf) -> {
|
||||
if (!isPressed) return;
|
||||
Vector2f cursor = app.getInputManager().getCursorPosition();
|
||||
|
||||
// Reihen prüfen
|
||||
for (int i = 0; i < rows.size(); i++) {
|
||||
Row r = rows.get(i);
|
||||
if (hits(cursor, r.x, r.y, r.w, r.h)) {
|
||||
waitingRow = i;
|
||||
r.bg.getMaterial().setColor("Color", COL_ROW_WAIT);
|
||||
r.keyText.setText("...");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Speichern
|
||||
if (hits(cursor, saveBtnX, saveBtnY, saveBtnW, saveBtnH)) {
|
||||
liveBindings.copyFrom(editCopy);
|
||||
KeyBindingStore.save(liveBindings);
|
||||
if (onSave != null) onSave.run();
|
||||
setEnabled(false);
|
||||
if (onClose != null) onClose.run();
|
||||
return;
|
||||
}
|
||||
|
||||
// Abbrechen
|
||||
if (hits(cursor, cancelBtnX, cancelBtnY, saveBtnW, saveBtnH)) {
|
||||
setEnabled(false);
|
||||
if (onClose != null) onClose.run();
|
||||
}
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Tastendruck beim Warten auf Zuweisung (RawInputListener)
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onKeyEvent(KeyInputEvent evt) {
|
||||
if (!evt.isPressed() || waitingRow < 0) return;
|
||||
if (evt.getKeyCode() == KeyInput.KEY_ESCAPE) return; // cancelWaiting() wird von BlightApp aufgerufen
|
||||
|
||||
Row r = rows.get(waitingRow);
|
||||
editCopy.set(r.field, evt.getKeyCode());
|
||||
r.keyText.setText(KeyNames.of(evt.getKeyCode()));
|
||||
resetRowColor(waitingRow);
|
||||
waitingRow = -1;
|
||||
}
|
||||
|
||||
private void resetRowColor(int idx) {
|
||||
rows.get(idx).bg.getMaterial().setColor("Color", COL_ROW);
|
||||
}
|
||||
|
||||
// RawInputListener-Pflichtmethoden
|
||||
@Override public void beginInput() {}
|
||||
@Override public void endInput() {}
|
||||
@Override public void onMouseMotionEvent(MouseMotionEvent evt) {}
|
||||
@Override public void onMouseButtonEvent(MouseButtonEvent evt) {}
|
||||
@Override public void onJoyAxisEvent(JoyAxisEvent evt) {}
|
||||
@Override public void onJoyButtonEvent(JoyButtonEvent evt) {}
|
||||
@Override public void onTouchEvent(TouchEvent evt) {}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Hilfsmethoden
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private Geometry addQuad(Node parent, float x, float y, float w, float h, ColorRGBA color, float z) {
|
||||
Geometry geo = new Geometry("q", new Quad(w, h));
|
||||
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setColor("Color", color.clone());
|
||||
if (color.a < 1f) {
|
||||
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
||||
geo.setQueueBucket(RenderQueue.Bucket.Transparent);
|
||||
}
|
||||
geo.setMaterial(mat);
|
||||
geo.setLocalTranslation(x, y, z);
|
||||
parent.attachChild(geo);
|
||||
return geo;
|
||||
}
|
||||
|
||||
private BitmapText text(String content, int size, ColorRGBA color) {
|
||||
BitmapText t = new BitmapText(font, false);
|
||||
t.setSize(size);
|
||||
t.setColor(color);
|
||||
t.setText(content);
|
||||
return t;
|
||||
}
|
||||
|
||||
private void centerText(BitmapText t, float x, float y, float width) {
|
||||
t.setLocalTranslation(x + (width - t.getLineWidth()) / 2f, y, 1);
|
||||
}
|
||||
|
||||
private boolean hits(Vector2f p, float x, float y, float w, float h) {
|
||||
return p.x >= x && p.x <= x + w && p.y >= y && p.y <= y + h;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,290 @@
|
||||
package de.blight.game.config;
|
||||
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.app.state.BaseAppState;
|
||||
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.renderer.queue.RenderQueue;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.shape.Quad;
|
||||
import com.jme3.system.AppSettings;
|
||||
|
||||
public class GraphicsScreen extends BaseAppState {
|
||||
|
||||
private static final ColorRGBA COL_BG = new ColorRGBA(0.05f, 0.05f, 0.08f, 0.88f);
|
||||
private static final ColorRGBA COL_PANEL = new ColorRGBA(0.10f, 0.10f, 0.16f, 1.00f);
|
||||
private static final ColorRGBA COL_ROW = new ColorRGBA(0.18f, 0.18f, 0.28f, 1.00f);
|
||||
private static final ColorRGBA COL_ARROW = new ColorRGBA(0.28f, 0.28f, 0.44f, 1.00f);
|
||||
private static final ColorRGBA COL_BTN_OK = new ColorRGBA(0.15f, 0.40f, 0.15f, 1.00f);
|
||||
private static final ColorRGBA COL_BTN_CANCEL = new ColorRGBA(0.40f, 0.15f, 0.15f, 1.00f);
|
||||
private static final ColorRGBA COL_TEXT = ColorRGBA.White;
|
||||
private static final ColorRGBA COL_TEXT_VAL = new ColorRGBA(0.85f, 0.85f, 0.50f, 1.00f);
|
||||
|
||||
private static final int[][] RESOLUTIONS = {
|
||||
{1280, 720}, {1600, 900}, {1920, 1080}, {2560, 1440}, {3840, 2160}
|
||||
};
|
||||
private static final int[] SAMPLES = {0, 2, 4, 8};
|
||||
|
||||
private static final int ROW_RES = 0;
|
||||
private static final int ROW_FULL = 1;
|
||||
private static final int ROW_VSYNC = 2;
|
||||
private static final int ROW_AA = 3;
|
||||
|
||||
private SimpleApplication app;
|
||||
private Node guiNode;
|
||||
private BitmapFont font;
|
||||
private Node panel;
|
||||
|
||||
private final GraphicsSettings live;
|
||||
private GraphicsSettings edit;
|
||||
private final Runnable onClose;
|
||||
|
||||
private int resIdx;
|
||||
private int samplesIdx;
|
||||
|
||||
// Per-row layout (indexed by ROW_*)
|
||||
private final float[] cellX = new float[4];
|
||||
private final float[] cellY = new float[4];
|
||||
private final float[] cellW = new float[4];
|
||||
private final float cellH = 36;
|
||||
private final float arrW = 30;
|
||||
private final float[] leftX = new float[4];
|
||||
private final float[] rightX = new float[4];
|
||||
private final BitmapText[] valTexts = new BitmapText[4];
|
||||
|
||||
private float okX, okY, okW, okH;
|
||||
private float cancelX, cancelY;
|
||||
|
||||
public GraphicsScreen(GraphicsSettings live, Runnable onClose) {
|
||||
this.live = live;
|
||||
this.onClose = onClose;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize(Application app) {
|
||||
this.app = (SimpleApplication) app;
|
||||
this.guiNode = this.app.getGuiNode();
|
||||
this.font = app.getAssetManager().loadFont("Interface/Fonts/Default.fnt");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEnable() {
|
||||
edit = new GraphicsSettings();
|
||||
edit.width = live.width; edit.height = live.height;
|
||||
edit.fullscreen = live.fullscreen;
|
||||
edit.vsync = live.vsync;
|
||||
edit.samples = live.samples;
|
||||
|
||||
resIdx = 0;
|
||||
for (int i = 0; i < RESOLUTIONS.length; i++) {
|
||||
if (RESOLUTIONS[i][0] == edit.width && RESOLUTIONS[i][1] == edit.height) {
|
||||
resIdx = i; break;
|
||||
}
|
||||
}
|
||||
samplesIdx = 0;
|
||||
for (int i = 0; i < SAMPLES.length; i++) {
|
||||
if (SAMPLES[i] == edit.samples) { samplesIdx = i; break; }
|
||||
}
|
||||
|
||||
buildUI();
|
||||
app.getInputManager().setCursorVisible(true);
|
||||
app.getInputManager().addMapping("_GfxClick", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
|
||||
app.getInputManager().addListener(clickListener, "_GfxClick");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDisable() {
|
||||
if (panel != null) { guiNode.detachChild(panel); panel = null; }
|
||||
app.getInputManager().deleteMapping("_GfxClick");
|
||||
app.getInputManager().setCursorVisible(false);
|
||||
}
|
||||
|
||||
@Override protected void cleanup(Application app) {}
|
||||
|
||||
private void buildUI() {
|
||||
float sw = app.getCamera().getWidth();
|
||||
float sh = app.getCamera().getHeight();
|
||||
panel = new Node("gfx-panel");
|
||||
addQuad(panel, 0, 0, sw, sh, COL_BG, -2);
|
||||
|
||||
float pw = 640, ph = 400;
|
||||
float px = (sw - pw) / 2f, py = (sh - ph) / 2f;
|
||||
addQuad(panel, px, py, pw, ph, COL_PANEL, -1);
|
||||
|
||||
BitmapText title = txt("GRAFIKEINSTELLUNGEN", 20, COL_TEXT);
|
||||
centerText(title, px, py + ph - 42, pw);
|
||||
panel.attachChild(title);
|
||||
|
||||
String[] labels = {"Auflösung", "Vollbild", "VSync", "Kantenglättung"};
|
||||
float lblX = px + 30;
|
||||
float vx = px + pw - 270;
|
||||
float vw = 190;
|
||||
float startY = py + ph - 100;
|
||||
float step = 60;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
float ry = startY - i * step;
|
||||
|
||||
BitmapText lbl = txt(labels[i], 16, COL_TEXT);
|
||||
lbl.setLocalTranslation(lblX, ry + cellH - 8, 0);
|
||||
panel.attachChild(lbl);
|
||||
|
||||
// Left arrow
|
||||
addQuad(panel, vx - arrW - 6, ry, arrW, cellH, COL_ARROW, 0);
|
||||
BitmapText lt = txt("<", 16, COL_TEXT);
|
||||
lt.setLocalTranslation(vx - arrW - 6 + (arrW - lt.getLineWidth()) / 2f, ry + cellH - 8, 1);
|
||||
panel.attachChild(lt);
|
||||
|
||||
// Value cell
|
||||
addQuad(panel, vx, ry, vw, cellH, COL_ROW, 0);
|
||||
|
||||
// Right arrow
|
||||
addQuad(panel, vx + vw + 6, ry, arrW, cellH, COL_ARROW, 0);
|
||||
BitmapText rt = txt(">", 16, COL_TEXT);
|
||||
rt.setLocalTranslation(vx + vw + 6 + (arrW - rt.getLineWidth()) / 2f, ry + cellH - 8, 1);
|
||||
panel.attachChild(rt);
|
||||
|
||||
BitmapText vt = txt("", 16, COL_TEXT_VAL);
|
||||
panel.attachChild(vt);
|
||||
valTexts[i] = vt;
|
||||
|
||||
cellX[i] = vx; cellY[i] = ry; cellW[i] = vw;
|
||||
leftX[i] = vx - arrW - 6;
|
||||
rightX[i] = vx + vw + 6;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 4; i++) refreshText(i);
|
||||
|
||||
float bw = 160, bh = 42;
|
||||
okW = bw; okH = bh;
|
||||
okX = px + pw / 2f - bw - 10;
|
||||
okY = py + 22;
|
||||
cancelX = px + pw / 2f + 10;
|
||||
cancelY = py + 22;
|
||||
|
||||
addQuad(panel, okX, okY, bw, bh, COL_BTN_OK, 0);
|
||||
BitmapText okLbl = txt("Übernehmen", 16, COL_TEXT);
|
||||
centerText(okLbl, okX, okY + bh - 10, bw);
|
||||
panel.attachChild(okLbl);
|
||||
|
||||
addQuad(panel, cancelX, cancelY, bw, bh, COL_BTN_CANCEL, 0);
|
||||
BitmapText cancelLbl = txt("Abbrechen", 16, COL_TEXT);
|
||||
centerText(cancelLbl, cancelX, cancelY + bh - 10, bw);
|
||||
panel.attachChild(cancelLbl);
|
||||
|
||||
guiNode.attachChild(panel);
|
||||
}
|
||||
|
||||
private void refreshText(int row) {
|
||||
String val = switch (row) {
|
||||
case ROW_RES -> RESOLUTIONS[resIdx][0] + "x" + RESOLUTIONS[resIdx][1];
|
||||
case ROW_FULL -> edit.fullscreen ? "An" : "Aus";
|
||||
case ROW_VSYNC -> edit.vsync ? "An" : "Aus";
|
||||
case ROW_AA -> SAMPLES[samplesIdx] == 0 ? "Aus" : SAMPLES[samplesIdx] + "x MSAA";
|
||||
default -> "";
|
||||
};
|
||||
BitmapText vt = valTexts[row];
|
||||
vt.setText(val);
|
||||
vt.setLocalTranslation(
|
||||
cellX[row] + (cellW[row] - vt.getLineWidth()) / 2f,
|
||||
cellY[row] + cellH - 8,
|
||||
1
|
||||
);
|
||||
}
|
||||
|
||||
private final ActionListener clickListener = (name, isPressed, tpf) -> {
|
||||
if (!isPressed) return;
|
||||
Vector2f c = app.getInputManager().getCursorPosition();
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (hits(c, leftX[i], cellY[i], arrW, cellH)) { cycleRow(i, -1); return; }
|
||||
if (hits(c, rightX[i], cellY[i], arrW, cellH)) { cycleRow(i, +1); return; }
|
||||
}
|
||||
if (hits(c, okX, okY, okW, okH)) { applyAndSave(); return; }
|
||||
if (hits(c, cancelX, cancelY, okW, okH)) { close(); }
|
||||
};
|
||||
|
||||
private void cycleRow(int row, int dir) {
|
||||
switch (row) {
|
||||
case ROW_RES:
|
||||
resIdx = (resIdx + dir + RESOLUTIONS.length) % RESOLUTIONS.length;
|
||||
edit.width = RESOLUTIONS[resIdx][0];
|
||||
edit.height = RESOLUTIONS[resIdx][1];
|
||||
break;
|
||||
case ROW_FULL:
|
||||
edit.fullscreen = !edit.fullscreen;
|
||||
break;
|
||||
case ROW_VSYNC:
|
||||
edit.vsync = !edit.vsync;
|
||||
break;
|
||||
case ROW_AA:
|
||||
samplesIdx = (samplesIdx + dir + SAMPLES.length) % SAMPLES.length;
|
||||
edit.samples = SAMPLES[samplesIdx];
|
||||
break;
|
||||
}
|
||||
refreshText(row);
|
||||
}
|
||||
|
||||
private void applyAndSave() {
|
||||
live.width = edit.width; live.height = edit.height;
|
||||
live.fullscreen = edit.fullscreen;
|
||||
live.vsync = edit.vsync;
|
||||
live.samples = edit.samples;
|
||||
|
||||
GraphicsStore.save(live);
|
||||
|
||||
AppSettings s = app.getContext().getSettings();
|
||||
s.setResolution(live.width, live.height);
|
||||
s.setFullscreen(live.fullscreen);
|
||||
s.setVSync(live.vsync);
|
||||
s.setSamples(live.samples);
|
||||
app.setSettings(s);
|
||||
|
||||
close();
|
||||
app.restart();
|
||||
}
|
||||
|
||||
private void close() {
|
||||
setEnabled(false);
|
||||
if (onClose != null) onClose.run();
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private Geometry addQuad(Node parent, float x, float y, float w, float h, ColorRGBA color, float z) {
|
||||
Geometry geo = new Geometry("q", new Quad(w, h));
|
||||
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setColor("Color", color.clone());
|
||||
if (color.a < 1f) {
|
||||
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
||||
geo.setQueueBucket(RenderQueue.Bucket.Transparent);
|
||||
}
|
||||
geo.setMaterial(mat);
|
||||
geo.setLocalTranslation(x, y, z);
|
||||
parent.attachChild(geo);
|
||||
return geo;
|
||||
}
|
||||
|
||||
private BitmapText txt(String s, int size, ColorRGBA color) {
|
||||
BitmapText t = new BitmapText(font, false);
|
||||
t.setSize(size); t.setColor(color); t.setText(s);
|
||||
return t;
|
||||
}
|
||||
|
||||
private void centerText(BitmapText t, float x, float y, float width) {
|
||||
t.setLocalTranslation(x + (width - t.getLineWidth()) / 2f, y, 1);
|
||||
}
|
||||
|
||||
private boolean hits(Vector2f p, float x, float y, float w, float h) {
|
||||
return p.x >= x && p.x <= x + w && p.y >= y && p.y <= y + h;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package de.blight.game.config;
|
||||
|
||||
public class GraphicsSettings {
|
||||
public int width = 1280;
|
||||
public int height = 720;
|
||||
public boolean fullscreen = false;
|
||||
public boolean vsync = false;
|
||||
public int samples = 4;
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package de.blight.game.config;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.*;
|
||||
|
||||
public class GraphicsStore {
|
||||
|
||||
private static final Path FILE = Paths.get("config", "graphics.json");
|
||||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||
|
||||
public static GraphicsSettings load() {
|
||||
if (Files.exists(FILE)) {
|
||||
try (Reader r = Files.newBufferedReader(FILE)) {
|
||||
return GSON.fromJson(r, GraphicsSettings.class);
|
||||
} catch (IOException e) {
|
||||
System.err.println("graphics.json konnte nicht geladen werden: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
return new GraphicsSettings();
|
||||
}
|
||||
|
||||
public static void save(GraphicsSettings gs) {
|
||||
try {
|
||||
Files.createDirectories(FILE.getParent());
|
||||
try (Writer w = Files.newBufferedWriter(FILE)) {
|
||||
GSON.toJson(gs, w);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.err.println("graphics.json konnte nicht gespeichert werden: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package de.blight.game.config;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.*;
|
||||
|
||||
public class KeyBindingStore {
|
||||
|
||||
private static final Path FILE = Paths.get("config", "keybindings.json");
|
||||
private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create();
|
||||
|
||||
public static KeyBindings load() {
|
||||
if (Files.exists(FILE)) {
|
||||
try (Reader r = Files.newBufferedReader(FILE)) {
|
||||
return GSON.fromJson(r, KeyBindings.class);
|
||||
} catch (IOException e) {
|
||||
System.err.println("keybindings.json konnte nicht geladen werden: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
return new KeyBindings();
|
||||
}
|
||||
|
||||
public static void save(KeyBindings kb) {
|
||||
try {
|
||||
Files.createDirectories(FILE.getParent());
|
||||
try (Writer w = Files.newBufferedWriter(FILE)) {
|
||||
GSON.toJson(kb, w);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
System.err.println("keybindings.json konnte nicht gespeichert werden: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package de.blight.game.config;
|
||||
|
||||
import com.jme3.input.KeyInput;
|
||||
|
||||
/** Speichert alle konfigurierbaren Tastenbelegungen als plain int-Felder (KeyInput-Codes). */
|
||||
public class KeyBindings {
|
||||
|
||||
public int forward = KeyInput.KEY_W;
|
||||
public int backward = KeyInput.KEY_S;
|
||||
public int left = KeyInput.KEY_A;
|
||||
public int right = KeyInput.KEY_D;
|
||||
public int jump = KeyInput.KEY_SPACE;
|
||||
public int sprint = KeyInput.KEY_LSHIFT;
|
||||
|
||||
/** Metadaten für die Config-UI: Feldname im Objekt + Anzeigename. */
|
||||
public static final String[][] ENTRIES = {
|
||||
{"forward", "Vorwärts"},
|
||||
{"backward", "Rückwärts"},
|
||||
{"left", "Links"},
|
||||
{"right", "Rechts"},
|
||||
{"jump", "Springen"},
|
||||
{"sprint", "Rennen"},
|
||||
};
|
||||
|
||||
public int get(String fieldName) {
|
||||
try { return (int) KeyBindings.class.getField(fieldName).get(this); }
|
||||
catch (Exception e) { return 0; }
|
||||
}
|
||||
|
||||
public void set(String fieldName, int keyCode) {
|
||||
try { KeyBindings.class.getField(fieldName).setInt(this, keyCode); }
|
||||
catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
public KeyBindings copy() {
|
||||
KeyBindings c = new KeyBindings();
|
||||
for (String[] e : ENTRIES) c.set(e[0], get(e[0]));
|
||||
return c;
|
||||
}
|
||||
|
||||
public void copyFrom(KeyBindings src) {
|
||||
for (String[] e : ENTRIES) set(e[0], src.get(e[0]));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package de.blight.game.config;
|
||||
|
||||
import com.jme3.input.KeyInput;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/** Bildet KeyInput-Codes auf lesbare Namen ab (via Reflection auf KeyInput-Konstanten). */
|
||||
public final class KeyNames {
|
||||
|
||||
private static final Map<Integer, String> NAMES = new HashMap<>();
|
||||
|
||||
static {
|
||||
for (Field f : KeyInput.class.getFields()) {
|
||||
if (f.getName().startsWith("KEY_") && f.getType() == int.class) {
|
||||
try {
|
||||
NAMES.put(f.getInt(null), pretty(f.getName().substring(4)));
|
||||
} catch (IllegalAccessException ignored) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String of(int keyCode) {
|
||||
return NAMES.getOrDefault(keyCode, "Key#" + keyCode);
|
||||
}
|
||||
|
||||
private static String pretty(String raw) {
|
||||
// "LSHIFT" → "L-Shift", "SPACE" → "Space", "W" → "W"
|
||||
return switch (raw) {
|
||||
case "SPACE" -> "Space";
|
||||
case "LSHIFT" -> "L-Shift";
|
||||
case "RSHIFT" -> "R-Shift";
|
||||
case "LCONTROL"-> "L-Ctrl";
|
||||
case "RCONTROL"-> "R-Ctrl";
|
||||
case "LMENU" -> "L-Alt";
|
||||
case "RMENU" -> "R-Alt";
|
||||
case "RETURN" -> "Enter";
|
||||
case "BACK" -> "Backspace";
|
||||
case "UP" -> "Pfeil-Hoch";
|
||||
case "DOWN" -> "Pfeil-Runter";
|
||||
case "LEFT" -> "Pfeil-Links";
|
||||
case "RIGHT" -> "Pfeil-Rechts";
|
||||
default -> raw.length() == 1 ? raw : capitalize(raw);
|
||||
};
|
||||
}
|
||||
|
||||
private static String capitalize(String s) {
|
||||
return s.isEmpty() ? s : s.charAt(0) + s.substring(1).toLowerCase();
|
||||
}
|
||||
}
|
||||
168
blight-game/src/main/java/de/blight/game/config/PauseMenu.java
Normal file
168
blight-game/src/main/java/de/blight/game/config/PauseMenu.java
Normal file
@@ -0,0 +1,168 @@
|
||||
package de.blight.game.config;
|
||||
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.app.state.BaseAppState;
|
||||
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.renderer.queue.RenderQueue;
|
||||
import com.jme3.scene.Geometry;
|
||||
import com.jme3.scene.Node;
|
||||
import com.jme3.scene.shape.Quad;
|
||||
|
||||
public class PauseMenu extends BaseAppState {
|
||||
|
||||
private static final ColorRGBA COL_BG = new ColorRGBA(0.05f, 0.05f, 0.08f, 0.88f);
|
||||
private static final ColorRGBA COL_PANEL = new ColorRGBA(0.10f, 0.10f, 0.16f, 1.00f);
|
||||
private static final ColorRGBA COL_BTN = new ColorRGBA(0.18f, 0.18f, 0.28f, 1.00f);
|
||||
private static final ColorRGBA COL_BTN_DIS = new ColorRGBA(0.12f, 0.12f, 0.18f, 1.00f);
|
||||
private static final ColorRGBA COL_BTN_QUIT = new ColorRGBA(0.38f, 0.10f, 0.10f, 1.00f);
|
||||
private static final ColorRGBA COL_TEXT = ColorRGBA.White;
|
||||
private static final ColorRGBA COL_TEXT_DIS = new ColorRGBA(0.40f, 0.40f, 0.40f, 1.00f);
|
||||
private static final ColorRGBA COL_TEXT_SUB = new ColorRGBA(0.35f, 0.35f, 0.35f, 1.00f);
|
||||
|
||||
private static final int BTN_GRAFIK = 0;
|
||||
private static final int BTN_AUDIO = 1;
|
||||
private static final int BTN_STEUERUNG = 2;
|
||||
private static final int BTN_BEENDEN = 3;
|
||||
|
||||
private SimpleApplication app;
|
||||
private Node guiNode;
|
||||
private BitmapFont font;
|
||||
private Node panel;
|
||||
|
||||
private Runnable onGraphics;
|
||||
private Runnable onControls;
|
||||
|
||||
// [x, y, w, h] per button
|
||||
private final float[][] btnBounds = new float[4][4];
|
||||
|
||||
public PauseMenu(Runnable onGraphics, Runnable onControls) {
|
||||
this.onGraphics = onGraphics;
|
||||
this.onControls = onControls;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initialize(Application app) {
|
||||
this.app = (SimpleApplication) app;
|
||||
this.guiNode = this.app.getGuiNode();
|
||||
this.font = app.getAssetManager().loadFont("Interface/Fonts/Default.fnt");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEnable() {
|
||||
buildUI();
|
||||
app.getInputManager().setCursorVisible(true);
|
||||
app.getInputManager().addMapping("_PauseClick", new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
|
||||
app.getInputManager().addListener(clickListener, "_PauseClick");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDisable() {
|
||||
if (panel != null) { guiNode.detachChild(panel); panel = null; }
|
||||
app.getInputManager().deleteMapping("_PauseClick");
|
||||
app.getInputManager().setCursorVisible(false);
|
||||
}
|
||||
|
||||
@Override protected void cleanup(Application app) {}
|
||||
|
||||
private void buildUI() {
|
||||
float sw = app.getCamera().getWidth();
|
||||
float sh = app.getCamera().getHeight();
|
||||
panel = new Node("pause-panel");
|
||||
addQuad(panel, 0, 0, sw, sh, COL_BG, -2);
|
||||
|
||||
float pw = 320, ph = 360;
|
||||
float px = (sw - pw) / 2f, py = (sh - ph) / 2f;
|
||||
addQuad(panel, px, py, pw, ph, COL_PANEL, -1);
|
||||
|
||||
BitmapText title = txt("PAUSE", 26, COL_TEXT);
|
||||
centerText(title, px, py + ph - 48, pw);
|
||||
panel.attachChild(title);
|
||||
|
||||
String[] labels = {"Grafik", "Audio", "Steuerung", "Beenden"};
|
||||
boolean[] enabled = {true, false, true, true};
|
||||
float bw = 260, bh = 52;
|
||||
float bx = px + (pw - bw) / 2f;
|
||||
float startY = py + ph - 112;
|
||||
float step = 62;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
float by = startY - i * step;
|
||||
ColorRGBA bgCol = !enabled[i] ? COL_BTN_DIS : (i == BTN_BEENDEN ? COL_BTN_QUIT : COL_BTN);
|
||||
ColorRGBA txCol = enabled[i] ? COL_TEXT : COL_TEXT_DIS;
|
||||
|
||||
addQuad(panel, bx, by, bw, bh, bgCol, 0);
|
||||
|
||||
BitmapText lbl = txt(labels[i], 18, txCol);
|
||||
if (!enabled[i]) {
|
||||
// Center label in upper portion, show hint below
|
||||
lbl.setLocalTranslation(bx + (bw - lbl.getLineWidth()) / 2f, by + bh - 12, 1);
|
||||
BitmapText hint = txt("Bald verfügbar", 12, COL_TEXT_SUB);
|
||||
hint.setLocalTranslation(bx + (bw - hint.getLineWidth()) / 2f, by + 14, 1);
|
||||
panel.attachChild(hint);
|
||||
} else {
|
||||
centerText(lbl, bx, by + bh - 16, bw);
|
||||
}
|
||||
panel.attachChild(lbl);
|
||||
|
||||
btnBounds[i][0] = bx; btnBounds[i][1] = by;
|
||||
btnBounds[i][2] = bw; btnBounds[i][3] = bh;
|
||||
}
|
||||
|
||||
guiNode.attachChild(panel);
|
||||
}
|
||||
|
||||
private final ActionListener clickListener = (name, isPressed, tpf) -> {
|
||||
if (!isPressed) return;
|
||||
Vector2f c = app.getInputManager().getCursorPosition();
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
if (!hits(c, btnBounds[i][0], btnBounds[i][1], btnBounds[i][2], btnBounds[i][3])) continue;
|
||||
switch (i) {
|
||||
case BTN_GRAFIK -> { if (onGraphics != null) onGraphics.run(); }
|
||||
case BTN_AUDIO -> { /* Bald verfügbar */ }
|
||||
case BTN_STEUERUNG -> { if (onControls != null) onControls.run(); }
|
||||
case BTN_BEENDEN -> app.stop();
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private Geometry addQuad(Node parent, float x, float y, float w, float h, ColorRGBA color, float z) {
|
||||
Geometry geo = new Geometry("q", new Quad(w, h));
|
||||
Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
|
||||
mat.setColor("Color", color.clone());
|
||||
if (color.a < 1f) {
|
||||
mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
|
||||
geo.setQueueBucket(RenderQueue.Bucket.Transparent);
|
||||
}
|
||||
geo.setMaterial(mat);
|
||||
geo.setLocalTranslation(x, y, z);
|
||||
parent.attachChild(geo);
|
||||
return geo;
|
||||
}
|
||||
|
||||
private BitmapText txt(String s, int size, ColorRGBA color) {
|
||||
BitmapText t = new BitmapText(font, false);
|
||||
t.setSize(size); t.setColor(color); t.setText(s);
|
||||
return t;
|
||||
}
|
||||
|
||||
private void centerText(BitmapText t, float x, float y, float width) {
|
||||
t.setLocalTranslation(x + (width - t.getLineWidth()) / 2f, y, 1);
|
||||
}
|
||||
|
||||
private boolean hits(Vector2f p, float x, float y, float w, float h) {
|
||||
return p.x >= x && p.x <= x + w && p.y >= y && p.y <= y + h;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package de.blight.game.control;
|
||||
|
||||
import com.jme3.bullet.control.CharacterControl;
|
||||
import com.jme3.input.InputManager;
|
||||
import com.jme3.input.controls.ActionListener;
|
||||
import com.jme3.input.controls.KeyTrigger;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.scene.Spatial;
|
||||
import de.blight.game.config.KeyBindings;
|
||||
|
||||
public class PlayerInputControl {
|
||||
|
||||
private static final float MOVE_SPEED = 0.07f;
|
||||
private static final float SPRINT_MULT = 1.5f;
|
||||
private static final float ROTATE_SPEED = 10f;
|
||||
|
||||
private static final String[] ACTION_NAMES =
|
||||
{"Forward", "Backward", "Left", "Right", "Jump", "Sprint"};
|
||||
|
||||
private final InputManager inputManager;
|
||||
private final Camera cam;
|
||||
|
||||
private CharacterControl physicsChar;
|
||||
private Spatial visual;
|
||||
|
||||
private boolean forward, backward, left, right, sprint;
|
||||
private boolean paused = false;
|
||||
|
||||
// Listener als Feld, damit er bei reload nicht doppelt registriert wird
|
||||
private final ActionListener actionListener = (name, isPressed, tpf) -> {
|
||||
if (paused) return;
|
||||
switch (name) {
|
||||
case "Forward" -> forward = isPressed;
|
||||
case "Backward" -> backward = isPressed;
|
||||
case "Left" -> left = isPressed;
|
||||
case "Right" -> right = isPressed;
|
||||
case "Sprint" -> sprint = isPressed;
|
||||
case "Jump" -> { if (isPressed && physicsChar != null) physicsChar.jump(); }
|
||||
}
|
||||
};
|
||||
|
||||
public PlayerInputControl(InputManager inputManager, Camera cam, KeyBindings kb) {
|
||||
this.inputManager = inputManager;
|
||||
this.cam = cam;
|
||||
registerMappings(kb);
|
||||
}
|
||||
|
||||
public void setPhysicsCharacter(CharacterControl physicsChar) {
|
||||
this.physicsChar = physicsChar;
|
||||
}
|
||||
|
||||
public void setVisual(Spatial visual) {
|
||||
this.visual = visual;
|
||||
}
|
||||
|
||||
public void setPaused(boolean paused) {
|
||||
this.paused = paused;
|
||||
if (paused) {
|
||||
forward = backward = left = right = sprint = false;
|
||||
if (physicsChar != null) physicsChar.setWalkDirection(Vector3f.ZERO);
|
||||
}
|
||||
}
|
||||
|
||||
/** Löscht alte Mappings und registriert neue aus den übergebenen KeyBindings. */
|
||||
public void reloadBindings(KeyBindings kb) {
|
||||
for (String a : ACTION_NAMES) inputManager.deleteMapping(a);
|
||||
registerMappings(kb);
|
||||
// Zustand zurücksetzen, damit keine Taste „hängt"
|
||||
forward = backward = left = right = sprint = false;
|
||||
if (physicsChar != null) physicsChar.setWalkDirection(Vector3f.ZERO);
|
||||
}
|
||||
|
||||
private void registerMappings(KeyBindings kb) {
|
||||
inputManager.addMapping("Forward", new KeyTrigger(kb.forward));
|
||||
inputManager.addMapping("Backward", new KeyTrigger(kb.backward));
|
||||
inputManager.addMapping("Left", new KeyTrigger(kb.left));
|
||||
inputManager.addMapping("Right", new KeyTrigger(kb.right));
|
||||
inputManager.addMapping("Jump", new KeyTrigger(kb.jump));
|
||||
inputManager.addMapping("Sprint", new KeyTrigger(kb.sprint));
|
||||
inputManager.addListener(actionListener, ACTION_NAMES);
|
||||
}
|
||||
|
||||
public void update(float tpf) {
|
||||
if (physicsChar == null || paused) return;
|
||||
|
||||
Vector3f camDir = cam.getDirection().clone().setY(0).normalizeLocal();
|
||||
Vector3f camLeft = cam.getLeft().clone().setY(0).normalizeLocal();
|
||||
|
||||
Vector3f moveDir = new Vector3f();
|
||||
if (forward) moveDir.addLocal(camDir);
|
||||
if (backward) moveDir.subtractLocal(camDir);
|
||||
if (left) moveDir.addLocal(camLeft);
|
||||
if (right) moveDir.subtractLocal(camLeft);
|
||||
|
||||
if (moveDir.lengthSquared() > 0.001f) {
|
||||
moveDir.normalizeLocal();
|
||||
float speed = sprint ? MOVE_SPEED * SPRINT_MULT : MOVE_SPEED;
|
||||
physicsChar.setWalkDirection(moveDir.mult(speed));
|
||||
|
||||
if (visual != null) {
|
||||
Quaternion targetRot = new Quaternion();
|
||||
targetRot.lookAt(moveDir, Vector3f.UNIT_Y);
|
||||
Quaternion current = visual.getLocalRotation().clone();
|
||||
current.slerp(targetRot, ROTATE_SPEED * tpf);
|
||||
visual.setLocalRotation(current);
|
||||
}
|
||||
} else {
|
||||
physicsChar.setWalkDirection(Vector3f.ZERO);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package de.blight.game.control;
|
||||
|
||||
import com.jme3.input.InputManager;
|
||||
import com.jme3.input.MouseInput;
|
||||
import com.jme3.input.controls.AnalogListener;
|
||||
import com.jme3.input.controls.MouseAxisTrigger;
|
||||
import com.jme3.math.*;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.scene.Spatial;
|
||||
|
||||
/**
|
||||
* Third-Person-Kamera:
|
||||
* - Mausbewegung → Kamera um den Charakter drehen (immer, kein Klick nötig)
|
||||
* - Y-Achse invertiert → Maus hoch = Kamera runter (oldschool)
|
||||
* - Mausrad → Zoom (Abstand)
|
||||
*/
|
||||
public class ThirdPersonCamera {
|
||||
|
||||
private static final float MOUSE_SENSITIVITY = 1.8f;
|
||||
private static final float MIN_DISTANCE = 3f;
|
||||
private static final float MAX_DISTANCE = 20f;
|
||||
private static final float MIN_VERTICAL_ANGLE = -0.3f;
|
||||
private static final float MAX_VERTICAL_ANGLE = FastMath.HALF_PI - 0.1f;
|
||||
private static final float TARGET_HEIGHT = 1.6f;
|
||||
|
||||
private final Camera cam;
|
||||
private final InputManager inputManager;
|
||||
|
||||
private Spatial target;
|
||||
|
||||
private float yaw = 0f;
|
||||
private float pitch = 0.4f;
|
||||
private float distance = 10f;
|
||||
private boolean paused = false;
|
||||
|
||||
public ThirdPersonCamera(Camera cam, InputManager inputManager) {
|
||||
this.cam = cam;
|
||||
this.inputManager = inputManager;
|
||||
registerMappings();
|
||||
}
|
||||
|
||||
public void setTarget(Spatial target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public void setPaused(boolean paused) { this.paused = paused; }
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private void registerMappings() {
|
||||
inputManager.addMapping("ZoomIn", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false));
|
||||
inputManager.addMapping("ZoomOut", new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true));
|
||||
inputManager.addMapping("MouseX", new MouseAxisTrigger(MouseInput.AXIS_X, false));
|
||||
inputManager.addMapping("MouseXNeg", new MouseAxisTrigger(MouseInput.AXIS_X, true));
|
||||
inputManager.addMapping("MouseY", new MouseAxisTrigger(MouseInput.AXIS_Y, false));
|
||||
inputManager.addMapping("MouseYNeg", new MouseAxisTrigger(MouseInput.AXIS_Y, true));
|
||||
|
||||
AnalogListener analogListener = (name, value, tpf) -> {
|
||||
if (paused) return;
|
||||
switch (name) {
|
||||
// Horizontale Rotation
|
||||
case "MouseX" -> yaw -= value * MOUSE_SENSITIVITY;
|
||||
case "MouseXNeg" -> yaw += value * MOUSE_SENSITIVITY;
|
||||
// Vertikale Rotation — Y invertiert: Maus hoch → Kamera runter
|
||||
case "MouseY" -> pitch = FastMath.clamp(pitch - value * MOUSE_SENSITIVITY, MIN_VERTICAL_ANGLE, MAX_VERTICAL_ANGLE);
|
||||
case "MouseYNeg" -> pitch = FastMath.clamp(pitch + value * MOUSE_SENSITIVITY, MIN_VERTICAL_ANGLE, MAX_VERTICAL_ANGLE);
|
||||
// Zoom
|
||||
case "ZoomIn" -> distance = FastMath.clamp(distance - value * 20f, MIN_DISTANCE, MAX_DISTANCE);
|
||||
case "ZoomOut" -> distance = FastMath.clamp(distance + value * 20f, MIN_DISTANCE, MAX_DISTANCE);
|
||||
}
|
||||
};
|
||||
inputManager.addListener(analogListener,
|
||||
"MouseX", "MouseXNeg", "MouseY", "MouseYNeg", "ZoomIn", "ZoomOut");
|
||||
}
|
||||
|
||||
public void update(float tpf) {
|
||||
if (target == null) return;
|
||||
|
||||
Vector3f pivot = target.getWorldTranslation().add(0, TARGET_HEIGHT, 0);
|
||||
|
||||
// Sphärische Koordinaten → kartesisch
|
||||
float x = distance * FastMath.cos(pitch) * FastMath.sin(yaw);
|
||||
float y = distance * FastMath.sin(pitch);
|
||||
float z = distance * FastMath.cos(pitch) * FastMath.cos(yaw);
|
||||
|
||||
Vector3f camPos = pivot.add(x, y, z);
|
||||
cam.setLocation(camPos);
|
||||
cam.lookAt(pivot, Vector3f.UNIT_Y);
|
||||
}
|
||||
|
||||
/** Aktueller Yaw-Winkel (für CharacterControl nutzbar). */
|
||||
public float getYaw() { return yaw; }
|
||||
}
|
||||
308
blight-game/src/main/java/de/blight/game/scene/WorldScene.java
Normal file
308
blight-game/src/main/java/de/blight/game/scene/WorldScene.java
Normal file
@@ -0,0 +1,308 @@
|
||||
package de.blight.game.scene;
|
||||
|
||||
import com.jme3.app.Application;
|
||||
import com.jme3.app.SimpleApplication;
|
||||
import com.jme3.app.state.BaseAppState;
|
||||
import com.jme3.asset.AssetManager;
|
||||
import com.jme3.bullet.BulletAppState;
|
||||
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
|
||||
import com.jme3.bullet.control.CharacterControl;
|
||||
import com.jme3.bullet.control.RigidBodyControl;
|
||||
import com.jme3.bullet.util.CollisionShapeFactory;
|
||||
import com.jme3.light.*;
|
||||
import com.jme3.material.Material;
|
||||
import com.jme3.math.*;
|
||||
import com.jme3.renderer.queue.RenderQueue;
|
||||
import com.jme3.scene.*;
|
||||
import com.jme3.scene.shape.*;
|
||||
import com.jme3.shadow.*;
|
||||
import com.jme3.terrain.geomipmap.*;
|
||||
import com.jme3.texture.*;
|
||||
import com.jme3.util.SkyFactory;
|
||||
import de.blight.game.config.KeyBindings;
|
||||
import de.blight.game.control.PlayerInputControl;
|
||||
import de.blight.game.control.ThirdPersonCamera;
|
||||
|
||||
public class WorldScene extends BaseAppState {
|
||||
|
||||
private SimpleApplication app;
|
||||
private Node rootNode;
|
||||
private AssetManager assetManager;
|
||||
private BulletAppState bulletAppState;
|
||||
|
||||
private final KeyBindings keyBindings;
|
||||
private ThirdPersonCamera thirdPersonCam;
|
||||
private PlayerInputControl playerInput;
|
||||
|
||||
public WorldScene(KeyBindings keyBindings) {
|
||||
this.keyBindings = keyBindings;
|
||||
}
|
||||
|
||||
/** Wird von ConfigScreen nach dem Speichern aufgerufen. */
|
||||
public void reloadBindings(KeyBindings kb) {
|
||||
if (playerInput != null) playerInput.reloadBindings(kb);
|
||||
}
|
||||
|
||||
public void setPaused(boolean paused) {
|
||||
if (playerInput != null) playerInput.setPaused(paused);
|
||||
if (thirdPersonCam != null) thirdPersonCam.setPaused(paused);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Lifecycle
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void initialize(Application app) {
|
||||
this.app = (SimpleApplication) app;
|
||||
this.rootNode = this.app.getRootNode();
|
||||
this.assetManager = app.getAssetManager();
|
||||
|
||||
bulletAppState = new BulletAppState();
|
||||
app.getStateManager().attach(bulletAppState);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onEnable() {
|
||||
buildLighting();
|
||||
TerrainQuad terrain = buildTerrain();
|
||||
buildDecorations(terrain);
|
||||
|
||||
Node character = buildCharacter();
|
||||
rootNode.attachChild(character);
|
||||
|
||||
// Bullet-Charakter: Kapsel 0.4 Radius, 1.0 Höhe, Y-Achse (1)
|
||||
CapsuleCollisionShape capsule = new CapsuleCollisionShape(0.4f, 1.0f, 1);
|
||||
CharacterControl physicsChar = new CharacterControl(capsule, 0.05f);
|
||||
physicsChar.setJumpSpeed(12f);
|
||||
physicsChar.setFallSpeed(35f);
|
||||
physicsChar.setGravity(35f);
|
||||
physicsChar.setPhysicsLocation(new Vector3f(0, 5f, 0));
|
||||
character.addControl(physicsChar);
|
||||
bulletAppState.getPhysicsSpace().add(physicsChar);
|
||||
|
||||
playerInput = new PlayerInputControl(app.getInputManager(), app.getCamera(), keyBindings);
|
||||
playerInput.setPhysicsCharacter(physicsChar);
|
||||
playerInput.setVisual(character);
|
||||
|
||||
thirdPersonCam = new ThirdPersonCamera(app.getCamera(), app.getInputManager());
|
||||
thirdPersonCam.setTarget(character);
|
||||
|
||||
// Maus einfangen – keine Klick-Pflicht für Kamerasteuerung
|
||||
app.getInputManager().setCursorVisible(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(float tpf) {
|
||||
playerInput.update(tpf);
|
||||
thirdPersonCam.update(tpf);
|
||||
}
|
||||
|
||||
@Override protected void cleanup(Application app) {}
|
||||
@Override protected void onDisable() {}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Beleuchtung
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private void buildLighting() {
|
||||
DirectionalLight sun = new DirectionalLight();
|
||||
sun.setDirection(new Vector3f(-0.5f, -1f, -0.5f).normalizeLocal());
|
||||
sun.setColor(ColorRGBA.White.mult(1.4f));
|
||||
rootNode.addLight(sun);
|
||||
|
||||
AmbientLight ambient = new AmbientLight();
|
||||
ambient.setColor(new ColorRGBA(0.3f, 0.3f, 0.4f, 1f));
|
||||
rootNode.addLight(ambient);
|
||||
|
||||
DirectionalLightShadowRenderer shadowRenderer =
|
||||
new DirectionalLightShadowRenderer(assetManager, 2048, 3);
|
||||
shadowRenderer.setLight(sun);
|
||||
shadowRenderer.setShadowIntensity(0.4f);
|
||||
app.getViewPort().addProcessor(shadowRenderer);
|
||||
|
||||
try {
|
||||
Spatial sky = SkyFactory.createSky(assetManager,
|
||||
"Textures/Sky/Bright/BrightSky.dds",
|
||||
SkyFactory.EnvMapType.CubeMap);
|
||||
rootNode.attachChild(sky);
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Terrain
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private TerrainQuad buildTerrain() {
|
||||
int size = 257;
|
||||
float[] heights = new float[size * size];
|
||||
|
||||
for (int z = 0; z < size; z++) {
|
||||
for (int x = 0; x < size; x++) {
|
||||
float nx = x / (float) size;
|
||||
float nz = z / (float) size;
|
||||
heights[z * size + x] =
|
||||
FastMath.sin(nx * FastMath.TWO_PI * 2) * 2f
|
||||
+ FastMath.sin(nz * FastMath.TWO_PI * 3) * 1.5f
|
||||
+ FastMath.sin((nx + nz) * FastMath.TWO_PI * 1.5f) * 1f;
|
||||
}
|
||||
}
|
||||
|
||||
TerrainQuad terrain = new TerrainQuad("terrain", 65, size, heights);
|
||||
|
||||
Material terrainMat = new Material(assetManager, "Common/MatDefs/Terrain/Terrain.j3md");
|
||||
Texture grass = assetManager.loadTexture("Textures/gras.png");
|
||||
grass.setWrap(Texture.WrapMode.Repeat);
|
||||
terrainMat.setTexture("Tex1", grass);
|
||||
terrainMat.setFloat("Tex1Scale", 64f);
|
||||
|
||||
terrain.setMaterial(terrainMat);
|
||||
terrain.setLocalTranslation(0, -5f, 0);
|
||||
terrain.setLocalScale(0.5f, 0.5f, 0.5f);
|
||||
terrain.setShadowMode(RenderQueue.ShadowMode.Receive);
|
||||
|
||||
// Statischer Physics-Body (mass=0) für Terrain-Kollision
|
||||
RigidBodyControl terrainPhysics = new RigidBodyControl(
|
||||
CollisionShapeFactory.createMeshShape(terrain), 0f);
|
||||
terrain.addControl(terrainPhysics);
|
||||
bulletAppState.getPhysicsSpace().add(terrainPhysics);
|
||||
|
||||
rootNode.attachChild(terrain);
|
||||
return terrain;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Dekorationen – Höhe per TerrainQuad.getHeight() anpassen
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private void buildDecorations(TerrainQuad terrain) {
|
||||
Material stoneMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
|
||||
stoneMat.setBoolean("UseMaterialColors", true);
|
||||
stoneMat.setColor("Diffuse", new ColorRGBA(0.55f, 0.55f, 0.55f, 1f));
|
||||
stoneMat.setColor("Ambient", new ColorRGBA(0.3f, 0.3f, 0.3f, 1f));
|
||||
stoneMat.setColor("Specular", ColorRGBA.White);
|
||||
stoneMat.setFloat("Shininess", 32f);
|
||||
|
||||
Material treeTrunkMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
|
||||
treeTrunkMat.setBoolean("UseMaterialColors", true);
|
||||
treeTrunkMat.setColor("Diffuse", new ColorRGBA(0.45f, 0.28f, 0.1f, 1f));
|
||||
treeTrunkMat.setColor("Ambient", new ColorRGBA(0.2f, 0.12f, 0.04f, 1f));
|
||||
treeTrunkMat.setColor("Specular", ColorRGBA.Black);
|
||||
|
||||
Material treeTopMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
|
||||
treeTopMat.setBoolean("UseMaterialColors", true);
|
||||
treeTopMat.setColor("Diffuse", new ColorRGBA(0.1f, 0.55f, 0.15f, 1f));
|
||||
treeTopMat.setColor("Ambient", new ColorRGBA(0.05f, 0.25f, 0.07f, 1f));
|
||||
treeTopMat.setColor("Specular", ColorRGBA.Black);
|
||||
|
||||
float[][] treeXZ = {
|
||||
{12, 8}, {-15, 5}, {20, -10}, {-8, -18},
|
||||
{5, 25}, {-22, 12}, {18, 20}, {-10, -5}
|
||||
};
|
||||
for (float[] xz : treeXZ) {
|
||||
float worldY = terrainWorldY(terrain, xz[0], xz[1]);
|
||||
Node tree = new Node("tree");
|
||||
|
||||
Geometry trunk = new Geometry("trunk", new Cylinder(8, 8, 0.25f, 2.5f, true));
|
||||
trunk.setMaterial(treeTrunkMat);
|
||||
trunk.rotate(FastMath.HALF_PI, 0, 0);
|
||||
trunk.setLocalTranslation(0, 1.25f, 0);
|
||||
trunk.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||
|
||||
Geometry crown = new Geometry("crown", new Sphere(12, 12, 2.2f));
|
||||
crown.setMaterial(treeTopMat);
|
||||
crown.setLocalTranslation(0, 3.8f, 0);
|
||||
crown.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||
|
||||
tree.attachChild(trunk);
|
||||
tree.attachChild(crown);
|
||||
tree.setLocalTranslation(xz[0], worldY, xz[1]);
|
||||
rootNode.attachChild(tree);
|
||||
}
|
||||
|
||||
float[][] stoneXZ = {{6, -6}, {-12, 15}, {16, -4}, {-3, 10}};
|
||||
for (float[] xz : stoneXZ) {
|
||||
float worldY = terrainWorldY(terrain, xz[0], xz[1]);
|
||||
float r = 0.6f + FastMath.nextRandomFloat() * 0.8f;
|
||||
Geometry stone = new Geometry("stone", new Sphere(8, 8, r));
|
||||
stone.setMaterial(stoneMat);
|
||||
stone.setLocalTranslation(xz[0], worldY + r * 0.5f, xz[1]);
|
||||
stone.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||
rootNode.attachChild(stone);
|
||||
}
|
||||
}
|
||||
|
||||
/** Konvertiert Welt-XZ in lokale Terrain-Koordinaten und fragt die Höhe ab. */
|
||||
private float terrainWorldY(TerrainQuad terrain, float worldX, float worldZ) {
|
||||
Vector3f terrainTranslation = terrain.getWorldTranslation();
|
||||
Vector3f terrainScale = terrain.getWorldScale();
|
||||
float localX = (worldX - terrainTranslation.x) / terrainScale.x;
|
||||
float localZ = (worldZ - terrainTranslation.z) / terrainScale.z;
|
||||
float localH = terrain.getHeight(new Vector2f(localX, localZ));
|
||||
return terrainTranslation.y + localH * terrainScale.y;
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Charakter (visueller Node, ohne eigenes Mesh für Physics)
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
private Node buildCharacter() {
|
||||
Node character = new Node("character");
|
||||
|
||||
Material bodyMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
|
||||
bodyMat.setBoolean("UseMaterialColors", true);
|
||||
bodyMat.setColor("Diffuse", new ColorRGBA(0.2f, 0.4f, 0.8f, 1f));
|
||||
bodyMat.setColor("Ambient", new ColorRGBA(0.1f, 0.2f, 0.4f, 1f));
|
||||
bodyMat.setColor("Specular", ColorRGBA.White);
|
||||
bodyMat.setFloat("Shininess", 64f);
|
||||
|
||||
Material headMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
|
||||
headMat.setBoolean("UseMaterialColors", true);
|
||||
headMat.setColor("Diffuse", new ColorRGBA(0.9f, 0.75f, 0.6f, 1f));
|
||||
headMat.setColor("Ambient", new ColorRGBA(0.45f, 0.37f, 0.3f, 1f));
|
||||
headMat.setColor("Specular", new ColorRGBA(0.2f, 0.2f, 0.2f, 1f));
|
||||
headMat.setFloat("Shininess", 16f);
|
||||
|
||||
Geometry body = new Geometry("body", new Cylinder(8, 16, 0.35f, 1.1f, true));
|
||||
body.setMaterial(bodyMat);
|
||||
body.rotate(FastMath.HALF_PI, 0, 0);
|
||||
body.setLocalTranslation(0, 0.9f, 0);
|
||||
body.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||
|
||||
Geometry head = new Geometry("head", new Sphere(16, 16, 0.28f));
|
||||
head.setMaterial(headMat);
|
||||
head.setLocalTranslation(0, 1.75f, 0);
|
||||
head.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||
|
||||
Geometry armL = buildLimb(bodyMat, 0.1f, 0.55f);
|
||||
armL.setLocalTranslation(-0.5f, 0.85f, 0);
|
||||
armL.rotate(0, 0, FastMath.DEG_TO_RAD * 20f);
|
||||
|
||||
Geometry armR = buildLimb(bodyMat, 0.1f, 0.55f);
|
||||
armR.setLocalTranslation(0.5f, 0.85f, 0);
|
||||
armR.rotate(0, 0, FastMath.DEG_TO_RAD * -20f);
|
||||
|
||||
Geometry legL = buildLimb(bodyMat, 0.12f, 0.6f);
|
||||
legL.setLocalTranslation(-0.18f, 0.3f, 0);
|
||||
|
||||
Geometry legR = buildLimb(bodyMat, 0.12f, 0.6f);
|
||||
legR.setLocalTranslation(0.18f, 0.3f, 0);
|
||||
|
||||
character.attachChild(body);
|
||||
character.attachChild(head);
|
||||
character.attachChild(armL);
|
||||
character.attachChild(armR);
|
||||
character.attachChild(legL);
|
||||
character.attachChild(legR);
|
||||
|
||||
return character;
|
||||
}
|
||||
|
||||
private Geometry buildLimb(Material mat, float radius, float height) {
|
||||
Geometry limb = new Geometry("limb", new Cylinder(6, 12, radius, height, true));
|
||||
limb.setMaterial(mat);
|
||||
limb.rotate(FastMath.HALF_PI, 0, 0);
|
||||
limb.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
|
||||
return limb;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user