mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2025-06-09 03:54:44 -04:00
Make sprite.costumes private so that I can enforce that when you add costumes, they get assigned a unique ID
This commit is contained in:
parent
8e1719b716
commit
729fc3d303
9 changed files with 135 additions and 59 deletions
|
@ -332,7 +332,7 @@ class Scratch3LooksBlocks {
|
|||
}
|
||||
if (target === this.runtime.getTargetForStage()) {
|
||||
// Target is the stage - start hats.
|
||||
const newName = target.sprite.costumes[target.currentCostume].name;
|
||||
const newName = target.getCostumes()[target.currentCostume].name;
|
||||
return this.runtime.startHats('event_whenbackdropswitchesto', {
|
||||
BACKDROP: newName
|
||||
});
|
||||
|
@ -442,7 +442,7 @@ class Scratch3LooksBlocks {
|
|||
return stage.currentCostume + 1;
|
||||
}
|
||||
// Else return name
|
||||
return stage.sprite.costumes[stage.currentCostume].name;
|
||||
return stage.getCostumes()[stage.currentCostume].name;
|
||||
}
|
||||
|
||||
getCostumeNumberName (args, util) {
|
||||
|
@ -450,7 +450,7 @@ class Scratch3LooksBlocks {
|
|||
return util.target.currentCostume + 1;
|
||||
}
|
||||
// Else return name
|
||||
return util.target.sprite.costumes[util.target.currentCostume].name;
|
||||
return util.target.getCostumes()[util.target.currentCostume].name;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -237,7 +237,7 @@ class Scratch3SensingBlocks {
|
|||
|
||||
case 'backdrop #': return attrTarget.currentCostume + 1;
|
||||
case 'backdrop name':
|
||||
return attrTarget.sprite.costumes[attrTarget.currentCostume].name;
|
||||
return attrTarget.getCostumes()[attrTarget.currentCostume].name;
|
||||
case 'volume': return; // @todo: Keep this in mind for sound blocks!
|
||||
}
|
||||
} else {
|
||||
|
@ -247,7 +247,7 @@ class Scratch3SensingBlocks {
|
|||
case 'direction': return attrTarget.direction;
|
||||
case 'costume #': return attrTarget.currentCostume + 1;
|
||||
case 'costume name':
|
||||
return attrTarget.sprite.costumes[attrTarget.currentCostume].name;
|
||||
return attrTarget.getCostumes()[attrTarget.currentCostume].name;
|
||||
case 'size': return attrTarget.size;
|
||||
case 'volume': return; // @todo: above, keep in mind for sound blocks..
|
||||
}
|
||||
|
|
|
@ -324,7 +324,7 @@ const parseScratchObject = function (object, runtime, extensions, topLevel) {
|
|||
target.isStage = topLevel;
|
||||
|
||||
Promise.all(costumePromises).then(costumes => {
|
||||
sprite.costumes = costumes;
|
||||
sprite.addCostumes(costumes);
|
||||
});
|
||||
|
||||
Promise.all(soundPromises).then(sounds => {
|
||||
|
|
|
@ -156,7 +156,7 @@ const parseScratchObject = function (object, runtime, extensions) {
|
|||
target.isStage = object.isStage;
|
||||
}
|
||||
Promise.all(costumePromises).then(costumes => {
|
||||
sprite.costumes = costumes;
|
||||
sprite.addCostumes(costumes);
|
||||
});
|
||||
Promise.all(soundPromises).then(sounds => {
|
||||
sprite.sounds = sounds;
|
||||
|
|
|
@ -372,10 +372,10 @@ class RenderedTarget extends Target {
|
|||
// Keep the costume index within possible values.
|
||||
index = Math.round(index);
|
||||
this.currentCostume = MathUtil.wrapClamp(
|
||||
index, 0, this.sprite.costumes.length - 1
|
||||
index, 0, this.getCostumes().length - 1
|
||||
);
|
||||
if (this.renderer) {
|
||||
const costume = this.sprite.costumes[this.currentCostume];
|
||||
const costume = this.sprite.getCostumeByIndex(this.currentCostume);
|
||||
const drawableProperties = {
|
||||
skinId: costume.skinId,
|
||||
costumeResolution: costume.bitmapResolution
|
||||
|
@ -403,9 +403,16 @@ class RenderedTarget extends Target {
|
|||
* @param {!object} costumeObject Object representing the costume.
|
||||
*/
|
||||
addCostume (costumeObject) {
|
||||
const usedNames = this.sprite.costumes.map(costume => costume.name);
|
||||
costumeObject.name = StringUtil.unusedName(costumeObject.name, usedNames);
|
||||
this.sprite.costumes.push(costumeObject);
|
||||
this.sprite.addCostumeAt(costumeObject, this.getCostumes().length);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
this.sprite.addCostumeAt(costumeObject, index);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -414,12 +421,12 @@ class RenderedTarget extends Target {
|
|||
* @param {string} newName - the desired new name of the costume (will be modified if already in use).
|
||||
*/
|
||||
renameCostume (costumeIndex, newName) {
|
||||
const usedNames = this.sprite.costumes
|
||||
const usedNames = this.getCostumes()
|
||||
.filter((costume, index) => costumeIndex !== index)
|
||||
.map(costume => costume.name);
|
||||
const oldName = this.sprite.costumes[costumeIndex].name;
|
||||
const oldName = this.sprite.getCostumeByIndex(costumeIndex).name;
|
||||
const newUnusedName = StringUtil.unusedName(newName, usedNames);
|
||||
this.sprite.costumes[costumeIndex].name = newUnusedName;
|
||||
this.sprite.getCostumeByIndex(costumeIndex).name = newUnusedName;
|
||||
|
||||
if (this.isStage) {
|
||||
// Since this is a backdrop, go through all targets and
|
||||
|
@ -440,12 +447,10 @@ class RenderedTarget extends Target {
|
|||
* @param {number} index Costume index to be deleted
|
||||
*/
|
||||
deleteCostume (index) {
|
||||
const originalCostumeCount = this.sprite.costumes.length;
|
||||
const originalCostumeCount = this.getCostumes().length;
|
||||
if (originalCostumeCount === 1) return;
|
||||
|
||||
this.sprite.costumes = this.sprite.costumes
|
||||
.slice(0, index)
|
||||
.concat(this.sprite.costumes.slice(index + 1));
|
||||
this.sprite.deleteCostumeByIndex(index);
|
||||
|
||||
if (index === this.currentCostume && index === originalCostumeCount - 1) {
|
||||
this.setCostume(index - 1);
|
||||
|
@ -525,8 +530,8 @@ class RenderedTarget extends Target {
|
|||
* @return {number} Index of the named costume, or -1 if not present.
|
||||
*/
|
||||
getCostumeIndexByName (costumeName) {
|
||||
for (let i = 0; i < this.sprite.costumes.length; i++) {
|
||||
if (this.sprite.costumes[i].name === costumeName) {
|
||||
for (let i = 0; i < this.getCostumes().length; i++) {
|
||||
if (this.sprite.getCostumeByIndex(i).name === costumeName) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
@ -538,7 +543,7 @@ class RenderedTarget extends Target {
|
|||
* @return {object} current costume
|
||||
*/
|
||||
getCurrentCostume () {
|
||||
return this.sprite.costumes[this.currentCostume];
|
||||
return this.sprite.getCostumeByIndex(this.currentCostume);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -546,7 +551,7 @@ class RenderedTarget extends Target {
|
|||
* @return {object[]} list of costumes
|
||||
*/
|
||||
getCostumes () {
|
||||
return this.sprite.costumes;
|
||||
return this.sprite.getCostumes();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -564,7 +569,7 @@ class RenderedTarget extends Target {
|
|||
updateAllDrawableProperties () {
|
||||
if (this.renderer) {
|
||||
const renderedDirectionScale = this._getRenderedDirectionAndScale();
|
||||
const costume = this.sprite.costumes[this.currentCostume];
|
||||
const costume = this.sprite.getCostumeByIndex(this.currentCostume);
|
||||
const bitmapResolution = costume.bitmapResolution || 1;
|
||||
const props = {
|
||||
position: [this.x, this.y],
|
||||
|
|
|
@ -32,11 +32,12 @@ class Sprite {
|
|||
* name: "Costume Name",
|
||||
* bitmapResolution: 2,
|
||||
* rotationCenterX: 0,
|
||||
* rotationCenterY: 0
|
||||
* rotationCenterY: 0,
|
||||
* costumeId: 0
|
||||
* }
|
||||
* @type {Array.<!Object>}
|
||||
*/
|
||||
this.costumes = [];
|
||||
this.costumes_ = [];
|
||||
/**
|
||||
* List of sounds for this sprite.
|
||||
*/
|
||||
|
@ -46,6 +47,68 @@ class Sprite {
|
|||
* @type {Array.<!RenderedTarget>}
|
||||
*/
|
||||
this.clones = [];
|
||||
/**
|
||||
* A costume ID generator. Costumes within a sprite should each have a unique ID.
|
||||
*/
|
||||
this.nextCostumeId = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get full costume list
|
||||
* @return {object[]} list of costumes
|
||||
*/
|
||||
getCostumes () {
|
||||
return this.costumes_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get costume at index
|
||||
* @param {int} index Index
|
||||
* @return {object} costumes
|
||||
*/
|
||||
getCostumeByIndex (index) {
|
||||
return this.costumes_[index];
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a costume, taking care to avoid duplicate names.
|
||||
* @param {!object} costumeObject Object representing the costume.
|
||||
*/
|
||||
addCostume (costumeObject) {
|
||||
this.addCostumeAt(costumeObject, this.costumes_.length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an array of costumes, taking care to avoid duplicate names.
|
||||
* @param {!Array<object>} costumes Array of objects representing costumes.
|
||||
*/
|
||||
addCostumes (costumes) {
|
||||
for (const costume of costumes) {
|
||||
this.addCostumeAt(costume, this.costumes_.length);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
const usedNames = this.costumes_.map(costume => costume.name);
|
||||
costumeObject.name = StringUtil.unusedName(costumeObject.name, usedNames);
|
||||
costumeObject.costumeId = this.nextCostumeId;
|
||||
this.nextCostumeId++;
|
||||
this.costumes_.splice(index, 0, costumeObject);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a costume by index.
|
||||
* @param {number} index Costume index to be deleted
|
||||
*/
|
||||
deleteCostumeByIndex (index) {
|
||||
this.costumes_ = this.costumes_
|
||||
.slice(0, index)
|
||||
.concat(this.costumes_.slice(index + 1));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -89,12 +152,12 @@ class Sprite {
|
|||
|
||||
const assetPromises = [];
|
||||
|
||||
newSprite.costumes = this.costumes.map(costume => {
|
||||
newSprite.addCostumes(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);
|
||||
|
|
|
@ -322,11 +322,19 @@ class VirtualMachine extends EventEmitter {
|
|||
return loadCostume(md5ext, costumeObject, this.runtime).then(() => {
|
||||
this.editingTarget.addCostume(costumeObject);
|
||||
this.editingTarget.setCostume(
|
||||
this.editingTarget.sprite.costumes.length - 1
|
||||
this.editingTarget.getCostumes().length - 1
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
duplicateCostume (costumeIndex) {
|
||||
const currentCostume = this.editingTarget.getCostumes()[costumeIndex];
|
||||
const clone = Object.assign({}, currentCostume);
|
||||
this.editingTarget.addCostumeAt(clone, costumeIndex + 1);
|
||||
this.editingTarget.setCostume(costumeIndex + 1);
|
||||
this.emitTargetsUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename a costume on the current editing target.
|
||||
* @param {int} costumeIndex - the index of the costume to be renamed.
|
||||
|
@ -407,7 +415,7 @@ class VirtualMachine extends EventEmitter {
|
|||
* @return {string} the costume's SVG string, or null if it's not an SVG costume.
|
||||
*/
|
||||
getCostumeSvg (costumeIndex) {
|
||||
const id = this.editingTarget.sprite.costumes[costumeIndex].assetId;
|
||||
const id = this.editingTarget.getCostumes()[costumeIndex].assetId;
|
||||
if (id && this.runtime && this.runtime.storage &&
|
||||
this.runtime.storage.get(id).dataFormat === 'svg') {
|
||||
return this.runtime.storage.get(id).decodeText();
|
||||
|
@ -423,7 +431,7 @@ class VirtualMachine extends EventEmitter {
|
|||
* @param {number} rotationCenterY y of point about which the costume rotates, relative to its upper left corner
|
||||
*/
|
||||
updateSvg (costumeIndex, svg, rotationCenterX, rotationCenterY) {
|
||||
const costume = this.editingTarget.sprite.costumes[costumeIndex];
|
||||
const costume = this.editingTarget.getCostumes()[costumeIndex];
|
||||
if (costume && this.runtime && this.runtime.renderer) {
|
||||
costume.rotationCenterX = rotationCenterX;
|
||||
costume.rotationCenterY = rotationCenterY;
|
||||
|
@ -452,7 +460,7 @@ class VirtualMachine extends EventEmitter {
|
|||
return loadCostume(md5ext, backdropObject, this.runtime).then(() => {
|
||||
const stage = this.runtime.getTargetForStage();
|
||||
stage.addCostume(backdropObject);
|
||||
stage.setCostume(stage.sprite.costumes.length - 1);
|
||||
stage.setCostume(stage.getCostumes().length - 1);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -52,8 +52,8 @@ test('sb3-roundtrip', t => {
|
|||
t.strictEqual(sprite.clones.length, 1);
|
||||
t.strictEqual(sprite.clones[0], spriteClone);
|
||||
|
||||
t.strictEqual(sprite.costumes.length, 2);
|
||||
const [cat, squirrel] = sprite.costumes;
|
||||
t.strictEqual(sprite.getCostumes().length, 2);
|
||||
const [cat, squirrel] = sprite.getCostumes();
|
||||
t.strictEqual(cat.assetId, 'f88bf1935daea28f8ca098462a31dbb0');
|
||||
t.strictEqual(cat.dataFormat, 'svg');
|
||||
t.strictEqual(squirrel.assetId, '7e24c99c1b853e52f8e7f9004416fa34');
|
||||
|
@ -77,7 +77,7 @@ test('sb3-roundtrip', t => {
|
|||
const stageBlocks = new Blocks();
|
||||
const stage = new Sprite(stageBlocks, runtime1);
|
||||
stage.name = 'Stage';
|
||||
stage.costumes = [building];
|
||||
stage.addCostume(building);
|
||||
stage.sounds = [];
|
||||
const stageClone = stage.createClone();
|
||||
stageClone.isStage = true;
|
||||
|
@ -85,7 +85,7 @@ test('sb3-roundtrip', t => {
|
|||
const spriteBlocks = new Blocks();
|
||||
const sprite = new Sprite(spriteBlocks, runtime1);
|
||||
sprite.name = 'Sprite';
|
||||
sprite.costumes = [cat, squirrel];
|
||||
sprite.addCostumes([cat, squirrel]);
|
||||
sprite.sounds = [meow];
|
||||
const spriteClone = sprite.createClone();
|
||||
|
||||
|
|
|
@ -90,7 +90,7 @@ test('setCostume', t => {
|
|||
const o = new Object();
|
||||
const s = new Sprite();
|
||||
const r = new Runtime();
|
||||
s.costumes = [o];
|
||||
s.addCostume(o);
|
||||
const a = new RenderedTarget(s, r);
|
||||
const renderer = new FakeRenderer();
|
||||
a.renderer = renderer;
|
||||
|
@ -107,7 +107,7 @@ test('deleteCostume', t => {
|
|||
|
||||
const s = new Sprite();
|
||||
const r = new Runtime();
|
||||
s.costumes = [o1, o2, o3];
|
||||
s.addCostumes([o1, o2, o3]);
|
||||
const a = new RenderedTarget(s, r);
|
||||
const renderer = new FakeRenderer();
|
||||
a.renderer = renderer;
|
||||
|
@ -117,32 +117,32 @@ test('deleteCostume', t => {
|
|||
// Costume 3
|
||||
a.setCostume(0);
|
||||
a.deleteCostume(0);
|
||||
t.deepEqual(a.sprite.costumes, [o2, o3]);
|
||||
t.deepEqual(a.sprite.getCostumes(), [o2, o3]);
|
||||
t.equals(a.currentCostume, 0);
|
||||
|
||||
// Costume 1 Costume 1
|
||||
// x* Costume 2 => * Costume 3
|
||||
// Costume 3
|
||||
a.sprite.costumes = [o1, o2, o3];
|
||||
a.sprite.addCostumes([o1, o2, o3]);
|
||||
a.setCostume(1);
|
||||
a.deleteCostume(1);
|
||||
t.deepEqual(a.sprite.costumes, [o1, o3]);
|
||||
t.deepEqual(a.sprite.getCostumes(), [o1, o3]);
|
||||
t.equals(a.currentCostume, 1);
|
||||
|
||||
// Costume 1 Costume 1
|
||||
// Costume 2 => * Costume 2
|
||||
// x* Costume 3
|
||||
a.sprite.costumes = [o1, o2, o3];
|
||||
a.sprite.addCostumes([o1, o2, o3]);
|
||||
a.setCostume(2);
|
||||
a.deleteCostume(2);
|
||||
t.deepEqual(a.sprite.costumes, [o1, o2]);
|
||||
t.deepEqual(a.sprite.getCostumes(), [o1, o2]);
|
||||
t.equals(a.currentCostume, 1);
|
||||
|
||||
// Refuses to delete only costume
|
||||
a.sprite.costumes = [o1];
|
||||
a.sprite.addCostume(o1);
|
||||
a.setCostume(0);
|
||||
a.deleteCostume(0);
|
||||
t.deepEqual(a.sprite.costumes, [o1]);
|
||||
t.deepEqual(a.sprite.getCostumes(), [o1]);
|
||||
t.equals(a.currentCostume, 0);
|
||||
|
||||
// Costume 1 Costume 1
|
||||
|
@ -150,10 +150,10 @@ test('deleteCostume', t => {
|
|||
// Costume 3 => * Costume 4
|
||||
// * Costume 4 Costume 5
|
||||
// Costume 5
|
||||
a.sprite.costumes = [o1, o2, o3, o4, o5];
|
||||
a.sprite.addCostumes([o1, o2, o3, o4, o5]);
|
||||
a.setCostume(3);
|
||||
a.deleteCostume(1);
|
||||
t.deepEqual(a.sprite.costumes, [o1, o3, o4, o5]);
|
||||
t.deepEqual(a.sprite.getCostumes(), [o1, o3, o4, o5]);
|
||||
t.equals(a.currentCostume, 2);
|
||||
|
||||
// Costume 1 Costume 1
|
||||
|
@ -161,10 +161,10 @@ test('deleteCostume', t => {
|
|||
// Costume 3 => Costume 3
|
||||
// x Costume 4 Costume 5
|
||||
// Costume 5
|
||||
a.sprite.costumes = [o1, o2, o3, o4, o5];
|
||||
a.sprite.addCostumes([o1, o2, o3, o4, o5]);
|
||||
a.setCostume(1);
|
||||
a.deleteCostume(3);
|
||||
t.deepEqual(a.sprite.costumes, [o1, o2, o3, o5]);
|
||||
t.deepEqual(a.sprite.getCostumes(), [o1, o2, o3, o5]);
|
||||
t.equals(a.currentCostume, 1);
|
||||
|
||||
// Costume 1 Costume 1
|
||||
|
@ -172,10 +172,10 @@ test('deleteCostume', t => {
|
|||
// Costume 3 => Costume 3
|
||||
// Costume 4 Costume 4
|
||||
// x Costume 5
|
||||
a.sprite.costumes = [o1, o2, o3, o4, o5];
|
||||
a.sprite.addCostumes([o1, o2, o3, o4, o5]);
|
||||
a.setCostume(1);
|
||||
a.deleteCostume(4);
|
||||
t.deepEqual(a.sprite.costumes, [o1, o2, o3, o4]);
|
||||
t.deepEqual(a.sprite.getCostumes(), [o1, o2, o3, o4]);
|
||||
t.equals(a.currentCostume, 1);
|
||||
t.end();
|
||||
});
|
||||
|
@ -334,7 +334,7 @@ test('#getCostumes returns the costumes', t => {
|
|||
const spr = new Sprite();
|
||||
const a = new RenderedTarget(spr, null);
|
||||
const costumes = [1, 2, 3];
|
||||
a.sprite.costumes = costumes;
|
||||
a.sprite.getCostumes() = costumes;
|
||||
t.equals(a.getCostumes(), costumes);
|
||||
t.end();
|
||||
});
|
||||
|
@ -354,9 +354,9 @@ test('#toJSON returns the sounds and costumes', t => {
|
|||
const sounds = [1, 2, 3];
|
||||
const costumes = ['a', 'b', 'c'];
|
||||
a.sprite.sounds = sounds;
|
||||
a.sprite.costumes = costumes;
|
||||
a.sprite.getCostumes() = costumes;
|
||||
t.same(a.toJSON().sounds, sounds);
|
||||
t.same(a.toJSON().costumes, costumes);
|
||||
t.same(a.toJSON().getCostumes(), costumes);
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
@ -372,9 +372,9 @@ test('#addSound does not duplicate names', t => {
|
|||
test('#addCostume does not duplicate names', t => {
|
||||
const spr = new Sprite();
|
||||
const a = new RenderedTarget(spr, null);
|
||||
a.sprite.costumes = [{name: 'first'}];
|
||||
a.sprite.addCostumes([{name: 'first'}]);
|
||||
a.addCostume({name: 'first'});
|
||||
t.deepEqual(a.sprite.costumes, [{name: 'first'}, {name: 'first2'}]);
|
||||
t.deepEqual(a.sprite.getCostumes(), [{name: 'first'}, {name: 'first2'}]);
|
||||
t.end();
|
||||
});
|
||||
|
||||
|
@ -392,10 +392,10 @@ test('#renameSound does not duplicate names', t => {
|
|||
test('#renameCostume does not duplicate names', t => {
|
||||
const spr = new Sprite();
|
||||
const a = new RenderedTarget(spr, null);
|
||||
a.sprite.costumes = [{name: 'first'}, {name: 'second'}];
|
||||
a.sprite.addCostumes([{name: 'first'}, {name: 'second'}]);
|
||||
a.renameCostume(0, 'first'); // Shouldn't increment the name, noop
|
||||
t.deepEqual(a.sprite.costumes, [{name: 'first'}, {name: 'second'}]);
|
||||
t.deepEqual(a.sprite.getCostumes(), [{name: 'first'}, {name: 'second'}]);
|
||||
a.renameCostume(1, 'first');
|
||||
t.deepEqual(a.sprite.costumes, [{name: 'first'}, {name: 'first2'}]);
|
||||
t.deepEqual(a.sprite.getCostumes(), [{name: 'first'}, {name: 'first2'}]);
|
||||
t.end();
|
||||
});
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue