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 d118b8a..c620c62 100644 --- a/blight-editor/src/main/java/de/blight/editor/EditorApp.java +++ b/blight-editor/src/main/java/de/blight/editor/EditorApp.java @@ -1713,6 +1713,7 @@ public class EditorApp extends Application { input.activeLayer = SharedInput.LAYER_PLAY_TOOL; input.playToolMode = SharedInput.PlayToolMode.EDIT; root.setRight(buildPlayToolPanel()); + Platform.runLater(() -> { if (gamePlayBtn != null) gamePlayBtn.requestFocus(); }); }); worldHintLabel = new Label(camHintText()); diff --git a/blight-game/src/main/java/de/blight/game/config/KeyBindings.java b/blight-game/src/main/java/de/blight/game/config/KeyBindings.java index 83859df..2d0debe 100644 --- a/blight-game/src/main/java/de/blight/game/config/KeyBindings.java +++ b/blight-game/src/main/java/de/blight/game/config/KeyBindings.java @@ -12,7 +12,7 @@ public class KeyBindings { public int jump = KeyInput.KEY_SPACE; public int sprint = KeyInput.KEY_LSHIFT; public int walk = KeyInput.KEY_LMENU; - public int interact = KeyInput.KEY_E; + public int interact = KeyInput.KEY_F; public int turnLeft = KeyInput.KEY_Q; public int turnRight = KeyInput.KEY_E; public int inventory = KeyInput.KEY_I; diff --git a/blight-game/src/main/java/de/blight/game/control/PlayerInputControl.java b/blight-game/src/main/java/de/blight/game/control/PlayerInputControl.java index 4258d17..efcc248 100644 --- a/blight-game/src/main/java/de/blight/game/control/PlayerInputControl.java +++ b/blight-game/src/main/java/de/blight/game/control/PlayerInputControl.java @@ -64,8 +64,6 @@ public class PlayerInputControl { // Bone-Namen nach Phase-0-Bone-Log eintragen: private static final String BONE_LEFT_FOOT = "mixamorig:LeftFoot"; private static final String BONE_RIGHT_FOOT = "mixamorig:RightFoot"; - // Y-Schwelle im Model-Space: Fuß gilt als am Boden wenn Y < FOOT_GROUND_Y - private static final float FOOT_GROUND_Y = 0.05f; /** Wassertiefe (m) ab der Laufen/Sprinten gesperrt ist und Watgeräusch spielt. */ public static final float WATER_WADING_DEPTH = 0.5f; @@ -75,16 +73,23 @@ public class PlayerInputControl { private com.jme3.audio.AudioNode wadingNode; /** Aktuell berechnete Wassertiefe – einmal pro Update() gesetzt, in pollFootsteps() gelesen. */ private float currentWaterDepth = 0f; - private float lastLeftFootY = Float.MAX_VALUE; - private float lastRightFootY = Float.MAX_VALUE; + // Velocity-based footstep detection: fires at the local Y-minimum of each foot (no fixed threshold). + private float lastLeftFootY = Float.MAX_VALUE; + private float lastRightFootY = Float.MAX_VALUE; + private boolean leftFootDescending = false; + private boolean rightFootDescending = false; + private float stepCooldown = 0f; + private static final float MIN_STEP_INTERVAL = 0.15f; // ── Anim-Offsets ───────────────────────────────────────────────────────── private Map animOffsets = new LinkedHashMap<>(); /** Basis-Local-Translation des Visual-Nodes; wird beim Laden des AnimSet einmalig gespeichert. */ private Vector3f visualBaseTranslation = new Vector3f(); private final Vector3f animOffsetCurrent = new Vector3f(); + private final Vector3f animOffsetFrom = new Vector3f(); private final Vector3f animOffsetTarget = new Vector3f(); - private float animOffsetSpeed = 0f; + // Zeit-basierter Lerp über AnimPlayer.BLEND_DURATION – exakt wie im AnimPreviewState (Editor). + private float animOffsetElapsed = Float.MAX_VALUE; // MAX_VALUE = stabil / kein Lerp aktiv // ── Navigation ──────────────────────────────────────────────────────────── private CharacterNavigator navigator = null; @@ -422,6 +427,7 @@ public class PlayerInputControl { if (physicsChar != null) physicsChar.setWalkDirection(Vector3f.ZERO); autopilotDir = null; clearAnimOffset(); + if (animPlayer != null) animPlayer.stop(); if (arriveRadius > 0f) { navigator.navigateTo(target, speed, arriveRadius, onArrival, onFailed); } else { @@ -466,16 +472,11 @@ public class PlayerInputControl { runningClip = animPlayer.getCurrentClip(); } - if (visual != null && animOffsetSpeed > 0f) { - Vector3f delta = animOffsetTarget.subtract(animOffsetCurrent); - float dist = delta.length(); - float step = animOffsetSpeed * tpf; - if (dist <= step) { - animOffsetCurrent.set(animOffsetTarget); - animOffsetSpeed = 0f; - } else { - animOffsetCurrent.addLocal(delta.normalizeLocal().multLocal(step)); - } + if (visual != null && animOffsetElapsed < AnimPlayer.BLEND_DURATION) { + animOffsetElapsed += tpf; + float t = Math.min(1f, animOffsetElapsed / AnimPlayer.BLEND_DURATION); + animOffsetCurrent.set(animOffsetFrom); + animOffsetCurrent.interpolateLocal(animOffsetTarget, t); visual.setLocalTranslation(visualBaseTranslation.clone().addLocal(animOffsetCurrent)); } @@ -626,7 +627,7 @@ public class PlayerInputControl { currentAnim = target; } - pollFootsteps(); + pollFootsteps(tpf); } private void playAction(AnimationAction action) { @@ -670,26 +671,31 @@ public class PlayerInputControl { log.info("{}", sb); } - private void pollFootsteps() { + private void resetFootstepState() { + lastLeftFootY = Float.MAX_VALUE; + lastRightFootY = Float.MAX_VALUE; + leftFootDescending = false; + rightFootDescending = false; + stepCooldown = 0f; + } + + private void pollFootsteps(float tpf) { if (!physicsChar.onGround() || blockingAnimActive || lockedInPlace) { stopWading(); - lastLeftFootY = Float.MAX_VALUE; - lastRightFootY = Float.MAX_VALUE; + resetFootstepState(); return; } float speed = physicsChar.getWalkDirection().length(); if (speed < 0.001f) { stopWading(); - lastLeftFootY = Float.MAX_VALUE; - lastRightFootY = Float.MAX_VALUE; + resetFootstepState(); return; } if (currentWaterDepth > WATER_WADING_DEPTH) { // Im Wasser: Watgeräusch abspielen, Fußgeräusche unterdrücken startWading(); - lastLeftFootY = Float.MAX_VALUE; - lastRightFootY = Float.MAX_VALUE; + resetFootstepState(); return; } @@ -699,20 +705,29 @@ public class PlayerInputControl { String gait = currentAnimToGait(currentAnim); if (gait == null) return; + stepCooldown = Math.max(0f, stepCooldown - tpf); + com.jme3.anim.Joint lf = armature.getJoint(BONE_LEFT_FOOT); com.jme3.anim.Joint rf = armature.getJoint(BONE_RIGHT_FOOT); if (lf != null) { float y = lf.getModelTransform().getTranslation().y; - if (y < FOOT_GROUND_Y && lastLeftFootY >= FOOT_GROUND_Y) { + // Descend→ascend transition = local Y-minimum = foot touches ground + boolean desc = (lastLeftFootY - y) > 0.001f; + if (!desc && leftFootDescending && stepCooldown <= 0f) { triggerStep(gait); + stepCooldown = MIN_STEP_INTERVAL; } + leftFootDescending = desc; lastLeftFootY = y; } if (rf != null) { float y = rf.getModelTransform().getTranslation().y; - if (y < FOOT_GROUND_Y && lastRightFootY >= FOOT_GROUND_Y) { + boolean desc = (lastRightFootY - y) > 0.001f; + if (!desc && rightFootDescending && stepCooldown <= 0f) { triggerStep(gait); + stepCooldown = MIN_STEP_INTERVAL; } + rightFootDescending = desc; lastRightFootY = y; } } @@ -770,9 +785,10 @@ public class PlayerInputControl { if (off == null) { clearAnimOffset(); return; } Quaternion facing = visual.getLocalRotation().clone(); Vector3f worldOffset = facing.mult(new Vector3f(off.tx, off.ty, off.tz)); - animOffsetTarget.set(worldOffset); if (animOffsetCurrent.distanceSquared(worldOffset) > 1e-4f) { - animOffsetSpeed = 5.0f; + animOffsetFrom.set(animOffsetCurrent); + animOffsetTarget.set(worldOffset); + animOffsetElapsed = 0f; } log.debug("[AnimOffset] Clip '{}' → Ziel ({},{},{})", clip, worldOffset.x, worldOffset.y, worldOffset.z); } @@ -790,9 +806,10 @@ public class PlayerInputControl { if (off == null) { clearAnimOffsetInstant(); return; } Quaternion facing = visual.getLocalRotation().clone(); Vector3f worldOffset = facing.mult(new Vector3f(off.tx, off.ty, off.tz)); + animOffsetFrom.set(worldOffset); animOffsetTarget.set(worldOffset); animOffsetCurrent.set(worldOffset); - animOffsetSpeed = 0f; + animOffsetElapsed = Float.MAX_VALUE; visual.setLocalTranslation(visualBaseTranslation.clone().addLocal(animOffsetCurrent)); log.info("[AnimOffset] Sofort: action={} clip='{}' → ({},{},{})", action, clip, worldOffset.x, worldOffset.y, worldOffset.z); @@ -800,8 +817,11 @@ public class PlayerInputControl { public void clearAnimOffset() { log.info("[AnimOffset] clearAnimOffset() → Lerp zu 0, current=({},{},{})", animOffsetCurrent.x, animOffsetCurrent.y, animOffsetCurrent.z); - animOffsetTarget.set(0, 0, 0); - animOffsetSpeed = 5.0f; + if (animOffsetCurrent.lengthSquared() > 1e-4f) { + animOffsetFrom.set(animOffsetCurrent); + animOffsetTarget.set(0, 0, 0); + animOffsetElapsed = 0f; + } } /** @@ -812,18 +832,20 @@ public class PlayerInputControl { if (visual == null) return; Quaternion facing = visual.getLocalRotation().clone(); Vector3f worldOffset = facing.mult(new Vector3f(tx, ty, tz)); + animOffsetFrom.set(worldOffset); animOffsetTarget.set(worldOffset); animOffsetCurrent.set(worldOffset); - animOffsetSpeed = 0f; + animOffsetElapsed = Float.MAX_VALUE; visual.setLocalTranslation(visualBaseTranslation.clone().addLocal(animOffsetCurrent)); log.info("[AnimOffset] Instant: ({},{},{}) → world ({},{},{})", tx, ty, tz, worldOffset.x, worldOffset.y, worldOffset.z); } /** Setzt den Visual-Versatz sofort auf 0 zurück (kein Lerp). */ public void clearAnimOffsetInstant() { + animOffsetFrom.set(0, 0, 0); animOffsetTarget.set(0, 0, 0); animOffsetCurrent.set(0, 0, 0); - animOffsetSpeed = 0f; + animOffsetElapsed = Float.MAX_VALUE; if (visual != null) { visual.setLocalTranslation(visualBaseTranslation.clone()); } diff --git a/blight-game/src/main/java/de/blight/game/state/WorldInteractableState.java b/blight-game/src/main/java/de/blight/game/state/WorldInteractableState.java index db6b951..b0aa4d4 100644 --- a/blight-game/src/main/java/de/blight/game/state/WorldInteractableState.java +++ b/blight-game/src/main/java/de/blight/game/state/WorldInteractableState.java @@ -148,7 +148,9 @@ public class WorldInteractableState extends BaseAppState { @Override protected void onEnable() { - inputManager.addMapping(INTERACT_ACTION, new KeyTrigger(keyBindings.interact)); + inputManager.addMapping(INTERACT_ACTION, + new MouseButtonTrigger(MouseInput.BUTTON_LEFT), + new KeyTrigger(keyBindings.interact)); inputManager.addMapping(GET_UP_ACTION, new MouseButtonTrigger(MouseInput.BUTTON_RIGHT)); inputManager.addListener(interactListener, INTERACT_ACTION); inputManager.addListener(getUpListener, GET_UP_ACTION); diff --git a/blight-map/src/main/map/benches/9ac9943d-0e12-4d5c-8323-0e2b92eebdec.bench b/blight-map/src/main/map/benches/9ac9943d-0e12-4d5c-8323-0e2b92eebdec.bench new file mode 100644 index 0000000..4a304e4 --- /dev/null +++ b/blight-map/src/main/map/benches/9ac9943d-0e12-4d5c-8323-0e2b92eebdec.bench @@ -0,0 +1,9 @@ +{ + "id": "9ac9943d-0e12-4d5c-8323-0e2b92eebdec", + "benchType": "Simple", + "sitzX": 236.63928, + "sitzY": 6.9953, + "sitzZ": -888.1745, + "sitzRotY": 4.7967663, + "sitzSet": true +} \ No newline at end of file