melonjs 20.13.0 → 20.14.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.
Files changed (120) hide show
  1. package/README.md +65 -22
  2. package/build/application/application.d.ts +6 -1
  3. package/build/application/application.d.ts.map +1 -1
  4. package/build/application/header.d.ts.map +1 -1
  5. package/build/application/resize.d.ts.map +1 -1
  6. package/build/application/settings.d.ts +13 -8
  7. package/build/application/settings.d.ts.map +1 -1
  8. package/build/audio/audio.d.ts +8 -4
  9. package/build/audio/audio.d.ts.map +1 -1
  10. package/build/audio/backend/engine.d.ts.map +1 -1
  11. package/build/audio/playback.d.ts +2 -2
  12. package/build/audio/playback.d.ts.map +1 -1
  13. package/build/audio/spatial.d.ts.map +1 -1
  14. package/build/audio/types.d.ts +15 -5
  15. package/build/audio/types.d.ts.map +1 -1
  16. package/build/camera/camera2d.d.ts +10 -9
  17. package/build/camera/camera2d.d.ts.map +1 -1
  18. package/build/camera/camera3d.d.ts +11 -8
  19. package/build/camera/camera3d.d.ts.map +1 -1
  20. package/build/geometries/path2d.d.ts +9 -8
  21. package/build/geometries/path2d.d.ts.map +1 -1
  22. package/build/index.d.ts +1 -1
  23. package/build/index.d.ts.map +1 -1
  24. package/build/index.js +40993 -39738
  25. package/build/index.js.map +4 -4
  26. package/build/input/keyboard.d.ts.map +1 -1
  27. package/build/input/pointerevent.d.ts.map +1 -1
  28. package/build/level/gltf/GLTFModel.d.ts.map +1 -1
  29. package/build/level/level.d.ts.map +1 -1
  30. package/build/level/tiled/TMXLayer.d.ts +2 -1
  31. package/build/level/tiled/TMXLayer.d.ts.map +1 -1
  32. package/build/level/tiled/TMXTileMap.d.ts +13 -3
  33. package/build/level/tiled/TMXTileMap.d.ts.map +1 -1
  34. package/build/level/tiled/TMXTileset.d.ts.map +1 -1
  35. package/build/level/tiled/renderer/TMXHexagonalRenderer.d.ts.map +1 -1
  36. package/build/level/tiled/renderer/TMXIsometricRenderer.d.ts.map +1 -1
  37. package/build/level/tiled/renderer/TMXObliqueRenderer.d.ts +1 -0
  38. package/build/level/tiled/renderer/TMXObliqueRenderer.d.ts.map +1 -1
  39. package/build/level/tiled/renderer/TMXRenderer.d.ts +7 -3
  40. package/build/level/tiled/renderer/TMXRenderer.d.ts.map +1 -1
  41. package/build/lighting/light3d.d.ts +55 -0
  42. package/build/lighting/light3d.d.ts.map +1 -1
  43. package/build/loader/cache.d.ts +146 -97
  44. package/build/loader/cache.d.ts.map +1 -1
  45. package/build/loader/internal-types.d.ts +69 -0
  46. package/build/loader/internal-types.d.ts.map +1 -0
  47. package/build/loader/loader.d.ts +584 -292
  48. package/build/loader/loader.d.ts.map +1 -1
  49. package/build/loader/parsers/audio.d.ts.map +1 -1
  50. package/build/loader/parsers/fontface.d.ts.map +1 -1
  51. package/build/loader/parsers/gltf.d.ts.map +1 -1
  52. package/build/loader/parsers/tmx.d.ts.map +1 -1
  53. package/build/math/color.d.ts +4 -0
  54. package/build/math/color.d.ts.map +1 -1
  55. package/build/math/noise.d.ts +16 -16
  56. package/build/math/noise.d.ts.map +1 -1
  57. package/build/physics/adapter.d.ts +64 -25
  58. package/build/physics/adapter.d.ts.map +1 -1
  59. package/build/physics/builtin/body.d.ts +9 -0
  60. package/build/physics/builtin/body.d.ts.map +1 -1
  61. package/build/physics/builtin/builtin-adapter.d.ts +25 -3
  62. package/build/physics/builtin/builtin-adapter.d.ts.map +1 -1
  63. package/build/physics/builtin/detector.d.ts.map +1 -1
  64. package/build/physics/builtin/raycast.d.ts.map +1 -1
  65. package/build/physics/world.d.ts +40 -10
  66. package/build/physics/world.d.ts.map +1 -1
  67. package/build/renderable/container.d.ts +1 -0
  68. package/build/renderable/container.d.ts.map +1 -1
  69. package/build/renderable/draggable.d.ts.map +1 -1
  70. package/build/renderable/frameAnimation.d.ts.map +1 -1
  71. package/build/renderable/instanced_mesh.d.ts +16 -14
  72. package/build/renderable/instanced_mesh.d.ts.map +1 -1
  73. package/build/renderable/mesh.d.ts +62 -5
  74. package/build/renderable/mesh.d.ts.map +1 -1
  75. package/build/renderable/trigger.d.ts +5 -0
  76. package/build/renderable/trigger.d.ts.map +1 -1
  77. package/build/renderable/ui/uibaseelement.d.ts +8 -1
  78. package/build/renderable/ui/uibaseelement.d.ts.map +1 -1
  79. package/build/renderable/ui/uicheckbox.d.ts.map +1 -1
  80. package/build/system/device.d.ts +11 -2
  81. package/build/system/device.d.ts.map +1 -1
  82. package/build/system/fullscreen.d.ts.map +1 -1
  83. package/build/system/pool.d.ts.map +1 -1
  84. package/build/utils/utils.d.ts.map +1 -1
  85. package/build/video/canvas/canvas_renderer.d.ts +16 -14
  86. package/build/video/canvas/canvas_renderer.d.ts.map +1 -1
  87. package/build/video/canvas_factory.d.ts.map +1 -1
  88. package/build/video/effects/shadereffect.d.ts +127 -0
  89. package/build/video/effects/shadereffect.d.ts.map +1 -1
  90. package/build/video/renderer.d.ts.map +1 -1
  91. package/build/video/webgl/batchers/lit_mesh_batcher.d.ts.map +1 -1
  92. package/build/video/webgl/webgl_renderer.d.ts.map +1 -1
  93. package/build/video/webgpu/batchers/lit_mesh_batcher.d.ts.map +1 -1
  94. package/build/video/webgpu/webgpu_renderer.d.ts.map +1 -1
  95. package/package.json +1 -1
  96. package/skills/melonjs/SKILL.md +38 -14
  97. package/skills/melonjs-20-migration/SKILL.md +50 -7
  98. package/skills/melonjs-3d/SKILL.md +138 -38
  99. package/skills/melonjs-3d-assets/SKILL.md +83 -33
  100. package/skills/melonjs-audio/SKILL.md +55 -14
  101. package/skills/melonjs-camera-and-drawing/SKILL.md +95 -8
  102. package/skills/melonjs-deployment/SKILL.md +92 -14
  103. package/skills/melonjs-display-and-scaling/SKILL.md +8 -3
  104. package/skills/melonjs-effects-and-shaders/SKILL.md +46 -15
  105. package/skills/melonjs-events/SKILL.md +3 -2
  106. package/skills/melonjs-getting-started/SKILL.md +5 -0
  107. package/skills/melonjs-input/SKILL.md +65 -15
  108. package/skills/melonjs-lighting/SKILL.md +13 -1
  109. package/skills/melonjs-loading-assets/SKILL.md +202 -27
  110. package/skills/melonjs-math/SKILL.md +12 -5
  111. package/skills/melonjs-particles-and-trails/SKILL.md +7 -3
  112. package/skills/melonjs-performance/SKILL.md +18 -8
  113. package/skills/melonjs-physics/SKILL.md +385 -127
  114. package/skills/melonjs-plugins/SKILL.md +45 -11
  115. package/skills/melonjs-renderables/SKILL.md +24 -7
  116. package/skills/melonjs-renderer-backends/SKILL.md +38 -17
  117. package/skills/melonjs-scenes-and-state/SKILL.md +30 -5
  118. package/skills/melonjs-sprites-and-animation/SKILL.md +23 -2
  119. package/skills/melonjs-tilemaps/SKILL.md +56 -18
  120. package/skills/melonjs-ui-and-text/SKILL.md +120 -25
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: melonjs-ui-and-text
3
- description: "Use this skill for HUDs, buttons, menus, options screens, dialogue panels, progress bars and on-screen text in melonJS. Covers UIBaseElement/UISpriteElement/UITextButton, UICheckbox, UISlider, ProgressBar, UIPanel (skins, layout, scrolling), Draggable and DropTarget, the floating screen-space container pattern, which of two overlapping panels gets the pointer, Text and BitmapText, web font loading, and NineSliceSprite panels. Triggers on: UI, HUD, button, checkbox, radio button, UICheckbox, slider, UISlider, volume slider, options menu, settings screen, scrolling list, scroll panel, layout, column, grid, UI skin, UIBaseElement, UISpriteElement, UITextButton, ProgressBar, progress bar, health bar, gauge, Draggable, DropTarget, menu, dialogue, overlapping panels, moveToTop, onOver, onClick, bindKey, keyboard shortcut, button keyboard, Tween, easing, animate a label, wavy text, per-glyph effect, glyphEffect, shake text, rainbow text, Text, BitmapText, fillStyle, fillGradient, gradient text, tint, font, fontface, wordWrapWidth, NineSliceSprite, score display, floating., fullscreen, full screen, requestFullscreen, exitFullscreen, isFullscreen, fullscreen button, fullscreen option"
3
+ description: "Use this skill for HUDs, buttons, menus, options screens, dialogue panels, progress bars and on-screen text in melonJS. Covers UIBaseElement/UISpriteElement/UITextButton, UICheckbox, UISlider, ProgressBar, UIPanel (skins, layout, scrolling), keyboard and gamepad focus, Draggable and DropTarget, the floating screen-space container pattern, which of two overlapping panels gets the pointer, Text and BitmapText, web font loading, and NineSliceSprite panels. Triggers on: UI, HUD, button, checkbox, radio button, slider, volume slider, options menu, settings screen, scrolling list, UI skin, progress bar, health bar, menu, dialogue, moveToTop, onOver, onClick, bindKey, keyboard shortcut, Tween, animate a label, wavy text, glyphEffect, rainbow text, typewriter, visibleRatio, drag and drop, isDraggable, focus, fillStyle, fillGradient, gradient text, tint, font, fontface, wordWrapWidth, score display, fullscreen, requestFullscreen, fullscreen button."
4
4
  license: MIT
5
5
  ---
6
6
 
@@ -204,6 +204,26 @@ const help = new Text(8, 8, {
204
204
  // "CONTROLS" on its own line, then the sentence wrapped under it
205
205
  ```
206
206
 
207
+ ### Typewriter reveal: `visibleRatio`
208
+
209
+ `visibleCharacters` (`-1`, the default, shows them all) and `visibleRatio`
210
+ (0 to 1) show a label a character at a time, on `Text` and `BitmapText` alike.
211
+ Tween the ratio: it takes the same time whatever the length of the string.
212
+
213
+ ```js
214
+ line.setText("The door is locked. The keeper has the key.");
215
+ line.visibleRatio = 0;
216
+ new Tween(line).to({ visibleRatio: 1 }, { duration: 1500 }).start();
217
+ ```
218
+
219
+ Line breaks are not counted, and the wrapping and the bounds are those of the
220
+ whole string, so a word does not jump to the next line as it appears. The two
221
+ classes differ in two ways. A `BitmapText` keeps each glyph where the full line
222
+ puts it, while a centred or right-aligned `Text` aligns only the part shown so
223
+ far, so it slides sideways as it types: left-align a `Text` you reveal. And a
224
+ `Text` repaints and re-uploads its bake at every step, where a `BitmapText`
225
+ just draws fewer glyphs.
226
+
207
227
  ### `fillStyle` means two different things
208
228
 
209
229
  The name is shared and the behaviour is not, which is the trap when moving a
@@ -291,10 +311,13 @@ an amplitude around an eighth of it, a wavelength around ten times it, and a
291
311
  little under 5 radians per second. For a shake, re-target each glyph roughly
292
312
  20 to 40 times a second.
293
313
 
294
- `ctx.time` starts when the effect is **assigned**, so attach it at the moment
295
- you want to be t=0 — on the frame the hit lands, not when the label was built.
296
- A label reused across lives carries the old clock, so a decay attached once
297
- will already be spent the second time.
314
+ `ctx.time` runs only while an effect is set and nothing resets it, so it is 0
315
+ only when a label gets its **first** effect. A label that already had one, or
316
+ is reused across lives, carries the old clock, and a decay measured from
317
+ `ctx.time` itself is already spent there. Note `ctx.time` on the effect's first
318
+ call and measure from that, as the shake below does, and attach it at the
319
+ moment you want to be t=0: on the frame the hit lands, not when the label was
320
+ built.
298
321
 
299
322
  Both, whole:
300
323
 
@@ -322,22 +345,28 @@ const angle = (i, tick) => {
322
345
  const h = Math.sin(i * 127.1 + tick * 311.7) * 43758.5453;
323
346
  return (h - Math.floor(h)) * Math.PI * 2;
324
347
  };
325
- const impact = (amplitude, durationMs, rate) => (out, ctx) => {
326
- const left = 1 - ctx.time / durationMs;
327
- if (left <= 0) {
328
- return; // settled; `out` is already zeroed
329
- }
330
- const amp = amplitude * left * left;
331
- const tick = (ctx.time / 1000) * rate;
332
- const held = Math.floor(tick);
333
- const k = tick - held;
334
- const a = angle(ctx.index, held);
335
- const b = angle(ctx.index, held + 1);
336
- out.offsetX = Math.round((Math.sin(a) + (Math.sin(b) - Math.sin(a)) * k) * amp);
337
- out.offsetY = Math.round((Math.cos(a) + (Math.cos(b) - Math.cos(a)) * k) * amp);
348
+ const impact = (amplitude, durationMs, rate) => {
349
+ let start; // ctx.time on the first call
350
+ return (out, ctx) => {
351
+ start ??= ctx.time; // the label's clock never resets
352
+ const t = ctx.time - start;
353
+ const left = 1 - t / durationMs;
354
+ if (left <= 0) {
355
+ return; // settled; `out` is already zeroed
356
+ }
357
+ const amp = amplitude * left * left;
358
+ const tick = (t / 1000) * rate;
359
+ const held = Math.floor(tick);
360
+ const k = tick - held;
361
+ const a = angle(ctx.index, held);
362
+ const b = angle(ctx.index, held + 1);
363
+ out.offsetX = Math.round((Math.sin(a) + (Math.sin(b) - Math.sin(a)) * k) * amp);
364
+ out.offsetY = Math.round((Math.cos(a) + (Math.cos(b) - Math.cos(a)) * k) * amp);
365
+ };
338
366
  };
339
367
 
340
- // assigned at the moment of the hit, which is what makes ctx.time start here
368
+ // a new impact at the moment of each hit: it measures from its own first call,
369
+ // so it shakes again on a banner that has shown one before
341
370
  banner.setText("CAPSIZED!");
342
371
  banner.glyphEffect = impact(3, 650, 24);
343
372
  ```
@@ -462,6 +491,13 @@ hp.max = 50; // re-clamps the value into the new range
462
491
  `direction` takes `"left-to-right"` (the default), `"right-to-left"`,
463
492
  `"top-to-bottom"` or `"bottom-to-top"`, each growing from its own edge.
464
493
 
494
+ `showLabel: true` writes text centred over the bar, a percentage by default:
495
+ `` labelFormat: (value, ratio) => `${Math.round(value)} HP` `` changes what it says,
496
+ and `font` (a CSS family), `fontSize` and `labelFillStyle` how it looks.
497
+ `onChange(value, ratio, bar)` is called on every change of the value, from code
498
+ too, unlike a checkbox's or a slider's: a bar has no user input to tell apart.
499
+ Setting the value it already has calls nothing.
500
+
465
501
  **The colour follows the value by mutating the `Color` you passed.** This is
466
502
  the one non-obvious part, and it is deliberate: every game wants a different
467
503
  rule, and most want the colour somewhere else too, next to a shader uniform or
@@ -667,7 +703,9 @@ scrolls with the wheel over the panel and by dragging, with momentum when
667
703
  flung. A scrollbar shows while it does not fit. `scrollTo(x, y)`,
668
704
  `scrollToChild(child)`, `scrollX` / `scrollY`, `maxScrollX` / `maxScrollY` and
669
705
  an `onScroll(x, y, panel)` setting are there to drive it from code. Scrolling
670
- from code stops a fling that is still coasting.
706
+ from code stops a fling that is still coasting. `clearChildren()` empties the
707
+ list and scrolls back to the start, ready to be filled again, and `autoSize`
708
+ is ignored on a scrolling panel.
671
709
 
672
710
  What to know:
673
711
 
@@ -717,7 +755,7 @@ by default):
717
755
  | `onClick(event)` | pressed | `false` to stop the event propagating |
718
756
  | `onRelease(event)` | the press ends (up, left while held, or cancelled) | `false` to stop propagating |
719
757
  | `onOver(event)` | pointer enters | `false` to stop propagating |
720
- | `onOut(event)` | pointer leaves | — |
758
+ | `onOut(event)` | pointer leaves, or the element is removed while hovered (no event then) | — |
721
759
  | `onHold()` | pressed and held | — |
722
760
 
723
761
  ```js
@@ -736,6 +774,12 @@ class MuteButton extends UISpriteElement {
736
774
  }
737
775
  ```
738
776
 
777
+ Undo in `onOut` whatever `onOver` set up (a tooltip, a highlight, a cursor):
778
+ it also runs when the element leaves the world while the pointer is on it,
779
+ removed on its own, with the panel holding it, or with the scene, so nothing
780
+ is left behind. Keep effects that should only follow the pointer (a sound, a
781
+ tween) out of it, or check `event`, which is `undefined` for a removal.
782
+
739
783
  The image is `button.sprite`: animations, tint and regions live there
740
784
  (`setRegion`, `addAnimation`, `setCurrentAnimation` and `isCurrentAnimation`
741
785
  are also on the element as shortcuts). As a `UIBaseElement` it has
@@ -777,7 +821,11 @@ as before, for subclasses.
777
821
 
778
822
  `font` is a loaded bitmap font's name, drawn with `BitmapText` as before, or
779
823
  any other name, drawn as a CSS font with `Text`. The label is
780
- `button.label` (`bitmapText` is the old name for it).
824
+ `button.label` (`bitmapText`, its old name, is deprecated since 20.13.0).
825
+
826
+ With no skin it is a rounded rectangle in `hoverOffColor`, `hoverOnColor`
827
+ under the pointer and `pressedColor` while held (`hoverOnColor` when left
828
+ out), outlined in `borderColor`.
781
829
 
782
830
  The button takes the same names as the other widgets: `width` and `height`
783
831
  for its size, `fontSize` (pixels) and `labelFillStyle` for its text,
@@ -1024,7 +1072,8 @@ from art: the track and fill as nine-slices along its length, the thumb at its
1024
1072
  own size.
1025
1073
 
1026
1074
  Put work that should not run 60 times a second, such as saving or playing a
1027
- test sound, in `onChangeEnd`.
1075
+ test sound, in `onChangeEnd`, which runs once on release and after each wheel
1076
+ or key step.
1028
1077
 
1029
1078
  ```js
1030
1079
  // whole numbers: a step of 1 shows "7", not "7.00"
@@ -1103,6 +1152,47 @@ Fullscreen does not resize a fixed-resolution game: with
1103
1152
  unchanged and only the canvas scales, so a HUD built from
1104
1153
  `renderer.width`/`height` needs no repositioning.
1105
1154
 
1155
+ ### Dragging: `isDraggable`, or `Draggable` and `DropTarget`
1156
+
1157
+ Any UI element follows the pointer while held once `isDraggable` is set:
1158
+ `onClick` is the pick-up, `onMove(event)` runs after each move with `pos`
1159
+ already following, and `onRelease` is the drop. It works by touch as by
1160
+ mouse, on a floating element as on one in the world. (`UIPanel` has its own
1161
+ `draggable` setting, above.)
1162
+
1163
+ ```js
1164
+ class Token extends UISpriteElement {
1165
+ constructor(x, y) {
1166
+ super(x, y, { image: atlas, region: "token" });
1167
+ this.isDraggable = true;
1168
+ }
1169
+ onRelease() {
1170
+ this.pos.x = Math.round(this.pos.x / 32) * 32; // snap to the grid
1171
+ this.pos.y = Math.round(this.pos.y / 32) * 32;
1172
+ return false;
1173
+ }
1174
+ }
1175
+ ```
1176
+
1177
+ When something else has to notice the drop, `Draggable` and `DropTarget` are
1178
+ the pair. Both are plain `Renderable`s, in the world unless you make them
1179
+ `floating`, that draw nothing until you give them a `draw()`. A `Draggable`
1180
+ follows the pointer while held (`dragStart(e)` / `dragEnd(e)` to override,
1181
+ calling `super`), and when it is let go every `DropTarget` its bounds overlap
1182
+ gets `drop(draggable)`; after `setCheckMethod(target.CHECKMETHOD_CONTAINS)`
1183
+ only one it lies wholly inside does.
1184
+
1185
+ ```js
1186
+ class Slot extends DropTarget {
1187
+ drop(card) {
1188
+ card.pos.set(this.pos.x, this.pos.y, card.pos.z); // settle it in
1189
+ }
1190
+ }
1191
+ ```
1192
+
1193
+ The drop is checked on the draggable's own pointer-up (`event.DRAGEND` comes
1194
+ from it), so the release has to land on the draggable itself.
1195
+
1106
1196
  ### `z` is container-local
1107
1197
 
1108
1198
  `addChild(child, z)` writes a depth that means something **only among that
@@ -1249,14 +1339,19 @@ City and Instanced Forest examples park a floating sky at `-10000` or `100000`.
1249
1339
  | the arrow keys or the pad do nothing in a menu | no widget has the focus: nothing listens until one does | call `firstWidget.focus()` when the menu opens |
1250
1340
  | the arrow keys stop moving the player after a menu | a widget still has the focus | `UIBaseElement.focusOwner?.blur()`, or remove the menu: a focused widget leaving the scene gives the keys back |
1251
1341
  | a `bindKey: ENTER` button does not fire while another widget is focused | Enter and Space go to the focused widget, ring shown or not, so one press never clicks both | focus that button, `blur()` the focused one, or bind another key |
1342
+ | a tooltip or highlight shown in `onOver` stays after its widget is removed (before 20.14) | the pointer's leave never reaches a removed widget | update: a widget removed while hovered now gets `onOut()`, with no event |
1343
+ | an `onOut` sound or tween plays when a menu closes or the scene changes (since 20.14) | a widget removed while hovered gets `onOut()` | keep pointer-only effects out of `onOut`, or skip them when its `event` is `undefined` |
1252
1344
  | a widget removed while held gets `onCancel()` and no `onRelease()` | the pointer-up never reaches a removed widget, so the press is called off (and `hover` cleared), leaving it at rest when added back | stop what `onClick()` started in `onCancel()` as well as in `onRelease()` |
1253
1345
  | a `ProgressBar` fill runs past its track after `max` is lowered (before 20.13) | `min` and `max` did not re-clamp the value | update: they re-clamp it now (and call `onChange` if it moved); `ratio` stays within 0..1 |
1254
1346
  | a skinned panel's shadow has square corners | the shadow follows `radius`, and a skin does not set it | set `radius` to the art's corner size |
1255
1347
  | a wrapped label lost its `\n` line breaks, or starts with a blank line (before 20.13) | word wrap joined the lines with spaces, and closed an empty line before an over-wide first word | update: each line is now wrapped on its own and an over-wide word starts the first line |
1256
1348
  | the second line of a multi-line `"bottom"` or `"middle"` `Text` is drawn below where you put it, or outside its bounds (before 20.13) | `Text` placed only the first line by its baseline | update: the whole block is placed by the baseline, as `BitmapText` does |
1257
- | `setText("1,000")` leaves a label showing `["1", "000"]` on two lines, or the other way round (before 20.13) | the change test compared `toString()`, and an array's joins with commas; update |
1258
- | a centred or right-aligned label jumps sideways, or is clipped, after `label.measureText("…")` (before 20.13) | measuring another string overwrote the label's own metrics until its next `setText()`; update, or measure on a label that is not displayed |
1349
+ | `setText("1,000")` leaves a label showing `["1", "000"]` on two lines, or the other way round (before 20.13) | the change test compared `toString()`, and an array's joins with commas | update |
1350
+ | a centred or right-aligned label jumps sideways, or is clipped, after `label.measureText("…")` (before 20.13) | measuring another string overwrote the label's own metrics until its next `setText()` | update, or measure on a label that is not displayed |
1259
1351
  | `new UITextButton` with a font name draws in a plain font | the name is not a loaded bitmap font, so it is drawn as a CSS font | load the bitmap font (image and `.fnt`) under that name, or load the web font |
1352
+ | `UI skin: no region named "…" in the atlas` thrown where a widget is made | every region a `skin` names is looked up when the widget is built | fix the name, or re-export the atlas with that image |
1353
+ | a typewriter reveal on a centred `Text` slides sideways as it types | a `Text` aligns only the part shown so far | left-align it, or reveal a `BitmapText`, which keeps each glyph in its final place |
1354
+ | a `glyphEffect` decay or intro plays the first time and never again | `ctx.time` runs while any effect is set and never resets, so a later effect starts late | note `ctx.time` on the effect's first call and measure from that |
1260
1355
 
1261
1356
  ## Related skills
1262
1357