HelloSun commited on
Commit
e6a5657
·
verified ·
1 Parent(s): ad2a684

Fix hero spinning while walking: joystick dead zone with hysteresis, vector smoothing, floating stick origin

Browse files
Files changed (2) hide show
  1. index.html +24 -8
  2. sw.js +1 -1
index.html CHANGED
@@ -228,6 +228,7 @@ button.big.ghost { background:rgba(20,16,10,.8); color:var(--gold); }
228
  // ---------------------------------------------------------------- constants
229
  const HW = 96, HH = 48; // half tile size (tile 192x96)
230
  const HERO_SPEED = 4.2; // tiles / second
 
231
  const ENEMY_SPEED = 0.7; // multiplier on enemy file speed
232
  const ENEMY_DMG_SCALE = 0.3; // balance factor on file damage (web port)
233
  const HERO_R = 0.28, ENEMY_R = 0.28;
@@ -270,7 +271,7 @@ button.big.ghost { background:rgba(20,16,10,.8); color:var(--gold); }
270
  enemies: [], drops: [], projs: [], texts: [], marks: [],
271
  canvas: null, ctx: null, W: 0, H: 0, zoom: 1,
272
  auto: false, lock: true, atkOn: true, eid: 0,
273
- joy: { on: false, ox: 0, oy: 0, vx: 0, vy: 0 },
274
  keyDir: null, ptr: null, cls: 'brute', running: false,
275
  noHotUntil: 0, changing: false, lastHud: 0, lastSave: 0, kills: 0, skillBtns: [],
276
  };
@@ -899,14 +900,26 @@ button.big.ghost { background:rgba(20,16,10,.8); color:var(--gold); }
899
  // while an attack animation is playing, keep the attack facing and do not replace the swing with run/stance
900
  const attacking = t < H.lockUntil;
901
  const inReach = H.target && !H.target.dead && dist(H.x, H.y, H.target.x, H.target.y) <= S.world.classes[H.cls].range + 0.25;
902
- if (S.joy.on || S.keyDir) {
903
- const [vx, vy] = S.joy.on ? [S.joy.vx, S.joy.vy] : S.keyDir;
 
 
 
 
 
 
 
 
 
 
 
 
904
  const len = Math.hypot(vx, vy);
905
- if (len > 0.01) {
906
  // screen vector -> world (iso inverse on direction vector)
907
  const wx = (vx / HW + vy / HH) / 2, wy = (vy / HH - vx / HW) / 2;
908
  const wl = Math.hypot(wx, wy) || 1;
909
- const k = sp * dt * Math.min(1, len);
910
  let mx = wx / wl * k, my = wy / wl * k;
911
  // already in reach of the locked target: do not walk into it (that made the hero overshoot back and forth)
912
  if (inReach && mx * (H.target.x - H.x) + my * (H.target.y - H.y) > 0) { mx = 0; my = 0; }
@@ -1088,15 +1101,18 @@ button.big.ghost { background:rgba(20,16,10,.8); color:var(--gold); }
1088
  }
1089
  if (S.joy.on) {
1090
  let vx = e.clientX - S.joy.ox, vy = e.clientY - S.joy.oy;
1091
- const L = Math.hypot(vx, vy), R = 70;
1092
- if (L > R) { vx = vx / L * R; vy = vy / L * R; }
 
 
 
1093
  S.joy.vx = vx; S.joy.vy = vy; S.joy.cx = S.joy.ox + vx; S.joy.cy = S.joy.oy + vy;
1094
  }
1095
  });
1096
  const up = (e) => {
1097
  if (!S.ptr || e.pointerId !== S.ptr.id) return;
1098
  if (!S.joy.on && now() - S.ptr.t0 < 0.45) onTap(e.clientX, e.clientY);
1099
- S.joy.on = false; S.joy.vx = S.joy.vy = 0; S.ptr = null;
1100
  };
1101
  cv.addEventListener('pointerup', up);
1102
  cv.addEventListener('pointercancel', up);
 
228
  // ---------------------------------------------------------------- constants
229
  const HW = 96, HH = 48; // half tile size (tile 192x96)
230
  const HERO_SPEED = 4.2; // tiles / second
231
+ const JOY_R = 70, JOY_DEAD = 10; // joystick radius / dead zone (css px); released again below 60% of the dead zone
232
  const ENEMY_SPEED = 0.7; // multiplier on enemy file speed
233
  const ENEMY_DMG_SCALE = 0.3; // balance factor on file damage (web port)
234
  const HERO_R = 0.28, ENEMY_R = 0.28;
 
271
  enemies: [], drops: [], projs: [], texts: [], marks: [],
272
  canvas: null, ctx: null, W: 0, H: 0, zoom: 1,
273
  auto: false, lock: true, atkOn: true, eid: 0,
274
+ joy: { on: false, act: false, ox: 0, oy: 0, vx: 0, vy: 0, sx: 0, sy: 0 },
275
  keyDir: null, ptr: null, cls: 'brute', running: false,
276
  noHotUntil: 0, changing: false, lastHud: 0, lastSave: 0, kills: 0, skillBtns: [],
277
  };
 
900
  // while an attack animation is playing, keep the attack facing and do not replace the swing with run/stance
901
  const attacking = t < H.lockUntil;
902
  const inReach = H.target && !H.target.dead && dist(H.x, H.y, H.target.x, H.target.y) <= S.world.classes[H.cls].range + 0.25;
903
+ // movement input: stick with dead zone (+ smoothing against thumb tremor) or keyboard
904
+ let vx = 0, vy = 0, power = 0;
905
+ if (S.joy.on) {
906
+ const L = Math.hypot(S.joy.vx, S.joy.vy);
907
+ if (!S.joy.act && L > JOY_DEAD) { S.joy.act = true; S.joy.sx = S.joy.vx; S.joy.sy = S.joy.vy; }
908
+ else if (S.joy.act && L < JOY_DEAD * 0.6) S.joy.act = false;
909
+ if (S.joy.act) {
910
+ const a = Math.min(1, dt * 14);
911
+ S.joy.sx += (S.joy.vx - S.joy.sx) * a; S.joy.sy += (S.joy.vy - S.joy.sy) * a;
912
+ vx = S.joy.sx; vy = S.joy.sy;
913
+ power = Math.min(1, 0.4 + (Math.hypot(vx, vy) - JOY_DEAD) / 30);
914
+ }
915
+ } else if (S.keyDir) { [vx, vy] = S.keyDir; power = 1; }
916
+ {
917
  const len = Math.hypot(vx, vy);
918
+ if (power > 0 && len > 0.01) {
919
  // screen vector -> world (iso inverse on direction vector)
920
  const wx = (vx / HW + vy / HH) / 2, wy = (vy / HH - vx / HW) / 2;
921
  const wl = Math.hypot(wx, wy) || 1;
922
+ const k = sp * dt * power;
923
  let mx = wx / wl * k, my = wy / wl * k;
924
  // already in reach of the locked target: do not walk into it (that made the hero overshoot back and forth)
925
  if (inReach && mx * (H.target.x - H.x) + my * (H.target.y - H.y) > 0) { mx = 0; my = 0; }
 
1101
  }
1102
  if (S.joy.on) {
1103
  let vx = e.clientX - S.joy.ox, vy = e.clientY - S.joy.oy;
1104
+ const L = Math.hypot(vx, vy), R = JOY_R;
1105
+ if (L > R) { // the stick follows the thumb, so reversing direction never needs a long drag back
1106
+ S.joy.ox = e.clientX - vx / L * R; S.joy.oy = e.clientY - vy / L * R;
1107
+ vx = vx / L * R; vy = vy / L * R;
1108
+ }
1109
  S.joy.vx = vx; S.joy.vy = vy; S.joy.cx = S.joy.ox + vx; S.joy.cy = S.joy.oy + vy;
1110
  }
1111
  });
1112
  const up = (e) => {
1113
  if (!S.ptr || e.pointerId !== S.ptr.id) return;
1114
  if (!S.joy.on && now() - S.ptr.t0 < 0.45) onTap(e.clientX, e.clientY);
1115
+ S.joy.on = false; S.joy.act = false; S.joy.vx = S.joy.vy = S.joy.sx = S.joy.sy = 0; S.ptr = null;
1116
  };
1117
  cv.addEventListener('pointerup', up);
1118
  cv.addEventListener('pointercancel', up);
sw.js CHANGED
@@ -2,7 +2,7 @@
2
  // The Space serves assets with "Cache-Control: no-store", so the browser HTTP cache never keeps them.
3
  // Cache Storage (used here) is not affected by that header, so images are downloaded once per build.
4
  // The two placeholders below (build id and asset list) are filled in by tools/inline_build.py.
5
- const BUILD = '43cf7f987b8c';
6
  const SHELL = 'flare-shell-' + BUILD; // index.html (network first, cache fallback)
7
  const ASSETS = 'flare-assets-' + BUILD; // sprites, tiles, icons (cache-first)
8
  const KEEP = [SHELL, ASSETS];
 
2
  // The Space serves assets with "Cache-Control: no-store", so the browser HTTP cache never keeps them.
3
  // Cache Storage (used here) is not affected by that header, so images are downloaded once per build.
4
  // The two placeholders below (build id and asset list) are filled in by tools/inline_build.py.
5
+ const BUILD = '0c47d0b01e9d';
6
  const SHELL = 'flare-shell-' + BUILD; // index.html (network first, cache fallback)
7
  const ASSETS = 'flare-assets-' + BUILD; // sprites, tiles, icons (cache-first)
8
  const KEEP = [SHELL, ASSETS];