2014-04-02 38 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