2014-04-02 34 views
0

我正在JavaScript和HTML5画布中制作RPG。对于对话框,我添加了一个“打字”效果,就好像NPC的对话框自动输出一样(逐字母)。如何确保递归函数在返回之前不会再次调用

当用户按下按钮时会触发此功能。当函数遍历字符串长度时,在给定索引处附加每个字符,我不希望用户能够再次按下按钮,直到函数停止(到达字符串的结尾索引)。

为此,我在函数重复时添加了$("#responseButton").prop('disabled', true);,并且在索引匹配对话框string.length时添加了$("#responseButton").prop('disabled', false);

问题:看来,预期该按钮被禁用,而功能再次出现,但只要它这样做,我可以继续点击启用按钮...功能好像是叫在每个数次按下按钮的时间...即使先前调用的再次发生尚未完成...这导致文本不断被覆盖。

问题:如何确保函数在重新调用之前重复执行?这就像是我有一个滞后时间,如果我继续点击按钮,直到它再次运行...

typeEffect : function (index) {  
    if (index >= game.data.NPCdialog.length) { 
     game.data.enableCycle = true; 
     $("#responseButton").prop('disabled', false); 
     return; 
    } 
    else{ 
     $("#responseButton").prop('disabled', true); //wait until typing is done before user can press again 
     $("#npc_dialog").append(game.data.NPCdialog.charAt(index++)); 
     var t = setTimeout('Utilities.typeEffect('+ (index) +')', 40);  
    }  
}, 

回答

1

Javascript是单线程。除非您故意放弃控制,否则不能调用其他Javascript函数。

编辑

诺加德写道:

想必大家看到的setTimeout,有

我没有看到setTimeout和别再叫我雪莉。

我认为他的计划是可行的,只是在处理程序被解雇之前有几个事件正在排队。

咆哮者,我认为你遇到的问题是,你认为处理程序将被解雇,而按钮被禁用后,第一次点击,因此不会产生后续事件。我相信这不是正确的。

相反,试试这个:

typeEffect : function() { 
    var enabled = true; 

    var process = function(index) { 
    if (index >= game.data.NPCdialog.length) { 
     game.data.enableCycle = true; 
     enabled = true; 
     $("#responseButton").prop('disabled', false); 
    } 
    else { 
     $("#npc_dialog").append(game.data.NPCdialog.charAt(index)); 
     setTimeout(function() { 
     process(index+1); 
     }, 40); 
    } 
    }; 

    return function(index) {  
    if (enabled) { 
     $("#responseButton").prop('disabled', true); 
     process(index); 
    } 
    }; 
}(), 
+0

当然你会看到setTimeout,那里。 – Norguard

+0

...看起来像我选错了一周,放弃嗅探胶水。也就是说,我相信它会是'.charAt(index)',然后'process(index + 1)',因为解析后缀的时候。 '.charAt(++ index)'是'.charAt(index + 1)'。 – Norguard

+0

@Norguard - 谢谢,修正。 – Malvolio

1

的问题是,setTimeout的马上,然后返回你的代码继续。

您需要一个在超时后调用自身并检查是否应该启用该按钮的函数。

这里有一个演示: http://cdpn.io/Idypu

+0

由于某种原因,这仍然不能解决我的问题。我可以继续点击按钮后,它再次启用,并有一个延迟,它一遍又一遍地运行呼叫 – Growler

1

正如已经提到的,在咬你的部分是事件的反应,异步以及可能不是你会如何以及何时期待踢递归函数的组合。它更进一步,但这足以说明出现问题的地方,而无需进入JS之间的元编程和浏览器引擎之间的元编程。

现在,其他答案已经告诉你为什么它不会按照你的想法工作,我建议问题的根源在于你试图管理应该发生一次的事情(切换按钮),可能会发生一百次,如果你写了更多的代码并将其翻过来,可视化起来会更容易。

或许重构,看起来有点像这样,可能会使事情变得更简单:

var button = { 
    el : $("#responseButton"), // cache your references 
    setDisabled : function (state) { button.el.prop("disabled", state); }, 
    enable : function() { button.setDisabled(false); }, 
    disable : function() { button.setDisabled(true); }, 
    handleClick : function() { 
     button.disable(); 
     Utilities.typeEffect(0, function() { button.enable(); }); 
    } 
}, 

dialogBox = { 
    // if you're going to call this 100x in a row, cache it 
    el  : $("#npc_dialog"), 
    append : function (text) { dialogBox.el.append(text); } 
}, 

sec = 1000, 
textSpeed = { 
    fast : 1/25 * sec, // 25x /sec 
    medium : 1/10 * sec, // 10x /sec 
    slow : 1/ 5 * sec // 5x /sec 
}; 

button.el.on("click", button.handleClick); 



Utilities.typeEffect = function (index, done) { 
    if (index >= game.data.NPCdialog.length) { 
     game.data.enableCycle = true; 
     return done(); 
    } else { 
     var character = game.data.NPCdialog[index]; 
     dialogBox.append(character); 
     setTimeout(function() { 
      Utilities.typeEffect(index+1, done); 
     }, textSpeed.fast); 
    } 
}; 

你应该注意到,我改变了实际.typeEffect的非常非常小的内部。
做了什么做的是快速构建一个快速的button抽象,它包含对元素的缓存引用(不要让jQuery在页面上查找它,每秒25x),添加一些quick'n'dirty帮助方法......然后我给出了事件处理的按钮控件,包括知道如何清理的时间。

如果你会发现,我给.typeEffect一个参数,done,和我通过它的参数是由button定义的函数,它知道如何清理自己,当一切都完了。该函数并没有更大,然而,我现在通过传递回调来执行输入阻塞/解除阻塞一次。

更好的是,我可以保证它在递归开始之前就被阻塞了,并且在最后一次递归发生之后它就被解除阻塞,没有任何不明确的状态阻碍。

我也缓存了对话框,重新加快了这个过程;
这些天,jQuery确实有奇迹,但这仍然应该被缓存。

如果我把它进一步,我可能会做typeEffect一个对话框,书写组件插件,使几种不同类型的影响可能在被丢弃。
我可能也开始传递递归运行它所关心的其他参数(文本速度,参考对话框等),以便它们更易于交换,并且该函数变得更加可重用(并且更容易看到它的工作原理)。

请注意,这不是真的答案。这是解决方案,它不仅解决问题,而且说明未来如何预防问题。
如果可行,请使用它,如果不行,请勿使用。
如果这个概念有效,但它不是你的风格,然后改变它(尽管如此,避免使用setTimeout中的字符串......应该在几年前停止教学;如果你是性能杀手,安全危害在用户创建的字符串中混搭,容易造成非严格的eval-context错误......所有的好东西)。

+0

诺芮特,我怎么能模拟打字效果没有setTimeout延迟? – Growler

+0

@Growler需要'setTimeout',或者'setImmediate'或'requestAnimationFrame',以便在JavaScript中获得所需的效果(其他解决方案将完全锁定浏览器窗口)。你应该避免的是内部的字符串:'setTimeout(“doStuff(data);”,50);'。相反,传递一个函数:'setTimeout(function(){doStuff(data);},50);'。它更快,更安全,更可预测。 – Norguard