事件和帽子积木
事件积木和帽子积木是两种用于控制脚本运行时机的积木类型。尽管它们看起来相似,但本质不同。
事件积木
事件积木用于响应外部事件运行脚本。例如“当绿旗被点击”——事件就是用户点击绿旗按钮的那一刻。“当此角色被点击”类似地是响应点击事件而运行的,尽管 Scratch 需要做更多工作来确定事件发生在哪个角色上。事件积木可用于任何允许你运行回调的外部事件。
事件积木仅在非沙盒化扩展中支持。
事件积木本身永远不会被执行;它们只是标记哪些脚本应该运行。例如,考虑这个你熟悉的积木:
当你运行这个积木时,它本身不会做任何事情。它只是表示其下方的内容应该被运行。我们可以使用 Scratch.BlockType.EVENT 创建自己的具有类似功能的事件积木:
(function(Scratch) {
'use strict';
if (!Scratch.extensions.unsandboxed) {
throw new Error('This example must run unsandboxed');
}
class WhenSpaceKeyPressed {
getInfo() {
return {
id: 'eventexampleunsandboxed',
name: 'Event Block Example',
blocks: [
{
blockType: Scratch.BlockType.EVENT,
opcode: 'whenSpacePressed',
text: 'when space key pressed',
isEdgeActivated: false // required boilerplate
}
]
};
}
// Notice: whenSpacePressed does not have a function defined!
}
document.addEventListener('keydown', (e) => {
if (e.key === ' ') {
Scratch.vm.runtime.startHats('eventexampleunsandboxed_whenSpacePressed');
}
});
Scratch.extensions.register(new WhenSpaceKeyPressed());
})(Scratch);
我们稍后会讨论 isEdgeActivated: false 的含义,但对于事件积木来说,这是必需的样板代码。
在此示例中,我们定义了 opcode whenSpacePressed,但是因为它是事件积木,我们不需要为它编写任何代码。
要启动事件积木,你需要使用 startHats。有两个版本:
Scratch.vm.runtime.startHats应该在积木外部使用(例如,点击绿旗)util.startHats应该在积木内部使用(例如,广播积木)
在积木内部使用 Scratch.vm.runtime.startHats 而不是 util.startHats 可能会破坏脚本执行。
参数和返回值完全相同。传递给 startHats 的第一个参数是完整的积木 opcode,即 extensionid_opcode。在此示例中为 eventexampleunsandboxed_whenSpacePressed。这将启动运行项目中所有顶部积木为此 opcode 的脚本。
在此示例中,我们使用了 keydown 事件,但你可以使用任何你想要的事件。尝试改用 click 事件,或 setTimeout/setInterval、fetch() 等其他 API。只要你能获得回调,这就会起作用。
按菜单筛选
你可能会注意到 Scratch 的内置“当按键被按下”积木有一个菜单。使用我们当前的扩展,我们需要为每个键添加一个新积木。这不理想。相反,我们也可以使用菜单。
(function(Scratch) {
'use strict';
if (!Scratch.extensions.unsandboxed) {
throw new Error('This example must run unsandboxed');
}
class WhenKeyPressed {
getInfo() {
return {
id: 'eventexample2unsandboxed',
name: 'Event Block Example 2',
blocks: [
{
blockType: Scratch.BlockType.EVENT,
opcode: 'whenPressed',
text: 'when [KEY] key pressed',
isEdgeActivated: false, // required boilerplate
arguments: {
KEY: {
type: Scratch.ArgumentType.STRING,
menu: 'key'
}
}
}
],
menus: {
key: {
acceptReporters: false,
items: [
{
// startHats filters by *value*, not by text
text: 'space',
value: ' '
},
'a',
'b',
'c',
// ...
]
}
}
};
}
}
document.addEventListener('keydown', (e) => {
Scratch.vm.runtime.startHats('eventexample2unsandboxed_whenPressed', {
KEY: e.key
});
});
Scratch.extensions.register(new WhenKeyPressed());
})(Scratch);
积木的定义与其他积木类似。请注意,事件积木仅支持字段菜单。你不能有文本输入或放置积木的地方。要按参数筛选,它必须是一个 acceptReporters: false 的菜单。(稍后讨论的积木类型对此更宽松。)
startHats 的第一个参数再次是完整的 opcode。startHats 的第二个参数是一个对象,用于筛选要激活的事件。此对象中的键名是积木的参数名称(在此示例中为 KEY),值对应于菜单的值(不是文本!)。如果你想按多个键筛选,所有键都必须匹配。
Scratch 中真正的“当按键被按下”积木比这个复杂一点。这只是一个例子。
按角色筛选
Scratch 的“当此角色被点击”积木只在一个角色上运行,而不是每个角色上。要自己做到这一点,你可以使用 startHats 的第三个(也是最后一个)参数。第三个参数可以设置为目标对象——每个角色或克隆都是一个“目标”。如果设置,只有该目标中的事件积木会运行。
获取目标对象的最常见方法是:
Scratch.vm.runtime.getTargetForStage()获取舞台目标Scratch.vm.runtime.getSpriteTargetByName("Sprite1")获取具有给定名称的非克隆目标Scratch.vm.runtime.targets获取完整列表供你自己搜索
在此示例中,我们修改了之前的扩展,使其仅在舞台中运行积木。
(function(Scratch) {
'use strict';
if (!Scratch.extensions.unsandboxed) {
throw new Error('This example must run unsandboxed');
}
class WhenKeyPressedInStage {
getInfo() {
return {
id: 'eventexample3unsandboxed',
name: 'Event Block Example 3',
blocks: [
{
blockType: Scratch.BlockType.EVENT,
opcode: 'whenPressed',
text: 'when [KEY] key pressed',
isEdgeActivated: false,
arguments: {
KEY: {
type: Scratch.ArgumentType.STRING,
menu: 'key'
}
}
}
],
menus: {
key: {
acceptReporters: false,
items: [
{
text: 'space',
value: ' '
},
'a',
'b',
'c',
// ...
]
}
}
};
}
}
document.addEventListener('keydown', (e) => {
Scratch.vm.runtime.startHats('eventexample3unsandboxed_whenPressed', {
KEY: e.key
}, Scratch.vm.runtime.getTargetForStage());
});
Scratch.extensions.register(new WhenKeyPressedInStage());
})(Scratch);
要仅按角色筛选而不是按字段筛选,可以将第二个参数设置为 null 或空对象 ({})。
重新启动现有线程
考虑这个脚本:
你可能会观察到,如果你反复按空格键,脚本不会重新启动(这会重置等待积木计时器)——它只是继续执行。如果这不是你想要的,请在积木上设置 shouldRestartExistingThreads: true。
(function(Scratch) {
'use strict';
if (!Scratch.extensions.unsandboxed) {
throw new Error('This example must run unsandboxed');
}
class WhenKeyPressed {
getInfo() {
return {
id: 'restartexampleunsandboxed',
name: 'Restart Threads Example',
blocks: [
{
blockType: Scratch.BlockType.EVENT,
opcode: 'whenPressed',
text: 'when [KEY] key pressed',
isEdgeActivated: false,
shouldRestartExistingThreads: true,
arguments: {
KEY: {
type: Scratch.ArgumentType.STRING,
menu: 'key'
}
}
}
],
menus: {
key: {
acceptReporters: false,
items: [
{
text: 'space',
value: ' '
},
'a',
'b',
'c',
// ...
]
}
}
};
}
}
document.addEventListener('keydown', (e) => {
Scratch.vm.runtime.startHats('restartexampleunsandboxed_whenPressed', {
KEY: e.key
});
});
Scratch.extensions.register(new WhenKeyPressed());
})(Scratch);
如果你重新创建相同的脚本,只要你反复按空格键,说积木将永远不会执行,因为脚本每次都会重新启动,这会重置等待积木计时器。
请注意,如果顶部积木具有 shouldRestartExistingThreads: true 的脚本运行时调用 startHats 本身(类似于“当我收到 message1:广播 message1”),当前运行的脚本将被标记为重新启动,但会继续运行积木直到它产生。
启动的线程列表
最后,startHats 返回它启动的 Thread 对象数组。你可以使用它来监控线程状态,确定启动了多少线程等。
(function(Scratch) {
'use strict';
class Broadcast5 {
getInfo() {
return {
id: 'broadcast5example',
name: 'Broadcast Example 5',
blocks: [
{
opcode: 'whenReceived',
blockType: Scratch.BlockType.HAT,
text: 'when I receive [EVENT_OPTION]',
isEdgeActivated: false,
arguments: {
EVENT_OPTION: {
type: Scratch.ArgumentType.STRING,
menu: 'EVENT_FIELD'
}
}
},
{
opcode: 'broadcast',
blockType: Scratch.BlockType.REPORTER,
text: 'broadcast [EVENT]',
arguments: {
EVENT: {
type: Scratch.ArgumentType.STRING,
menu: 'EVENT_FIELD'
}
}
}
],
menus: {
EVENT_FIELD: {
acceptReporters: false,
items: [
'Event 1',
'Event 2',
'Event 3'
]
}
}
};
}
broadcast({EVENT, TARGET}, util) {
const threads = util.startHats('broadcast5example_whenReceived', {
EVENT_OPTION: EVENT
});
return `Started ${threads.length} new threads!`;
}
}
Scratch.extensions.register(new Broadcast5());
}(Scratch));