littlejsengine 1.9.1 → 1.9.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.
Files changed (68) hide show
  1. package/README.md +11 -9
  2. package/{build → dist}/littlejs.d.ts +48 -43
  3. package/{build → dist}/littlejs.esm.js +256 -147
  4. package/dist/littlejs.esm.min.js +1 -0
  5. package/{build → dist}/littlejs.js +256 -147
  6. package/dist/littlejs.min.js +1 -0
  7. package/{build → dist}/littlejs.release.js +249 -144
  8. package/examples/breakout/game.js +2 -2
  9. package/examples/breakout/gameObjects.js +3 -3
  10. package/examples/breakout/index.html +3 -3
  11. package/examples/breakoutTutorial/index.html +2 -2
  12. package/examples/electron/build.js +1 -1
  13. package/examples/electron/game.js +49 -38
  14. package/examples/electron/index.html +2 -2
  15. package/examples/electron/tiles.png +0 -0
  16. package/examples/empty/index.html +1 -1
  17. package/examples/js13k/build.js +1 -1
  18. package/examples/js13k/game.js +10 -9
  19. package/examples/js13k/index.html +13 -13
  20. package/examples/js13k/tiles.png +0 -0
  21. package/examples/logo.png +0 -0
  22. package/examples/module/game.js +45 -41
  23. package/examples/module/index.html +1 -1
  24. package/examples/module/tiles.png +0 -0
  25. package/examples/particles/index.html +1 -1
  26. package/examples/platformer/data/gameLevelData.tmx +143 -0
  27. package/examples/platformer/data/gameLevelData.tsx +4 -0
  28. package/examples/platformer/game.js +31 -11
  29. package/examples/platformer/gameCharacter.js +9 -7
  30. package/examples/platformer/gameEffects.js +105 -164
  31. package/examples/platformer/gameLevel.js +164 -181
  32. package/examples/platformer/gameLevelData.js +181 -0
  33. package/examples/platformer/gameObjects.js +71 -26
  34. package/examples/platformer/gamePlayer.js +3 -2
  35. package/examples/platformer/index.html +8 -7
  36. package/examples/platformer/tiles.png +0 -0
  37. package/examples/platformer/tilesLevel.png +0 -0
  38. package/examples/puzzle/game.js +12 -12
  39. package/examples/puzzle/index.html +2 -2
  40. package/examples/screenshot.jpg +0 -0
  41. package/examples/starter/build.js +1 -1
  42. package/examples/starter/game.js +6 -6
  43. package/examples/starter/index.html +13 -13
  44. package/examples/starter/tiles.png +0 -0
  45. package/examples/stress/index.html +2 -2
  46. package/examples/typescript/build.bat +4 -1
  47. package/examples/typescript/game.js +34 -31
  48. package/examples/typescript/game.ts +40 -36
  49. package/examples/typescript/index.html +1 -1
  50. package/examples/typescript/tiles.png +0 -0
  51. package/package.json +3 -3
  52. package/reference.md +409 -0
  53. package/src/engine.js +46 -41
  54. package/src/engineAudio.js +22 -19
  55. package/src/engineBuild.js +9 -2
  56. package/src/engineDebug.js +7 -3
  57. package/src/engineDraw.js +8 -7
  58. package/src/engineInput.js +5 -5
  59. package/src/engineMedals.js +3 -3
  60. package/src/engineObject.js +2 -2
  61. package/src/engineParticles.js +16 -15
  62. package/src/engineSettings.js +3 -3
  63. package/src/engineTileLayer.js +3 -3
  64. package/src/engineUtilities.js +139 -38
  65. package/src/engineWebGL.js +2 -8
  66. package/build/littlejs.esm.min.js +0 -1
  67. package/build/littlejs.min.js +0 -1
  68. package/index.d.ts +0 -2094
@@ -21,7 +21,7 @@ const sound_dodge = new Sound([.4,.2,150,.05,,.05,,,-1,,,,,4,,,,,.02]);
21
21
  const sound_walk = new Sound([.3,.1,50,.005,,.01,4,,,,,,,,10,,,.5]);
22
22
  const sound_explosion = new Sound([2,.2,72,.01,.01,.2,4,,,,,,,1,,.5,.1,.5,.02]);
23
23
  const sound_grenade = new Sound([.5,.01,300,,,.02,3,.22,,,-9,.2,,,,,,.5]);
24
- const sound_killEnemy = new Sound([,,783,,.03,.02,1,2,,,940,.03,,,,,.2,.6,,.06]);
24
+ const sound_score = new Sound([,,783,,.03,.02,1,2,,,940,.03,,,,,.2,.6,,.06]);
25
25
 
26
26
  ///////////////////////////////////////////////////////////////////////////////
27
27
  // special effects
@@ -31,15 +31,15 @@ const persistentParticleDestroyCallback = (particle)=>
31
31
  // copy particle to tile layer on death
32
32
  ASSERT(!particle.tileInfo, 'quick draw to tile layer uses canvas 2d so must be untextured');
33
33
  if (particle.groundObject)
34
- tileLayer.drawTile(particle.pos, particle.size, particle.tileInfo, particle.color, particle.angle, particle.mirror);
34
+ tileLayers[foregroundLayerIndex].drawTile(particle.pos, particle.size, particle.tileInfo, particle.color, particle.angle, particle.mirror);
35
35
  }
36
36
 
37
- function makeBlood(pos, amount) { makeDebris(pos, new Color(1,0,0), 50, .1, 0); }
38
- function makeDebris(pos, color = new Color, amount = 100, size=.2, elasticity = .3)
37
+ function makeBlood(pos, amount) { makeDebris(pos, hsl(0,1,.5), amount, .1, 0); }
38
+ function makeDebris(pos, color = hsl(), amount = 50, size=.2, elasticity = .3)
39
39
  {
40
- const color2 = color.lerp(new Color, .5);
40
+ const color2 = color.lerp(hsl(), .5);
41
41
  const emitter = new ParticleEmitter(
42
- pos, 0, 1, .1, 100, PI, // pos, angle, emitSize, emitTime, emitRate, emiteCone
42
+ pos, 0, 1, .1, amount/.1, PI, // pos, angle, emitSize, emitTime, emitRate, emiteCone
43
43
  0, // tileInfo
44
44
  color, color2, // colorStartA, colorStartB
45
45
  color, color2, // colorEndA, colorEndB
@@ -58,8 +58,6 @@ function explosion(pos, radius=3)
58
58
  {
59
59
  ASSERT(radius > 0);
60
60
 
61
- const damage = radius*2;
62
-
63
61
  // destroy level
64
62
  for (let x = -radius; x < radius; ++x)
65
63
  {
@@ -80,6 +78,7 @@ function explosion(pos, radius=3)
80
78
  // kill/push objects
81
79
  engineObjectsCallback(pos, radius*3, (o)=>
82
80
  {
81
+ const damage = radius*2;
83
82
  const d = o.pos.distance(pos);
84
83
  if (o.isGameObject)
85
84
  {
@@ -99,11 +98,11 @@ function explosion(pos, radius=3)
99
98
 
100
99
  // smoke
101
100
  new ParticleEmitter(
102
- pos, 0, // pos, angle
103
- radius/2, .2, 50*radius, PI, // emitSize, emitTime, emitRate, emiteCone
104
- 0, // tileInfo
105
- new Color(0,0,0), new Color(0,0,0), // colorStartA, colorStartB
106
- new Color(0,0,0,0), new Color(0,0,0,0), // colorEndA, colorEndB
101
+ pos, 0, // pos, angle
102
+ radius/2, .2, 50*radius, PI,// emitSize, emitTime, emitRate, emiteCone
103
+ 0, // tileInfo
104
+ hsl(0,0,0), hsl(0,0,0), // colorStartA, colorStartB
105
+ hsl(0,0,0,0), hsl(0,0,0,0), // colorEndA, colorEndB
107
106
  1, .5, 2, .2, .05, // time, sizeStart, sizeEnd, speed, angleSpeed
108
107
  .9, 1, -.3, PI, .1, // damp, angleDamp, gravity, particleCone, fade
109
108
  .5, 0, 0, 0, 1e8 // randomness, collide, additive, colorLinear, renderOrder
@@ -111,11 +110,11 @@ function explosion(pos, radius=3)
111
110
 
112
111
  // fire
113
112
  new ParticleEmitter(
114
- pos, 0, // pos, angle
115
- radius/2, .1, 100*radius, PI, // emitSize, emitTime, emitRate, emiteCone
116
- 0, // tileInfo
117
- new Color(1,.5,.1), new Color(1,.1,.1), // colorStartA, colorStartB
118
- new Color(1,.5,.1,0), new Color(1,.1,.1,0), // colorEndA, colorEndB
113
+ pos, 0, // pos, angle
114
+ radius/2, .1, 100*radius, PI, // emitSize, emitTime, emitRate, emiteCone
115
+ 0, // tileInfo
116
+ rgb(1,.5,.1), rgb(1,.1,.1), // colorStartA, colorStartB
117
+ rgb(1,.5,.1,0), rgb(1,.1,.1,0), // colorEndA, colorEndB
119
118
  .7, .8, .2, .2, .05, // time, sizeStart, sizeEnd, speed, angleSpeed
120
119
  .9, 1, -.2, PI, .05, // damp, angleDamp, gravity, particleCone, fade
121
120
  .5, 0, 1, 0, 1e9 // randomness, collide, additive, colorLinear, renderOrder
@@ -132,186 +131,128 @@ function destroyTile(pos, makeSound = 1, cleanNeighbors = 1)
132
131
  // destroy tile
133
132
  const tileType = getTileCollisionData(pos);
134
133
 
135
- if (!tileType) return 1; // empty
134
+ if (!tileType || tileType == tileType_solid)
135
+ return 1;
136
136
 
137
+ const tileLayer = tileLayers[foregroundLayerIndex];
137
138
  const centerPos = pos.add(vec2(.5));
138
139
  const layerData = tileLayer.getData(pos);
139
- if (layerData)
140
- {
141
- makeDebris(centerPos, layerData.color.mutate());
142
- makeSound && sound_destroyObject.play(centerPos);
143
-
144
- setTileCollisionData(pos, tileType_empty);
145
- tileLayer.setData(pos, new TileLayerData, 1); // set and clear tile
146
-
147
- // cleanup neighbors
148
- if (cleanNeighbors)
149
- {
150
- for (let i=-1;i<=1;++i)
151
- for (let j=-1;j<=1;++j)
152
- decorateTile(pos.add(vec2(i,j)));
153
- }
154
- }
155
-
156
- return 1;
157
- }
158
-
159
- function decorateBackgroundTile(pos)
160
- {
161
- if (!tileBackgroundLayer)
140
+ if (!layerData)
162
141
  return;
163
142
 
164
- const tileData = getTileBackgroundData(pos);
165
- if (tileData <= 0)
166
- return;
143
+ // create effects
144
+ makeDebris(centerPos, layerData.color.mutate());
145
+ makeSound && sound_destroyObject.play(centerPos);
167
146
 
168
- // make round corners
169
- for (let i=4;i--;)
170
- {
171
- // check corner neighbors
172
- const neighborTileDataA = getTileBackgroundData(pos.add(vec2().setAngle(i*PI/2)));
173
- const neighborTileDataB = getTileBackgroundData(pos.add(vec2().setAngle((i+1)%4*PI/2)));
174
- if (neighborTileDataA > 0 || neighborTileDataB > 0)
175
- continue;
176
-
177
- const directionVector = vec2().setAngle(i*PI/2+PI/4, 10).floor();
178
- const drawPos = pos.add(vec2(.5)) // center
179
- .scale(16).add(directionVector).floor(); // direction offset
180
-
181
- // clear rect without any scaling to prevent blur from filtering
182
- const s = 2;
183
- tileBackgroundLayer.context.clearRect(
184
- drawPos.x - s/2, tileBackgroundLayer.canvas.height - drawPos.y - s/2, s, s);
185
- }
186
- }
147
+ // set and clear tile
148
+ tileLayer.setData(pos, new TileLayerData, 1);
149
+ setTileCollisionData(pos, tileType_empty);
150
+ setTileData(pos, foregroundLayerIndex, 0);
187
151
 
188
- function decorateTile(pos)
189
- {
190
- ASSERT((pos.x|0) == pos.x && (pos.y|0)== pos.y);
191
- const tileData = getTileCollisionData(pos);
192
- if (tileData <= 0)
152
+ // cleanup neighbors
153
+ if (cleanNeighbors)
193
154
  {
194
- tileData || tileLayer.setData(pos, new TileLayerData, 1); // force it to clear if it is empty
195
- return;
155
+ for (let i=-1;i<=1;++i)
156
+ for (let j=-1;j<=1;++j)
157
+ decorateTile(pos.add(vec2(i,j)));
196
158
  }
197
159
 
198
- for (let i=4;i--;)
199
- {
200
- // outline towards neighbors of differing type
201
- const neighborTileData = getTileCollisionData(pos.add(vec2().setAngle(i*PI/2)));
202
- if (neighborTileData == tileData)
203
- continue;
204
-
205
- // make pixel perfect outlines
206
- const size = i&1 ? vec2(2, 16) : vec2(16, 2);
207
- tileLayer.context.fillStyle = levelGroundColor.mutate(.1);
208
- const drawPos = pos.scale(16)
209
- .add(vec2(i==1?14:0,(i==0?14:0)))
210
- .subtract((i&1? vec2(0,8-size.y/2) : vec2(8-size.x/2,0)));
211
- tileLayer.context.fillRect(
212
- drawPos.x, tileLayer.canvas.height - drawPos.y, size.x, -size.y);
213
- }
160
+ return 1;
214
161
  }
215
162
 
216
163
  ///////////////////////////////////////////////////////////////////////////////
217
164
  // sky with background gradient, stars, and planets
218
165
 
219
- let skySeed, skyColor, horizonColor;
220
-
221
- function initSky()
166
+ class Sky extends EngineObject
222
167
  {
223
- skySeed = rand(1e9);
224
- skyColor = randColor(new Color(.5,.5,.5), new Color(.9,.9,.9));
225
- horizonColor = skyColor.subtract(new Color(.05,.05,.05)).mutate(.3).clamp();
226
- }
168
+ constructor()
169
+ {
170
+ super();
227
171
 
228
- function drawSky()
229
- {
230
- // fill background with a gradient
231
- const gradient = mainContext.fillStyle = mainContext.createLinearGradient(0, 0, 0, mainCanvas.height);
232
- gradient.addColorStop(0, skyColor);
233
- gradient.addColorStop(1, horizonColor);
234
- mainContext.fillRect(0, 0, mainCanvas.width, mainCanvas.height);
235
- }
172
+ this.renderOrder = -1e4;
173
+ this.seed = randInt(1e9);
174
+ this.skyColor = randColor(hsl(0,0,.5), hsl(0,0,.9));
175
+ this.horizonColor = this.skyColor.subtract(hsl(0,0,.05,0)).mutate(.3);
176
+ }
236
177
 
237
- function drawStars()
238
- {
239
- // draw stars and planets
240
- const random = new RandomGenerator(skySeed);
241
- const largeStarCount = 9;
242
- for (let i = 1e3; i--;)
178
+ render()
243
179
  {
244
- let size = random.float(6, 1);
245
- let speed = random.float() < .9 ? random.float(5) : random.float(99,9);
246
- let color = (new Color).setHSLA(random.float(.2,-.3), random.float()**9, random.float(1,.5), random.float(.9,.3));
247
- if (i < largeStarCount)
248
- {
249
- // large planets and suns
250
- size = random.float()**3*99 + 9;
251
- speed = random.float(5);
252
- color = (new Color).setHSLA(random.float(), random.float(), random.float(1,.5)).add(skyColor.scale(.5)).clamp();
253
- }
180
+ // fill background with a gradient
181
+ const gradient = mainContext.createLinearGradient(0, 0, 0, mainCanvas.height);
182
+ gradient.addColorStop(0, this.skyColor);
183
+ gradient.addColorStop(1, this.horizonColor);
184
+ mainContext.save();
185
+ mainContext.fillStyle = gradient;
186
+ mainContext.fillRect(0, 0, mainCanvas.width, mainCanvas.height);
187
+ mainContext.globalCompositeOperation = 'lighter';
254
188
 
255
- const extraSpace = 200;
256
- const w = mainCanvas.width+2*extraSpace, h = mainCanvas.height+2*extraSpace;
257
- const screenPos = vec2(
258
- (random.float(w)+time*speed)%w-extraSpace,
259
- (random.float(h)+time*speed*random.float())%h-extraSpace);
260
-
261
- mainContext.fillStyle = color;
262
- if (size < 9)
263
- mainContext.fillRect(screenPos.x, screenPos.y, size, size);
264
- else
189
+ // draw stars
190
+ const random = new RandomGenerator(this.seed);
191
+ for (let i=1e3; i--;)
265
192
  {
266
- mainContext.arc(screenPos.x, screenPos.y, size, 0, 9);
267
- mainContext.fill();
268
- mainContext.beginPath();
193
+ const size = random.float(.5,2)**2;
194
+ const speed = random.float() < .9 ? random.float(5) : random.float(9,99);
195
+ const color = hsl(random.float(-.3,.2), random.float(), random.float());
196
+ const extraSpace = 50;
197
+ const w = mainCanvas.width+2*extraSpace, h = mainCanvas.height+2*extraSpace;
198
+ const screenPos = vec2(
199
+ (random.float(w)+time*speed)%w-extraSpace,
200
+ (random.float(h)+time*speed*random.float())%h-extraSpace);
201
+ mainContext.fillStyle = color;
202
+ mainContext.fillRect(screenPos.x, screenPos.y, size, size);
269
203
  }
204
+ mainContext.restore();
270
205
  }
271
206
  }
272
207
 
273
208
  ///////////////////////////////////////////////////////////////////////////////
274
209
  // parallax background mountain ranges
275
210
 
276
- let tileParallaxLayers;
277
-
278
- function initParallaxLayers()
211
+ class ParallaxLayer extends EngineObject
279
212
  {
280
- tileParallaxLayers = [];
281
- for (let i=3; i--;)
213
+ constructor(index)
282
214
  {
283
- // setup the layer
284
- const parallaxSize = vec2(600,300), startGroundLevel = rand(99,120)+i*30;
285
- const tileParallaxLayer = tileParallaxLayers[i] = new TileLayer(vec2(), parallaxSize);
286
- tileParallaxLayer.renderOrder = -3e3+i;
287
- tileParallaxLayer.canvas.width = parallaxSize.x;
215
+ super();
216
+
217
+ const size = vec2(1024,512);
218
+ this.size = size;
219
+ this.index = index;
220
+ this.renderOrder = -3e3 + index;
221
+ this.canvas = document.createElement('canvas');
222
+ this.context = this.canvas.getContext('2d');
223
+ this.canvas.width = size.x;
224
+ this.canvas.height = size.y;
288
225
 
289
226
  // create a gradient
290
- const layerColor = levelColor.mutate(.2).lerp(skyColor,.95-i*.15);
291
- const gradient = tileParallaxLayer.context.fillStyle =
292
- tileParallaxLayer.context.createLinearGradient(0,0,0,tileParallaxLayer.canvas.height = parallaxSize.y);
227
+ const o1 = 40-20*index;
228
+ const o2 = 100-30*index;
229
+ const gradient = this.context.createLinearGradient(0,size.y/2-o1,0,size.y/2+o2);
230
+ const layerColor = levelColor.mutate(.1).lerp(sky.skyColor,1-index*.15);
293
231
  gradient.addColorStop(0,layerColor);
294
- gradient.addColorStop(1,layerColor.subtract(new Color(1,1,1,0)).mutate(.1).clamp());
295
-
296
- // draw mountains ranges
297
- let groundLevel = startGroundLevel, groundSlope = rand(-1,1);
298
- for (let x=parallaxSize.x;x--;)
299
- tileParallaxLayer.context.fillRect(x,groundLevel += groundSlope = rand() < .05 ? rand(1, -1) :
300
- groundSlope + (startGroundLevel - groundLevel)/2e3, 1, parallaxSize.y)
232
+ gradient.addColorStop(1,layerColor.mutate(.5).scale(.1,1));
233
+ this.context.fillStyle = gradient;
234
+
235
+ // draw procedural mountains ranges
236
+ let groundLevel = size.y/2, groundSlope = rand(-1,1);
237
+ for (let i=size.x; i--;)
238
+ this.context.fillRect(i, groundLevel += groundSlope = rand() < .3 ? rand(1,-1) :
239
+ groundSlope + (size.y/2 - groundLevel)/300, 1, size.y);
301
240
  }
302
- }
303
241
 
304
- function updateParallaxLayers()
305
- {
306
- tileParallaxLayers.forEach((tileParallaxLayer, i)=>
242
+ render()
307
243
  {
308
- const distance = 4+i;
309
- const parallax = vec2(150,30).scale((i*i+1));
310
- const cameraDeltaFromCenter = cameraPos.subtract(levelSize.scale(.5)).divide(levelSize.scale(-.5).divide(parallax));
311
- tileParallaxLayer.scale = vec2(distance/cameraScale);
312
- tileParallaxLayer.pos = cameraPos
313
- .subtract(tileParallaxLayer.size.multiply(tileParallaxLayer.scale).scale(.5))
314
- .add(cameraDeltaFromCenter.scale(1/cameraScale))
315
- .subtract(vec2(0,150/cameraScale))
316
- });
244
+ // position layer based on camera distance from center of level
245
+ const parallax = vec2(1e3,-100).scale(this.index**2);
246
+ const cameraDeltaFromCenter = cameraPos
247
+ .subtract(levelSize.scale(.5))
248
+ .divide(levelSize.divide(parallax));
249
+ const scale = this.size.scale(2+2*this.index);
250
+ const pos = mainCanvasSize.scale(.5) // center screen
251
+ .add(vec2(-scale.x/2,-scale.y/2)) // center layer
252
+ .add(cameraDeltaFromCenter.scale(-.5)) // apply parallax
253
+ .add(vec2(0,(this.index*.1)*this.size.y)); // separate layers
254
+
255
+ // draw the parallax layer onto the main canvas
256
+ mainContext.drawImage(this.canvas, pos.x, pos.y, scale.x, scale.y);
257
+ }
317
258
  }