mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-24 06:52:40 -05:00
Add say/think functionality to looks blocks
This commit is contained in:
parent
6087b5346c
commit
43a17bdaa8
1 changed files with 157 additions and 7 deletions
|
@ -1,4 +1,15 @@
|
||||||
const Cast = require('../util/cast');
|
const Cast = require('../util/cast');
|
||||||
|
const Clone = require('../util/clone');
|
||||||
|
|
||||||
|
const RenderedTarget = require('../sprites/rendered-target');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} BubbleState - the bubble state associated with a particular target.
|
||||||
|
* @property {Boolean} onSpriteRight - tracks whether the bubble is right or left of the sprite.
|
||||||
|
* @property {?int} drawableId - the ID of the associated bubble Drawable, null if none.
|
||||||
|
* @property {string} text - the text of the bubble.
|
||||||
|
* @property {string} type - the type of the bubble, "say" or "think"
|
||||||
|
*/
|
||||||
|
|
||||||
class Scratch3LooksBlocks {
|
class Scratch3LooksBlocks {
|
||||||
constructor (runtime) {
|
constructor (runtime) {
|
||||||
|
@ -7,6 +18,144 @@ class Scratch3LooksBlocks {
|
||||||
* @type {Runtime}
|
* @type {Runtime}
|
||||||
*/
|
*/
|
||||||
this.runtime = runtime;
|
this.runtime = runtime;
|
||||||
|
|
||||||
|
this._onTargetMoved = this._onTargetMoved.bind(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default bubble state, to be used when a target has no existing bubble state.
|
||||||
|
* @type {BubbleState}
|
||||||
|
*/
|
||||||
|
static get DEFAULT_BUBBLE_STATE () {
|
||||||
|
return {
|
||||||
|
drawableId: null,
|
||||||
|
onSpriteRight: true,
|
||||||
|
skinId: null,
|
||||||
|
text: '',
|
||||||
|
type: 'say'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The key to load & store a target's bubble-related state.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
static get STATE_KEY () {
|
||||||
|
return 'Scratch.looks';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Target} target - collect bubble state for this target. Probably, but not necessarily, a RenderedTarget.
|
||||||
|
* @returns {BubbleState} the mutable bubble state associated with that target. This will be created if necessary.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_getBubbleState (target) {
|
||||||
|
let bubbleState = target.getCustomState(Scratch3LooksBlocks.STATE_KEY);
|
||||||
|
if (!bubbleState) {
|
||||||
|
bubbleState = Clone.simple(Scratch3LooksBlocks.DEFAULT_BUBBLE_STATE);
|
||||||
|
target.setCustomState(Scratch3LooksBlocks.STATE_KEY, bubbleState);
|
||||||
|
}
|
||||||
|
return bubbleState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Target} target - collect bubble state for this target. Probably, but not necessarily, a RenderedTarget.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_resetBubbleState (target) {
|
||||||
|
const defaultBubbleState = Clone.simple(Scratch3LooksBlocks.DEFAULT_BUBBLE_STATE);
|
||||||
|
target.setCustomState(Scratch3LooksBlocks.STATE_KEY, defaultBubbleState);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a target which has moved. This only fires when the bubble is visible.
|
||||||
|
* @param {RenderedTarget} target - the target which has moved.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_onTargetMoved (target) {
|
||||||
|
const bubbleState = this._getBubbleState(target);
|
||||||
|
|
||||||
|
if (bubbleState.drawableId) {
|
||||||
|
this._checkBubbleBounds(target);
|
||||||
|
this._positionBubble(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_positionBubble (target) {
|
||||||
|
const bubbleState = this._getBubbleState(target);
|
||||||
|
const [bubbleWidth, bubbleHeight] = this.runtime.renderer.getSkinSize(bubbleState.drawableId);
|
||||||
|
const targetBounds = target.getBounds();
|
||||||
|
const stageBounds = this.runtime.getTargetForStage().getBounds();
|
||||||
|
|
||||||
|
this.runtime.renderer.updateDrawableProperties(bubbleState.drawableId, {
|
||||||
|
position: [
|
||||||
|
bubbleState.onSpriteRight ? targetBounds.right : targetBounds.left - bubbleWidth,
|
||||||
|
Math.min(stageBounds.top, targetBounds.top + bubbleHeight)
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
this.runtime.requestRedraw();
|
||||||
|
}
|
||||||
|
|
||||||
|
_checkBubbleBounds (target) {
|
||||||
|
const bubbleState = this._getBubbleState(target);
|
||||||
|
const [bubbleWidth, _] = this.runtime.renderer.getSkinSize(bubbleState.drawableId);
|
||||||
|
const targetBounds = target.getBounds();
|
||||||
|
const stageBounds = this.runtime.getTargetForStage().getBounds();
|
||||||
|
if (bubbleState.onSpriteRight && bubbleWidth + targetBounds.right > stageBounds.right) {
|
||||||
|
bubbleState.onSpriteRight = false;
|
||||||
|
this._renderBubble(target);
|
||||||
|
} else if (!bubbleState.onSpriteRight && targetBounds.left - bubbleWidth < stageBounds.left) {
|
||||||
|
bubbleState.onSpriteRight = true;
|
||||||
|
this._renderBubble(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_renderBubble (target) {
|
||||||
|
const bubbleState = this._getBubbleState(target);
|
||||||
|
const {type, text, onSpriteRight} = bubbleState;
|
||||||
|
|
||||||
|
if (bubbleState.skinId) {
|
||||||
|
this.runtime.renderer.updateTextSkin(bubbleState.skinId, type, text, onSpriteRight, [0, 0]);
|
||||||
|
} else {
|
||||||
|
target.addListener(RenderedTarget.EVENT_TARGET_MOVED, this._onTargetMoved);
|
||||||
|
|
||||||
|
bubbleState.drawableId = this.runtime.renderer.createDrawable();
|
||||||
|
bubbleState.skinId = this.runtime.renderer.createTextSkin(type, text, bubbleState.onSpriteRight, [0, 0]);
|
||||||
|
|
||||||
|
const order = this.runtime.renderer.getDrawableOrder(target.drawableID);
|
||||||
|
this.runtime.renderer.setDrawableOrder(bubbleState.drawableId, order + 1);
|
||||||
|
this.runtime.renderer.updateDrawableProperties(bubbleState.drawableId, {
|
||||||
|
skinId: bubbleState.skinId
|
||||||
|
});
|
||||||
|
|
||||||
|
this._checkBubbleBounds(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._positionBubble(target);
|
||||||
|
}
|
||||||
|
_updateBubble (target, type, text) {
|
||||||
|
const bubbleState = this._getBubbleState(target);
|
||||||
|
bubbleState.type = type;
|
||||||
|
bubbleState.text = text;
|
||||||
|
|
||||||
|
this._renderBubble(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
_clearBubble (target) {
|
||||||
|
const bubbleState = this._getBubbleState(target);
|
||||||
|
if (bubbleState.drawableId) {
|
||||||
|
this.runtime.renderer.destroyDrawable(bubbleState.drawableId);
|
||||||
|
}
|
||||||
|
if (bubbleState.drawableId) {
|
||||||
|
this.runtime.renderer.destroySkin(bubbleState.skinId);
|
||||||
|
}
|
||||||
|
this._resetBubbleState(target);
|
||||||
|
|
||||||
|
// @TODO is this safe? It could have been already removed?
|
||||||
|
target.removeListener(RenderedTarget.EVENT_TARGET_MOVED, this._onTargetMoved);
|
||||||
|
|
||||||
|
this.runtime.requestRedraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -18,7 +167,7 @@ class Scratch3LooksBlocks {
|
||||||
looks_say: this.say,
|
looks_say: this.say,
|
||||||
looks_sayforsecs: this.sayforsecs,
|
looks_sayforsecs: this.sayforsecs,
|
||||||
looks_think: this.think,
|
looks_think: this.think,
|
||||||
looks_thinkforsecs: this.sayforsecs,
|
looks_thinkforsecs: this.thinkforsecs,
|
||||||
looks_show: this.show,
|
looks_show: this.show,
|
||||||
looks_hide: this.hide,
|
looks_hide: this.hide,
|
||||||
looks_switchcostumeto: this.switchCostume,
|
looks_switchcostumeto: this.switchCostume,
|
||||||
|
@ -41,30 +190,31 @@ class Scratch3LooksBlocks {
|
||||||
}
|
}
|
||||||
|
|
||||||
say (args, util) {
|
say (args, util) {
|
||||||
util.target.setSay('say', args.MESSAGE);
|
// @TODO in 2.0 calling say/think resets the right/left bias of the bubble
|
||||||
|
this._updateBubble(util.target, 'say', args.MESSAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
sayforsecs (args, util) {
|
sayforsecs (args, util) {
|
||||||
util.target.setSay('say', args.MESSAGE);
|
this.say(args, util);
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// Clear say bubble and proceed.
|
// Clear say bubble and proceed.
|
||||||
util.target.setSay();
|
this._clearBubble(util.target);
|
||||||
resolve();
|
resolve();
|
||||||
}, 1000 * args.SECS);
|
}, 1000 * args.SECS);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
think (args, util) {
|
think (args, util) {
|
||||||
util.target.setSay('think', args.MESSAGE);
|
this._updateBubble(util.target, 'think', args.MESSAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
thinkforsecs (args, util) {
|
thinkforsecs (args, util) {
|
||||||
util.target.setSay('think', args.MESSAGE);
|
this.think(args, util);
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
// Clear say bubble and proceed.
|
// Clear say bubble and proceed.
|
||||||
util.target.setSay();
|
this._clearBubble(util.target);
|
||||||
resolve();
|
resolve();
|
||||||
}, 1000 * args.SECS);
|
}, 1000 * args.SECS);
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue