Kofferdetektiv iPad: zwei feste Joysticks (immer 50% sichtbar)
Statt schwebendem Links-Joystick + Rechts-Wisch jetzt zwei feste, halbtransparente Joysticks: - links = Umsehen (Auge 👁), ratenbasiertes Drehen/Neigen in update() - rechts = Bewegen (Fuß 🦶) Touch wird dem naeheren Joystick im Fangradius zugewiesen (assign/stickVec). Interaktions-Button (🧳) nach unten-mittig verschoben (kollidierte sonst mit dem rechten Bewegungs-Joystick). Joysticks nur auf Touch sichtbar (body.kd-touch). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -393,7 +393,8 @@ const World = {
|
||||
fundMeshes: {}, // Fundsachen am Boden (Badge-Sammeln), je Spawn-Index
|
||||
player: { x: 0, z: 24, yaw: 0, speed: 4.2 },
|
||||
keys: {}, look: { lx: 0, ly: 0 }, pitch: 0,
|
||||
joy: { active: false, dx: 0, dy: 0 },
|
||||
joy: { active: false, dx: 0, dy: 0 }, // rechter Joystick: Bewegen (Fuß)
|
||||
lookJoy: { dx: 0, dy: 0 }, // linker Joystick: Umsehen (Auge)
|
||||
moving: false, locked: false,
|
||||
jumpY: 0, vy: 0,
|
||||
animated: [], // {update(dt,t)} — Bänder, Roboter, Blinklichter
|
||||
@@ -2757,41 +2758,46 @@ const World = {
|
||||
}
|
||||
});
|
||||
|
||||
// Touch: linke Hälfte Joystick, rechte Hälfte Umschauen
|
||||
const joyEl = $('joystick'), knob = $('joy-knob');
|
||||
const joyPos = t => {
|
||||
const r = joyEl.getBoundingClientRect();
|
||||
const dx = (t.clientX - (r.left + r.width / 2)) / (r.width / 2);
|
||||
const dy = (t.clientY - (r.top + r.height / 2)) / (r.height / 2);
|
||||
// Touch: zwei feste, immer sichtbare Joysticks.
|
||||
// links = Umsehen (Auge 👁) → this.lookJoy (ratenbasiertes Drehen in update())
|
||||
// rechts = Bewegen (Fuß 🦶) → this.joy
|
||||
if (IS_TOUCH) document.body.classList.add('kd-touch');
|
||||
const moveEl = $('joystick'), moveKnob = $('joy-knob');
|
||||
const lookEl = $('lookstick'), lookKnob = $('look-knob');
|
||||
const KNOB_TRAVEL = 34; // px, wie weit der Knopf maximal auswandert
|
||||
const CATCH_R = 160; // px Fangradius um den Joystick-Mittelpunkt
|
||||
const centerOf = el => { const r = el.getBoundingClientRect(); return { cx: r.left + r.width / 2, cy: r.top + r.height / 2, hw: r.width / 2, hh: r.height / 2 }; };
|
||||
// Normalisierter Vektor (−1..1) eines Touches relativ zum festen Joystick-Zentrum.
|
||||
const stickVec = (el, t) => {
|
||||
const c = centerOf(el);
|
||||
const dx = (t.clientX - c.cx) / c.hw, dy = (t.clientY - c.cy) / c.hh;
|
||||
const len = Math.hypot(dx, dy) || 1, cl = Math.min(1, len);
|
||||
return { dx: dx / len * cl, dy: dy / len * cl };
|
||||
};
|
||||
// Weist einen neuen Touch dem näheren freien Joystick zu (nur im Fangradius).
|
||||
const assign = t => {
|
||||
const dist = el => { const c = centerOf(el); return Math.hypot(t.clientX - c.cx, t.clientY - c.cy); };
|
||||
const dM = dist(moveEl), dL = dist(lookEl);
|
||||
if (dM <= dL) return dM < CATCH_R ? 'move' : null;
|
||||
return dL < CATCH_R ? 'look' : null;
|
||||
};
|
||||
let joyTouch = null, lookTouch = null;
|
||||
const applyMove = t => { const v = stickVec(moveEl, t); this.joy.dx = v.dx; this.joy.dy = v.dy; moveKnob.style.transform = 'translate(' + v.dx * KNOB_TRAVEL + 'px,' + v.dy * KNOB_TRAVEL + 'px)'; };
|
||||
const applyLook = t => { const v = stickVec(lookEl, t); this.lookJoy.dx = v.dx; this.lookJoy.dy = v.dy; lookKnob.style.transform = 'translate(' + v.dx * KNOB_TRAVEL + 'px,' + v.dy * KNOB_TRAVEL + 'px)'; };
|
||||
canvas.parentElement.addEventListener('touchstart', e => {
|
||||
if (UI.overlayOpen()) return;
|
||||
if (e.target.closest && e.target.closest('#btn-touch-act, #btn-map, #minimap-wrap')) return;
|
||||
for (const t of e.changedTouches) {
|
||||
if (t.clientX < innerWidth / 2 && joyTouch === null) {
|
||||
joyTouch = t.identifier; this.joy.active = true;
|
||||
joyEl.style.left = (t.clientX - 60) + 'px'; joyEl.style.top = (t.clientY - 60) + 'px';
|
||||
joyEl.classList.add('visible');
|
||||
const p = joyPos(t); this.joy.dx = p.dx; this.joy.dy = p.dy;
|
||||
} else if (lookTouch === null) {
|
||||
lookTouch = t.identifier; this.look.lx = t.clientX; this.look.ly = t.clientY;
|
||||
}
|
||||
const which = assign(t);
|
||||
if (which === 'move' && joyTouch === null) { joyTouch = t.identifier; this.joy.active = true; applyMove(t); }
|
||||
else if (which === 'look' && lookTouch === null) { lookTouch = t.identifier; applyLook(t); }
|
||||
}
|
||||
e.preventDefault();
|
||||
}, { passive: false });
|
||||
canvas.parentElement.addEventListener('touchmove', e => {
|
||||
for (const t of e.changedTouches) {
|
||||
if (t.identifier === joyTouch) {
|
||||
const p = joyPos(t); this.joy.dx = p.dx; this.joy.dy = p.dy;
|
||||
knob.style.transform = 'translate(' + p.dx * 34 + 'px,' + p.dy * 34 + 'px)';
|
||||
} else if (t.identifier === lookTouch) {
|
||||
this.player.yaw -= (t.clientX - this.look.lx) * 0.005;
|
||||
this.pitch = Math.max(-0.9, Math.min(0.9, this.pitch - (t.clientY - this.look.ly) * 0.004));
|
||||
this.look.lx = t.clientX; this.look.ly = t.clientY;
|
||||
}
|
||||
if (t.identifier === joyTouch) applyMove(t);
|
||||
else if (t.identifier === lookTouch) applyLook(t);
|
||||
}
|
||||
e.preventDefault();
|
||||
}, { passive: false });
|
||||
@@ -2799,9 +2805,12 @@ const World = {
|
||||
for (const t of e.changedTouches) {
|
||||
if (t.identifier === joyTouch) {
|
||||
joyTouch = null; this.joy.active = false; this.joy.dx = this.joy.dy = 0;
|
||||
joyEl.classList.remove('visible'); knob.style.transform = '';
|
||||
moveKnob.style.transform = '';
|
||||
}
|
||||
if (t.identifier === lookTouch) {
|
||||
lookTouch = null; this.lookJoy.dx = this.lookJoy.dy = 0;
|
||||
lookKnob.style.transform = '';
|
||||
}
|
||||
if (t.identifier === lookTouch) lookTouch = null;
|
||||
}
|
||||
};
|
||||
canvas.parentElement.addEventListener('touchend', endTouch);
|
||||
@@ -2878,6 +2887,13 @@ const World = {
|
||||
if (!this.blocked(p.x + vx, p.z)) p.x += vx;
|
||||
if (!this.blocked(p.x, p.z + vz)) p.z += vz;
|
||||
}
|
||||
// Linker Joystick: ratenbasiertes Umsehen (Auslenkung = Dreh-/Neige-Tempo).
|
||||
if (this.lookJoy.dx || this.lookJoy.dy) {
|
||||
const LOOK_RATE = 2.4; // rad/s bei Vollausschlag
|
||||
p.yaw -= this.lookJoy.dx * LOOK_RATE * dt;
|
||||
this.pitch = Math.max(-0.9, Math.min(0.9, this.pitch - this.lookJoy.dy * LOOK_RATE * dt));
|
||||
}
|
||||
|
||||
if (this.mode === 'glb') this.checkPortals();
|
||||
Sound.setWalking(this.moving && this.jumpY <= 0 && !UI.overlayOpen(),
|
||||
this.keys['ShiftLeft'] || this.keys['ShiftRight']
|
||||
|
||||
Reference in New Issue
Block a user