@@ -3,7 +3,7 @@ import Renderer from "./Renderer";
3
3
import Input from "./Input" ;
4
4
import LoudnessHandler from "./Loudness" ;
5
5
import Sound from "./Sound" ;
6
- import type { Stage , Sprite } from "./Sprite" ;
6
+ import { Stage , Sprite } from "./Sprite" ;
7
7
8
8
type TriggerWithTarget = {
9
9
target : Sprite | Stage ;
@@ -13,6 +13,13 @@ type TriggerWithTarget = {
13
13
export default class Project {
14
14
public stage : Stage ;
15
15
public sprites : Partial < Record < string , Sprite > > ;
16
+ /**
17
+ * All rendered targets (the stage, sprites, and clones), in layer order.
18
+ * This is kept private so that nobody can improperly modify it. The only way
19
+ * to add or remove targets is via the appropriate methods, and iteration can
20
+ * be done with {@link forEachTarget}.
21
+ */
22
+ private targets : ( Sprite | Stage ) [ ] ;
16
23
public renderer : Renderer ;
17
24
public input : Input ;
18
25
@@ -34,12 +41,29 @@ export default class Project {
34
41
this . stage = stage ;
35
42
this . sprites = sprites ;
36
43
37
- Object . freeze ( sprites ) ; // Prevent adding/removing sprites while project is running
44
+ this . targets = [
45
+ stage ,
46
+ ...Object . values ( this . sprites as Record < string , Sprite > ) ,
47
+ ] ;
48
+ this . targets . sort ( ( a , b ) => {
49
+ // There should only ever be one stage, but it's best to maintain a total
50
+ // ordering to avoid weird sorting-algorithm stuff from happening if
51
+ // there's more than one
52
+ if ( a instanceof Stage && ! ( b instanceof Stage ) ) {
53
+ return - 1 ;
54
+ }
55
+ if ( b instanceof Stage && ! ( a instanceof Stage ) ) {
56
+ return 1 ;
57
+ }
38
58
39
- for ( const sprite of this . spritesAndClones ) {
40
- sprite . _project = this ;
59
+ return a . getInitialLayerOrder ( ) - b . getInitialLayerOrder ( ) ;
60
+ } ) ;
61
+ for ( const target of this . targets ) {
62
+ target . clearInitialLayerOrder ( ) ;
63
+ target . _project = this ;
41
64
}
42
- this . stage . _project = this ;
65
+
66
+ Object . freeze ( sprites ) ; // Prevent adding/removing sprites while project is running
43
67
44
68
this . renderer = new Renderer ( this , null ) ;
45
69
this . input = new Input ( this . stage , this . renderer . stage , ( key ) => {
@@ -77,7 +101,7 @@ export default class Project {
77
101
void Sound . audioContext . resume ( ) ;
78
102
}
79
103
80
- let clickedSprite = this . renderer . pick ( this . spritesAndClones , {
104
+ let clickedSprite = this . renderer . pick ( this . targets , {
81
105
x : this . input . mouse . x ,
82
106
y : this . input . mouse . y ,
83
107
} ) ;
@@ -113,8 +137,7 @@ export default class Project {
113
137
triggerMatches : ( tr : Trigger , target : Sprite | Stage ) => boolean
114
138
) : TriggerWithTarget [ ] {
115
139
const matchingTriggers : TriggerWithTarget [ ] = [ ] ;
116
- const targets = this . spritesAndStage ;
117
- for ( const target of targets ) {
140
+ for ( const target of this . targets ) {
118
141
const matchingTargetTriggers = target . triggers . filter ( ( tr ) =>
119
142
triggerMatches ( tr , target )
120
143
) ;
@@ -198,15 +221,13 @@ export default class Project {
198
221
this . stopAllSounds ( ) ;
199
222
this . runningTriggers = [ ] ;
200
223
201
- for ( const spriteName in this . sprites ) {
202
- const sprite = this . sprites [ spriteName ] ! ;
203
- sprite . clones = [ ] ;
204
- }
224
+ this . filterSprites ( ( sprite ) => {
225
+ if ( ! sprite . isOriginal ) return false ;
205
226
206
- for ( const sprite of this . spritesAndStage ) {
207
227
sprite . effects . clear ( ) ;
208
228
sprite . audioEffects . clear ( ) ;
209
- }
229
+ return true ;
230
+ } ) ;
210
231
}
211
232
212
233
const matchingTriggers = this . _matchingTriggers ( ( tr , target ) =>
@@ -237,22 +258,49 @@ export default class Project {
237
258
) ;
238
259
}
239
260
240
- public get spritesAndClones ( ) : Sprite [ ] {
241
- return Object . values ( this . sprites )
242
- . flatMap ( ( sprite ) => sprite ! . andClones ( ) )
243
- . sort ( ( a , b ) => a . _layerOrder - b . _layerOrder ) ;
261
+ public addSprite ( sprite : Sprite , behind ?: Sprite ) : void {
262
+ if ( behind ) {
263
+ const currentIndex = this . targets . indexOf ( behind ) ;
264
+ this . targets . splice ( currentIndex , 0 , sprite ) ;
265
+ } else {
266
+ this . targets . push ( sprite ) ;
267
+ }
268
+ }
269
+
270
+ public removeSprite ( sprite : Sprite ) : void {
271
+ const index = this . targets . indexOf ( sprite ) ;
272
+ if ( index === - 1 ) return ;
273
+
274
+ this . targets . splice ( index , 1 ) ;
275
+ this . cleanupSprite ( sprite ) ;
276
+ }
277
+
278
+ public filterSprites ( predicate : ( sprite : Sprite ) => boolean ) : void {
279
+ let nextKeptSpriteIndex = 0 ;
280
+ for ( let i = 0 ; i < this . targets . length ; i ++ ) {
281
+ const target = this . targets [ i ] ;
282
+ if ( target instanceof Stage || predicate ( target ) ) {
283
+ this . targets [ nextKeptSpriteIndex ] = target ;
284
+ nextKeptSpriteIndex ++ ;
285
+ } else {
286
+ this . cleanupSprite ( target ) ;
287
+ }
288
+ }
289
+ this . targets . length = nextKeptSpriteIndex ;
244
290
}
245
291
246
- public get spritesAndStage ( ) : ( Sprite | Stage ) [ ] {
247
- return [ ...this . spritesAndClones , this . stage ] ;
292
+ private cleanupSprite ( sprite : Sprite ) : void {
293
+ this . runningTriggers = this . runningTriggers . filter (
294
+ ( { target } ) => target !== sprite
295
+ ) ;
248
296
}
249
297
250
298
public changeSpriteLayer (
251
299
sprite : Sprite ,
252
300
layerDelta : number ,
253
301
relativeToSprite = sprite
254
302
) : void {
255
- const spritesArray = this . spritesAndClones ;
303
+ const spritesArray = this . targets ;
256
304
257
305
const originalIndex = spritesArray . indexOf ( sprite ) ;
258
306
const relativeToIndex = spritesArray . indexOf ( relativeToSprite ) ;
@@ -264,17 +312,16 @@ export default class Project {
264
312
// Remove sprite from originalIndex and insert at newIndex
265
313
spritesArray . splice ( originalIndex , 1 ) ;
266
314
spritesArray . splice ( newIndex , 0 , sprite ) ;
315
+ }
267
316
268
- // spritesArray is sorted correctly, but to influence
269
- // the actual order of the sprites we need to update
270
- // each one's _layerOrder property.
271
- spritesArray . forEach ( ( sprite , index ) => {
272
- sprite . _layerOrder = index + 1 ;
273
- } ) ;
317
+ public forEachTarget ( callback : ( target : Sprite | Stage ) => void ) : void {
318
+ for ( const target of this . targets ) {
319
+ callback ( target ) ;
320
+ }
274
321
}
275
322
276
323
public stopAllSounds ( ) : void {
277
- for ( const target of this . spritesAndStage ) {
324
+ for ( const target of this . targets ) {
278
325
target . stopAllOfMySounds ( ) ;
279
326
}
280
327
}
0 commit comments