littlejsengine 1.14.28 → 1.15.3

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.
@@ -8,6 +8,7 @@ function gameInit()
8
8
  uiSystem.defaultSoundPress = new Sound([.5,0,220]);
9
9
  uiSystem.defaultSoundClick = new Sound([.5,0,440]);
10
10
  uiSystem.defaultCornerRadius = 20;
11
+ uiSystem.defaultShadowColor = BLACK;
11
12
  canvasClearColor = hsl(.9,.3,.2);
12
13
 
13
14
  // setup music player UI
@@ -9,6 +9,7 @@ function gameInit()
9
9
  uiSystem.defaultSoundClick = new Sound([.5,0,440]);
10
10
  uiSystem.defaultCornerRadius = 20;
11
11
  uiSystem.defaultGradientColor = WHITE;
12
+ uiSystem.defaultShadowColor = BLACK;
12
13
  canvasClearColor = hsl(.9,.3,.2);
13
14
 
14
15
  // setup music player UI
@@ -28,6 +28,7 @@ class UISequencerButton extends UIButton
28
28
  this.cornerRadius = 0;
29
29
  this.dragActivate = true;
30
30
  this.isOn = false;
31
+ this.shadowColor = CLEAR_BLACK;
31
32
 
32
33
  // set instrument and color based on track
33
34
  const pianoStart = 2;
@@ -70,6 +71,7 @@ function gameInit()
70
71
  // initialize UI system
71
72
  new UISystemPlugin;
72
73
  uiSystem.defaultCornerRadius = 8;
74
+ uiSystem.defaultShadowColor = BLACK;
73
75
  canvasClearColor = hsl(0,0,.3);
74
76
 
75
77
  // create sequencer buttons
Binary file
@@ -2,6 +2,7 @@ function gameInit()
2
2
  {
3
3
  // initialize UI system
4
4
  new UISystemPlugin;
5
+ uiSystem.defaultShadowColor = BLACK;
5
6
  canvasClearColor = hsl(.6,.3,.2);
6
7
 
7
8
  // create a grid of buttons with different sounds
@@ -5,6 +5,7 @@ function gameInit()
5
5
  // setup ui system plugin
6
6
  new UISystemPlugin();
7
7
  uiSystem.defaultCornerRadius = 10;
8
+ uiSystem.defaultShadowColor = BLACK;
8
9
  canvasClearColor = hsl(.3,.3,.2);
9
10
 
10
11
  // create timer button
@@ -5,6 +5,7 @@ function gameInit()
5
5
  uiSystem.defaultSoundPress = new Sound([.5,0,220]);
6
6
  uiSystem.defaultSoundClick = new Sound([.5,0,440]);
7
7
  uiSystem.defaultCornerRadius = 8;
8
+ uiSystem.defaultShadowColor = BLACK;
8
9
 
9
10
  // example button that returns to menu
10
11
  const buttonBack = new UIButton(mainCanvasSize.scale(.5), vec2(200),
Binary file
@@ -0,0 +1,34 @@
1
+ function gameInit()
2
+ {
3
+ new UISystemPlugin;
4
+ uiSystem.defaultCornerRadius = 20;
5
+ uiSystem.defaultShadowColor = BLACK;
6
+ canvasClearColor = hsl(0,0,.1);
7
+
8
+ // video player
9
+ const center = mainCanvasSize.scale(.5);
10
+ const videoPos = center.add(vec2(0, -50));
11
+ const videoSize = vec2(480, 270);
12
+ const filename = 'video.webm';
13
+ const autoplay = true;
14
+ videoPlayer = new UIVideo(videoPos, videoSize, filename, autoplay);
15
+ videoPlayer.lineWidth = 5;
16
+ videoPlayer.lineColor = WHITE;
17
+
18
+ // play/pause button
19
+ const buttonSize = vec2(200, 100);
20
+ const playButtonPos = center.add(vec2(-130, 170));
21
+ const playButton = new UIButton(playButtonPos, buttonSize);
22
+ playButton.onClick = ()=> videoPlayer.isPaused() ?
23
+ videoPlayer.play() : videoPlayer.pause();
24
+ playButton.onUpdate = ()=> playButton.text =
25
+ videoPlayer.isPaused() ? 'Play' : 'Pause';
26
+
27
+ // status text
28
+ const statusTextPos = center.add(vec2(130, 170));
29
+ const statusText = new UIText(statusTextPos, vec2(250, 90));
30
+ statusText.textColor = WHITE;
31
+ statusText.onUpdate = ()=> statusText.text =
32
+ videoPlayer.hasEnded() ? 'Ended' :
33
+ videoPlayer.isPlaying() ? 'Playing' : 'Paused';
34
+ }
@@ -7,9 +7,10 @@
7
7
  </head><body>
8
8
 
9
9
  <!-- LittleJS Engine -->
10
- <script src=../../dist/littlejs.js?1.15.0></script>
10
+ <script src=../../dist/littlejs.js?1.15.3></script>
11
11
 
12
12
  <!-- LittleJS Engine Source
13
+ <script src=../../src/engine.js></script>
13
14
  <script src=../../src/engineDebug.js></script>
14
15
  <script src=../../src/engineUtilities.js></script>
15
16
  <script src=../../src/engineSettings.js></script>
@@ -21,7 +22,6 @@
21
22
  <script src=../../src/engineParticles.js></script>
22
23
  <script src=../../src/engineMedals.js></script>
23
24
  <script src=../../src/engineWebGL.js></script>
24
- <script src=../../src/engine.js></script>
25
25
  <script src=../../plugins/box2d.js></script>
26
26
  <script src=../../plugins/box2d.wasm.js></script>
27
27
  <script src=../../plugins/drawUtilities.js></script>
@@ -31,4 +31,4 @@
31
31
  -->
32
32
 
33
33
  <!-- Add your game scripts here -->
34
- <script src=game.js?1.15.0></script>
34
+ <script src=game.js?1.15.3></script>
@@ -25,6 +25,7 @@ function createUI()
25
25
  LJS.uiSystem.defaultSoundClick = new LJS.Sound([.5,0,440]);
26
26
  LJS.uiSystem.defaultCornerRadius = 8;
27
27
  LJS.uiSystem.defaultGradientColor = LJS.WHITE;
28
+ LJS.uiSystem.defaultShadowColor = LJS.BLACK;
28
29
 
29
30
  // setup root to attach all ui elements to
30
31
  uiRoot = new LJS.UIObject;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "littlejsengine",
3
- "version": "1.14.28",
3
+ "version": "1.15.3",
4
4
  "description": "LittleJS - Tiny and Fast HTML5 Game Engine",
5
5
  "main": "dist/littlejs.esm.js",
6
6
  "types": "dist/littlejs.d.ts",
@@ -25,6 +25,7 @@ export
25
25
  UIButton,
26
26
  UICheckbox,
27
27
  UIScrollbar,
28
+ UIVideo,
28
29
 
29
30
  // Box2D Physics
30
31
  box2d,
@@ -36,6 +36,7 @@ class UISystemPlugin
36
36
  ASSERT(!uiSystem, 'UI system already initialized');
37
37
  uiSystem = this;
38
38
 
39
+ // default settings
39
40
  /** @property {Color} - Default fill color for UI elements */
40
41
  this.defaultColor = WHITE;
41
42
  /** @property {Color} - Default outline color for UI elements */
@@ -64,6 +65,13 @@ class UISystemPlugin
64
65
  this.defaultSoundRelease = undefined;
65
66
  /** @property {Sound} - Default sound when interactive UI element is clicked */
66
67
  this.defaultSoundClick = undefined;
68
+ /** @property {Color} - Color for shadow */
69
+ this.defaultShadowColor = CLEAR_BLACK;
70
+ /** @property {number} - Size of shadow blur */
71
+ this.defaultShadowBlur = 5;
72
+ /** @property {Vector2} - Offset of shadow blur */
73
+ this.defaultShadowOffset = vec2(5);
74
+ // system state
67
75
  /** @property {Array<UIObject>} - List of all UI elements */
68
76
  this.uiObjects = [];
69
77
  /** @property {CanvasRenderingContext2D|OffscreenCanvasRenderingContext2D} - Context to render UI elements to */
@@ -108,11 +116,16 @@ class UISystemPlugin
108
116
  // reset hover object at start of update
109
117
  uiSystem.lastHoverObject = uiSystem.hoverObject;
110
118
  uiSystem.hoverObject = undefined;
119
+
120
+ // update in reverse order so topmost objects get priority
111
121
  for (let i = uiSystem.uiObjects.length; i--;)
112
122
  {
113
123
  const o = uiSystem.uiObjects[i];
114
124
  o.parent || updateObject(o)
115
125
  }
126
+
127
+ // remove destroyed objects
128
+ uiSystem.uiObjects = uiSystem.uiObjects.filter(o=>!o.destroyed);
116
129
  }
117
130
  function uiRender()
118
131
  {
@@ -149,8 +162,11 @@ class UISystemPlugin
149
162
  * @param {number} [lineWidth]
150
163
  * @param {Color} [lineColor]
151
164
  * @param {number} [cornerRadius]
152
- * @param {Color} [gradientColor] */
153
- drawRect(pos, size, color=WHITE, lineWidth=0, lineColor=BLACK, cornerRadius=0, gradientColor)
165
+ * @param {Color} [gradientColor]
166
+ * @param {Color} [shadowColor]
167
+ * @param {number} [shadowBlur]
168
+ * @param {Color} [shadowOffset] */
169
+ drawRect(pos, size, color=WHITE, lineWidth=0, lineColor=BLACK, cornerRadius=0, gradientColor, shadowColor=BLACK, shadowBlur=0, shadowOffset=vec2())
154
170
  {
155
171
  ASSERT(isVector2(pos), 'pos must be a vec2');
156
172
  ASSERT(isVector2(size), 'size must be a vec2');
@@ -172,12 +188,22 @@ class UISystemPlugin
172
188
  }
173
189
  else
174
190
  context.fillStyle = color.toString();
191
+ if (shadowBlur || shadowOffset.x || shadowOffset.y)
192
+ if (shadowColor.a > 0)
193
+ {
194
+ // setup shadow
195
+ context.shadowColor = shadowColor.toString();
196
+ context.shadowBlur = shadowBlur;
197
+ context.shadowOffsetX = shadowOffset.x;
198
+ context.shadowOffsetY = shadowOffset.y;
199
+ }
175
200
  context.beginPath();
176
201
  if (cornerRadius && context['roundRect'])
177
202
  context['roundRect'](pos.x-size.x/2, pos.y-size.y/2, size.x, size.y, cornerRadius);
178
203
  else
179
204
  context.rect(pos.x-size.x/2, pos.y-size.y/2, size.x, size.y);
180
205
  context.fill();
206
+ context.shadowColor = '#0000'
181
207
  if (lineWidth)
182
208
  {
183
209
  context.strokeStyle = lineColor.toString();
@@ -282,6 +308,15 @@ class UISystemPlugin
282
308
  p.x -= sInv*mainCanvasSize.x/2;
283
309
  return p;
284
310
  }
311
+
312
+ /** Destroy and remove all objects
313
+ * @memberof Engine */
314
+ destroyObjects()
315
+ {
316
+ for (const o of this.uiObjects)
317
+ o.parent || o.destroy();
318
+ this.uiObjects = this.uiObjects.filter(o=>!o.destroyed);
319
+ }
285
320
  }
286
321
 
287
322
  ///////////////////////////////////////////////////////////////////////////////
@@ -357,6 +392,12 @@ class UIObject
357
392
  this.dragActivate = false;
358
393
  /** @property {boolean} - True if this can be a hover object */
359
394
  this.canBeHover = true;
395
+ /** @property {Color} - Color for shadow, undefined if no shadow */
396
+ this.shadowColor = uiSystem.defaultShadowColor?.copy();
397
+ /** @property {number} - Size of shadow blur */
398
+ this.shadowBlur = uiSystem.defaultShadowBlur;
399
+ /** @property {Vector2} - Offset of shadow blur */
400
+ this.shadowOffset = uiSystem.defaultShadowOffset.copy();
360
401
  uiSystem.uiObjects.push(this);
361
402
 
362
403
  /** @property {Vector2} - How much to offset the text shadow or undefined */
@@ -383,6 +424,22 @@ class UIObject
383
424
  child.parent = undefined;
384
425
  }
385
426
 
427
+
428
+ /** Destroy this object, destroy its children, detach its parent, and mark it for removal */
429
+ destroy()
430
+ {
431
+ if (this.destroyed)
432
+ return;
433
+
434
+ // disconnect from parent and destroy children
435
+ this.destroyed = 1;
436
+ this.parent && this.parent.removeChild(this);
437
+ for (const child of this.children)
438
+ {
439
+ child.parent = 0;
440
+ child.destroy();
441
+ }
442
+ }
386
443
  /** Check if the mouse is overlapping a box in screen space
387
444
  * @return {boolean} - True if overlapping
388
445
  */
@@ -458,7 +515,7 @@ class UIObject
458
515
 
459
516
  const lineColor = this.interactive && this.isActiveObject() && !this.disabled ? this.color : this.lineColor;
460
517
  const color = this.disabled ? this.disabledColor : this.interactive ? this.isActiveObject() ? this.activeColor || this.color : this.isHoverObject() ? this.hoverColor : this.color : this.color;
461
- uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor, this.cornerRadius, this.gradientColor);
518
+ uiSystem.drawRect(this.pos, this.size, color, this.lineWidth, lineColor, this.cornerRadius, this.gradientColor, this.shadowColor, this.shadowBlur, this.shadowOffset);
462
519
  }
463
520
 
464
521
  /** Special update when object is not visible */
@@ -762,4 +819,148 @@ class UIScrollbar extends UIObject
762
819
  uiSystem.drawText(this.text, this.pos, textSize,
763
820
  this.textColor, 0, undefined, this.align, this.font, this.fontStyle, true, this.textShadow);
764
821
  }
822
+ }
823
+
824
+ ///////////////////////////////////////////////////////////////////////////////
825
+ /**
826
+ * VideoPlayerUIObject - A UI object that plays video
827
+ * @extends UIObject
828
+ * @example
829
+ * // Create a video player UI object
830
+ * const video = new VideoPlayerUIObject(vec2(400, 300), vec2(320, 240), 'cutscene.mp4', true);
831
+ * video.play();
832
+ * @memberof UISystem
833
+ */
834
+ class UIVideo extends UIObject
835
+ {
836
+ /** Create a video player UI object
837
+ * @param {Vector2} [pos]
838
+ * @param {Vector2} [size]
839
+ * @param {string} src - Video file path or URL
840
+ * @param {boolean} [autoplay=false] - Start playing immediately?
841
+ * @param {boolean} [loop=false] - Loop the video?
842
+ * @param {number} [volume=1] - Volume percent scaled by global volume (0-1)
843
+ */
844
+ constructor(pos, size, src, autoplay=false, loop=false, volume=1)
845
+ {
846
+ super(pos, size || vec2());
847
+
848
+ ASSERT(isString(src), 'video src must be a string');
849
+ ASSERT(isNumber(volume), 'video volume must be a number');
850
+
851
+ this.color = BLACK; // default to black background
852
+ this.cornerRadius = 0; // default to no corner radius
853
+
854
+ /** @property {float} - The video volume */
855
+ this.volume = volume;
856
+
857
+ // create video element
858
+ /** @property {HTMLVideoElement} - The video player */
859
+ this.video = document.createElement('video');
860
+ this.video.loop = loop;
861
+ this.video.volume = clamp(volume * soundVolume);
862
+ this.video.muted = !soundEnable;
863
+ this.video.style.display = 'none';
864
+ this.video.src = src;
865
+ document.body.appendChild(this.video);
866
+ autoplay && this.play();
867
+ }
868
+
869
+ /** Play or resume the video
870
+ * @return {Promise} Promise that resolves when playback starts */
871
+ play()
872
+ {
873
+ // try to play the video, catch any errors (autoplay may be blocked)
874
+ const promise = this.video.play();
875
+ promise?.catch(()=>{});
876
+ return promise;
877
+ }
878
+
879
+ /** Pause the video */
880
+ pause() { this.video.pause(); }
881
+
882
+ /** Stop and reset the video */
883
+ stop() { this.video.pause(); this.video.currentTime = 0; }
884
+
885
+ /** Check if video is currently loading
886
+ * @return {boolean} */
887
+ isLoadng()
888
+ { return this.video.readyState < this.video.HAVE_CURRENT_DATA; }
889
+
890
+ /** Check if video is currently paused
891
+ * @return {boolean} */
892
+ isPaused() { return this.video.paused; }
893
+
894
+ /** Check if video is currently playing
895
+ * @return {boolean} */
896
+ isPlaying()
897
+ { return !this.isPaused() && !this.hasEnded() && !this.isLoadng(); }
898
+
899
+ /** Check if video has ended playing
900
+ * @return {boolean} */
901
+ hasEnded() { return this.video.ended; }
902
+
903
+ /** Set volume (0-1)
904
+ * @param {number} volume - Volume level (0-1) */
905
+ setVolume(volume)
906
+ {
907
+ this.volume = volume;
908
+ this.video.volume = clamp(volume * soundVolume);
909
+ }
910
+
911
+ /** Set playback speed
912
+ * @param {number} rate - Playback rate multiplier */
913
+ setPlaybackRate(rate) { this.video.playbackRate = rate; }
914
+
915
+ /** Get current time in seconds
916
+ * @return {number} Current playback time */
917
+ getCurrentTime() { return this.video.currentTime || 0; }
918
+
919
+ /** Get duration in seconds
920
+ * @return {number} Total video duration */
921
+ getDuration() { return this.video.duration || 0; }
922
+
923
+ /** Get the native video dimensions
924
+ * @return {Vector2} Video dimensions (may be 0,0 if metadata not loaded) */
925
+ getVideoSize()
926
+ { return vec2(this.video.videoWidth, this.video.videoHeight); }
927
+
928
+ /** Seek to time in seconds
929
+ * @param {number} time - Time in seconds to seek to */
930
+ setTime(time)
931
+ { this.video.currentTime = clamp(time, 0, this.getDuration()); }
932
+
933
+ update()
934
+ {
935
+ super.update();
936
+
937
+ // update volume based on global sound volume
938
+ this.video.volume = clamp(this.volume * soundVolume);
939
+ }
940
+
941
+ /** Render video to UI canvas */
942
+ render()
943
+ {
944
+ super.render();
945
+
946
+ if (this.isLoadng())
947
+ return;
948
+ const context = uiSystem.uiContext;
949
+ const s = this.size;
950
+ context.save();
951
+ context.translate(this.pos.x, this.pos.y);
952
+ context.drawImage(this.video, -s.x/2, -s.y/2, s.x, s.y);
953
+ context.restore();
954
+ }
955
+
956
+ /** Clean up video on destroy */
957
+ destroy()
958
+ {
959
+ if (this.destroyed)
960
+ return;
961
+
962
+ this.video.pause();
963
+ this.video.remove();
964
+ super.destroy();
965
+ }
765
966
  }
package/src/engine.js CHANGED
@@ -30,7 +30,7 @@ const engineName = 'LittleJS';
30
30
  * @type {string}
31
31
  * @default
32
32
  * @memberof Engine */
33
- const engineVersion = '1.15.0';
33
+ const engineVersion = '1.15.3';
34
34
 
35
35
  /** Frames per second to update
36
36
  * @type {number}
@@ -131,7 +131,7 @@ function engineAddPlugin(update, render, glContextLost, glContextRestored)
131
131
 
132
132
  /**
133
133
  * @callback GameInitCallback - Called after the engine starts, can be async
134
- * @returns {void|Promise<void>}
134
+ * @return {void|Promise<void>}
135
135
  * @memberof Engine
136
136
  */
137
137
  /**
@@ -487,10 +487,10 @@ function engineObjectsDestroy()
487
487
  }
488
488
 
489
489
  /** Collects all object within a given area
490
- * @param {Vector2} [pos] - Center of test area, or undefined for all objects
491
- * @param {Vector2|number} [size] - Radius of circle if float, rectangle size if Vector2
490
+ * @param {Vector2} [pos] - Center of test area, or undefined for all objects
491
+ * @param {Vector2|number} [size] - Radius of circle if float, rectangle size if Vector2
492
492
  * @param {Array<EngineObject>} [objects=engineObjects] - List of objects to check
493
- * @return {Array<EngineObject>} - List of collected objects
493
+ * @return {Array<EngineObject>} - List of collected objects
494
494
  * @memberof Engine */
495
495
  function engineObjectsCollect(pos, size, objects=engineObjects)
496
496
  {
@@ -113,7 +113,6 @@ class Sound
113
113
  ASSERT(isNumber(pitch), 'pitch must be a number');
114
114
  ASSERT(isNumber(randomnessScale), 'randomnessScale must be a number');
115
115
 
116
-
117
116
  if (!soundEnable || headlessMode) return;
118
117
  if (!this.sampleChannels) return;
119
118
 
@@ -70,8 +70,9 @@ function LOG(...output) { console.log(...output); }
70
70
  * @param {number} [time]
71
71
  * @param {number} [angle]
72
72
  * @param {boolean} [fill]
73
+ * @param {boolean} [screenSpace]
73
74
  * @memberof Debug */
74
- function debugRect(pos, size=vec2(), color=WHITE, time=0, angle=0, fill=false)
75
+ function debugRect(pos, size=vec2(), color=WHITE, time=0, angle=0, fill=false, screenSpace=false)
75
76
  {
76
77
  ASSERT(isVector2(pos), 'pos must be a vec2');
77
78
  ASSERT(isVector2(size), 'size must be a vec2');
@@ -86,7 +87,7 @@ function debugRect(pos, size=vec2(), color=WHITE, time=0, angle=0, fill=false)
86
87
  pos = pos.copy();
87
88
  size = size.copy();
88
89
  const timer = new Timer(time);
89
- debugPrimitives.push({pos:pos.copy(), size:size.copy(), color, timer, angle, fill});
90
+ debugPrimitives.push({pos:pos.copy(), size:size.copy(), color, timer, angle, fill, screenSpace});
90
91
  }
91
92
 
92
93
  /** Draw a debug poly in world space
@@ -96,8 +97,9 @@ function debugRect(pos, size=vec2(), color=WHITE, time=0, angle=0, fill=false)
96
97
  * @param {number} [time]
97
98
  * @param {number} [angle]
98
99
  * @param {boolean} [fill]
100
+ * @param {boolean} [screenSpace]
99
101
  * @memberof Debug */
100
- function debugPoly(pos, points, color=WHITE, time=0, angle=0, fill=false)
102
+ function debugPoly(pos, points, color=WHITE, time=0, angle=0, fill=false, screenSpace=false)
101
103
  {
102
104
  ASSERT(isVector2(pos), 'pos must be a vec2');
103
105
  ASSERT(isArray(points), 'points must be an array');
@@ -110,7 +112,7 @@ function debugPoly(pos, points, color=WHITE, time=0, angle=0, fill=false)
110
112
  pos = pos.copy();
111
113
  points = points.map(p=>p.copy());
112
114
  const timer = new Timer(time);
113
- debugPrimitives.push({pos, points, color, timer, angle, fill});
115
+ debugPrimitives.push({pos, points, color, timer, angle, fill, screenSpace});
114
116
  }
115
117
 
116
118
  /** Draw a debug circle in world space
@@ -119,8 +121,9 @@ function debugPoly(pos, points, color=WHITE, time=0, angle=0, fill=false)
119
121
  * @param {Color|string} [color]
120
122
  * @param {number} [time]
121
123
  * @param {boolean} [fill]
124
+ * @param {boolean} [screenSpace]
122
125
  * @memberof Debug */
123
- function debugCircle(pos, size=0, color=WHITE, time=0, fill=false)
126
+ function debugCircle(pos, size=0, color=WHITE, time=0, fill=false, screenSpace=false)
124
127
  {
125
128
  ASSERT(isVector2(pos), 'pos must be a vec2');
126
129
  ASSERT(isNumber(size), 'size must be a number');
@@ -131,7 +134,7 @@ function debugCircle(pos, size=0, color=WHITE, time=0, fill=false)
131
134
  color = color.toString();
132
135
  pos = pos.copy();
133
136
  const timer = new Timer(time);
134
- debugPrimitives.push({pos, size, color, timer, angle:0, fill});
137
+ debugPrimitives.push({pos, size, color, timer, angle:0, fill, screenSpace});
135
138
  }
136
139
 
137
140
  /** Draw a debug point in world space
@@ -139,9 +142,10 @@ function debugCircle(pos, size=0, color=WHITE, time=0, fill=false)
139
142
  * @param {Color|string} [color]
140
143
  * @param {number} [time]
141
144
  * @param {number} [angle]
145
+ * @param {boolean} [screenSpace]
142
146
  * @memberof Debug */
143
- function debugPoint(pos, color, time, angle)
144
- { debugRect(pos, undefined, color, time, angle); }
147
+ function debugPoint(pos, color, time, angle, screenSpace=false)
148
+ { debugRect(pos, undefined, color, time, angle, false, screenSpace); }
145
149
 
146
150
  /** Draw a debug line in world space
147
151
  * @param {Vector2} posA
@@ -149,8 +153,9 @@ function debugPoint(pos, color, time, angle)
149
153
  * @param {Color|string} [color]
150
154
  * @param {number} [width]
151
155
  * @param {number} [time]
156
+ * @param {boolean} [screenSpace]
152
157
  * @memberof Debug */
153
- function debugLine(posA, posB, color, width=.1, time)
158
+ function debugLine(posA, posB, color, width=.1, time, screenSpace=false)
154
159
  {
155
160
  ASSERT(isVector2(posA), 'posA must be a vec2');
156
161
  ASSERT(isVector2(posB), 'posB must be sa vec2');
@@ -158,7 +163,7 @@ function debugLine(posA, posB, color, width=.1, time)
158
163
 
159
164
  const halfDelta = vec2((posB.x - posA.x)/2, (posB.y - posA.y)/2);
160
165
  const size = vec2(width, halfDelta.length()*2);
161
- debugRect(posA.add(halfDelta), size, color, time, halfDelta.angle(), true);
166
+ debugRect(posA.add(halfDelta), size, color, time, halfDelta.angle(), true, screenSpace);
162
167
  }
163
168
 
164
169
  /** Draw a debug combined axis aligned bounding box in world space
@@ -167,8 +172,10 @@ function debugLine(posA, posB, color, width=.1, time)
167
172
  * @param {Vector2} posB
168
173
  * @param {Vector2} sizeB
169
174
  * @param {Color|string} [color]
175
+ * @param {number} [time]
176
+ * @param {boolean} [screenSpace]
170
177
  * @memberof Debug */
171
- function debugOverlap(posA, sizeA, posB, sizeB, color)
178
+ function debugOverlap(posA, sizeA, posB, sizeB, color, time, screenSpace=false)
172
179
  {
173
180
  ASSERT(isVector2(posA), 'posA must be a vec2');
174
181
  ASSERT(isVector2(posB), 'posB must be a vec2');
@@ -183,7 +190,7 @@ function debugOverlap(posA, sizeA, posB, sizeB, color)
183
190
  max(posA.x + sizeA.x/2, posB.x + sizeB.x/2),
184
191
  max(posA.y + sizeA.y/2, posB.y + sizeB.y/2)
185
192
  );
186
- debugRect(minPos.lerp(maxPos,.5), maxPos.subtract(minPos), color);
193
+ debugRect(minPos.lerp(maxPos,.5), maxPos.subtract(minPos), color, time, 0, false, screenSpace);
187
194
  }
188
195
 
189
196
  /** Draw a debug axis aligned bounding box in world space
@@ -194,8 +201,9 @@ function debugOverlap(posA, sizeA, posB, sizeB, color)
194
201
  * @param {number} [time]
195
202
  * @param {number} [angle]
196
203
  * @param {string} [font]
204
+ * @param {boolean} [screenSpace]
197
205
  * @memberof Debug */
198
- function debugText(text, pos, size=1, color=WHITE, time=0, angle=0, font='monospace')
206
+ function debugText(text, pos, size=1, color=WHITE, time=0, angle=0, font='monospace', screenSpace=false)
199
207
  {
200
208
  ASSERT(isString(text), 'text must be a string');
201
209
  ASSERT(isVector2(pos), 'pos must be a vec2');
@@ -209,7 +217,7 @@ function debugText(text, pos, size=1, color=WHITE, time=0, angle=0, font='monosp
209
217
  color = color.toString();
210
218
  pos = pos.copy();
211
219
  const timer = new Timer(time);
212
- debugPrimitives.push({text, pos, size, color, timer, angle, font});
220
+ debugPrimitives.push({text, pos, size, color, timer, angle, font, screenSpace});
213
221
  }
214
222
 
215
223
  /** Clear all debug primitives in the list
@@ -419,21 +427,26 @@ function debugRender()
419
427
  {
420
428
  // draw debug primitives
421
429
  overlayContext.lineWidth = 2;
422
- const pointSize = debugPointSize * cameraScale;
423
430
  debugPrimitives.forEach(p=>
424
431
  {
425
432
  overlayContext.save();
426
433
 
427
434
  // create canvas transform from world space to screen space
428
- const pos = worldToScreen(p.pos);
435
+ // without scaling because we want consistent pixel sizes
436
+ let pos = p.pos, scale = 1, angle = p.angle;
437
+ if (!p.screenSpace)
438
+ {
439
+ pos = worldToScreen(p.pos);
440
+ scale = cameraScale;
441
+ angle -= cameraAngle;
442
+ }
429
443
  overlayContext.translate(pos.x|0, pos.y|0);
430
- overlayContext.rotate(p.angle);
444
+ overlayContext.rotate(angle);
431
445
  overlayContext.scale(1, p.text ? 1 : -1);
432
446
  overlayContext.fillStyle = overlayContext.strokeStyle = p.color;
433
-
434
447
  if (p.text !== undefined)
435
448
  {
436
- overlayContext.font = p.size*cameraScale + 'px '+ p.font;
449
+ overlayContext.font = p.size*scale + 'px '+ p.font;
437
450
  overlayContext.textAlign = 'center';
438
451
  overlayContext.textBaseline = 'middle';
439
452
  overlayContext.fillText(p.text, 0, 0);
@@ -444,7 +457,7 @@ function debugRender()
444
457
  overlayContext.beginPath();
445
458
  for (const point of p.points)
446
459
  {
447
- const p2 = point.scale(cameraScale).floor();
460
+ const p2 = point.scale(scale).floor();
448
461
  overlayContext.lineTo(p2.x, p2.y);
449
462
  }
450
463
  overlayContext.closePath();
@@ -454,13 +467,14 @@ function debugRender()
454
467
  else if (p.size === 0 || (p.size.x === 0 && p.size.y === 0))
455
468
  {
456
469
  // point
470
+ const pointSize = debugPointSize * scale;
457
471
  overlayContext.fillRect(-pointSize/2, -1, pointSize, 3);
458
472
  overlayContext.fillRect(-1, -pointSize/2, 3, pointSize);
459
473
  }
460
474
  else if (p.size.x !== undefined)
461
475
  {
462
476
  // rect
463
- const s = p.size.scale(cameraScale).floor();
477
+ const s = p.size.scale(scale).floor();
464
478
  const w = s.x, h = s.y;
465
479
  p.fill && overlayContext.fillRect(-w/2|0, -h/2|0, w, h);
466
480
  overlayContext.strokeRect(-w/2|0, -h/2|0, w, h);
@@ -469,7 +483,7 @@ function debugRender()
469
483
  {
470
484
  // circle
471
485
  overlayContext.beginPath();
472
- overlayContext.arc(0, 0, p.size*cameraScale/2, 0, 9);
486
+ overlayContext.arc(0, 0, p.size*scale/2, 0, 9);
473
487
  p.fill && overlayContext.fill();
474
488
  overlayContext.stroke();
475
489
  }