Laufen und Rollen verbessert

This commit is contained in:
2026-09-24 16:01:47 +02:00
parent 470c743848
commit f457b897bd
3 changed files with 98 additions and 65 deletions

View File

@@ -115,6 +115,7 @@
"Sword And Shield Block Walk Backwards", "Sword And Shield Block Walk Backwards",
"Sword And Shield Strafe Left", "Sword And Shield Strafe Left",
"Sword And Shield Strafe Right", "Sword And Shield Strafe Right",
"Sword And Shield Heavy Attack" "Sword And Shield Heavy Attack",
"Roll"
] ]
} }

View File

@@ -22,6 +22,7 @@ public class KeyBindings {
public int drawMeleeWeapon = KeyInput.KEY_1; public int drawMeleeWeapon = KeyInput.KEY_1;
public int drawRangedWeapon = KeyInput.KEY_2; public int drawRangedWeapon = KeyInput.KEY_2;
public int sheathWeapon = KeyInput.KEY_X; public int sheathWeapon = KeyInput.KEY_X;
public int roll = KeyInput.KEY_LCONTROL;
/** Metadaten für die Config-UI: Feldname im Objekt + Anzeigename. */ /** Metadaten für die Config-UI: Feldname im Objekt + Anzeigename. */
public static final String[][] ENTRIES = { public static final String[][] ENTRIES = {
@@ -42,6 +43,7 @@ public class KeyBindings {
{"drawMeleeWeapon", "Nahkampfwaffe ziehen"}, {"drawMeleeWeapon", "Nahkampfwaffe ziehen"},
{"drawRangedWeapon", "Fernkampfwaffe ziehen"}, {"drawRangedWeapon", "Fernkampfwaffe ziehen"},
{"sheathWeapon", "Waffe wegstecken"}, {"sheathWeapon", "Waffe wegstecken"},
{"roll", "Rolle"},
}; };
public int get(String fieldName) { public int get(String fieldName) {

View File

@@ -7,9 +7,16 @@ import com.jme3.anim.tween.action.BaseAction;
import com.jme3.bullet.control.CharacterControl; import com.jme3.bullet.control.CharacterControl;
import com.jme3.input.InputManager; import com.jme3.input.InputManager;
import com.jme3.input.MouseInput; import com.jme3.input.MouseInput;
import com.jme3.input.RawInputListener;
import com.jme3.input.controls.ActionListener; import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger; import com.jme3.input.controls.KeyTrigger;
import com.jme3.input.controls.MouseButtonTrigger; 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 de.blight.common.model.ItemSubCategory; import de.blight.common.model.ItemSubCategory;
import de.blight.common.model.MainCharacter; import de.blight.common.model.MainCharacter;
import com.jme3.math.Quaternion; import com.jme3.math.Quaternion;
@@ -64,8 +71,8 @@ public class PlayerInputControl {
private static final int STAMINA_ATTACK5_COST = 20; private static final int STAMINA_ATTACK5_COST = 20;
private static final int STAMINA_HEAVY_COST = 30; private static final int STAMINA_HEAVY_COST = 30;
private static final int STAMINA_BLOCK_HIT_COST = 30; private static final int STAMINA_BLOCK_HIT_COST = 30;
private static final int STAMINA_ROLL_COST = 20; private static final int STAMINA_ROLL_COST = 20;
private static final float ROLL_DOUBLE_TAP_WINDOW = 0.3f; private static final float ROLL_AIRBORNE_THRESHOLD = 0.90f; // Hips-Y unter 90% Baseline → Luftphase
/** Dauer der Ziehen/Wegstecken-Animation in Sekunden. */ /** Dauer der Ziehen/Wegstecken-Animation in Sekunden. */
private static final float DRAW_DURATION = 0.85f; private static final float DRAW_DURATION = 0.85f;
// Ziel-Quaternions für die Ziehen-Animation: aus Heavy-Attack-Wind-Up bei t=0.933s gelesen // Ziel-Quaternions für die Ziehen-Animation: aus Heavy-Attack-Wind-Up bei t=0.933s gelesen
@@ -95,10 +102,12 @@ public class PlayerInputControl {
private static final String ACT_SHEATH = "SheathWeapon"; private static final String ACT_SHEATH = "SheathWeapon";
private static final String ACT_COMBAT_ATK = "CombatAttack"; private static final String ACT_COMBAT_ATK = "CombatAttack";
private static final String ACT_COMBAT_ALT = "CombatAltAction"; private static final String ACT_COMBAT_ALT = "CombatAltAction";
private static final String ACT_ROLL = "Roll";
private static final String[] ACTION_NAMES = { private static final String[] ACTION_NAMES = {
"Forward", "Backward", "Left", "Right", "Jump", "Sprint", "Walk", "Forward", "Backward", "Left", "Right", "Jump", "Sprint", "Walk",
ACT_DRAW_MELEE, ACT_DRAW_RANGED, ACT_SHEATH, ACT_COMBAT_ATK, ACT_COMBAT_ALT ACT_DRAW_MELEE, ACT_DRAW_RANGED, ACT_SHEATH, ACT_COMBAT_ATK, ACT_COMBAT_ALT,
ACT_ROLL
}; };
private final InputManager inputManager; private final InputManager inputManager;
@@ -107,6 +116,8 @@ public class PlayerInputControl {
private CharacterControl physicsChar; private CharacterControl physicsChar;
private Spatial visual; private Spatial visual;
private int sprintKey = -1;
private RawInputListener sprintRawListener = null;
private boolean forward, backward, left, right, sprint, walk; private boolean forward, backward, left, right, sprint, walk;
private boolean paused = false; private boolean paused = false;
@@ -263,15 +274,13 @@ public class PlayerInputControl {
private Vector3f autopilotDir = null; private Vector3f autopilotDir = null;
// ── Dodge Roll ──────────────────────────────────────────────────────────── // ── Dodge Roll ────────────────────────────────────────────────────────────
// Double-tap-Timer pro Richtung: -1 = kein erster Tap ausstehend private boolean rollActive = false;
private float rollTapFwd = -1f; private float rollRemaining = 0f;
private float rollTapBwd = -1f; private float rollInitRemaining = 0f;
private float rollTapLeft = -1f; private boolean rollBaselineCaptured = false;
private float rollTapRight = -1f; private float rollHipsBaseY = 0f;
private int pendingRollDir = 0; // 1=vor, 2=zurück, 3=links, 4=rechts private boolean rollAirborne = false;
private boolean rollActive = false; private Vector3f rollDir = null;
private float rollRemaining = 0f;
private Vector3f rollDir = null;
// Pre-allokierte Scratch-Objekte — keine Heap-Allokation pro Frame in update() // Pre-allokierte Scratch-Objekte — keine Heap-Allokation pro Frame in update()
private final Vector3f scratchCamDir = new Vector3f(); private final Vector3f scratchCamDir = new Vector3f();
@@ -285,11 +294,11 @@ public class PlayerInputControl {
private final ActionListener actionListener = (name, isPressed, tpf) -> { private final ActionListener actionListener = (name, isPressed, tpf) -> {
if (paused) return; if (paused) return;
switch (name) { switch (name) {
case "Forward" -> { if (isPressed) detectRollTap(1); forward = isPressed; } case "Forward" -> forward = isPressed;
case "Backward" -> { if (isPressed) detectRollTap(2); backward = isPressed; } case "Backward" -> backward = isPressed;
case "Left" -> { if (isPressed) detectRollTap(3); left = isPressed; } case "Left" -> left = isPressed;
case "Right" -> { if (isPressed) detectRollTap(4); right = isPressed; } case "Right" -> right = isPressed;
case "Sprint" -> sprint = isPressed; case "Sprint" -> sprint = isPressed;
case "Walk" -> walk = isPressed; case "Walk" -> walk = isPressed;
case "Jump" -> { if (isPressed && physicsChar != null) { physicsChar.jump(); jumpFrames = 12; } } case "Jump" -> { if (isPressed && physicsChar != null) { physicsChar.jump(); jumpFrames = 12; } }
case ACT_DRAW_MELEE -> { if (isPressed) handleDrawMelee(); } case ACT_DRAW_MELEE -> { if (isPressed) handleDrawMelee(); }
@@ -324,6 +333,7 @@ public class PlayerInputControl {
} }
} }
case ACT_COMBAT_ALT -> handleCombatAlt(isPressed); case ACT_COMBAT_ALT -> handleCombatAlt(isPressed);
case ACT_ROLL -> { if (isPressed) triggerRollFromInput(); }
} }
}; };
@@ -680,12 +690,28 @@ public class PlayerInputControl {
inputManager.addMapping("Right", new KeyTrigger(kb.right)); inputManager.addMapping("Right", new KeyTrigger(kb.right));
inputManager.addMapping("Jump", new KeyTrigger(kb.jump)); inputManager.addMapping("Jump", new KeyTrigger(kb.jump));
inputManager.addMapping("Sprint", new KeyTrigger(kb.sprint)); inputManager.addMapping("Sprint", new KeyTrigger(kb.sprint));
if (sprintRawListener != null) inputManager.removeRawInputListener(sprintRawListener);
sprintKey = kb.sprint;
sprintRawListener = new RawInputListener() {
public void onKeyEvent(KeyInputEvent e) {
if (e.getKeyCode() == sprintKey) sprint = !e.isReleased();
}
public void beginInput() {}
public void endInput() {}
public void onJoyAxisEvent(JoyAxisEvent e) {}
public void onJoyButtonEvent(JoyButtonEvent e) {}
public void onMouseMotionEvent(MouseMotionEvent e) {}
public void onMouseButtonEvent(MouseButtonEvent e) {}
public void onTouchEvent(TouchEvent e) {}
};
inputManager.addRawInputListener(sprintRawListener);
inputManager.addMapping("Walk", new KeyTrigger(kb.walk)); inputManager.addMapping("Walk", new KeyTrigger(kb.walk));
inputManager.addMapping(ACT_DRAW_MELEE, new KeyTrigger(kb.drawMeleeWeapon)); inputManager.addMapping(ACT_DRAW_MELEE, new KeyTrigger(kb.drawMeleeWeapon));
inputManager.addMapping(ACT_DRAW_RANGED, new KeyTrigger(kb.drawRangedWeapon)); inputManager.addMapping(ACT_DRAW_RANGED, new KeyTrigger(kb.drawRangedWeapon));
inputManager.addMapping(ACT_SHEATH, new KeyTrigger(kb.sheathWeapon)); inputManager.addMapping(ACT_SHEATH, new KeyTrigger(kb.sheathWeapon));
inputManager.addMapping(ACT_COMBAT_ATK, new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); inputManager.addMapping(ACT_COMBAT_ATK, new MouseButtonTrigger(MouseInput.BUTTON_LEFT));
inputManager.addMapping(ACT_COMBAT_ALT, new MouseButtonTrigger(MouseInput.BUTTON_RIGHT)); inputManager.addMapping(ACT_COMBAT_ALT, new MouseButtonTrigger(MouseInput.BUTTON_RIGHT));
inputManager.addMapping(ACT_ROLL, new KeyTrigger(kb.roll));
inputManager.addListener(actionListener, ACTION_NAMES); inputManager.addListener(actionListener, ACTION_NAMES);
} }
@@ -716,29 +742,6 @@ public class PlayerInputControl {
return; return;
} }
// Double-tap-Timer ablaufen lassen
if (rollTapFwd > 0f) { rollTapFwd -= tpf; if (rollTapFwd < 0f) rollTapFwd = -1f; }
if (rollTapBwd > 0f) { rollTapBwd -= tpf; if (rollTapBwd < 0f) rollTapBwd = -1f; }
if (rollTapLeft > 0f) { rollTapLeft -= tpf; if (rollTapLeft < 0f) rollTapLeft = -1f; }
if (rollTapRight > 0f) { rollTapRight -= tpf; if (rollTapRight < 0f) rollTapRight = -1f; }
// Doppeltippen → Rolle einleiten (nicht während Kampf, Block, Pickup usw.)
if (pendingRollDir != 0) {
if (!rollActive && !pickupActive && !blockingAnimActive && !turnActive
&& !lockedInPlace && !combatAnimActive && !isBlocking()) {
cam.getDirection(scratchCamDir).setY(0).normalizeLocal();
cam.getLeft(scratchCamLeft).setY(0).normalizeLocal();
Vector3f dir = switch (pendingRollDir) {
case 1 -> scratchCamDir.clone();
case 2 -> scratchCamDir.clone().negateLocal();
case 3 -> scratchCamLeft.clone();
default -> scratchCamLeft.clone().negateLocal();
};
startRoll(dir);
}
pendingRollDir = 0;
}
// Navigator: hat Vorrang vor allem außer Pause // Navigator: hat Vorrang vor allem außer Pause
if (navigator != null && navigator.isActive()) { if (navigator != null && navigator.isActive()) {
navigator.update(tpf); navigator.update(tpf);
@@ -763,11 +766,42 @@ public class PlayerInputControl {
rollActive = false; rollActive = false;
rollDir = null; rollDir = null;
currentAnim = null; currentAnim = null;
if (physicsChar != null) physicsChar.setWalkDirection(Vector3f.ZERO);
} else { } else {
if (physicsChar != null) { boolean rollEndedEarly = false;
physicsChar.setWalkDirection(Vector3f.ZERO); if (rollInitRemaining > 0f) {
rollInitRemaining -= tpf;
if (physicsChar != null) physicsChar.setWalkDirection(Vector3f.ZERO);
} else if (!rollBaselineCaptured && hipsJoint != null) {
rollBaselineCaptured = true;
rollHipsBaseY = hipsJoint.getModelTransform().getTranslation().y;
rollAirborne = false;
log.info("[ROLL] Baseline hipsY={}", rollHipsBaseY);
if (physicsChar != null) physicsChar.setWalkDirection(Vector3f.ZERO);
} else if (rollBaselineCaptured && hipsJoint != null && physicsChar != null) {
float hipsY = hipsJoint.getModelTransform().getTranslation().y;
boolean wasAirborne = rollAirborne;
rollAirborne = hipsY < rollHipsBaseY * ROLL_AIRBORNE_THRESHOLD;
if (rollAirborne != wasAirborne) {
log.info("[ROLL] hipsY={} baseY={} → {}", hipsY, rollHipsBaseY,
rollAirborne ? "AIRBORNE" : "GROUNDED");
}
// Gelandet + Bewegungstaste → Rolle beenden, direkt in Lauf-Logik fallen
if (wasAirborne && !rollAirborne && (forward || backward || left || right)) {
rollActive = false;
rollDir = null;
currentAnim = null;
rollEndedEarly = true;
physicsChar.setWalkDirection(Vector3f.ZERO);
} else if (rollAirborne && rollDir != null) {
physicsChar.setWalkDirection(scratchWalk.set(rollDir).multLocal(MOVE_SPEED));
} else {
physicsChar.setWalkDirection(Vector3f.ZERO);
}
} else {
if (physicsChar != null) physicsChar.setWalkDirection(Vector3f.ZERO);
} }
return; if (!rollEndedEarly) return;
} }
} }
@@ -1331,32 +1365,28 @@ public class PlayerInputControl {
return true; return true;
} }
private void detectRollTap(int dir) { private void triggerRollFromInput() {
float cur = switch (dir) { if (rollActive || pickupActive || blockingAnimActive || turnActive
case 1 -> rollTapFwd; || lockedInPlace || combatAnimActive || isBlocking()) return;
case 2 -> rollTapBwd; cam.getDirection(scratchCamDir).setY(0).normalizeLocal();
case 3 -> rollTapLeft; cam.getLeft(scratchCamLeft).setY(0).normalizeLocal();
default -> rollTapRight; Vector3f rollDirVec;
}; if (backward && !forward) rollDirVec = scratchCamDir.clone().negateLocal();
if (cur > 0f) { else if (left && !right) rollDirVec = scratchCamLeft.clone();
pendingRollDir = dir; else if (right && !left) rollDirVec = scratchCamLeft.clone().negateLocal();
} else { else rollDirVec = scratchCamDir.clone();
switch (dir) { startRoll(rollDirVec);
case 1 -> rollTapFwd = ROLL_DOUBLE_TAP_WINDOW;
case 2 -> rollTapBwd = ROLL_DOUBLE_TAP_WINDOW;
case 3 -> rollTapLeft = ROLL_DOUBLE_TAP_WINDOW;
default -> rollTapRight = ROLL_DOUBLE_TAP_WINDOW;
}
}
} }
private void startRoll(Vector3f dir) { private void startRoll(Vector3f dir) {
if (!spendStamina(STAMINA_ROLL_COST)) return; if (!spendStamina(STAMINA_ROLL_COST)) return;
nextTransitionLength = 0.1f; nextTransitionLength = 0.1f;
rollActive = true; rollActive = true;
rollRemaining = resolveClipLength(AnimationAction.ROLL, 0.7f); rollRemaining = resolveClipLength(AnimationAction.ROLL, 0.7f);
rollDir = dir; rollInitRemaining = 0.12f;
forward = backward = left = right = false; rollBaselineCaptured = false;
rollAirborne = false;
rollDir = dir;
if (physicsChar != null) physicsChar.setWalkDirection(Vector3f.ZERO); if (physicsChar != null) physicsChar.setWalkDirection(Vector3f.ZERO);
if (visual != null) { if (visual != null) {
scratchRot.lookAt(dir, Vector3f.UNIT_Y); scratchRot.lookAt(dir, Vector3f.UNIT_Y);