littlejsengine 1.22.0 → 1.23.1
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/COPYRIGHT.txt +65 -38
- package/EDITOR.md +359 -0
- package/README.md +12 -7
- package/REFERENCE.md +2314 -0
- package/dist/littlejs.d.ts +640 -100
- package/dist/littlejs.esm.js +3596 -1444
- package/dist/littlejs.esm.min.js +3 -3
- package/dist/littlejs.js +3583 -1444
- package/dist/littlejs.min.js +2 -2
- package/dist/littlejs.release.js +2106 -1355
- package/package.json +4 -3
- package/plugins/gltf.js +29 -13
- package/plugins/lightSystem.js +8 -6
- package/plugins/parallax.js +174 -0
- package/plugins/particleEffects.js +37 -22
- package/plugins/pluginExport.js +8 -0
- package/plugins/postProcess.js +5 -2
- package/plugins/render3d.js +59 -21
- package/plugins/render3dEditor.js +1058 -118
- package/plugins/render3dExtras.js +82 -21
- package/plugins/render3dLevel.js +328 -8
- package/plugins/textureSheet.js +1 -0
- package/plugins/tweenSystem.js +39 -34
- package/plugins/uiSystem.js +7 -3
- package/plugins/zzfxm.js +161 -159
- package/src/engine.js +1 -1
- package/src/engineAudio.js +17 -3
- package/src/engineBuild.mjs +18 -2
- package/src/engineDraw.js +26 -10
- package/src/engineEditor.js +515 -52
- package/src/engineExport.js +5 -0
- package/src/engineInput.js +48 -6
- package/src/engineObject.js +675 -675
- package/src/engineRelease.js +17 -2
- package/src/engineSettings.js +14 -0
- package/src/engineTileLayer.js +2 -1
- package/src/engineUtilities.js +291 -291
- package/src/engineWebGL.js +12 -3
package/plugins/tweenSystem.js
CHANGED
|
@@ -593,45 +593,50 @@ function tweenUpdate(gameDelta, realDelta)
|
|
|
593
593
|
const list = tweenUpdateList.length ? [] : tweenUpdateList, pass = ++tweenUpdatePass;
|
|
594
594
|
for (const t of tweenActive)
|
|
595
595
|
list.push(t);
|
|
596
|
-
|
|
596
|
+
// the list is let go however the walk ends: a callback that throws must not leave it held, or every
|
|
597
|
+
// update after would take it for an update still going and make a list of its own
|
|
598
|
+
try
|
|
597
599
|
{
|
|
598
|
-
|
|
599
|
-
// stopped, or started again by a callback this update, or during an update a callback ran inside it, which
|
|
600
|
-
// counts on from this one
|
|
601
|
-
if (!t.active || t.activePass >= pass) continue;
|
|
602
|
-
let dt;
|
|
603
|
-
if (enginePath)
|
|
600
|
+
for (let i = list.length; i--;)
|
|
604
601
|
{
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
t.
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
602
|
+
const t = list[i];
|
|
603
|
+
// stopped, or started again by a callback this update, or during an update a callback ran inside it, which
|
|
604
|
+
// counts on from this one
|
|
605
|
+
if (!t.active || t.activePass >= pass) continue;
|
|
606
|
+
let dt;
|
|
607
|
+
if (enginePath)
|
|
608
|
+
{
|
|
609
|
+
// a paused tween keeps count too, so it does not jump when resumed
|
|
610
|
+
dt = t.useRealTime ? timeReal - t.lastTimeReal : time - t.lastTime;
|
|
611
|
+
t.lastTime = time;
|
|
612
|
+
t.lastTimeReal = timeReal;
|
|
613
|
+
}
|
|
614
|
+
else
|
|
615
|
+
dt = t.useRealTime ? realDelta : gameDelta;
|
|
616
|
+
if (t.target?.destroyed) { t.stop(); continue; } // its object is gone, paused or not
|
|
617
|
+
if (t.paused || dt <= 0) continue;
|
|
614
618
|
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
619
|
+
t.life -= dt;
|
|
620
|
+
if (t.life > 1e-9) // the engine's deltas add up a rounding error short of the duration
|
|
621
|
+
{
|
|
622
|
+
t.callback(t.interp(t.life));
|
|
623
|
+
}
|
|
624
|
+
else
|
|
625
|
+
{
|
|
626
|
+
// Completion: fire end value, remove from active, start the next iteration
|
|
627
|
+
// of a loop or pingPong, or when there is none it has completed, fire onComplete
|
|
628
|
+
t.callback(t.interp(0));
|
|
629
|
+
if (!t.active || t.activePass >= pass)
|
|
630
|
+
continue; // stopped or restarted by its own callback, the run it was on ends without completing
|
|
631
|
+
tweenDeactivate(t);
|
|
632
|
+
const next = t.thenCallback;
|
|
633
|
+
t.thenCallback = undefined;
|
|
634
|
+
if (!(next && next()) && t.onComplete)
|
|
635
|
+
t.onComplete();
|
|
636
|
+
}
|
|
632
637
|
}
|
|
633
638
|
}
|
|
634
|
-
list.length = 0;
|
|
639
|
+
finally { list.length = 0; }
|
|
635
640
|
}
|
|
636
641
|
|
|
637
642
|
/** Stop every active tween, ending loops too, without calling their then-callbacks.
|
package/plugins/uiSystem.js
CHANGED
|
@@ -615,8 +615,7 @@ class UISystemPlugin
|
|
|
615
615
|
}
|
|
616
616
|
}
|
|
617
617
|
|
|
618
|
-
/** Destroy and remove all objects
|
|
619
|
-
* @memberof UISystem */
|
|
618
|
+
/** Destroy and remove all objects */
|
|
620
619
|
destroyObjects()
|
|
621
620
|
{
|
|
622
621
|
for (const o of this.uiObjects)
|
|
@@ -1137,8 +1136,10 @@ class UIObject
|
|
|
1137
1136
|
return void inputClearKey(0, 0, false, true, false);
|
|
1138
1137
|
}
|
|
1139
1138
|
}
|
|
1139
|
+
// the object that was the active one going into this update: one pressed and let go inside a
|
|
1140
|
+
// frame is clicked on the next, with its release, not on both
|
|
1140
1141
|
if (!uiSystem.activateOnPress)
|
|
1141
|
-
if (!mouseDown && this.isActiveObject() && this.interactive)
|
|
1142
|
+
if (!mouseDown && isActive && this.isActiveObject() && this.interactive)
|
|
1142
1143
|
this.click();
|
|
1143
1144
|
if (this.destroyed) return;
|
|
1144
1145
|
}
|
|
@@ -1881,7 +1882,10 @@ class UIVideo extends UIObject
|
|
|
1881
1882
|
if (this.destroyed)
|
|
1882
1883
|
return;
|
|
1883
1884
|
|
|
1885
|
+
// let go of the media too, a paused video keeps what it has loaded
|
|
1884
1886
|
this.video.pause();
|
|
1887
|
+
this.video.removeAttribute?.('src');
|
|
1888
|
+
this.video.load?.();
|
|
1885
1889
|
this.video.remove();
|
|
1886
1890
|
super.destroy();
|
|
1887
1891
|
}
|
package/plugins/zzfxm.js
CHANGED
|
@@ -1,160 +1,162 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* LittleJS ZzFXM Plugin
|
|
3
|
-
*
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* @
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* ]
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
* @param {Array}
|
|
64
|
-
* @param {
|
|
65
|
-
* @
|
|
66
|
-
* @
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
let
|
|
72
|
-
let
|
|
73
|
-
let
|
|
74
|
-
let
|
|
75
|
-
let
|
|
76
|
-
let
|
|
77
|
-
let
|
|
78
|
-
let
|
|
79
|
-
let
|
|
80
|
-
let
|
|
81
|
-
let
|
|
82
|
-
let
|
|
83
|
-
let
|
|
84
|
-
let
|
|
85
|
-
let
|
|
86
|
-
let
|
|
87
|
-
let
|
|
88
|
-
let
|
|
89
|
-
let
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
//
|
|
108
|
-
|
|
109
|
-
for
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
1
|
+
/**
|
|
2
|
+
* LittleJS ZzFXM Plugin
|
|
3
|
+
* - A port of ZzFXM, the Zuper Zmall Zound Zynth music player, by Keith Clark and Frank Force, MIT licensed,
|
|
4
|
+
* https://github.com/keithclark/ZzFXM; its notice is in COPYRIGHT.txt
|
|
5
|
+
* @namespace ZzFXM
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
'use strict';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Music Object - Stores a zzfx music track for later use
|
|
12
|
+
*
|
|
13
|
+
* <a href=https://keithclark.github.io/ZzFXM/>Create music with the ZzFXM tracker.</a>
|
|
14
|
+
* @extends Sound
|
|
15
|
+
* @memberof ZzFXM
|
|
16
|
+
* @example
|
|
17
|
+
* // create some music
|
|
18
|
+
* const music_example = new ZzFXMusic(
|
|
19
|
+
* [
|
|
20
|
+
* [ // instruments
|
|
21
|
+
* [,0,400] // simple note
|
|
22
|
+
* ],
|
|
23
|
+
* [ // patterns
|
|
24
|
+
* [ // pattern 1
|
|
25
|
+
* [ // channel 0
|
|
26
|
+
* 0, -1, // instrument 0, left speaker
|
|
27
|
+
* 1, 0, 9, 1 // channel notes
|
|
28
|
+
* ],
|
|
29
|
+
* [ // channel 1
|
|
30
|
+
* 0, 1, // instrument 0, right speaker
|
|
31
|
+
* 0, 12, 17, -1 // channel notes
|
|
32
|
+
* ]
|
|
33
|
+
* ],
|
|
34
|
+
* ],
|
|
35
|
+
* [0, 0, 0, 0], // sequence, play pattern 0 four times
|
|
36
|
+
* 90 // BPM
|
|
37
|
+
* ]);
|
|
38
|
+
*
|
|
39
|
+
* // play the music on a loop
|
|
40
|
+
* music_example.playMusic();
|
|
41
|
+
*/
|
|
42
|
+
class ZzFXMusic extends Sound
|
|
43
|
+
{
|
|
44
|
+
/** Create a music object and cache the zzfx music samples for later use
|
|
45
|
+
* @param {[Array, Array, Array, number?, ...any[]]} zzfxMusic - Array of zzfx music parameters: instruments, patterns, sequence, and an optional BPM; anything after it, like the tracker's metadata, is ignored
|
|
46
|
+
*/
|
|
47
|
+
constructor(zzfxMusic)
|
|
48
|
+
{
|
|
49
|
+
super(undefined);
|
|
50
|
+
|
|
51
|
+
if (!soundEnable || headlessMode) return;
|
|
52
|
+
const [instruments, patterns, sequence, BPM] = zzfxMusic;
|
|
53
|
+
super.sampleChannels = zzfxM(instruments, patterns, sequence, BPM); // the setter, without declaring a field that hides it in the typings
|
|
54
|
+
this.buildSampleBuffer(); // hand the samples to an audio buffer now, like a zzfx sound, so the arrays are released
|
|
55
|
+
this.loadedPercent = 1; // generated in place, so it is loaded like a zzfx sound
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
///////////////////////////////////////////////////////////////////////////////
|
|
60
|
+
// ZzFX Music Renderer v2.0.3 by Keith Clark and Frank Force
|
|
61
|
+
|
|
62
|
+
/** Generate samples for a ZzFM song with given parameters
|
|
63
|
+
* @param {Array} instruments - Array of ZzFX sound parameters
|
|
64
|
+
* @param {Array} patterns - Array of pattern data
|
|
65
|
+
* @param {Array} sequence - Array of pattern indexes
|
|
66
|
+
* @param {number} [BPM] - Playback speed of the song in BPM
|
|
67
|
+
* @return {Array} - Left and right channel sample data
|
|
68
|
+
* @memberof ZzFXM */
|
|
69
|
+
function zzfxM(instruments, patterns, sequence, BPM = 125)
|
|
70
|
+
{
|
|
71
|
+
let i, j, k;
|
|
72
|
+
let instrumentParameters;
|
|
73
|
+
let note;
|
|
74
|
+
let sample;
|
|
75
|
+
let patternChannel;
|
|
76
|
+
let notFirstBeat;
|
|
77
|
+
let stop;
|
|
78
|
+
let instrument;
|
|
79
|
+
let attenuation;
|
|
80
|
+
let outSampleOffset;
|
|
81
|
+
let isSequenceEnd;
|
|
82
|
+
let sampleOffset = 0;
|
|
83
|
+
let nextSampleOffset;
|
|
84
|
+
let sampleBuffer = [];
|
|
85
|
+
let leftChannelBuffer = [];
|
|
86
|
+
let rightChannelBuffer = [];
|
|
87
|
+
let channelIndex = 0;
|
|
88
|
+
let panning = 0;
|
|
89
|
+
let hasMore = 1;
|
|
90
|
+
let sampleCache = {};
|
|
91
|
+
let beatLength = audioDefaultSampleRate / BPM * 60 >> 2;
|
|
92
|
+
|
|
93
|
+
// for each channel in order until there are no more
|
|
94
|
+
for (; hasMore; channelIndex++) {
|
|
95
|
+
|
|
96
|
+
// reset current values
|
|
97
|
+
sampleBuffer = [hasMore = notFirstBeat = outSampleOffset = 0];
|
|
98
|
+
|
|
99
|
+
// for each pattern in sequence
|
|
100
|
+
sequence.forEach((patternIndex, sequenceIndex)=> {
|
|
101
|
+
// get pattern for current channel, use empty 1 note pattern if none found
|
|
102
|
+
patternChannel = patterns[patternIndex][channelIndex] || [0, 0, 0];
|
|
103
|
+
|
|
104
|
+
// check if there are more channels
|
|
105
|
+
hasMore |= patterns[patternIndex][channelIndex]&&1;
|
|
106
|
+
|
|
107
|
+
// get next offset, use the length of first channel
|
|
108
|
+
nextSampleOffset = outSampleOffset + (patterns[patternIndex][0].length - 2 - (notFirstBeat?0:1)) * beatLength;
|
|
109
|
+
// for each beat in pattern, plus one extra if end of sequence
|
|
110
|
+
isSequenceEnd = sequenceIndex === sequence.length - 1;
|
|
111
|
+
for (i = 2, k = outSampleOffset; i < patternChannel.length + isSequenceEnd; notFirstBeat = ++i) {
|
|
112
|
+
|
|
113
|
+
// <channel-note>
|
|
114
|
+
note = patternChannel[i];
|
|
115
|
+
|
|
116
|
+
// stop if end, different instrument or new note
|
|
117
|
+
stop = i === patternChannel.length + isSequenceEnd - 1 && isSequenceEnd ||
|
|
118
|
+
instrument !== (patternChannel[0] || 0) || note | 0;
|
|
119
|
+
|
|
120
|
+
// fill buffer with samples for previous beat, most cpu intensive part
|
|
121
|
+
for (j = 0; j < beatLength && notFirstBeat;
|
|
122
|
+
|
|
123
|
+
// fade off attenuation at end of beat if stopping note, prevents clicking
|
|
124
|
+
j++ > beatLength - 99 && stop && attenuation < 1? attenuation += 1 / 99 : 0
|
|
125
|
+
) {
|
|
126
|
+
// copy sample to stereo buffers with panning
|
|
127
|
+
sample = (1 - attenuation) * sampleBuffer[sampleOffset++] / 2 || 0;
|
|
128
|
+
leftChannelBuffer[k] = (leftChannelBuffer[k] || 0) - sample * panning + sample;
|
|
129
|
+
rightChannelBuffer[k] = (rightChannelBuffer[k++] || 0) + sample * panning + sample;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// set up for next note
|
|
133
|
+
if (note) {
|
|
134
|
+
// set attenuation
|
|
135
|
+
attenuation = note % 1;
|
|
136
|
+
panning = patternChannel[1] || 0;
|
|
137
|
+
if (note |= 0) {
|
|
138
|
+
// get cached sample
|
|
139
|
+
sampleBuffer = sampleCache[
|
|
140
|
+
[
|
|
141
|
+
instrument = patternChannel[sampleOffset = 0] || 0,
|
|
142
|
+
note
|
|
143
|
+
]
|
|
144
|
+
] = sampleCache[[instrument, note]] || (
|
|
145
|
+
// add sample to cache
|
|
146
|
+
instrumentParameters = [...instruments[instrument]],
|
|
147
|
+
instrumentParameters[2] = (instrumentParameters[2] || 220) * 2**(note / 12 - 1),
|
|
148
|
+
|
|
149
|
+
// allow negative values to stop notes
|
|
150
|
+
note > 0 ? zzfxG(...instrumentParameters) : []
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// update the sample offset
|
|
157
|
+
outSampleOffset = nextSampleOffset;
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return [leftChannelBuffer, rightChannelBuffer];
|
|
160
162
|
}
|
package/src/engine.js
CHANGED
package/src/engineAudio.js
CHANGED
|
@@ -222,7 +222,13 @@ class Sound
|
|
|
222
222
|
*/
|
|
223
223
|
constructor(asset, randomness, range, taper=soundDefaultTaper, onloadCallback)
|
|
224
224
|
{
|
|
225
|
-
if (!soundEnable || headlessMode)
|
|
225
|
+
if (!soundEnable || headlessMode)
|
|
226
|
+
{
|
|
227
|
+
// no sound is made: it counts as loaded, so a game that waits for its sounds goes on
|
|
228
|
+
this.loadedPercent = 1;
|
|
229
|
+
onloadCallback?.(this);
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
226
232
|
const rangeIsDefault = range === undefined;
|
|
227
233
|
if (rangeIsDefault)
|
|
228
234
|
range = soundDefaultRange;
|
|
@@ -233,10 +239,9 @@ class Sound
|
|
|
233
239
|
ASSERT(isNumber(range), 'range must be a number');
|
|
234
240
|
ASSERT(isNumber(taper), 'taper must be a number');
|
|
235
241
|
|
|
236
|
-
/** @property {number} - World space max range of sound */
|
|
237
242
|
this.range = range;
|
|
238
243
|
/** @property {boolean} - No range was given, so it got soundDefaultRange; played in 3D such a sound is heard
|
|
239
|
-
* to render3D.soundDefaultRange instead, which is further */
|
|
244
|
+
* to render3D.soundDefaultRange instead, which is further; setting range makes it false */
|
|
240
245
|
this.rangeIsDefault = rangeIsDefault;
|
|
241
246
|
/** @property {number} - At what percentage of range should it start tapering */
|
|
242
247
|
this.taper = taper;
|
|
@@ -292,6 +297,15 @@ class Sound
|
|
|
292
297
|
}
|
|
293
298
|
}
|
|
294
299
|
|
|
300
|
+
/** World space max range of sound, 0 for no limit; a range that is set is the sound's own, in 3D too
|
|
301
|
+
* @type {number} */
|
|
302
|
+
get range() { return this.rangeValue; }
|
|
303
|
+
set range(range)
|
|
304
|
+
{
|
|
305
|
+
this.rangeValue = range;
|
|
306
|
+
this.rangeIsDefault = false;
|
|
307
|
+
}
|
|
308
|
+
|
|
295
309
|
/** Sample data for each channel
|
|
296
310
|
* Sounds keep their samples in an audio buffer, so reading this rebuilds
|
|
297
311
|
* the arrays from it and caches them. The copies are safe to hold onto,
|
package/src/engineBuild.mjs
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* - Produces TypeScript definition file (.d.ts)
|
|
7
7
|
* - Includes plugin files in builds
|
|
8
8
|
* - Validates code and checks for errors
|
|
9
|
-
* - Outputs to dist/ folder
|
|
9
|
+
* - Outputs to dist/ folder, which is left as it was when a step fails
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import fs from 'node:fs';
|
|
@@ -19,7 +19,9 @@ const __dirname = dirname(__filename);
|
|
|
19
19
|
const ROOT_DIR = join(__dirname, '..');
|
|
20
20
|
|
|
21
21
|
const ENGINE_NAME = 'littlejs';
|
|
22
|
-
|
|
22
|
+
// built in a folder of its own and put in dist when all of it is there, so a step that fails leaves dist whole
|
|
23
|
+
const OUTPUT_FOLDER = join(ROOT_DIR, 'dist');
|
|
24
|
+
const BUILD_FOLDER = join(ROOT_DIR, 'dist.build');
|
|
23
25
|
const SOURCE_FOLDER = join(ROOT_DIR, 'src');
|
|
24
26
|
const PLUGIN_FOLDER = join(ROOT_DIR, 'plugins');
|
|
25
27
|
const engineSourceFiles =
|
|
@@ -50,6 +52,7 @@ const enginePluginFiles =
|
|
|
50
52
|
`${PLUGIN_FOLDER}/textureSheet.js`,
|
|
51
53
|
`${PLUGIN_FOLDER}/tweenSystem.js`,
|
|
52
54
|
`${PLUGIN_FOLDER}/sceneSystem.js`,
|
|
55
|
+
`${PLUGIN_FOLDER}/parallax.js`,
|
|
53
56
|
`${PLUGIN_FOLDER}/pathFinder.js`,
|
|
54
57
|
`${PLUGIN_FOLDER}/math3d.js`,
|
|
55
58
|
`${PLUGIN_FOLDER}/render3d.js`,
|
|
@@ -103,6 +106,19 @@ catch (e) { handleError(e, 'Failed to create build folder!'); }
|
|
|
103
106
|
// Build all versions
|
|
104
107
|
await buildAll();
|
|
105
108
|
|
|
109
|
+
try
|
|
110
|
+
{
|
|
111
|
+
// every step is done: dist is given what was built, and loses what is no longer built
|
|
112
|
+
fs.mkdirSync(OUTPUT_FOLDER, { recursive: true });
|
|
113
|
+
const built = fs.readdirSync(BUILD_FOLDER);
|
|
114
|
+
for (const file of built)
|
|
115
|
+
fs.copyFileSync(join(BUILD_FOLDER, file), join(OUTPUT_FOLDER, file));
|
|
116
|
+
for (const file of fs.readdirSync(OUTPUT_FOLDER))
|
|
117
|
+
built.includes(file) || fs.rmSync(join(OUTPUT_FOLDER, file), { recursive: true, force: true });
|
|
118
|
+
fs.rmSync(BUILD_FOLDER, { recursive: true, force: true });
|
|
119
|
+
}
|
|
120
|
+
catch (e) { handleError(e, 'Failed to put the build in dist!'); }
|
|
121
|
+
|
|
106
122
|
console.log(`Engine built in ${((Date.now() - startTime)/1e3).toFixed(2)} seconds! ✨`);
|
|
107
123
|
|
|
108
124
|
///////////////////////////////////////////////////////////////////////////////
|
package/src/engineDraw.js
CHANGED
|
@@ -496,17 +496,29 @@ class SpriteAnimator
|
|
|
496
496
|
* @return {SpriteAnimation} */
|
|
497
497
|
get clip() { return this.clips[this.name]; }
|
|
498
498
|
|
|
499
|
+
/** The clip showing now, once a clip that has ended has called its onEnd: the callback may switch clips, and a
|
|
500
|
+
* read gives the clip it switched to, not the one that ended
|
|
501
|
+
* @return {SpriteAnimation}
|
|
502
|
+
* @ignore */
|
|
503
|
+
get settledClip()
|
|
504
|
+
{
|
|
505
|
+
// each clip's own isDone calls its onEnd once; a few switches in a row at most
|
|
506
|
+
for (let i = 4, clip; i-- && clip !== this.clip;)
|
|
507
|
+
(clip = this.clip).isDone;
|
|
508
|
+
return this.clip;
|
|
509
|
+
}
|
|
510
|
+
|
|
499
511
|
/** The tile of the frame showing now
|
|
500
512
|
* @return {TileInfo} */
|
|
501
|
-
get tileInfo() { return this.
|
|
513
|
+
get tileInfo() { return this.settledClip.tileInfo; }
|
|
502
514
|
|
|
503
515
|
/** The frame of the clip showing now
|
|
504
516
|
* @return {number} */
|
|
505
|
-
get frame() { return this.
|
|
517
|
+
get frame() { return this.settledClip.frame; }
|
|
506
518
|
|
|
507
519
|
/** True once the clip showing now is a play that has ended
|
|
508
520
|
* @return {boolean} */
|
|
509
|
-
get isDone() { return this.
|
|
521
|
+
get isDone() { return this.settledClip.isDone; }
|
|
510
522
|
}
|
|
511
523
|
|
|
512
524
|
///////////////////////////////////////////////////////////////////////////////
|
|
@@ -931,7 +943,7 @@ function drawRegularPoly(pos, size=vec2(1), sides=3, color=WHITE, lineWidth=0, l
|
|
|
931
943
|
// build regular polygon points
|
|
932
944
|
const points = [];
|
|
933
945
|
const sizeX = size.x/2, sizeY = size.y/2;
|
|
934
|
-
for (let i=sides; i
|
|
946
|
+
for (let i=sides; i-- > 0;) // a count that is not whole, or below zero, still ends
|
|
935
947
|
{
|
|
936
948
|
const a = (i/sides)*PI*2;
|
|
937
949
|
points.push(vec2(sin(a)*sizeX, cos(a)*sizeY));
|
|
@@ -1042,7 +1054,7 @@ function drawEllipse(pos, size=vec2(1), color=WHITE, angle=0, lineWidth=0, lineC
|
|
|
1042
1054
|
if (!ring)
|
|
1043
1055
|
{
|
|
1044
1056
|
const points = [];
|
|
1045
|
-
for (let i=sides; i
|
|
1057
|
+
for (let i=sides; i-- > 0;)
|
|
1046
1058
|
{
|
|
1047
1059
|
const a = (i/sides)*PI*2;
|
|
1048
1060
|
points.push(vec2(sin(a), cos(a)));
|
|
@@ -1142,7 +1154,7 @@ function drawEllipseGradient(pos, size=vec2(1), colorInner=WHITE, colorOuter=CLE
|
|
|
1142
1154
|
const startA = (offset%sides)/sides*PI*2;
|
|
1143
1155
|
const points = [rim(startA)];
|
|
1144
1156
|
const colors = [outerInt];
|
|
1145
|
-
for (let i=sides; i
|
|
1157
|
+
for (let i=sides; i-- > 0;)
|
|
1146
1158
|
{
|
|
1147
1159
|
const a = ((i+offset)%sides)/sides*PI*2;
|
|
1148
1160
|
points.push(pos);
|
|
@@ -1565,7 +1577,8 @@ function isOnScreen(pos, size=0)
|
|
|
1565
1577
|
function setAdditiveBlendMode(additive=true)
|
|
1566
1578
|
{
|
|
1567
1579
|
glAdditive = additive;
|
|
1568
|
-
drawContext
|
|
1580
|
+
if (drawContext) // none headless
|
|
1581
|
+
drawContext.globalCompositeOperation = additive ? 'lighter' : 'source-over';
|
|
1569
1582
|
}
|
|
1570
1583
|
|
|
1571
1584
|
/** Set the Shader that 2D draws use from now on, none for the engine's own
|
|
@@ -1688,9 +1701,12 @@ function drawImageColor(context, image, sx, sy, sWidth, sHeight, dx, dy, dWidth,
|
|
|
1688
1701
|
}
|
|
1689
1702
|
else
|
|
1690
1703
|
{
|
|
1691
|
-
// copy to offscreen canvas
|
|
1692
|
-
|
|
1693
|
-
workReadCanvas.height
|
|
1704
|
+
// copy to offscreen canvas, sized again only when the size changes: setting a size remakes the canvas even
|
|
1705
|
+
// to the same one, and a game's tinted tiles are mostly of one size
|
|
1706
|
+
if (workReadCanvas.width !== sWidth || workReadCanvas.height !== sHeight)
|
|
1707
|
+
workReadCanvas.width = sWidth, workReadCanvas.height = sHeight;
|
|
1708
|
+
else
|
|
1709
|
+
workReadContext.clearRect(0, 0, sWidth, sHeight);
|
|
1694
1710
|
workReadContext.drawImage(image, sx|0, sy|0, sWidth, sHeight, 0, 0, sWidth, sHeight);
|
|
1695
1711
|
|
|
1696
1712
|
// tint image using offscreen work context
|