Bugs bezüglich Footssteps und Animationen behoben

This commit is contained in:
2026-08-21 08:07:10 +02:00
parent 024a7ffaff
commit 5d8e4db468
5 changed files with 68 additions and 34 deletions

View File

@@ -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;

View File

@@ -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<String, AnimOffset> 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());
}

View File

@@ -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);