@@ -7,6 +7,7 @@ const replayLog = require('./replay');
7
7
const constants = require ( './constants' ) ;
8
8
const modifySongData = require ( './modifySongData' ) ;
9
9
const ResourceLoader = require ( './ResourceLoader' ) ;
10
+ const ExternalDancerLayer = require ( './ExternalDancer' ) ;
10
11
11
12
function Behavior ( func , id , extraArgs ) {
12
13
if ( ! extraArgs ) {
@@ -54,6 +55,7 @@ module.exports = class DanceParty {
54
55
// For testing: Can provide a custom resource loader class
55
56
// to load fixtures and/or isolate us entirely from network activity
56
57
resourceLoader = new ResourceLoader ( ) ,
58
+ externalRendererFactory = ( ) => null ,
57
59
} ) {
58
60
this . onHandleEvents = onHandleEvents ;
59
61
this . onInit = onInit ;
@@ -155,6 +157,8 @@ module.exports = class DanceParty {
155
157
156
158
this . logger = logger ;
157
159
160
+ this . createExternalRenderer = externalRendererFactory ;
161
+
158
162
new P5 ( p5Inst => {
159
163
this . p5_ = p5Inst ;
160
164
this . resourceLoader_ . initWithP5 ( p5Inst ) ;
@@ -320,6 +324,8 @@ module.exports = class DanceParty {
320
324
backgroundEffect . reset ( ) ;
321
325
}
322
326
327
+ this . externalLayer ?. setSource ( null ) ;
328
+
323
329
let foregroundEffect = this . getForegroundEffect ( ) ;
324
330
if ( foregroundEffect && foregroundEffect . reset ) {
325
331
foregroundEffect . reset ( ) ;
@@ -353,11 +359,28 @@ module.exports = class DanceParty {
353
359
}
354
360
355
361
setup ( ) {
356
- this . bgEffects_ = new BackgroundEffects ( this . p5_ , this . getEffectsInPreviewMode . bind ( this ) , this . extraImages ) ;
357
- this . fgEffects_ = new ForegroundEffects ( this . p5_ , this . getEffectsInPreviewMode . bind ( this ) ) ;
362
+ this . bgEffects_ = new BackgroundEffects (
363
+ this . p5_ ,
364
+ this . getEffectsInPreviewMode . bind ( this ) ,
365
+ this . extraImages
366
+ ) ;
367
+ this . fgEffects_ = new ForegroundEffects (
368
+ this . p5_ ,
369
+ this . getEffectsInPreviewMode . bind ( this )
370
+ ) ;
358
371
359
372
this . performanceData_ . initTime = timeSinceLoad ( ) ;
360
373
this . onInit && this . onInit ( this ) ;
374
+
375
+ const externalRenderer = this . createExternalRenderer ( ) ;
376
+ if ( externalRenderer ) {
377
+ this . externalLayer = new ExternalDancerLayer (
378
+ this . p5_ ,
379
+ this . p5_ . width ,
380
+ this . p5_ . height ,
381
+ externalRenderer
382
+ ) ;
383
+ }
361
384
}
362
385
363
386
getBackgroundEffect ( ) {
@@ -384,7 +407,8 @@ module.exports = class DanceParty {
384
407
this . analysisPosition_ = 0 ;
385
408
this . songStartTime_ = new Date ( ) ;
386
409
this . loopAnalysisEvents = true ;
387
- this . livePreviewStopTime = durationMs === undefined ? 0 : Date . now ( ) + durationMs ;
410
+ this . livePreviewStopTime =
411
+ durationMs === undefined ? 0 : Date . now ( ) + durationMs ;
388
412
this . p5_ . loop ( ) ;
389
413
}
390
414
@@ -496,7 +520,6 @@ module.exports = class DanceParty {
496
520
sprite . looping_move = 0 ;
497
521
sprite . looping_frame = 0 ;
498
522
sprite . current_move = 0 ;
499
- sprite . previous_move = 0 ; // I don't think this is used?
500
523
501
524
for ( var i = 0 ; i < this . animations [ costume ] . length ; i ++ ) {
502
525
sprite . addAnimation ( 'anim' + i , this . animations [ costume ] [ i ] . animation ) ;
@@ -520,9 +543,26 @@ module.exports = class DanceParty {
520
543
sprite . sinceLastFrame -= msPerFrame ;
521
544
sprite . looping_frame ++ ;
522
545
if ( sprite . animation . looping ) {
523
- sprite . animation . changeFrame (
524
- sprite . looping_frame % sprite . animation . images . length
525
- ) ;
546
+ const animationLength = sprite . animation . images . length ;
547
+ const currentMeasure = this . getCurrentMeasure ( ) ;
548
+ if ( currentMeasure < 1 ) {
549
+ sprite . earlyStart = true ;
550
+ const measureTick =
551
+ ( Math . max ( 0 , currentMeasure ) * sprite . dance_speed * 2 ) % 1 ;
552
+ const measureFrame = Math . min (
553
+ animationLength - 1 ,
554
+ Math . floor ( measureTick * animationLength )
555
+ ) ;
556
+ sprite . animation . changeFrame ( measureFrame ) ;
557
+ } else {
558
+ if ( ! sprite . hasStarted && sprite . earlyStart ) {
559
+ sprite . looping_frame = 0 ;
560
+ sprite . hasStarted = true ;
561
+ }
562
+ sprite . animation . changeFrame (
563
+ sprite . looping_frame % animationLength
564
+ ) ;
565
+ }
526
566
} else {
527
567
sprite . animation . nextFrame ( ) ;
528
568
}
@@ -541,7 +581,6 @@ module.exports = class DanceParty {
541
581
currentFrame === sprite . animation . getLastFrame ( ) &&
542
582
! sprite . animation . looping
543
583
) {
544
- //changeMoveLR(sprite, sprite.current_move, sprite.mirroring);
545
584
sprite . changeAnimation ( 'anim' + sprite . current_move ) ;
546
585
sprite . animation . changeFrame (
547
586
sprite . looping_frame % sprite . animation . images . length
@@ -686,6 +725,7 @@ module.exports = class DanceParty {
686
725
if ( sprite . animation . looping ) {
687
726
sprite . looping_frame = 0 ;
688
727
}
728
+ sprite . sinceLastFrame = 0 ;
689
729
sprite . animation . looping = true ;
690
730
sprite . current_move = move ;
691
731
sprite . alternatingMoveInfo = undefined ;
@@ -1197,8 +1237,10 @@ module.exports = class DanceParty {
1197
1237
// Called when executing the AI block.
1198
1238
ai ( params ) {
1199
1239
this . world . aiBlockCalled = true ;
1200
- console . log ( 'handle AI:' , params ) ;
1201
- if ( this . contextType === constants . KEY_WENT_DOWN_EVENT_TYPE && this . contextKey ) {
1240
+ if (
1241
+ this . contextType === constants . KEY_WENT_DOWN_EVENT_TYPE &&
1242
+ this . contextKey
1243
+ ) {
1202
1244
// Note that this.contextKey is the key that was pressed to trigger this AI block, e.g., 'up', 'down',...
1203
1245
this . world . aiBlockContextUserEventKey = this . contextKey ;
1204
1246
}
@@ -1263,12 +1305,14 @@ module.exports = class DanceParty {
1263
1305
if ( ! this . spriteExists_ ( sprite ) ) {
1264
1306
return ;
1265
1307
}
1308
+ sprite . depth = this . getAdjustedSpriteDepth ( sprite ) ;
1309
+ }
1266
1310
1311
+ getAdjustedSpriteDepth ( sprite ) {
1267
1312
// Bias scale heavily (especially since it largely hovers around 1.0) but use
1268
1313
// Y coordinate as the first tie-breaker and X coordinate as the second.
1269
1314
// (Both X and Y range from 0-399 pixels.)
1270
- sprite . depth =
1271
- 10000 * sprite . scale + ( 100 * sprite . y ) / 400 + ( 1 * sprite . x ) / 400 ;
1315
+ return 10000 * sprite . scale + ( 100 * sprite . y ) / 400 + ( 1 * sprite . x ) / 400 ;
1272
1316
}
1273
1317
1274
1318
// Behaviors
@@ -1408,7 +1452,8 @@ module.exports = class DanceParty {
1408
1452
1409
1453
for ( let key of WATCHED_KEYS ) {
1410
1454
if ( this . p5_ . keyWentDown ( key ) ) {
1411
- events [ constants . KEY_WENT_DOWN_EVENT_TYPE ] = events [ constants . KEY_WENT_DOWN_EVENT_TYPE ] || { } ;
1455
+ events [ constants . KEY_WENT_DOWN_EVENT_TYPE ] =
1456
+ events [ constants . KEY_WENT_DOWN_EVENT_TYPE ] || { } ;
1412
1457
events [ constants . KEY_WENT_DOWN_EVENT_TYPE ] [ key ] = true ;
1413
1458
this . world . keysPressed . add ( key ) ;
1414
1459
}
@@ -1499,6 +1544,17 @@ module.exports = class DanceParty {
1499
1544
console . warn ( message ) ;
1500
1545
}
1501
1546
1547
+ setExternalLayerSource ( dancerName ) {
1548
+ if ( ! this . externalLayer ) {
1549
+ return ;
1550
+ }
1551
+ const base = 'https://curriculum.code.org/media/musiclab/generate/dancers/' ;
1552
+ const url = dancerName ? `${ base } ${ dancerName } .json` : null ;
1553
+ this . externalLayer . setSource ( { url} ) . catch ( err => {
1554
+ console . error ( 'Error loading external layer source' , err ) ;
1555
+ } ) ;
1556
+ }
1557
+
1502
1558
draw ( ) {
1503
1559
const { bpm, artist, title} = this . songMetadata_ || { } ;
1504
1560
@@ -1554,7 +1610,43 @@ module.exports = class DanceParty {
1554
1610
} ) ;
1555
1611
}
1556
1612
1557
- this . p5_ . drawSprites ( ) ;
1613
+ if ( ! this . externalLayer ) {
1614
+ this . p5_ . drawSprites ( ) ;
1615
+ } else {
1616
+ // Draw sprites in two passes, before and after the external layer.
1617
+ // This is done so that sprites that are larger (or lower down) on
1618
+ // on the canvas appear in front of the external layer, creating an
1619
+ // illusion of depth.
1620
+ const sprites = this . p5_ . allSprites . sort ( ( a , b ) => a . depth - b . depth ) ;
1621
+
1622
+ // Mock sprite in the center of the canvas with default scale.
1623
+ const mockSprite = { x : 200 , y : 200 , scale : 1 } ;
1624
+ const layerDepthCutoff = this . getAdjustedSpriteDepth ( mockSprite ) ;
1625
+
1626
+ const highDepth = sprites . filter ( s => s . depth >= layerDepthCutoff ) ;
1627
+ const lowDepth = sprites . filter ( s => s . depth < layerDepthCutoff ) ;
1628
+
1629
+ for ( const s of lowDepth ) this . p5_ . drawSprite ( s ) ;
1630
+
1631
+ const total = this . externalLayer . getDurationFrames ( ) || 0 ;
1632
+ if ( total ) {
1633
+ // Align animations to a half-measure cycle, similar to dancers.
1634
+ const measureTick = ( this . getCurrentMeasure ( ) * 2 ) % 1 ;
1635
+ const frameIndexToDraw = Math . floor ( measureTick * total ) ;
1636
+
1637
+ this . externalLayer . render ( frameIndexToDraw , {
1638
+ mode : 'fit' ,
1639
+ scale : 0.75 ,
1640
+ align : { x : 'center' , y : 'center' } ,
1641
+ clearBeforeDraw : true ,
1642
+ } ) ;
1643
+
1644
+ this . p5_ . image ( this . externalLayer . graphics , 0 , 0 ) ;
1645
+ }
1646
+
1647
+ for ( const s of highDepth ) this . p5_ . drawSprite ( s ) ;
1648
+ }
1649
+
1558
1650
if ( this . recordReplayLog_ ) {
1559
1651
replayLog . logFrame ( {
1560
1652
bg : this . world . bg_effect ,
0 commit comments