@umicat/three-sdk 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/GameAudio.js CHANGED
@@ -230,6 +230,19 @@ export class GameAudio {
230
230
  const ctx = this.ctx;
231
231
  if (this.muted)
232
232
  return;
233
+ // The music may still be waiting for a gesture even though the context is
234
+ // not. `start()` is the only other place that starts it, and it early-exits
235
+ // when the context is ALREADY running — which is what happens in a
236
+ // cross-origin iframe, where the context comes up running and the first
237
+ // `play()` therefore never takes the unlock path. Measured: a game
238
+ // embedded that way had a live context, decoded buffers, working effects
239
+ // and no music at all, because `startMusic()` had never been reached.
240
+ //
241
+ // `play()` is the right place for the second attempt: it is called from
242
+ // inside a real gesture (a button, a move), which is exactly the
243
+ // permission an element needs, and `startMusic` is idempotent.
244
+ if (this.ready && this.musicName && !this.musicEl)
245
+ this.startMusic();
233
246
  if (!this.ready || !ctx || !this.master) {
234
247
  // Not "drop it": this is the unlocking gesture, and the sound it asked
235
248
  // for is the one the player is waiting to hear. `start()` plays it.
package/dist/Input3D.js CHANGED
@@ -409,10 +409,9 @@ export class Input3D {
409
409
  transform: 'translate(-50%, -50%)', pointerEvents: 'none',
410
410
  });
411
411
  pad.appendChild(knob);
412
- // ONE cluster holds every button, laid out by flexbox, because the bug this
413
- // replaces was two buttons independently choosing "bottom right" and
414
- // landing on top of each other. Jump sits at the corner where it always
415
- // was; actions stack to its left and wrap upward.
412
+ // ONE cluster holds every button, because the bug this replaces was two of
413
+ // them independently choosing "bottom right" and landing on top of each
414
+ // other. What it does with them is `layOnArc` below.
416
415
  const cluster = document.createElement('div');
417
416
  Object.assign(cluster.style, {
418
417
  position: 'absolute', right: '6vmin', bottom: '7vmin',
@@ -420,6 +419,53 @@ export class Input3D {
420
419
  flexWrap: 'wrap-reverse', justifyContent: 'flex-start',
421
420
  gap: '3vmin', maxWidth: '52vmin', pointerEvents: 'none',
422
421
  });
422
+ /**
423
+ * Put the buttons on the arc a THUMB actually sweeps.
424
+ *
425
+ * They were a wrapping flex row, which is how they ended up in a straight
426
+ * line across the bottom of the screen the moment a game had exactly two —
427
+ * the third button had been the only thing forcing a wrap, so removing it
428
+ * silently changed the layout of the two that were left. A row is also the
429
+ * wrong shape regardless: a thumb is hinged at the bottom corner and moves
430
+ * on an arc around it, so buttons at the same HEIGHT are at different
431
+ * reaches, and the far one is the one that gets missed.
432
+ *
433
+ * So: a quarter circle centred on the corner. Index 0 sits low and to the
434
+ * left, and each one after it steps up and in toward the edge, every one of
435
+ * them the same distance from the thumb's hinge.
436
+ *
437
+ * The radius is DERIVED, not chosen: neighbours must be at least a button
438
+ * apart along the chord, so a wider sweep — which is what more buttons
439
+ * need — pushes the arc out. Past three the radius it asks for has the
440
+ * buttons halfway up the screen, so the old wrapping row is kept for that
441
+ * case rather than pretending an arc still fits.
442
+ */
443
+ const layOnArc = (els) => {
444
+ const S = 20; // vmin — the button size, below
445
+ const [A0, A1] = [16, 74]; // degrees, the comfortable sweep
446
+ if (els.length > 3)
447
+ return; // the flex row stays as it is
448
+ const step = els.length > 1 ? (A1 - A0) / (els.length - 1) : 0;
449
+ // Chord between neighbours = 2R·sin(step/2), and it may not be smaller
450
+ // than a button plus a little air.
451
+ const need = els.length > 1 ? (S * 1.12) / (2 * Math.sin((step * Math.PI) / 360)) : S * 1.3;
452
+ const R = Math.max(S * 1.3, need);
453
+ cluster.style.display = 'block';
454
+ cluster.style.width = '0';
455
+ cluster.style.height = '0';
456
+ els.forEach((el, i) => {
457
+ const t = ((A0 + step * i) * Math.PI) / 180;
458
+ // From the corner, in vmin, then back off by half a button so the
459
+ // ARC passes through each button's middle rather than its corner.
460
+ const dx = (R * Math.cos(t)).toFixed(2);
461
+ const dy = (R * Math.sin(t)).toFixed(2);
462
+ Object.assign(el.style, {
463
+ position: 'absolute',
464
+ right: `calc(${dx}vmin - min(${S / 2}vmin, 60px))`,
465
+ bottom: `calc(${dy}vmin - min(${S / 2}vmin, 60px))`,
466
+ });
467
+ });
468
+ };
423
469
  /** Returns the button; its glyph, if it has one, is left in `glyph`. */
424
470
  let glyph = null;
425
471
  const makeButton = (label, icon) => {
@@ -480,11 +526,13 @@ export class Input3D {
480
526
  ...this.actions.map((a) => ({ id: a.id, label: a.label ?? a.id.slice(0, 1).toUpperCase(), icon: a.icon })),
481
527
  ];
482
528
  let jumpBtn = null;
529
+ const made = [];
483
530
  for (const b of buttons) {
484
531
  const el = makeButton(b.label, b.icon);
485
532
  if (glyph)
486
533
  this.glyphs.set(b.id, glyph);
487
534
  cluster.append(el);
535
+ made.push(el);
488
536
  // Jump is the platform's own: its press is read through `get jump()`
489
537
  // rather than through the action latch, so it is wired below instead.
490
538
  if (b.id === 'jump')
@@ -492,6 +540,7 @@ export class Input3D {
492
540
  else
493
541
  this.wireButton(el, b.id);
494
542
  }
543
+ layOnArc(made);
495
544
  root.append(zone, lookZone, pad, cluster);
496
545
  container.appendChild(root);
497
546
  this.root = root;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@umicat/three-sdk",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "description": "Three.js runtime for Umicat games: the scene3d design format, its loader with physics, a kinematic character controller, and the Umicat platform via @umicat/platform-sdk.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",