diff --git a/blight-editor/src/main/java/de/blight/editor/EditorApp.java b/blight-editor/src/main/java/de/blight/editor/EditorApp.java index fb0257d..4607bb3 100644 --- a/blight-editor/src/main/java/de/blight/editor/EditorApp.java +++ b/blight-editor/src/main/java/de/blight/editor/EditorApp.java @@ -1536,7 +1536,8 @@ public class EditorApp extends Application { root.setRight(buildLocationZonePanel()); }); playToolBtn.setOnAction(e -> { - input.activeLayer = SharedInput.LAYER_PLAY_TOOL; + input.activeLayer = SharedInput.LAYER_PLAY_TOOL; + input.playToolMode = SharedInput.PlayToolMode.EDIT; root.setRight(buildPlayToolPanel()); }); voxelBtn.setOnAction(e -> { @@ -8258,6 +8259,11 @@ public class EditorApp extends Application { p.setProperty("cam.z", String.valueOf(input.camZ)); p.setProperty("cam.yaw", String.valueOf(input.camYaw)); p.setProperty("cam.pitch", String.valueOf(input.camPitch)); + if (!Float.isNaN(input.tempSpawnX) && !Float.isNaN(input.tempSpawnZ)) { + p.setProperty("tempSpawn.x", String.valueOf(input.tempSpawnX)); + p.setProperty("tempSpawn.z", String.valueOf(input.tempSpawnZ)); + p.setProperty("tempSpawn.yaw", String.valueOf(input.tempSpawnYaw)); + } try { Files.createDirectories(BlightHome.resolve("config")); try (java.io.Writer w = Files.newBufferedWriter(BlightHome.resolve("config", "editor.prefs"))) { @@ -8857,9 +8863,8 @@ public class EditorApp extends Application { Button setTempBtn = new Button("πŸ“ Temp. Spawn hier setzen"); setTempBtn.setMaxWidth(Double.MAX_VALUE); setTempBtn.setTooltip(new javafx.scene.control.Tooltip( - "Setzt Yaw = Kamera-Blickrichtung und aktiviert Klick-Modus im Viewport")); + "Aktiviert Klick-Modus im Viewport zum Setzen des temporΓ€ren Spawnpunkts")); setTempBtn.setOnAction(e -> { - input.tempSpawnYaw = camYawToSpawnYaw(input.camYaw); input.playToolMode = SharedInput.PlayToolMode.SET_TEMP; setStatus("L-Klick im Viewport β†’ temporΓ€ren Spawnpunkt setzen"); }); @@ -8867,24 +8872,13 @@ public class EditorApp extends Application { Button setPermBtn = new Button("🏁 Perm. Spawn hier setzen"); setPermBtn.setMaxWidth(Double.MAX_VALUE); setPermBtn.setTooltip(new javafx.scene.control.Tooltip( - "Setzt Yaw = Kamera-Blickrichtung und aktiviert Klick-Modus im Viewport")); + "Aktiviert Klick-Modus im Viewport zum Setzen des permanenten Spawnpunkts")); setPermBtn.setOnAction(e -> { - input.permSpawnYaw = camYawToSpawnYaw(input.camYaw); input.playToolMode = SharedInput.PlayToolMode.SET_PERM; setStatus("L-Klick im Viewport β†’ permanenten Spawnpunkt setzen"); }); - ToggleButton editModeBtn = new ToggleButton("✎ Bearbeiten"); - editModeBtn.setMaxWidth(Double.MAX_VALUE); - editModeBtn.setTooltip(new javafx.scene.control.Tooltip( - "Drag auf Marker β†’ Position; Drag auf Pfeilspitze β†’ Richtung drehen")); - editModeBtn.setOnAction(e -> { - input.playToolMode = editModeBtn.isSelected() - ? SharedInput.PlayToolMode.EDIT - : SharedInput.PlayToolMode.NONE; - }); - - inner.getChildren().addAll(setTempBtn, setPermBtn, editModeBtn, new Separator()); + inner.getChildren().addAll(setTempBtn, setPermBtn, new Separator()); // ── Koordinatenanzeige ──────────────────────────────────────────────── tempSpawnCoordsLabel = new Label(tempSpawnCoordsText()); @@ -8945,11 +8939,6 @@ public class EditorApp extends Application { return panel; } - /** Konvertiert den Editor-Kamera-Yaw in den Spawnpunkt-Yaw (0=+Z, 90=+X, UZS von oben). */ - private static float camYawToSpawnYaw(float camYaw) { - return ((180f + camYaw) % 360f + 360f) % 360f; - } - private String tempSpawnCoordsText() { if (Float.isNaN(input.tempSpawnX) || Float.isNaN(input.tempSpawnZ)) return "(nicht gesetzt)"; return String.format("X=%.1f Z=%.1f Yaw=%.0fΒ°", input.tempSpawnX, input.tempSpawnZ, input.tempSpawnYaw); diff --git a/blight-editor/src/main/java/de/blight/editor/state/PlayToolState.java b/blight-editor/src/main/java/de/blight/editor/state/PlayToolState.java index f25287c..03eeb88 100644 --- a/blight-editor/src/main/java/de/blight/editor/state/PlayToolState.java +++ b/blight-editor/src/main/java/de/blight/editor/state/PlayToolState.java @@ -14,14 +14,13 @@ import com.jme3.terrain.geomipmap.TerrainQuad; import de.blight.editor.SharedInput; /** - * Rendert Spawn-Marker im Editor (temp=grΓΌn, perm=blau) mit Richtungspfeilen. + * Rendert Spawn-Marker im Editor (temp=grΓΌn, perm=blau) mit nordweisendem Richtungspfeil. + * Pfeil folgt der Terrain-Neigung, zeigt aber immer Richtung Norden (+Z). * * Modi: - * NONE – Marker sichtbar, aber keine Interaktion - * SET_TEMP – NΓ€chster Viewport-Klick setzt temp. Spawnpunkt - * SET_PERM – NΓ€chster Viewport-Klick setzt perm. Spawnpunkt - * EDIT – Drag auf Marker-KΓΆrper β†’ Positionieren; - * Drag auf Pfeilspitze β†’ Richtung drehen (via XZ-Projektion) + * EDIT – Standard; Drag auf Marker-Scheibe β†’ Position, Drag auf Pfeil β†’ Richtung + * SET_TEMP – NΓ€chster Viewport-Klick setzt temp. Spawnpunkt (danach β†’ EDIT) + * SET_PERM – NΓ€chster Viewport-Klick setzt perm. Spawnpunkt (danach β†’ EDIT) */ public class PlayToolState extends BaseAppState { @@ -46,12 +45,6 @@ public class PlayToolState extends BaseAppState { private Node tempArrowNode; private Node permArrowNode; - // Separate Geometrien fΓΌr Ray-Cast-Erkennung - private Geometry tempBodyGeom; - private Geometry permBodyGeom; - private Geometry tempTipGeom; - private Geometry permTipGeom; - private DragTarget dragTarget = DragTarget.NONE; private float lastDragX; private float lastDragY; @@ -72,10 +65,6 @@ public class PlayToolState extends BaseAppState { tempArrowNode = (Node) tempMarkerNode.getChild("arrow"); permArrowNode = (Node) permMarkerNode.getChild("arrow"); - tempBodyGeom = (Geometry) tempMarkerNode.getChild("body_temp"); - permBodyGeom = (Geometry) permMarkerNode.getChild("body_perm"); - tempTipGeom = (Geometry) tempArrowNode.getChild("tip_temp"); - permTipGeom = (Geometry) permArrowNode.getChild("tip_perm"); } @Override @@ -138,9 +127,10 @@ public class PlayToolState extends BaseAppState { if (pt != null) { input.tempSpawnX = pt.x; input.tempSpawnZ = pt.z; + input.tempSpawnYaw = 0f; input.pickedSpawnInfo = pt.x + "|" + pt.z; input.spawnPickChanged = true; - input.playToolMode = SharedInput.PlayToolMode.NONE; + input.playToolMode = SharedInput.PlayToolMode.EDIT; } return; } @@ -148,11 +138,12 @@ public class PlayToolState extends BaseAppState { if (mode == SharedInput.PlayToolMode.SET_PERM) { Vector3f pt = hitTerrain(ray); if (pt != null) { - input.permSpawnX = pt.x; - input.permSpawnZ = pt.z; + input.permSpawnX = pt.x; + input.permSpawnZ = pt.z; + input.permSpawnYaw = 0f; input.pickedPermSpawnInfo = pt.x + "|" + pt.z; - input.permSpawnChanged = true; - input.playToolMode = SharedInput.PlayToolMode.NONE; + input.permSpawnChanged = true; + input.playToolMode = SharedInput.PlayToolMode.EDIT; } return; } @@ -165,30 +156,86 @@ public class PlayToolState extends BaseAppState { } private DragTarget detectDragTarget(Ray ray) { - // Pfeilspitzen zuerst prΓΌfen (kleineres Ziel, hΓΆhere PrioritΓ€t) - if (tempTipGeom != null && !Float.isNaN(input.tempSpawnX)) { - CollisionResults res = new CollisionResults(); - tempTipGeom.collideWith(ray, res); - if (res.size() > 0) return DragTarget.ROT_TEMP; + DragTarget temp = detectDragTargetForSpawn(ray, + input.tempSpawnX, input.tempSpawnZ, input.tempSpawnYaw, + DragTarget.POS_TEMP, DragTarget.ROT_TEMP); + if (temp != DragTarget.NONE) return temp; + return detectDragTargetForSpawn(ray, + input.permSpawnX, input.permSpawnZ, input.permSpawnYaw, + DragTarget.POS_PERM, DragTarget.ROT_PERM); + } + + private DragTarget detectDragTargetForSpawn(Ray ray, float sx, float sz, float yawDeg, + DragTarget posTarget, DragTarget rotTarget) { + if (Float.isNaN(sx) || Float.isNaN(sz)) return DragTarget.NONE; + float markerY = terrainY(sx, sz); + Vector3f center = new Vector3f(sx, markerY + 0.06f, sz); + Vector3f up = terrainNormal(sx, sz); + + // ── Scheibe: Ray-Ebene-Schnitt mit Terrain-Normalebene ─────────────────── + float denom = ray.getDirection().dot(up); + if (Math.abs(denom) > 1e-6f) { + float t = center.subtract(ray.getOrigin()).dot(up) / denom; + if (t > 0f) { + Vector3f hit = ray.getOrigin().add(ray.getDirection().mult(t)); + if (hit.distanceSquared(center) <= MARKER_RADIUS * MARKER_RADIUS) { + return posTarget; + } + } } - if (permTipGeom != null && !Float.isNaN(input.permSpawnX)) { - CollisionResults res = new CollisionResults(); - permTipGeom.collideWith(ray, res); - if (res.size() > 0) return DragTarget.ROT_PERM; - } - if (tempBodyGeom != null && !Float.isNaN(input.tempSpawnX)) { - CollisionResults res = new CollisionResults(); - tempBodyGeom.collideWith(ray, res); - if (res.size() > 0) return DragTarget.POS_TEMP; - } - if (permBodyGeom != null && !Float.isNaN(input.permSpawnX)) { - CollisionResults res = new CollisionResults(); - permBodyGeom.collideWith(ray, res); - if (res.size() > 0) return DragTarget.POS_PERM; + + // ── Pfeil: 3D Ray-zu-Segment-Distanz (korrekt fΓΌr geneigte Terrain) ───── + float rad = yawDeg * FastMath.DEG_TO_RAD; + Vector3f flatF = new Vector3f(FastMath.sin(rad), 0f, -FastMath.cos(rad)); + Vector3f fwd = flatF.subtract(up.mult(up.dot(flatF))); + if (fwd.lengthSquared() > 1e-6f) { + fwd.normalizeLocal(); + Vector3f arrowEnd = center.add(fwd.mult(SHAFT_LEN + TIP_LEN)); + if (rayToSegDistSq(ray.getOrigin(), ray.getDirection(), center, arrowEnd) < 0.4f * 0.4f) { + return rotTarget; + } } + return DragTarget.NONE; } + /** + * Minimaler quadratischer Abstand zwischen einem Strahl (Ursprung o, normalisierte Richtung d) + * und einem Linienabschnitt [segA, segB]. + */ + private static float rayToSegDistSq(Vector3f o, Vector3f d, Vector3f segA, Vector3f segB) { + Vector3f v = segB.subtract(segA); + Vector3f w = o.subtract(segA); + float b = d.dot(v); + float c = v.dot(v); + float e = v.dot(w); + float f = d.dot(w); + float D = c - b * b; + + float sc, tc; + if (D < 1e-8f) { + sc = 0f; + tc = (c > 1e-8f) ? FastMath.clamp(e / c, 0f, 1f) : 0f; + } else { + sc = (b * e - c * f) / D; + tc = (e - b * f) / D; + if (sc < 0f) { + sc = 0f; + tc = FastMath.clamp(e / c, 0f, 1f); + } else if (tc < 0f) { + tc = 0f; + sc = Math.max(0f, -f); + } else if (tc > 1f) { + tc = 1f; + sc = Math.max(0f, b - f); + } + } + + Vector3f pR = o.add(d.mult(sc)); + Vector3f pS = segA.add(v.mult(tc)); + return pR.distanceSquared(pS); + } + // ── Drag-Handler ────────────────────────────────────────────────────────── private void handleDrag(SharedInput.PlayToolDrag drag) { @@ -227,8 +274,8 @@ public class PlayToolState extends BaseAppState { float dx = proj.x - input.tempSpawnX; float dz = proj.z - input.tempSpawnZ; if (dx * dx + dz * dz > 0.01f) { - // spawnYaw: 0=+Z, 90=+X β†’ atan2(dx, dz) - float yaw = (float) Math.toDegrees(Math.atan2(dx, dz)); + // yaw=0=Norden=-Z β†’ atan2(dx, -dz) + float yaw = (float) Math.toDegrees(Math.atan2(dx, -dz)); input.tempSpawnYaw = ((yaw % 360f) + 360f) % 360f; } } @@ -240,7 +287,7 @@ public class PlayToolState extends BaseAppState { float dx = proj.x - input.permSpawnX; float dz = proj.z - input.permSpawnZ; if (dx * dx + dz * dz > 0.01f) { - float yaw = (float) Math.toDegrees(Math.atan2(dx, dz)); + float yaw = (float) Math.toDegrees(Math.atan2(dx, -dz)); input.permSpawnYaw = ((yaw % 360f) + 360f) % 360f; input.permSpawnChanged = true; } @@ -280,7 +327,7 @@ public class PlayToolState extends BaseAppState { arrowNode.attachChild(shaftGeom); // Pfeilspitze: Kegel mit breiter Basis bei z=-TIP_LEN/2 β†’ nach rotate: Basis bei y=SHAFT_LEN (Schaft-Ende) - Cylinder tip = new Cylinder(4, 8, TIP_RADIUS, 0.001f, TIP_LEN, true, false); + Cylinder tip = new Cylinder(4, 8, 0.001f, TIP_RADIUS, TIP_LEN, true, false); Geometry tipGeom = new Geometry("tip_" + id, tip); tipGeom.setMaterial(mat); tipGeom.rotate(-FastMath.HALF_PI, 0f, 0f); @@ -304,28 +351,26 @@ public class PlayToolState extends BaseAppState { if (arrowNode == null) return; - // Terrain-Normale als lokale Oben-Richtung Vector3f up = terrainNormal(x, z); - // Yaw-Richtung als horizontaler Richtungsvektor (0=+Z, 90=+X) + // yaw=0 = Norden = -Z (JME3 Kamera blickt entlang -Z als Norden) float rad = yawDeg * FastMath.DEG_TO_RAD; - Vector3f flatForward = new Vector3f(FastMath.sin(rad), 0f, FastMath.cos(rad)); + Vector3f flatForward = new Vector3f(FastMath.sin(rad), 0f, -FastMath.cos(rad)); - // right = up Γ— flatForward (liegt auf der Terrain-OberflΓ€che, senkrecht zur Richtung) - Vector3f right = up.cross(flatForward); - if (right.lengthSquared() < 1e-6f) { - // Sonderfall: Terrain fast senkrecht β†’ horizontale Rotation als Fallback - arrowNode.setLocalRotation(new Quaternion().fromAngles(FastMath.HALF_PI, rad, 0f)); + // Auf Terrain-Ebene projizieren + Vector3f fwd = flatForward.subtract(up.mult(up.dot(flatForward))); + if (fwd.lengthSquared() < 1e-6f) { + arrowNode.setLocalRotation(Quaternion.IDENTITY); return; } - right.normalizeLocal(); - // forward = right Γ— up (auf Terrain-FlΓ€che projiziertes Forward in Yaw-Richtung) - Vector3f forward = right.cross(up).normalizeLocal(); + fwd.normalizeLocal(); + + // det=+1: col0 = fwd Γ— up + Vector3f right = fwd.cross(up).normalizeLocal(); - // Rotationsmatrix: local X β†’ right, local Y β†’ forward (Pfeil), local Z β†’ up (Terrain-Normal) Matrix3f rot = new Matrix3f(); rot.setColumn(0, right); - rot.setColumn(1, forward); + rot.setColumn(1, fwd); rot.setColumn(2, up); arrowNode.setLocalRotation(new Quaternion().fromRotationMatrix(rot)); } @@ -333,6 +378,14 @@ public class PlayToolState extends BaseAppState { // ── Terrain-Hilfsmethoden ───────────────────────────────────────────────── private Vector3f hitTerrain(Ray ray) { + VoxelEditorState ves = getStateManager().getState(VoxelEditorState.class); + if (ves != null) { + Vector3f pt = ves.clickAt(ray); + if (pt != null && ves.terrainTypeAt(pt.x, pt.z) == VoxelEditorState.TerrainType.VOXEL_UNBAKED) { + return null; + } + return pt; + } if (terrain == null) return null; CollisionResults hits = new CollisionResults(); terrain.collideWith(ray, hits); @@ -341,6 +394,8 @@ public class PlayToolState extends BaseAppState { } private float terrainY(float x, float z) { + VoxelEditorState ves = getStateManager().getState(VoxelEditorState.class); + if (ves != null) return ves.heightAt(x, z); if (terrain == null) return 0f; float h = terrain.getHeight(new Vector2f(x, z)); return Float.isNaN(h) ? 0f : h; diff --git a/blight-editor/src/main/java/de/blight/editor/state/TerrainEditorState.java b/blight-editor/src/main/java/de/blight/editor/state/TerrainEditorState.java index a83798e..4e7964d 100644 --- a/blight-editor/src/main/java/de/blight/editor/state/TerrainEditorState.java +++ b/blight-editor/src/main/java/de/blight/editor/state/TerrainEditorState.java @@ -210,6 +210,12 @@ public class TerrainEditorState extends BaseAppState { parsePref(p, "cam.z", 0f)); camYaw = (float) Math.toRadians(parsePref(p, "cam.yaw", 0f)); camPitch = (float) Math.toRadians(parsePref(p, "cam.pitch", (float) Math.toDegrees(DEFAULT_PITCH))); + if (p.containsKey("tempSpawn.x") && p.containsKey("tempSpawn.z")) { + input.tempSpawnX = parsePref(p, "tempSpawn.x", Float.NaN); + input.tempSpawnZ = parsePref(p, "tempSpawn.z", Float.NaN); + input.tempSpawnYaw = parsePref(p, "tempSpawn.yaw", 0f); + input.spawnPickChanged = true; + } } catch (IOException e) { log.warn("Kamera-Prefs nicht ladbar", e); } diff --git a/blight-game/src/main/java/de/blight/game/BlightGame.java b/blight-game/src/main/java/de/blight/game/BlightGame.java index 4758dc1..0669611 100644 --- a/blight-game/src/main/java/de/blight/game/BlightGame.java +++ b/blight-game/src/main/java/de/blight/game/BlightGame.java @@ -1,5 +1,6 @@ package de.blight.game; +import com.jme3.app.Application; import com.jme3.app.SimpleApplication; import com.jme3.app.state.ScreenshotAppState; import com.jme3.input.KeyInput; @@ -329,6 +330,35 @@ public class BlightGame extends SimpleApplication { }); stateManager.attach(console); + // ── Debug-Stats (F3): oben-links, default aus ──────────────────────────── + com.jme3.app.StatsAppState defaultStats = stateManager.getState(com.jme3.app.StatsAppState.class); + if (defaultStats != null) stateManager.detach(defaultStats); + com.jme3.app.StatsAppState statsState = new com.jme3.app.StatsAppState() { + @Override + public void initialize(com.jme3.app.state.AppStateManager sm, com.jme3.app.Application a) { + super.initialize(sm, a); + setDisplayFps(false); + setDisplayStatView(false); + int h = a.getCamera().getHeight(); + if (fpsText != null) fpsText.setLocalTranslation(0, h, 0); + if (statsView != null) { + float fpsH = fpsText != null ? fpsText.getLineHeight() : 0f; + statsView.setLocalTranslation(0, h - fpsH, 0); + } + } + }; + stateManager.attach(statsState); + final boolean[] statsVisible = {false}; + inputManager.addMapping("ToggleStats", new KeyTrigger(KeyInput.KEY_F3)); + inputManager.addListener((ActionListener) (name, isPressed, tpf) -> { + if (!isPressed) return; + com.jme3.app.StatsAppState s = stateManager.getState(com.jme3.app.StatsAppState.class); + if (s == null) return; + statsVisible[0] = !statsVisible[0]; + s.setDisplayFps(statsVisible[0]); + s.setDisplayStatView(statsVisible[0]); + }, "ToggleStats"); + // ── Debug: Nebel-Toggle (F7) ───────────────────────────────────────────── inputManager.addMapping("DebugFog", new KeyTrigger(KeyInput.KEY_F7)); inputManager.addListener((ActionListener) (name, isPressed, tpf) -> { diff --git a/blight-game/src/main/java/de/blight/game/control/ThirdPersonCamera.java b/blight-game/src/main/java/de/blight/game/control/ThirdPersonCamera.java index 3ad48a1..4c7261c 100644 --- a/blight-game/src/main/java/de/blight/game/control/ThirdPersonCamera.java +++ b/blight-game/src/main/java/de/blight/game/control/ThirdPersonCamera.java @@ -38,6 +38,13 @@ public class ThirdPersonCamera { private float distance = BASE_DISTANCE; private boolean paused = false; + // Intro-Kameradrehung (neues Spiel): yaw animiert von vorne nach hinten + private boolean introRotating = false; + private float introStartYaw; + private float introEndYaw; + private float introDuration; + private float introTimer; + public ThirdPersonCamera(Camera cam, InputManager inputManager, KeyBindings keyBindings) { this.cam = cam; @@ -65,7 +72,7 @@ public class ThirdPersonCamera { inputManager.addMapping("CamTurnRight", new KeyTrigger(keyBindings.turnRight)); AnalogListener analogListener = (name, value, tpf) -> { - if (paused) return; + if (paused || introRotating) return; switch (name) { // Horizontale Rotation β€” Maus case "MouseX" -> yaw -= value * MOUSE_SENSITIVITY; @@ -87,6 +94,13 @@ public class ThirdPersonCamera { } public void update(float tpf) { + if (introRotating) { + introTimer += tpf; + float t = FastMath.clamp(introTimer / introDuration, 0f, 1f); + yaw = introStartYaw + (introEndYaw - introStartYaw) * t; + if (t >= 1f) introRotating = false; + } + if (target == null || paused) return; Vector3f pivot = target.getWorldTranslation().add(0, TARGET_HEIGHT, 0); @@ -101,6 +115,23 @@ public class ThirdPersonCamera { cam.lookAt(pivot, Vector3f.UNIT_Y); } -/** Aktueller Yaw-Winkel (fΓΌr CharacterControl nutzbar). */ + /** Aktueller Yaw-Winkel (fΓΌr CharacterControl nutzbar). */ public float getYaw() { return yaw; } + + /** Setzt initialen Kamera-Yaw passend zum Spawn-Yaw (Kamera hinter dem Charakter). */ + public void setInitialYaw(float spawnYawDegrees) { + this.yaw = -spawnYawDegrees * FastMath.DEG_TO_RAD; + } + + /** + * Startet die Intro-Drehung: Kamera dreht sich im Uhrzeigersinn 180Β° (von vorne nach hinten) + * ΓΌber die angegebene Dauer. WΓ€hrend der Drehung ist Mauseingabe blockiert. + */ + public void startIntroRotation(float duration) { + introStartYaw = this.yaw; + introEndYaw = this.yaw - FastMath.PI; // 180Β° CW von oben gesehen + introDuration = duration; + introTimer = 0f; + introRotating = true; + } } diff --git a/blight-game/src/main/java/de/blight/game/scene/WorldScene.java b/blight-game/src/main/java/de/blight/game/scene/WorldScene.java index 68a434c..1997b05 100644 --- a/blight-game/src/main/java/de/blight/game/scene/WorldScene.java +++ b/blight-game/src/main/java/de/blight/game/scene/WorldScene.java @@ -557,7 +557,11 @@ public class WorldScene extends BaseAppState { characterVisual.setCullHint(Spatial.CullHint.Inherit); } - playerInput.setInitialFacing(spawnYaw); + playerInput.setInitialFacing(spawnYaw + 180f); + + boolean newGame = "true".equals(System.getProperty("blight.new.game")); + // Neues Spiel: Kamera startet gegenΓΌber der Blickrichtung (von vorne) und dreht sich 180Β° CW + thirdPersonCam.setInitialYaw(newGame ? spawnYaw + 180f : spawnYaw); String reviveClip = de.blight.game.animation.AnimationLibrary.getClipForAction( AnimationLibrary.findAssetRoot(), setName, de.blight.game.animation.AnimationAction.REVIVE); @@ -568,9 +572,9 @@ public class WorldScene extends BaseAppState { drownState.setReviveInfo(reviveClip, reviveLength); } - if ("true".equals(System.getProperty("blight.new.game"))) { + if (newGame) { app.getStateManager().attach( - new de.blight.game.state.NewGameIntroState(playerInput, reviveClip, reviveLength)); + new de.blight.game.state.NewGameIntroState(playerInput, thirdPersonCam, reviveClip, reviveLength)); } } diff --git a/blight-game/src/main/java/de/blight/game/state/CompassHudState.java b/blight-game/src/main/java/de/blight/game/state/CompassHudState.java index 81a861e..ec1c0d4 100644 --- a/blight-game/src/main/java/de/blight/game/state/CompassHudState.java +++ b/blight-game/src/main/java/de/blight/game/state/CompassHudState.java @@ -95,8 +95,8 @@ public class CompassHudState extends BaseAppState { if (compassNode == null) { return; } Vector3f dir = cam.getDirection(); - // yaw=0 = Blick nach +Z (Nord); positiv = Osten - float yaw = FastMath.atan2(dir.x, dir.z); + // yaw=0 = Blick nach -Z (Nord); JME3-Kamera blickt in -Z bei Norden + float yaw = FastMath.atan2(dir.x, -dir.z); // Rose dreht sich so, dass Nord immer in der echten Nord-Richtung liegt roseRot.fromAngleAxis(-yaw, Vector3f.UNIT_Z); diff --git a/blight-game/src/main/java/de/blight/game/state/NewGameIntroState.java b/blight-game/src/main/java/de/blight/game/state/NewGameIntroState.java index 7f7e9c0..fc256b7 100644 --- a/blight-game/src/main/java/de/blight/game/state/NewGameIntroState.java +++ b/blight-game/src/main/java/de/blight/game/state/NewGameIntroState.java @@ -10,6 +10,7 @@ import com.jme3.renderer.queue.RenderQueue; import com.jme3.scene.Geometry; import com.jme3.scene.shape.Quad; import de.blight.game.control.PlayerInputControl; +import de.blight.game.control.ThirdPersonCamera; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,6 +33,7 @@ public class NewGameIntroState extends BaseAppState { private enum Phase { BLACK, FADING_IN, REVIVE_PLAYING, DONE } private final PlayerInputControl playerInput; + private final ThirdPersonCamera thirdPersonCam; private final String reviveClip; private final float reviveLength; @@ -42,10 +44,12 @@ public class NewGameIntroState extends BaseAppState { private Phase phase; private float timer; - public NewGameIntroState(PlayerInputControl playerInput, String reviveClip, float reviveLength) { - this.playerInput = playerInput; - this.reviveClip = reviveClip; - this.reviveLength = reviveLength; + public NewGameIntroState(PlayerInputControl playerInput, ThirdPersonCamera thirdPersonCam, + String reviveClip, float reviveLength) { + this.playerInput = playerInput; + this.thirdPersonCam = thirdPersonCam; + this.reviveClip = reviveClip; + this.reviveLength = reviveLength; } @Override @@ -85,11 +89,15 @@ public class NewGameIntroState extends BaseAppState { case BLACK -> { timer -= tpf; if (timer <= 0f) { - // REVIVE unmittelbar vor dem Einblenden einfrieren playerInput.startFrozenRevive(reviveClip); + // Drehung startet sofort mit Fade-In: Gesamtdauer = Einblenden + REVIVE + if (thirdPersonCam != null) { + thirdPersonCam.startIntroRotation(FADE_DURATION + reviveLength); + } phase = Phase.FADING_IN; timer = FADE_DURATION; - log.info("[NewGameIntro] BLACK fertig – REVIVE eingefroren, starte FADING_IN ({} s)", FADE_DURATION); + log.info("[NewGameIntro] BLACK fertig – REVIVE eingefroren, Kamera dreht {} s, starte FADING_IN", + FADE_DURATION + reviveLength); } } case FADING_IN -> { diff --git a/blight-map/src/main/map/blight_map.blm b/blight-map/src/main/map/blight_map.blm index 127e81d..f685d4f 100644 Binary files a/blight-map/src/main/map/blight_map.blm and b/blight-map/src/main/map/blight_map.blm differ