q5play 4.1.0 → 4.1.2

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 (3) hide show
  1. package/package.json +2 -2
  2. package/q5play.d.ts +18 -19
  3. package/q5play.js +88 -41
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "q5play",
3
- "version": "4.1.0",
3
+ "version": "4.1.2",
4
4
  "author": "quinton-ashley",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "description": "A beginner friendly, web-based game engine that uses q5.js WebGPU for graphics and Box2D v3 WASM for physics.",
@@ -25,6 +25,6 @@
25
25
  },
26
26
  "dependencies": {
27
27
  "box2d3-wasm": "^5.2.0",
28
- "q5": "^4.6.3"
28
+ "q5": "^4.6.6"
29
29
  }
30
30
  }
package/q5play.d.ts CHANGED
@@ -105,6 +105,7 @@ declare global {
105
105
  type: string;
106
106
  geom: any;
107
107
  density: number;
108
+ applyWind(speed: number, angle: number, drag?: number, lift?: number): void;
108
109
  scaleBy(scaleX: number, scaleY?: number): void;
109
110
  delete(): void;
110
111
  }
@@ -678,11 +679,7 @@ declare global {
678
679
  get rotationDrag(): number;
679
680
  set rotationDrag(val: number);
680
681
  /**
681
- * Known issue, this doesn't work yet.
682
- * https://github.com/q5play/q5play/issues/36
683
- *
684
682
  * If true, the sprite can not rotate.
685
- * @deprecated
686
683
  * @default false
687
684
  */
688
685
  get rotationLock(): boolean;
@@ -870,6 +867,14 @@ declare global {
870
867
  * @param n The point the force is applied from, relative to the sprite's center of mass. Accepts a coordinate array or object with x and y properties. If not given, the force is applied at the center of mass.
871
868
  */
872
869
  applyForceScaled(amount: number, origin?: any): void;
870
+ /**
871
+ * Applies wind force to the sprite.
872
+ * @param strength the strength of the wind
873
+ * @param angle the angle the wind is blowing at
874
+ * @param drag the force that opposes the relative velocity
875
+ * @param lift the force that is perpendicular to the relative velocity
876
+ */
877
+ applyWind(strength: number, angle: number, drag?: number, lift?: number): void;
873
878
  /**
874
879
  * Applies a force to the sprite's center of mass attracting it to
875
880
  * the given position.
@@ -1521,10 +1526,7 @@ declare global {
1521
1526
  */
1522
1527
  rotationDrag: number;
1523
1528
  /**
1524
- * Known issue, this doesn't work.
1525
- *
1526
1529
  * If true, the group sprites can not rotate.
1527
- * @deprecated
1528
1530
  */
1529
1531
  rotationLock: boolean;
1530
1532
  /**
@@ -1732,6 +1734,7 @@ declare global {
1732
1734
  passes(target: Group): void;
1733
1735
  applyForce(amount: number, origin?: [] | { x: number; y: number } | Q5.Vector): void;
1734
1736
  applyForceScaled(amount: number, origin?: [] | { x: number; y: number } | Q5.Vector): void;
1737
+ applyWind(speed: number, angle: number, drag?: number, lift?: number): void;
1735
1738
  attractTo(x: number | any, y?: number, force?: number): void;
1736
1739
  applyTorque(torque: any): void;
1737
1740
  moveTowards(x: number | any, y?: number, tracking?: number): void;
@@ -1915,8 +1918,7 @@ declare global {
1915
1918
  set allowSleeping(val: boolean);
1916
1919
  /**
1917
1920
  * Finds the first sprite (with a physics body) that
1918
- * intersects a ray (line), excluding any sprites that intersect
1919
- * with the starting point.
1921
+ * intersects a ray (line).
1920
1922
  *
1921
1923
  * @param startPos starting position of the ray cast
1922
1924
  * @param direction direction of the ray
@@ -1926,14 +1928,13 @@ declare global {
1926
1928
  rayCast(startPos: any, direction: number, maxDistance: number): Sprite;
1927
1929
  /**
1928
1930
  * Finds sprites (with physics bodies) that intersect
1929
- * a line (ray), excluding any sprites that intersect the
1930
- * starting point.
1931
+ * a line (ray).
1931
1932
  *
1932
1933
  * @param startPos starting position of the ray cast
1933
1934
  * @param direction direction of the ray
1934
1935
  * @param maxDistance max distance the ray should check
1935
- * @param limiter limiter function that's run each time the ray intersects a sprite, return true to stop the ray
1936
- * @returns An array of sprites that the ray cast hit, sorted by distance. The sprite closest to the starting point will be at index 0.
1936
+ * @param limiter callback that's run each time the ray intersects a sprite, receives an intersected sprite as an input parameter, return true to stop the ray
1937
+ * @returns An array of sprites that the ray cast hit, sorted by distance. The sprite closest to the starting point will be at index 0. If a limiter is provided, this array includes the sprite that caused the ray to stop.
1937
1938
  */
1938
1939
  rayCastAll(startPos: any, direction: number, maxDistance: number, limiter?: Function): Sprite[];
1939
1940
  /**
@@ -2172,10 +2173,12 @@ declare global {
2172
2173
  set limitsEnabled(val: boolean);
2173
2174
  /**
2174
2175
  * The minimum length allowed when limits are enabled.
2176
+ * @readonly
2175
2177
  */
2176
2178
  get minLength(): number;
2177
2179
  /**
2178
2180
  * The maximum length allowed when limits are enabled.
2181
+ * @readonly
2179
2182
  */
2180
2183
  get maxLength(): number;
2181
2184
  /**
@@ -2354,12 +2357,12 @@ declare global {
2354
2357
  set limitsEnabled(val: boolean);
2355
2358
  /**
2356
2359
  * The lower limit of rotation.
2357
- * @default undefined
2360
+ * @readonly
2358
2361
  */
2359
2362
  get minAngle(): number;
2360
2363
  /**
2361
2364
  * The upper limit of rotation.
2362
- * @default undefined
2365
+ * @readonly
2363
2366
  */
2364
2367
  get maxAngle(): number;
2365
2368
  /**
@@ -2453,10 +2456,6 @@ declare global {
2453
2456
  * or an array with the lower and upper translation limits.
2454
2457
  */
2455
2458
  set range(val: [number, number] | number);
2456
- /**
2457
- * Alias for range.
2458
- */
2459
- set limits(val: [number, number] | number);
2460
2459
  /**
2461
2460
  * Whether spring behavior is enabled.
2462
2461
  * @default false
package/q5play.js CHANGED
@@ -85,6 +85,7 @@ async function q5playPreSetup(q) {
85
85
  b2World_OverlapShape,
86
86
  b2World_CastRay,
87
87
  b2World_CastRayClosest,
88
+ b2World_CastShape,
88
89
  b2World_SetCustomFilterCallback,
89
90
  b2World_SetPreSolveCallback,
90
91
  b2World_GetGravity,
@@ -381,7 +382,10 @@ async function q5playPreSetup(q) {
381
382
  $.imageMode($.CENTER);
382
383
 
383
384
  const ZERO_VEC = new b2Vec2(0, 0),
384
- ZERO_ROT = b2MakeRot(0);
385
+ ZERO_ROT = b2MakeRot(0),
386
+ NULL_FILTER = new b2QueryFilter();
387
+ NULL_FILTER.categoryBits = 0xffffffff;
388
+ NULL_FILTER.maskBits = 0xffffffff;
385
389
 
386
390
  let meterSize = 60;
387
391
 
@@ -535,6 +539,12 @@ async function q5playPreSetup(q) {
535
539
  this._density = val;
536
540
  b2Shape_SetDensity(this.id, val);
537
541
  }
542
+
543
+ applyWind(strength, angle, drag = 0, lift = 0) {
544
+ const wind = new b2Vec2(strength * $.cos(angle), strength * $.sin(angle));
545
+ b2Shape_ApplyWind(this.id, wind, drag, lift, true);
546
+ wind.delete();
547
+ }
538
548
  };
539
549
 
540
550
  const Sensor = class extends Shape {
@@ -2050,17 +2060,15 @@ async function q5playPreSetup(q) {
2050
2060
  }
2051
2061
  set rotationLock(val) {
2052
2062
  if (this.watch) this.mod[25] = true;
2063
+ this._rotationLock = val;
2064
+ if (!this._physicsEnabled) return;
2053
2065
 
2054
- // let mass = this.mass;
2055
-
2056
- // TODO: not working, shape is ignored by physics sim after this
2057
- let locks = new b2MotionLocks();
2066
+ const locks = new b2MotionLocks();
2058
2067
  locks.linearX = false;
2059
2068
  locks.linearY = false;
2060
2069
  locks.angularZ = val;
2061
2070
  b2Body_SetMotionLocks(this.bdID, locks);
2062
-
2063
- // this.mass = mass;
2071
+ locks.delete();
2064
2072
  }
2065
2073
 
2066
2074
  get rotationSpeed() {
@@ -2760,6 +2768,14 @@ async function q5playPreSetup(q) {
2760
2768
  b2Body_ApplyTorque(this.bdID, val, true);
2761
2769
  }
2762
2770
 
2771
+ applyWind(strength, angle, drag = 0, lift = 0) {
2772
+ const wind = new b2Vec2(strength * $.cos(angle), strength * $.sin(angle));
2773
+ for (let shape of this._shapes) {
2774
+ b2Shape_ApplyWind(shape.id, wind, drag, lift, true);
2775
+ }
2776
+ wind.delete();
2777
+ }
2778
+
2763
2779
  angleTo(x, y) {
2764
2780
  if (typeof x == 'object') {
2765
2781
  y = x.y;
@@ -4464,6 +4480,16 @@ async function q5playPreSetup(q) {
4464
4480
  }
4465
4481
  }
4466
4482
 
4483
+ applyWind(strength, angle, drag = 0, lift = 0) {
4484
+ const wind = new b2Vec2(strength * $.cos(angle), strength * $.sin(angle));
4485
+ for (let s of this) {
4486
+ for (let shape of s._shapes) {
4487
+ b2Shape_ApplyWind(shape.id, wind, drag, lift, true);
4488
+ }
4489
+ }
4490
+ wind.delete();
4491
+ }
4492
+
4467
4493
  _resetCentroid() {
4468
4494
  let x = 0;
4469
4495
  let y = 0;
@@ -4784,7 +4810,7 @@ async function q5playPreSetup(q) {
4784
4810
  $.Visuals.prototype.addAni = $.Group.prototype.addAni = $.Sprite.prototype.addAni;
4785
4811
  $.Visuals.prototype.addAnis = $.Group.prototype.addAnis = $.Sprite.prototype.addAnis;
4786
4812
 
4787
- class RayInfo {
4813
+ class CastInfo {
4788
4814
  constructor(sprite, px, py, nx, ny, fraction, maxDistance) {
4789
4815
  this.sprite = sprite;
4790
4816
  this._px = px;
@@ -5075,14 +5101,9 @@ async function q5playPreSetup(q) {
5075
5101
 
5076
5102
  const point = scaleTo(x, y),
5077
5103
  proxy = b2MakeProxy(point, 1, radius / meterSize),
5078
- filter = new b2QueryFilter(),
5079
5104
  shapes = [];
5080
5105
 
5081
- // no filter
5082
- filter.categoryBits = 0xffffffff;
5083
- filter.maskBits = 0xffffffff;
5084
-
5085
- b2World_OverlapShape(wID, proxy, filter, (overlapResult) => {
5106
+ b2World_OverlapShape(wID, proxy, NULL_FILTER, (overlapResult) => {
5086
5107
  const { shapeId } = overlapResult;
5087
5108
  if (shapeId) {
5088
5109
  shapes.push(shapeDict[shapeId.index1]);
@@ -5091,7 +5112,6 @@ async function q5playPreSetup(q) {
5091
5112
  });
5092
5113
 
5093
5114
  proxy.delete();
5094
- filter.delete();
5095
5115
 
5096
5116
  if (!shapes.length) return [];
5097
5117
 
@@ -5179,17 +5199,14 @@ async function q5playPreSetup(q) {
5179
5199
  const origin = scaleTo(startX, startY);
5180
5200
  const translation = scaleTo(endX - startX, endY - startY);
5181
5201
 
5182
- const filter = new b2QueryFilter();
5183
- filter.categoryBits = 0xffffffff;
5184
- filter.maskBits = 0xffffffff;
5185
-
5186
5202
  const results = [];
5187
5203
 
5188
- b2World_CastRay(wID, origin, translation, filter, (castResult) => {
5204
+ b2World_CastRay(wID, origin, translation, NULL_FILTER, (castResult) => {
5189
5205
  const shape = shapeDict[castResult.shapeId.index1];
5190
5206
  if (shape?.sprite) {
5191
5207
  const s = shape.sprite;
5192
- s.ray = new RayInfo(
5208
+
5209
+ s.cast = new CastInfo(
5193
5210
  s,
5194
5211
  castResult.point.x,
5195
5212
  castResult.point.y,
@@ -5199,18 +5216,62 @@ async function q5playPreSetup(q) {
5199
5216
  maxDistance
5200
5217
  );
5201
5218
  results.push(s);
5219
+
5220
+ if (limiter && limiter(s)) return 0; // stop raycast
5202
5221
  }
5203
5222
  return 1; // continue to collect all hits
5204
5223
  });
5205
5224
 
5206
- filter.delete();
5207
-
5208
5225
  // sort results by distance from start
5209
- results.sort((a, b) => a.ray.distance - b.ray.distance);
5226
+ results.sort((a, b) => a.cast.distance - b.cast.distance);
5227
+ return results;
5228
+ }
5229
+
5230
+ circleCast(startPos, endPos, radius) {
5231
+ let sprites = this.circleCastAll(startPos, endPos, radius, () => true);
5232
+ return sprites[0];
5233
+ }
5234
+
5235
+ circleCastAll(startPos, endPos, radius, limiter) {
5236
+ const startX = startPos.x ?? startPos[0];
5237
+ const startY = startPos.y ?? startPos[1];
5238
+ const endX = endPos.x ?? endPos[0];
5239
+ const endY = endPos.y ?? endPos[1];
5240
+
5241
+ const maxDistance = Math.sqrt((endX - startX) ** 2 + (endY - startY) ** 2);
5242
+
5243
+ const center = scaleTo(startX, startY);
5244
+ const proxy = b2MakeProxy(center, 1, radius / meterSize);
5245
+ const translation = scaleTo(endX - startX, endY - startY);
5210
5246
 
5211
- if (!limiter) return results;
5247
+ const results = [];
5212
5248
 
5213
- return results.filter(limiter);
5249
+ b2World_CastShape(wID, proxy, translation, NULL_FILTER, (castResult) => {
5250
+ const shape = shapeDict[castResult.shapeId.index1];
5251
+ if (shape?.sprite) {
5252
+ const s = shape.sprite;
5253
+
5254
+ s.cast = new CastInfo(
5255
+ s,
5256
+ castResult.point.x,
5257
+ castResult.point.y,
5258
+ castResult.normal.x,
5259
+ castResult.normal.y,
5260
+ castResult.fraction,
5261
+ maxDistance
5262
+ );
5263
+ results.push(s);
5264
+
5265
+ if (limiter && limiter(s)) return 0; // stop cast
5266
+ }
5267
+ return 1; // continue to collect all hits
5268
+ });
5269
+
5270
+ proxy.delete();
5271
+
5272
+ // sort results by distance from start
5273
+ results.sort((a, b) => a.cast.distance - b.cast.distance);
5274
+ return results;
5214
5275
  }
5215
5276
  };
5216
5277
 
@@ -5506,7 +5567,7 @@ async function q5playPreSetup(q) {
5506
5567
  }
5507
5568
 
5508
5569
  _draw(xA, yA, xB, yB) {
5509
- if (xB) $.line(xA, yA, xB, yB);
5570
+ if (xB || yB) $.line(xA, yA, xB, yB);
5510
5571
  else $.point(xA, yA);
5511
5572
  }
5512
5573
 
@@ -6054,20 +6115,6 @@ async function q5playPreSetup(q) {
6054
6115
  Box2D.b2PrismaticJoint_EnableLimit(this.jID, true);
6055
6116
  }
6056
6117
 
6057
- set limits(val) {
6058
- let min, max;
6059
- if (typeof val == 'number') {
6060
- val /= 2;
6061
- min = -val;
6062
- max = val;
6063
- } else {
6064
- min = val[0];
6065
- max = val[1];
6066
- }
6067
- Box2D.b2PrismaticJoint_SetLimits(this.jID, min / meterSize, max / meterSize);
6068
- Box2D.b2PrismaticJoint_EnableLimit(this.jID, true);
6069
- }
6070
-
6071
6118
  get springEnabled() {
6072
6119
  return Box2D.b2PrismaticJoint_IsSpringEnabled(this.jID);
6073
6120
  }