2017-04-17 15:10:04 -04:00
|
|
|
const RenderedTarget = require('./rendered-target');
|
|
|
|
const Blocks = require('../engine/blocks');
|
2017-09-07 11:51:21 -04:00
|
|
|
const {loadSoundFromAsset} = require('../import/load-sound');
|
|
|
|
const {loadCostumeFromAsset} = require('../import/load-costume');
|
|
|
|
const StringUtil = require('../util/string-util');
|
2018-05-18 10:11:59 -04:00
|
|
|
const StageLayering = require('../engine/stage-layering');
|
2016-06-29 13:48:30 -04:00
|
|
|
|
2017-04-17 19:42:48 -04:00
|
|
|
class Sprite {
|
2016-08-31 11:30:09 -04:00
|
|
|
/**
|
2017-04-17 19:42:48 -04:00
|
|
|
* Sprite to be used on the Scratch stage.
|
2018-06-15 12:22:49 -04:00
|
|
|
* All clones of a sprite have shared blocks, shared costumes, shared variables,
|
|
|
|
* shared sounds, etc.
|
2017-04-17 19:42:48 -04:00
|
|
|
* @param {?Blocks} blocks Shared blocks object for all clones of sprite.
|
|
|
|
* @param {Runtime} runtime Reference to the runtime.
|
|
|
|
* @constructor
|
2016-08-31 11:30:09 -04:00
|
|
|
*/
|
2018-05-18 10:11:59 -04:00
|
|
|
constructor (blocks, runtime) {
|
2017-04-17 19:42:48 -04:00
|
|
|
this.runtime = runtime;
|
|
|
|
if (!blocks) {
|
|
|
|
// Shared set of blocks for all clones.
|
|
|
|
blocks = new Blocks();
|
|
|
|
}
|
|
|
|
this.blocks = blocks;
|
|
|
|
/**
|
|
|
|
* Human-readable name for this sprite (and all clones).
|
|
|
|
* @type {string}
|
|
|
|
*/
|
|
|
|
this.name = '';
|
|
|
|
/**
|
|
|
|
* List of costumes for this sprite.
|
|
|
|
* Each entry is an object, e.g.,
|
|
|
|
* {
|
|
|
|
* skinId: 1,
|
|
|
|
* name: "Costume Name",
|
|
|
|
* bitmapResolution: 2,
|
|
|
|
* rotationCenterX: 0,
|
2018-02-23 10:21:29 -05:00
|
|
|
* rotationCenterY: 0
|
2017-04-17 19:42:48 -04:00
|
|
|
* }
|
|
|
|
* @type {Array.<!Object>}
|
|
|
|
*/
|
2018-02-21 19:59:35 -05:00
|
|
|
this.costumes_ = [];
|
2017-04-17 19:42:48 -04:00
|
|
|
/**
|
|
|
|
* List of sounds for this sprite.
|
|
|
|
*/
|
|
|
|
this.sounds = [];
|
|
|
|
/**
|
|
|
|
* List of clones for this sprite, including the original.
|
|
|
|
* @type {Array.<!RenderedTarget>}
|
|
|
|
*/
|
|
|
|
this.clones = [];
|
2018-06-15 12:22:49 -04:00
|
|
|
|
|
|
|
this.soundBank = null;
|
|
|
|
if (this.runtime && this.runtime.audioEngine) {
|
|
|
|
this.soundBank = this.runtime.audioEngine.createBank();
|
|
|
|
}
|
2018-02-21 19:59:35 -05:00
|
|
|
}
|
2018-05-15 22:22:44 -04:00
|
|
|
|
2018-02-21 19:59:35 -05:00
|
|
|
/**
|
|
|
|
* Add an array of costumes, taking care to avoid duplicate names.
|
|
|
|
* @param {!Array<object>} costumes Array of objects representing costumes.
|
|
|
|
*/
|
2018-02-22 15:42:35 -05:00
|
|
|
set costumes (costumes) {
|
|
|
|
this.costumes_ = [];
|
2018-02-21 19:59:35 -05:00
|
|
|
for (const costume of costumes) {
|
|
|
|
this.addCostumeAt(costume, this.costumes_.length);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-22 15:42:35 -05:00
|
|
|
/**
|
|
|
|
* Get full costume list
|
|
|
|
* @return {object[]} list of costumes. Note that mutating the returned list will not
|
|
|
|
* mutate the list on the sprite. The sprite list should be mutated by calling
|
|
|
|
* addCostumeAt, deleteCostumeAt, or setting costumes.
|
|
|
|
*/
|
|
|
|
get costumes () {
|
2018-03-05 11:07:50 -05:00
|
|
|
return this.costumes_;
|
2018-02-22 15:42:35 -05:00
|
|
|
}
|
|
|
|
|
2018-02-21 19:59:35 -05:00
|
|
|
/**
|
|
|
|
* Add a costume at the given index, taking care to avoid duplicate names.
|
|
|
|
* @param {!object} costumeObject Object representing the costume.
|
|
|
|
* @param {!int} index Index at which to add costume
|
|
|
|
*/
|
|
|
|
addCostumeAt (costumeObject, index) {
|
2018-02-22 15:42:35 -05:00
|
|
|
if (!costumeObject.name) {
|
|
|
|
costumeObject.name = '';
|
|
|
|
}
|
2018-02-21 19:59:35 -05:00
|
|
|
const usedNames = this.costumes_.map(costume => costume.name);
|
|
|
|
costumeObject.name = StringUtil.unusedName(costumeObject.name, usedNames);
|
|
|
|
this.costumes_.splice(index, 0, costumeObject);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Delete a costume by index.
|
|
|
|
* @param {number} index Costume index to be deleted
|
|
|
|
*/
|
2018-02-22 15:42:35 -05:00
|
|
|
deleteCostumeAt (index) {
|
2018-02-21 19:59:35 -05:00
|
|
|
this.costumes_ = this.costumes_
|
|
|
|
.slice(0, index)
|
|
|
|
.concat(this.costumes_.slice(index + 1));
|
2017-04-17 19:42:48 -04:00
|
|
|
}
|
|
|
|
|
2016-08-31 11:30:09 -04:00
|
|
|
/**
|
2017-04-17 19:42:48 -04:00
|
|
|
* Create a clone of this sprite.
|
2018-05-18 10:11:59 -04:00
|
|
|
* @param {string=} optLayerGroup Optional layer group the clone's drawable should be added to
|
|
|
|
* Defaults to the sprite layer group
|
2017-04-17 19:42:48 -04:00
|
|
|
* @returns {!RenderedTarget} Newly created clone.
|
2016-08-31 11:30:09 -04:00
|
|
|
*/
|
2018-05-18 10:11:59 -04:00
|
|
|
createClone (optLayerGroup) {
|
2017-04-17 19:42:48 -04:00
|
|
|
const newClone = new RenderedTarget(this, this.runtime);
|
|
|
|
newClone.isOriginal = this.clones.length === 0;
|
|
|
|
this.clones.push(newClone);
|
2018-01-09 15:57:33 -05:00
|
|
|
newClone.initAudio();
|
2017-04-17 19:42:48 -04:00
|
|
|
if (newClone.isOriginal) {
|
2018-05-18 10:11:59 -04:00
|
|
|
// Default to the sprite layer group if optLayerGroup is not provided
|
|
|
|
const layerGroup = typeof optLayerGroup === 'string' ? optLayerGroup : StageLayering.SPRITE_LAYER;
|
|
|
|
newClone.initDrawable(layerGroup);
|
2017-06-16 17:22:51 -04:00
|
|
|
this.runtime.fireTargetWasCreated(newClone);
|
|
|
|
} else {
|
|
|
|
this.runtime.fireTargetWasCreated(newClone, this.clones[0]);
|
2017-04-17 19:42:48 -04:00
|
|
|
}
|
|
|
|
return newClone;
|
2016-09-15 19:37:12 -04:00
|
|
|
}
|
2017-06-07 20:05:24 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Disconnect a clone from this sprite. The clone is unmodified.
|
|
|
|
* In particular, the clone's dispose() method is not called.
|
|
|
|
* @param {!RenderedTarget} clone - the clone to be removed.
|
|
|
|
*/
|
|
|
|
removeClone (clone) {
|
2017-10-06 13:43:07 -04:00
|
|
|
this.runtime.fireTargetWasRemoved(clone);
|
2017-06-07 20:05:24 -04:00
|
|
|
const cloneIndex = this.clones.indexOf(clone);
|
|
|
|
if (cloneIndex >= 0) {
|
|
|
|
this.clones.splice(cloneIndex, 1);
|
|
|
|
}
|
|
|
|
}
|
2017-09-07 11:51:21 -04:00
|
|
|
|
|
|
|
duplicate () {
|
|
|
|
const newSprite = new Sprite(null, this.runtime);
|
|
|
|
|
|
|
|
newSprite.blocks = this.blocks.duplicate();
|
|
|
|
|
2018-02-26 23:17:25 -05:00
|
|
|
const allNames = this.runtime.targets.map(t => t.sprite.name);
|
2017-09-07 11:51:21 -04:00
|
|
|
newSprite.name = StringUtil.unusedName(this.name, allNames);
|
|
|
|
|
|
|
|
const assetPromises = [];
|
|
|
|
|
2018-02-22 15:42:35 -05:00
|
|
|
newSprite.costumes = this.costumes_.map(costume => {
|
2017-09-07 11:51:21 -04:00
|
|
|
const newCostume = Object.assign({}, costume);
|
|
|
|
const costumeAsset = this.runtime.storage.get(costume.assetId);
|
|
|
|
assetPromises.push(loadCostumeFromAsset(newCostume, costumeAsset, this.runtime));
|
|
|
|
return newCostume;
|
2018-02-22 15:42:35 -05:00
|
|
|
});
|
2017-09-07 11:51:21 -04:00
|
|
|
|
|
|
|
newSprite.sounds = this.sounds.map(sound => {
|
|
|
|
const newSound = Object.assign({}, sound);
|
|
|
|
const soundAsset = this.runtime.storage.get(sound.assetId);
|
2018-06-22 09:45:23 -04:00
|
|
|
assetPromises.push(loadSoundFromAsset(newSound, soundAsset, this.runtime, this));
|
2017-09-07 11:51:21 -04:00
|
|
|
return newSound;
|
|
|
|
});
|
|
|
|
|
|
|
|
return Promise.all(assetPromises).then(() => newSprite);
|
|
|
|
}
|
2018-06-15 12:22:49 -04:00
|
|
|
|
|
|
|
dispose () {
|
|
|
|
if (this.soundBank) {
|
|
|
|
this.soundBank.dispose();
|
|
|
|
}
|
|
|
|
}
|
2017-04-17 19:42:48 -04:00
|
|
|
}
|
2016-06-29 20:56:55 -04:00
|
|
|
|
2016-06-29 13:48:30 -04:00
|
|
|
module.exports = Sprite;
|