Laufen und Rollen verbessert
This commit is contained in:
@@ -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"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
Reference in New Issue
Block a user