mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-24 15:02:52 -05:00
Stage, costumes, backdrops (#149)
* Add `Clone.prototype.getCostumeIndexByName`, keep in range * Add basic costume primitives from Scratch 2.0 * Add costume getter block * Add properties and methods for distinguishing stage and sprites-vs-clones * Add backdrop-related looks blocks * Fix up "switch to backdrop" to be working * Costume/backdrop reporters are 1-indexed * Fire "when backdrop switched" hats * Cut cloning helpers for a separate PR * Disable many blocks on the stage * Refactor into _setCostumeOrBackdrop; implement switch backdrop and wait * Fire hats even when backdrop unchanged
This commit is contained in:
parent
14feb64005
commit
797f844de3
5 changed files with 183 additions and 7 deletions
|
@ -29,10 +29,9 @@ Scratch3EventBlocks.prototype.getHats = function () {
|
||||||
'event_whenthisspriteclicked': {
|
'event_whenthisspriteclicked': {
|
||||||
restartExistingThreads: true
|
restartExistingThreads: true
|
||||||
},
|
},
|
||||||
/*
|
|
||||||
'event_whenbackdropswitchesto': {
|
'event_whenbackdropswitchesto': {
|
||||||
restartExistingThreads: true
|
restartExistingThreads: true
|
||||||
},*/
|
},
|
||||||
'event_whengreaterthan': {
|
'event_whengreaterthan': {
|
||||||
restartExistingThreads: false,
|
restartExistingThreads: false,
|
||||||
edgeActivated: true
|
edgeActivated: true
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
var Cast = require('../util/cast');
|
||||||
|
|
||||||
function Scratch3LooksBlocks(runtime) {
|
function Scratch3LooksBlocks(runtime) {
|
||||||
/**
|
/**
|
||||||
* The runtime instantiating this block package.
|
* The runtime instantiating this block package.
|
||||||
|
@ -18,13 +20,23 @@ Scratch3LooksBlocks.prototype.getPrimitives = function() {
|
||||||
'looks_thinkforsecs': this.sayforsecs,
|
'looks_thinkforsecs': this.sayforsecs,
|
||||||
'looks_show': this.show,
|
'looks_show': this.show,
|
||||||
'looks_hide': this.hide,
|
'looks_hide': this.hide,
|
||||||
|
'looks_backdrops': this.backdropMenu,
|
||||||
|
'looks_costume': this.costumeMenu,
|
||||||
|
'looks_switchcostumeto': this.switchCostume,
|
||||||
|
'looks_switchbackdropto': this.switchBackdrop,
|
||||||
|
'looks_switchbackdroptoandwait': this.switchBackdropAndWait,
|
||||||
|
'looks_nextcostume': this.nextCostume,
|
||||||
|
'looks_nextbackdrop': this.nextBackdrop,
|
||||||
'looks_effectmenu': this.effectMenu,
|
'looks_effectmenu': this.effectMenu,
|
||||||
'looks_changeeffectby': this.changeEffect,
|
'looks_changeeffectby': this.changeEffect,
|
||||||
'looks_seteffectto': this.setEffect,
|
'looks_seteffectto': this.setEffect,
|
||||||
'looks_cleargraphiceffects': this.clearEffects,
|
'looks_cleargraphiceffects': this.clearEffects,
|
||||||
'looks_changesizeby': this.changeSize,
|
'looks_changesizeby': this.changeSize,
|
||||||
'looks_setsizeto': this.setSize,
|
'looks_setsizeto': this.setSize,
|
||||||
'looks_size': this.getSize
|
'looks_size': this.getSize,
|
||||||
|
'looks_costumeorder': this.getCostumeIndex,
|
||||||
|
'looks_backdroporder': this.getBackdropIndex,
|
||||||
|
'looks_backdropname': this.getBackdropName
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -66,6 +78,103 @@ Scratch3LooksBlocks.prototype.hide = function (args, util) {
|
||||||
util.target.setVisible(false);
|
util.target.setVisible(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility function to set the costume or backdrop of a target.
|
||||||
|
* Matches the behavior of Scratch 2.0 for different types of arguments.
|
||||||
|
* @param {!Target} target Target to set costume/backdrop to.
|
||||||
|
* @param {Any} requestedCostume Costume requested, e.g., 0, 'name', etc.
|
||||||
|
* @param {boolean=} opt_zeroIndex Set to zero-index the requestedCostume.
|
||||||
|
* @return {Array.<!Thread>} Any threads started by this switch.
|
||||||
|
*/
|
||||||
|
Scratch3LooksBlocks.prototype._setCostumeOrBackdrop = function (target,
|
||||||
|
requestedCostume, opt_zeroIndex) {
|
||||||
|
if (typeof requestedCostume === 'number') {
|
||||||
|
target.setCostume(opt_zeroIndex ?
|
||||||
|
requestedCostume : requestedCostume - 1);
|
||||||
|
} else {
|
||||||
|
var costumeIndex = target.getCostumeIndexByName(requestedCostume);
|
||||||
|
if (costumeIndex > -1) {
|
||||||
|
target.setCostume(costumeIndex);
|
||||||
|
} else if (costumeIndex == 'previous costume' ||
|
||||||
|
costumeIndex == 'previous backdrop') {
|
||||||
|
target.setCostume(target.currentCostume - 1);
|
||||||
|
} else if (costumeIndex == 'next costume' ||
|
||||||
|
costumeIndex == 'next backdrop') {
|
||||||
|
target.setCostume(target.currentCostume + 1);
|
||||||
|
} else {
|
||||||
|
var forcedNumber = Cast.toNumber(requestedCostume);
|
||||||
|
if (!isNaN(forcedNumber)) {
|
||||||
|
target.setCostume(opt_zeroIndex ?
|
||||||
|
forcedNumber : forcedNumber - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (target == this.runtime.getTargetForStage()) {
|
||||||
|
// Target is the stage - start hats.
|
||||||
|
var newName = target.sprite.costumes[target.currentCostume].name;
|
||||||
|
return this.runtime.startHats('event_whenbackdropswitchesto', {
|
||||||
|
'BACKDROP': newName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
};
|
||||||
|
|
||||||
|
// @todo(GH-146): Remove.
|
||||||
|
Scratch3LooksBlocks.prototype.costumeMenu = function (args) {
|
||||||
|
return args.COSTUME;
|
||||||
|
};
|
||||||
|
|
||||||
|
Scratch3LooksBlocks.prototype.switchCostume = function (args, util) {
|
||||||
|
this._setCostumeOrBackdrop(util.target, args.COSTUME);
|
||||||
|
};
|
||||||
|
|
||||||
|
Scratch3LooksBlocks.prototype.nextCostume = function (args, util) {
|
||||||
|
this._setCostumeOrBackdrop(
|
||||||
|
util.target, util.target.currentCostume + 1, true
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// @todo(GH-146): Remove.
|
||||||
|
Scratch3LooksBlocks.prototype.backdropMenu = function (args) {
|
||||||
|
return args.BACKDROP;
|
||||||
|
};
|
||||||
|
|
||||||
|
Scratch3LooksBlocks.prototype.switchBackdrop = function (args) {
|
||||||
|
this._setCostumeOrBackdrop(this.runtime.getTargetForStage(), args.BACKDROP);
|
||||||
|
};
|
||||||
|
|
||||||
|
Scratch3LooksBlocks.prototype.switchBackdropAndWait = function (args, util) {
|
||||||
|
// Have we run before, starting threads?
|
||||||
|
if (!util.stackFrame.startedThreads) {
|
||||||
|
// No - switch the backdrop.
|
||||||
|
util.stackFrame.startedThreads = (
|
||||||
|
this._setCostumeOrBackdrop(
|
||||||
|
this.runtime.getTargetForStage(),
|
||||||
|
args.BACKDROP
|
||||||
|
)
|
||||||
|
);
|
||||||
|
if (util.stackFrame.startedThreads.length == 0) {
|
||||||
|
// Nothing was started.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// We've run before; check if the wait is still going on.
|
||||||
|
var instance = this;
|
||||||
|
var waiting = util.stackFrame.startedThreads.some(function(thread) {
|
||||||
|
return instance.runtime.isActiveThread(thread);
|
||||||
|
});
|
||||||
|
if (waiting) {
|
||||||
|
util.yieldFrame();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Scratch3LooksBlocks.prototype.nextBackdrop = function () {
|
||||||
|
var stage = this.runtime.getTargetForStage();
|
||||||
|
this._setCostumeOrBackdrop(
|
||||||
|
stage, stage.currentCostume + 1, true
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
Scratch3LooksBlocks.prototype.effectMenu = function (args) {
|
Scratch3LooksBlocks.prototype.effectMenu = function (args) {
|
||||||
return args.EFFECT.toLowerCase();
|
return args.EFFECT.toLowerCase();
|
||||||
};
|
};
|
||||||
|
@ -95,4 +204,18 @@ Scratch3LooksBlocks.prototype.getSize = function (args, util) {
|
||||||
return util.target.size;
|
return util.target.size;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Scratch3LooksBlocks.prototype.getBackdropIndex = function () {
|
||||||
|
var stage = this.runtime.getTargetForStage();
|
||||||
|
return stage.currentCostume + 1;
|
||||||
|
};
|
||||||
|
|
||||||
|
Scratch3LooksBlocks.prototype.getBackdropName = function () {
|
||||||
|
var stage = this.runtime.getTargetForStage();
|
||||||
|
return stage.sprite.costumes[stage.currentCostume].name;
|
||||||
|
};
|
||||||
|
|
||||||
|
Scratch3LooksBlocks.prototype.getCostumeIndex = function (args, util) {
|
||||||
|
return util.target.currentCostume + 1;
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = Scratch3LooksBlocks;
|
module.exports = Scratch3LooksBlocks;
|
||||||
|
|
|
@ -434,6 +434,19 @@ Runtime.prototype.getTargetById = function (targetId) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a target representing the Scratch stage, if one exists.
|
||||||
|
* @return {?Target} The target, if found.
|
||||||
|
*/
|
||||||
|
Runtime.prototype.getTargetForStage = function () {
|
||||||
|
for (var i = 0; i < this.targets.length; i++) {
|
||||||
|
var target = this.targets[i];
|
||||||
|
if (target.isStage) {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle an animation frame from the main thread.
|
* Handle an animation frame from the main thread.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -20,7 +20,8 @@ var specMap = require('./sb2specmap');
|
||||||
function sb2import (json, runtime) {
|
function sb2import (json, runtime) {
|
||||||
parseScratchObject(
|
parseScratchObject(
|
||||||
JSON.parse(json),
|
JSON.parse(json),
|
||||||
runtime
|
runtime,
|
||||||
|
true
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,8 +29,9 @@ function sb2import (json, runtime) {
|
||||||
* Parse a single "Scratch object" and create all its in-memory VM objects.
|
* Parse a single "Scratch object" and create all its in-memory VM objects.
|
||||||
* @param {!Object} object From-JSON "Scratch object:" sprite, stage, watcher.
|
* @param {!Object} object From-JSON "Scratch object:" sprite, stage, watcher.
|
||||||
* @param {!Runtime} runtime Runtime object to load all structures into.
|
* @param {!Runtime} runtime Runtime object to load all structures into.
|
||||||
|
* @param {boolean} topLevel Whether this is the top-level object (stage).
|
||||||
*/
|
*/
|
||||||
function parseScratchObject (object, runtime) {
|
function parseScratchObject (object, runtime, topLevel) {
|
||||||
if (!object.hasOwnProperty('objName')) {
|
if (!object.hasOwnProperty('objName')) {
|
||||||
// Watcher/monitor - skip this object until those are implemented in VM.
|
// Watcher/monitor - skip this object until those are implemented in VM.
|
||||||
// @todo
|
// @todo
|
||||||
|
@ -84,10 +86,11 @@ function parseScratchObject (object, runtime) {
|
||||||
if (object.currentCostumeIndex) {
|
if (object.currentCostumeIndex) {
|
||||||
target.currentCostume = object.currentCostumeIndex;
|
target.currentCostume = object.currentCostumeIndex;
|
||||||
}
|
}
|
||||||
|
target.isStage = topLevel;
|
||||||
// The stage will have child objects; recursively process them.
|
// The stage will have child objects; recursively process them.
|
||||||
if (object.children) {
|
if (object.children) {
|
||||||
for (var j = 0; j < object.children.length; j++) {
|
for (var j = 0; j < object.children.length; j++) {
|
||||||
parseScratchObject(object.children[j], runtime);
|
parseScratchObject(object.children[j], runtime, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,6 +50,12 @@ Clone.prototype.initDrawable = function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Clone-level properties.
|
// Clone-level properties.
|
||||||
|
/**
|
||||||
|
* Whether this clone represents the Scratch stage.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
Clone.prototype.isStage = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Scratch X coordinate. Currently should range from -240 to 240.
|
* Scratch X coordinate. Currently should range from -240 to 240.
|
||||||
* @type {Number}
|
* @type {Number}
|
||||||
|
@ -107,6 +113,9 @@ Clone.prototype.effects = {
|
||||||
* @param {!number} y New Y coordinate of clone, in Scratch coordinates.
|
* @param {!number} y New Y coordinate of clone, in Scratch coordinates.
|
||||||
*/
|
*/
|
||||||
Clone.prototype.setXY = function (x, y) {
|
Clone.prototype.setXY = function (x, y) {
|
||||||
|
if (this.isStage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.x = x;
|
this.x = x;
|
||||||
this.y = y;
|
this.y = y;
|
||||||
if (this.renderer) {
|
if (this.renderer) {
|
||||||
|
@ -121,6 +130,9 @@ Clone.prototype.setXY = function (x, y) {
|
||||||
* @param {!number} direction New direction of clone.
|
* @param {!number} direction New direction of clone.
|
||||||
*/
|
*/
|
||||||
Clone.prototype.setDirection = function (direction) {
|
Clone.prototype.setDirection = function (direction) {
|
||||||
|
if (this.isStage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Keep direction between -179 and +180.
|
// Keep direction between -179 and +180.
|
||||||
this.direction = MathUtil.wrapClamp(direction, -179, 180);
|
this.direction = MathUtil.wrapClamp(direction, -179, 180);
|
||||||
if (this.renderer) {
|
if (this.renderer) {
|
||||||
|
@ -136,6 +148,9 @@ Clone.prototype.setDirection = function (direction) {
|
||||||
* @param {?string} message Message to put in say bubble.
|
* @param {?string} message Message to put in say bubble.
|
||||||
*/
|
*/
|
||||||
Clone.prototype.setSay = function (type, message) {
|
Clone.prototype.setSay = function (type, message) {
|
||||||
|
if (this.isStage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// @todo: Render to stage.
|
// @todo: Render to stage.
|
||||||
if (!type || !message) {
|
if (!type || !message) {
|
||||||
console.log('Clearing say bubble');
|
console.log('Clearing say bubble');
|
||||||
|
@ -149,6 +164,9 @@ Clone.prototype.setSay = function (type, message) {
|
||||||
* @param {!boolean} visible True if the sprite should be shown.
|
* @param {!boolean} visible True if the sprite should be shown.
|
||||||
*/
|
*/
|
||||||
Clone.prototype.setVisible = function (visible) {
|
Clone.prototype.setVisible = function (visible) {
|
||||||
|
if (this.isStage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.visible = visible;
|
this.visible = visible;
|
||||||
if (this.renderer) {
|
if (this.renderer) {
|
||||||
this.renderer.updateDrawableProperties(this.drawableID, {
|
this.renderer.updateDrawableProperties(this.drawableID, {
|
||||||
|
@ -162,6 +180,9 @@ Clone.prototype.setVisible = function (visible) {
|
||||||
* @param {!number} size Size of clone, from 5 to 535.
|
* @param {!number} size Size of clone, from 5 to 535.
|
||||||
*/
|
*/
|
||||||
Clone.prototype.setSize = function (size) {
|
Clone.prototype.setSize = function (size) {
|
||||||
|
if (this.isStage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
// Keep size between 5% and 535%.
|
// Keep size between 5% and 535%.
|
||||||
this.size = MathUtil.clamp(size, 5, 535);
|
this.size = MathUtil.clamp(size, 5, 535);
|
||||||
if (this.renderer) {
|
if (this.renderer) {
|
||||||
|
@ -202,7 +223,10 @@ Clone.prototype.clearEffects = function () {
|
||||||
* @param {number} index New index of costume.
|
* @param {number} index New index of costume.
|
||||||
*/
|
*/
|
||||||
Clone.prototype.setCostume = function (index) {
|
Clone.prototype.setCostume = function (index) {
|
||||||
this.currentCostume = index;
|
// Keep the costume index within possible values.
|
||||||
|
this.currentCostume = MathUtil.wrapClamp(
|
||||||
|
index, 0, this.sprite.costumes.length - 1
|
||||||
|
);
|
||||||
if (this.renderer) {
|
if (this.renderer) {
|
||||||
this.renderer.updateDrawableProperties(this.drawableID, {
|
this.renderer.updateDrawableProperties(this.drawableID, {
|
||||||
skin: this.sprite.costumes[this.currentCostume].skin
|
skin: this.sprite.costumes[this.currentCostume].skin
|
||||||
|
@ -210,6 +234,20 @@ Clone.prototype.setCostume = function (index) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a costume index of this clone, by name of the costume.
|
||||||
|
* @param {?string} costumeName Name of a costume.
|
||||||
|
* @return {number} Index of the named costume, or -1 if not present.
|
||||||
|
*/
|
||||||
|
Clone.prototype.getCostumeIndexByName = function (costumeName) {
|
||||||
|
for (var i = 0; i < this.sprite.costumes.length; i++) {
|
||||||
|
if (this.sprite.costumes[i].name == costumeName) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update all drawable properties for this clone.
|
* Update all drawable properties for this clone.
|
||||||
* Use when a batch has changed, e.g., when the drawable is first created.
|
* Use when a batch has changed, e.g., when the drawable is first created.
|
||||||
|
|
Loading…
Reference in a new issue