Animationen ingame und im editor angepasst
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
package de.blight.game.animation;
|
||||
|
||||
import com.jme3.anim.AnimClip;
|
||||
import com.jme3.anim.AnimComposer;
|
||||
import com.jme3.anim.SkinningControl;
|
||||
import com.jme3.anim.TransformTrack;
|
||||
import com.jme3.anim.tween.action.Action;
|
||||
import com.jme3.anim.tween.action.BlendAction;
|
||||
import com.jme3.anim.tween.action.BlendableAction;
|
||||
import com.jme3.anim.tween.action.LinearBlendSpace;
|
||||
import com.jme3.math.Quaternion;
|
||||
import com.jme3.math.Transform;
|
||||
import com.jme3.math.Vector3f;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Kapselt das Abspielen von AnimClips: BlendAction+FreezeClip für nahtlose
|
||||
* Übergänge. Gemeinsame Codebasis für Editor (AnimPreviewState) und Spiel
|
||||
* (PlayerInputControl).
|
||||
*
|
||||
* snapRootBoneXZ für Locomotion-Clips bleibt Aufgabe des Aufrufers (AnimationLibrary).
|
||||
*/
|
||||
public class AnimPlayer {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(AnimPlayer.class);
|
||||
|
||||
/** Standard-Übergangsdauer in Sekunden. */
|
||||
public static final float BLEND_DURATION = 0.15f;
|
||||
|
||||
private final AnimComposer composer;
|
||||
private final SkinningControl skinning;
|
||||
|
||||
private String currentClip;
|
||||
private Action currentAction;
|
||||
|
||||
private LinearBlendSpace blendSpace;
|
||||
private String pendingClip;
|
||||
private float blendElapsed;
|
||||
|
||||
public AnimPlayer(AnimComposer composer, SkinningControl skinning) {
|
||||
this.composer = composer;
|
||||
this.skinning = skinning;
|
||||
}
|
||||
|
||||
/**
|
||||
* Spielt einen Clip ab.
|
||||
* Bei laufendem Clip und {@code transition > 0}: nahtloser Übergang
|
||||
* via BlendAction+FreezeClip (kein interner JME3-Cross-Fade).
|
||||
*
|
||||
* @param clipName Ziel-Clip (muss im AnimComposer bekannt sein)
|
||||
* @param transition Übergangsdauer in Sekunden; 0 = harter Schnitt
|
||||
* @return true wenn Playback erfolgreich gestartet
|
||||
*/
|
||||
public boolean play(String clipName, float transition) {
|
||||
if (composer == null) return false;
|
||||
if (!composer.getAnimClipsNames().contains(clipName)) {
|
||||
log.warn("[AnimPlayer] Clip '{}' nicht im AnimComposer", clipName);
|
||||
return false;
|
||||
}
|
||||
if (skinning != null && !skinning.isEnabled()) skinning.setEnabled(true);
|
||||
|
||||
if ((currentClip != null || pendingClip != null) && transition > 0f) {
|
||||
com.jme3.anim.Armature arm = skinning != null ? skinning.getArmature() : null;
|
||||
AnimClip freeze = buildFreezeClip(arm);
|
||||
if (freeze != null) {
|
||||
try {
|
||||
if (composer.hasAnimClip("__freeze__"))
|
||||
composer.removeAnimClip(composer.getAnimClip("__freeze__"));
|
||||
composer.addAnimClip(freeze);
|
||||
if (composer.hasAction("__blend__"))
|
||||
composer.removeAction("__blend__");
|
||||
LinearBlendSpace space = new LinearBlendSpace(0f, 1f);
|
||||
space.setValue(0f);
|
||||
BlendAction blend = composer.actionBlended("__blend__", space, "__freeze__", clipName);
|
||||
blend.clearSpeedFactors();
|
||||
// setTransitionLength(0) verhindert internen JME3-Cross-Fade,
|
||||
// der sonst parallel zu unserem blendSpace.setValue läuft.
|
||||
blend.setTransitionLength(0.0);
|
||||
composer.getLayer(AnimComposer.DEFAULT_LAYER).setCurrentAction(blend);
|
||||
blendSpace = space;
|
||||
pendingClip = clipName;
|
||||
blendElapsed = 0f;
|
||||
currentAction = blend;
|
||||
currentClip = null;
|
||||
log.debug("[AnimPlayer] Blend → '{}'", clipName);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
log.warn("[AnimPlayer] BlendAction fehlgeschlagen, direktes Wechseln: {}", e.getMessage());
|
||||
blendSpace = null;
|
||||
pendingClip = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return directPlay(clipName);
|
||||
}
|
||||
|
||||
/** Muss jeden Frame aufgerufen werden (Blend-Fortschritt). */
|
||||
public void update(float tpf) {
|
||||
if (pendingClip == null || blendSpace == null) return;
|
||||
blendElapsed += tpf;
|
||||
float bt = Math.min(1f, blendElapsed / BLEND_DURATION);
|
||||
blendSpace.setValue(bt);
|
||||
if (bt >= 1f) finishBlend();
|
||||
}
|
||||
|
||||
/** Setzt Playback-Zustand zurück (kein AnimComposer-Stop). */
|
||||
public void stop() {
|
||||
pendingClip = null;
|
||||
blendSpace = null;
|
||||
currentClip = null;
|
||||
currentAction = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Name des aktiven Clips: Target-Clip während Blend, stabiler Clip danach.
|
||||
* Gibt null zurück wenn nichts spielt.
|
||||
*/
|
||||
public String getCurrentClip() {
|
||||
return pendingClip != null ? pendingClip : currentClip;
|
||||
}
|
||||
|
||||
/** Aktuelle JME3-Action (BlendAction während Blend, ClipAction danach). */
|
||||
public Action getCurrentAction() { return currentAction; }
|
||||
|
||||
// ── intern ───────────────────────────────────────────────────────────────
|
||||
|
||||
private boolean directPlay(String clipName) {
|
||||
try {
|
||||
Action act = composer.action(clipName);
|
||||
if (act instanceof BlendableAction ba) ba.setTransitionLength(0.0);
|
||||
currentAction = composer.setCurrentAction(clipName);
|
||||
currentClip = clipName;
|
||||
pendingClip = null;
|
||||
blendSpace = null;
|
||||
log.debug("[AnimPlayer] direktPlay → '{}'", clipName);
|
||||
return currentAction != null;
|
||||
} catch (Exception e) {
|
||||
log.warn("[AnimPlayer] direktPlay('{}') fehlgeschlagen: {}", clipName, e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void finishBlend() {
|
||||
String clip = pendingClip;
|
||||
float elapsed = blendElapsed;
|
||||
pendingClip = null;
|
||||
blendSpace = null;
|
||||
if (clip == null) return;
|
||||
try {
|
||||
if (composer.hasAction("__blend__")) composer.removeAction("__blend__");
|
||||
} catch (Exception ignored) {}
|
||||
try {
|
||||
Action next = composer.action(clip);
|
||||
if (next instanceof BlendableAction ba) ba.setTransitionLength(0.0);
|
||||
currentAction = composer.setCurrentAction(clip);
|
||||
currentClip = clip;
|
||||
double len = currentAction != null ? currentAction.getLength() : 0.0;
|
||||
// AnimLayer.setTime: kein extra controlUpdate, kein loop=false-Seiteneffekt.
|
||||
// Clip startet dort, wo der Übergang endete (kein sichtbarer Sprung).
|
||||
if (len > 0.01 && elapsed > 0) {
|
||||
composer.getLayer(AnimComposer.DEFAULT_LAYER).setTime(elapsed % len);
|
||||
}
|
||||
log.debug("[AnimPlayer] finishBlend → '{}'", clip);
|
||||
} catch (Exception e) {
|
||||
log.warn("[AnimPlayer] finishBlend('{}') fehlgeschlagen: {}", clip, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
private static AnimClip buildFreezeClip(com.jme3.anim.Armature arm) {
|
||||
if (arm == null || arm.getJointCount() == 0) return null;
|
||||
int n = arm.getJointCount();
|
||||
AnimClip clip = new AnimClip("__freeze__");
|
||||
com.jme3.anim.AnimTrack[] tracks = new com.jme3.anim.AnimTrack[n];
|
||||
for (int i = 0; i < n; i++) {
|
||||
com.jme3.anim.Joint j = arm.getJoint(i);
|
||||
Transform lt = j.getLocalTransform();
|
||||
tracks[i] = new TransformTrack(
|
||||
j, new float[]{0f},
|
||||
new Vector3f[]{lt.getTranslation().clone()},
|
||||
new Quaternion[]{lt.getRotation().clone()},
|
||||
new Vector3f[]{lt.getScale().clone()});
|
||||
}
|
||||
clip.setTracks(tracks);
|
||||
return clip;
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,7 @@ import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
|
||||
|
||||
/**
|
||||
* Lädt alle Clip-Dateien aus {@code animations/clips/} beim Start.
|
||||
* Clip-Schlüssel entsprechen dem Dateinamen ohne Extension (= Clip-Name).
|
||||
@@ -39,6 +40,13 @@ public class AnimationLibrary extends BaseAppState {
|
||||
private final Map<String, AnimClip> clips = new LinkedHashMap<>();
|
||||
/** clip name → Armatur der Quell-Datei */
|
||||
private final Map<String, Armature> armatures = new LinkedHashMap<>();
|
||||
/** Clips die snapRootBoneXZ erhalten (Locomotion: Walk, Run, Sprint, …). */
|
||||
private final Set<String> snapClipNames = new LinkedHashSet<>();
|
||||
|
||||
/** Standard-Locomotion-Aktionen, deren gemappte Clips snapRootBoneXZ erhalten. */
|
||||
private static final Set<String> LOCOMOTION_ACTIONS = Set.of(
|
||||
"WALK", "RUN", "SPRINT", "JUMP", "RUNNING_JUMP", "IDLE", "DEFAULT"
|
||||
);
|
||||
|
||||
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -118,6 +126,9 @@ public class AnimationLibrary extends BaseAppState {
|
||||
log.warn("[AnimLib] applyTo: Retargeting für '{}' schlug fehl", clipName);
|
||||
return false;
|
||||
}
|
||||
if (snapClipNames.contains(clipName)) {
|
||||
target = snapRootBoneXZ(target, sc.getArmature());
|
||||
}
|
||||
|
||||
ac.addAnimClip(target);
|
||||
log.info("[AnimLib] Clip '{}' zu AnimComposer von '{}' hinzugefügt", clipName, model.getName());
|
||||
@@ -201,6 +212,24 @@ public class AnimationLibrary extends BaseAppState {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bestimmt anhand des AnimSets welche Clips snapRootBoneXZ erhalten sollen
|
||||
* (Locomotion-Aktionen: Walk, Run, Sprint, Jump, Idle, Default).
|
||||
* Muss vor dem ersten {@link #applyTo}-Aufruf für das Player-Modell gesetzt werden.
|
||||
*/
|
||||
public void configureLocomotionSnap(Path assetRoot, String animSetName) {
|
||||
snapClipNames.clear();
|
||||
try {
|
||||
AnimSet set = AnimSet.load(assetRoot.resolve("Characters").resolve("sets"), animSetName);
|
||||
set.getActionMap().forEach((action, clip) -> {
|
||||
if (LOCOMOTION_ACTIONS.contains(action)) snapClipNames.add(clip);
|
||||
});
|
||||
log.info("[AnimLib] snapRootBoneXZ für Locomotion-Clips: {}", snapClipNames);
|
||||
} catch (Exception e) {
|
||||
log.warn("[AnimLib] AnimSet '{}' nicht ladbar für Locomotion-Snap: {}", animSetName, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// ── Loading ───────────────────────────────────────────────────────────────
|
||||
|
||||
private void loadAll() {
|
||||
@@ -307,9 +336,6 @@ public class AnimationLibrary extends BaseAppState {
|
||||
|
||||
for (String name : ac.getAnimClipsNames()) {
|
||||
com.jme3.anim.AnimClip animClip = ac.getAnimClip(name);
|
||||
if (armature != null) {
|
||||
animClip = snapRootBoneXZ(animClip, armature);
|
||||
}
|
||||
clips.put(name, animClip);
|
||||
if (armature != null) armatures.put(name, armature);
|
||||
log.info("[AnimLib] Clip geladen: '{}' aus {}", name, assetKey);
|
||||
|
||||
@@ -10,6 +10,7 @@ import com.jme3.math.Vector3f;
|
||||
import com.jme3.renderer.Camera;
|
||||
import com.jme3.scene.Spatial;
|
||||
import de.blight.game.animation.AnimOffset;
|
||||
import de.blight.game.animation.AnimPlayer;
|
||||
import de.blight.game.animation.AnimSet;
|
||||
import de.blight.game.animation.AnimationAction;
|
||||
import de.blight.game.animation.AnimationLibrary;
|
||||
@@ -53,6 +54,7 @@ public class PlayerInputControl {
|
||||
private boolean animCtxLogged = false;
|
||||
|
||||
private AnimComposer animComposer;
|
||||
private AnimPlayer animPlayer;
|
||||
private String runningClip;
|
||||
|
||||
private com.jme3.anim.SkinningControl skinningControl = null;
|
||||
@@ -91,6 +93,7 @@ public class PlayerInputControl {
|
||||
private int jumpFrames = 0;
|
||||
private int groundGraceFrames = 0;
|
||||
private double nextTransitionLength = 0.0;
|
||||
|
||||
private boolean pickupActive = false;
|
||||
private float pickupRemaining = 0f;
|
||||
|
||||
@@ -156,6 +159,7 @@ public class PlayerInputControl {
|
||||
this.assetRoot = assetRoot;
|
||||
this.currentAnim = null;
|
||||
this.runningClip = null;
|
||||
animLib.configureLocomotionSnap(assetRoot, animSetName);
|
||||
this.animComposer = (visual != null) ? RetargetingSystem.findAnimComposer(visual) : null;
|
||||
log.info("[AnimCtx] AnimComposer gefunden: {}", animComposer != null);
|
||||
skinningControl = findSkinningControl(visual);
|
||||
@@ -164,6 +168,9 @@ public class PlayerInputControl {
|
||||
armature = skinningControl.getArmature();
|
||||
logJointNames(armature);
|
||||
}
|
||||
animPlayer = (animComposer != null && skinningControl != null)
|
||||
? new AnimPlayer(animComposer, skinningControl)
|
||||
: null;
|
||||
if (visual != null) {
|
||||
visualBaseTranslation = visual.getLocalTranslation().clone();
|
||||
}
|
||||
@@ -336,18 +343,16 @@ public class PlayerInputControl {
|
||||
inputsBlocked = true;
|
||||
if (animComposer != null) {
|
||||
if (animLib != null && visual != null) animLib.ensureApplied(reviveClip, visual);
|
||||
// transitionLength=0: verhindert den 0.4 s Überblend-Effekt bei speed=0.
|
||||
// Ohne diesen Fix wäre transitionWeight=time/0.4=0 → Charakter zeigt Idle-Pose.
|
||||
com.jme3.anim.tween.action.Action reviveAction = animComposer.action(reviveClip);
|
||||
if (reviveAction instanceof com.jme3.anim.tween.action.BlendableAction ba) {
|
||||
ba.setTransitionLength(0.0);
|
||||
}
|
||||
frozenReviveAction = animComposer.setCurrentAction(reviveClip);
|
||||
if (animPlayer == null) animPlayer = new AnimPlayer(animComposer, skinningControl);
|
||||
// transition=0: harter Schnitt, kein BlendAction → Charakter startet sofort in frame 0
|
||||
animPlayer.play(reviveClip, 0f);
|
||||
frozenReviveAction = animPlayer.getCurrentAction();
|
||||
if (frozenReviveAction != null) {
|
||||
frozenReviveAction.setSpeed(0f);
|
||||
runningClip = animPlayer.getCurrentClip();
|
||||
log.info("[Intro] REVIVE '{}' eingefroren (frame 0)", reviveClip);
|
||||
} else {
|
||||
log.warn("[Intro] setCurrentAction('{}') → null – REVIVE nicht eingefroren", reviveClip);
|
||||
log.warn("[Intro] REVIVE '{}' → action null", reviveClip);
|
||||
}
|
||||
} else {
|
||||
log.warn("[Intro] animComposer null – REVIVE kann nicht eingefroren werden");
|
||||
@@ -456,6 +461,11 @@ public class PlayerInputControl {
|
||||
public void update(float tpf) {
|
||||
if (physicsChar == null) return;
|
||||
|
||||
if (animPlayer != null) {
|
||||
animPlayer.update(tpf);
|
||||
runningClip = animPlayer.getCurrentClip();
|
||||
}
|
||||
|
||||
if (visual != null && animOffsetSpeed > 0f) {
|
||||
Vector3f delta = animOffsetTarget.subtract(animOffsetCurrent);
|
||||
float dist = delta.length();
|
||||
@@ -764,7 +774,28 @@ public class PlayerInputControl {
|
||||
if (animOffsetCurrent.distanceSquared(worldOffset) > 1e-4f) {
|
||||
animOffsetSpeed = 5.0f;
|
||||
}
|
||||
log.info("[AnimOffset] Clip '{}' → Ziel ({},{},{})", clip, worldOffset.x, worldOffset.y, worldOffset.z);
|
||||
log.debug("[AnimOffset] Clip '{}' → Ziel ({},{},{})", clip, worldOffset.x, worldOffset.y, worldOffset.z);
|
||||
}
|
||||
|
||||
/**
|
||||
* Setzt den Anim-Offset für eine AnimationAction sofort (kein Lerp).
|
||||
* Verwenden bei Übergängen, bei denen der Lerp die Skelett-Animation kompensieren würde
|
||||
* (z.B. sit-down: Offset-Lerp 0→-0.5m hebt Hip-Bone-Vorwärtsbewegung auf → Hüfte wirkt statisch).
|
||||
*/
|
||||
public void applyActionOffsetInstant(AnimationAction action) {
|
||||
if (visual == null || animLib == null || animSetName == null) return;
|
||||
String clip = AnimationLibrary.getClipForAction(assetRoot, animSetName, action);
|
||||
if (clip == null) return;
|
||||
AnimOffset off = animOffsets.get(clip);
|
||||
if (off == null) { clearAnimOffsetInstant(); return; }
|
||||
Quaternion facing = visual.getLocalRotation().clone();
|
||||
Vector3f worldOffset = facing.mult(new Vector3f(off.tx, off.ty, off.tz));
|
||||
animOffsetTarget.set(worldOffset);
|
||||
animOffsetCurrent.set(worldOffset);
|
||||
animOffsetSpeed = 0f;
|
||||
visual.setLocalTranslation(visualBaseTranslation.clone().addLocal(animOffsetCurrent));
|
||||
log.info("[AnimOffset] Sofort: action={} clip='{}' → ({},{},{})", action, clip,
|
||||
worldOffset.x, worldOffset.y, worldOffset.z);
|
||||
}
|
||||
|
||||
public void clearAnimOffset() {
|
||||
@@ -821,29 +852,17 @@ public class PlayerInputControl {
|
||||
log.info("[Anim] tryPlay('{}') → applyTo FAILED", clip);
|
||||
return false;
|
||||
}
|
||||
// Optionale Überblend-Zeit (einmalig, wird nach Verwendung auf 0 zurückgesetzt)
|
||||
double transLen = nextTransitionLength;
|
||||
if (animPlayer == null) {
|
||||
animPlayer = new AnimPlayer(animComposer, skinningControl);
|
||||
}
|
||||
float transLen = (float) nextTransitionLength;
|
||||
nextTransitionLength = 0.0;
|
||||
if (transLen > 0.0) {
|
||||
com.jme3.anim.tween.action.Action nextAction = animComposer.action(clip);
|
||||
if (nextAction instanceof com.jme3.anim.tween.action.BlendableAction) {
|
||||
((com.jme3.anim.tween.action.BlendableAction) nextAction).setTransitionLength(transLen);
|
||||
log.info("[Anim] Transition {} → '{}' über {:.2f}s", runningClip, clip, transLen);
|
||||
}
|
||||
boolean ok = animPlayer.play(clip, transLen);
|
||||
log.info("[Anim] AnimPlayer.play('{}') transLen={} → {}", clip, transLen, ok ? "OK" : "FAILED");
|
||||
if (ok) {
|
||||
runningClip = animPlayer.getCurrentClip();
|
||||
applyAnimOffset(clip);
|
||||
}
|
||||
// Erst Action setzen, DANN SkinningControl aktivieren –
|
||||
// vermeidet 1 Frame in Bind-Pose × Armature-Rx90° = liegender Charakter.
|
||||
com.jme3.anim.tween.action.Action action = animComposer.setCurrentAction(clip);
|
||||
log.info("[Anim] setCurrentAction('{}') → {}", clip, action != null ? "OK" : "FAILED");
|
||||
if (action == null) {
|
||||
return false;
|
||||
}
|
||||
if (skinningControl != null && !skinningControl.isEnabled()) {
|
||||
skinningControl.setEnabled(true);
|
||||
log.info("[Anim] SkinningControl aktiviert nach Action '{}'", clip);
|
||||
}
|
||||
runningClip = clip;
|
||||
applyAnimOffset(clip);
|
||||
return true;
|
||||
return ok;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,9 +130,9 @@ public class WorldScene extends BaseAppState {
|
||||
if (loadedMapData == null) return result;
|
||||
|
||||
int size = de.blight.common.MapData.SPLAT_SIZE;
|
||||
int px = Math.max(0, Math.min(size - 1, Math.round((worldX + 2048f) / 2f)));
|
||||
// Z ist im Splatmap gespiegelt (Editor: pz = (size-1) - round((z+2048)/2))
|
||||
int pz = Math.max(0, Math.min(size - 1, (size - 1) - Math.round((worldZ + 2048f) / 2f)));
|
||||
int px = Math.max(0, Math.min(size - 1, Math.round((worldX + 1024f) / 2f)));
|
||||
// Z ist im Splatmap gespiegelt (Editor: pz = (size-1) - round((z+1024)/2))
|
||||
int pz = Math.max(0, Math.min(size - 1, (size - 1) - Math.round((worldZ + 1024f) / 2f)));
|
||||
int idx = pz * size + px;
|
||||
|
||||
// Upper/Third-Splatmap unterdrücken die Basistextur visuell (Overlay-Modell).
|
||||
@@ -540,9 +540,12 @@ public class WorldScene extends BaseAppState {
|
||||
@Override protected void onDisable() {}
|
||||
|
||||
private void setupAnimationContext() {
|
||||
animLib.applyAllTo(characterVisual != null ? characterVisual : character);
|
||||
MainCharacter mc = findMainCharacter();
|
||||
String setName = (mc != null) ? mc.getAnimSetPath() : null;
|
||||
if (setName != null) {
|
||||
animLib.configureLocomotionSnap(AnimationLibrary.findAssetRoot(), setName);
|
||||
}
|
||||
animLib.applyAllTo(characterVisual != null ? characterVisual : character);
|
||||
log.info("[AnimCtx] MainCharacter: {} animSetPath: {} clipCount: {} clips: {}",
|
||||
mc != null ? mc.getCharacterId() : "null",
|
||||
setName,
|
||||
|
||||
@@ -58,7 +58,6 @@ public class WorldInteractableState extends BaseAppState {
|
||||
private static final float BENCH_RANGE = 5f;
|
||||
private static final float BED_RANGE = 6f;
|
||||
private static final float WALK_TIMEOUT = 12f;
|
||||
private static final float BENCH_SIT_MOVE_DIST = 0.5f;
|
||||
|
||||
// Nach dem Aufstehen: Bank-Physik erst re-enablen wenn Charakter weit genug weg
|
||||
private static final float BENCH_REENABLE_DIST_SQ = 0.36f; // 0.6m Radius
|
||||
@@ -67,8 +66,6 @@ public class WorldInteractableState extends BaseAppState {
|
||||
private float benchPendingX = 0f;
|
||||
private float benchPendingZ = 0f;
|
||||
private float benchPendingTimer = 0f;
|
||||
/** Blickrichtung des Charakters beim Hinsetzen (weg von der Bank); für Positions-Versatz nach Anim. */
|
||||
private Vector3f currentBenchSitDir = null;
|
||||
|
||||
// ── Abhängigkeiten ────────────────────────────────────────────────────────
|
||||
|
||||
@@ -333,9 +330,6 @@ public class WorldInteractableState extends BaseAppState {
|
||||
InteractableEntry entry = entries.get(targetIdx);
|
||||
float rotY = getSitFacingRotY(entry);
|
||||
Vector3f sitDir = new Vector3f((float) Math.cos(rotY), 0f, (float) Math.sin(rotY));
|
||||
if (isBench(entry)) {
|
||||
currentBenchSitDir = sitDir.clone();
|
||||
}
|
||||
playerInput.requestTurn(sitDir, 0.35f, () -> startSitAnim(entry));
|
||||
}
|
||||
|
||||
@@ -360,18 +354,11 @@ public class WorldInteractableState extends BaseAppState {
|
||||
AnimationAction downAction = isBench(entry) ? AnimationAction.SIT_DOWN : AnimationAction.LIE_DOWN;
|
||||
AnimationAction idleAction = isBench(entry) ? AnimationAction.SITTING : AnimationAction.LYING;
|
||||
|
||||
if (isBench(entry)) {
|
||||
playerInput.applyActionOffsetInstant(downAction);
|
||||
}
|
||||
|
||||
playerInput.requestAnimation(downAction, 0f, () -> {
|
||||
if (isBench(entry) && currentBenchSitDir != null) {
|
||||
Vector3f cur = physicsChar.getPhysicsLocation();
|
||||
Vector3f delta = currentBenchSitDir.mult(-BENCH_SIT_MOVE_DIST);
|
||||
physicsChar.setPhysicsLocation(cur.add(delta));
|
||||
// Sofortiges Spatial-Update verhindert 1-Frame-Kamerazuckeln
|
||||
Spatial phySpatial = physicsChar.getSpatial();
|
||||
if (phySpatial != null) {
|
||||
phySpatial.setLocalTranslation(phySpatial.getLocalTranslation().add(delta));
|
||||
}
|
||||
log.info("[WorldInteractable] Charakter 50cm zur Bank verschoben.");
|
||||
}
|
||||
if (!isBench(entry)) snapToSitPos(entry);
|
||||
playerInput.lockInPlace();
|
||||
if (isBench(entry)) playerInput.setNextAnimTransition(0.2);
|
||||
@@ -412,20 +399,8 @@ public class WorldInteractableState extends BaseAppState {
|
||||
|
||||
playerInput.requestAnimation(upAction, 0f, () -> {
|
||||
if (isBench(entry)) {
|
||||
// Nach stand_up_bench: Charakter 50cm von Bank wegbewegen
|
||||
if (currentBenchSitDir != null) {
|
||||
Vector3f cur = physicsChar.getPhysicsLocation();
|
||||
Vector3f delta = currentBenchSitDir.mult(BENCH_SIT_MOVE_DIST);
|
||||
physicsChar.setPhysicsLocation(cur.add(delta));
|
||||
Spatial phySpatial = physicsChar.getSpatial();
|
||||
if (phySpatial != null) {
|
||||
phySpatial.setLocalTranslation(phySpatial.getLocalTranslation().add(delta));
|
||||
}
|
||||
currentBenchSitDir = null;
|
||||
playerInput.setGroundGrace(4);
|
||||
playerInput.setNextAnimTransition(0.2);
|
||||
log.info("[WorldInteractable] Charakter 50cm von Bank wegbewegt.");
|
||||
}
|
||||
playerInput.setGroundGrace(4);
|
||||
playerInput.setNextAnimTransition(0.2);
|
||||
playerInput.clearAnimOffset();
|
||||
benchPendingId = entry.interactableId();
|
||||
benchPendingX = entry.worldX();
|
||||
|
||||
Reference in New Issue
Block a user