Compare commits
2 Commits
ec0865d09b
...
974ee5765e
| Author | SHA1 | Date | |
|---|---|---|---|
| 974ee5765e | |||
| c221c26a23 |
Binary file not shown.
@@ -2,35 +2,27 @@
|
|||||||
"chapter": 0,
|
"chapter": 0,
|
||||||
"level": 0,
|
"level": 0,
|
||||||
"xp": 0,
|
"xp": 0,
|
||||||
"currentHp": 100,
|
"currentHp": 0,
|
||||||
"currentStamina": 100,
|
"currentStamina": 0,
|
||||||
"currentMana": 100,
|
"currentMana": 0,
|
||||||
"maxHp": 100,
|
"maxHp": 0,
|
||||||
"maxStamina": 100,
|
"maxStamina": 0,
|
||||||
"maxMana": 100,
|
"maxMana": 0,
|
||||||
"openHpRegeneration": 0,
|
"openHpRegeneration": 0,
|
||||||
"openManaRegeneration": 0,
|
"openManaRegeneration": 0,
|
||||||
"openStaminaRegeneration": 0,
|
"openStaminaRegeneration": 0,
|
||||||
"restorationValue": 10,
|
"restorationValue": 10,
|
||||||
"abilities": {
|
"playedMonologueIds": [],
|
||||||
"lvlMagic": 1,
|
"firedTriggerIds": [],
|
||||||
"lvlStaffCombat": 1,
|
"visitedZoneIds": [],
|
||||||
"lvlSwordsmanship": 1,
|
"gameVariables": {},
|
||||||
"lvlArchery": 1,
|
"memberFractionIds": [],
|
||||||
"lvlHeavyWeapons": 1,
|
|
||||||
"lvlCrossbow": 1,
|
|
||||||
"lvlThievery": 0,
|
|
||||||
"lvlAlchemy": 0,
|
|
||||||
"lvlEngineering": 0,
|
|
||||||
"lvlSmithery": 0,
|
|
||||||
"lvlEnchanting": 0
|
|
||||||
},
|
|
||||||
"listeners": [],
|
"listeners": [],
|
||||||
"characterId": "hero",
|
"characterId": "hero",
|
||||||
"name": {
|
"name": {
|
||||||
"id": "hero.name"
|
"id": "hero.name"
|
||||||
},
|
},
|
||||||
"modelPath": "Models/Chars/mainchar.j3o",
|
"modelPath": "Characters/Models/hero.j3o",
|
||||||
"animSetPath": "human",
|
"animSetPath": "human",
|
||||||
"type": "MAIN_CHARACTER"
|
"type": "MAIN_CHARACTER"
|
||||||
}
|
}
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 6.8 KiB After Width: | Height: | Size: 6.9 KiB |
@@ -11326,8 +11326,10 @@ public class EditorApp extends Application {
|
|||||||
var clip = clipCombo.getValue();
|
var clip = clipCombo.getValue();
|
||||||
if (action != null && clip != null) {
|
if (action != null && clip != null) {
|
||||||
String entry = action.name() + " → " + clip;
|
String entry = action.name() + " → " + clip;
|
||||||
if (!animSetActionListView.getItems().contains(entry))
|
if (!animSetActionListView.getItems().contains(entry)) {
|
||||||
animSetActionListView.getItems().add(entry);
|
animSetActionListView.getItems().add(entry);
|
||||||
|
autoSaveAnimSet();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -11410,6 +11412,11 @@ public class EditorApp extends Application {
|
|||||||
saveCurrentAnimSet(animSetCurrentName, animSetCurrentDir);
|
saveCurrentAnimSet(animSetCurrentName, animSetCurrentDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void autoSaveCharacter() {
|
||||||
|
if (charIdField == null || charIdField.getText().trim().isBlank()) return;
|
||||||
|
saveCharacter(ASSET_ROOT.resolve("Characters"));
|
||||||
|
}
|
||||||
|
|
||||||
private void saveCurrentAnimSet(String setName, Path setDir) {
|
private void saveCurrentAnimSet(String setName, Path setDir) {
|
||||||
if (animSetClipListView == null) {
|
if (animSetClipListView == null) {
|
||||||
return;
|
return;
|
||||||
@@ -12037,16 +12044,6 @@ public class EditorApp extends Application {
|
|||||||
VBox inner = new VBox(8);
|
VBox inner = new VBox(8);
|
||||||
inner.setPadding(new Insets(10));
|
inner.setPadding(new Insets(10));
|
||||||
|
|
||||||
Button newCharBtn = new Button("Neuer Charakter");
|
|
||||||
newCharBtn.setMaxWidth(Double.MAX_VALUE);
|
|
||||||
newCharBtn.setOnAction(e -> clearCharacterForm());
|
|
||||||
|
|
||||||
Button importCharModelBtn = new Button("Character-Modell importieren (FBX)…");
|
|
||||||
importCharModelBtn.setMaxWidth(Double.MAX_VALUE);
|
|
||||||
importCharModelBtn.setStyle("-fx-font-weight: bold;");
|
|
||||||
importCharModelBtn.setOnAction(e -> handleCharacterModelImport(importCharModelBtn.getScene().getWindow()));
|
|
||||||
|
|
||||||
inner.getChildren().addAll(newCharBtn, importCharModelBtn);
|
|
||||||
|
|
||||||
charEditorStatusLabel = new Label("Wähle einen Character im Baum oder erstelle einen neuen");
|
charEditorStatusLabel = new Label("Wähle einen Character im Baum oder erstelle einen neuen");
|
||||||
charEditorStatusLabel.setWrapText(true);
|
charEditorStatusLabel.setWrapText(true);
|
||||||
@@ -12097,18 +12094,6 @@ public class EditorApp extends Application {
|
|||||||
refreshCharAnimSetCombo();
|
refreshCharAnimSetCombo();
|
||||||
charAnimSetCombo.setOnAction(e -> updateCharActionCombosFromSet());
|
charAnimSetCombo.setOnAction(e -> updateCharActionCombosFromSet());
|
||||||
|
|
||||||
Button stripClipsBtn = new Button("Eingebettete Clips löschen");
|
|
||||||
stripClipsBtn.setMaxWidth(Double.MAX_VALUE);
|
|
||||||
stripClipsBtn.setDisable(true);
|
|
||||||
charModelCombo.valueProperty().addListener((obs, ov, nv) ->
|
|
||||||
stripClipsBtn.setDisable(nv == null || nv.isBlank()));
|
|
||||||
stripClipsBtn.setOnAction(e -> {
|
|
||||||
String modelPath = charModelCombo.getValue();
|
|
||||||
if (modelPath == null || modelPath.isBlank()) return;
|
|
||||||
if (charEditorStatusLabel != null)
|
|
||||||
charEditorStatusLabel.setText("Entferne eingebettete Clips…");
|
|
||||||
input.animStripClipsRequest.set(modelPath);
|
|
||||||
});
|
|
||||||
|
|
||||||
charEditContainer.getChildren().addAll(
|
charEditContainer.getChildren().addAll(
|
||||||
dl.apply("ID:"), charIdField,
|
dl.apply("ID:"), charIdField,
|
||||||
@@ -12202,8 +12187,7 @@ public class EditorApp extends Application {
|
|||||||
charEditContainer.getChildren().add(charNpcSection);
|
charEditContainer.getChildren().add(charNpcSection);
|
||||||
charEditContainer.getChildren().addAll(
|
charEditContainer.getChildren().addAll(
|
||||||
dl.apply("Modell:"), charModelCombo,
|
dl.apply("Modell:"), charModelCombo,
|
||||||
dl.apply("Anim-Set:"), charAnimSetCombo,
|
dl.apply("Anim-Set:"), charAnimSetCombo
|
||||||
stripClipsBtn
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// ── Aktions-Zuweisung ─────────────────────────────────────────────────
|
// ── Aktions-Zuweisung ─────────────────────────────────────────────────
|
||||||
@@ -12214,13 +12198,20 @@ public class EditorApp extends Application {
|
|||||||
charActionLabelsBox.getChildren().add(actionHint);
|
charActionLabelsBox.getChildren().add(actionHint);
|
||||||
charEditContainer.getChildren().add(charActionLabelsBox);
|
charEditContainer.getChildren().add(charActionLabelsBox);
|
||||||
|
|
||||||
// ── Speichern ─────────────────────────────────────────────────────────
|
// ── Auto-Save-Listener ────────────────────────────────────────────────
|
||||||
charEditContainer.getChildren().add(new Separator());
|
charIdField.focusedProperty().addListener((obs, ov, nv) -> { if (!nv) autoSaveCharacter(); });
|
||||||
Button saveCharBtn = new Button("Charakter speichern");
|
charTypeCombo.valueProperty().addListener((obs, ov, nv) -> autoSaveCharacter());
|
||||||
saveCharBtn.setMaxWidth(Double.MAX_VALUE);
|
charModelCombo.valueProperty().addListener((obs, ov, nv) -> autoSaveCharacter());
|
||||||
saveCharBtn.setStyle("-fx-font-weight:bold;-fx-background-color:#3a7a3a;-fx-text-fill:white;");
|
charAnimSetCombo.valueProperty().addListener((obs, ov, nv) -> autoSaveCharacter());
|
||||||
saveCharBtn.setOnAction(e -> saveCharacter(charDir));
|
charFractionCombo.valueProperty().addListener((obs, ov, nv) -> autoSaveCharacter());
|
||||||
charEditContainer.getChildren().add(saveCharBtn);
|
charStatusCombo.valueProperty().addListener((obs, ov, nv) -> autoSaveCharacter());
|
||||||
|
charTraderCheck.selectedProperty().addListener((obs, ov, nv) -> autoSaveCharacter());
|
||||||
|
charTraderItemsView.getItems().addListener(
|
||||||
|
(javafx.collections.ListChangeListener<String>) c -> autoSaveCharacter());
|
||||||
|
charMsgFriendly.focusedProperty().addListener((obs, ov, nv) -> { if (!nv) autoSaveCharacter(); });
|
||||||
|
charMsgNeutral.focusedProperty().addListener((obs, ov, nv) -> { if (!nv) autoSaveCharacter(); });
|
||||||
|
charMsgEnraged.focusedProperty().addListener((obs, ov, nv) -> { if (!nv) autoSaveCharacter(); });
|
||||||
|
charMsgEnemy.focusedProperty().addListener((obs, ov, nv) -> { if (!nv) autoSaveCharacter(); });
|
||||||
|
|
||||||
inner.getChildren().add(charEditContainer);
|
inner.getChildren().add(charEditContainer);
|
||||||
|
|
||||||
|
|||||||
@@ -336,21 +336,14 @@ public class AnimationLibrary extends BaseAppState {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clips mit Vorwärts-Root-Motion in Local-Y (rennen, gehen, springen):
|
|
||||||
// Y wird auf 0 eingefroren → kein Drift. Alle anderen Clips: Y bleibt frei (Hinsetzen usw.)
|
|
||||||
private static final java.util.Set<String> LOCOMOTION_CLIPS = java.util.Set.of(
|
|
||||||
"running", "walking", "sprinting", "running_jump"
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Entfernt Root-Motion aus dem flachsten Bone mit Translation-Track (typischerweise Hips).
|
* Entfernt Root-Motion aus dem flachsten Bone mit Translation-Track (typischerweise Hips).
|
||||||
* Nur dieser eine Bone wird modifiziert — alle anderen Bones bleiben unverändert.
|
* Nur dieser eine Bone wird modifiziert — alle anderen Bones bleiben unverändert.
|
||||||
*
|
*
|
||||||
* In diesen Mixamo-Exporten ist die Achsenbelegung des Hips-Bones:
|
* Achsenbelegung dieses Mixamo-Exports im Hips-Bone-Local-Space:
|
||||||
* Local X → seitlich → wird auf 0 eingefroren (kein Seiten-Drift)
|
* Local X → JME3-X (seitlich) → wird auf 0 eingefroren
|
||||||
* Local Y → vorwärts → wird auf 0 eingefroren für Lauf-Clips (kein Vorwärts-Drift)
|
* Local Y → JME3-Y (Höhe oben) → IMMER frei lassen (Charakter-Höhe erhalten)
|
||||||
* bleibt frei für Sitz/Stand-Clips (leichte Neigungsbewegung)
|
* Local Z → JME3-Z (vorwärts) → wird auf 0 eingefroren (alle Clips auf der Stelle)
|
||||||
* Local Z → Höhe → IMMER frei lassen (Charakter-Höhe und Setz-Bewegung erhalten)
|
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("rawtypes")
|
@SuppressWarnings("rawtypes")
|
||||||
public static AnimClip snapRootBoneXZ(AnimClip clip, Armature armature) {
|
public static AnimClip snapRootBoneXZ(AnimClip clip, Armature armature) {
|
||||||
@@ -367,8 +360,6 @@ public class AnimationLibrary extends BaseAppState {
|
|||||||
}
|
}
|
||||||
if (minDepth == Integer.MAX_VALUE) return clip;
|
if (minDepth == Integer.MAX_VALUE) return clip;
|
||||||
|
|
||||||
boolean isLocomotion = LOCOMOTION_CLIPS.contains(clip.getName().toLowerCase(java.util.Locale.ROOT));
|
|
||||||
|
|
||||||
List<AnimTrack<?>> newTracks = new ArrayList<>();
|
List<AnimTrack<?>> newTracks = new ArrayList<>();
|
||||||
boolean modified = false;
|
boolean modified = false;
|
||||||
for (AnimTrack<?> track : clip.getTracks()) {
|
for (AnimTrack<?> track : clip.getTracks()) {
|
||||||
@@ -383,11 +374,6 @@ public class AnimationLibrary extends BaseAppState {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Y-Normalisierung für Nicht-Lauf-Clips: Frame-0 auf Bind-Pose-Y
|
|
||||||
float bindY = j.getInitialTransform().getTranslation().y;
|
|
||||||
float frame0Y = translations[0].y;
|
|
||||||
float yOffset = bindY - frame0Y;
|
|
||||||
|
|
||||||
// Diagnostik
|
// Diagnostik
|
||||||
float yMin = Float.MAX_VALUE, yMax = -Float.MAX_VALUE;
|
float yMin = Float.MAX_VALUE, yMax = -Float.MAX_VALUE;
|
||||||
float xRange = 0, zRange = 0;
|
float xRange = 0, zRange = 0;
|
||||||
@@ -397,21 +383,18 @@ public class AnimationLibrary extends BaseAppState {
|
|||||||
xRange = Math.max(xRange, Math.abs(t.x - translations[0].x));
|
xRange = Math.max(xRange, Math.abs(t.x - translations[0].x));
|
||||||
zRange = Math.max(zRange, Math.abs(t.z - translations[0].z));
|
zRange = Math.max(zRange, Math.abs(t.z - translations[0].z));
|
||||||
}
|
}
|
||||||
log.info("[AnimLib] snap '{}' root='{}' locomotion={} bindY={} frame0Y={} yOffset={} yRange={} xRange={} zRange={}",
|
log.info("[AnimLib] snap '{}' root='{}' xRange={} yRange={} zRange={}",
|
||||||
clip.getName(), j.getName(), isLocomotion,
|
clip.getName(), j.getName(),
|
||||||
String.format("%.3f", bindY), String.format("%.3f", frame0Y),
|
String.format("%.3f", xRange),
|
||||||
String.format("%.3f", yOffset), String.format("%.3f", yMax - yMin),
|
String.format("%.3f", yMax - yMin),
|
||||||
String.format("%.3f", xRange), String.format("%.3f", zRange));
|
String.format("%.3f", zRange));
|
||||||
|
|
||||||
Vector3f[] snapped = new Vector3f[translations.length];
|
Vector3f[] snapped = new Vector3f[translations.length];
|
||||||
for (int i = 0; i < translations.length; i++) {
|
for (int i = 0; i < translations.length; i++) {
|
||||||
float newY = isLocomotion
|
|
||||||
? 0f // Lauf-Clips: Y einfrieren (Vorwärts-Drift weg)
|
|
||||||
: (translations[i].y + yOffset); // Rest: Y normalisiert (Neigung erhalten)
|
|
||||||
snapped[i] = new Vector3f(
|
snapped[i] = new Vector3f(
|
||||||
0f, // X immer 0 (Seiten-Drift weg)
|
0f, // X = 0 (kein seitlicher Drift)
|
||||||
newY,
|
translations[i].y, // Y frei (Höhe; Y=oben in bone-local / JME3-Y)
|
||||||
translations[i].z // Z immer frei (Höhe und Setz-Bewegung erhalten)
|
0f // Z = 0 (kein Vorwärts-Drift; Z=vorwärts in bone-local)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
newTracks.add(new TransformTrack(j, tt.getTimes(), snapped, tt.getRotations(), tt.getScales()));
|
newTracks.add(new TransformTrack(j, tt.getTimes(), snapped, tt.getRotations(), tt.getScales()));
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ import com.jme3.renderer.queue.RenderQueue;
|
|||||||
import com.jme3.scene.Geometry;
|
import com.jme3.scene.Geometry;
|
||||||
import com.jme3.scene.Node;
|
import com.jme3.scene.Node;
|
||||||
import com.jme3.scene.shape.Quad;
|
import com.jme3.scene.shape.Quad;
|
||||||
|
import com.jme3.texture.Image;
|
||||||
|
import com.jme3.texture.Texture;
|
||||||
|
import com.jme3.texture.Texture2D;
|
||||||
|
import com.jme3.texture.image.ColorSpace;
|
||||||
|
import com.jme3.util.BufferUtils;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
import de.blight.game.post.GaussianBlurFilter;
|
import de.blight.game.post.GaussianBlurFilter;
|
||||||
import de.blight.game.scene.WorldScene;
|
import de.blight.game.scene.WorldScene;
|
||||||
|
|
||||||
@@ -65,8 +71,15 @@ public abstract class MenuScreen extends BaseAppState {
|
|||||||
|
|
||||||
private final String MAP_CLICK = "_MsClick_" + System.identityHashCode(this);
|
private final String MAP_CLICK = "_MsClick_" + System.identityHashCode(this);
|
||||||
|
|
||||||
private record Btn(float x, float y, float w, float h, Runnable action) {}
|
private record Btn(float x, float y, float w, float h, Runnable action,
|
||||||
|
Node patchNode, BitmapText label) {}
|
||||||
private final List<Btn> buttons = new ArrayList<>();
|
private final List<Btn> buttons = new ArrayList<>();
|
||||||
|
private Btn pressedBtn = null;
|
||||||
|
private Btn hoveredBtn = null;
|
||||||
|
private Node hoverGlow = null;
|
||||||
|
private Texture2D glowTex = null;
|
||||||
|
private static final float PRESS_DX = 1f, PRESS_DY = -2f;
|
||||||
|
private static final float GLOW_PAD = 20f;
|
||||||
|
|
||||||
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
// ── Lifecycle ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -114,6 +127,9 @@ public abstract class MenuScreen extends BaseAppState {
|
|||||||
if (canvasNode != null) { guiNode.detachChild(canvasNode); canvasNode = null; }
|
if (canvasNode != null) { guiNode.detachChild(canvasNode); canvasNode = null; }
|
||||||
panel = null;
|
panel = null;
|
||||||
buttons.clear();
|
buttons.clear();
|
||||||
|
pressedBtn = null;
|
||||||
|
hoveredBtn = null;
|
||||||
|
if (hoverGlow != null) { hoverGlow.removeFromParent(); hoverGlow = null; }
|
||||||
if (withBlur()) removeBlur();
|
if (withBlur()) removeBlur();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -171,7 +187,8 @@ public abstract class MenuScreen extends BaseAppState {
|
|||||||
case ARROW -> NinePatch.buttonArrow(assets);
|
case ARROW -> NinePatch.buttonArrow(assets);
|
||||||
default -> NinePatch.button(assets);
|
default -> NinePatch.button(assets);
|
||||||
};
|
};
|
||||||
parent.attachChild(patch.build(x, y, w, h, 0f));
|
Node patchNode = patch.build(x, y, w, h, 0f);
|
||||||
|
parent.attachChild(patchNode);
|
||||||
|
|
||||||
ColorRGBA col = (style == BtnStyle.DISABLED) ? UiTheme.COL_MUTED : UiTheme.COL_WHITE;
|
ColorRGBA col = (style == BtnStyle.DISABLED) ? UiTheme.COL_MUTED : UiTheme.COL_WHITE;
|
||||||
BitmapText lbl = crispText(label, UiTheme.FONT_LABEL, col);
|
BitmapText lbl = crispText(label, UiTheme.FONT_LABEL, col);
|
||||||
@@ -180,21 +197,108 @@ public abstract class MenuScreen extends BaseAppState {
|
|||||||
parent.attachChild(lbl);
|
parent.attachChild(lbl);
|
||||||
|
|
||||||
if (style != BtnStyle.DISABLED && action != null) {
|
if (style != BtnStyle.DISABLED && action != null) {
|
||||||
buttons.add(new Btn(x, y, w, h, action));
|
buttons.add(new Btn(x, y, w, h, action, patchNode, lbl));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Update-Loop (Hover) ───────────────────────────────────────────────────
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void update(float tpf) {
|
||||||
|
if (buttons.isEmpty() || panel == null) return;
|
||||||
|
float[] v = toVirtual(app.getInputManager().getCursorPosition());
|
||||||
|
Btn hovered = null;
|
||||||
|
for (Btn b : buttons) {
|
||||||
|
if (v[0] >= b.x() && v[0] <= b.x() + b.w()
|
||||||
|
&& v[1] >= b.y() && v[1] <= b.y() + b.h()) {
|
||||||
|
hovered = b;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hovered != hoveredBtn) {
|
||||||
|
if (hoverGlow != null) { hoverGlow.removeFromParent(); hoverGlow = null; }
|
||||||
|
hoveredBtn = hovered;
|
||||||
|
if (hoveredBtn != null) {
|
||||||
|
hoverGlow = buildHoverGlow(hoveredBtn);
|
||||||
|
panel.attachChild(hoverGlow);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (hoverGlow != null) {
|
||||||
|
float dx = (pressedBtn == hoveredBtn) ? PRESS_DX : 0f;
|
||||||
|
float dy = (pressedBtn == hoveredBtn) ? PRESS_DY : 0f;
|
||||||
|
hoverGlow.setLocalTranslation(hoveredBtn.x() + dx, hoveredBtn.y() + dy, 0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Node buildHoverGlow(Btn b) {
|
||||||
|
Geometry q = new Geometry("glow",
|
||||||
|
new Quad(b.w() + GLOW_PAD * 2f, b.h() + GLOW_PAD * 2f));
|
||||||
|
Material m = new Material(assets, "Common/MatDefs/Misc/Unshaded.j3md");
|
||||||
|
m.setTexture("ColorMap", getGlowTex());
|
||||||
|
m.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.AlphaAdditive);
|
||||||
|
q.setQueueBucket(RenderQueue.Bucket.Gui);
|
||||||
|
q.setMaterial(m);
|
||||||
|
// -GLOW_PAD damit der Glow um den Button herausragt; Node selbst wird auf Button-Pos gesetzt
|
||||||
|
q.setLocalTranslation(-GLOW_PAD, -GLOW_PAD, -0.5f);
|
||||||
|
Node g = new Node("hover-glow");
|
||||||
|
g.attachChild(q);
|
||||||
|
return g;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Texture2D getGlowTex() {
|
||||||
|
if (glowTex != null) return glowTex;
|
||||||
|
int W = 64, H = 64;
|
||||||
|
ByteBuffer buf = BufferUtils.createByteBuffer(W * H * 4);
|
||||||
|
for (int py = 0; py < H; py++) {
|
||||||
|
for (int px = 0; px < W; px++) {
|
||||||
|
float u = (px + 0.5f) / W;
|
||||||
|
float v = (py + 0.5f) / H;
|
||||||
|
float a = glowFade(u) * glowFade(v);
|
||||||
|
buf.put((byte) 13); // R ≈ 0.05 × 255
|
||||||
|
buf.put((byte) 255); // G = neon
|
||||||
|
buf.put((byte) 51); // B ≈ 0.20 × 255
|
||||||
|
buf.put((byte)(int)(a * 220f)); // A peak ≈ 86 %
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buf.flip();
|
||||||
|
glowTex = new Texture2D(new Image(Image.Format.RGBA8, W, H, buf, ColorSpace.Linear));
|
||||||
|
glowTex.setMagFilter(Texture.MagFilter.Bilinear);
|
||||||
|
glowTex.setMinFilter(Texture.MinFilter.BilinearNoMipMaps);
|
||||||
|
return glowTex;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float glowFade(float t) {
|
||||||
|
float d = Math.abs(t - 0.5f) * 2f; // 0 = Mitte, 1 = Rand
|
||||||
|
float x = 1f - Math.min(1f, d);
|
||||||
|
return x * x * (3f - 2f * x); // smoothstep
|
||||||
|
}
|
||||||
|
|
||||||
// ── Klick-Listener ────────────────────────────────────────────────────────
|
// ── Klick-Listener ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
private final ActionListener clickListener = (name, pressed, tpf) -> {
|
private final ActionListener clickListener = (name, pressed, tpf) -> {
|
||||||
if (!pressed) return;
|
|
||||||
float[] v = toVirtual(app.getInputManager().getCursorPosition());
|
float[] v = toVirtual(app.getInputManager().getCursorPosition());
|
||||||
onScreenClick(v);
|
if (pressed) {
|
||||||
for (Btn b : buttons) {
|
onScreenClick(v);
|
||||||
if (v[0] >= b.x() && v[0] <= b.x() + b.w()
|
for (Btn b : buttons) {
|
||||||
&& v[1] >= b.y() && v[1] <= b.y() + b.h()) {
|
if (v[0] >= b.x() && v[0] <= b.x() + b.w()
|
||||||
b.action().run();
|
&& v[1] >= b.y() && v[1] <= b.y() + b.h()) {
|
||||||
return;
|
pressedBtn = b;
|
||||||
|
b.patchNode().move(PRESS_DX, PRESS_DY, 0f);
|
||||||
|
b.label().move(PRESS_DX, PRESS_DY, 0f);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (pressedBtn != null) {
|
||||||
|
pressedBtn.patchNode().move(-PRESS_DX, -PRESS_DY, 0f);
|
||||||
|
pressedBtn.label().move(-PRESS_DX, -PRESS_DY, 0f);
|
||||||
|
Btn released = pressedBtn;
|
||||||
|
pressedBtn = null;
|
||||||
|
// Aktion nur auslösen wenn Maus noch über dem Button
|
||||||
|
if (v[0] >= released.x() && v[0] <= released.x() + released.w()
|
||||||
|
&& v[1] >= released.y() && v[1] <= released.y() + released.h()) {
|
||||||
|
released.action().run();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -171,6 +171,12 @@ public class NinePatch {
|
|||||||
int t = ty >= 0 ? ty + 1 : h / 4;
|
int t = ty >= 0 ? ty + 1 : h / 4;
|
||||||
int b = by >= 0 ? h - by : h / 4;
|
int b = by >= 0 ? h - by : h / 4;
|
||||||
|
|
||||||
|
// Sicherstellen, dass Mittelbereich mindestens 1 Pixel groß ist.
|
||||||
|
// Fehlerhafte Guide-Erkennung (Pixel mit zufällig passendem Alpha) kann
|
||||||
|
// sonst l+r >= w oder t+b >= h erzeugen → Endlosschleife in den Tiling-Methoden.
|
||||||
|
if (l + r >= w) { l = w / 4; r = w / 4; }
|
||||||
|
if (t + b >= h) { t = h / 4; b = h / 4; }
|
||||||
|
|
||||||
return new int[]{l, r, t, b};
|
return new int[]{l, r, t, b};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,12 +188,16 @@ public class NinePatch {
|
|||||||
* ty, by: Zeilenindizes der oberen/unteren Guide-Linie (PNG-Koordinaten)
|
* ty, by: Zeilenindizes der oberen/unteren Guide-Linie (PNG-Koordinaten)
|
||||||
*/
|
*/
|
||||||
private static void stripGuides(BufferedImage img, int w, int h, int lx, int rx, int ty, int by) {
|
private static void stripGuides(BufferedImage img, int w, int h, int lx, int rx, int ty, int by) {
|
||||||
// Snapshot der Guide-Positionen (vor Modifikation)
|
// Snapshot der Guide-Positionen (vor Modifikation).
|
||||||
|
// Als Guide zählen: exakte Guide-Alphas ODER semi-transparente Pixel (alpha < 200)
|
||||||
|
// auf den bekannten Guide-Zeilen/-Spalten — entsteht durch Anti-Aliasing beim Malen.
|
||||||
boolean[][] isGuide = new boolean[h][w];
|
boolean[][] isGuide = new boolean[h][w];
|
||||||
for (int y = 0; y < h; y++) {
|
for (int y = 0; y < h; y++) {
|
||||||
for (int x = 0; x < w; x++) {
|
for (int x = 0; x < w; x++) {
|
||||||
int a = (img.getRGB(x, y) >> 24) & 0xff;
|
int a = (img.getRGB(x, y) >> 24) & 0xff;
|
||||||
isGuide[y][x] = GUIDE_ALPHAS.contains(a);
|
boolean exactGuide = GUIDE_ALPHAS.contains(a);
|
||||||
|
boolean onGuideLine = (x == lx || x == rx || y == ty || y == by);
|
||||||
|
isGuide[y][x] = exactGuide || (onGuideLine && a < 200 && a > 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -332,6 +342,7 @@ public class NinePatch {
|
|||||||
float u1, float v1, float u2, float v2,
|
float u1, float v1, float u2, float v2,
|
||||||
float cw, float ch,
|
float cw, float ch,
|
||||||
float texW, float texH, float z) {
|
float texW, float texH, float z) {
|
||||||
|
if (cw <= 0 || ch <= 0) return;
|
||||||
int iz = 0;
|
int iz = 0;
|
||||||
for (float py = sy1; py < sy2; py += ch, iz++) {
|
for (float py = sy1; py < sy2; py += ch, iz++) {
|
||||||
float th = Math.min(ch, sy2 - py);
|
float th = Math.min(ch, sy2 - py);
|
||||||
@@ -352,6 +363,7 @@ public class NinePatch {
|
|||||||
float sx1, float py, float sx2, float ph,
|
float sx1, float py, float sx2, float ph,
|
||||||
float u1, float u2, float vMin, float vMax,
|
float u1, float u2, float vMin, float vMax,
|
||||||
float cw, float texW, float z) {
|
float cw, float texW, float z) {
|
||||||
|
if (cw <= 0) return;
|
||||||
int ix = 0;
|
int ix = 0;
|
||||||
for (float px = sx1; px < sx2; px += cw, ix++) {
|
for (float px = sx1; px < sx2; px += cw, ix++) {
|
||||||
float tw = Math.min(cw, sx2 - px);
|
float tw = Math.min(cw, sx2 - px);
|
||||||
@@ -366,6 +378,7 @@ public class NinePatch {
|
|||||||
float px, float sy1, float pw, float sy2,
|
float px, float sy1, float pw, float sy2,
|
||||||
float uMin, float uMax, float v1, float v2,
|
float uMin, float uMax, float v1, float v2,
|
||||||
float ch, float texH, float z) {
|
float ch, float texH, float z) {
|
||||||
|
if (ch <= 0) return;
|
||||||
int iz = 0;
|
int iz = 0;
|
||||||
for (float py = sy1; py < sy2; py += ch, iz++) {
|
for (float py = sy1; py < sy2; py += ch, iz++) {
|
||||||
float th = Math.min(ch, sy2 - py);
|
float th = Math.min(ch, sy2 - py);
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ public class PlayerInputControl {
|
|||||||
visualBaseTranslation = visual.getLocalTranslation().clone();
|
visualBaseTranslation = visual.getLocalTranslation().clone();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
AnimSet as = AnimSet.load(assetRoot.resolve("animations/sets"), animSetName);
|
AnimSet as = AnimSet.load(assetRoot.resolve("Characters").resolve("sets"), animSetName);
|
||||||
animOffsets = as.getAnimOffsets();
|
animOffsets = as.getAnimOffsets();
|
||||||
log.info("[AnimCtx] {} Anim-Offset-Einträge geladen.", animOffsets.size());
|
log.info("[AnimCtx] {} Anim-Offset-Einträge geladen.", animOffsets.size());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -629,6 +629,7 @@ public class PlayerInputControl {
|
|||||||
}
|
}
|
||||||
String clip = AnimationLibrary.getClipForAction(assetRoot, animSetName, action);
|
String clip = AnimationLibrary.getClipForAction(assetRoot, animSetName, action);
|
||||||
log.info("[Anim] {} → clip='{}' (set={})", action, clip, animSetName);
|
log.info("[Anim] {} → clip='{}' (set={})", action, clip, animSetName);
|
||||||
|
if (runningClip != null) nextTransitionLength = 0.15;
|
||||||
if (clip != null && tryPlay(clip)) return;
|
if (clip != null && tryPlay(clip)) return;
|
||||||
if (action != AnimationAction.DEFAULT) {
|
if (action != AnimationAction.DEFAULT) {
|
||||||
String defClip = AnimationLibrary.getClipForAction(assetRoot, animSetName, AnimationAction.DEFAULT);
|
String defClip = AnimationLibrary.getClipForAction(assetRoot, animSetName, AnimationAction.DEFAULT);
|
||||||
|
|||||||
@@ -277,6 +277,12 @@ public class CharacterNavigator {
|
|||||||
}
|
}
|
||||||
if (clip == null) return;
|
if (clip == null) return;
|
||||||
if (!animLib.applyTo(clip, visual)) return;
|
if (!animLib.applyTo(clip, visual)) return;
|
||||||
|
if (currentAnim != null) {
|
||||||
|
com.jme3.anim.tween.action.Action nextAction = animComposer.action(clip);
|
||||||
|
if (nextAction instanceof com.jme3.anim.tween.action.BlendableAction ba) {
|
||||||
|
ba.setTransitionLength(0.15);
|
||||||
|
}
|
||||||
|
}
|
||||||
com.jme3.anim.tween.action.Action act = animComposer.setCurrentAction(clip);
|
com.jme3.anim.tween.action.Action act = animComposer.setCurrentAction(clip);
|
||||||
if (act == null) return;
|
if (act == null) return;
|
||||||
if (skinningControl != null && !skinningControl.isEnabled()) {
|
if (skinningControl != null && !skinningControl.isEnabled()) {
|
||||||
|
|||||||
Reference in New Issue
Block a user