我正在使用Blockly构建一个海龟图形应用程序。用户可以从块构建代码,然后Blockly引擎生成JS代码,该代码将绘制到画布上。
Blockly引擎生成JS代码,但将其作为字符串返回,我必须eval()
绘制到画布。
我可以更改块的代码以生成不同的输出,但保持它尽可能简单非常重要,因为用户可以读取块输入后面的实际代码。所以我不想搞砸它。
我可以完全控制原子操作(go
,turn
等),所以我想在函数的开头插入一小段代码,这会延迟执行功能的其余部分。类似的东西:
function go(dir, dist) {
// wait here a little
// do the drawing
}
我认为它应该是同步的东西,它可以保持执行流程的延迟。我尝试使用setTimeout
(异步,失败),promise
(失败),时间戳检查循环(失败)。
甚至可以在JS中使用吗?
答案 0 :(得分:3)
您不能让代码同步等待。你唯一能得到的就是一个冻结的浏览器窗口。
您需要的是使用js interpreter而不是eval。通过这种方式,您可以暂停执行,播放动画,突出显示当前正在执行的块等...本教程有许多示例可帮助您入门。这是一个基于JS interpreter example:
的工作代码
var workspace = Blockly.inject("editor-div", {
toolbox: document.getElementById('toolbox')
});
Blockly.JavaScript.STATEMENT_PREFIX = 'highlightBlock(%1);\n';
Blockly.JavaScript.addReservedWords('highlightBlock');
Blockly.JavaScript['text_print'] = function(block) {
var argument0 = Blockly.JavaScript.valueToCode(
block, 'TEXT',
Blockly.JavaScript.ORDER_FUNCTION_CALL
) || '\'\'';
return "print(" + argument0 + ');\n';
};
function run() {
var code = Blockly.JavaScript.workspaceToCode(workspace);
var running = false;
workspace.traceOn(true);
workspace.highlightBlock(null);
var lastBlockToHighlight = null;
var myInterpreter = new Interpreter(code, (interpreter, scope) => {
interpreter.setProperty(
scope, 'highlightBlock',
interpreter.createNativeFunction(id => {
id = id ? id.toString() : '';
running = false;
workspace.highlightBlock(lastBlockToHighlight);
lastBlockToHighlight = id;
})
);
interpreter.setProperty(
scope, 'print',
interpreter.createNativeFunction(val => {
val = val ? val.toString() : '';
console.log(val);
})
);
});
var intervalId = setInterval(() => {
running = true;
while (running) {
if (!myInterpreter.step()) {
workspace.highlightBlock(lastBlockToHighlight);
clearInterval(intervalId);
return;
}
}
}, 500);
}

#editor-div {
width: 500px;
height: 150px;
}

<script src="https://rawgit.com/google/blockly/master/blockly_compressed.js"></script>
<script src="https://rawgit.com/google/blockly/master/blocks_compressed.js"></script>
<script src="https://rawgit.com/google/blockly/master/javascript_compressed.js"></script>
<script src="https://rawgit.com/google/blockly/master/msg/js/en.js"></script>
<script src="https://rawgit.com/NeilFraser/JS-Interpreter/master/acorn_interpreter.js"></script>
<xml id="toolbox" style="display: none">
<block type="text"></block>
<block type="text_print"></block>
<block type="controls_repeat_ext"></block>
<block type="math_number"></block>
</xml>
<div>
<button id="run-code" onclick="run()">run</button>
</div>
<div id="editor-div"></div>
&#13;
修改强>
添加了变量running
来控制解释器。现在它逐步执行,直到running
变量设置为false,因此highlightBlock函数内的running = false
语句基本上用作断点。
修改强>
引入lastBlockToHighlight
变量以延迟突出显示,因此突出显示最新的运行语句,而不是下一个。很遗憾,JavaScript代码生成器没有类似于STATEMENT_SUFFIX
的{{1}}配置。
答案 1 :(得分:2)
最近,我发布了一个库,允许您异步进行块式交互,我为此类游戏设计了该库。 实际上,在文档中,您可以找到一个maze游戏翻版的游戏演示。 该库名为 gamepad.js?,我希望它就是您想要的。
这是演示的gif图像。
与常规使用分组方式相比,这是一种不同且简化的方法。
首先,您必须定义块 (请参见如何在documentation中定义它们)。
您不必定义any code generator
,所有与代码生成有关的事情都由库执行。
每个块都会生成一个请求。
// the request
{ method: 'TURN', args: ['RIGHT'] }
当执行块时,相应的请求将传递到您的游戏。
class Game{
manageRequests(request){
// requests are passed here
if(request.method == 'TURN')
// animate your sprite
turn(request.args)
}
}
您可以根据情况使用 promise (承诺)来管理异步动画。
class Game{
async manageRequests(request){
if(request.method == 'TURN')
await turn(request.args)
}
}
方块与游戏之间的链接由游戏手柄管理。
let gamepad = new Blockly.Gamepad(),
game = new Game()
// requests will be passed here
gamepad.setGame(game, game.manageRequest)
游戏手柄提供了一些方法来管理块执行,并因此请求生成。
// load the code from the blocks in the workspace
gamepad.load()
// reset the code loaded previously
gamepad.reset()
// the blocks are executed one after the other
gamepad.play()
// play in reverse
gamepad.play(true)
// the blocks execution is paused
gamepad.pause()
// toggle play
gamepad.togglePlay()
// load the next request
gamepad.forward()
// load the prior request
gamepad.backward()
// use a block as a breakpoint and play until it is reached
gamepad.debug(id)
您可以阅读完整的文档here。
答案 2 :(得分:1)
您可以构建一个新类来处理 go(dir,dist) 函数的执行,并覆盖go函数以创建新的 执行者中的 。
function GoExecutor(){
var executeArray = []; // Store go methods that waiting for execute
var isRunning = false; // Handle looper function
// start runner function
var run = function(){
if(isRunning)
return;
isRunning = true;
runner();
}
// looper for executeArray
var runner = function(){
if(executeArray.length == 0){
isRunning = false;
return;
}
// pop the first inserted params
var currentExec = executeArray.shift(0);
// wait delay miliseconds
setTimeout(function(){
// execute the original go function
originalGoFunction(currentExec.dir, currentExec.dist);
// after finish drawing loop on the next execute method
runner();
}, currentExec.delay);
}
this.push = function(dir, dist){
executeArray.push([dir,dist]);
run();
}
}
// GoExecutor instance
var goExec = new GoExecutor();
// Override go function
var originalGoFunction = go;
var go = function (dir, dist, delay){
goExec.push({"dir":dir, "dist":dist, "delay":delay});
}
现在你必须用你的函数和参数调用 callWithDelay , 执行程序将通过将params应用于指定的函数来处理此调用。
function GoExecutor(){
var executeArray = []; // Store go methods that waiting for execute
var isRunning = false; // Handle looper function
// start runner function
var run = function(){
if(isRunning)
return;
isRunning = true;
runner();
}
// looper for executeArray
var runner = function(){
if(executeArray.length == 0){
isRunning = false;
return;
}
// pop the first inserted params
var currentExec = executeArray.shift(0);
// wait delay miliseconds
setTimeout(function(){
// execute the original go function
currentExec.funcNam.apply(currentExec.funcNam, currentExec.arrayParams);
// after finish drawing loop on the next execute method
runner();
}, currentExec.delay);
}
this.push = function(dir, dist){
executeArray.push([dir,dist]);
run();
}
}
// GoExecutor instance
var goExec = new GoExecutor();
var callWithDelay = function (func, arrayParams, delay){
goExec.push({"func": func, "arrayParams":arrayParams, "delay":delay});
}