Add sprite duplication method

This commit is contained in:
Paul Kaplan 2017-09-07 11:51:21 -04:00
parent 2665ef2b2b
commit b68b874067
6 changed files with 140 additions and 48 deletions

View file

@ -2,6 +2,7 @@ const adapter = require('./adapter');
const mutationAdapter = require('./mutation-adapter'); const mutationAdapter = require('./mutation-adapter');
const xmlEscape = require('../util/xml-escape'); const xmlEscape = require('../util/xml-escape');
const MonitorRecord = require('./monitor-record'); const MonitorRecord = require('./monitor-record');
const Clone = require('../util/clone');
/** /**
* @fileoverview * @fileoverview
@ -178,6 +179,12 @@ class Blocks {
return null; return null;
} }
duplicate () {
const newBlocks = new Blocks();
newBlocks._blocks = Clone.simple(this._blocks);
newBlocks._scripts = Clone.simple(this._scripts);
return newBlocks;
}
// --------------------------------------------------------------------- // ---------------------------------------------------------------------
/** /**

View file

@ -25,30 +25,30 @@ const loadCostume = function (md5ext, costume, runtime) {
const ext = idParts[1].toLowerCase(); const ext = idParts[1].toLowerCase();
const assetType = (ext === 'svg') ? AssetType.ImageVector : AssetType.ImageBitmap; const assetType = (ext === 'svg') ? AssetType.ImageVector : AssetType.ImageBitmap;
return runtime.storage.load(assetType, md5, ext).then(costumeAsset => {
costume.dataFormat = ext;
return loadCostumeFromAsset(costume, costumeAsset, runtime);
});
};
const loadCostumeFromAsset = function (costume, costumeAsset, runtime) {
const rotationCenter = [ const rotationCenter = [
costume.rotationCenterX / costume.bitmapResolution, costume.rotationCenterX / costume.bitmapResolution,
costume.rotationCenterY / costume.bitmapResolution costume.rotationCenterY / costume.bitmapResolution
]; ];
let promise = runtime.storage.load(assetType, md5, ext).then(costumeAsset => {
costume.assetId = costumeAsset.assetId;
costume.dataFormat = ext;
return costumeAsset;
});
if (!runtime.renderer) { if (!runtime.renderer) {
log.error('No rendering module present; cannot load costume asset: ', md5ext); log.error('No rendering module present; cannot load costume asset: ', md5ext);
return promise.then(() => costume); return costume;
} }
const AssetType = runtime.storage.AssetType;
if (assetType === AssetType.ImageVector) { costume.assetId = costumeAsset.assetId;
promise = promise.then(costumeAsset => { if (costumeAsset.assetType === AssetType.ImageVector) {
costume.skinId = runtime.renderer.createSVGSkin(costumeAsset.decodeText(), rotationCenter); costume.skinId = runtime.renderer.createSVGSkin(costumeAsset.decodeText(), rotationCenter);
return costume; return costume;
}); }
} else {
promise = promise.then(costumeAsset => ( return new Promise((resolve, reject) => {
new Promise((resolve, reject) => {
const imageElement = new Image(); const imageElement = new Image();
const onError = function () { const onError = function () {
// eslint-disable-next-line no-use-before-define // eslint-disable-next-line no-use-before-define
@ -67,13 +67,12 @@ const loadCostume = function (md5ext, costume, runtime) {
imageElement.addEventListener('error', onError); imageElement.addEventListener('error', onError);
imageElement.addEventListener('load', onLoad); imageElement.addEventListener('load', onLoad);
imageElement.src = costumeAsset.encodeDataURI(); imageElement.src = costumeAsset.encodeDataURI();
}) }).then(imageElement => {
)).then(imageElement => {
costume.skinId = runtime.renderer.createBitmapSkin(imageElement, costume.bitmapResolution, rotationCenter); costume.skinId = runtime.renderer.createBitmapSkin(imageElement, costume.bitmapResolution, rotationCenter);
return costume; return costume;
}); });
}
return promise;
}; };
loadCostume.loadCostumeFromAsset = loadCostumeFromAsset;
module.exports = loadCostume; module.exports = loadCostume;

View file

@ -23,18 +23,22 @@ const loadSound = function (sound, runtime) {
const ext = idParts[1].toLowerCase(); const ext = idParts[1].toLowerCase();
return runtime.storage.load(runtime.storage.AssetType.Sound, md5, ext) return runtime.storage.load(runtime.storage.AssetType.Sound, md5, ext)
.then(soundAsset => { .then(soundAsset => {
sound.assetId = soundAsset.assetId;
sound.dataFormat = ext; sound.dataFormat = ext;
return loadSoundFromAsset(sound, soundAsset, runtime);
});
};
const loadSoundFromAsset = function (sound, soundAsset, runtime) {
sound.assetId = soundAsset.assetId;
return runtime.audioEngine.decodeSound(Object.assign( return runtime.audioEngine.decodeSound(Object.assign(
{}, {},
sound, sound,
{data: soundAsset.data} {data: soundAsset.data}
)); )).then(soundId => {
})
.then(soundId => {
sound.soundId = soundId; sound.soundId = soundId;
return sound; return sound;
}); });
}; };
loadSound.loadSoundFromAsset = loadSoundFromAsset;
module.exports = loadSound; module.exports = loadSound;

View file

@ -787,6 +787,33 @@ class RenderedTarget extends Target {
return newClone; return newClone;
} }
/**
* Make a duplicate using a duplicate sprite.
* @return {RenderedTarget} New clone.
*/
duplicate () {
return this.sprite.duplicate().then(newSprite => {
const newTarget = newSprite.createClone();
// Copy all properties.
// @todo refactor with clone methods
newTarget.x = Math.random() * 400 / 2;
newTarget.y = Math.random() * 300 / 2;
newTarget.direction = this.direction;
newTarget.draggable = this.draggable;
newTarget.visible = this.visible;
newTarget.size = this.size;
newTarget.currentCostume = this.currentCostume;
newTarget.rotationStyle = this.rotationStyle;
newTarget.effects = JSON.parse(JSON.stringify(this.effects));
newTarget.variables = JSON.parse(JSON.stringify(this.variables));
newTarget.lists = JSON.parse(JSON.stringify(this.lists));
newTarget.initDrawable();
newTarget.updateAllDrawableProperties();
newTarget.goBehindOther(this);
return newTarget;
});
}
/** /**
* Called when the project receives a "green flag." * Called when the project receives a "green flag."
* For a rendered target, this clears graphic effects. * For a rendered target, this clears graphic effects.

View file

@ -1,5 +1,8 @@
const RenderedTarget = require('./rendered-target'); const RenderedTarget = require('./rendered-target');
const Blocks = require('../engine/blocks'); const Blocks = require('../engine/blocks');
const {loadSoundFromAsset} = require('../import/load-sound');
const {loadCostumeFromAsset} = require('../import/load-costume');
const StringUtil = require('../util/string-util');
class Sprite { class Sprite {
/** /**
@ -73,6 +76,33 @@ class Sprite {
this.clones.splice(cloneIndex, 1); this.clones.splice(cloneIndex, 1);
} }
} }
duplicate () {
const newSprite = new Sprite(null, this.runtime);
newSprite.blocks = this.blocks.duplicate();
const allNames = this.runtime.targets.map(t => t.name);
newSprite.name = StringUtil.unusedName(this.name, allNames);
const assetPromises = [];
newSprite.costumes = this.costumes.map(costume => {
const newCostume = Object.assign({}, costume);
const costumeAsset = this.runtime.storage.get(costume.assetId);
assetPromises.push(loadCostumeFromAsset(newCostume, costumeAsset, this.runtime));
return newCostume;
});
newSprite.sounds = this.sounds.map(sound => {
const newSound = Object.assign({}, sound);
const soundAsset = this.runtime.storage.get(sound.assetId);
assetPromises.push(loadSoundFromAsset(newSound, soundAsset, this.runtime));
return newSound;
});
return Promise.all(assetPromises).then(() => newSprite);
}
} }
module.exports = Sprite; module.exports = Sprite;

View file

@ -439,6 +439,31 @@ class VirtualMachine extends EventEmitter {
} }
} }
/**
* Duplicate a sprite.
* @param {string} targetId ID of a target whose sprite to duplicate.
*/
duplicateSprite (targetId) {
const target = this.runtime.getTargetById(targetId);
if (target) {
if (!target.isSprite()) {
throw new Error('Cannot duplicate non-sprite targets.');
}
const sprite = target.sprite;
if (!sprite) {
throw new Error('No sprite associated with this target.');
}
target.duplicate().then(newTarget => {
this.runtime.targets.push(newTarget);
this.setEditingTarget(newTarget.id);
});
} else {
throw new Error('No target with the provided id.');
}
}
/** /**
* Set the audio engine for the VM/runtime * Set the audio engine for the VM/runtime
* @param {!AudioEngine} audioEngine The audio engine to attach * @param {!AudioEngine} audioEngine The audio engine to attach